miden_debug_engine/profiling/
profiler.rs1use alloc::{boxed::Box, vec::Vec};
2
3use miden_core::operations::Operation;
4
5use crate::profiling::{OutputResult, OutputWriter, ProfilerConfig, instrument::Instrument};
6
7#[derive(Default)]
11pub struct Profiler {
12 instruments: Vec<Box<dyn Instrument>>,
13 #[cfg(feature = "std")]
14 reports_dir: Option<std::path::PathBuf>,
15}
16
17impl Profiler {
18 pub fn from_config(config: ProfilerConfig) -> Self {
19 Self {
20 instruments: config.instruments,
21 #[cfg(feature = "std")]
22 reports_dir: config.reports_dir,
23 }
24 }
25
26 pub fn on_operation_execution_cycle(&mut self, op: Operation, proc: Option<&str>) {
28 for instrument in &mut self.instruments {
29 instrument.on_operation_execution_cycle(op, proc);
30 }
31 }
32
33 #[cfg(feature = "std")]
38 pub fn write_reports(&self) {
39 if self.instruments.is_empty() {
40 return;
41 }
42
43 let Some(ref reports_dir) = self.reports_dir else {
44 log::warn!("cannot write profiler reports: no reports directory configured");
45 return;
46 };
47
48 if let Err(e) = std::fs::create_dir_all(reports_dir) {
49 log::error!(
50 "failed to create profiler reports directory {}: {e}",
51 reports_dir.display()
52 );
53 return;
54 }
55
56 for instrument in &self.instruments {
57 let name = instrument.name();
58 let path = reports_dir.join(name);
59 let mut file = match std::fs::File::create(&path) {
60 Ok(file) => file,
61 Err(e) => {
62 log::error!(
63 "failed to create profiler output file for `{name}` at {}: {e}",
64 path.display()
65 );
66 continue;
67 }
68 };
69 if let Err(e) = instrument.write_report_to(&mut file) {
70 log::error!("failed to write `{name}` report to {}: {e}", path.display());
71 }
72 }
73 }
74
75 pub fn write_reports_to(&self, writer: &mut dyn OutputWriter) -> OutputResult<()> {
77 if self.instruments.is_empty() {
78 return Ok(());
79 }
80
81 for instrument in &self.instruments {
82 instrument.write_report_to(writer)?;
83 }
84 Ok(())
85 }
86}
87
88#[cfg(all(test, feature = "std"))]
89mod tests {
90 use alloc::string::String;
91 use std::collections::HashMap;
92
93 use miden_core::operations::Operation;
94
95 use super::*;
96 use crate::profiling::{OutputResult, OutputWriter, ProfilerConfig};
97
98 struct CountingInstrument {
100 name: &'static str,
101 ops: u32,
102 }
103
104 impl Instrument for CountingInstrument {
105 fn name(&self) -> &'static str {
106 self.name
107 }
108
109 fn on_operation_execution_cycle(&mut self, _op: Operation, _proc: Option<&str>) {
110 self.ops += 1;
111 }
112
113 fn write_report_to(&self, writer: &mut dyn OutputWriter) -> OutputResult<()> {
114 writer.write_fmt(format_args!("{}:{}", self.name, self.ops))
115 }
116 }
117
118 #[test]
119 fn profiler_writes_reports_to_directory() {
120 let tmp_dir = tempfile::tempdir().unwrap();
121
122 let config = ProfilerConfig {
123 instruments: vec![
124 Box::new(CountingInstrument {
125 name: "alpha",
126 ops: 0,
127 }),
128 Box::new(CountingInstrument {
129 name: "beta",
130 ops: 0,
131 }),
132 ],
133 reports_dir: Some(tmp_dir.path().to_path_buf()),
134 };
135 let mut profiler = Profiler::from_config(config);
136
137 profiler.on_operation_execution_cycle(Operation::Add, None);
139 profiler.on_operation_execution_cycle(Operation::Noop, None);
140 profiler.on_operation_execution_cycle(Operation::Add, None);
141
142 profiler.write_reports();
143
144 let alpha_content = std::fs::read_to_string(tmp_dir.path().join("alpha")).unwrap();
146 assert_eq!(alpha_content, "alpha:3");
147
148 let beta_content = std::fs::read_to_string(tmp_dir.path().join("beta")).unwrap();
149 assert_eq!(beta_content, "beta:3");
150 }
151
152 #[test]
153 fn profiler_dispatches_events_to_all_instruments() {
154 let config = ProfilerConfig {
155 instruments: vec![
156 Box::new(CountingInstrument { name: "a", ops: 0 }),
157 Box::new(CountingInstrument { name: "b", ops: 0 }),
158 ],
159 ..Default::default()
160 };
161 let mut profiler = Profiler::from_config(config);
162
163 profiler.on_operation_execution_cycle(Operation::Add, None);
165 profiler.on_operation_execution_cycle(Operation::Noop, None);
166
167 fn report_of(instrument: &dyn Instrument) -> String {
169 let mut buf = Vec::new();
170 instrument.write_report_to(&mut buf).unwrap();
171 String::from_utf8(buf).unwrap()
172 }
173
174 let reports: HashMap<&'static str, String> = profiler
175 .instruments
176 .iter()
177 .map(|instrument| (instrument.name(), report_of(instrument.as_ref())))
178 .collect();
179
180 assert_eq!(reports.get("a").unwrap(), "a:2");
181 assert_eq!(reports.get("b").unwrap(), "b:2");
182 }
183}