#[derive(Debug, Clone, Default)]
pub struct KvCacheStateTrace {
pub step: usize,
pub cache_size_bytes: usize,
pub valid_positions: usize,
pub max_positions: usize,
pub evictions_this_step: usize,
pub cache_hit_rate: f32,
pub oldest_position: usize,
pub fragmentation: f32,
pub accessed_positions: Vec<usize>,
}
impl KvCacheStateTrace {
pub fn new(step: usize, max_positions: usize) -> Self {
Self { step, max_positions, ..Default::default() }
}
pub fn is_window_exhausted(&self) -> bool {
self.valid_positions >= self.max_positions
}
pub fn utilization(&self) -> f32 {
if self.max_positions == 0 {
return 0.0;
}
self.valid_positions as f32 / self.max_positions as f32
}
}
#[derive(Debug, Clone, Default)]
pub struct KvCacheSessionTrace {
pub steps: Vec<KvCacheStateTrace>,
pub total_evictions: usize,
pub avg_hit_rate: f32,
pub peak_memory_bytes: usize,
}
impl KvCacheSessionTrace {
pub fn add_step(&mut self, trace: KvCacheStateTrace) {
self.total_evictions += trace.evictions_this_step;
self.peak_memory_bytes = self.peak_memory_bytes.max(trace.cache_size_bytes);
let n = self.steps.len() as f32 + 1.0;
self.avg_hit_rate = (self.avg_hit_rate * (n - 1.0) + trace.cache_hit_rate) / n;
self.steps.push(trace);
}
pub fn has_high_eviction_rate(&self) -> bool {
if self.steps.is_empty() {
return false;
}
let eviction_steps = self.steps.iter().filter(|s| s.evictions_this_step > 0).count();
eviction_steps as f32 / self.steps.len() as f32 > 0.1
}
pub fn has_thrashing(&self, window: usize, min_hit_rate: f32) -> bool {
if self.steps.is_empty() {
return false;
}
let actual_window = std::cmp::min(window, self.steps.len());
let recent_steps = &self.steps[self.steps.len() - actual_window..];
let recent_evictions: usize = recent_steps.iter().map(|s| s.evictions_this_step).sum();
let recent_hit_rate: f32 =
recent_steps.iter().map(|s| s.cache_hit_rate).sum::<f32>() / actual_window as f32;
recent_evictions > actual_window / 2 && recent_hit_rate < min_hit_rate
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_kv_cache_state_trace() {
let trace = KvCacheStateTrace::new(0, 2048);
assert_eq!(trace.step, 0);
assert_eq!(trace.max_positions, 2048);
assert!(!trace.is_window_exhausted());
}
#[test]
fn test_kv_cache_state_utilization() {
let mut trace = KvCacheStateTrace::new(0, 1000);
trace.valid_positions = 500;
assert!((trace.utilization() - 0.5).abs() < 0.01);
}
#[test]
fn test_kv_cache_session_trace() {
let mut session = KvCacheSessionTrace::default();
session.add_step(KvCacheStateTrace {
step: 0,
cache_hit_rate: 0.9,
evictions_this_step: 0,
cache_size_bytes: 1000,
..Default::default()
});
session.add_step(KvCacheStateTrace {
step: 1,
cache_hit_rate: 0.8,
evictions_this_step: 5,
cache_size_bytes: 2000,
..Default::default()
});
assert_eq!(session.steps.len(), 2);
assert_eq!(session.total_evictions, 5);
assert_eq!(session.peak_memory_bytes, 2000);
assert!((session.avg_hit_rate - 0.85).abs() < 0.01);
}
#[test]
fn test_high_eviction_rate_empty_session() {
let session = KvCacheSessionTrace::default();
assert!(!session.has_high_eviction_rate());
}
#[test]
fn test_high_eviction_rate_no_evictions() {
let mut session = KvCacheSessionTrace::default();
for i in 0..10 {
session.add_step(KvCacheStateTrace {
step: i,
evictions_this_step: 0,
..Default::default()
});
}
assert!(!session.has_high_eviction_rate());
}
#[test]
fn test_high_eviction_rate_at_boundary() {
let mut session = KvCacheSessionTrace::default();
for i in 0..10 {
session.add_step(KvCacheStateTrace {
step: i,
evictions_this_step: if i == 0 { 1 } else { 0 },
..Default::default()
});
}
assert!(!session.has_high_eviction_rate());
}
#[test]
fn test_high_eviction_rate_just_above_boundary() {
let mut session = KvCacheSessionTrace::default();
for i in 0..10 {
session.add_step(KvCacheStateTrace {
step: i,
evictions_this_step: if i < 2 { 1 } else { 0 },
..Default::default()
});
}
assert!(session.has_high_eviction_rate());
}
#[test]
fn test_high_eviction_rate_all_evictions() {
let mut session = KvCacheSessionTrace::default();
for i in 0..5 {
session.add_step(KvCacheStateTrace {
step: i,
evictions_this_step: 3,
..Default::default()
});
}
assert!(session.has_high_eviction_rate());
}
#[test]
fn test_high_eviction_rate_single_step_with_eviction() {
let mut session = KvCacheSessionTrace::default();
session.add_step(KvCacheStateTrace {
step: 0,
evictions_this_step: 1,
..Default::default()
});
assert!(session.has_high_eviction_rate());
}
#[test]
fn test_high_eviction_rate_single_step_without_eviction() {
let mut session = KvCacheSessionTrace::default();
session.add_step(KvCacheStateTrace {
step: 0,
evictions_this_step: 0,
..Default::default()
});
assert!(!session.has_high_eviction_rate());
}
}