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

nordisk/edda

crates/nornir-saga/src/bench.rs

//! **Bench the hot execution path** (feature `bench`) — replaying (folding) a
//! saga's whole movement stream through the deterministic
//! [`Viewer`](crate::ui::Viewer). This is the crate's inner loop: the CLI replay,
//! the web SSE stream, and [`Player::verify`](crate::ui::Player::verify) all fold
//! the saga this way, so its throughput is the number worth guarding.
//!
//! Registered with the `nornir-bench` harness via [`register_bench!`]. OFF by
//! default; a release build carries no `nornir-bench` dependency.

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

use crate::ui::{demo_saga, Player, Saga};

/// The replay-fold bencher: fold a sizable deterministic saga through the viewer
/// many times and report movements-folded per second. The saga is the
/// hand-authored [`demo_saga`] repeated with a time offset so the fold is large
/// enough to time stably and identical every run (no clock, no rng).
pub struct ReplayFold {
    saga: Saga,
    reps: usize,
}

impl Default for ReplayFold {
    fn default() -> Self {
        Self::new()
    }
}

impl ReplayFold {
    /// A bencher over a `reps`-fold of a saga built from `copies` demo sessions.
    ///
    /// PREVIEW (`NORNIR_BENCH_PREVIEW=1`): the same fold through the same `Viewer`,
    /// emitting the same metric keys, over a saga 1/10 the size folded 1/10 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.
    pub fn new() -> Self {
        if nornir_bench::api::preview_run() {
            Self::with_size(4, 5)
        } else {
            Self::with_size(40, 50)
        }
    }

    /// Build the bencher explicitly: `copies` back-to-back demo sessions folded
    /// `reps` times per `run()`.
    pub fn with_size(copies: u64, reps: usize) -> Self {
        let base = demo_saga();
        let span = base.duration_ms() + 1;
        let mut saga = Saga::new("bench · replay-fold");
        for r in 0..copies {
            let off = r * span;
            for ev in &base.events {
                let mut e = ev.clone();
                e.t_ms += off;
                saga.add(e);
            }
        }
        Self { saga, reps }
    }
}

impl Bencher for ReplayFold {
    fn id(&self) -> &'static str {
        "saga-replay-fold"
    }

    fn run(&self) -> anyhow::Result<BenchResult> {
        let movements = self.saga.len();
        let t0 = std::time::Instant::now();
        let mut folded = 0usize;
        for _ in 0..self.reps {
            // `frames()` folds the whole saga from a clean viewer — the hot path.
            folded += Player::new(self.saga.clone()).frames().len();
        }
        let secs = t0.elapsed().as_secs_f64().max(1e-9);
        let mut m = serde_json::Map::new();
        m.insert("movements".into(), serde_json::json!(movements as f64));
        m.insert("reps".into(), serde_json::json!(self.reps as f64));
        m.insert("folds_per_sec".into(), serde_json::json!(folded as f64 / secs));
        Ok(BenchResult::measured(self.id(), m))
    }
}

// Register with the shared harness so `nornir bench` picks up the replay-fold
// path alongside every other nordisk bencher.
nornir_bench::register_bench!(ReplayFold::default());

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

    #[test]
    fn replay_fold_bencher_runs_and_reports_throughput() {
        let b = ReplayFold::with_size(4, 3);
        assert_eq!(b.id(), "saga-replay-fold");
        let r = b.run().expect("bench runs");
        let per_sec = r.metrics.get("folds_per_sec").and_then(|v| v.as_f64()).unwrap();
        let moves = r.metrics.get("movements").and_then(|v| v.as_f64()).unwrap();
        assert!(per_sec > 0.0, "reports a positive throughput");
        assert!(moves > 40.0, "the saga has real movement to fold");
    }
}