use std::sync::atomic::{AtomicU64, AtomicUsize, Ordering};
use std::time::{Duration, Instant};
#[derive(Debug, Clone, Copy)]
pub struct LatencyMetric {
pub total_ns: u64,
pub count: u64,
pub max_ns: u64,
}
impl LatencyMetric {
pub fn new() -> Self {
Self {
total_ns: 0,
count: 0,
max_ns: 0,
}
}
pub fn average_ns(&self) -> f64 {
if self.count == 0 {
0.0
} else {
self.total_ns as f64 / self.count as f64
}
}
pub fn average_duration(&self) -> Duration {
if self.count == 0 {
Duration::from_nanos(0)
} else {
Duration::from_nanos((self.total_ns / self.count) as u64)
}
}
}
impl Default for LatencyMetric {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Default)]
pub struct Metrics {
hits: AtomicU64,
misses: AtomicU64,
evictions: AtomicU64,
insertions: AtomicU64,
total_bytes: AtomicUsize,
entry_count: AtomicUsize,
get_latency: std::sync::Mutex<LatencyMetric>,
set_latency: std::sync::Mutex<LatencyMetric>,
}
impl Metrics {
pub fn new() -> Self {
Self::default()
}
pub fn record_hit(&self) {
self.hits.fetch_add(1, Ordering::Relaxed);
}
pub fn record_miss(&self) {
self.misses.fetch_add(1, Ordering::Relaxed);
}
pub fn record_eviction(&self) {
self.evictions.fetch_add(1, Ordering::Relaxed);
}
pub fn record_insertion(&self) {
self.insertions.fetch_add(1, Ordering::Relaxed);
}
pub fn record_entry_size(&self, old_size: usize, new_size: usize) {
if old_size > 0 {
let _ = self.total_bytes.fetch_sub(old_size, Ordering::Relaxed);
} else {
let _ = self.entry_count.fetch_add(1, Ordering::Relaxed);
}
if new_size > 0 {
let _ = self.total_bytes.fetch_add(new_size, Ordering::Relaxed);
}
}
pub fn record_entry_removal(&self, size: usize) {
if size > 0 {
let _ = self.total_bytes.fetch_sub(size, Ordering::Relaxed);
}
let _ = self.entry_count.fetch_sub(1, Ordering::Relaxed);
}
pub fn begin_get_timing(&self) -> Instant {
Instant::now()
}
pub fn record_get_latency(&self, start: Instant) {
let duration = start.elapsed();
let nanos = duration.as_nanos() as u64;
let mut get_latency = self.get_latency.lock().unwrap();
get_latency.total_ns += nanos;
get_latency.count += 1;
if nanos > get_latency.max_ns {
get_latency.max_ns = nanos;
}
}
pub fn begin_set_timing(&self) -> Instant {
Instant::now()
}
pub fn record_set_latency(&self, start: Instant) {
let duration = start.elapsed();
let nanos = duration.as_nanos() as u64;
let mut set_latency = self.set_latency.lock().unwrap();
set_latency.total_ns += nanos;
set_latency.count += 1;
if nanos > set_latency.max_ns {
set_latency.max_ns = nanos;
}
}
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 insertions(&self) -> u64 {
self.insertions.load(Ordering::Relaxed)
}
pub fn total_bytes(&self) -> usize {
self.total_bytes.load(Ordering::Relaxed)
}
pub fn entry_count(&self) -> usize {
self.entry_count.load(Ordering::Relaxed)
}
pub fn average_entry_size(&self) -> usize {
let count = self.entry_count();
let bytes = self.total_bytes();
if count == 0 {
0
} else {
bytes / count
}
}
pub fn get_latency(&self) -> LatencyMetric {
self.get_latency.lock().unwrap().clone()
}
pub fn set_latency(&self) -> LatencyMetric {
self.set_latency.lock().unwrap().clone()
}
pub fn average_get_latency_ns(&self) -> f64 {
self.get_latency.lock().unwrap().average_ns()
}
pub fn average_set_latency_ns(&self) -> f64 {
self.set_latency.lock().unwrap().average_ns()
}
pub fn hit_rate(&self) -> f64 {
let hits = self.hits();
let misses = self.misses();
if hits == 0 && misses == 0 {
0.0
} else {
hits as f64 / (hits + misses) as f64
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_metrics_counting() {
let metrics = Metrics::new();
assert_eq!(metrics.hits(), 0);
assert_eq!(metrics.misses(), 0);
assert_eq!(metrics.evictions(), 0);
assert_eq!(metrics.insertions(), 0);
assert_eq!(metrics.hit_rate(), 0.0);
metrics.record_hit();
metrics.record_miss();
metrics.record_eviction();
metrics.record_insertion();
assert_eq!(metrics.hits(), 1);
assert_eq!(metrics.misses(), 1);
assert_eq!(metrics.evictions(), 1);
assert_eq!(metrics.insertions(), 1);
assert_eq!(metrics.hit_rate(), 0.5);
}
#[test]
fn test_hit_rate_edge_cases() {
let metrics = Metrics::new();
assert_eq!(metrics.hit_rate(), 0.0);
metrics.record_miss();
assert_eq!(metrics.hit_rate(), 0.0);
metrics.record_hit();
let metrics = Metrics::new();
metrics.record_hit();
assert_eq!(metrics.hit_rate(), 1.0);
}
}