examples/mimir-light-bench.rs
//! LIGHT-arm Mimir bench runner — the working-box-safe subset.
//!
//! The full `nornir-bench` suite (105 benchers, up to 100k-row appends and 10M-key
//! Ragnar builds) is a HEAVY sweep and is owed to an idle machine (heavy-bench
//! law). This runner executes only the **small** arms named on the command line
//! (or `NORNIR_BENCH_ONLY`, comma-separated substrings) so an optimize-loop cycle
//! can measure a single populate path on a developer box without burning it.
//!
//! It reuses the SAME registry (`inventory` + `register_bench!`) and the SAME
//! `Bencher::run()` bodies as `nornir-bench` — no forked fixtures, no second
//! definition of the work being timed — so a number here is directly comparable
//! to that arm's row in the authoritative run.
//!
//! cargo run --release --example mimir-light-bench -- iceberg_commit_ sql_point_500
//!
//! Prints one JSON line per bench (`{"name":…,"metrics":{…}}`) plus a compact
//! human table on stderr. `reps` (env `MIMIR_BENCH_REPS`, default 3) repeats each
//! arm and reports the MEDIAN, since a single fsync-bound run on a busy desktop
//! is noisy.
use anyhow::Result;
use nornir::bench::api::{Bencher, BencherRegistration};
fn main() -> Result<()> {
let mut pats: Vec<String> = std::env::args().skip(1).collect();
if pats.is_empty() {
pats = std::env::var("NORNIR_BENCH_ONLY")
.unwrap_or_default()
.split(',')
.map(|s| s.trim().to_string())
.filter(|s| !s.is_empty())
.collect();
}
if pats.is_empty() {
eprintln!("usage: mimir-light-bench <id-substring>… (or NORNIR_BENCH_ONLY=…)");
std::process::exit(2);
}
let reps: usize = std::env::var("MIMIR_BENCH_REPS")
.ok()
.and_then(|v| v.parse().ok())
.unwrap_or(3);
let mut chosen: Vec<&'static dyn Bencher> = inventory::iter::<BencherRegistration>()
.map(|r| (r.make)())
.filter(|b| pats.iter().any(|p| b.id().contains(p.as_str())))
.collect();
chosen.sort_by_key(|b| b.id());
if chosen.is_empty() {
eprintln!("mimir-light-bench: no registered bench id matched {pats:?}");
std::process::exit(2);
}
for b in chosen {
// Repeat and keep the MEDIAN result — the run bodies are self-contained
// (fresh tempdir warehouse per run), so reps are independent samples.
let mut runs: Vec<nornir::bench::BenchResult> = Vec::with_capacity(reps);
for _ in 0..reps {
runs.push(b.run()?);
}
// Sort by the arm's headline throughput metric (the sole `*_per_sec`).
let key = |r: &nornir::bench::BenchResult| -> f64 {
r.metrics
.iter()
.find(|(k, _)| k.ends_with("_per_sec"))
.and_then(|(_, v)| v.as_f64())
.unwrap_or(0.0)
};
runs.sort_by(|a, c| {
key(a)
.partial_cmp(&key(c))
.unwrap_or(std::cmp::Ordering::Equal)
});
let med = &runs[runs.len() / 2];
eprintln!(
"{:<48} {:>12.1}/s p50={:>9.1}us p99={:>9.1}us (median of {reps})",
med.name,
key(med),
med.metrics
.get("p50_us")
.and_then(|v| v.as_f64())
.unwrap_or(0.0),
med.metrics
.get("p99_us")
.and_then(|v| v.as_f64())
.unwrap_or(0.0),
);
println!("{}", serde_json::to_string(med)?);
}
Ok(())
}