-
Notifications
You must be signed in to change notification settings - Fork 4
feat(benches): add benchmarks for historic and latest events scanning #254
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
Signed-off-by: yug49 <148035793+yug49@users.noreply.github.com>
465a88a to
fa67e5b
Compare
|
Hey @0xNeshi |
|
All good 👍 |
|
GM @0xNeshi, Workflow Architecture Three GitHub Actions workflows handle different scenarios:
Required Setup
How It Works When code is merged to |
0xNeshi
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Excellent work, let's polish now
| with: | ||
| egress-policy: audit | ||
|
|
||
| - name: Free up disk space |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this step required for our use case? Does event scanner benching need this additional space?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, I added this additional step since running benches was taking a lot of space, sometimes it exceeded the github runners' limited space (~14GB).
Here:

Even after adding this step, the github CI was often timing out for the latest events benches, because it was trying to generate 100,000 events which is a very heavy operation that can timeout on CI.
Sometimes, it was getting stuck at this step for > 30 mins:
Run cargo bench --manifest-path benches/Cargo.toml --bench latest_events_scanning 2>&1 | tee latest_results.txt
Updating crates.io index
Compiling event-scanner v0.9.0-alpha (/home/runner/work/Event-Scanner/Event-Scanner)
Compiling event-scanner-benches v0.9.0-alpha (/home/runner/work/Event-Scanner/Event-Scanner/benches)
Finished `bench` profile [optimized] target(s) in 17.61s
Running benches/latest_events_scanning.rs (target/release/deps/latest_events_scanning-c7b9440175034d98)
Gnuplot not found, using plotters backend
Setting up environment with 100000 total events...
To counter this, I reduced the total events for this case to 50k from 100k, which I think is also a reasonable number for real world scenarios.
Rust compilation is the main disk consumer, unchanged by event count, the cleanup step runs in <5 seconds, which I think is a negligible cost. It is a cheap safety net that prevents potential flaky failures.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Alright, makes sense.
Once blocks are loaded into Anvil using a dump file, double check if this step becomes redundant and if it's possible to still keep the event count at 100,000.
benches/benches/historic_scanning.rs
Outdated
| } | ||
| } | ||
|
|
||
| assert_eq!(log_count, expected_count, "expected {expected_count} events, got {log_count}"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
On second thought, this validation unnecessarily affects bench result, while also being kind of redundant - the project has extensive integration tests with this validation.
Remove the assertions and stop tracking log count
| let env: BenchEnvironment = rt.block_on(async { | ||
| let config = BenchConfig::new(event_count); | ||
| setup_environment(config).await.expect("failed to setup benchmark environment") | ||
| }); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There's really no reason to setup a new Anvil node for each bench run, it just adds to total bench execution time.
Let's make an optimization to the both benches.
Instead of setting up a new Anvil node for each event_count case, let's do the following:
- setup a single Anvil node with 100,000 events
- get latest block number
- for historic bench:
- bench with 3 different block ranges that roughly correspond to:
- first 1/10 of all blocks
- first 1/2 of all blocks
- all blocks
- bench with 3 different block ranges that roughly correspond to:
- for latest events - leave as-is, i.e. bench latest 10k events, then 50k, then 100k (all) events
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Related #254 (comment)
benches/benches/historic_scanning.rs
Outdated
| } | ||
|
|
||
| fn historic_scanning_benchmark(c: &mut Criterion) { | ||
| let rt = tokio::runtime::Runtime::new().expect("failed to create tokio runtime"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Create a singleton tokio runtime that can be reused across bench runs.
Something like:
use std::sync::OnceLock;
static RUNTIME: OnceLock<tokio::runtime::Runtime> = OnceLock::new();
fn get_runtime() -> &'static tokio::runtime::Runtime {
RUNTIME.get_or_init(|| {
tokio::runtime::Runtime::new().expect("failed to create tokio runtime")
})
}
fn historic_scanning_benchmark(c: &mut Criterion) {
let rt = get_runtime();
// ... rest of benchmark ...
}
benches/src/lib.rs
Outdated
| sol! { | ||
| // Built directly with solc 0.8.30+commit.73712a01.Darwin.appleclang | ||
| #[sol(rpc, bytecode="608080604052346015576101b0908161001a8239f35b5f80fdfe6080806040526004361015610012575f80fd5b5f3560e01c90816306661abd1461016157508063a87d942c14610145578063d732d955146100ad5763e8927fbc14610048575f80fd5b346100a9575f3660031901126100a9575f5460018101809111610095576020817f7ca2ca9527391044455246730762df008a6b47bbdb5d37a890ef78394535c040925f55604051908152a1005b634e487b7160e01b5f52601160045260245ffd5b5f80fd5b346100a9575f3660031901126100a9575f548015610100575f198101908111610095576020817f53a71f16f53e57416424d0d18ccbd98504d42a6f98fe47b09772d8f357c620ce925f55604051908152a1005b60405162461bcd60e51b815260206004820152601860248201527f436f756e742063616e6e6f74206265206e6567617469766500000000000000006044820152606490fd5b346100a9575f3660031901126100a95760205f54604051908152f35b346100a9575f3660031901126100a9576020905f548152f3fea2646970667358221220471585b420a1ad0093820ff10129ec863f6df4bec186546249391fbc3cdbaa7c64736f6c634300081e0033")] | ||
| contract BenchCounter { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| contract BenchCounter { | |
| contract Counter { |
align name with other contracts in examples
| // - 10 samples (iterations) | ||
| // - Long measurement time to accommodate heavy loads | ||
| group.sample_size(10); | ||
| group.measurement_time(std::time::Duration::from_secs(120)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's also increase warm up time to at least 5 seconds
Co-authored-by: Nenad <xinef.it@gmail.com>
Co-authored-by: Nenad <xinef.it@gmail.com>
Co-authored-by: Nenad <xinef.it@gmail.com>
|
@0xNeshi what do you think about creating multiple dump files and starting anvil from that state instead of having to recreate it every time we start a bench |
Even better 👍 |
Co-authored-by: Nenad <xinef.it@gmail.com>
Co-authored-by: Nenad <xinef.it@gmail.com>
Related to #229
Hey!
This PR introduces a benchmarking system for Event Scanner using Criterion.rs to measure performance impact of changes to the scanner. Currently drafting, Bencher CI integration coming in follow-up.
What's Included
New benches Crate Structure
Benchmarks Implemented
example of Historic:

How Regression Testing Works
Criterion stores baseline results in
target/criterion/<benchmark>/base/. On subsequent runs, it compares new measurements against this baseline and reports:If a change introduces a regression, you'll see something like:
This makes it easy to catch slowdowns before merging
Running Benchmarks
Next Steps