Skip to main content

memscope_rs/tracking/
stats.rs

1use std::sync::atomic::{AtomicUsize, Ordering};
2use std::time::{Duration, Instant};
3use tracing::warn;
4
5/// Memory tracking statistics
6///
7/// Provides tracking completeness monitoring and quality warnings
8/// to ensure users can monitor memory tracking quality in real-time.
9#[derive(Debug)]
10pub struct TrackingStats {
11    /// Total tracking attempts
12    pub total_attempts: AtomicUsize,
13    /// Successful tracking count  
14    pub successful_tracks: AtomicUsize,
15    /// Failed attempts due to contention
16    pub missed_due_to_contention: AtomicUsize,
17    /// Last warning time for rate limiting
18    pub last_warning_time: std::sync::Mutex<Option<Instant>>,
19}
20
21impl TrackingStats {
22    /// Create new tracking statistics instance
23    pub fn new() -> Self {
24        Self {
25            total_attempts: AtomicUsize::new(0),
26            successful_tracks: AtomicUsize::new(0),
27            missed_due_to_contention: AtomicUsize::new(0),
28            last_warning_time: std::sync::Mutex::new(None),
29        }
30    }
31
32    /// Record a tracking attempt
33    #[inline]
34    pub fn record_attempt(&self) {
35        self.total_attempts.fetch_add(1, Ordering::Relaxed);
36    }
37
38    /// Record a successful tracking
39    #[inline]
40    pub fn record_success(&self) {
41        self.successful_tracks.fetch_add(1, Ordering::Relaxed);
42    }
43
44    /// Record a failed tracking due to contention
45    #[inline]
46    pub fn record_miss(&self) {
47        self.missed_due_to_contention
48            .fetch_add(1, Ordering::Relaxed);
49        self.maybe_warn();
50    }
51
52    /// Get tracking completeness percentage
53    ///
54    /// # Returns
55    ///
56    /// Returns value between 0.0-1.0 representing successful tracking ratio
57    pub fn get_completeness(&self) -> f64 {
58        let attempts = self.total_attempts.load(Ordering::Relaxed);
59        let successful = self.successful_tracks.load(Ordering::Relaxed);
60        if attempts == 0 {
61            1.0
62        } else {
63            successful as f64 / attempts as f64
64        }
65    }
66
67    /// Get detailed statistics
68    pub fn get_detailed_stats(&self) -> DetailedStats {
69        let attempts = self.total_attempts.load(Ordering::Relaxed);
70        let successful = self.successful_tracks.load(Ordering::Relaxed);
71        let missed = self.missed_due_to_contention.load(Ordering::Relaxed);
72
73        DetailedStats {
74            total_attempts: attempts,
75            successful_tracks: successful,
76            missed_due_to_contention: missed,
77            completeness: self.get_completeness(),
78            contention_rate: if attempts > 0 {
79                missed as f64 / attempts as f64
80            } else {
81                0.0
82            },
83        }
84    }
85
86    /// Reset all statistics counters
87    pub fn reset(&self) {
88        self.total_attempts.store(0, Ordering::Relaxed);
89        self.successful_tracks.store(0, Ordering::Relaxed);
90        self.missed_due_to_contention.store(0, Ordering::Relaxed);
91
92        if let Ok(mut last_warning) = self.last_warning_time.lock() {
93            *last_warning = None;
94        }
95    }
96
97    /// Check if warning should be issued
98    ///
99    /// Warns when tracking completeness drops below 90%, limited to once per 10 seconds
100    fn maybe_warn(&self) {
101        let completeness = self.get_completeness();
102
103        // Only consider warning when completeness is below 90%
104        if completeness < 0.9 {
105            if let Ok(mut last_warning) = self.last_warning_time.lock() {
106                let now = Instant::now();
107                let should_warn = last_warning
108                    .map(|last| now.duration_since(last) > Duration::from_secs(10))
109                    .unwrap_or(true);
110
111                if should_warn {
112                    let stats = self.get_detailed_stats();
113                    warn!(
114                        "Memory tracking completeness: {:.1}% ({}/{} successful, {} missed due to contention)",
115                        completeness * 100.0,
116                        stats.successful_tracks,
117                        stats.total_attempts,
118                        stats.missed_due_to_contention
119                    );
120                    *last_warning = Some(now);
121                }
122            }
123        }
124    }
125}
126
127impl Default for TrackingStats {
128    fn default() -> Self {
129        Self::new()
130    }
131}
132
133/// Detailed tracking statistics
134#[derive(Debug, Clone)]
135pub struct DetailedStats {
136    /// Total attempts count
137    pub total_attempts: usize,
138    /// Successful tracking count
139    pub successful_tracks: usize,
140    /// Failed attempts due to contention
141    pub missed_due_to_contention: usize,
142    /// Tracking completeness (0.0-1.0)
143    pub completeness: f64,
144    /// Lock contention rate (0.0-1.0)
145    pub contention_rate: f64,
146}
147
148impl DetailedStats {
149    /// Check if tracking quality is healthy
150    pub fn is_healthy(&self) -> bool {
151        self.completeness >= 0.95 && self.contention_rate <= 0.05
152    }
153
154    /// Get quality grade description
155    pub fn quality_grade(&self) -> &'static str {
156        match self.completeness {
157            x if x >= 0.98 => "Excellent",
158            x if x >= 0.95 => "Good",
159            x if x >= 0.90 => "Fair",
160            x if x >= 0.80 => "Poor",
161            _ => "Critical",
162        }
163    }
164}
165
166#[cfg(test)]
167mod tests {
168    use super::*;
169    use std::thread;
170
171    #[test]
172    fn test_tracking_stats_basic() {
173        let stats = TrackingStats::new();
174
175        // Initial state
176        assert_eq!(stats.get_completeness(), 1.0);
177
178        // Record some operations
179        stats.record_attempt();
180        stats.record_success();
181        assert_eq!(stats.get_completeness(), 1.0);
182
183        stats.record_attempt();
184        stats.record_miss();
185        assert_eq!(stats.get_completeness(), 0.5);
186    }
187
188    #[test]
189    fn test_detailed_stats() {
190        let stats = TrackingStats::new();
191
192        // Simulate some tracking operations
193        for _ in 0..100 {
194            stats.record_attempt();
195            stats.record_success();
196        }
197
198        for _ in 0..5 {
199            stats.record_attempt();
200            stats.record_miss();
201        }
202
203        let detailed = stats.get_detailed_stats();
204        assert_eq!(detailed.total_attempts, 105);
205        assert_eq!(detailed.successful_tracks, 100);
206        assert_eq!(detailed.missed_due_to_contention, 5);
207        assert!((detailed.completeness - 0.9523).abs() < 0.001);
208        assert!(detailed.is_healthy());
209    }
210
211    #[test]
212    fn test_quality_grades() {
213        let stats = TrackingStats::new();
214
215        // Test different quality levels
216        let test_cases = vec![
217            (100, 100, "Excellent"),
218            (100, 96, "Good"),
219            (100, 92, "Fair"),
220            (100, 85, "Poor"),
221            (100, 70, "Critical"),
222        ];
223
224        for (attempts, successes, expected_grade) in test_cases {
225            stats.reset();
226
227            for _ in 0..attempts {
228                stats.record_attempt();
229            }
230            for _ in 0..successes {
231                stats.record_success();
232            }
233
234            let detailed = stats.get_detailed_stats();
235            assert_eq!(detailed.quality_grade(), expected_grade);
236        }
237    }
238
239    #[test]
240    fn test_concurrent_access() {
241        let stats = std::sync::Arc::new(TrackingStats::new());
242        let mut handles = vec![];
243
244        // Start multiple threads for concurrent access
245        for _ in 0..4 {
246            let stats_clone = stats.clone();
247            let handle = thread::spawn(move || {
248                for _ in 0..1000 {
249                    stats_clone.record_attempt();
250                    #[allow(clippy::manual_is_multiple_of)]
251                    if thread_local_random() % 10 != 0 {
252                        stats_clone.record_success();
253                    } else {
254                        stats_clone.record_miss();
255                    }
256                }
257            });
258            handles.push(handle);
259        }
260
261        // Wait for all threads to complete
262        for handle in handles {
263            handle.join().unwrap();
264        }
265
266        let detailed = stats.get_detailed_stats();
267        assert_eq!(detailed.total_attempts, 4000);
268        assert!(detailed.successful_tracks >= 3000); // approximately 90% success rate
269        assert!(detailed.completeness >= 0.8);
270    }
271
272    fn thread_local_random() -> usize {
273        use std::cell::Cell;
274        thread_local! {
275            static RNG: Cell<usize> = const { Cell::new(1) };
276        }
277
278        RNG.with(|rng| {
279            let x = rng.get();
280            let next = x.wrapping_mul(1103515245).wrapping_add(12345);
281            rng.set(next);
282            next
283        })
284    }
285}