Your browser sucks, JavaScript disabled! Gunnar's console needs JavaScript and WebAssembly. Git over SSH on port 2222 does not.

nordisk/edda

crates/autonom/src/bench.rs

//! **Bench the scan hot path** (feature `bench`) — parse a sizeable Rust source
//! blob, build the Function surface, and compute the coverage gap, which is the
//! exact inner loop [`crate::report::NeedsTestsReport::from_scan`] runs for every
//! file in a repo. Its throughput (functions classified per second) is the number
//! worth guarding as the feeder scales to constellation-sized trees.
//!
//! Registered with the shared `nornir-bench` harness via [`register_bench!`]; the
//! harness stamps host cores + peak memory telemetry onto the row automatically.
//! OFF by default — a release build carries no `nornir-bench` dependency.

use nornir_bench::api::Bencher;
use nornir_bench::BenchResult;

use crate::feeder::Scanner;
use crate::report::NeedsTestsReport;
use crate::Allowlist;

/// The scan-and-gap bencher: generate a deterministic module of `n_fns`
/// functions (a realistic mix — a test root, a covered chain, and orphans), then
/// scan + gap it `reps` times. The generated source is identical every run (no
/// clock, no rng), so the fold is stable and comparable across hosts.
pub struct ScanFold {
    source: String,
    n_fns: usize,
    reps: usize,
}

impl Default for ScanFold {
    fn default() -> Self {
        // PREVIEW (`NORNIR_BENCH_PREVIEW=1`): the same syn parse → Function-surface fold,
        // emitting the same metric keys, over 1/10 the functions folded 1/6 as often —
        // the fast verification pass. A preview run is stamped `<host>-preview` by the
        // harness, so its number can never be mistaken for the published one.
        if nornir_bench::api::preview_run() {
            Self::with_size(60, 5)
        } else {
            Self::with_size(600, 30)
        }
    }
}

impl ScanFold {
    /// A bencher over a generated module of `n_fns` functions, scanned `reps`
    /// times per `run()`.
    pub fn with_size(n_fns: usize, reps: usize) -> Self {
        Self { source: generate_source(n_fns), n_fns, reps }
    }
}

/// Build a deterministic Rust module with `n` functions in a realistic mix: the
/// first ~2/3 form ONE long covered chain (`f{i}` calls `f{i-1}`, `f0` is the
/// base) that a single test at the head reaches — so the reachability BFS
/// traverses a deep chain, the representative work — and the remaining ~1/3 are
/// orphans nobody calls (the non-trivial gap). Exercises the parser, module-walk,
/// call resolution, and the deep BFS.
fn generate_source(n: usize) -> String {
    let chain = (n * 2 / 3).max(1);
    let mut s = String::with_capacity(n * 48);
    // The covered chain f0..f{chain-1}.
    s.push_str("pub fn f0() { let _ = 0; }\n");
    for i in 1..chain {
        s.push_str(&format!("pub fn f{i}() {{ f{}(); }}\n", i - 1));
    }
    // The orphans (no caller) → the gap.
    for i in chain..n {
        s.push_str(&format!("pub fn orphan_{i}() {{ let _ = {i}; }}\n"));
    }
    // One test at the head of the chain → reaches the whole f-chain.
    s.push_str(&format!(
        "#[cfg(test)] mod tests {{ #[test] fn drives() {{ crate::f{}(); }} }}\n",
        chain - 1,
    ));
    s
}

impl Bencher for ScanFold {
    fn id(&self) -> &'static str {
        "autonom.function_scan"
    }

    fn run(&self) -> anyhow::Result<BenchResult> {
        let t0 = std::time::Instant::now();
        let mut classified = 0usize;
        let mut last_gap = 0usize;
        for _ in 0..self.reps {
            let mut sc = Scanner::new();
            sc.add_source(&self.source, "bench", false);
            let scan = sc.finish();
            let report = NeedsTestsReport::from_scan("bench", &scan, &Allowlist::new());
            classified += report.total_functions;
            last_gap = report.gap_count();
        }
        let secs = t0.elapsed().as_secs_f64().max(1e-9);
        let mut m = serde_json::Map::new();
        m.insert("functions".into(), serde_json::json!(self.n_fns as f64));
        m.insert("reps".into(), serde_json::json!(self.reps as f64));
        m.insert("gap".into(), serde_json::json!(last_gap as f64));
        m.insert("fns_per_sec".into(), serde_json::json!(classified as f64 / secs));
        Ok(BenchResult::measured(self.id(), m))
    }
}

// Register with the shared harness so `nornir bench` / the example entrypoint
// picks up the scan path alongside every other nordisk bencher.
nornir_bench::register_bench!(ScanFold::default());

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn scan_fold_bencher_runs_and_reports_throughput() {
        let b = ScanFold::with_size(30, 2);
        assert_eq!(b.id(), "autonom.function_scan");
        let r = b.run().expect("bench runs");
        let per_sec = r.metrics.get("fns_per_sec").and_then(|v| v.as_f64()).unwrap();
        let gap = r.metrics.get("gap").and_then(|v| v.as_f64()).unwrap();
        assert!(per_sec > 0.0, "reports positive throughput");
        assert!(gap > 0.0, "the generated module has real untested orphans to find");
    }

    #[test]
    fn generated_source_parses_and_has_a_gap() {
        let src = generate_source(30);
        let mut sc = Scanner::new();
        sc.add_source(&src, "bench", false);
        let scan = sc.finish();
        assert_eq!(scan.files_skipped, 0, "generated source must parse");
        let report = NeedsTestsReport::from_scan("bench", &scan, &Allowlist::new());
        assert!(report.gap_count() > 0, "orphans are the gap");
        assert!(report.covered > 0, "the f-chain is covered by the head test");
    }
}