use std::sync::atomic::{AtomicU64, Ordering};
#[derive(Debug)]
pub struct CacheStats {
hits: AtomicU64,
misses: AtomicU64,
}
impl CacheStats {
pub fn new() -> Self {
Self {
hits: AtomicU64::new(0),
misses: AtomicU64::new(0),
}
}
#[inline]
pub fn record_hit(&self) {
self.hits.fetch_add(1, Ordering::Relaxed);
}
#[inline]
pub fn record_miss(&self) {
self.misses.fetch_add(1, Ordering::Relaxed);
}
#[inline]
pub fn hits(&self) -> u64 {
self.hits.load(Ordering::Relaxed)
}
#[inline]
pub fn misses(&self) -> u64 {
self.misses.load(Ordering::Relaxed)
}
#[inline]
pub fn total_accesses(&self) -> u64 {
self.hits() + self.misses()
}
#[inline]
pub fn hit_rate(&self) -> f64 {
let total = self.total_accesses();
if total == 0 {
0.0
} else {
self.hits() as f64 / total as f64
}
}
#[inline]
pub fn miss_rate(&self) -> f64 {
let total = self.total_accesses();
if total == 0 {
0.0
} else {
self.misses() as f64 / total as f64
}
}
pub fn reset(&self) {
self.hits.store(0, Ordering::Relaxed);
self.misses.store(0, Ordering::Relaxed);
}
}
impl Default for CacheStats {
fn default() -> Self {
Self::new()
}
}
impl Clone for CacheStats {
fn clone(&self) -> Self {
Self {
hits: AtomicU64::new(self.hits()),
misses: AtomicU64::new(self.misses()),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_new_stats() {
let stats = CacheStats::new();
assert_eq!(stats.hits(), 0);
assert_eq!(stats.misses(), 0);
assert_eq!(stats.total_accesses(), 0);
}
#[test]
fn test_record_hit() {
let stats = CacheStats::new();
stats.record_hit();
stats.record_hit();
assert_eq!(stats.hits(), 2);
assert_eq!(stats.misses(), 0);
}
#[test]
fn test_record_miss() {
let stats = CacheStats::new();
stats.record_miss();
stats.record_miss();
stats.record_miss();
assert_eq!(stats.hits(), 0);
assert_eq!(stats.misses(), 3);
}
#[test]
fn test_total_accesses() {
let stats = CacheStats::new();
stats.record_hit();
stats.record_hit();
stats.record_miss();
assert_eq!(stats.total_accesses(), 3);
}
#[test]
fn test_hit_rate() {
let stats = CacheStats::new();
stats.record_hit();
stats.record_hit();
stats.record_miss();
assert!((stats.hit_rate() - 0.6666).abs() < 0.001);
}
#[test]
fn test_miss_rate() {
let stats = CacheStats::new();
stats.record_hit();
stats.record_miss();
stats.record_miss();
assert!((stats.miss_rate() - 0.6666).abs() < 0.001);
}
#[test]
fn test_hit_rate_no_accesses() {
let stats = CacheStats::new();
assert_eq!(stats.hit_rate(), 0.0);
assert_eq!(stats.miss_rate(), 0.0);
}
#[test]
fn test_reset() {
let stats = CacheStats::new();
stats.record_hit();
stats.record_hit();
stats.record_miss();
assert_eq!(stats.total_accesses(), 3);
stats.reset();
assert_eq!(stats.hits(), 0);
assert_eq!(stats.misses(), 0);
assert_eq!(stats.total_accesses(), 0);
}
#[test]
fn test_default() {
let stats = CacheStats::default();
assert_eq!(stats.hits(), 0);
assert_eq!(stats.misses(), 0);
}
#[test]
fn test_clone() {
let stats = CacheStats::new();
stats.record_hit();
stats.record_miss();
let cloned = stats.clone();
assert_eq!(cloned.hits(), stats.hits());
assert_eq!(cloned.misses(), stats.misses());
stats.record_hit();
assert_eq!(stats.hits(), 2);
assert_eq!(cloned.hits(), 1);
}
#[test]
fn test_concurrent_access() {
use std::sync::Arc;
use std::thread;
let stats = Arc::new(CacheStats::new());
let mut handles = vec![];
for _ in 0..10 {
let stats_clone = Arc::clone(&stats);
let handle = thread::spawn(move || {
for _ in 0..100 {
stats_clone.record_hit();
}
for _ in 0..50 {
stats_clone.record_miss();
}
});
handles.push(handle);
}
for handle in handles {
handle.join().unwrap();
}
assert_eq!(stats.hits(), 1000);
assert_eq!(stats.misses(), 500);
assert_eq!(stats.total_accesses(), 1500);
assert!((stats.hit_rate() - 0.6666).abs() < 0.001);
}
}