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) {
117 if PROFILING_ENABLED && let Ok(mut profiler) = PROFILER.lock() {
118 profiler.start_timer(section);
119 }
120}
121
122pub fn stop_timer(section: &str) {
126 if PROFILING_ENABLED && let Ok(mut profiler) = PROFILER.lock() {
127 profiler.stop_timer(section);
128 }
129}
130
131pub fn get_report() -> String {
135 if PROFILING_ENABLED {
136 match PROFILER.lock() {
137 Ok(profiler) => profiler.get_report(),
138 Err(_) => "Profiling report unavailable (mutex poisoned).".to_string(),
139 }
140 } else {
141 "Profiling is disabled.".to_string()
142 }
143}
144
145pub fn reset() {
149 if PROFILING_ENABLED && let Ok(mut profiler) = PROFILER.lock() {
150 profiler.reset();
151 }
152}
153
154pub struct ScopedTimer {
156 section: String,
157 enabled: bool,
158}
159
160impl ScopedTimer {
161 pub fn new(section: &str) -> Self {
163 let enabled = PROFILING_ENABLED;
164 if enabled {
165 start_timer(section);
166 }
167 ScopedTimer {
168 section: section.to_string(),
169 enabled,
170 }
171 }
172}
173
174impl Drop for ScopedTimer {
175 fn drop(&mut self) {
176 if self.enabled {
177 stop_timer(&self.section);
178 }
179 }
180}
181
182#[macro_export]
184macro_rules! time_section {
185 ($section:expr, $block:block) => {{
186 let _timer = $crate::profiling::ScopedTimer::new($section);
187 $block
188 }};
189}
190
191#[macro_export]
193macro_rules! time_function {
194 ($section:expr, $func:expr) => {{
195 let _timer = $crate::profiling::ScopedTimer::new($section);
196 $func
197 }};
198}
199
200#[cfg(test)]
201mod tests {
202 use super::*;
203 use std::thread;
204 use std::time::Duration;
205
206 #[test]
207 fn test_profiler_new() {
208 let profiler = Profiler::new();
209 assert!(profiler.measurements.is_empty());
210 assert!(profiler.active_timers.is_empty());
211 }
212
213 #[test]
214 fn test_profiler_default() {
215 let profiler = Profiler::default();
216 assert!(profiler.measurements.is_empty());
217 assert!(profiler.active_timers.is_empty());
218 }
219
220 #[test]
221 fn test_profiler_start_stop_timer() {
222 let mut profiler = Profiler::new();
223
224 if PROFILING_ENABLED {
226 profiler.start_timer("test_section");
227 thread::sleep(Duration::from_millis(10));
228 profiler.stop_timer("test_section");
229
230 assert!(profiler.measurements.contains_key("test_section"));
231 let (duration, count) = profiler.measurements.get("test_section").unwrap();
232 assert!(*count == 1);
233 assert!(duration.as_millis() >= 10);
234 }
235 }
236
237 #[test]
238 fn test_profiler_multiple_measurements() {
239 let mut profiler = Profiler::new();
240
241 if PROFILING_ENABLED {
242 for _ in 0..3 {
244 profiler.start_timer("test_section");
245 thread::sleep(Duration::from_millis(5));
246 profiler.stop_timer("test_section");
247 }
248
249 assert!(profiler.measurements.contains_key("test_section"));
250 let (duration, count) = profiler.measurements.get("test_section").unwrap();
251 assert_eq!(*count, 3);
252 assert!(duration.as_millis() >= 15);
253 }
254 }
255
256 #[test]
257 fn test_profiler_get_report() {
258 let mut profiler = Profiler::new();
259
260 if PROFILING_ENABLED {
261 profiler.start_timer("section1");
262 thread::sleep(Duration::from_millis(20));
263 profiler.stop_timer("section1");
264
265 profiler.start_timer("section2");
266 thread::sleep(Duration::from_millis(10));
267 profiler.stop_timer("section2");
268
269 let report = profiler.get_report();
270 assert!(report.contains("Profiling Report"));
271 assert!(report.contains("section1"));
272 assert!(report.contains("section2"));
273 assert!(report.contains("Total execution time"));
274 } else {
275 let report = profiler.get_report();
276 assert_eq!(report, "Profiling disabled or no measurements recorded.");
277 }
278 }
279
280 #[test]
281 fn test_profiler_reset() {
282 let mut profiler = Profiler::new();
283
284 if PROFILING_ENABLED {
285 profiler.start_timer("test_section");
286 profiler.stop_timer("test_section");
287
288 assert!(!profiler.measurements.is_empty());
289
290 profiler.reset();
291 assert!(profiler.measurements.is_empty());
292 assert!(profiler.active_timers.is_empty());
293 }
294 }
295
296 #[test]
297 fn test_profiler_stop_without_start() {
298 let mut profiler = Profiler::new();
299
300 profiler.stop_timer("nonexistent_section");
302 assert!(profiler.measurements.is_empty());
303 }
304
305 #[test]
306 #[serial_test::serial]
307 fn test_global_start_stop_timer() {
308 if PROFILING_ENABLED {
309 reset(); start_timer("global_test");
312 thread::sleep(Duration::from_millis(10));
313 stop_timer("global_test");
314
315 let report = get_report();
316 assert!(report.contains("global_test"));
317 }
318 }
319
320 #[test]
321 fn test_global_get_report() {
322 let report = get_report();
323 if PROFILING_ENABLED {
324 assert!(report.contains("Profiling Report") || report.contains("no measurements"));
325 } else {
326 assert_eq!(report, "Profiling is disabled.");
327 }
328 }
329
330 #[test]
331 #[serial_test::serial]
332 fn test_global_reset() {
333 if PROFILING_ENABLED {
334 start_timer("test_reset");
335 stop_timer("test_reset");
336
337 reset();
338 let report = get_report();
339 assert!(!report.contains("test_reset"));
340 }
341 }
342
343 #[test]
344 #[serial_test::serial]
345 fn test_scoped_timer() {
346 if PROFILING_ENABLED {
347 reset();
348
349 {
350 let _timer = ScopedTimer::new("scoped_test");
351 thread::sleep(Duration::from_millis(10));
352 } let report = get_report();
355 assert!(report.contains("scoped_test"));
356 }
357 }
358
359 #[test]
360 fn test_scoped_timer_drop() {
361 let timer = ScopedTimer::new("drop_test");
362 assert_eq!(timer.section, "drop_test");
363 assert_eq!(timer.enabled, PROFILING_ENABLED);
364 }
366
367 #[test]
368 fn test_empty_report() {
369 let profiler = Profiler::new();
370 let report = profiler.get_report();
371
372 if PROFILING_ENABLED {
373 assert_eq!(report, "Profiling disabled or no measurements recorded.");
374 }
375 }
376
377 #[test]
378 fn test_report_formatting() {
379 let mut profiler = Profiler::new();
380
381 if PROFILING_ENABLED {
382 profiler
384 .measurements
385 .insert("test1".to_string(), (Duration::from_secs(1), 10));
386 profiler
387 .measurements
388 .insert("test2".to_string(), (Duration::from_millis(500), 5));
389
390 let report = profiler.get_report();
391
392 assert!(report.contains("Section"));
394 assert!(report.contains("Total Time (s)"));
395 assert!(report.contains("Calls"));
396 assert!(report.contains("Avg Time (ms)"));
397 assert!(report.contains("% of Total"));
398
399 let test1_pos = report.find("test1").unwrap();
401 let test2_pos = report.find("test2").unwrap();
402 assert!(test1_pos < test2_pos);
403 }
404 }
405
406 #[test]
407 #[serial_test::serial]
408 fn test_concurrent_access() {
409 use std::sync::Arc;
410 use std::sync::Barrier;
411
412 if PROFILING_ENABLED {
413 reset();
414
415 let barrier = Arc::new(Barrier::new(3));
416 let mut handles = vec![];
417
418 for i in 0..3 {
419 let b = barrier.clone();
420 let handle = thread::spawn(move || {
421 b.wait();
422 start_timer(&format!("thread_{i}"));
423 thread::sleep(Duration::from_millis(10));
424 stop_timer(&format!("thread_{i}"));
425 });
426 handles.push(handle);
427 }
428
429 for handle in handles {
430 handle.join().unwrap();
431 }
432
433 let report = get_report();
434 assert!(report.contains("thread_0"));
435 assert!(report.contains("thread_1"));
436 assert!(report.contains("thread_2"));
437 }
438 }
439}