mikronova/modgunn
benches/manifest_parse.rs
//! HEAVY benchmark — **multi-ecosystem manifest parse throughput**.
//!
//! Generates a large synthetic manifest for every ecosystem the `manifests`
//! ingest seam supports (one representative format each), then parses each one
//! `REPEATS` times keeping the best pass, and reports parsed components/sec. This
//! measures the pure-Rust, zero-shell parse path a fleet-scale SBOM ingest runs
//! before any advisory match. Host-stamped, plain numbers.
//!
//! No smoke variant — sizes default to a heavy corpus, overridable UPWARD. Env:
//! MODGUNN_BENCH_DEPS (default 50_000 — packages per generated manifest)
//! MODGUNN_BENCH_REPEATS (default 5 — timed passes per format, best kept)
//!
//! Run: `CARGO_TARGET_DIR=/mnt/work4t/... cargo bench --features manifests --bench manifest_parse`
use std::time::Instant;
use modgunn::manifests::parse_manifest;
fn env_usize(key: &str, default: usize) -> usize {
std::env::var(key)
.ok()
.and_then(|v| v.parse().ok())
.unwrap_or(default)
}
fn peak_rss_mib() -> f64 {
std::fs::read_to_string("/proc/self/status")
.ok()
.and_then(|s| {
s.lines().find(|l| l.starts_with("VmHWM:")).and_then(|l| {
l.split_whitespace()
.nth(1)
.and_then(|kb| kb.parse::<f64>().ok())
})
})
.map(|kb| kb / 1024.0)
.unwrap_or(0.0)
}
fn hostname() -> String {
std::fs::read_to_string("/proc/sys/kernel/hostname")
.map(|s| s.trim().to_string())
.unwrap_or_else(|_| "unknown".into())
}
// ── synthetic manifest generators (one representative format per ecosystem) ──
fn gen_cargo_lock(n: usize) -> String {
let mut s = String::new();
for i in 0..n {
s.push_str(&format!(
"[[package]]\nname = \"crate{i}\"\nversion = \"1.{}.{}\"\n\n",
i % 90,
i % 40
));
}
s
}
fn gen_requirements(n: usize) -> String {
let mut s = String::new();
for i in 0..n {
s.push_str(&format!("pkg{i}=={}.{}.{}\n", i % 30, i % 90, i % 40));
}
s
}
fn gen_package_lock(n: usize) -> String {
let mut s = String::from("{\n \"lockfileVersion\": 3,\n \"packages\": {\n \"\": {\"name\": \"root\", \"version\": \"1.0.0\"}");
for i in 0..n {
s.push_str(&format!(
",\n \"node_modules/pkg{i}\": {{\"version\": \"{}.{}.{}\", \"license\": \"MIT\"}}",
i % 30,
i % 90,
i % 40
));
}
s.push_str("\n }\n}\n");
s
}
fn gen_pom(n: usize) -> String {
let mut s = String::from("<project><dependencies>");
for i in 0..n {
s.push_str(&format!(
"<dependency><groupId>com.example.g{i}</groupId><artifactId>a{i}</artifactId><version>{}.{}.{}</version></dependency>",
i % 30,
i % 90,
i % 40
));
}
s.push_str("</dependencies></project>");
s
}
fn gen_go_sum(n: usize) -> String {
let mut s = String::new();
for i in 0..n {
s.push_str(&format!(
"example.com/mod{i} v{}.{}.{} h1:abcdef=\n",
i % 30,
i % 90,
i % 40
));
}
s
}
fn gen_gemfile_lock(n: usize) -> String {
let mut s = String::from("GEM\n remote: https://rubygems.org/\n specs:\n");
for i in 0..n {
s.push_str(&format!(" gem{i} ({}.{}.{})\n", i % 30, i % 90, i % 40));
}
s
}
fn bench_one(name: &str, filename: &str, content: &str, repeats: usize) -> (usize, f64) {
let mut best = f64::INFINITY;
let mut count = 0usize;
for _ in 0..repeats {
let t = Instant::now();
let comps = parse_manifest(filename, content)
.expect("known manifest")
.expect("valid manifest");
best = best.min(t.elapsed().as_secs_f64());
count = comps.len();
std::hint::black_box(&comps);
}
let tput = count as f64 / best;
eprintln!(
"{name:<10} {filename:<20} {count:>10} comp {best:>10.4}s {tput:>14.0} comp/s ({:.1} MiB in)",
content.len() as f64 / (1024.0 * 1024.0)
);
(count, tput)
}
fn main() {
let n = env_usize("MODGUNN_BENCH_DEPS", 50_000);
let repeats = env_usize("MODGUNN_BENCH_REPEATS", 5);
let cores = std::thread::available_parallelism()
.map(|c| c.get())
.unwrap_or(1);
let host = hostname();
eprintln!("== modgunn manifest-parse HEAVY bench ==");
eprintln!("host={host} cores={cores} deps_per_manifest={n} repeats={repeats}");
eprintln!();
eprintln!(
"{:<10} {:<20} {:>10} {:>10} {:>14}",
"ecosystem", "format", "comps", "best", "throughput"
);
// Generate once (untimed), then parse repeatedly.
let cargo = gen_cargo_lock(n);
let pip = gen_requirements(n);
let npm = gen_package_lock(n);
let maven = gen_pom(n);
let go = gen_go_sum(n);
let gem = gen_gemfile_lock(n);
let results = [
bench_one("cargo", "Cargo.lock", &cargo, repeats),
bench_one("pip", "requirements.txt", &pip, repeats),
bench_one("npm", "package-lock.json", &npm, repeats),
bench_one("maven", "pom.xml", &maven, repeats),
bench_one("go", "go.sum", &go, repeats),
bench_one("gem", "Gemfile.lock", &gem, repeats),
];
let total_comps: usize = results.iter().map(|(c, _)| c).sum();
let agg_tput: f64 = results.iter().map(|(_, t)| t).sum();
let rss = peak_rss_mib();
eprintln!();
eprintln!("total components parsed: {total_comps}");
eprintln!("peak RSS: {rss:.0} MiB");
// Machine-readable one-liner for the results file.
println!(
"RESULT host={host} cores={cores} deps_per_manifest={n} repeats={repeats} \
cargo_tput={:.0} pip_tput={:.0} npm_tput={:.0} maven_tput={:.0} go_tput={:.0} gem_tput={:.0} \
total_components={total_comps} sum_tput={agg_tput:.0} peak_rss_mib={rss:.0}",
results[0].1, results[1].1, results[2].1, results[3].1, results[4].1, results[5].1
);
}