metrale_bench/benchmarks/spec_cost/
acceptance.rs1use std::collections::BTreeMap;
21
22use anyhow::{Context, Result, bail};
23
24use super::prom::Scrape;
25
26const CONFIDENCE_METRIC: &str = "metrale_spec_draft_confidence_total";
27const STEPS_METRIC: &str = "metrale_spec_verify_steps_total";
28
29#[derive(Clone, Debug, Default, PartialEq)]
31pub struct AcceptanceCounts {
32 pub edges: Vec<f32>,
34 pub accepted: Vec<u64>,
35 pub rejected: Vec<u64>,
36 pub steps: BTreeMap<(usize, usize), u64>,
38}
39
40fn delta(first: &Scrape, last: f64, name: &str, labels: &[(&str, &str)]) -> Result<u64> {
41 let before = first.value(name, labels)?.unwrap_or(0.0);
42 let d = last - before;
43 if d < 0.0 || !d.is_finite() {
44 bail!("{name}{labels:?} went from {before} to {last}: the serve restarted");
45 }
46 Ok(d as u64)
47}
48
49pub(crate) fn record(first: &Scrape, last: &Scrape, m: &mut BTreeMap<String, f64>) -> Result<()> {
53 let mut buckets: BTreeMap<String, (f32, [u64; 2])> = BTreeMap::new();
54 for s in last.series.iter().filter(|s| s.name == CONFIDENCE_METRIC) {
55 let (Some(le), Some(acc)) = (s.labels.get("le"), s.labels.get("accepted")) else {
56 bail!(
57 "{CONFIDENCE_METRIC} without le/accepted labels: {:?}",
58 s.labels
59 );
60 };
61 let edge: f32 = le
62 .parse()
63 .with_context(|| format!("{CONFIDENCE_METRIC} le={le:?}"))?;
64 let slot = match acc.as_str() {
65 "1" => 1,
66 "0" => 0,
67 other => bail!("{CONFIDENCE_METRIC} accepted={other:?}"),
68 };
69 let d = delta(
70 first,
71 s.value,
72 CONFIDENCE_METRIC,
73 &[("le", le), ("accepted", acc)],
74 )?;
75 buckets.entry(le.clone()).or_insert((edge, [0, 0])).1[slot] = d;
76 }
77 let mut ordered: Vec<(f32, [u64; 2])> = buckets.into_values().collect();
78 ordered.sort_by(|a, b| a.0.total_cmp(&b.0));
79 for (i, (edge, [rej, acc])) in ordered.into_iter().enumerate() {
80 m.insert(format!("conf{i}_le"), f64::from(edge));
81 m.insert(format!("conf{i}_accepted"), acc as f64);
82 m.insert(format!("conf{i}_rejected"), rej as f64);
83 }
84 for s in last.series.iter().filter(|s| s.name == STEPS_METRIC) {
85 let (Some(d), Some(a)) = (s.labels.get("drafts"), s.labels.get("accepted")) else {
86 bail!(
87 "{STEPS_METRIC} without drafts/accepted labels: {:?}",
88 s.labels
89 );
90 };
91 let n = delta(
92 first,
93 s.value,
94 STEPS_METRIC,
95 &[("drafts", d), ("accepted", a)],
96 )?;
97 m.insert(format!("steps_d{d}_a{a}"), n as f64);
98 }
99 Ok(())
100}
101
102pub fn read(metrics: &BTreeMap<String, f64>) -> Result<Option<AcceptanceCounts>> {
105 let mut c = AcceptanceCounts::default();
106 while let Some(&le) = metrics.get(&format!("conf{}_le", c.edges.len())) {
107 let i = c.edges.len();
108 let get = |f: &str| {
109 metrics
110 .get(&format!("conf{i}_{f}"))
111 .copied()
112 .with_context(|| format!("acceptance record: bucket {i} has no {f}"))
113 };
114 c.accepted.push(get("accepted")? as u64);
115 c.rejected.push(get("rejected")? as u64);
116 c.edges.push(le as f32);
117 }
118 if c.edges.is_empty() {
119 return Ok(None);
120 }
121 for (key, &v) in metrics {
122 let Some(rest) = key.strip_prefix("steps_d") else {
123 continue;
124 };
125 let parsed = rest
126 .split_once("_a")
127 .and_then(|(d, a)| Some((d.parse().ok()?, a.parse().ok()?)));
128 let shape = parsed.with_context(|| format!("acceptance record: bad key {key:?}"))?;
129 c.steps.insert(shape, v as u64);
130 }
131 Ok(Some(c))
132}
133
134#[cfg(test)]
135#[path = "acceptance_tests.rs"]
136mod tests;