Skip to main content

miden_debug_engine/profiling/instrument/
op_histogram_global.rs

1use miden_core::operations::Operation;
2
3use super::{Instrument, InstrumentRegistration};
4use crate::profiling::{OutputResult, OutputWriter, helpers::op_histogram::OpHistogram};
5
6/// An [`Instrument`] to create global operation histograms.
7///
8/// The global histogram aggregates across all procedures over the entire runtime.
9///
10/// At each cycle, it records the current operation and produces a histogram of executed operations
11/// weighted by cycles per operation. If `opX` takes 4 cycles and was executed twice, its count
12/// will be 8.
13#[derive(Default)]
14pub struct OpHistogramGlobal {
15    hist: OpHistogram,
16}
17
18impl InstrumentRegistration for OpHistogramGlobal {
19    const NAME: &'static str = "op-histogram-global";
20
21    fn build(_config: &crate::profiling::ProfilerConfig) -> Result<Self, super::InstrumentError> {
22        Ok(Self::default())
23    }
24}
25
26#[cfg(feature = "std")]
27crate::register_instrument!(OpHistogramGlobal);
28
29impl Instrument for OpHistogramGlobal {
30    fn name(&self) -> &'static str {
31        Self::NAME
32    }
33
34    fn on_operation_execution_cycle(&mut self, op: Operation, _proc: Option<&str>) {
35        // The global histogram aggregates over all procedures, ignoring `proc`.
36        self.hist.record(op);
37    }
38
39    fn write_report_to(&self, writer: &mut dyn OutputWriter) -> OutputResult<()> {
40        writer.write_all(self.hist.report().as_bytes())
41    }
42}
43
44#[cfg(test)]
45mod tests {
46    use alloc::{
47        string::{String, ToString},
48        vec::Vec,
49    };
50
51    use miden_core::operations::Operation;
52
53    use super::OpHistogramGlobal;
54    use crate::profiling::instrument::Instrument;
55
56    #[test]
57    fn op_histogram_reports_recorded_ops() {
58        let mut hist = OpHistogramGlobal::default();
59
60        // 3 cycles, procedure names are ignored by the global histogram.
61        hist.on_operation_execution_cycle(Operation::Add, Some("main"));
62        hist.on_operation_execution_cycle(Operation::Add, Some("sum"));
63        hist.on_operation_execution_cycle(Operation::Noop, None);
64
65        let mut buf = Vec::new();
66        hist.write_report_to(&mut buf).unwrap();
67        let report = String::from_utf8(buf).unwrap();
68
69        let lines: Vec<&str> = report.lines().collect();
70        // First line is the header reporting 100% of 3 cycles.
71        assert!(lines[0].contains("total_cycles") && lines[0].contains('3'));
72
73        // `Add` must preceed `Noop`, due to higher count.
74        assert!(lines[1].contains(Operation::Add.to_string().as_str()) && lines[1].contains("2"));
75        assert!(lines[2].contains(Operation::Noop.to_string().as_str()) && lines[2].contains("1"));
76
77        // no further lines
78        assert_eq!(lines.len(), 3);
79    }
80
81    /// The global histogram must not be affected by which procedure an op is recorded in.
82    #[test]
83    fn op_histogram_ignores_procedure() {
84        let mut across_procs = OpHistogramGlobal::default();
85        let mut single_proc = OpHistogramGlobal::default();
86
87        across_procs.on_operation_execution_cycle(Operation::Add, Some("sum"));
88        across_procs.on_operation_execution_cycle(Operation::Add, Some("main"));
89        across_procs.on_operation_execution_cycle(Operation::Noop, None);
90
91        single_proc.on_operation_execution_cycle(Operation::Add, Some("main"));
92        single_proc.on_operation_execution_cycle(Operation::Add, Some("main"));
93        single_proc.on_operation_execution_cycle(Operation::Noop, Some("main"));
94
95        assert_eq!(across_procs.hist.report(), single_proc.hist.report());
96    }
97}