metrale_bench/benchmarks/spec_cost/
table_input.rs

1// SPDX-License-Identifier: MIT OR Apache-2.0
2
3//! 2026-10-04: Reads a spec-cost run's record back into per-width step costs, the input of
4//! `met benchmark spec-cost-table`. The writer is `cell::record`; the key names are shared
5//! with it. Pure: no I/O.
6//!
7//! The cost a table carries is the WALL time of a step (`wall_ms`, the window over the step
8//! count), split into its draft share (`draft_ms`, the timed propose phase) and the rest
9//! (`verify_ms` = `wall_ms` − `draft_ms`, which includes the scheduler loop between steps).
10//! That makes the step time agree with the energy, which is the whole window's joules over
11//! the step count, and with `k = 0`, whose step time is wall time by construction.
12//!
13//! Owner: bench, spec-cost.
14//! Invariants: a record is read only when every width it lists was measured with the rail
15//! sampled; a vacuous width, or one without joules, refuses the whole record.
16
17use std::collections::BTreeMap;
18
19use anyhow::{Context, Result, bail};
20
21use super::cell::{
22    KEY_DRAFT_J, KEY_DRAFT_MS, KEY_K, KEY_VACUOUS, KEY_VERIFY_J, KEY_WALL_MS, record_key,
23};
24
25/// 2026-10-04: One width's step cost at the record's draft depth.
26#[derive(Clone, Copy, Debug, PartialEq)]
27pub struct MeasuredCell {
28    pub n: usize,
29    pub k: usize,
30    pub verify_ms: f64,
31    pub verify_j: f64,
32    pub draft_ms: f64,
33    pub draft_j: f64,
34}
35
36/// 2026-10-04: The cells of one spec-cost record (`frame.metrics` of a run), ascending in
37/// width. Refused: no `k`, no width, a vacuous width, a width without joules (the rail was
38/// not sampled), or a draft share larger than the step.
39pub fn read(metrics: &BTreeMap<String, f64>) -> Result<Vec<MeasuredCell>> {
40    let k = *metrics
41        .get(KEY_K)
42        .context("not a spec-cost record: no `k`")?;
43    if !(k >= 0.0 && k.fract() == 0.0) {
44        bail!("spec-cost record: k = {k} is not a draft count");
45    }
46    let suffix = format!("_{KEY_VACUOUS}");
47    let mut widths: Vec<usize> = metrics
48        .keys()
49        .filter_map(|key| {
50            key.strip_prefix('n')?
51                .strip_suffix(suffix.as_str())?
52                .parse()
53                .ok()
54        })
55        .collect();
56    widths.sort_unstable();
57    if widths.is_empty() {
58        bail!("spec-cost record at k = {k}: no width");
59    }
60    let field = |n: usize, f: &str| -> Result<f64> {
61        metrics.get(&record_key(n, f)).copied().with_context(|| {
62            format!("spec-cost record at k = {k}: width {n} has no {f} (rail not sampled?)")
63        })
64    };
65    widths
66        .into_iter()
67        .map(|n| {
68            if field(n, KEY_VACUOUS)? != 0.0 {
69                bail!("spec-cost record at k = {k}: width {n} is vacuous; re-run it");
70            }
71            let (wall_ms, draft_ms) = (field(n, KEY_WALL_MS)?, field(n, KEY_DRAFT_MS)?);
72            if draft_ms > wall_ms {
73                bail!("spec-cost record at k = {k}: width {n} drafts {draft_ms} ms of a {wall_ms} ms step");
74            }
75            Ok(MeasuredCell {
76                n,
77                k: k as usize,
78                verify_ms: wall_ms - draft_ms,
79                verify_j: field(n, KEY_VERIFY_J)?,
80                draft_ms,
81                draft_j: field(n, KEY_DRAFT_J)?,
82            })
83        })
84        .collect()
85}
86
87#[cfg(test)]
88#[path = "table_input_tests.rs"]
89mod tests;