Gunnar

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
    );
}