Skip to main content

miden_debug_engine/profiling/instrument/
op_histogram_proc.rs

1use 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
13/// Map key under which operations that cannot be attributed to a procedure are collected. The
14/// angle brackets cannot occur in a MASM identifier, so this never collides with a procedure name
15const UNKNOWN_PROCEDURE: &str = "<unknown>";
16
17/// An [`Instrument`] to create per-procedure operation histograms.
18///
19/// At each cycle, it records the current operation into the histogram of the most recent live
20/// procedure. Operations that cannot be attributed to a procedure are collected into a separate
21/// histogram, reported under [`UNKNOWN_PROCEDURE`].
22///
23/// The report contains one section per procedure, sorted by the number of cycles spent
24/// in that procedure (highest first).
25#[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        // Look up by borrow first so the key is only cloned when a new procedure is seen.
49        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        // Print the histogram with the highest total cycle count first, break ties by procedure
63        // name for a stable order.
64        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;