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