openvm_prof/
summary.rs

1use std::{io::Write, path::PathBuf};
2
3use eyre::Result;
4use itertools::Itertools;
5
6use crate::{
7    aggregate::{
8        AggregateMetrics, EXECUTE_METERED_TIME_LABEL, EXECUTE_PREFLIGHT_INSNS_LABEL,
9        EXECUTE_PREFLIGHT_TIME_LABEL, MAIN_CELLS_USED_LABEL, PROOF_TIME_LABEL,
10        PROVE_EXCL_TRACE_TIME_LABEL, TRACE_GEN_TIME_LABEL,
11    },
12    types::MdTableCell,
13};
14
15#[derive(Clone, Debug)]
16pub struct GithubSummary {
17    pub rows: Vec<SummaryRow>,
18    pub benchmark_results_link: String,
19}
20
21#[derive(Clone, Debug)]
22pub struct SummaryRow {
23    pub name: String,
24    pub md_filename: String,
25    pub metrics: BenchSummaryMetrics,
26}
27
28#[derive(Clone, Debug)]
29pub struct BenchSummaryMetrics {
30    pub app: SingleSummaryMetrics,
31    pub leaf: Option<SingleSummaryMetrics>,
32    pub internals: Vec<SingleSummaryMetrics>,
33    pub root: Option<SingleSummaryMetrics>,
34    pub halo2_outer: Option<SingleSummaryMetrics>,
35    pub halo2_wrapper: Option<SingleSummaryMetrics>,
36}
37
38#[derive(Clone, Debug)]
39pub struct SingleSummaryMetrics {
40    pub proof_time_ms: MdTableCell,
41    /// Parallel proof time is approximated as the max of proof times within a group
42    pub par_proof_time_ms: MdTableCell,
43    pub cells_used: MdTableCell,
44    pub insns: MdTableCell,
45}
46
47impl GithubSummary {
48    pub fn new(
49        names: &[String],
50        aggregated_metrics: &[(AggregateMetrics, Option<AggregateMetrics>)],
51        md_paths: &[PathBuf],
52        benchmark_results_link: &str,
53    ) -> Self {
54        let rows = aggregated_metrics
55            .iter()
56            .zip_eq(md_paths.iter())
57            .zip_eq(names)
58            .map(|(((aggregated, prev_aggregated), md_path), name)| {
59                let md_filename = md_path
60                    .file_name()
61                    .expect("Path should have a filename")
62                    .to_str()
63                    .expect("Filename should be valid UTF-8");
64                let mut row = aggregated.get_summary_row(md_filename).unwrap_or_else(|| {
65                    panic!("Failed to get summary row for file '{}'", md_filename)
66                });
67                if let Some(prev_aggregated) = prev_aggregated {
68                    // md_filename doesn't matter
69                    if let Some(prev_row) = prev_aggregated.get_summary_row(md_filename) {
70                        if row.name == prev_row.name {
71                            row.metrics.set_diff(&prev_row.metrics);
72                        }
73                    }
74                }
75                row.name = name.clone();
76                row
77            })
78            .collect();
79
80        Self {
81            rows,
82            benchmark_results_link: benchmark_results_link.to_string(),
83        }
84    }
85
86    pub fn write_markdown(&self, writer: &mut impl Write) -> Result<()> {
87        writeln!(writer, "| group | app.proof_time_ms | app.cycles | app.cells_used | leaf.proof_time_ms | leaf.cycles | leaf.cells_used |")?;
88        write!(writer, "| -- |")?;
89        for _ in 0..6 {
90            write!(writer, " -- |")?;
91        }
92        writeln!(writer)?;
93
94        for row in self.rows.iter() {
95            write!(
96                writer,
97                "| [{}]({}/{}) |",
98                row.name, self.benchmark_results_link, row.md_filename
99            )?;
100            row.metrics.write_partial_md_row(writer)?;
101            writeln!(writer)?;
102        }
103        writeln!(writer)?;
104
105        Ok(())
106    }
107}
108
109impl BenchSummaryMetrics {
110    pub fn write_partial_md_row(&self, writer: &mut impl Write) -> Result<()> {
111        self.app.write_partial_md_row(writer)?;
112        if let Some(leaf) = &self.leaf {
113            leaf.write_partial_md_row(writer)?;
114        } else {
115            // Always write placeholder for leaf
116            write!(writer, "- | - | - |")?;
117        }
118        // Don't print other metrics in summary for now:
119
120        // for internal in &self.internals {
121        //     internal.write_partial_md_row(writer)?;
122        // }
123        // if let Some(root) = &self.root {
124        //     root.write_partial_md_row(writer)?;
125        // }
126
127        Ok(())
128    }
129
130    pub fn set_diff(&mut self, prev: &Self) {
131        self.app.set_diff(&prev.app);
132        if let (Some(leaf), Some(prev_leaf)) = (&mut self.leaf, &prev.leaf) {
133            leaf.set_diff(prev_leaf);
134        }
135        for (internal, prev_internal) in self.internals.iter_mut().zip(prev.internals.iter()) {
136            internal.set_diff(prev_internal);
137        }
138        if let (Some(root), Some(prev_root)) = (&mut self.root, &prev.root) {
139            root.set_diff(prev_root);
140        }
141    }
142}
143
144impl SingleSummaryMetrics {
145    pub fn write_partial_md_row(&self, writer: &mut impl Write) -> Result<()> {
146        write!(
147            writer,
148            "{} | {} | {} |",
149            self.proof_time_ms, self.insns, self.cells_used,
150        )?;
151        Ok(())
152    }
153
154    pub fn set_diff(&mut self, prev: &Self) {
155        self.cells_used.diff = Some(self.cells_used.val - prev.cells_used.val);
156        self.insns.diff = Some(self.insns.val - prev.insns.val);
157        self.proof_time_ms.diff = Some(self.proof_time_ms.val - prev.proof_time_ms.val);
158    }
159}
160
161impl AggregateMetrics {
162    pub fn get_single_summary(&self, name: &str) -> Option<SingleSummaryMetrics> {
163        let stats = self.by_group.get(name)?;
164        // Any group must have proof_time, but may not have cells_used or cycles (e.g., halo2)
165        let proof_time_ms = if let Some(proof_stats) = stats.get(PROOF_TIME_LABEL) {
166            proof_stats.sum
167        } else {
168            // Note: execute_metered is outside any segment scope, so it should have sum = max = avg
169            let execute_metered = stats
170                .get(EXECUTE_METERED_TIME_LABEL)
171                .map(|s| s.sum.val)
172                .unwrap_or(0.0);
173            let execute_preflight = stats
174                .get(EXECUTE_PREFLIGHT_TIME_LABEL)
175                .map(|s| s.sum.val)
176                .unwrap_or(0.0);
177            // If total_proof_time_ms is not available, compute it from components
178            let trace_gen = stats
179                .get(TRACE_GEN_TIME_LABEL)
180                .map(|s| s.sum.val)
181                .unwrap_or(0.0);
182            let stark_prove = stats
183                .get(PROVE_EXCL_TRACE_TIME_LABEL)
184                .map(|s| s.sum.val)
185                .unwrap_or(0.0);
186            println!(
187                "{} {} {} {}",
188                execute_metered, execute_preflight, trace_gen, stark_prove
189            );
190            MdTableCell::new(
191                execute_metered + execute_preflight + trace_gen + stark_prove,
192                None,
193            )
194        };
195        println!("{}", self.total_proof_time.val);
196        let par_proof_time_ms = if let Some(proof_stats) = stats.get(PROOF_TIME_LABEL) {
197            proof_stats.max
198        } else {
199            // Use the same computation for max
200            let execute_metered = stats
201                .get(EXECUTE_METERED_TIME_LABEL)
202                .map(|s| s.max.val)
203                .unwrap_or(0.0);
204            let execute_preflight = stats
205                .get(EXECUTE_PREFLIGHT_TIME_LABEL)
206                .map(|s| s.max.val)
207                .unwrap_or(0.0);
208            let trace_gen = stats
209                .get(TRACE_GEN_TIME_LABEL)
210                .map(|s| s.max.val)
211                .unwrap_or(0.0);
212            let stark_prove = stats
213                .get(PROVE_EXCL_TRACE_TIME_LABEL)
214                .map(|s| s.max.val)
215                .unwrap_or(0.0);
216            MdTableCell::new(
217                execute_metered + execute_preflight + trace_gen + stark_prove,
218                None,
219            )
220        };
221        let cells_used = stats
222            .get(MAIN_CELLS_USED_LABEL)
223            .map(|s| s.sum)
224            .unwrap_or_default();
225        let insns = stats
226            .get(EXECUTE_PREFLIGHT_INSNS_LABEL)
227            .map(|s| s.sum)
228            .unwrap_or_default();
229        Some(SingleSummaryMetrics {
230            cells_used,
231            insns,
232            proof_time_ms,
233            par_proof_time_ms,
234        })
235    }
236
237    /// Returns `None` if no group for app is found.
238    pub fn get_summary_row(&self, md_filename: &str) -> Option<SummaryRow> {
239        let app_name = self.name();
240        let app = self.get_single_summary(&app_name)?;
241        let leaf = self.get_single_summary("leaf");
242        let mut internals = Vec::new();
243        let mut hgt = 0;
244        while let Some(internal) = self.get_single_summary(&format!("internal.{hgt}")) {
245            internals.push(internal);
246            hgt += 1;
247        }
248        let root = self.get_single_summary("root");
249        let halo2_outer = self.get_single_summary("halo2_outer");
250        let halo2_wrapper = self.get_single_summary("halo2_wrapper");
251        Some(SummaryRow {
252            name: app_name.to_string(),
253            md_filename: md_filename.to_string(),
254            metrics: BenchSummaryMetrics {
255                app,
256                leaf,
257                internals,
258                root,
259                halo2_outer,
260                halo2_wrapper,
261            },
262        })
263    }
264}