Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "gardal"
version = "0.0.1-alpha.4"
version = "0.0.1-alpha.5"
edition = "2024"
license = "Apache-2.0 OR MIT"
authors = ["Ahmed Farghal <me@asoli.dev>"]
Expand Down
34 changes: 17 additions & 17 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,11 +40,11 @@ gardal = { version = "0.0.1-alpha.4", features = ["async", "tokio-hrtime"] }
### Basic Usage

```rust
use gardal::{RateLimit, TokenBucket};
use gardal::{Limit, TokenBucket};
use nonzero_ext::nonzero;

// Create a token bucket: 10 tokens per second, burst of 20
let bucket = TokenBucket::new(RateLimit::per_second_and_burst(
let bucket = TokenBucket::new(Limit::per_second_and_burst(
nonzero!(10u32),
nonzero!(20u32),
));
Expand All @@ -60,20 +60,20 @@ match bucket.consume(nonzero!(5u32)) {

```rust
use futures::{StreamExt, stream};
use gardal::futures::RateLimitedStreamExt;
use gardal::{RateLimit, TokenBucket, AtomicSharedStorage, QuantaClock};
use gardal::futures::StreamExt as GardalStreamExt;
use gardal::{Limit, TokenBucket, AtomicSharedStorage, QuantaClock};
use nonzero_ext::nonzero;

