miden_debug_engine/profiling/instrument/
op_histogram_proc.rs1use std::collections::HashMap;
2
3use miden_core::operations::Operation;
4
5use super::{Instrument, InstrumentRegistration};
6use crate::{profiling::helpers::op_histogram::OpHistogram, register_instrument};
7
8const UNKNOWN_PROCEDURE: &str = "<unknown>";
11
12#[derive(Default)]
21pub struct OpHistogramProc {
22 histograms: HashMap<String, OpHistogram>,
23}
24
25impl InstrumentRegistration for OpHistogramProc {
26 const NAME: &'static str = "op-histogram-proc";
27
28 fn build(_config: &crate::profiling::ProfilerConfig) -> Result<Self, super::InstrumentError> {
29 Ok(Self::default())
30 }
31}
32
33register_instrument!(OpHistogramProc);
34
35impl Instrument for OpHistogramProc {
36 fn name(&self) -> &'static str {
37 Self::NAME
38 }
39
40 fn on_operation_execution_cycle(&mut self, op: Operation, proc: Option<&str>) {
41 let key = proc.unwrap_or(UNKNOWN_PROCEDURE);
42 match self.histograms.get_mut(key) {
44 Some(hist) => hist.record(op),
45 None => {
46 let mut hist = OpHistogram::default();
47 hist.record(op);
48 self.histograms.insert(key.to_owned(), hist);
49 }
50 }
51 }
52
53 fn write_report_to(&self, writer: &mut dyn std::io::Write) -> std::io::Result<()> {
54 let mut entries: Vec<(&str, &OpHistogram)> =
55 self.histograms.iter().map(|(name, hist)| (name.as_str(), hist)).collect();
56 entries
59 .sort_by(|a, b| b.1.total_cycles().cmp(&a.1.total_cycles()).then_with(|| a.0.cmp(b.0)));
60
61 for (name, hist) in entries {
62 writeln!(writer, "procedure: {name}")?;
63 writer.write_all(hist.report().as_bytes())?;
64 }
65 Ok(())
66 }
67}
68
69#[cfg(test)]
70mod tests {
71 use miden_core::operations::Operation;
72
73 use super::OpHistogramProc;
74 use crate::profiling::instrument::Instrument;
75
76 fn section<'a>(report: &'a str, name: &str) -> &'a str {
78 let marker = format!("procedure: {name}\n");
79 let start = report.find(&marker).expect("section exists") + marker.len();
80 match report[start..].find("procedure: ") {
81 Some(rel) => &report[start..start + rel],
82 None => &report[start..],
83 }
84 }
85
86 fn count_for(section: &str, label: &str) -> Option<u64> {
92 section
93 .lines()
94 .find(|line| line.split_whitespace().next() == Some(label))
95 .map(parse_count_from_line)
96 }
97
98 fn sum_total_cycles(report: &str) -> u64 {
100 report
101 .lines()
102 .filter(|line| line.split_whitespace().next() == Some("total_cycles"))
103 .map(parse_count_from_line)
104 .sum()
105 }
106
107 fn parse_count_from_line(line: &str) -> u64 {
109 line.split_whitespace()
110 .last()
111 .and_then(|count| count.parse().ok())
112 .expect("report line ends with the count")
113 }
114
115 #[test]
116 fn op_histogram_proc_reports_per_procedure_histograms() {
117 let mut hist = OpHistogramProc::default();
118
119 hist.on_operation_execution_cycle(Operation::Add, Some("main"));
120 hist.on_operation_execution_cycle(Operation::Add, Some("main"));
121 hist.on_operation_execution_cycle(Operation::Noop, Some("sum"));
122 hist.on_operation_execution_cycle(Operation::Mul, Some("sum"));
123
124 let mut buf = Vec::new();
125 hist.write_report_to(&mut buf).unwrap();
126 let report = String::from_utf8(buf).unwrap();
127
128 let main = section(&report, "main");
130 assert_eq!(count_for(main, "total_cycles"), Some(2));
131 assert_eq!(count_for(main, "add"), Some(2));
132 assert_eq!(count_for(main, "noop"), None);
133
134 let sum = section(&report, "sum");
136 assert_eq!(count_for(sum, "total_cycles"), Some(2));
137 assert_eq!(count_for(sum, "noop"), Some(1));
138 assert_eq!(count_for(sum, "mul"), Some(1));
139
140 assert_eq!(sum_total_cycles(&report), 4);
142 }
143
144 #[test]
145 fn op_histogram_proc_sorts_by_total_cycles() {
146 let mut hist = OpHistogramProc::default();
147
148 hist.on_operation_execution_cycle(Operation::Add, Some("z"));
150 hist.on_operation_execution_cycle(Operation::Add, Some("z"));
151 hist.on_operation_execution_cycle(Operation::Add, Some("z"));
152 hist.on_operation_execution_cycle(Operation::Noop, Some("a"));
153
154 let mut buf = Vec::new();
155 hist.write_report_to(&mut buf).unwrap();
156 let report = String::from_utf8(buf).unwrap();
157
158 let z_pos = report.find("procedure: z").unwrap();
159 let a_pos = report.find("procedure: a").unwrap();
160 assert!(z_pos < a_pos, "histogram with more cycles must be printed first:\n{report}");
161
162 assert_eq!(sum_total_cycles(&report), 4);
164 }
165
166 #[test]
167 fn op_histogram_proc_collects_unattributed_ops_separately() {
168 let mut hist = OpHistogramProc::default();
169
170 hist.on_operation_execution_cycle(Operation::Add, None);
171 hist.on_operation_execution_cycle(Operation::Add, None);
172 hist.on_operation_execution_cycle(Operation::Noop, Some("main"));
173
174 let mut buf = Vec::new();
175 hist.write_report_to(&mut buf).unwrap();
176 let report = String::from_utf8(buf).unwrap();
177
178 let unknown = section(&report, "<unknown>");
180 assert_eq!(count_for(unknown, "total_cycles"), Some(2));
181 assert_eq!(count_for(unknown, "add"), Some(2));
182 assert_eq!(count_for(unknown, "noop"), None);
183
184 let main = section(&report, "main");
185 assert_eq!(count_for(main, "noop"), Some(1));
186 assert_eq!(count_for(main, "add"), None);
187
188 assert_eq!(sum_total_cycles(&report), 3);
190 }
191}