memscope_rs/tracking/
stats.rs

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