#[tokio::main]
async fn main() {
let limit = RateLimit::per_second(nonzero!(5u32));
let limit = Limit::per_second(nonzero!(5u32));
let bucket = TokenBucket::<AtomicSharedStorage, _>::from_parts(
limit,
QuantaClock::default()
);

let mut stream = stream::iter(1..=100)
.rate_limit(bucket)
.throttle(bucket)
.boxed();

while let Some(item) = stream.next().await {
Expand All @@ -89,20 +89,20 @@ If you want to have an unlimited stream in a type-compatible way, you can pass `
Gardal supports various rate limit configurations:

```rust
use gardal::RateLimit;
use gardal::Limit;
use nonzero_ext::nonzero;

// 10 requests per second
let limit = RateLimit::per_second(nonzero!(10u32));
let limit = Limit::per_second(nonzero!(10u32));

// 10 requests per second with burst of 20
let limit = RateLimit::per_second_and_burst(nonzero!(10u32), nonzero!(20u32));
let limit = Limit::per_second_and_burst(nonzero!(10u32), nonzero!(20u32));

// 100 requests per minute
let limit = RateLimit::per_minute(nonzero!(100u32));
let limit = Limit::per_minute(nonzero!(100u32));

// 1000 requests per hour
let limit = RateLimit::per_hour(nonzero!(1000u32));
let limit = Limit::per_hour(nonzero!(1000u32));
```

## Storage Strategies
Expand All @@ -115,12 +115,12 @@ Choose the appropriate storage strategy for your use case:
- **`LocalStorage`**: Thread-local storage for single-threaded applications

```rust
use gardal::{TokenBucket, AtomicSharedStorage, RateLimit};
use gardal::{TokenBucket, AtomicSharedStorage, Limit};
use nonzero_ext::nonzero;

// Explicitly specify storage type
let bucket = TokenBucket::<AtomicSharedStorage>::from_parts(
RateLimit::per_second(nonzero!(10u32)),
Limit::per_second(nonzero!(10u32)),
gardal::StdClock::default()
);
```
Expand All @@ -140,17 +140,17 @@ For applications requiring precise timing in async contexts, enable the `tokio-h

```rust
use futures::{StreamExt, stream};
use gardal::futures::RateLimitedStreamExt;
use gardal::{RateLimit, TokenBucket};
use gardal::futures::StreamExt as GardalStreamExt;
use gardal::{Limit, TokenBucket};
use nonzero_ext::nonzero;

#[tokio::main]
async fn main() {
let limit = RateLimit::per_second(nonzero!(1000u32)); // High-frequency rate limiting
let limit = Limit::per_second(nonzero!(1000u32)); // High-frequency rate limiting
let bucket = TokenBucket::new(limit);

let mut stream = stream::iter(1..=10000)
.rate_limit(bucket)
.throttle(bucket)
.boxed();

// Uses tokio-hrtime for microsecond-precision delays
Expand Down
10 changes: 5 additions & 5 deletions benches/throughput.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,14 @@ use std::time::Duration;

use criterion::{Criterion, Throughput, criterion_group, criterion_main};
use gardal::{
AtomicStorage, FastClock, LocalStorage, ManualClock, PaddedAtomicStorage, QuantaClock,
RateLimit, StdClock, TokenBucket,
AtomicStorage, FastClock, Limit, LocalStorage, ManualClock, PaddedAtomicStorage, QuantaClock,
StdClock, TokenBucket,
};
use nonzero_ext::nonzero;

fn bench_consume(c: &mut Criterion) {
let clock = quanta::Clock::new();
let limit = RateLimit::per_second(nonzero!(10_000u32));
let limit = Limit::per_second(nonzero!(10_000u32));
let _quanta_thread = quanta::Upkeep::new_with_clock(Duration::from_micros(10), clock.clone())
.start()
.unwrap();
Expand Down Expand Up @@ -80,7 +80,7 @@ fn multi_threaded(c: &mut Criterion) {
.start()
.unwrap();
let clock = FastClock::new(clock);
let limit = RateLimit::per_second(nonzero!(10_000u32));
let limit = Limit::per_second(nonzero!(10_000u32));
let mut group = c.benchmark_group("multi_threaded");
group
.throughput(Throughput::Elements(1))
Expand Down Expand Up @@ -138,7 +138,7 @@ fn multi_threaded2(c: &mut Criterion) {
.start()
.unwrap();
let clock = FastClock::new(clock);
let limit = RateLimit::per_second(nonzero!(50u32));
let limit = Limit::per_second(nonzero!(50u32));
let mut group = c.benchmark_group("multi_threaded2");
group
.throughput(Throughput::Elements(1))
Expand Down
4 changes: 2 additions & 2 deletions examples/basic.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
use std::time::Duration;

use gardal::{RateLimit, TokenBucket};
use gardal::{Limit, TokenBucket};
use nonzero_ext::nonzero;

fn main() {
let tb = TokenBucket::new(RateLimit::per_second_and_burst(
let tb = TokenBucket::new(Limit::per_second_and_burst(
nonzero!(10u32),
nonzero!(20u32),
));
Expand Down
4 changes: 2 additions & 2 deletions examples/fast_clock.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use std::time::Duration;

use gardal::{FastClock, RateLimit, TokenBucket};
use gardal::{FastClock, Limit, TokenBucket};
use nonzero_ext::nonzero;

fn main() {
Expand All @@ -10,7 +10,7 @@ fn main() {
.start()
.unwrap();
let clock = FastClock::new(clock);
let limit = RateLimit::per_second_and_burst(nonzero!(10u32), nonzero!(20u32));
let limit = Limit::per_second_and_burst(nonzero!(10u32), nonzero!(20u32));
let tb = TokenBucket::with_clock(limit, clock);
// after two seconds bucket should be full
println!("sleeping for 2 seconds...");
Expand Down
13 changes: 7 additions & 6 deletions examples/streams.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,16 @@ use std::sync::Arc;
use std::sync::atomic::AtomicU64;
use std::time::Duration;

use futures::{StreamExt, stream};
use gardal::futures::RateLimitedStreamExt;
use gardal::{PaddedAtomicSharedStorage, RateLimit, TokenBucket, TokioClock};
use futures::StreamExt;
use futures::stream;
use gardal::futures::StreamExt as GardalStreamExt;
use gardal::{Limit, PaddedAtomicSharedStorage, TokenBucket, TokioClock};
use nonzero_ext::nonzero;
use tokio::task::JoinSet;

#[tokio::main(flavor = "multi_thread")]
async fn main() {
let limit = RateLimit::per_second_and_burst(nonzero!(1000000u32), nonzero!(100u32));
let limit = Limit::per_second_and_burst(nonzero!(1000000u32), nonzero!(100u32));
let bucket =
TokenBucket::<PaddedAtomicSharedStorage, _>::from_parts(limit, TokioClock::default());

Expand All @@ -23,9 +24,9 @@ async fn main() {
let bucket = bucket.clone();
let global_processed = global_processed.clone();
async move {
let mut stream1 = std::pin::pin!(stream::repeat(1).rate_limit(bucket));
let mut stream1 = std::pin::pin!(stream::repeat(1).throttle(bucket));
// for unthrottled stream those have identical performance.
// let mut stream1 = std::pin::pin!(stream::repeat(1).rate_limit(None::<TokenBucket>));
// let mut stream1 = std::pin::pin!(stream::repeat(1).throttle(None::<TokenBucket>));
// let mut stream1 = std::pin::pin!(stream::iter(1..=1000000000));
let mut iter_start = tokio::time::Instant::now();
let mut processed = 0;
Expand Down
16 changes: 8 additions & 8 deletions examples/weighted_stream.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use futures::stream;
use gardal::futures::{RateLimitedStreamExt, WeightedStream};
use gardal::{LocalStorage, RateLimit, TokenBucket, TokioClock};
use gardal::futures::{StreamExt as GardalStreamExt, WeightedStream};
use gardal::{Limit, LocalStorage, TokenBucket, TokioClock};
use nonzero_ext::nonzero;
use std::num::NonZeroU32;
use tokio_stream::StreamExt;
Expand Down Expand Up @@ -36,8 +36,8 @@ async fn main() {

let stream = stream::iter(tasks);

// Create a rate limit: 5 tokens per second with a burst of 25
let limit = RateLimit::per_second_and_burst(nonzero!(5u32), nonzero!(25u32));
// Create a throttling limit: 5 tokens per second with a burst of 25
let limit = Limit::per_second_and_burst(nonzero!(5u32), nonzero!(25u32));
let bucket = TokenBucket::<LocalStorage, _>::from_parts(limit, TokioClock::default());

// Create a weighted stream where each task consumes tokens based on its size
Expand All @@ -46,8 +46,8 @@ async fn main() {
NonZeroU32::new(task.size as u32).unwrap_or(nonzero!(1u32))
});

println!("Processing tasks with weighted rate limiting...");
println!("Rate limit: 5 tokens/second, burst: 25 tokens");
println!("Processing tasks with weighted throttling...");
println!("Throttling: 5 tokens/second, burst: 25 tokens");
println!();

let start = std::time::Instant::now();
Expand Down Expand Up @@ -75,11 +75,11 @@ async fn main() {
let tasks2 = vec!["short", "medium_length", "very_long_string_here", "x"];

let stream2 = stream::iter(tasks2);
let limit2 = RateLimit::per_second_and_burst(nonzero!(3u32), nonzero!(25u32));
let limit2 = Limit::per_second_and_burst(nonzero!(3u32), nonzero!(25u32));
let bucket2 = TokenBucket::<LocalStorage, _>::from_parts(limit2, TokioClock::default());

// Use the extension trait to create a weighted stream
let weighted_stream2 = stream2.rate_limit_weighted(bucket2, |text: &&str| {
let weighted_stream2 = stream2.throttle_weighted(bucket2, |text: &&str| {
// Consume tokens based on string length
NonZeroU32::new(text.len() as u32).unwrap_or(nonzero!(1u32))
});
Expand Down
Loading
Loading