metrale_bench/gate/group.rs
1// SPDX-License-Identifier: MIT OR Apache-2.0
2
3//! 2026-09-26: Benchmark groups: one gate satisfied by a complete partition of shard runs.
4//!
5//! Owner: bench gate.
6//! Invariants:
7//! - [`select_partition`] returns only a partition holding one record per distinct
8//! index, `count` of them, all measured at one commit. It relies on each index being
9//! below its count, which `GateRecord::shard` ensures for the records `check_group`
10//! passes.
11//!
12//! A group id is a gate id: `coverage::REQUIRED`, BENCH.toml and
13//! `.github/pr-taxonomy.json` name it, and every shard is that benchmark run with
14//! `--param shard=i/n`, filed under the group. `check_one` hands a group id to
15//! `check_group`, which skips whole-draw records, judges each shard of the chosen
16//! partition by the per-record rules of a plain gate record (required subject, still
17//! standing, run status, clean tree, signature), refuses a shard with transport
18//! failures or without per-subset tallies, and sums per-subset counts
19//! ([`crate::benchmarks::bfcl::aggregate`]) rather than averaging shard scores.
20//!
21//! The aggregate is exact over its shards' counts, but shards need not answer as the
22//! whole draw run in one order would: the samples measured to differ are listed in
23//! `crate::benchmarks::bfcl::sensitive`, and a run records how many it scored as
24//! `known_partition_sensitive`.
25
26/// 2026-09-26: One shard record, as the partition rule sees it: which slice it measured,
27/// at which commit, when, and the caller's handle to the record.
28#[derive(Clone, Debug, PartialEq, Eq)]
29pub struct ShardRecord {
30 pub index: usize,
31 pub count: usize,
32 pub git_sha: String,
33 pub recorded_at: u64,
34 /// 2026-09-26: The caller's handle; `check_group` uses the position in its list.
35 pub handle: usize,
36}
37
38/// 2026-09-26: The chosen partition: its shard count, the one commit it was measured
39/// at, and the caller's handles in index order.
40#[derive(Clone, Debug, PartialEq, Eq)]
41pub struct Partition {
42 pub count: usize,
43 pub git_sha: String,
44 pub handles: Vec<usize>,
45}
46
47/// 2026-09-26: The newest complete partition among `shards`, or why there is none.
48///
49/// Records are grouped by `(count, commit)`, keeping the newest record per index, so a
50/// re-run shard replaces its earlier record instead of being counted twice. A group is
51/// complete when it holds `count` distinct indices. Among complete groups the one whose
52/// newest record is newest wins; a tie goes to the larger count. Records at two commits
53/// are never combined.
54///
55/// # Errors
56/// [`GroupFault::Missing`] when `shards` is empty or no group is complete; `held` then
57/// lists, per `(count, commit)`, the indices present.
58pub fn select_partition(
59 group: &'static str,
60 shards: &[ShardRecord],
61) -> Result<Partition, GroupFault> {
62 if shards.is_empty() {
63 return Err(GroupFault::Missing {
64 group,
65 held: Vec::new(),
66 });
67 }
68 let held = held_by(shards);
69 // 2026-09-26: Every complete partition, ranked by the age of its newest record.
70 let mut complete: Vec<(u64, &PartitionKey, &PerIndex<'_>)> = held
71 .iter()
72 .filter(|(key, per_index)| per_index.len() == key.0)
73 .map(|(key, per_index)| {
74 let newest = per_index.values().map(|s| s.recorded_at).max().unwrap_or(0);
75 (newest, key, per_index)
76 })
77 .collect();
78 complete.sort_by(|a, b| b.0.cmp(&a.0).then_with(|| b.1.0.cmp(&a.1.0)));
79 match complete.into_iter().next() {
80 Some((_, (count, git_sha), per_index)) => Ok(Partition {
81 count: *count,
82 git_sha: git_sha.clone(),
83 handles: per_index.values().map(|s| s.handle).collect(),
84 }),
85 None => Err(GroupFault::Missing {
86 group,
87 held: held
88 .iter()
89 .map(|((count, sha), per_index)| {
90 (*count, sha.clone(), per_index.keys().copied().collect())
91 })
92 .collect(),
93 }),
94 }
95}
96
97/// 2026-09-26: `(count, commit)`: what a partition is keyed by.
98pub type PartitionKey = (usize, String);
99/// 2026-09-26: The newest record per index within one partition.
100pub type PerIndex<'a> = std::collections::BTreeMap<usize, &'a ShardRecord>;
101
102/// 2026-09-26: Per `(count, commit)`, the newest record per index. On equal
103/// `recorded_at` the record that comes first in `shards` is kept.
104pub fn held_by(shards: &[ShardRecord]) -> std::collections::BTreeMap<PartitionKey, PerIndex<'_>> {
105 let mut held: std::collections::BTreeMap<PartitionKey, PerIndex<'_>> =
106 std::collections::BTreeMap::new();
107 for s in shards {
108 let slot = held
109 .entry((s.count, s.git_sha.clone()))
110 .or_default()
111 .entry(s.index)
112 .or_insert(s);
113 if s.recorded_at > slot.recorded_at {
114 *slot = s;
115 }
116 }
117 held
118}
119
120/// 2026-09-26: A gate whose measurement is produced by a partition of shard runs.
121#[derive(Clone, Copy, Debug, PartialEq, Eq)]
122pub struct BenchmarkGroup {
123 /// 2026-09-26: The gate id, which is also the benchmark every shard runs.
124 pub id: &'static str,
125}
126
127/// 2026-09-26: Every group.
128pub const GROUPS: &[BenchmarkGroup] = &[
129 BenchmarkGroup { id: "bfcl-subset" },
130 BenchmarkGroup {
131 id: "bfcl-subset-echolp",
132 },
133];
134
135/// 2026-09-26: The group a benchmark id names, if any.
136pub fn find(id: &str) -> Option<&'static BenchmarkGroup> {
137 GROUPS.iter().find(|g| g.id == id)
138}
139
140/// 2026-09-26: Why a group is not satisfied.
141#[derive(Clone, Debug, PartialEq, Eq)]
142pub enum GroupFault {
143 /// 2026-09-26: No complete partition at one commit.
144 Missing {
145 group: &'static str,
146 /// 2026-09-26: `(count, commit, indices present)` for every `(count, commit)`
147 /// among the records.
148 held: Vec<(usize, String, Vec<usize>)>,
149 },
150}
151
152impl std::fmt::Display for GroupFault {
153 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
154 match self {
155 Self::Missing { group, held } if held.is_empty() => write!(
156 f,
157 "{group} is a benchmark group and has no shard record at this commit. \
158 A group is satisfied only by a COMPLETE partition of its draw \
159 (`--param shard=i/n` for every i in 0..n at one commit): an \
160 aggregate over a subset is computed on a sample set the thresholds \
161 were never drawn against, which is a different measurement, not a \
162 partial one."
163 ),
164 Self::Missing { group, held } => write!(
165 f,
166 "{group} is a benchmark group and no shard partition is complete at one \
167 commit: {}. A group is satisfied only by a COMPLETE partition of its \
168 draw — every index 0..n once, all at one commit — since an aggregate \
169 over a subset is computed on a sample set the thresholds were never \
170 drawn against, which is a different measurement, not a partial one; \
171 and a group is ONE measurement.",
172 held.iter()
173 .map(|(n, sha, idx)| {
174 let missing: Vec<String> = (0..*n)
175 .filter(|i| !idx.contains(i))
176 .map(|i| i.to_string())
177 .collect();
178 format!(
179 "{n}-way at {} holds {:?}, missing {}",
180 sha.chars().take(10).collect::<String>(),
181 idx,
182 missing.join(",")
183 )
184 })
185 .collect::<Vec<_>>()
186 .join("; ")
187 ),
188 }
189 }
190}
191
192/// 2026-09-26: Which id's BENCH.toml entry describes how to run this benchmark:
193/// `benchmark_id` itself, since a shard runs under its group's id.
194pub fn serve_baseline_id(benchmark_id: &str) -> &str {
195 benchmark_id
196}