use std::collections::HashMap;
use std::sync::atomic::{AtomicUsize, Ordering};
#[derive(Debug, Clone)]
pub struct CacheStats {
pub total_lookups: usize,
pub cache_hits: usize,
pub cache_misses: usize,
pub cache_size: usize,
pub hit_ratio: f64,
}
pub struct StackTraceCache<T> {
cache: HashMap<u64, T>,
lookup_count: AtomicUsize,
hit_count: AtomicUsize,
max_size: usize,
}
impl<T> StackTraceCache<T> {
pub fn new(max_size: usize) -> Self {
Self {
cache: HashMap::new(),
lookup_count: AtomicUsize::new(0),
hit_count: AtomicUsize::new(0),
max_size,
}
}
pub fn get(&mut self, key: u64) -> Option<&T> {
self.lookup_count.fetch_add(1, Ordering::Relaxed);
if let Some(value) = self.cache.get(&key) {
self.hit_count.fetch_add(1, Ordering::Relaxed);
Some(value)
} else {
None
}
}
pub fn insert(&mut self, key: u64, value: T) {
if self.cache.len() >= self.max_size {
self.evict_oldest();
}
self.cache.insert(key, value);
}
pub fn clear(&mut self) {
self.cache.clear();
self.lookup_count.store(0, Ordering::Relaxed);
self.hit_count.store(0, Ordering::Relaxed);
}
pub fn stats(&self) -> CacheStats {
let lookups = self.lookup_count.load(Ordering::Relaxed);
let hits = self.hit_count.load(Ordering::Relaxed);
let misses = lookups.saturating_sub(hits);
let hit_ratio = if lookups > 0 {
hits as f64 / lookups as f64
} else {
0.0
};
CacheStats {
total_lookups: lookups,
cache_hits: hits,
cache_misses: misses,
cache_size: self.cache.len(),
hit_ratio,
}
}
pub fn size(&self) -> usize {
self.cache.len()
}
pub fn is_empty(&self) -> bool {
self.cache.is_empty()
}
fn evict_oldest(&mut self) {
if let Some(key) = self.cache.keys().next().copied() {
self.cache.remove(&key);
}
}
}
impl<T> Default for StackTraceCache<T> {
fn default() -> Self {
Self::new(1000) }
}
impl CacheStats {
pub fn is_performing_well(&self) -> bool {
self.hit_ratio >= 0.8 && self.total_lookups > 10
}
pub fn efficiency_description(&self) -> &'static str {
match self.hit_ratio {
x if x >= 0.9 => "Excellent",
x if x >= 0.8 => "Good",
x if x >= 0.6 => "Fair",
x if x >= 0.4 => "Poor",
_ => "Very Poor",
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_basic_cache_operations() {
let mut cache = StackTraceCache::new(3);
assert!(cache.is_empty());
assert_eq!(cache.size(), 0);
cache.insert(1, "first");
cache.insert(2, "second");
assert_eq!(cache.size(), 2);
assert!(!cache.is_empty());
assert_eq!(cache.get(1), Some(&"first"));
assert_eq!(cache.get(2), Some(&"second"));
assert_eq!(cache.get(3), None);
}
#[test]
fn test_cache_eviction() {
let mut cache = StackTraceCache::new(2);
cache.insert(1, "first");
cache.insert(2, "second");
assert_eq!(cache.size(), 2);
cache.insert(3, "third");
assert_eq!(cache.size(), 2);
let get1 = cache.get(1).is_some();
let get2 = cache.get(2).is_some();
let get3 = cache.get(3).is_some();
let remaining_count = [get1, get2, get3].iter().filter(|&&x| x).count();
assert_eq!(remaining_count, 2); }
#[test]
fn test_cache_stats() {
let mut cache = StackTraceCache::new(10);
cache.insert(1, "value1");
cache.insert(2, "value2");
cache.get(1); cache.get(2); cache.get(3); cache.get(1);
let stats = cache.stats();
assert_eq!(stats.total_lookups, 4);
assert_eq!(stats.cache_hits, 3);
assert_eq!(stats.cache_misses, 1);
assert_eq!(stats.cache_size, 2);
assert!((stats.hit_ratio - 0.75).abs() < 0.01);
}
#[test]
fn test_cache_clear() {
let mut cache = StackTraceCache::new(10);
cache.insert(1, "value");
cache.get(1);
assert!(!cache.is_empty());
assert!(cache.stats().total_lookups > 0);
cache.clear();
assert!(cache.is_empty());
assert_eq!(cache.stats().total_lookups, 0);
assert_eq!(cache.stats().cache_hits, 0);
}
#[test]
fn test_efficiency_description() {
let excellent = CacheStats {
hit_ratio: 0.95,
total_lookups: 100,
cache_hits: 95,
cache_misses: 5,
cache_size: 50,
};
assert_eq!(excellent.efficiency_description(), "Excellent");
let poor = CacheStats {
hit_ratio: 0.45, total_lookups: 100,
cache_hits: 45,
cache_misses: 55,
cache_size: 20,
};
assert_eq!(poor.efficiency_description(), "Poor");
}
}