Skip to main content

soothfast_report/
perf_table.rs

1//! Perf tables from a baseline document: one row per item, the deterministic
2//! metrics first.
3
4use serde_json::Value;
5
6/// One measured item's metrics, each `None` when that backend didn't run.
7pub struct Row {
8    pub id: String,
9    pub instructions: Option<u64>,
10    pub median_ns: Option<f64>,
11    pub p99_ns: Option<f64>,
12    pub allocs: Option<u64>,
13    pub polls: Option<u64>,
14}
15
16/// One `Row` per measured item in the baseline, in baseline (map) order.
17pub fn rows(baseline: &Value) -> Vec<Row> {
18    let Some(items) = baseline["items"].as_object() else {
19        return Vec::new();
20    };
21    items
22        .iter()
23        .map(|(id, m)| Row {
24            id: id.clone(),
25            instructions: m["perfcnt"]["instructions"].as_u64(),
26            median_ns: m["walltime"]["median_ns"].as_f64(),
27            p99_ns: m["walltime"]["p99_ns"].as_f64(),
28            allocs: m["alloc"]["allocs"].as_u64(),
29            polls: m["asyncexec"]["polls"].as_u64(),
30        })
31        .collect()
32}
33
34fn fmt_u(v: Option<u64>) -> String {
35    v.map(|x| x.to_string()).unwrap_or_else(|| "n/a".into())
36}
37fn fmt_ns(v: Option<f64>) -> String {
38    match v {
39        Some(ns) if ns >= 1e6 => format!("{:.2}ms", ns / 1e6),
40        Some(ns) if ns >= 1e3 => format!("{:.2}µs", ns / 1e3),
41        Some(ns) => format!("{ns:.1}ns"),
42        None => "n/a".into(),
43    }
44}
45
46/// `rows` rendered as a GitHub-flavored markdown table.
47pub fn markdown(baseline: &Value) -> String {
48    let mut out = String::from(
49        "| item | instructions | median | p99 | allocs | polls |\n|---|---:|---:|---:|---:|---:|\n",
50    );
51    for r in rows(baseline) {
52        out.push_str(&format!(
53            "| `{}` | {} | {} | {} | {} | {} |\n",
54            r.id,
55            fmt_u(r.instructions),
56            fmt_ns(r.median_ns),
57            fmt_ns(r.p99_ns),
58            fmt_u(r.allocs),
59            fmt_u(r.polls),
60        ));
61    }
62    out
63}
64
65/// `rows` rendered as a plain `<table>` for embedding in the docs site.
66pub fn html(baseline: &Value) -> String {
67    let mut out = String::from(
68        "<table>\n<thead><tr><th>item</th><th>instructions</th><th>median</th><th>p99</th><th>allocs</th><th>polls</th></tr></thead>\n<tbody>\n",
69    );
70    for r in rows(baseline) {
71        out.push_str(&format!(
72            "<tr><td><code>{}</code></td><td>{}</td><td>{}</td><td>{}</td><td>{}</td><td>{}</td></tr>\n",
73            r.id,
74            fmt_u(r.instructions),
75            fmt_ns(r.median_ns),
76            fmt_ns(r.p99_ns),
77            fmt_u(r.allocs),
78            fmt_u(r.polls),
79        ));
80    }
81    out.push_str("</tbody>\n</table>\n");
82    out
83}
84
85#[cfg(test)]
86mod tests {
87    use serde_json::json;
88
89    #[test]
90    fn renders_markdown_rows() {
91        let baseline = json!({ "items": { "demo::f": {
92            "perfcnt": { "instructions": 98 },
93            "walltime": { "median_ns": 13.7, "p99_ns": 15.0 },
94            "alloc": { "allocs": 0, "bytes": 0 }
95        }}});
96        let md = super::markdown(&baseline);
97        assert!(md.contains("| `demo::f` | 98 | 13.7ns | 15.0ns | 0 | n/a |"));
98    }
99}