use std::sync::atomic::{AtomicUsize, Ordering};
use std::time::{Duration, Instant};
#[derive(Debug)]
pub struct TrackingStats {
pub total_attempts: AtomicUsize,
pub successful_tracks: AtomicUsize,
pub missed_due_to_contention: AtomicUsize,
pub last_warning_time: std::sync::Mutex<Option<Instant>>,
}
impl TrackingStats {
pub fn new() -> Self {
Self {
total_attempts: AtomicUsize::new(0),
successful_tracks: AtomicUsize::new(0),
missed_due_to_contention: AtomicUsize::new(0),
last_warning_time: std::sync::Mutex::new(None),
}
}
#[inline]
pub fn record_attempt(&self) {
self.total_attempts.fetch_add(1, Ordering::Relaxed);
}
#[inline]
pub fn record_success(&self) {
self.successful_tracks.fetch_add(1, Ordering::Relaxed);
}
#[inline]
pub fn record_miss(&self) {
self.missed_due_to_contention
.fetch_add(1, Ordering::Relaxed);
self.maybe_warn();
}
pub fn get_completeness(&self) -> f64 {
let attempts = self.total_attempts.load(Ordering::Relaxed);
let successful = self.successful_tracks.load(Ordering::Relaxed);
if attempts == 0 {
1.0
} else {
successful as f64 / attempts as f64
}
}
pub fn get_detailed_stats(&self) -> DetailedStats {
let attempts = self.total_attempts.load(Ordering::Relaxed);
let successful = self.successful_tracks.load(Ordering::Relaxed);
let missed = self.missed_due_to_contention.load(Ordering::Relaxed);
DetailedStats {
total_attempts: attempts,
successful_tracks: successful,
missed_due_to_contention: missed,
completeness: self.get_completeness(),
contention_rate: if attempts > 0 {
missed as f64 / attempts as f64
} else {
0.0
},
}
}
pub fn reset(&self) {
self.total_attempts.store(0, Ordering::Relaxed);
self.successful_tracks.store(0, Ordering::Relaxed);
self.missed_due_to_contention.store(0, Ordering::Relaxed);
if let Ok(mut last_warning) = self.last_warning_time.lock() {
*last_warning = None;
}
}
fn maybe_warn(&self) {
let completeness = self.get_completeness();
if completeness < 0.9 {
if let Ok(mut last_warning) = self.last_warning_time.lock() {
let now = Instant::now();
let should_warn = last_warning
.map(|last| now.duration_since(last) > Duration::from_secs(10))
.unwrap_or(true);
if should_warn {
let stats = self.get_detailed_stats();
eprintln!(
"WARNING: Memory tracking completeness: {:.1}% ({}/{} successful, {} missed due to contention)",
completeness * 100.0,
stats.successful_tracks,
stats.total_attempts,
stats.missed_due_to_contention
);
*last_warning = Some(now);
}
}
}
}
}
impl Default for TrackingStats {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone)]
pub struct DetailedStats {
pub total_attempts: usize,
pub successful_tracks: usize,
pub missed_due_to_contention: usize,
pub completeness: f64,
pub contention_rate: f64,
}
impl DetailedStats {
pub fn is_healthy(&self) -> bool {
self.completeness >= 0.95 && self.contention_rate <= 0.05
}
pub fn quality_grade(&self) -> &'static str {
match self.completeness {
x if x >= 0.98 => "Excellent",
x if x >= 0.95 => "Good",
x if x >= 0.90 => "Fair",
x if x >= 0.80 => "Poor",
_ => "Critical",
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::thread;
#[test]
fn test_tracking_stats_basic() {
let stats = TrackingStats::new();
assert_eq!(stats.get_completeness(), 1.0);
stats.record_attempt();
stats.record_success();
assert_eq!(stats.get_completeness(), 1.0);
stats.record_attempt();
stats.record_miss();
assert_eq!(stats.get_completeness(), 0.5);
}
#[test]
fn test_detailed_stats() {
let stats = TrackingStats::new();
for _ in 0..100 {
stats.record_attempt();
stats.record_success();
}
for _ in 0..5 {
stats.record_attempt();
stats.record_miss();
}
let detailed = stats.get_detailed_stats();
assert_eq!(detailed.total_attempts, 105);
assert_eq!(detailed.successful_tracks, 100);
assert_eq!(detailed.missed_due_to_contention, 5);
assert!((detailed.completeness - 0.9523).abs() < 0.001);
assert!(detailed.is_healthy());
}
#[test]
fn test_quality_grades() {
let stats = TrackingStats::new();
let test_cases = vec![
(100, 100, "Excellent"),
(100, 96, "Good"),
(100, 92, "Fair"),
(100, 85, "Poor"),
(100, 70, "Critical"),
];
for (attempts, successes, expected_grade) in test_cases {
stats.reset();
for _ in 0..attempts {
stats.record_attempt();
}
for _ in 0..successes {
stats.record_success();
}
let detailed = stats.get_detailed_stats();
assert_eq!(detailed.quality_grade(), expected_grade);
}
}
#[test]
fn test_concurrent_access() {
let stats = std::sync::Arc::new(TrackingStats::new());
let mut handles = vec![];
for _ in 0..4 {
let stats_clone = stats.clone();
let handle = thread::spawn(move || {
for _ in 0..1000 {
stats_clone.record_attempt();
#[allow(clippy::manual_is_multiple_of)]
if thread_local_random() % 10 != 0 {
stats_clone.record_success();
} else {
stats_clone.record_miss();
}
}
});
handles.push(handle);
}
for handle in handles {
handle.join().unwrap();
}
let detailed = stats.get_detailed_stats();
assert_eq!(detailed.total_attempts, 4000);
assert!(detailed.successful_tracks >= 3000); assert!(detailed.completeness >= 0.8);
}
fn thread_local_random() -> usize {
use std::cell::Cell;
thread_local! {
static RNG: Cell<usize> = const { Cell::new(1) };
}
RNG.with(|rng| {
let x = rng.get();
let next = x.wrapping_mul(1103515245).wrapping_add(12345);
rng.set(next);
next
})
}
}