pub mod api;
pub mod assets;
pub mod history;
pub mod legacy;
pub mod progress;
pub mod telemetry;
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct BenchResult {
pub name: String,
#[serde(flatten)]
pub metrics: serde_json::Map<String, serde_json::Value>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TestOutcome {
pub name: String,
pub passed: bool,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub duration_ms: Option<f64>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub message: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct BenchRun {
pub date: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub timestamp: Option<String>,
#[serde(default)]
pub version: String,
#[serde(default)]
pub machine: String,
#[serde(default)]
pub cores: u32,
pub results: Vec<BenchResult>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub tests: Vec<TestOutcome>,
}
impl BenchRun {
pub fn find(&self, name: &str) -> Option<&BenchResult> {
self.results.iter().find(|r| r.name == name)
}
pub fn all_tests_passed(&self) -> bool {
self.tests.iter().all(|t| t.passed)
}
pub fn failed_tests(&self) -> Vec<&str> {
self.tests
.iter()
.filter(|t| !t.passed)
.map(|t| t.name.as_str())
.collect()
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum MetricDirection {
High,
Low,
Neutral,
}
pub fn direction_of(
overrides: &std::collections::HashMap<String, MetricDirection>,
metric: &str,
) -> MetricDirection {
if let Some(d) = overrides.get(metric) {
return *d;
}
unit_of(metric).map(|u| u.direction).unwrap_or(MetricDirection::Neutral)
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Unit {
pub name: &'static str,
pub direction: MetricDirection,
}
pub fn unit_of(metric: &str) -> Option<Unit> {
use MetricDirection::*;
let m = metric.to_ascii_lowercase();
const TABLE: &[(&str, &str, MetricDirection)] = &[
("mb_per_sec", "mbs", High),
("mbps", "mbs", High),
("_mbs", "mbs", High),
("gb_per_sec", "gbs", High),
("_gbs", "gbs", High),
("ops_per_sec", "ops_sec", High),
("ops_sec", "ops_sec", High),
("_ops_sec", "ops_sec", High),
("_per_sec", "per_sec", High),
("seconds", "secs", Low),
("_secs", "secs", Low),
("_s", "secs", Low),
("_ms", "ms", Low),
("_us", "us", Low),
("_ns", "ns", Low),
("_pct", "pct", Neutral),
("_x", "x", Neutral),
("speedup", "x", Neutral),
];
for (suffix, name, dir) in TABLE {
if m == *suffix || m.ends_with(suffix) {
return Some(Unit { name, direction: *dir });
}
}
None
}
#[cfg(test)]
mod direction_tests {
use super::*;
use std::collections::HashMap;
#[test]
fn unit_and_direction_from_name() {
assert_eq!(unit_of("ljar_mbs").unwrap().name, "mbs");
assert_eq!(unit_of("unzip_mbs").unwrap().direction, MetricDirection::High);
assert_eq!(unit_of("mb_per_sec").unwrap().name, "mbs");
assert_eq!(unit_of("holger_ops_sec").unwrap().name, "ops_sec");
assert_eq!(unit_of("decode_ms").unwrap().direction, MetricDirection::Low);
assert_eq!(unit_of("seconds").unwrap().direction, MetricDirection::Low);
assert_eq!(unit_of("speedup_x").unwrap().direction, MetricDirection::Neutral);
assert!(unit_of("bytes").is_none());
assert!(unit_of("files").is_none());
}
#[test]
fn explicit_override_wins() {
let mut o = HashMap::new();
o.insert("weird_metric".to_string(), MetricDirection::Low);
assert_eq!(direction_of(&o, "weird_metric"), MetricDirection::Low);
assert_eq!(direction_of(&o, "ljar_mbs"), MetricDirection::High);
assert_eq!(direction_of(&o, "count"), MetricDirection::Neutral);
}
}
pub fn history_outcome(
repo: &str,
mut runs: Vec<BenchRun>,
limit: usize,
) -> crate::cli_outcome::CommandOutcome {
use crate::cli_outcome::CommandOutcome;
runs.sort_by(|x, y| {
let kx = x.timestamp.as_deref().unwrap_or(&x.date);
let ky = y.timestamp.as_deref().unwrap_or(&y.date);
ky.cmp(kx)
});
if limit > 0 {
runs.truncate(limit);
}
if runs.is_empty() {
return CommandOutcome::fail(
"bench history-show",
format!("no bench runs recorded for `{repo}`"),
);
}
let mut human = format!("{repo} — {} run(s)", runs.len());
for r in &runs {
let date = if r.date.is_empty() { "-" } else { r.date.as_str() };
let machine = if r.machine.is_empty() { "-" } else { r.machine.as_str() };
human.push_str(&format!("\n\nv{} · {} · {} cores · {}", r.version, machine, r.cores, date));
let mut scalars: Vec<String> = Vec::new();
for res in &r.results {
for (k, v) in &res.metrics {
if let Some(f) = v.as_f64() {
scalars.push(format!("{}.{k}={f:.2}", res.name));
}
}
}
scalars.sort();
if scalars.is_empty() {
human.push_str("\n metrics: (none)");
} else {
human.push_str(&format!("\n metrics: {}", scalars.join(" ")));
}
if r.tests.is_empty() {
human.push_str("\n tests: (none recorded)");
} else {
let passed = r.tests.iter().filter(|t| t.passed).count();
let failed = r.tests.len() - passed;
human.push_str(&format!("\n tests: {passed} passed, {failed} failed"));
for t in r.tests.iter().filter(|t| !t.passed) {
let msg = t.message.as_deref().unwrap_or("");
human.push_str(&format!("\n ✗ {} {}", t.name, msg));
}
}
}
let data = serde_json::json!({ "repo": repo, "runs": runs });
CommandOutcome::ok("bench history-show", data, human)
}
#[cfg(test)]
mod history_outcome_tests {
use super::*;
fn sample_run(date: &str, machine: &str) -> BenchRun {
BenchRun {
date: date.to_string(),
timestamp: Some(format!("{date}T00:00:00Z")),
version: "0.1.0".into(),
machine: machine.into(),
cores: 8,
results: Vec::new(),
tests: Vec::new(),
}
}
#[test]
fn empty_history_is_red_not_silently_green() {
let o = history_outcome("holger", Vec::new(), 0);
assert_eq!(o.command, "bench history-show");
assert!(!o.is_sannr(), "empty bench history must be RED (RAGNARÖK)");
assert!(o.human.contains("no bench runs"));
}
#[test]
fn real_runs_are_sannr_newest_first_and_limited() {
let runs = vec![sample_run("2026-06-01", "ryzen"), sample_run("2026-06-03", "epyc")];
let o = history_outcome("holger", runs, 1);
assert!(o.is_sannr(), "a real run is a true (sannr) outcome");
let arr = o.data["runs"].as_array().unwrap();
assert_eq!(arr.len(), 1, "limit=1 truncates");
assert_eq!(arr[0]["machine"], serde_json::json!("epyc"), "newest (2026-06-03) first");
assert_eq!(o.data["repo"], serde_json::json!("holger"));
}
}