openvm_prof/
aggregate.rs

1use std::{collections::HashMap, io::Write};
2
3use eyre::Result;
4use serde::{Deserialize, Serialize};
5
6use crate::types::{BencherValue, BenchmarkOutput, Labels, MdTableCell, MetricDb};
7
8type MetricName = String;
9type MetricsByName = HashMap<MetricName, Vec<(f64, Labels)>>;
10
11#[derive(Clone, Debug, Default)]
12pub struct GroupedMetrics {
13    /// "group" label => metrics with that "group" label, further grouped by metric name
14    pub by_group: HashMap<String, MetricsByName>,
15    pub ungrouped: MetricsByName,
16}
17
18#[derive(Clone, Debug, Default, Serialize, Deserialize)]
19pub struct AggregateMetrics {
20    /// "group" label => metric aggregate statistics
21    #[serde(flatten)]
22    pub by_group: HashMap<String, HashMap<MetricName, Stats>>,
23    /// In seconds
24    pub total_proof_time: MdTableCell,
25    /// In seconds (infinite parallelism)
26    pub total_par_proof_time: MdTableCell,
27    /// Per-group bounded parallel proof time in seconds
28    #[serde(skip)]
29    pub bounded_par_by_group: HashMap<String, MdTableCell>,
30}
31
32#[derive(Clone, Debug, Serialize, Deserialize)]
33pub struct BencherAggregateMetrics {
34    #[serde(flatten)]
35    pub by_group: HashMap<String, HashMap<String, BencherValue>>,
36    /// In seconds
37    pub total_proof_time: BencherValue,
38    /// In seconds
39    pub total_par_proof_time: BencherValue,
40}
41
42#[derive(Clone, Debug, Serialize, Deserialize)]
43pub struct Stats {
44    pub sum: MdTableCell,
45    pub max: MdTableCell,
46    pub min: MdTableCell,
47    pub avg: MdTableCell,
48    #[serde(skip)]
49    pub count: usize,
50    #[serde(skip)]
51    pub phase: Option<String>,
52}
53
54impl Default for Stats {
55    fn default() -> Self {
56        Self::new()
57    }
58}
59
60impl Stats {
61    pub fn new() -> Self {
62        Self {
63            sum: MdTableCell::default(),
64            max: MdTableCell::default(),
65            min: MdTableCell::new(f64::MAX, None),
66            avg: MdTableCell::default(),
67            count: 0,
68            phase: None,
69        }
70    }
71    pub fn push(&mut self, value: f64) {
72        self.sum.val += value;
73        self.count += 1;
74        if value > self.max.val {
75            self.max.val = value;
76        }
77        if value < self.min.val {
78            self.min.val = value;
79        }
80    }
81
82    pub fn finalize(&mut self) {
83        assert!(self.count != 0);
84        self.avg.val = self.sum.val / self.count as f64;
85    }
86
87    pub fn set_diff(&mut self, prev: &Self) {
88        self.sum.diff = Some(self.sum.val - prev.sum.val);
89        self.max.diff = Some(self.max.val - prev.max.val);
90        self.min.diff = Some(self.min.val - prev.min.val);
91        self.avg.diff = Some(self.avg.val - prev.avg.val);
92    }
93}
94
95impl GroupedMetrics {
96    pub fn new(db: &MetricDb, group_label_name: &str) -> Result<Self> {
97        let mut by_group = HashMap::<String, MetricsByName>::new();
98        let mut ungrouped = MetricsByName::new();
99        for (labels, metrics) in db.flat_dict.iter() {
100            let group_name = labels.get(group_label_name);
101            if let Some(group_name) = group_name {
102                let group_entry = by_group.entry(group_name.to_string()).or_default();
103                let mut labels = labels.clone();
104                labels.remove(group_label_name);
105                for metric in metrics {
106                    group_entry
107                        .entry(metric.name.clone())
108                        .or_default()
109                        .push((metric.value, labels.clone()));
110                }
111            } else {
112                for metric in metrics {
113                    ungrouped
114                        .entry(metric.name.clone())
115                        .or_default()
116                        .push((metric.value, labels.clone()));
117                }
118            }
119        }
120        Ok(Self {
121            by_group,
122            ungrouped,
123        })
124    }
125
126    /// Validates that E1, metered, and preflight instruction counts all match each other
127    fn validate_instruction_counts(group_summaries: &HashMap<MetricName, Stats>) {
128        let e1_insns = group_summaries.get(EXECUTE_E1_INSNS_LABEL);
129        let metered_insns = group_summaries.get(EXECUTE_METERED_INSNS_LABEL);
130        let preflight_insns = group_summaries.get(EXECUTE_PREFLIGHT_INSNS_LABEL);
131
132        if let (Some(e1_insns), Some(preflight_insns)) = (e1_insns, preflight_insns) {
133            assert_eq!(e1_insns.sum.val as u64, preflight_insns.sum.val as u64);
134        }
135        if let (Some(e1_insns), Some(metered_insns)) = (e1_insns, metered_insns) {
136            assert_eq!(e1_insns.sum.val as u64, metered_insns.sum.val as u64);
137        }
138        if let (Some(metered_insns), Some(preflight_insns)) = (metered_insns, preflight_insns) {
139            assert_eq!(metered_insns.sum.val as u64, preflight_insns.sum.val as u64);
140        }
141    }
142
143    pub fn aggregate(&self, num_parallel: usize) -> AggregateMetrics {
144        let by_group: HashMap<String, _> = self
145            .by_group
146            .iter()
147            .map(|(group_name, metrics)| {
148                let group_summaries: HashMap<MetricName, Stats> = metrics
149                    .iter()
150                    .map(|(metric_name, metrics)| {
151                        let mut summary = Stats::new();
152                        for (value, labels) in metrics {
153                            summary.push(*value);
154                            // Extract phase from labels if present
155                            if summary.phase.is_none() {
156                                if let Some(phase) = labels.get("phase") {
157                                    summary.phase = Some(phase.to_string());
158                                }
159                            }
160                        }
161                        summary.finalize();
162                        (metric_name.clone(), summary)
163                    })
164                    .collect();
165
166                if !group_name.contains("keygen") {
167                    Self::validate_instruction_counts(&group_summaries);
168                }
169
170                (group_name.clone(), group_summaries)
171            })
172            .collect();
173        let mut metrics = AggregateMetrics {
174            by_group,
175            ..Default::default()
176        };
177        metrics.compute_total();
178        metrics.bounded_par_by_group = self
179            .compute_bounded_par_times(num_parallel, &metrics.by_group)
180            .into_iter()
181            .map(|(k, v)| (k, MdTableCell::new(v, Some(0.0))))
182            .collect();
183
184        metrics
185    }
186
187    /// Compute per-group parallel proof time with bounded parallelism.
188    fn compute_bounded_par_times(
189        &self,
190        num_parallel: usize,
191        stats_by_group: &HashMap<String, HashMap<MetricName, Stats>>,
192    ) -> HashMap<String, f64> {
193        let mut per_group = HashMap::new();
194
195        for (group_name, metrics) in &self.by_group {
196            if group_name.contains("keygen") {
197                continue;
198            }
199
200            let mut group_time = 0.0;
201
202            // Add serial execution time for app_proof groups
203            if is_app_proof_group(group_name) {
204                if let Some(stats) = stats_by_group.get(group_name) {
205                    if let Some(metered) = stats.get(EXECUTE_METERED_TIME_LABEL) {
206                        group_time += metered.avg.val / 1000.0;
207                    }
208                    if let Some(e1) = stats.get(EXECUTE_E1_TIME_LABEL) {
209                        group_time += e1.avg.val / 1000.0;
210                    }
211                }
212            }
213
214            // Schedule proofs in parallel
215            if let Some(proof_times) = metrics.get(PROOF_TIME_LABEL) {
216                let times_s: Vec<f64> = proof_times.iter().map(|(ms, _)| ms / 1000.0).collect();
217                group_time += schedule_parallel(&times_s, num_parallel);
218            }
219
220            per_group.insert(group_name.clone(), group_time);
221        }
222
223        per_group
224    }
225}
226
227/// Round-robin assignment: proof i -> slot i % num_parallel. Returns max slot time.
228fn schedule_parallel(proof_times: &[f64], num_parallel: usize) -> f64 {
229    if proof_times.is_empty() || num_parallel == 0 {
230        return 0.0;
231    }
232
233    let mut slot_times = vec![0.0_f64; num_parallel];
234    for (i, duration) in proof_times.iter().enumerate() {
235        slot_times[i % num_parallel] += duration;
236    }
237    slot_times.iter().cloned().fold(0.0_f64, f64::max)
238}
239
240fn is_app_proof_group(name: &str) -> bool {
241    name != "leaf"
242        && name != "root"
243        && name != "halo2_outer"
244        && name != "halo2_wrapper"
245        && !name.starts_with("internal")
246}
247
248// A hacky way to order the groups for display.
249pub(crate) fn group_weight(name: &str) -> usize {
250    let label_prefix = ["leaf", "internal", "root", "halo2_outer", "halo2_wrapper"];
251    if name.contains("keygen") {
252        return label_prefix.len() + 1;
253    }
254    for (i, prefix) in label_prefix.iter().enumerate().rev() {
255        if name.starts_with(prefix) {
256            return i + 1;
257        }
258    }
259    0
260}
261
262impl AggregateMetrics {
263    pub fn compute_total(&mut self) {
264        let mut total_proof_time = MdTableCell::new(0.0, Some(0.0));
265        let mut total_par_proof_time = MdTableCell::new(0.0, Some(0.0));
266        for (group_name, metrics) in &self.by_group {
267            let stats = metrics.get(PROOF_TIME_LABEL);
268            let execute_metered_stats = metrics.get(EXECUTE_METERED_TIME_LABEL);
269            let execute_e1_stats = metrics.get(EXECUTE_E1_TIME_LABEL);
270            if stats.is_none() {
271                continue;
272            }
273            let stats = stats.unwrap_or_else(|| {
274                panic!("Missing proof time statistics for group '{group_name}'")
275            });
276            let mut sum = stats.sum;
277            let mut max = stats.max;
278            // convert ms to s
279            sum.val /= 1000.0;
280            max.val /= 1000.0;
281            if let Some(diff) = &mut sum.diff {
282                *diff /= 1000.0;
283            }
284            if let Some(diff) = &mut max.diff {
285                *diff /= 1000.0;
286            }
287            if !group_name.contains("keygen") {
288                // Proving time in keygen group is dummy and not part of total.
289                total_proof_time.val += sum.val;
290                *total_proof_time
291                    .diff
292                    .as_mut()
293                    .expect("total_proof_time.diff should be initialized") +=
294                    sum.diff.unwrap_or(0.0);
295                total_par_proof_time.val += max.val;
296                *total_par_proof_time
297                    .diff
298                    .as_mut()
299                    .expect("total_par_proof_time.diff should be initialized") +=
300                    max.diff.unwrap_or(0.0);
301
302                // Account for the serial execute_metered and execute_e1 for app outside of segments
303                if is_app_proof_group(group_name) {
304                    if let Some(execute_metered_stats) = execute_metered_stats {
305                        // For metered metrics without segment labels, we just use the value
306                        // directly Count is 1, so avg = sum = max = min =
307                        // value
308                        total_proof_time.val += execute_metered_stats.avg.val / 1000.0;
309                        total_par_proof_time.val += execute_metered_stats.avg.val / 1000.0;
310                        if let Some(diff) = execute_metered_stats.avg.diff {
311                            *total_proof_time
312                                .diff
313                                .as_mut()
314                                .expect("total_proof_time.diff should be initialized") +=
315                                diff / 1000.0;
316                            *total_par_proof_time
317                                .diff
318                                .as_mut()
319                                .expect("total_par_proof_time.diff should be initialized") +=
320                                diff / 1000.0;
321                        }
322                    }
323
324                    if let Some(execute_e1_stats) = execute_e1_stats {
325                        total_proof_time.val += execute_e1_stats.avg.val / 1000.0;
326                        total_par_proof_time.val += execute_e1_stats.avg.val / 1000.0;
327                        if let Some(diff) = execute_e1_stats.avg.diff {
328                            *total_proof_time
329                                .diff
330                                .as_mut()
331                                .expect("total_proof_time.diff should be initialized") +=
332                                diff / 1000.0;
333                            *total_par_proof_time
334                                .diff
335                                .as_mut()
336                                .expect("total_par_proof_time.diff should be initialized") +=
337                                diff / 1000.0;
338                        }
339                    }
340                }
341            }
342        }
343        self.total_proof_time = total_proof_time;
344        self.total_par_proof_time = total_par_proof_time;
345    }
346
347    pub fn set_diff(&mut self, prev: &Self) {
348        for (group_name, metrics) in self.by_group.iter_mut() {
349            if let Some(prev_metrics) = prev.by_group.get(group_name) {
350                for (metric_name, stats) in metrics.iter_mut() {
351                    if let Some(prev_stats) = prev_metrics.get(metric_name) {
352                        stats.set_diff(prev_stats);
353                    }
354                }
355            }
356        }
357        for (group_name, bounded) in self.bounded_par_by_group.iter_mut() {
358            if let Some(prev_bounded) = prev.bounded_par_by_group.get(group_name) {
359                bounded.diff = Some(bounded.val - prev_bounded.val);
360            }
361        }
362        self.compute_total();
363    }
364
365    pub fn to_vec(&self) -> Vec<(String, HashMap<MetricName, Stats>)> {
366        let mut group_names: Vec<_> = self.by_group.keys().collect();
367        group_names.sort_by(|a, b| {
368            let a_wt = group_weight(a);
369            let b_wt = group_weight(b);
370            if a_wt == b_wt {
371                a.cmp(b)
372            } else {
373                a_wt.cmp(&b_wt)
374            }
375        });
376        group_names
377            .into_iter()
378            .map(|group_name| {
379                let key = group_name.clone();
380                let value = self
381                    .by_group
382                    .get(group_name)
383                    .unwrap_or_else(|| panic!("Group '{group_name}' should exist in by_group map"))
384                    .clone();
385                (key, value)
386            })
387            .collect()
388    }
389
390    pub fn to_bencher_metrics(&self) -> BencherAggregateMetrics {
391        let by_group = self
392            .by_group
393            .iter()
394            .map(|(group_name, metrics)| {
395                let metrics = metrics
396                    .iter()
397                    .filter(|(_, stats)| stats.avg.val.is_finite() && stats.sum.val.is_finite())
398                    .flat_map(|(metric_name, stats)| {
399                        [
400                            (format!("{metric_name}::sum"), stats.sum.into()),
401                            (
402                                metric_name.clone(),
403                                BencherValue {
404                                    value: stats.avg.val,
405                                    lower_value: Some(stats.min.val),
406                                    upper_value: Some(stats.max.val),
407                                },
408                            ),
409                        ]
410                    })
411                    .collect();
412                (group_name.clone(), metrics)
413            })
414            .collect();
415        let total_proof_time = self.total_proof_time.into();
416        let total_par_proof_time = self.total_par_proof_time.into();
417        BencherAggregateMetrics {
418            by_group,
419            total_proof_time,
420            total_par_proof_time,
421        }
422    }
423
424    pub fn write_markdown(
425        &self,
426        writer: &mut impl Write,
427        metric_names: &[&str],
428        num_parallel: usize,
429    ) -> Result<()> {
430        self.write_summary_markdown(writer, num_parallel)?;
431        writeln!(writer)?;
432
433        let metric_names = metric_names.to_vec();
434        for (group_name, summaries) in self.to_vec() {
435            if group_name.contains("keygen") {
436                continue;
437            }
438
439            let names: Vec<&str> = if metric_names.is_empty() {
440                summaries.keys().map(|s| s.as_str()).collect()
441            } else {
442                metric_names.clone()
443            };
444            let names: Vec<&str> = names
445                .into_iter()
446                .filter(|name| summaries.contains_key(*name))
447                .collect();
448            if names.is_empty() {
449                continue;
450            }
451
452            writeln!(writer, "| {group_name} |||||")?;
453            writeln!(writer, "|:---|---:|---:|---:|---:|")?;
454            writeln!(writer, "|metric|avg|sum|max|min|")?;
455
456            // Group metrics by phase
457            let get_phase = |name: &str| -> Option<&str> {
458                summaries.get(name).and_then(|stats| stats.phase.as_deref())
459            };
460
461            // Collect unique phases (preserving order: uncategorized first, then by phase)
462            let mut phases: Vec<Option<&str>> = vec![None];
463            for name in &names {
464                if let Some(phase) = get_phase(name) {
465                    if !phases.contains(&Some(phase)) {
466                        phases.push(Some(phase));
467                    }
468                }
469            }
470
471            // Write metrics grouped by phase
472            for phase in &phases {
473                let phase_names: Vec<&str> = names
474                    .iter()
475                    .filter(|name| get_phase(name) == *phase)
476                    .copied()
477                    .collect();
478
479                if phase_names.is_empty() {
480                    continue;
481                }
482
483                // Write separator for non-default phases
484                if let Some(p) = phase {
485                    let label = p[0..1].to_uppercase() + &p[1..]; // Capitalize
486                    writeln!(writer, "| __{label}__ |||||")?;
487                }
488
489                for metric_name in &phase_names {
490                    self.write_metric_row(writer, &group_name, &summaries, metric_name)?;
491                }
492            }
493
494            writeln!(writer)?;
495        }
496        writeln!(writer)?;
497
498        Ok(())
499    }
500
501    fn write_metric_row(
502        &self,
503        writer: &mut impl Write,
504        group_name: &str,
505        summaries: &HashMap<MetricName, Stats>,
506        metric_name: &str,
507    ) -> Result<()> {
508        let summary = summaries.get(metric_name);
509        if let Some(summary) = summary {
510            // Special handling for execute_metered metrics (not aggregated across segments
511            // in the app proof case)
512            if (metric_name == EXECUTE_METERED_TIME_LABEL
513                || metric_name == EXECUTE_METERED_INSNS_LABEL)
514                && is_app_proof_group(group_name)
515            {
516                writeln!(
517                    writer,
518                    "| `{:<20}` | {:<10} | {:<10} | {:<10} | {:<10} |",
519                    metric_name, summary.avg, "-", "-", "-",
520                )?;
521            } else if metric_name == EXECUTE_E1_INSN_MI_S_LABEL
522                || metric_name == EXECUTE_PREFLIGHT_INSN_MI_S_LABEL
523                || metric_name == EXECUTE_METERED_INSN_MI_S_LABEL
524            {
525                // skip sum because it is misleading
526                writeln!(
527                    writer,
528                    "| `{:<20}` | {:<10} | {:<10} | {:<10} | {:<10} |",
529                    metric_name, summary.avg, "-", summary.max, summary.min,
530                )?;
531            } else {
532                writeln!(
533                    writer,
534                    "| `{:<20}` | {:<10} | {:<10} | {:<10} | {:<10} |",
535                    metric_name, summary.avg, summary.sum, summary.max, summary.min,
536                )?;
537            }
538        }
539        Ok(())
540    }
541
542    fn write_summary_markdown(&self, writer: &mut impl Write, num_parallel: usize) -> Result<()> {
543        writeln!(
544            writer,
545            "| Summary | Proof Time (s) | Parallel Proof Time (s) | Parallel Proof Time ({} provers) (s) |",
546            num_parallel
547        )?;
548        writeln!(writer, "|:---|---:|---:|---:|")?;
549        let mut rows = Vec::new();
550        for (group_name, summaries) in self.to_vec() {
551            if group_name.contains("keygen") {
552                continue;
553            }
554            let stats = summaries.get(PROOF_TIME_LABEL);
555            if stats.is_none() {
556                continue;
557            }
558            let stats = stats.unwrap_or_else(|| {
559                panic!("Missing proof time statistics for group '{group_name}'")
560            });
561            let mut sum = stats.sum;
562            let mut max = stats.max;
563            // convert ms to s
564            sum.val /= 1000.0;
565            max.val /= 1000.0;
566            if let Some(diff) = &mut sum.diff {
567                *diff /= 1000.0;
568            }
569            if let Some(diff) = &mut max.diff {
570                *diff /= 1000.0;
571            }
572            // Add serial execution time for app_proof groups
573            if is_app_proof_group(&group_name) {
574                if let Some(metered) = summaries.get(EXECUTE_METERED_TIME_LABEL) {
575                    sum.val += metered.avg.val / 1000.0;
576                    max.val += metered.avg.val / 1000.0;
577                }
578                if let Some(e1) = summaries.get(EXECUTE_E1_TIME_LABEL) {
579                    sum.val += e1.avg.val / 1000.0;
580                    max.val += e1.avg.val / 1000.0;
581                }
582            }
583            rows.push((group_name, sum, max));
584        }
585        let mut total_bounded = MdTableCell::new(0.0, None);
586        for cell in self.bounded_par_by_group.values() {
587            total_bounded.val += cell.val;
588            if let Some(diff) = cell.diff {
589                *total_bounded.diff.get_or_insert(0.0) += diff;
590            }
591        }
592        writeln!(
593            writer,
594            "| Total | {} | {} | {} |",
595            self.total_proof_time, self.total_par_proof_time, total_bounded
596        )?;
597        for (group_name, proof_time, par_proof_time) in rows {
598            let bounded = self
599                .bounded_par_by_group
600                .get(&group_name)
601                .map(|v| v.to_string())
602                .unwrap_or_else(|| "-".to_string());
603            writeln!(
604                writer,
605                "| {group_name} | {proof_time} | {par_proof_time} | {bounded} |"
606            )?;
607        }
608        writeln!(writer)?;
609        Ok(())
610    }
611
612    pub fn name(&self) -> Option<String> {
613        // A hacky way to determine the app name
614        let name = self
615            .by_group
616            .keys()
617            .find(|k| group_weight(k) == 0)
618            .or_else(|| self.by_group.keys().next())
619            .cloned();
620        if name.is_none() {
621            eprintln!("Warning: no group found to determine app name; by_group is empty");
622        }
623        name
624    }
625}
626
627impl BenchmarkOutput {
628    pub fn insert(&mut self, name: &str, metrics: BencherAggregateMetrics) {
629        for (group_name, metrics) in metrics.by_group {
630            self.by_name
631                .entry(format!("{name}::{group_name}"))
632                .or_default()
633                .extend(metrics);
634        }
635        if let Some(e) = self.by_name.insert(
636            name.to_owned(),
637            HashMap::from_iter([
638                ("total_proof_time".to_owned(), metrics.total_proof_time),
639                (
640                    "total_par_proof_time".to_owned(),
641                    metrics.total_par_proof_time,
642                ),
643            ]),
644        ) {
645            panic!("Duplicate metric: {e:?}");
646        }
647    }
648}
649
650pub const PROOF_TIME_LABEL: &str = "total_proof_time_ms";
651pub const MAIN_CELLS_USED_LABEL: &str = "main_cells_used";
652pub const TOTAL_CELLS_USED_LABEL: &str = "total_cells_used";
653pub const EXECUTE_E1_INSNS_LABEL: &str = "execute_e1_insns";
654pub const EXECUTE_METERED_INSNS_LABEL: &str = "execute_metered_insns";
655pub const EXECUTE_PREFLIGHT_INSNS_LABEL: &str = "execute_preflight_insns";
656pub const EXECUTE_E1_TIME_LABEL: &str = "execute_e1_time_ms";
657pub const EXECUTE_E1_INSN_MI_S_LABEL: &str = "execute_e1_insn_mi/s";
658pub const EXECUTE_METERED_TIME_LABEL: &str = "execute_metered_time_ms";
659pub const EXECUTE_METERED_INSN_MI_S_LABEL: &str = "execute_metered_insn_mi/s";
660pub const EXECUTE_PREFLIGHT_TIME_LABEL: &str = "execute_preflight_time_ms";
661pub const EXECUTE_PREFLIGHT_INSN_MI_S_LABEL: &str = "execute_preflight_insn_mi/s";
662pub const TRACE_GEN_TIME_LABEL: &str = "trace_gen_time_ms";
663pub const GENERATE_BLOB_TIME_LABEL: &str = "generate_blob_total_time_ms";
664pub const MEM_FIN_TIME_LABEL: &str = "memory_finalize_time_ms";
665pub const BOUNDARY_FIN_TIME_LABEL: &str = "boundary_finalize_time_ms";
666pub const MERKLE_FIN_TIME_LABEL: &str = "merkle_finalize_time_ms";
667pub const PROVE_EXCL_TRACE_TIME_LABEL: &str = "stark_prove_excluding_trace_time_ms";
668
669pub const HALO2_VERIFIER_K_LABEL: &str = "halo2_verifier_k";
670pub const HALO2_WRAPPER_K_LABEL: &str = "halo2_wrapper_k";
671
672pub const AGGREGATED_METRIC_NAMES: &[&str] = &[
673    PROOF_TIME_LABEL,
674    MAIN_CELLS_USED_LABEL,
675    TOTAL_CELLS_USED_LABEL,
676    EXECUTE_E1_TIME_LABEL,
677    EXECUTE_E1_INSN_MI_S_LABEL,
678    EXECUTE_METERED_TIME_LABEL,
679    EXECUTE_METERED_INSNS_LABEL,
680    EXECUTE_METERED_INSN_MI_S_LABEL,
681    EXECUTE_PREFLIGHT_INSNS_LABEL,
682    EXECUTE_PREFLIGHT_TIME_LABEL,
683    EXECUTE_PREFLIGHT_INSN_MI_S_LABEL,
684    TRACE_GEN_TIME_LABEL,
685    GENERATE_BLOB_TIME_LABEL,
686    MEM_FIN_TIME_LABEL,
687    BOUNDARY_FIN_TIME_LABEL,
688    MERKLE_FIN_TIME_LABEL,
689    PROVE_EXCL_TRACE_TIME_LABEL,
690    "prover.main_trace_commit_time_ms",
691    "prover.rap_constraints_time_ms",
692    "prover.openings_time_ms",
693    "prover.rap_constraints.logup_gkr_time_ms",
694    "prover.rap_constraints.round0_time_ms",
695    "prover.rap_constraints.mle_rounds_time_ms",
696    "prover.openings.stacked_reduction_time_ms",
697    "prover.openings.stacked_reduction.round0_time_ms",
698    "prover.openings.stacked_reduction.mle_rounds_time_ms",
699    "prover.openings.whir_time_ms",
700    HALO2_VERIFIER_K_LABEL,
701    HALO2_WRAPPER_K_LABEL,
702];