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