use crate::{Config, Reason};
use std::collections::BTreeMap;
use std::fmt;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Environment {
pub machine: String,
pub os: String,
pub arch: String,
pub date: String,
pub digest: Option<String>,
pub rerun: String,
pub host: Option<String>,
}
impl Environment {
pub fn detect(rerun: impl Into<String>, digest: Option<String>) -> Self {
Environment {
machine: machine(),
os: std::env::consts::OS.to_string(),
arch: std::env::consts::ARCH.to_string(),
date: today_utc(),
digest,
rerun: clean(&rerun.into(), true),
host: None,
}
}
pub fn comparable_key(&self) -> String {
format!("{} | {} | {}", self.machine, self.os, self.arch)
}
fn to_config(&self) -> Config {
let mut c = Config::new()
.with("arch", &self.arch)
.with("date", &self.date)
.with("machine", clean(&self.machine, false))
.with("os", &self.os);
if let Some(d) = &self.digest {
c = c.with("digest", clean(d, false));
}
if let Some(h) = &self.host {
c = c.with("host", clean(h, false));
}
c
}
}
pub fn host_snapshot() -> String {
if let (Ok(load), Ok(mem)) = (
std::fs::read_to_string("/proc/loadavg"),
std::fs::read_to_string("/proc/meminfo"),
) {
let load1 = load.split_whitespace().next().unwrap_or("?");
let kb = mem
.lines()
.find_map(|l| l.strip_prefix("MemAvailable:"))
.and_then(|v| v.split_whitespace().next())
.and_then(|v| v.parse::<u64>().ok());
if let Some(kb) = kb {
return format!("load1:{load1},ram_available_mb:{}", kb / 1024);
}
}
let script = "$o=Get-CimInstance Win32_OperatingSystem; $c=(Get-CimInstance Win32_Processor | Measure-Object LoadPercentage -Average).Average; \"cpu_load_pct:$([int]$c),ram_available_mb:$([int]($o.FreePhysicalMemory/1024))\"";
std::process::Command::new("powershell")
.args(["-NoProfile", "-NonInteractive", "-Command", script])
.output()
.ok()
.filter(|o| o.status.success())
.map(|o| String::from_utf8_lossy(&o.stdout).trim().to_string())
.filter(|t| t.starts_with("cpu_load_pct:"))
.unwrap_or_else(|| "unavailable".to_string())
}
fn clean(s: &str, free_text: bool) -> String {
s.chars()
.map(|c| match c {
'\t' | '\n' | '\r' => ' ',
';' | '=' if !free_text => ' ',
c => c,
})
.collect::<String>()
.trim()
.to_string()
}
fn machine() -> String {
let cores = std::thread::available_parallelism().map_or(0, std::num::NonZero::get);
let model = std::env::var("PROCESSOR_IDENTIFIER")
.ok()
.or_else(|| {
std::fs::read_to_string("/proc/cpuinfo").ok().and_then(|t| {
t.lines()
.find(|l| l.starts_with("model name"))
.and_then(|l| l.split_once(':'))
.map(|(_, v)| v.trim().to_string())
})
})
.unwrap_or_else(|| "unknown cpu".to_string());
format!("{} x{cores}", clean(&model, false))
}
fn today_utc() -> String {
let secs = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.map_or(0, |d| d.as_secs());
let (y, m, d) = civil_from_days(i64::try_from(secs / 86_400).unwrap_or(0));
format!("{y:04}-{m:02}-{d:02}")
}
fn civil_from_days(z: i64) -> (i64, u32, u32) {
let z = z + 719_468;
let era = z.div_euclid(146_097);
let doe = z.rem_euclid(146_097);
let yoe = (doe - doe / 1460 + doe / 36_524 - doe / 146_096) / 365;
let y = yoe + era * 400;
let doy = doe - (365 * yoe + yoe / 4 - yoe / 100);
let mp = (5 * doy + 2) / 153;
let d = u32::try_from(doy - (153 * mp + 2) / 5 + 1).unwrap_or(1);
let m = u32::try_from(if mp < 10 { mp + 3 } else { mp - 9 }).unwrap_or(1);
(if m <= 2 { y + 1 } else { y }, m, d)
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Stats {
pub n: u32,
pub p50_ns: u64,
pub p95_ns: u64,
pub p99_ns: u64,
pub max_ns: u64,
pub ops_per_sec: u64,
}
impl Stats {
pub fn from_samples(mut us: Vec<u64>) -> Option<Self> {
if us.is_empty() {
return None;
}
us.sort_unstable();
let n = us.len();
let at = |p: usize| us[(n * p).div_ceil(100).saturating_sub(1).min(n - 1)];
let total: u64 = us.iter().sum();
Some(Stats {
n: u32::try_from(n).unwrap_or(u32::MAX),
p50_ns: at(50),
p95_ns: at(95),
p99_ns: at(99),
max_ns: us[n - 1],
ops_per_sec: u64::try_from(n)
.unwrap_or(u64::MAX)
.saturating_mul(1_000_000_000)
.checked_div(total)
.unwrap_or(0),
})
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Outcome {
Measured(Stats),
Excluded {
leaf: String,
reason: Reason,
note: String,
},
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct SpeedRow {
pub workload: String,
pub backend: String,
pub version: String,
pub config: Config,
pub env: Environment,
pub outcome: Outcome,
}
const HEADER: &str = "workload\tbackend\tversion\tconfig\tstatus\tn\tp50_ns\tp95_ns\tp99_ns\tmax_ns\tops_per_sec\tnote\tenv\trerun";
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct SpeedParseError(pub String);
impl fmt::Display for SpeedParseError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "speed file: {}", self.0)
}
}
impl std::error::Error for SpeedParseError {}
impl SpeedRow {
fn to_line(&self) -> String {
let (status, nums, note) = match &self.outcome {
Outcome::Measured(s) => (
"measured",
[
s.n.to_string(),
s.p50_ns.to_string(),
s.p95_ns.to_string(),
s.p99_ns.to_string(),
s.max_ns.to_string(),
s.ops_per_sec.to_string(),
],
String::new(),
),
Outcome::Excluded { leaf, reason, note } => (
"excluded",
Default::default(),
format!("{leaf}: {}: {}", reason.as_str(), clean(note, true)),
),
};
let mut cols: Vec<String> = vec![
self.workload.clone(),
self.backend.clone(),
self.version.clone(),
self.config.to_string(),
status.to_string(),
];
cols.extend(nums);
cols.push(note);
cols.push(self.env.to_config().to_string());
cols.push(self.env.rerun.clone());
cols.join("\t")
}
fn from_line(line: &str) -> Result<Self, SpeedParseError> {
let c: Vec<&str> = line.split('\t').collect();
if c.len() != 14 {
return Err(SpeedParseError(format!(
"{} columns, want 14: {line:?}",
c.len()
)));
}
let bad = |what: &str| SpeedParseError(format!("{what} in {line:?}"));
let env_cfg = Config::parse(c[12]).map_err(|e| SpeedParseError(e.to_string()))?;
let get = |k: &str| env_cfg.get(k).map(str::to_string).ok_or_else(|| bad(k));
let env = Environment {
machine: get("machine")?,
os: get("os")?,
arch: get("arch")?,
date: get("date")?,
digest: env_cfg.get("digest").map(str::to_string),
rerun: c[13].to_string(),
host: env_cfg.get("host").map(str::to_string),
};
let num = |i: usize| c[i].parse::<u64>().map_err(|_| bad("number"));
let outcome = match c[4] {
"measured" => Outcome::Measured(Stats {
n: u32::try_from(num(5)?).map_err(|_| bad("n"))?,
p50_ns: num(6)?,
p95_ns: num(7)?,
p99_ns: num(8)?,
max_ns: num(9)?,
ops_per_sec: num(10)?,
}),
"excluded" => {
let mut parts = c[11].splitn(3, ": ");
let (leaf, reason, note) = (
parts.next().unwrap_or(""),
parts.next().unwrap_or(""),
parts.next().unwrap_or(""),
);
let reason = match reason {
"missing" => Reason::Missing,
"wrong" => Reason::Wrong,
"untested" => Reason::Untested,
_ => return Err(bad("reason")),
};
Outcome::Excluded {
leaf: leaf.to_string(),
reason,
note: note.to_string(),
}
}
_ => return Err(bad("status")),
};
Ok(SpeedRow {
workload: c[0].to_string(),
backend: c[1].to_string(),
version: c[2].to_string(),
config: Config::parse(c[3]).map_err(|e| SpeedParseError(e.to_string()))?,
env,
outcome,
})
}
}
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct SpeedTable {
rows: Vec<SpeedRow>,
}
impl SpeedTable {
pub fn from_rows(rows: Vec<SpeedRow>) -> Self {
SpeedTable { rows }
}
pub fn rows(&self) -> &[SpeedRow] {
&self.rows
}
pub fn merge(&mut self, other: SpeedTable) {
self.rows.extend(other.rows);
}
pub fn to_tsv(&self) -> String {
let mut out = String::from(HEADER);
out.push('\n');
for r in &self.rows {
out.push_str(&r.to_line());
out.push('\n');
}
out
}
pub fn parse(text: &str) -> Result<Self, SpeedParseError> {
let mut lines = text.lines();
if lines.next() != Some(HEADER) {
return Err(SpeedParseError("missing or wrong header".to_string()));
}
let rows = lines
.filter(|l| !l.is_empty())
.map(SpeedRow::from_line)
.collect::<Result<_, _>>()?;
Ok(SpeedTable { rows })
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Ranked {
pub backend: String,
pub version: String,
pub config: Config,
pub stats: Stats,
pub date: String,
pub rerun: String,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Left {
pub backend: String,
pub version: String,
pub leaf: String,
pub reason: Reason,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Comparison {
pub workload: String,
pub environment: String,
pub ranked: Vec<Ranked>,
pub excluded: Vec<Left>,
}
pub fn compare(table: &SpeedTable) -> Vec<Comparison> {
let mut groups: BTreeMap<(String, String), Comparison> = BTreeMap::new();
for r in table.rows() {
let key = (r.workload.clone(), r.env.comparable_key());
let g = groups.entry(key.clone()).or_insert_with(|| Comparison {
workload: key.0.clone(),
environment: key.1.clone(),
ranked: Vec::new(),
excluded: Vec::new(),
});
match &r.outcome {
Outcome::Measured(s) => g.ranked.push(Ranked {
backend: r.backend.clone(),
version: r.version.clone(),
config: r.config.clone(),
stats: *s,
date: r.env.date.clone(),
rerun: r.env.rerun.clone(),
}),
Outcome::Excluded { leaf, reason, .. } => g.excluded.push(Left {
backend: r.backend.clone(),
version: r.version.clone(),
leaf: leaf.clone(),
reason: *reason,
}),
}
}
let mut out: Vec<Comparison> = groups.into_values().collect();
for g in &mut out {
g.ranked.sort_by(|a, b| {
(a.stats.p50_ns, &a.backend, &a.version).cmp(&(b.stats.p50_ns, &b.backend, &b.version))
});
}
out
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Position {
pub backend: String,
pub version: String,
pub config: Config,
pub places: Vec<Option<(usize, u64)>>,
pub dominated_by: Vec<String>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Profile {
pub environment: String,
pub workloads: Vec<String>,
pub positions: Vec<Position>,
}
pub fn compare_across(table: &SpeedTable, workloads: &[&str]) -> Vec<Profile> {
let mut by_env: BTreeMap<String, Vec<Comparison>> = BTreeMap::new();
for c in compare(table) {
if workloads.contains(&c.workload.as_str()) {
by_env.entry(c.environment.clone()).or_default().push(c);
}
}
let mut out = Vec::new();
for (environment, comps) in by_env {
let mut who: BTreeMap<(String, String, String), Position> = BTreeMap::new();
for (wi, w) in workloads.iter().enumerate() {
let Some(c) = comps.iter().find(|c| c.workload == *w) else {
continue;
};
for (i, r) in c.ranked.iter().enumerate() {
let p = who
.entry((r.backend.clone(), r.version.clone(), r.config.to_string()))
.or_insert_with(|| Position {
backend: r.backend.clone(),
version: r.version.clone(),
config: r.config.clone(),
places: vec![None; workloads.len()],
dominated_by: Vec::new(),
});
p.places[wi] = Some((i + 1, r.stats.p50_ns));
}
}
let mut positions: Vec<Position> = who.into_values().collect();
let full: Vec<Option<Vec<u64>>> = positions
.iter()
.map(|p| p.places.iter().map(|x| x.map(|(_, t)| t)).collect())
.collect();
for i in 0..positions.len() {
let Some(mine) = &full[i] else { continue };
for (j, other) in full.iter().enumerate() {
let Some(theirs) = other else { continue };
if i != j
&& theirs.iter().zip(mine).all(|(t, m)| t <= m)
&& theirs.iter().zip(mine).any(|(t, m)| t < m)
{
let name = format!("{} {}", positions[j].backend, positions[j].version);
positions[i].dominated_by.push(name);
}
}
}
out.push(Profile {
environment,
workloads: workloads.iter().map(|w| (*w).to_string()).collect(),
positions,
});
}
out
}
#[cfg(test)]
mod tests {
use super::*;
fn env(machine: &str) -> Environment {
Environment {
machine: machine.to_string(),
os: "linux".to_string(),
arch: "x86_64".to_string(),
date: "2026-10-16".to_string(),
digest: Some("sha256:abc".to_string()),
rerun: "cargo run --example speed".to_string(),
host: None,
}
}
fn measured(backend: &str, machine: &str, p50: u64) -> SpeedRow {
SpeedRow {
workload: "speed.kv.get".to_string(),
backend: backend.to_string(),
version: "1".to_string(),
config: Config::new(),
env: env(machine),
outcome: Outcome::Measured(Stats::from_samples(vec![p50; 10]).unwrap()),
}
}
#[test]
fn percentiles_are_nearest_rank_and_an_empty_sample_has_none() {
assert!(Stats::from_samples(vec![]).is_none());
let s = Stats::from_samples((1..=100).collect()).unwrap();
assert_eq!(
(s.n, s.p50_ns, s.p95_ns, s.p99_ns, s.max_ns),
(100, 50, 95, 99, 100)
);
let one = Stats::from_samples(vec![7]).unwrap();
assert_eq!((one.p50_ns, one.p99_ns, one.max_ns), (7, 7, 7));
}
#[test]
fn dates_are_civil_utc() {
assert_eq!(civil_from_days(0), (1970, 1, 1));
assert_eq!(civil_from_days(19_000), (2022, 1, 8));
assert_eq!(civil_from_days(20_742), (2026, 10, 16));
}
#[test]
fn a_table_round_trips_measured_and_excluded_rows() {
let mut rows = vec![measured("a", "m", 5)];
rows.push(SpeedRow {
outcome: Outcome::Excluded {
leaf: "kv.get".to_string(),
reason: Reason::Wrong,
note: "lost a write".to_string(),
},
..measured("b", "m", 1)
});
let t = SpeedTable::from_rows(rows);
assert_eq!(SpeedTable::parse(&t.to_tsv()).unwrap(), t);
}
#[test]
fn a_malformed_file_is_an_error_not_an_empty_table() {
assert!(SpeedTable::parse("nope").is_err());
assert!(SpeedTable::parse(&format!("{HEADER}\ntoo\tfew")).is_err());
}
#[test]
fn rankings_never_cross_machines_and_excluded_backends_are_listed_not_ranked() {
let mut rows = vec![
measured("slow", "laptop", 900),
measured("fast", "laptop", 20),
measured("slow", "server", 5),
measured("fast", "server", 50),
];
rows.push(SpeedRow {
outcome: Outcome::Excluded {
leaf: "kv.get".to_string(),
reason: Reason::Wrong,
note: "lost a write".to_string(),
},
..measured("broken", "laptop", 1)
});
let cmp = compare(&SpeedTable::from_rows(rows));
assert_eq!(cmp.len(), 2, "one comparison per environment");
let laptop = cmp
.iter()
.find(|c| c.environment.starts_with("laptop"))
.unwrap();
let names: Vec<_> = laptop.ranked.iter().map(|r| r.backend.as_str()).collect();
assert_eq!(names, ["fast", "slow"]);
assert_eq!(laptop.excluded.len(), 1);
assert_eq!(laptop.excluded[0].backend, "broken");
let server = cmp
.iter()
.find(|c| c.environment.starts_with("server"))
.unwrap();
let names: Vec<_> = server.ranked.iter().map(|r| r.backend.as_str()).collect();
assert_eq!(names, ["slow", "fast"]);
}
fn measured_on(workload: &str, backend: &str, p50: u64) -> SpeedRow {
SpeedRow {
workload: workload.to_string(),
..measured(backend, "laptop", p50)
}
}
#[test]
fn several_workloads_are_shown_side_by_side_with_no_combined_score() {
let rows = vec![
measured_on("speed.kv.get", "a", 10),
measured_on("speed.kv.get", "b", 20),
measured_on("speed.kv.get", "c", 30),
measured_on("speed.kv.put", "a", 900),
measured_on("speed.kv.put", "b", 100),
measured_on("speed.kv.put", "c", 500),
measured_on("speed.kv.get", "d", 5),
measured_on("speed.kv.batch", "a", 1),
];
let t = SpeedTable::from_rows(rows);
let prof = compare_across(&t, &["speed.kv.get", "speed.kv.put"]);
assert_eq!(prof.len(), 1);
let p = &prof[0];
assert_eq!(p.workloads, ["speed.kv.get", "speed.kv.put"]);
let names: Vec<_> = p.positions.iter().map(|x| x.backend.as_str()).collect();
assert_eq!(names, ["a", "b", "c", "d"], "ordered by name, not by speed");
let places = |n: &str| p.positions.iter().find(|x| x.backend == n).unwrap();
assert_eq!(places("d").places, [Some((1, 5)), None]);
assert_eq!(places("a").places, [Some((2, 10)), Some((3, 900))]);
assert_eq!(places("b").places, [Some((3, 20)), Some((1, 100))]);
assert_eq!(places("c").dominated_by, ["b 1"]);
assert!(places("a").dominated_by.is_empty());
assert!(places("b").dominated_by.is_empty());
assert!(places("d").dominated_by.is_empty());
assert_eq!(compare_across(&t, &["speed.kv.nothing"]).len(), 0);
assert_eq!(compare_across(&t, &["speed.kv.get"])[0].positions.len(), 4);
}
#[test]
fn profiles_never_cross_machines() {
let rows = vec![measured("a", "laptop", 10), measured("a", "server", 99)];
let prof = compare_across(&SpeedTable::from_rows(rows), &["speed.kv.get"]);
assert_eq!(prof.len(), 2);
assert!(prof.iter().all(|p| p.positions.len() == 1));
}
}