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