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

nordisk/nornir

crates/mimir-room/tests/advance_bench.rs

//! **The advance beats the refold — measured.** Micro-sized for a weak
//! machine: `HISTORY` commits rewrite the same `FNS` functions, so the log
//! holds `HISTORY × FNS` node events while the room keeps `FNS` nodes. The
//! full build folds the whole history; the advance reads one commit's
//! appended files and reassembles. The ratio grows with history — on the
//! production log (every commit ever over ~45 repos) it is the whole fold.

#[allow(dead_code)]
#[path = "room/common.rs"]
mod common;

use std::time::Instant;

use common::*;
use mimir_ingest::Consumer;
use mimir_room::{Room, Scope};
use mimir_schema::{EdgeKind, Origin};

const FNS: usize = 250;
const HISTORY: usize = 40;
const BENCH: &str = "bench";

/// Commit `c`'s dump: the same functions, every body moved (`ty` carries the
/// churn so each commit writes a fresh `+` event per function).
fn step(c: usize) -> Step {
    let mut nodes = Vec::with_capacity(FNS);
    let mut edges = Vec::with_capacity(FNS);
    for i in 0..FNS {
        let id = format!("b fn{i}");
        let mut n = node(&id, "fn", "b", Origin::Local, true);
        n.ty = Some(format!("c{c}"));
        nodes.push(n);
        edges.push(edge(&id, &format!("b fn{}", (i + 1) % FNS), EdgeKind::Calls));
    }
    Step { owner: "b@0.1.0:lib", nodes, edges, binary: None, ring: vec![] }
}

async fn run_one(log: mimir_ingest::EventLog, tmp: &std::path::Path, s: Step, tag: usize) -> mimir_ingest::EventLog {
    let src = tmp.join(format!("src-{BENCH}"));
    std::fs::write(src.join("src/lib.rs"), format!("// one more: {tag}\n")).unwrap();
    nornir_git::gitio::commit_all(&src, "one more").unwrap();
    let mut c = Consumer::boot(log, Scripted(vec![s])).await.unwrap();
    let rep = c.run(&repo_spec(tmp, BENCH)).await.unwrap();
    assert!(rep.iter().all(|r| r.status == "ok"), "{rep:?}");
    c.into_parts().0
}

#[tokio::test(flavor = "current_thread")]
async fn an_advance_beats_the_refold() {
    let _g = serial().await;
    Room::clear_cache();
    Room::set_cache_capacity(16);
    let tmp = tempfile::tempdir().unwrap();
    let log = open_log(tmp.path()).await;
    let log = consume(log, tmp.path(), BENCH, (0..HISTORY).map(step).collect()).await;

    // the refold: a cold open over the whole history
    let t = Instant::now();
    let full = Room::open(&log, &Scope::current()).await.unwrap();
    let full_ms = t.elapsed().as_secs_f64() * 1000.0;
    assert!(!full.cached && !full.advanced, "cold");
    let full_stats = serde_json::to_value(full.room.stats()).unwrap();

    // one more commit lands: the kept fold advances by its delta
    let log = run_one(log, tmp.path(), step(HISTORY), HISTORY).await;
    let t = Instant::now();
    let adv = Room::open(&log, &Scope::current()).await.unwrap();
    let adv_ms = t.elapsed().as_secs_f64() * 1000.0;
    assert!(!adv.cached && adv.advanced, "the delta path served it");
    // RED before: the old head's snapshot stayed beside the advanced one
    assert_eq!(Room::cached_count(), 1, "the old head's room left with its fold: one room in memory, not two");

    // the room is whole (same shape as the refold saw, one seq later)
    let adv_stats = serde_json::to_value(adv.room.stats()).unwrap();
    assert_eq!(adv_stats["nodes"], full_stats["nodes"]);
    assert_eq!(adv_stats["edges"], full_stats["edges"]);

    eprintln!(
        "advance bench: history {HISTORY} commits x {FNS} fns — refold {full_ms:.0} ms, advance {adv_ms:.0} ms ({:.1}x)",
        full_ms / adv_ms
    );
    assert!(adv_ms < full_ms, "the advance ({adv_ms:.0} ms) must beat the refold ({full_ms:.0} ms)");

    // ---- the universe arm, over the same log: the ring room's kept fold
    // advances too (the dependency universe does not move between commits)
    // a ring view keeps no fold by default (MIMIR_RING_KEEP_FOLD): this arm opts in
    Room::set_ring_keeps_fold(true);
    let ring_scope = Scope::current().with_universe(true);
    let t = Instant::now();
    let full = Room::open(&log, &ring_scope).await.unwrap();
    let ring_full_ms = t.elapsed().as_secs_f64() * 1000.0;
    assert!(!full.cached && !full.advanced, "cold ring build");
    let full_stats = serde_json::to_value(full.room.stats()).unwrap();

    let log = run_one(log, tmp.path(), step(HISTORY + 1), HISTORY + 1).await;
    let t = Instant::now();
    let adv = Room::open(&log, &ring_scope).await.unwrap();
    let ring_adv_ms = t.elapsed().as_secs_f64() * 1000.0;
    assert!(!adv.cached && adv.advanced, "the ring room's delta path served it");
    let adv_stats = serde_json::to_value(adv.room.stats()).unwrap();
    assert_eq!(adv_stats["nodes"], full_stats["nodes"]);
    assert_eq!(adv_stats["edges"], full_stats["edges"]);
    assert_eq!(adv_stats["universe_rows"], full_stats["universe_rows"]);

    eprintln!(
        "advance bench (ring): history {} commits x {FNS} fns — refold {ring_full_ms:.0} ms, advance {ring_adv_ms:.0} ms ({:.1}x)",
        HISTORY + 1,
        ring_full_ms / ring_adv_ms
    );
    assert!(ring_adv_ms < ring_full_ms, "the ring advance ({ring_adv_ms:.0} ms) must beat the ring refold ({ring_full_ms:.0} ms)");
}