Skip to content

Commit bb81e8d

Browse files
committed
feat: add benchmarking system using anvil dump files
1 parent 089d5af commit bb81e8d

File tree

10 files changed

+631
-70
lines changed

10 files changed

+631
-70
lines changed

.github/workflows/benchmarks.yml

Lines changed: 12 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -46,14 +46,6 @@ jobs:
4646
with:
4747
egress-policy: audit
4848

49-
- name: Free up disk space
50-
run: |
51-
sudo rm -rf /usr/share/dotnet
52-
sudo rm -rf /opt/ghc
53-
sudo rm -rf "/usr/local/share/boost"
54-
sudo rm -rf /usr/local/lib/android
55-
df -h
56-
5749
- name: Checkout repository
5850
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8
5951

@@ -66,6 +58,12 @@ jobs:
6658
- name: Install Bencher CLI
6759
uses: bencherdev/bencher@2f1532643adc0e69e52acaec936d227ff14da24f # v0.5.9
6860

61+
- name: Decompress benchmark state dumps
62+
run: |
63+
echo "Decompressing Anvil state dumps..."
64+
gunzip -k benches/dumps/*.json.gz
65+
ls -lh benches/dumps/
66+
6967
- name: Run historic benchmarks and track with Bencher
7068
env:
7169
BENCHER_API_TOKEN: ${{ secrets.BENCHER_API_TOKEN }}
@@ -99,14 +97,6 @@ jobs:
9997
with:
10098
egress-policy: audit
10199

102-
- name: Free up disk space
103-
run: |
104-
sudo rm -rf /usr/share/dotnet
105-
sudo rm -rf /opt/ghc
106-
sudo rm -rf "/usr/local/share/boost"
107-
sudo rm -rf /usr/local/lib/android
108-
df -h
109-
110100
- name: Checkout repository
111101
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8
112102

@@ -119,6 +109,12 @@ jobs:
119109
- name: Install Bencher CLI
120110
uses: bencherdev/bencher@2f1532643adc0e69e52acaec936d227ff14da24f # v0.5.9
121111

112+
- name: Decompress benchmark state dumps
113+
run: |
114+
echo "Decompressing Anvil state dumps..."
115+
gunzip -k benches/dumps/*.json.gz
116+
ls -lh benches/dumps/
117+
122118
- name: Run latest events benchmarks and track with Bencher
123119
env:
124120
BENCHER_API_TOKEN: ${{ secrets.BENCHER_API_TOKEN }}

.gitignore

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
11
/target
22
/examples/**/target
33
.DS_Store
4+
5+
# Benchmark dumps - only commit compressed files
6+
benches/dumps/*.json
7+
!benches/dumps/*.metadata.json

Cargo.lock

Lines changed: 53 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

benches/Cargo.toml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,9 @@ tokio.workspace = true
1414
tokio-stream.workspace = true
1515
futures.workspace = true
1616
anyhow.workspace = true
17+
serde = { version = "1.0", features = ["derive"] }
18+
serde_json = "1.0"
19+
flate2 = "1.1"
1720

1821
[dev-dependencies]
1922
criterion.workspace = true
@@ -28,3 +31,7 @@ harness = false
2831
[[bench]]
2932
name = "latest_events_scanning"
3033
harness = false
34+
35+
[[bin]]
36+
name = "generate_dump"
37+
path = "src/bin/generate_dump.rs"

benches/benches/historic_scanning.rs

Lines changed: 52 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,35 +1,45 @@
11
//! Benchmarks for historic scanning mode.
22
//!
3-
//! Heavy load tests that measure the time to fetch all expected events.
3+
//! Heavy load tests that measure the time to fetch events from different block ranges.
4+
//! Uses pre-generated Anvil state dumps for fast, reproducible setup.
5+
//!
6+
//! Benchmarks three block ranges from a 100k event dump:
7+
//! - First 1/10 of blocks (~10k events)
8+
//! - First 1/2 of blocks (~50k events)
9+
//! - All blocks (100k events)
410
11+
use std::path::{Path, PathBuf};
512
use std::sync::OnceLock;
613

714
use anyhow::{Result, bail};
815
use criterion::{BenchmarkId, Criterion, Throughput, criterion_group, criterion_main};
916
use event_scanner::{EventFilter, EventScannerBuilder, Message};
10-
use event_scanner_benches::{
11-
BenchConfig, BenchEnvironment, count_increased_signature, setup_environment,
12-
};
17+
use event_scanner_benches::{BenchEnvironment, count_increased_signature, setup_from_dump};
1318
use tokio_stream::StreamExt;
1419

20+
/// Returns the path to the dump file, resolved from the crate's manifest directory.
21+
fn dump_path() -> PathBuf {
22+
Path::new(env!("CARGO_MANIFEST_DIR")).join("dumps/state_100000.json.gz")
23+
}
24+
1525
static RUNTIME: OnceLock<tokio::runtime::Runtime> = OnceLock::new();
1626

1727
fn get_runtime() -> &'static tokio::runtime::Runtime {
1828
RUNTIME.get_or_init(|| tokio::runtime::Runtime::new().expect("failed to create tokio runtime"))
1929
}
2030

21-
/// Runs a single historic scan.
31+
/// Runs a historic scan for a specific block range.
2232
///
23-
/// This fetches ALL events from block 0 to latest.
24-
async fn run_historic_scan(env: &BenchEnvironment) -> Result<()> {
33+
/// Fetches events from block 0 to `to_block`.
34+
async fn run_historic_scan(env: &BenchEnvironment, to_block: u64) -> Result<()> {
2535
let filter = EventFilter::new()
2636
.contract_address(env.contract_address)
2737
.event(count_increased_signature());
2838

2939
let mut scanner = EventScannerBuilder::historic()
3040
.max_block_range(100)
3141
.from_block(0)
32-
.to_block(alloy::eips::BlockNumberOrTag::Latest)
42+
.to_block(to_block)
3343
.connect(env.provider.clone())
3444
.await?;
3545

@@ -58,26 +68,40 @@ fn historic_scanning_benchmark(c: &mut Criterion) {
5868

5969
// Configure for heavy load tests
6070
group.warm_up_time(std::time::Duration::from_secs(5));
61-
group.measurement_time(std::time::Duration::from_secs(120));
62-
63-
// Heavy load test: 100,000 events
64-
// Also include smaller sizes for regression comparison
65-
for event_count in [10_000, 50_000, 100_000] {
66-
println!("Setting up environment with {event_count} events...");
67-
68-
// Setup environment once per event count (events are pre-generated)
69-
let env: BenchEnvironment = rt.block_on(async {
70-
let config = BenchConfig::new(event_count);
71-
setup_environment(config).await.expect("failed to setup benchmark environment")
72-
});
73-
74-
println!("Environment ready. Starting benchmark...");
75-
76-
group.throughput(Throughput::Elements(event_count as u64));
77-
78-
group.bench_with_input(BenchmarkId::new("events", event_count), &env, |b, env| {
79-
b.to_async(&rt)
80-
.iter(|| async { run_historic_scan(env).await.expect("historic scan failed") });
71+
group.measurement_time(std::time::Duration::from_secs(140));
72+
73+
// Load environment from pre-generated dump (100k events)
74+
println!("Loading benchmark environment from dump file...");
75+
let env: BenchEnvironment = rt.block_on(async {
76+
setup_from_dump(&dump_path()).await.expect("failed to load benchmark environment from dump")
77+
});
78+
println!(
79+
"Environment ready: {} events across {} blocks at contract {}",
80+
env.event_count, env.block_number, env.contract_address
81+
);
82+
83+
// Calculate block ranges:
84+
// - 1/10 of blocks: ~10k events
85+
// - 1/2 of blocks: ~50k events
86+
// - All blocks: 100k events
87+
let total_blocks = env.block_number;
88+
let block_ranges = [
89+
(total_blocks / 10, "1/10 blocks (~10k events)"),
90+
(total_blocks / 2, "1/2 blocks (~50k events)"),
91+
(total_blocks, "all blocks (100k events)"),
92+
];
93+
94+
for (to_block, description) in block_ranges {
95+
println!("Benchmarking historic scan: {description} (to block {to_block})...");
96+
97+
// Estimate events based on block ratio (events are roughly evenly distributed)
98+
let estimated_events = (env.event_count as u64 * to_block) / total_blocks;
99+
group.throughput(Throughput::Elements(estimated_events));
100+
101+
group.bench_with_input(BenchmarkId::new("blocks", to_block), &to_block, |b, &to_block| {
102+
b.to_async(rt).iter(|| async {
103+
run_historic_scan(&env, to_block).await.expect("historic scan failed");
104+
});
81105
});
82106
}
83107

benches/benches/latest_events_scanning.rs

Lines changed: 27 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -2,17 +2,27 @@
22
//!
33
//! Heavy load tests that measure the time to fetch the N most recent events
44
//! from a large pool of pre-generated events.
5+
//! Uses pre-generated Anvil state dumps for fast, reproducible setup.
6+
//!
7+
//! Benchmarks fetching latest events from a 100k event pool:
8+
//! - 10,000 latest events
9+
//! - 50,000 latest events
10+
//! - 100,000 latest events (all)
511
12+
use std::path::{Path, PathBuf};
613
use std::sync::OnceLock;
714

815
use anyhow::{Result, bail, ensure};
916
use criterion::{BenchmarkId, Criterion, Throughput, criterion_group, criterion_main};
1017
use event_scanner::{EventFilter, EventScannerBuilder, Message};
11-
use event_scanner_benches::{
12-
BenchConfig, BenchEnvironment, count_increased_signature, setup_environment,
13-
};
18+
use event_scanner_benches::{BenchEnvironment, count_increased_signature, setup_from_dump};
1419
use tokio_stream::StreamExt;
1520

21+
/// Returns the path to the dump file, resolved from the crate's manifest directory.
22+
fn dump_path() -> PathBuf {
23+
Path::new(env!("CARGO_MANIFEST_DIR")).join("dumps/state_100000.json.gz")
24+
}
25+
1626
static RUNTIME: OnceLock<tokio::runtime::Runtime> = OnceLock::new();
1727

1828
fn get_runtime() -> &'static tokio::runtime::Runtime {
@@ -60,29 +70,23 @@ fn latest_events_scanning_benchmark(c: &mut Criterion) {
6070

6171
// Configure for heavy load tests
6272
group.warm_up_time(std::time::Duration::from_secs(5));
63-
group.measurement_time(std::time::Duration::from_secs(120));
64-
65-
// Generate a pool of events once
66-
// We'll benchmark fetching different "latest N" counts from this pool
67-
// Using 50K total
68-
let total_events = 50_000;
69-
70-
println!("Setting up environment with {total_events} total events...");
73+
group.measurement_time(std::time::Duration::from_secs(140));
7174

75+
// Load environment from pre-generated dump (100k events)
76+
println!("Loading benchmark environment from dump file...");
7277
let env: BenchEnvironment = rt.block_on(async {
73-
let config = BenchConfig::new(total_events);
74-
setup_environment(config).await.expect("failed to setup benchmark environment")
78+
setup_from_dump(&dump_path()).await.expect("failed to load benchmark environment from dump")
7579
});
80+
println!(
81+
"Environment ready: {} events across {} blocks at contract {}",
82+
env.event_count, env.block_number, env.contract_address
83+
);
7684

77-
println!("Environment ready. Starting benchmarks...");
78-
79-
// Benchmark fetching different "latest N" counts
80-
// Trying to replicate realistic use cases:
81-
// - 100: Quick recent activity check
82-
// - 1,000: Moderate history lookup
85+
// Benchmark fetching latest N events from the 100k event pool:
8386
// - 10,000: Substantial history fetch
84-
// - 25,000: Heavy load retrieval
85-
for latest_count in [100, 1_000, 10_000, 25_000] {
87+
// - 50,000: Heavy load retrieval
88+
// - 100,000: All events (full scan)
89+
for latest_count in [10_000, 50_000, 100_000] {
8690
println!("Benchmarking latest {latest_count} events...");
8791

8892
group.throughput(Throughput::Elements(latest_count as u64));
@@ -91,8 +95,8 @@ fn latest_events_scanning_benchmark(c: &mut Criterion) {
9195
BenchmarkId::new("latest", latest_count),
9296
&latest_count,
9397
|b, &count| {
94-
b.to_async(&rt).iter(|| async {
95-
run_latest_events_scan(&env, count).await.expect("latest events scan failed")
98+
b.to_async(rt).iter(|| async {
99+
run_latest_events_scan(&env, count).await.expect("latest events scan failed");
96100
});
97101
},
98102
);

benches/dumps/state_100000.json.gz

24.2 MB
Binary file not shown.

0 commit comments

Comments
 (0)