memscope_rs/tracking/
stats.rs1use std::sync::atomic::{AtomicUsize, Ordering};
2use std::time::{Duration, Instant};
3
4#[derive(Debug)]
9pub struct TrackingStats {
10 pub total_attempts: AtomicUsize,
12 pub successful_tracks: AtomicUsize,
14 pub missed_due_to_contention: AtomicUsize,
16 pub last_warning_time: std::sync::Mutex<Option<Instant>>,
18}
19
20impl TrackingStats {
21 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 #[inline]
33 pub fn record_attempt(&self) {
34 self.total_attempts.fetch_add(1, Ordering::Relaxed);
35 }
36
37 #[inline]
39 pub fn record_success(&self) {
40 self.successful_tracks.fetch_add(1, Ordering::Relaxed);
41 }
42
43 #[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 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 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 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 fn maybe_warn(&self) {
100 let completeness = self.get_completeness();
101
102 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#[derive(Debug, Clone)]
134pub struct DetailedStats {
135 pub total_attempts: usize,
137 pub successful_tracks: usize,
139 pub missed_due_to_contention: usize,
141 pub completeness: f64,
143 pub contention_rate: f64,
145}
146
147impl DetailedStats {
148 pub fn is_healthy(&self) -> bool {
150 self.completeness >= 0.95 && self.contention_rate <= 0.05
151 }
152
153 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 assert_eq!(stats.get_completeness(), 1.0);
176
177 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 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 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 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 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); 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}