use serde::{Deserialize, Serialize};
use std::sync::Arc;
use std::sync::atomic::{AtomicU64, Ordering};
use std::time::{Duration, Instant};
#[derive(Debug, Clone)]
pub struct CacheStats {
hits: Arc<AtomicU64>,
misses: Arc<AtomicU64>,
evictions: Arc<AtomicU64>,
bytes_saved: Arc<AtomicU64>,
bytes_written: Arc<AtomicU64>,
bytes_evicted: Arc<AtomicU64>,
read_operations: Arc<AtomicU64>,
write_operations: Arc<AtomicU64>,
delete_operations: Arc<AtomicU64>,
start_time: Instant,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CacheStatsSnapshot {
pub hits: u64,
pub misses: u64,
pub evictions: u64,
pub bytes_saved: u64,
pub bytes_written: u64,
pub bytes_evicted: u64,
pub read_operations: u64,
pub write_operations: u64,
pub delete_operations: u64,
pub hit_rate: f64,
pub miss_rate: f64,
pub total_operations: u64,
pub uptime_seconds: u64,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CacheReport {
pub stats: CacheStatsSnapshot,
pub avg_bytes_per_hit: f64,
pub avg_bytes_per_write: f64,
pub bandwidth_savings_ratio: f64,
pub operations_per_second: f64,
pub bytes_per_second_saved: f64,
pub effectiveness_score: f64,
}
impl Default for CacheStats {
fn default() -> Self {
Self::new()
}
}
impl CacheStats {
pub fn new() -> Self {
Self {
hits: Arc::new(AtomicU64::new(0)),
misses: Arc::new(AtomicU64::new(0)),
evictions: Arc::new(AtomicU64::new(0)),
bytes_saved: Arc::new(AtomicU64::new(0)),
bytes_written: Arc::new(AtomicU64::new(0)),
bytes_evicted: Arc::new(AtomicU64::new(0)),
read_operations: Arc::new(AtomicU64::new(0)),
write_operations: Arc::new(AtomicU64::new(0)),
delete_operations: Arc::new(AtomicU64::new(0)),
start_time: Instant::now(),
}
}
pub fn record_hit(&self, bytes: u64) {
self.hits.fetch_add(1, Ordering::Relaxed);
self.bytes_saved.fetch_add(bytes, Ordering::Relaxed);
self.read_operations.fetch_add(1, Ordering::Relaxed);
}
pub fn record_miss(&self) {
self.misses.fetch_add(1, Ordering::Relaxed);
self.read_operations.fetch_add(1, Ordering::Relaxed);
}
pub fn record_eviction(&self, bytes: u64) {
self.evictions.fetch_add(1, Ordering::Relaxed);
self.bytes_evicted.fetch_add(bytes, Ordering::Relaxed);
}
pub fn record_write(&self, bytes: u64) {
self.bytes_written.fetch_add(bytes, Ordering::Relaxed);
self.write_operations.fetch_add(1, Ordering::Relaxed);
}
pub fn record_delete(&self) {
self.delete_operations.fetch_add(1, Ordering::Relaxed);
}
pub fn hits(&self) -> u64 {
self.hits.load(Ordering::Relaxed)
}
pub fn misses(&self) -> u64 {
self.misses.load(Ordering::Relaxed)
}
pub fn evictions(&self) -> u64 {
self.evictions.load(Ordering::Relaxed)
}
pub fn bytes_saved(&self) -> u64 {
self.bytes_saved.load(Ordering::Relaxed)
}
pub fn bytes_written(&self) -> u64 {
self.bytes_written.load(Ordering::Relaxed)
}
pub fn bytes_evicted(&self) -> u64 {
self.bytes_evicted.load(Ordering::Relaxed)
}
pub fn hit_rate(&self) -> f64 {
let hits = self.hits();
let total = hits + self.misses();
if total == 0 {
0.0
} else {
(hits as f64 / total as f64) * 100.0
}
}
pub fn miss_rate(&self) -> f64 {
let misses = self.misses();
let total = self.hits() + misses;
if total == 0 {
0.0
} else {
(misses as f64 / total as f64) * 100.0
}
}
pub fn total_operations(&self) -> u64 {
self.hits() + self.misses()
}
pub fn uptime(&self) -> Duration {
self.start_time.elapsed()
}
pub fn reset(&self) {
self.hits.store(0, Ordering::Relaxed);
self.misses.store(0, Ordering::Relaxed);
self.evictions.store(0, Ordering::Relaxed);
self.bytes_saved.store(0, Ordering::Relaxed);
self.bytes_written.store(0, Ordering::Relaxed);
self.bytes_evicted.store(0, Ordering::Relaxed);
self.read_operations.store(0, Ordering::Relaxed);
self.write_operations.store(0, Ordering::Relaxed);
self.delete_operations.store(0, Ordering::Relaxed);
}
pub fn snapshot(&self) -> CacheStatsSnapshot {
let hits = self.hits();
let misses = self.misses();
let total_ops = hits + misses;
let uptime_secs = self.uptime().as_secs();
CacheStatsSnapshot {
hits,
misses,
evictions: self.evictions(),
bytes_saved: self.bytes_saved(),
bytes_written: self.bytes_written(),
bytes_evicted: self.bytes_evicted(),
read_operations: self.read_operations.load(Ordering::Relaxed),
write_operations: self.write_operations.load(Ordering::Relaxed),
delete_operations: self.delete_operations.load(Ordering::Relaxed),
hit_rate: self.hit_rate(),
miss_rate: self.miss_rate(),
total_operations: total_ops,
uptime_seconds: uptime_secs,
}
}
pub fn report(&self) -> CacheReport {
let stats = self.snapshot();
let uptime_secs = stats.uptime_seconds as f64;
let avg_bytes_per_hit = if stats.hits > 0 {
stats.bytes_saved as f64 / stats.hits as f64
} else {
0.0
};
let avg_bytes_per_write = if stats.write_operations > 0 {
stats.bytes_written as f64 / stats.write_operations as f64
} else {
0.0
};
let total_bytes_served = stats.bytes_saved + (stats.misses * avg_bytes_per_hit as u64);
let bandwidth_savings_ratio = if total_bytes_served > 0 {
stats.bytes_saved as f64 / total_bytes_served as f64
} else {
0.0
};
let operations_per_second = if uptime_secs > 0.0 {
stats.total_operations as f64 / uptime_secs
} else {
0.0
};
let bytes_per_second_saved = if uptime_secs > 0.0 {
stats.bytes_saved as f64 / uptime_secs
} else {
0.0
};
let hit_rate_score = stats.hit_rate;
let bandwidth_score = bandwidth_savings_ratio * 100.0;
let effectiveness_score = (hit_rate_score * 0.7) + (bandwidth_score * 0.3);
CacheReport {
stats,
avg_bytes_per_hit,
avg_bytes_per_write,
bandwidth_savings_ratio,
operations_per_second,
bytes_per_second_saved,
effectiveness_score,
}
}
}
impl CacheStatsSnapshot {
pub fn format_bytes(bytes: u64) -> String {
const UNITS: &[&str] = &["B", "KB", "MB", "GB", "TB"];
let mut size = bytes as f64;
let mut unit_index = 0;
while size >= 1024.0 && unit_index < UNITS.len() - 1 {
size /= 1024.0;
unit_index += 1;
}
if unit_index == 0 {
format!("{} {}", bytes, UNITS[unit_index])
} else {
format!("{:.2} {}", size, UNITS[unit_index])
}
}
pub fn format_uptime(&self) -> String {
let secs = self.uptime_seconds;
let days = secs / 86400;
let hours = (secs % 86400) / 3600;
let minutes = (secs % 3600) / 60;
let seconds = secs % 60;
if days > 0 {
format!("{days}d {hours}h {minutes}m {seconds}s")
} else if hours > 0 {
format!("{hours}h {minutes}m {seconds}s")
} else if minutes > 0 {
format!("{minutes}m {seconds}s")
} else {
format!("{seconds}s")
}
}
}
impl std::fmt::Display for CacheStatsSnapshot {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
writeln!(f, "Cache Statistics:")?;
writeln!(
f,
" Operations: {} hits, {} misses ({:.1}% hit rate)",
self.hits, self.misses, self.hit_rate
)?;
writeln!(
f,
" Bandwidth: {} saved, {} written",
Self::format_bytes(self.bytes_saved),
Self::format_bytes(self.bytes_written)
)?;
writeln!(
f,
" Evictions: {} entries ({} bytes)",
self.evictions,
Self::format_bytes(self.bytes_evicted)
)?;
writeln!(f, " Uptime: {}", self.format_uptime())?;
Ok(())
}
}
impl std::fmt::Display for CacheReport {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
writeln!(f, "Cache Performance Report:")?;
writeln!(f, "{}", self.stats)?;
writeln!(f, "Performance Metrics:")?;
writeln!(f, " Average bytes per hit: {:.1}", self.avg_bytes_per_hit)?;
writeln!(
f,
" Average bytes per write: {:.1}",
self.avg_bytes_per_write
)?;
writeln!(
f,
" Bandwidth savings: {:.1}%",
self.bandwidth_savings_ratio * 100.0
)?;
writeln!(
f,
" Operations per second: {:.1}",
self.operations_per_second
)?;
writeln!(
f,
" Bytes per second saved: {}",
CacheStatsSnapshot::format_bytes(self.bytes_per_second_saved as u64)
)?;
writeln!(
f,
" Effectiveness score: {:.1}/100",
self.effectiveness_score
)?;
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::thread;
use std::time::Duration as StdDuration;
#[test]
fn test_cache_stats_creation() {
let stats = CacheStats::new();
assert_eq!(stats.hits(), 0);
assert_eq!(stats.misses(), 0);
assert_eq!(stats.evictions(), 0);
assert_eq!(stats.bytes_saved(), 0);
}
#[test]
fn test_record_operations() {
let stats = CacheStats::new();
stats.record_hit(1024);
stats.record_hit(2048);
assert_eq!(stats.hits(), 2);
assert_eq!(stats.bytes_saved(), 3072);
stats.record_miss();
stats.record_miss();
assert_eq!(stats.misses(), 2);
stats.record_eviction(512);
assert_eq!(stats.evictions(), 1);
assert_eq!(stats.bytes_evicted(), 512);
stats.record_write(4096);
assert_eq!(stats.bytes_written(), 4096);
}
#[test]
fn test_hit_miss_rates() {
let stats = CacheStats::new();
assert_eq!(stats.hit_rate(), 0.0);
assert_eq!(stats.miss_rate(), 0.0);
stats.record_hit(100);
stats.record_hit(200);
stats.record_hit(300);
stats.record_miss();
assert!((stats.hit_rate() - 75.0).abs() < 0.001);
assert!((stats.miss_rate() - 25.0).abs() < 0.001);
assert_eq!(stats.total_operations(), 4);
}
#[test]
fn test_stats_reset() {
let stats = CacheStats::new();
stats.record_hit(1000);
stats.record_miss();
stats.record_eviction(500);
stats.record_write(2000);
assert_eq!(stats.hits(), 1);
assert_eq!(stats.misses(), 1);
assert_eq!(stats.evictions(), 1);
assert_eq!(stats.bytes_saved(), 1000);
assert_eq!(stats.bytes_written(), 2000);
stats.reset();
assert_eq!(stats.hits(), 0);
assert_eq!(stats.misses(), 0);
assert_eq!(stats.evictions(), 0);
assert_eq!(stats.bytes_saved(), 0);
assert_eq!(stats.bytes_written(), 0);
}
#[test]
fn test_snapshot() {
let stats = CacheStats::new();
stats.record_hit(500);
stats.record_hit(1500);
stats.record_miss();
stats.record_write(1000);
stats.record_eviction(200);
let snapshot = stats.snapshot();
assert_eq!(snapshot.hits, 2);
assert_eq!(snapshot.misses, 1);
assert_eq!(snapshot.evictions, 1);
assert_eq!(snapshot.bytes_saved, 2000);
assert_eq!(snapshot.bytes_written, 1000);
assert_eq!(snapshot.bytes_evicted, 200);
assert!((snapshot.hit_rate - 66.666).abs() < 0.01);
assert_eq!(snapshot.total_operations, 3);
}
#[test]
fn test_report_generation() {
let stats = CacheStats::new();
stats.record_hit(1024); stats.record_hit(2048); stats.record_hit(4096); stats.record_miss(); stats.record_write(8192);
let report = stats.report();
assert_eq!(report.stats.hits, 3);
assert_eq!(report.stats.misses, 1);
assert_eq!(report.stats.bytes_saved, 7168);
assert!((report.avg_bytes_per_hit - 2389.33).abs() < 0.01); assert_eq!(report.avg_bytes_per_write, 8192.0);
assert!(report.effectiveness_score > 0.0);
assert!(report.effectiveness_score <= 100.0);
}
#[test]
fn test_format_bytes() {
assert_eq!(CacheStatsSnapshot::format_bytes(0), "0 B");
assert_eq!(CacheStatsSnapshot::format_bytes(512), "512 B");
assert_eq!(CacheStatsSnapshot::format_bytes(1024), "1.00 KB");
assert_eq!(CacheStatsSnapshot::format_bytes(1536), "1.50 KB");
assert_eq!(CacheStatsSnapshot::format_bytes(1048576), "1.00 MB");
assert_eq!(CacheStatsSnapshot::format_bytes(1073741824), "1.00 GB");
}
#[test]
fn test_concurrent_access() {
let stats = Arc::new(CacheStats::new());
let mut handles = vec![];
for i in 0..10 {
let stats_clone = Arc::clone(&stats);
let handle = thread::spawn(move || {
for j in 0..100 {
stats_clone.record_hit((i * 100 + j) as u64);
stats_clone.record_miss();
stats_clone.record_write(1000);
}
});
handles.push(handle);
}
for handle in handles {
handle.join().unwrap();
}
assert_eq!(stats.hits(), 1000);
assert_eq!(stats.misses(), 1000);
assert_eq!(stats.bytes_written(), 1000000); assert!((stats.hit_rate() - 50.0).abs() < 0.001);
}
#[test]
fn test_uptime_tracking() {
let stats = CacheStats::new();
thread::sleep(StdDuration::from_millis(10));
let uptime = stats.uptime();
assert!(uptime.as_millis() >= 10);
let snapshot = stats.snapshot();
assert!(snapshot.uptime_seconds < 3600); }
#[test]
fn test_display_formatting() {
let stats = CacheStats::new();
stats.record_hit(1024);
stats.record_miss();
let snapshot = stats.snapshot();
let display_output = format!("{snapshot}");
assert!(display_output.contains("Cache Statistics:"));
assert!(display_output.contains("1 hits, 1 misses"));
assert!(display_output.contains("1.00 KB saved"));
let report = stats.report();
let report_output = format!("{report}");
assert!(report_output.contains("Cache Performance Report:"));
assert!(report_output.contains("Performance Metrics:"));
}
}