Skip to main content

amaters_core/
profiling.rs

1//! CPU and memory profiling infrastructure for AmateRS.
2
3use std::sync::atomic::{AtomicU64, Ordering};
4use std::time::{Duration, Instant};
5
6/// Global counter for tracked allocation events.
7pub static ALLOC_COUNT: AtomicU64 = AtomicU64::new(0);
8
9/// Global counter for tracked allocated bytes.
10pub static ALLOC_BYTES: AtomicU64 = AtomicU64::new(0);
11
12/// A guard that measures wall-clock time for a named operation.
13pub struct ProfilingGuard {
14    name: &'static str,
15    start: Instant,
16}
17
18impl ProfilingGuard {
19    /// Start timing an operation named `name`.
20    ///
21    /// # Example
22    ///
23    /// ```
24    /// use amaters_core::profiling::ProfilingGuard;
25    ///
26    /// let guard = ProfilingGuard::new("my_operation");
27    /// // Do some work...
28    /// let summary = guard.finish();
29    /// println!("Operation took {}µs", summary.elapsed.as_micros());
30    /// ```
31    pub fn new(name: &'static str) -> Self {
32        Self {
33            name,
34            start: Instant::now(),
35        }
36    }
37
38    /// Return the elapsed duration since this guard was created.
39    pub fn elapsed(&self) -> Duration {
40        self.start.elapsed()
41    }
42
43    /// Consume the guard and return a [`ProfilingSummary`].
44    pub fn finish(self) -> ProfilingSummary {
45        let elapsed = self.elapsed();
46        let alloc_count_delta = ALLOC_COUNT.load(Ordering::Relaxed);
47        let alloc_bytes_delta = ALLOC_BYTES.load(Ordering::Relaxed);
48
49        tracing::debug!(
50            target: "amaters::profiling",
51            name = self.name,
52            elapsed_us = elapsed.as_micros() as u64,
53            alloc_count = alloc_count_delta,
54            alloc_bytes = alloc_bytes_delta,
55            "profiling guard finished"
56        );
57
58        let name = self.name;
59        // Prevent the Drop impl from also logging
60        std::mem::forget(self);
61
62        ProfilingSummary {
63            name,
64            elapsed,
65            alloc_count_delta,
66            alloc_bytes_delta,
67        }
68    }
69}
70
71impl Drop for ProfilingGuard {
72    fn drop(&mut self) {
73        tracing::debug!(
74            target: "amaters::profiling",
75            name = self.name,
76            elapsed_us = self.elapsed().as_micros() as u64,
77            "profiling guard dropped"
78        );
79    }
80}
81
82/// Summary of a profiled operation.
83#[derive(Debug, Clone)]
84pub struct ProfilingSummary {
85    /// Name passed to [`ProfilingGuard::new`].
86    pub name: &'static str,
87    /// Wall-clock time measured by the guard.
88    pub elapsed: Duration,
89    /// Global `ALLOC_COUNT` value at `finish()` time.
90    pub alloc_count_delta: u64,
91    /// Global `ALLOC_BYTES` value at `finish()` time.
92    pub alloc_bytes_delta: u64,
93}
94
95#[cfg(test)]
96mod tests {
97    use super::*;
98    use std::thread;
99
100    #[test]
101    fn test_profiling_guard_measures_elapsed() {
102        let guard = ProfilingGuard::new("test_op");
103        thread::sleep(Duration::from_millis(5));
104        let elapsed = guard.elapsed();
105        assert!(elapsed >= Duration::from_millis(1), "elapsed: {elapsed:?}");
106        drop(guard);
107    }
108
109    #[test]
110    fn test_profiling_summary_has_valid_duration() {
111        let guard = ProfilingGuard::new("summary_op");
112        thread::sleep(Duration::from_millis(2));
113        let summary = guard.finish();
114        assert_eq!(summary.name, "summary_op");
115        assert!(
116            summary.elapsed >= Duration::from_millis(1),
117            "elapsed: {:?}",
118            summary.elapsed
119        );
120    }
121
122    #[test]
123    fn test_profiling_guard_drop_does_not_panic() {
124        let guard = ProfilingGuard::new("drop_test");
125        drop(guard);
126    }
127}