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

nordisk/dwarves

dwarves-gui/examples/step-console-bench.rs

//! **The step-console bench** (wave 3) — frame cost of the ⚒ console leaf over a
//! 50-step synthetic DAG, and the build GRAPH's frame at gunnar scale after the
//! `BYGG <kant>` work, headless, on the shipped scene.
//!
//! ```text
//! CARGO_TARGET_DIR=… cargo run -p dwarves-gui --release --example step-console-bench
//! ```
//!
//! Prints one JSON object: `{"suite":"dwarves.step_console","results":[…]}` — the
//! same shape the `hammer-graph-bench` arms report (`frame_ms`, `graph_paint_us`,
//! `iters`), so the graph arm compares 1:1 with `dwarves.hammer_graph_frame_gunnar`.
//! It lives in `dwarves-gui/examples` because the console is dwarves-gui's; folding
//! the two arms into `bench/examples/hammer-graph-bench.rs` as registered `Bencher`s
//! is the bench lane's call.
//!
//! `NORNIR_BENCH_PREVIEW=1` shrinks iteration counts, never the scale.

use std::time::Instant;

use facett_core::Facet;
use nornir_build_thing::variant::ArtifactKind;
use nornir_robotui::egui_kittest::Harness;
use serde_json::{json, Value};

use dwarves_gui::build_forge::DeclaredForge;
use dwarves_gui::build_scene::{Act, Axis, AxisPick, BuildScene, Forge, Menu, Query, Report};
use dwarves_gui::build_steps::{Step, StepPlan, StepRole, StepState};
use dwarves_gui::build_thing_scene::{CONSOLE, GRAPH, MENU};
use dwarves_gui::tabbed::LeafPanel;

fn preview() -> bool {
    std::env::var_os("NORNIR_BENCH_PREVIEW").is_some_and(|v| !v.is_empty() && v != "0")
}

fn scaled(heavy: u64, light: u64) -> u64 {
    if preview() {
        light
    } else {
        heavy
    }
}

struct NoForge;
impl Forge for NoForge {
    fn axis(&mut self, _pick: &AxisPick) -> Axis {
        Axis::Absent
    }
    fn resolve(&mut self, _q: &Query) -> Result<Menu, String> {
        Err("bench".into())
    }
    fn act(&mut self, _act: Act) {}
    fn poll(&mut self) -> Report {
        Report::default()
    }
}

struct App(BuildScene);

/// 50 steps: 49 donors cycling through the five states, the consumer last.
fn synthetic_dag(n: usize) -> StepPlan {
    let mut steps = Vec::with_capacity(n);
    for i in 0..n - 1 {
        let coordinate = format!("donor{i}/variant{i}");
        let mut s = Step::queued(
            &coordinate,
            StepRole::Donor,
            ArtifactKind::Lib,
            vec![format!("--build {coordinate}")],
        );
        s.state = match i % 5 {
            0 => StepState::Queued,
            1 => StepState::Running {
                image: format!("nordisk-cargo-toolchain@sha256:{i:064x}"),
            },
            2 => StepState::Cached {
                key: format!("{i:064x}"),
            },
            3 => StepState::Done {
                sha: format!("{i:064x}"),
                fragment: Some(format!("{:064x}", i * 7)),
            },
            _ => StepState::Refused {
                why: format!("steg {i}: ingen [build.toolchain]"),
            },
        };
        s.elapsed_ms = (i as u64) * 1_000;
        steps.push(s);
    }
    steps.push(Step::queued(
        "orten-installer",
        StepRole::Consumer,
        ArtifactKind::Installer,
        vec![],
    ));
    StepPlan {
        product: Some("orten".into()),
        variant: "orten-installer".into(),
        edge_path: "kernel".into(),
        coordinate: "donor0/variant0".into(),
        cli: "dwarves buildthing orten --variant orten-installer --build kernel".into(),
        steps,
        settings: Default::default(),
        params: Vec::new(),
    }
}

fn console_frames(n_steps: usize, iters: u64) -> Value {
    let scene = BuildScene::new(Box::new(NoForge)).hosted();
    let mut h = Harness::builder()
        .with_size(nornir_robotui::egui::Vec2::new(1600.0, 1000.0))
        .build_ui_state(
            |ui, app: &mut App| {
                app.0.ui_leaf(CONSOLE, ui);
            },
            App(scene),
        );
    h.run();
    let plan = synthetic_dag(n_steps);
    h.state_mut()
        .0
        .stage_steps(&serde_json::to_value(&plan).unwrap())
        .expect("a plan");
    h.run();
    let t = Instant::now();
    for _ in 0..iters {
        h.run();
    }
    let frame_ms = t.elapsed().as_secs_f64() * 1000.0 / iters as f64;
    let steps = h.state().0.steps_json()["steps"]
        .as_array()
        .map(Vec::len)
        .unwrap_or(0);
    json!({
        "name": "dwarves.step_console_frame_50",
        "steps": steps,
        "frame_ms": frame_ms,
        "iters": iters,
    })
}

