1use std::collections::HashMap;
5use std::sync::LazyLock;
6use std::sync::Mutex;
7use std::time::{Duration, Instant};
8
9static PROFILER: LazyLock<Mutex<Profiler>> = LazyLock::new(|| Mutex::new(Profiler::new()));
11
12#[cfg(feature = "profiling")]
14pub(crate) const PROFILING_ENABLED: bool = true;
15
16#[cfg(not(feature = "profiling"))]
17pub(crate) const PROFILING_ENABLED: bool = false;
18
19pub struct Profiler {
21 measurements: HashMap<String, (Duration, usize)>,
22 active_timers: HashMap<String, Instant>,
23}
24
25impl Default for Profiler {
26 fn default() -> Self {
27 Self::new()
28 }
29}
30
31impl Profiler {
32 pub fn new() -> Self {
34 Profiler {
35 measurements: HashMap::new(),
36 active_timers: HashMap::new(),
37 }
38 }
39
40 pub fn start_timer(&mut self, section: &str) {
42 if PROFILING_ENABLED {
43 let section_name = section.to_string();
44 self.active_timers.insert(section_name, Instant::now());
45 }
46 }
47
48 pub fn stop_timer(&mut self, section: &str) {
50 if PROFILING_ENABLED {
51 let section_name = section.to_string();
52 if let Some(start_time) = self.active_timers.remove(§ion_name) {
53 let elapsed = start_time.elapsed();
54
55 let entry = self
57 .measurements
58 .entry(section_name)
59 .or_insert((Duration::new(0, 0), 0));
60 entry.0 += elapsed;
61 entry.1 += 1;
62 }
63 }
64 }
65
66 pub fn get_report(&self) -> String {
68 if !PROFILING_ENABLED || self.measurements.is_empty() {
69 return "Profiling disabled or no measurements recorded.".to_string();
70 }
71
72 let mut sorted_measurements: Vec<_> = self.measurements.iter().collect();
74 sorted_measurements.sort_by(|a, b| b.1.0.cmp(&a.1.0));
75
76 let total_time: Duration = sorted_measurements.iter().map(|(_, (duration, _))| duration).sum();
78
79 let mut report = String::new();
81 report.push_str("=== Profiling Report ===\n");
82 report.push_str(&format!(
83 "Total execution time: {:.6} seconds\n\n",
84 total_time.as_secs_f64()
85 ));
86 report.push_str(
87 "Section | Total Time (s) | Calls | Avg Time (ms) | % of Total\n",
88 );
89 report.push_str(
90 "------------------------------------------|----------------|-------|---------------|----------\n",
91 );
92
93 for (section, (duration, calls)) in sorted_measurements {
94 let total_seconds = duration.as_secs_f64();
95 let avg_ms = (duration.as_nanos() as f64) / (calls * 1_000_000) as f64;
96 let percentage = (total_seconds / total_time.as_secs_f64()) * 100.0;
97
98 report.push_str(&format!(
99 "{section:<42} | {total_seconds:<14.6} | {calls:<5} | {avg_ms:<13.3} | {percentage:<8.2}%\n"
100 ));
101 }
102
103 report
104 }
105
106 pub fn reset(&mut self) {
108 self.measurements.clear();
109 self.active_timers.clear();
110 }
111}
112
113pub fn start_timer(section: &str) {
115 if PROFILING_ENABLED {
116 let mut profiler = PROFILER.lock().expect("Profiler mutex poisoned");
117 profiler.start_timer(section);
118 }
119}
120
121pub fn stop_timer(section: &str) {
123 if PROFILING_ENABLED {
124 let mut profiler = PROFILER.lock().expect("Profiler mutex poisoned");
125 profiler.stop_timer(section);
126 }
127}
128
129pub fn get_report() -> String {
131 if PROFILING_ENABLED {
132 let profiler = PROFILER.lock().expect("Profiler mutex poisoned");
133 profiler.get_report()
134 } else {
135 "Profiling is disabled.".to_string()
136 }
137}
138
139pub fn reset() {
141 if PROFILING_ENABLED {
142 let mut profiler = PROFILER.lock().expect("Profiler mutex poisoned");
143 profiler.reset();
144 }
145}
146
147pub struct ScopedTimer {
149 section: String,
150 enabled: bool,
151}
152
153impl ScopedTimer {
154 pub fn new(section: &str) -> Self {
156 let enabled = PROFILING_ENABLED;
157 if enabled {
158 start_timer(section);
159 }
160 ScopedTimer {
161 section: section.to_string(),
162 enabled,
163 }
164 }
165}
166
167impl Drop for ScopedTimer {
168 fn drop(&mut self) {
169 if self.enabled {
170 stop_timer(&self.section);
171 }
172 }
173}
174
175#[macro_export]
177macro_rules! time_section {
178 ($section:expr, $block:block) => {{
179 let _timer = $crate::profiling::ScopedTimer::new($section);
180 $block
181 }};
182}
183
184#[macro_export]
186macro_rules! time_function {
187 ($section:expr, $func:expr) => {{
188 let _timer = $crate::profiling::ScopedTimer::new($section);
189 $func
190 }};
191}
192
193#[cfg(test)]
194mod tests {
195 use super::*;
196 use std::thread;
197 use std::time::Duration;
198
199 #[test]
200 fn test_profiler_new() {
201 let profiler = Profiler::new();
202 assert!(profiler.measurements.is_empty());
203 assert!(profiler.active_timers.is_empty());
204 }
205
206 #[test]
207 fn test_profiler_default() {
208 let profiler = Profiler::default();
209 assert!(profiler.measurements.is_empty());
210 assert!(profiler.active_timers.is_empty());
211 }
212
213 #[test]
214 fn test_profiler_start_stop_timer() {
215 let mut profiler = Profiler::new();
216
217 if PROFILING_ENABLED {
219 profiler.start_timer("test_section");
220 thread::sleep(Duration::from_millis(10));
221 profiler.stop_timer("test_section");
222
223 assert!(profiler.measurements.contains_key("test_section"));
224 let (duration, count) = profiler.measurements.get("test_section").unwrap();
225 assert!(*count == 1);
226 assert!(duration.as_millis() >= 10);
227 }
228 }
229
230 #[test]
231 fn test_profiler_multiple_measurements() {
232 let mut profiler = Profiler::new();
233
234 if PROFILING_ENABLED {
235 for _ in 0..3 {
237 profiler.start_timer("test_section");
238 thread::sleep(Duration::from_millis(5));
239 profiler.stop_timer("test_section");
240 }
241
242 assert!(profiler.measurements.contains_key("test_section"));
243 let (duration, count) = profiler.measurements.get("test_section").unwrap();
244 assert_eq!(*count, 3);
245 assert!(duration.as_millis() >= 15);
246 }
247 }
248
249 #[test]
250 fn test_profiler_get_report() {
251 let mut profiler = Profiler::new();
252
253 if PROFILING_ENABLED {
254 profiler.start_timer("section1");
255 thread::sleep(Duration::from_millis(20));
256 profiler.stop_timer("section1");
257
258 profiler.start_timer("section2");
259 thread::sleep(Duration::from_millis(10));
260 profiler.stop_timer("section2");
261
262 let report = profiler.get_report();
263 assert!(report.contains("Profiling Report"));
264 assert!(report.contains("section1"));
265 assert!(report.contains("section2"));
266 assert!(report.contains("Total execution time"));
267 } else {
268 let report = profiler.get_report();
269 assert_eq!(report, "Profiling disabled or no measurements recorded.");
270 }
271 }
272
273 #[test]
274 fn test_profiler_reset() {
275 let mut profiler = Profiler::new();
276
277 if PROFILING_ENABLED {
278 profiler.start_timer("test_section");
279 profiler.stop_timer("test_section");
280
281 assert!(!profiler.measurements.is_empty());
282
283 profiler.reset();
284 assert!(profiler.measurements.is_empty());
285 assert!(profiler.active_timers.is_empty());
286 }
287 }
288
289 #[test]
290 fn test_profiler_stop_without_start() {
291 let mut profiler = Profiler::new();
292
293 profiler.stop_timer("nonexistent_section");
295 assert!(profiler.measurements.is_empty());
296 }
297
298 #[test]
299 #[serial_test::serial]
300 fn test_global_start_stop_timer() {
301 if PROFILING_ENABLED {
302 reset(); start_timer("global_test");
305 thread::sleep(Duration::from_millis(10));
306 stop_timer("global_test");
307
308 let report = get_report();
309 assert!(report.contains("global_test"));
310 }
311 }
312
313 #[test]
314 fn test_global_get_report() {
315 let report = get_report();
316 if PROFILING_ENABLED {
317 assert!(report.contains("Profiling Report") || report.contains("no measurements"));
318 } else {
319 assert_eq!(report, "Profiling is disabled.");
320 }
321 }
322
323 #[test]
324 #[serial_test::serial]
325 fn test_global_reset() {
326 if PROFILING_ENABLED {
327 start_timer("test_reset");
328 stop_timer("test_reset");
329
330 reset();
331 let report = get_report();
332 assert!(!report.contains("test_reset"));
333 }
334 }
335
336 #[test]
337 #[serial_test::serial]
338 fn test_scoped_timer() {
339 if PROFILING_ENABLED {
340 reset();
341
342 {
343 let _timer = ScopedTimer::new("scoped_test");
344 thread::sleep(Duration::from_millis(10));
345 } let report = get_report();
348 assert!(report.contains("scoped_test"));
349 }
350 }
351
352 #[test]
353 fn test_scoped_timer_drop() {
354 let timer = ScopedTimer::new("drop_test");
355 assert_eq!(timer.section, "drop_test");
356 assert_eq!(timer.enabled, PROFILING_ENABLED);
357 }
359
360 #[test]
361 fn test_empty_report() {
362 let profiler = Profiler::new();
363 let report = profiler.get_report();
364
365 if PROFILING_ENABLED {
366 assert_eq!(report, "Profiling disabled or no measurements recorded.");
367 }
368 }
369
370 #[test]
371 fn test_report_formatting() {
372 let mut profiler = Profiler::new();
373
374 if PROFILING_ENABLED {
375 profiler
377 .measurements
378 .insert("test1".to_string(), (Duration::from_secs(1), 10));
379 profiler
380 .measurements
381 .insert("test2".to_string(), (Duration::from_millis(500), 5));
382
383 let report = profiler.get_report();
384
385 assert!(report.contains("Section"));
387 assert!(report.contains("Total Time (s)"));
388 assert!(report.contains("Calls"));
389 assert!(report.contains("Avg Time (ms)"));
390 assert!(report.contains("% of Total"));
391
392 let test1_pos = report.find("test1").unwrap();
394 let test2_pos = report.find("test2").unwrap();
395 assert!(test1_pos < test2_pos);
396 }
397 }
398
399 #[test]
400 #[serial_test::serial]
401 fn test_concurrent_access() {
402 use std::sync::Arc;
403 use std::sync::Barrier;
404
405 if PROFILING_ENABLED {
406 reset();
407
408 let barrier = Arc::new(Barrier::new(3));
409 let mut handles = vec![];
410
411 for i in 0..3 {
412 let b = barrier.clone();
413 let handle = thread::spawn(move || {
414 b.wait();
415 start_timer(&format!("thread_{i}"));
416 thread::sleep(Duration::from_millis(10));
417 stop_timer(&format!("thread_{i}"));
418 });
419 handles.push(handle);
420 }
421
422 for handle in handles {
423 handle.join().unwrap();
424 }
425
426 let report = get_report();
427 assert!(report.contains("thread_0"));
428 assert!(report.contains("thread_1"));
429 assert!(report.contains("thread_2"));
430 }
431 }
432}