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 pub omit_cells_used: bool,
20}
21
22#[derive(Clone, Debug)]
23pub struct SummaryRow {
24 pub name: String,
25 pub md_filename: String,
26 pub metrics: BenchSummaryMetrics,
27}
28
29#[derive(Clone, Debug)]
30pub struct BenchSummaryMetrics {
31 pub app: SingleSummaryMetrics,
32 pub leaf: Option<SingleSummaryMetrics>,
33 pub internals: Vec<SingleSummaryMetrics>,
34 pub root: Option<SingleSummaryMetrics>,
35 pub halo2_outer: Option<SingleSummaryMetrics>,
36 pub halo2_wrapper: Option<SingleSummaryMetrics>,
37}
38
39#[derive(Clone, Debug)]
40pub struct SingleSummaryMetrics {
41 pub proof_time_ms: MdTableCell,
42 pub par_proof_time_ms: MdTableCell,
44 pub cells_used: MdTableCell,
45 pub insns: MdTableCell,
46}
47
48impl GithubSummary {
49 pub fn new(
50 names: &[String],
51 aggregated_metrics: &[(AggregateMetrics, Option<AggregateMetrics>)],
52 md_paths: &[PathBuf],
53 benchmark_results_link: &str,
54 ) -> Self {
55 let omit_cells_used = aggregated_metrics
56 .iter()
57 .all(|(aggregated, _)| !aggregated.has_cells_used_metrics());
58 let rows = aggregated_metrics
59 .iter()
60 .zip_eq(md_paths.iter())
61 .zip_eq(names)
62 .filter_map(|(((aggregated, prev_aggregated), md_path), name)| {
63 let md_filename = md_path
64 .file_name()
65 .expect("Path should have a filename")
66 .to_str()
67 .expect("Filename should be valid UTF-8");
68 let mut row = match aggregated.get_summary_row(md_filename) {
69 Some(row) => row,
70 None => {
71 eprintln!(
72 "Warning: skipping benchmark '{}' ({}): no grouped metrics found",
73 name, md_filename
74 );
75 return None;
76 }
77 };
78 if let Some(prev_aggregated) = prev_aggregated {
79 if let Some(prev_row) = prev_aggregated.get_summary_row(md_filename) {
81 if row.name == prev_row.name {
82 row.metrics.set_diff(&prev_row.metrics);
83 }
84 }
85 }
86 row.name = name.clone();
87 Some(row)
88 })
89 .collect();
90
91 Self {
92 rows,
93 benchmark_results_link: benchmark_results_link.to_string(),
94 omit_cells_used,
95 }
96 }
97
98 pub fn write_markdown(&self, writer: &mut impl Write) -> Result<()> {
99 let include_cells_used = !self.omit_cells_used;
100 if include_cells_used {
101 writeln!(writer, "| group | app.proof_time_ms | app.cycles | app.cells_used | leaf.proof_time_ms | leaf.cells_used |")?;
102 } else {
103 writeln!(
104 writer,
105 "| group | app.proof_time_ms | app.cycles | leaf.proof_time_ms |"
106 )?;
107 }
108 write!(writer, "| -- |")?;
109 let trailing_columns = if include_cells_used { 5 } else { 3 };
110 for _ in 0..trailing_columns {
111 write!(writer, " -- |")?;
112 }
113 writeln!(writer)?;
114
115 for row in self.rows.iter() {
116 write!(
117 writer,
118 "| [{}]({}/{}) |",
119 row.name, self.benchmark_results_link, row.md_filename
120 )?;
121 row.metrics
122 .write_partial_md_row(writer, include_cells_used)?;
123 writeln!(writer)?;
124 }
125 writeln!(writer)?;
126 if self.omit_cells_used {
127 writeln!(
128 writer,
129 "Note: cells_used metrics omitted because CUDA tracegen does not expose unpadded trace heights."
130 )?;
131 writeln!(writer)?;
132 }
133
134 Ok(())
135 }
136}
137
138impl BenchSummaryMetrics {
139 pub fn write_partial_md_row(
140 &self,
141 writer: &mut impl Write,
142 include_cells_used: bool,
143 ) -> Result<()> {
144 if include_cells_used {
145 let (leaf_proof_time, leaf_cells_used) = match &self.leaf {
146 Some(leaf) => (leaf.proof_time_ms.to_string(), leaf.cells_used.to_string()),
147 None => ("-".to_string(), "-".to_string()),
148 };
149 self.app.write_partial_md_row(writer)?;
150 write!(writer, "{} | {} |", leaf_proof_time, leaf_cells_used)?;
151 } else {
152 let leaf_proof_time = self
153 .leaf
154 .as_ref()
155 .map(|leaf| leaf.proof_time_ms.to_string())
156 .unwrap_or_else(|| "-".to_string());
157 write!(
158 writer,
159 "{} | {} | {} |",
160 self.app.proof_time_ms, self.app.insns, leaf_proof_time
161 )?;
162 }
163 Ok(())
173 }
174
175 pub fn set_diff(&mut self, prev: &Self) {
176 self.app.set_diff(&prev.app);
177 if let (Some(leaf), Some(prev_leaf)) = (&mut self.leaf, &prev.leaf) {
178 leaf.set_diff(prev_leaf);
179 }
180 for (internal, prev_internal) in self.internals.iter_mut().zip(prev.internals.iter()) {
181 internal.set_diff(prev_internal);
182 }
183 if let (Some(root), Some(prev_root)) = (&mut self.root, &prev.root) {
184 root.set_diff(prev_root);
185 }
186 }
187}
188
189impl SingleSummaryMetrics {
190 pub fn write_partial_md_row(&self, writer: &mut impl Write) -> Result<()> {
191 write!(
192 writer,
193 "{} | {} | {} |",
194 self.proof_time_ms, self.insns, self.cells_used,
195 )?;
196 Ok(())
197 }
198
199 pub fn set_diff(&mut self, prev: &Self) {
200 self.cells_used.diff = Some(self.cells_used.val - prev.cells_used.val);
201 self.insns.diff = Some(self.insns.val - prev.insns.val);
202 self.proof_time_ms.diff = Some(self.proof_time_ms.val - prev.proof_time_ms.val);
203 }
204}
205
206impl AggregateMetrics {
207 pub fn has_cells_used_metrics(&self) -> bool {
208 self.by_group
209 .values()
210 .any(|stats| stats.contains_key(MAIN_CELLS_USED_LABEL))
211 }
212
213 pub fn get_single_summary(&self, name: &str) -> Option<SingleSummaryMetrics> {
214 let stats = self.by_group.get(name)?;
215 let proof_time_ms = if let Some(proof_stats) = stats.get(PROOF_TIME_LABEL) {
217 proof_stats.sum
218 } else {
219 let execute_metered = stats
221 .get(EXECUTE_METERED_TIME_LABEL)
222 .map(|s| s.sum.val)
223 .unwrap_or(0.0);
224 let execute_preflight = stats
225 .get(EXECUTE_PREFLIGHT_TIME_LABEL)
226 .map(|s| s.sum.val)
227 .unwrap_or(0.0);
228 let trace_gen = stats
230 .get(TRACE_GEN_TIME_LABEL)
231 .map(|s| s.sum.val)
232 .unwrap_or(0.0);
233 let stark_prove = stats
234 .get(PROVE_EXCL_TRACE_TIME_LABEL)
235 .map(|s| s.sum.val)
236 .unwrap_or(0.0);
237 println!("{execute_metered} {execute_preflight} {trace_gen} {stark_prove}");
238 MdTableCell::new(
239 execute_metered + execute_preflight + trace_gen + stark_prove,
240 None,
241 )
242 };
243 println!("{}", self.total_proof_time.val);
244 let par_proof_time_ms = if let Some(proof_stats) = stats.get(PROOF_TIME_LABEL) {
245 proof_stats.max
246 } else {
247 let execute_metered = stats
249 .get(EXECUTE_METERED_TIME_LABEL)
250 .map(|s| s.max.val)
251 .unwrap_or(0.0);
252 let execute_preflight = stats
253 .get(EXECUTE_PREFLIGHT_TIME_LABEL)
254 .map(|s| s.max.val)
255 .unwrap_or(0.0);
256 let trace_gen = stats
257 .get(TRACE_GEN_TIME_LABEL)
258 .map(|s| s.max.val)
259 .unwrap_or(0.0);
260 let stark_prove = stats
261 .get(PROVE_EXCL_TRACE_TIME_LABEL)
262 .map(|s| s.max.val)
263 .unwrap_or(0.0);
264 MdTableCell::new(
265 execute_metered + execute_preflight + trace_gen + stark_prove,
266 None,
267 )
268 };
269 let cells_used = stats
270 .get(MAIN_CELLS_USED_LABEL)
271 .map(|s| s.sum)
272 .unwrap_or_default();
273 let insns = stats
274 .get(EXECUTE_PREFLIGHT_INSNS_LABEL)
275 .map(|s| s.sum)
276 .unwrap_or_default();
277 Some(SingleSummaryMetrics {
278 cells_used,
279 insns,
280 proof_time_ms,
281 par_proof_time_ms,
282 })
283 }
284
285 pub fn get_summary_row(&self, md_filename: &str) -> Option<SummaryRow> {
287 let app_name = self.name()?;
288 let app = self.get_single_summary(&app_name)?;
289 let leaf = self.get_single_summary("leaf");
290 let mut internals = Vec::new();
291 let mut hgt = 0;
292 while let Some(internal) = self.get_single_summary(&format!("internal.{hgt}")) {
293 internals.push(internal);
294 hgt += 1;
295 }
296 let root = self.get_single_summary("root");
297 let halo2_outer = self.get_single_summary("halo2_outer");
298 let halo2_wrapper = self.get_single_summary("halo2_wrapper");
299 Some(SummaryRow {
300 name: app_name,
301 md_filename: md_filename.to_string(),
302 metrics: BenchSummaryMetrics {
303 app,
304 leaf,
305 internals,
306 root,
307 halo2_outer,
308 halo2_wrapper,
309 },
310 })
311 }
312}