openvm_prof/
summary.rs

1use std::{io::Write, path::PathBuf};
2
3use eyre::Result;
4use itertools::Itertools;
5
6use crate::{
7    aggregate::{AggregateMetrics, CELLS_USED_LABEL, CYCLES_LABEL, PROOF_TIME_LABEL},
8    types::MdTableCell,
9};
10
11#[derive(Clone, Debug)]
12pub struct GithubSummary {
13    pub rows: Vec<SummaryRow>,
14    pub benchmark_results_link: String,
15}
16
17#[derive(Clone, Debug)]
18pub struct SummaryRow {
19    pub name: String,
20    pub md_filename: String,
21    pub metrics: BenchSummaryMetrics,
22}
23
24#[derive(Clone, Debug)]
25pub struct BenchSummaryMetrics {
26    pub app: SingleSummaryMetrics,
27    pub leaf: Option<SingleSummaryMetrics>,
28    pub internals: Vec<SingleSummaryMetrics>,
29    pub root: Option<SingleSummaryMetrics>,
30    pub halo2_outer: Option<SingleSummaryMetrics>,
31    pub halo2_wrapper: Option<SingleSummaryMetrics>,
32}
33
34#[derive(Clone, Debug)]
35pub struct SingleSummaryMetrics {
36    pub proof_time_ms: MdTableCell,
37    /// Parallel proof time is approximated as the max of proof times within a group
38    pub par_proof_time_ms: MdTableCell,
39    pub cells_used: MdTableCell,
40    pub cycles: MdTableCell,
41}
42
43impl GithubSummary {
44    pub fn new(
45        aggregated_metrics: &[(AggregateMetrics, Option<AggregateMetrics>)],
46        md_paths: &[PathBuf],
47        benchmark_results_link: &str,
48    ) -> Self {
49        let rows = aggregated_metrics
50            .iter()
51            .zip_eq(md_paths.iter())
52            .map(|((aggregated, prev_aggregated), md_path)| {
53                let md_filename = md_path.file_name().unwrap().to_str().unwrap();
54                let mut row = aggregated.get_summary_row(md_filename).unwrap();
55                if let Some(prev_aggregated) = prev_aggregated {
56                    // md_filename doesn't matter
57                    if let Some(prev_row) = prev_aggregated.get_summary_row(md_filename) {
58                        if row.name == prev_row.name {
59                            row.metrics.set_diff(&prev_row.metrics);
60                        }
61                    }
62                }
63                row
64            })
65            .collect();
66
67        Self {
68            rows,
69            benchmark_results_link: benchmark_results_link.to_string(),
70        }
71    }
72
73    pub fn write_markdown(&self, writer: &mut impl Write) -> Result<()> {
74        writeln!(writer, "| group | app.proof_time_ms | app.cycles | app.cells_used | leaf.proof_time_ms | leaf.cycles | leaf.cells_used |")?;
75        write!(writer, "| -- |")?;
76        for _ in 0..6 {
77            write!(writer, " -- |")?;
78        }
79        writeln!(writer)?;
80
81        for row in self.rows.iter() {
82            write!(
83                writer,
84                "| [{}]({}/{}) |",
85                row.name, self.benchmark_results_link, row.md_filename
86            )?;
87            row.metrics.write_partial_md_row(writer)?;
88            writeln!(writer)?;
89        }
90        writeln!(writer)?;
91
92        Ok(())
93    }
94}
95
96impl BenchSummaryMetrics {
97    pub fn write_partial_md_row(&self, writer: &mut impl Write) -> Result<()> {
98        self.app.write_partial_md_row(writer)?;
99        if let Some(leaf) = &self.leaf {
100            leaf.write_partial_md_row(writer)?;
101        } else {
102            // Always write placeholder for leaf
103            write!(writer, "- | - | - |")?;
104        }
105        // Don't print other metrics in summary for now:
106
107        // for internal in &self.internals {
108        //     internal.write_partial_md_row(writer)?;
109        // }
110        // if let Some(root) = &self.root {
111        //     root.write_partial_md_row(writer)?;
112        // }
113
114        Ok(())
115    }
116
117    pub fn set_diff(&mut self, prev: &Self) {
118        self.app.set_diff(&prev.app);
119        if let (Some(leaf), Some(prev_leaf)) = (&mut self.leaf, &prev.leaf) {
120            leaf.set_diff(prev_leaf);
121        }
122        for (internal, prev_internal) in self.internals.iter_mut().zip(prev.internals.iter()) {
123            internal.set_diff(prev_internal);
124        }
125        if let (Some(root), Some(prev_root)) = (&mut self.root, &prev.root) {
126            root.set_diff(prev_root);
127        }
128    }
129}
130
131impl SingleSummaryMetrics {
132    pub fn write_partial_md_row(&self, writer: &mut impl Write) -> Result<()> {
133        write!(
134            writer,
135            "{} | {} | {} |",
136            self.proof_time_ms, self.cycles, self.cells_used,
137        )?;
138        Ok(())
139    }
140
141    pub fn set_diff(&mut self, prev: &Self) {
142        self.cells_used.diff = Some(self.cells_used.val - prev.cells_used.val);
143        self.cycles.diff = Some(self.cycles.val - prev.cycles.val);
144        self.proof_time_ms.diff = Some(self.proof_time_ms.val - prev.proof_time_ms.val);
145    }
146}
147
148impl AggregateMetrics {
149    pub fn get_single_summary(&self, name: &str) -> Option<SingleSummaryMetrics> {
150        let stats = self.by_group.get(name)?;
151        // Any group must have proof_time, but may not have cells_used or cycles (e.g., halo2)
152        let proof_time_ms = stats.get(PROOF_TIME_LABEL)?.sum;
153        let par_proof_time_ms = stats.get(PROOF_TIME_LABEL)?.max;
154        let cells_used = stats
155            .get(CELLS_USED_LABEL)
156            .map(|s| s.sum)
157            .unwrap_or_default();
158        let cycles = stats.get(CYCLES_LABEL).map(|s| s.sum).unwrap_or_default();
159        Some(SingleSummaryMetrics {
160            cells_used,
161            cycles,
162            proof_time_ms,
163            par_proof_time_ms,
164        })
165    }
166
167    /// Returns `None` if no group for app is found.
168    pub fn get_summary_row(&self, md_filename: &str) -> Option<SummaryRow> {
169        let app_name = self.name();
170        let app = self.get_single_summary(&app_name)?;
171        let leaf = self.get_single_summary("leaf");
172        let mut internals = Vec::new();
173        let mut hgt = 0;
174        while let Some(internal) = self.get_single_summary(&format!("internal.{hgt}")) {
175            internals.push(internal);
176            hgt += 1;
177        }
178        let root = self.get_single_summary("root");
179        let halo2_outer = self.get_single_summary("halo2_outer");
180        let halo2_wrapper = self.get_single_summary("halo2_wrapper");
181        Some(SummaryRow {
182            name: app_name.to_string(),
183            md_filename: md_filename.to_string(),
184            metrics: BenchSummaryMetrics {
185                app,
186                leaf,
187                internals,
188                root,
189                halo2_outer,
190                halo2_wrapper,
191            },
192        })
193    }
194}