Skip to content

smitebot: add bench command to measure Nyx execution speed - #160

Open
erickcestari wants to merge 3 commits into
lnfuzz:masterfrom
erickcestari:bench-exec-s
Open

smitebot: add bench command to measure Nyx execution speed#160
erickcestari wants to merge 3 commits into
lnfuzz:masterfrom
erickcestari:bench-exec-s

Conversation

@erickcestari

Copy link
Copy Markdown
Contributor

The goal is to tell whether a change concretely improved the target's throughput or not: run bench before and after and compare execs/sec.

It runs a single input through the target's Nyx VM many times, timing the snapshot-restore-plus-target-run loop. Each exec is one snapshot restore plus one target run, so the numbers isolate VM/snapshot and target speed from AFL++'s mutation and scheduling overhead.

Like start, it builds the image and sets up the sharedir by default (pass --no-build to reuse an existing one), and loads libnyx.so at runtime the same way afl-fuzz does. --repeat averages over several fresh VM boots so a real change stands out from boot/snapshot variance, and --timeout bounds a single execution (default 2s).

@Chand-ra Chand-ra left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like a great addition overall. Following are some improvement suggestions.

Comment thread smitebot/src/commands/start.rs Outdated
Comment thread smitebot/src/commands/bench.rs Outdated
Comment thread smitebot/README.md Outdated
Comment thread smitebot/src/commands/bench_exec.rs
@erickcestari

Copy link
Copy Markdown
Contributor Author

Thanks for the review @Chand-ra ! I've added the coverage metric, which is a nice feature to have. Specially when we start using the bedrock as one of the hypervisor.

@Ashish-Kumar-Dash Ashish-Kumar-Dash left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I had a few thoughts/nits, otherwise its a solid PR! The only real concern I have is the order in which we merge status, bench and print-ir since one of us will have to rebase, but I think it'd be much better if bench,print-ir could get merged and then we land status. I don't think it will be an issue on your part, since I'd have to do the rebase then :)

Comment thread smitebot/src/commands/bench_exec.rs
Comment thread smitebot/README.md Outdated
Comment thread smitebot/src/libnyx.rs Outdated
Comment thread smitebot/src/commands/bench_exec.rs Outdated
Comment thread smitebot/Cargo.toml Outdated
Comment thread smitebot/README.md Outdated
smitebot bench campaign.toml --input testcase.bin --iterations 5000
```

- `--input`: Input file to execute repeatedly. Defaults to a single `0x00` byte.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think --input should be required -- it should be rare that anyone wants to benchmark an empty program.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's useful for quickly checking performance improvements. I've used it to quickly test JVM optimization because the executor checks if the target is live, which does a ping-pong.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it is worth mentioning in the documentation then: the default input can be used to quickly check performance improvements due to factors like target optimization.

Comment thread smitebot/README.md Outdated
Comment thread smitebot/src/libnyx.rs Outdated
Comment thread smitebot/src/libnyx.rs
Comment thread smitebot/src/libnyx.rs
Comment on lines +29 to +36
/// Start of the `result` section within the buffer.
const RESULT: usize = 896;
/// `result.dirty_pages` (`u32`): pages restored for the last exec.
pub const DIRTY_PAGES: usize = RESULT + 16;
/// `result.runtime_usec` (`u32`): microsecond part of guest payload runtime.
pub const RUNTIME_USEC: usize = RESULT + 28;
/// `result.runtime_sec` (`u32`): second part of guest payload runtime.
pub const RUNTIME_SEC: usize = RESULT + 32;

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These offsets are all correct AFAICT. But it seems fragile -- it would be nice if Nyx provided a clearer interface.

Comment thread smitebot/src/commands/bench_exec.rs
Comment thread smitebot/src/commands/bench_exec.rs Outdated
@erickcestari
erickcestari force-pushed the bench-exec-s branch 3 times, most recently from d6d5659 to 931a8b2 Compare July 23, 2026 20:53
@erickcestari

Copy link
Copy Markdown
Contributor Author

Thanks @morehouse and @Ashish-Kumar-Dash for the review.

@Chand-ra Chand-ra left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

GitHub started acting up so I couldn't use the "suggest change" functionality🤦. Looks mostly good to me, following are a couple of suggestions.

Comment thread smitebot/src/stats.rs Outdated
Comment thread smitebot/src/latency_stats.rs
Comment thread smitebot/src/stats.rs Outdated
Comment thread smitebot/src/latency_stats.rs
Comment thread smitebot/src/latency_stats.rs
Comment thread smitebot/README.md Outdated
smitebot bench campaign.toml --input testcase.bin --iterations 5000
```

- `--input`: Input file to execute repeatedly. Defaults to a single `0x00` byte.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it is worth mentioning in the documentation then: the default input can be used to quickly check performance improvements due to factors like target optimization.