const PIN: &str = "949d166a1263bd0cf0c5aab44eeb57869615099e7c48bf88d0e4adb77379b999";

/// gunnar's shape — the same staged recipe `hammer-graph-bench`'s gunnar arm uses.
const GUNNAR_SHAPE: &str = r#"
[build]
base_suite = "ORIGINAL"

[[build.variant]]
name = "orten-server"
package = "orten"
bin = "orten"
role = "server"
default = true
suites = ["ORIGINAL"]
kind = "exe"

[[build.installer]]
name = "orten-installer"
variant = "orten-server"
kernel_donor = { repo = "tunnr", kernel = "tunnr-kernel" }

[[build.installer.params]]
name = "site"
kind = "text"
required = true

[[build.installer.params]]
name = "static_ip"
kind = "text"

[[build.installer.params]]
name = "hub_conf"
kind = "text"

[[build.installer.options]]
name = "dhcp"
label = "DHCP"
feature = "dhcp"
params_when_off = ["static_ip"]

[[build.installer.options]]
name = "wireguard"
label = "WireGuard"
feature = "wireguard"
params_when_on = ["hub_conf"]

[[build.variant]]
name = "orten-container"
package = "orten"
role = "server"
default = false
suites = ["ORIGINAL"]
kind = "container"
"#;

fn staged_gunnar() -> tempfile::TempDir {
    let dir = tempfile::tempdir().expect("tempdir");
    let repo = dir.path().join("orten");
    std::fs::create_dir_all(repo.join(".nornir")).expect("mkdir");
    std::fs::write(
        repo.join("Cargo.toml"),
        "[package]\nname = \"orten\"\nversion = \"1.0.0\"\nedition = \"2021\"\n\n[dependencies]\n",
    )
    .expect("manifest");
    std::fs::write(
        repo.join("Cargo.lock"),
        "version = 4\n\n[[package]]\nname = \"orten\"\nversion = \"1.0.0\"\n",
    )
    .expect("lock");
    let _ = PIN;
    std::fs::write(repo.join(".nornir/build-variants.toml"), GUNNAR_SHAPE).expect("declaration");
    dir
}

fn graph_frames(iters: u64) -> Value {
    let dir = staged_gunnar();
    let scene = BuildScene::new(Box::new(DeclaredForge::at(dir.path().to_path_buf()))).hosted();
    let mut h = Harness::builder()
        .with_size(nornir_robotui::egui::Vec2::new(1600.0, 1000.0))
        .build_ui_state(
            |ui, app: &mut App| {
                app.0.ui_leaf(GRAPH, ui);
                app.0.ui_leaf(MENU, ui);
            },
            App(scene),
        );
    h.state_mut().0.set_artifact("orten-installer");
    h.state_mut().0.resolve_artifact();
    h.run();
    h.run();
    h.state_mut().0.select_node("orten-installer");
    h.run();
    let nodes = h.state().0.graph_report()["nodes"].as_u64().unwrap_or(0);
    let verbs = Facet::state_json(&h.state().0)["menu"]["build_verbs"]
        .as_array()
        .map(Vec::len)
        .unwrap_or(0);
    let t = Instant::now();
    for _ in 0..iters {
        h.run();
    }
    let frame_ms = t.elapsed().as_secs_f64() * 1000.0 / iters as f64;
    let graph_us = h.state().0.graph_report()["last_frame_us"]
        .as_u64()
        .unwrap_or(0);
    json!({
        "name": "dwarves.hammer_graph_frame_gunnar",
        "nodes": nodes,
        "build_verbs_on_menu": verbs,
        "frame_ms": frame_ms,
        "graph_paint_us": graph_us,
        "iters": iters,
    })
}

fn main() {
    let iters = scaled(120, 10);
    let results = vec![console_frames(50, iters), graph_frames(iters)];
    println!(
        "{}",
        json!({
            "suite": "dwarves.step_console",
            "machine": std::env::var("HOSTNAME").unwrap_or_default(),
            "cores": std::thread::available_parallelism().map(|n| n.get()).unwrap_or(0),
            "results": results,
        })
    );
}