Comment thread smitebot/README.md Outdated
Comment thread smitebot/src/libnyx.rs Outdated
Comment thread smitebot/src/commands/bench_exec.rs Outdated
Comment thread smitebot/src/commands/bench_exec.rs Outdated
@erickcestari
erickcestari force-pushed the bench-exec-s branch 3 times, most recently from bfb96ea to 470fce8 Compare July 24, 2026 17:27

@Chand-ra Chand-ra left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Took a quick look and verified that all the pointers from the previous feedback were addressed, looks good to me now!

@Ashish-Kumar-Dash

Copy link
Copy Markdown
Contributor

@erickcestari I pushed some recent commits, you might have to rebase

@erickcestari
erickcestari force-pushed the bench-exec-s branch 2 times, most recently from c80f707 to 76314ac Compare July 30, 2026 14:28
@erickcestari

Copy link
Copy Markdown
Contributor Author

@erickcestari I pushed some recent commits, you might have to rebase

Thanks! I've rebased it now.

@erickcestari
erickcestari requested a review from morehouse July 31, 2026 14:14
Comment thread smitebot/src/commands/bench_exec.rs Outdated
Comment thread smitebot/src/commands/bench_exec.rs Outdated
Comment thread smitebot/src/commands/bench_exec.rs Outdated
Comment on lines +72 to +75
/// Maximum input buffer size in bytes for the Nyx VM. Defaults to the input
/// size rounded up to a 4 KiB page; pass 1048576 to match AFL++'s 1 MiB buffer.
#[arg(long)]
max_input_size: Option<u32>,

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there ever a reason to use this flag now that we automatically calculate the buffer size?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually I'll remove the adaptive buffer size, since when running with AFL++ the value used is always the 1MiB MAX_FILE. So I believe it makes more sense to bench in the same way AFL++ runs. Also I'll keep an option to change the max buffer size to test if changing it makes any difference on performance.

Comment thread smitebot/src/commands/bench_exec.rs Outdated
Comment thread smitebot/src/commands/bench_exec.rs Outdated
Comment thread smitebot/README.md
Comment thread smitebot/README.md Outdated
Comment thread smitebot/src/commands/bench_exec.rs Outdated
Comment thread smitebot/src/libnyx.rs Outdated
Comment thread smitebot/src/commands/bench_exec.rs Outdated
@erickcestari
erickcestari force-pushed the bench-exec-s branch 2 times, most recently from 01a9562 to 28fe6ec Compare August 6, 2026 15:37
@erickcestari
erickcestari requested a review from morehouse August 6, 2026 15:40
@erickcestari

Copy link
Copy Markdown
Contributor Author

Thanks @morehouse. It was an insightful review. It's ready for another one.

Comment thread smitebot/src/utils.rs
Comment thread smitebot/src/utils.rs
Comment thread smitebot/src/utils.rs Outdated
Comment thread smitebot/src/utils.rs
Comment thread smitebot/src/commands/bench_exec.rs Outdated
Comment thread smitebot/src/libnyx.rs Outdated
Comment thread smitebot/src/commands/bench_exec.rs Outdated
Comment on lines +94 to +98
/// Defaults to AFL++'s 1 MiB, the buffer a real campaign runs with. Lower it
/// to measure what a smaller buffer buys: the guest allocates the whole
/// buffer on every execution and the snapshot resets every page of it
/// dirtied, so buffer size shows up directly in the Nyx overhead and dirty
/// pages per exec.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think this logic checks out. Assume the input size is X pages and the buffer size is Y > X pages. Then regardless of what Y is, only X pages ever get dirtied and need to be reset.

Have you actually observed differences in performance with varying buffer sizes?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll provide a benchmark later. Currently I'm in a laptop without the charger which would result in unreliable benchmark.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually, that's not true at all. When I ran it, it ended up using an E-core instead of a P-core because I hadn't pinned the CPU. It produced a different result, which led me to think the difference was caused by max-input-size.

Comment thread smitebot/src/commands/bench_exec.rs Outdated
Comment thread smitebot/src/commands/bench_exec.rs Outdated
Comment thread smitebot/README.md Outdated
bench-exec needs the same Nyx sharedir setup as start, so lift the helper
out of start.rs into utils instead of duplicating it.
The goal is to tell whether a change concretely improved the target's
throughput or not: run bench before and after and compare execs/sec.

It runs a single input through the target's Nyx VM many times, timing
the snapshot-restore-plus-target-run loop. Each exec is one snapshot
restore plus one target run, so the numbers isolate VM/snapshot and
target speed from AFL++'s mutation and scheduling overhead.

Like start, it builds the image and sets up the sharedir by default
(pass --no-build to reuse an existing one), and loads libnyx.so at
runtime the same way afl-fuzz does. --repeat averages over several fresh
VM boots so a real change stands out from boot/snapshot variance, and
--timeout bounds a single execution (default 2s).

Adds FFI bindings to libnyx (loaded via dlopen) and a small stats
module for latency percentiles and mean/stddev aggregation.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants