trueno/brick/tracing/
kv_cache.rs1#[derive(Debug, Clone, Default)]
7pub struct KvCacheStateTrace {
8 pub step: usize,
10 pub cache_size_bytes: usize,
12 pub valid_positions: usize,
14 pub max_positions: usize,
16 pub evictions_this_step: usize,
18 pub cache_hit_rate: f32,
20 pub oldest_position: usize,
22 pub fragmentation: f32,
24 pub accessed_positions: Vec<usize>,
26}
27
28impl KvCacheStateTrace {
29 pub fn new(step: usize, max_positions: usize) -> Self {
31 Self { step, max_positions, ..Default::default() }
32 }
33
34 pub fn is_window_exhausted(&self) -> bool {
36 self.valid_positions >= self.max_positions
37 }
38
39 pub fn utilization(&self) -> f32 {
41 if self.max_positions == 0 {
42 return 0.0;
43 }
44 self.valid_positions as f32 / self.max_positions as f32
45 }
46}
47
48#[derive(Debug, Clone, Default)]
50pub struct KvCacheSessionTrace {
51 pub steps: Vec<KvCacheStateTrace>,
53 pub total_evictions: usize,
55 pub avg_hit_rate: f32,
57 pub peak_memory_bytes: usize,
59}
60
61impl KvCacheSessionTrace {
62 pub fn add_step(&mut self, trace: KvCacheStateTrace) {
64 self.total_evictions += trace.evictions_this_step;
65 self.peak_memory_bytes = self.peak_memory_bytes.max(trace.cache_size_bytes);
66
67 let n = self.steps.len() as f32 + 1.0;
69 self.avg_hit_rate = (self.avg_hit_rate * (n - 1.0) + trace.cache_hit_rate) / n;
70
71 self.steps.push(trace);
72 }
73
74 pub fn has_high_eviction_rate(&self) -> bool {
76 if self.steps.is_empty() {
77 return false;
78 }
79 let eviction_steps = self.steps.iter().filter(|s| s.evictions_this_step > 0).count();
80 eviction_steps as f32 / self.steps.len() as f32 > 0.1
81 }
82
83 pub fn has_thrashing(&self, window: usize, min_hit_rate: f32) -> bool {
92 if self.steps.is_empty() {
93 return false;
94 }
95
96 let actual_window = std::cmp::min(window, self.steps.len());
98 let recent_steps = &self.steps[self.steps.len() - actual_window..];
99 let recent_evictions: usize = recent_steps.iter().map(|s| s.evictions_this_step).sum();
100 let recent_hit_rate: f32 =
101 recent_steps.iter().map(|s| s.cache_hit_rate).sum::<f32>() / actual_window as f32;
102
103 recent_evictions > actual_window / 2 && recent_hit_rate < min_hit_rate
105 }
106}
107
108#[cfg(test)]
109mod tests {
110 use super::*;
111
112 #[test]
113 fn test_kv_cache_state_trace() {
114 let trace = KvCacheStateTrace::new(0, 2048);
115 assert_eq!(trace.step, 0);
116 assert_eq!(trace.max_positions, 2048);
117 assert!(!trace.is_window_exhausted());
118 }
119
120 #[test]
121 fn test_kv_cache_state_utilization() {
122 let mut trace = KvCacheStateTrace::new(0, 1000);
123 trace.valid_positions = 500;
124 assert!((trace.utilization() - 0.5).abs() < 0.01);
125 }
126
127 #[test]
128 fn test_kv_cache_session_trace() {
129 let mut session = KvCacheSessionTrace::default();
130 session.add_step(KvCacheStateTrace {
131 step: 0,
132 cache_hit_rate: 0.9,
133 evictions_this_step: 0,
134 cache_size_bytes: 1000,
135 ..Default::default()
136 });
137 session.add_step(KvCacheStateTrace {
138 step: 1,
139 cache_hit_rate: 0.8,
140 evictions_this_step: 5,
141 cache_size_bytes: 2000,
142 ..Default::default()
143 });
144
145 assert_eq!(session.steps.len(), 2);
146 assert_eq!(session.total_evictions, 5);
147 assert_eq!(session.peak_memory_bytes, 2000);
148 assert!((session.avg_hit_rate - 0.85).abs() < 0.01);
149 }
150
151 #[test]
156 fn test_high_eviction_rate_empty_session() {
157 let session = KvCacheSessionTrace::default();
158 assert!(!session.has_high_eviction_rate());
160 }
161
162 #[test]
163 fn test_high_eviction_rate_no_evictions() {
164 let mut session = KvCacheSessionTrace::default();
165 for i in 0..10 {
166 session.add_step(KvCacheStateTrace {
167 step: i,
168 evictions_this_step: 0,
169 ..Default::default()
170 });
171 }
172 assert!(!session.has_high_eviction_rate());
174 }
175
176 #[test]
177 fn test_high_eviction_rate_at_boundary() {
178 let mut session = KvCacheSessionTrace::default();
181 for i in 0..10 {
182 session.add_step(KvCacheStateTrace {
183 step: i,
184 evictions_this_step: if i == 0 { 1 } else { 0 },
185 ..Default::default()
186 });
187 }
188 assert!(!session.has_high_eviction_rate());
189 }
190
191 #[test]
192 fn test_high_eviction_rate_just_above_boundary() {
193 let mut session = KvCacheSessionTrace::default();
195 for i in 0..10 {
196 session.add_step(KvCacheStateTrace {
197 step: i,
198 evictions_this_step: if i < 2 { 1 } else { 0 },
199 ..Default::default()
200 });
201 }
202 assert!(session.has_high_eviction_rate());
203 }
204
205 #[test]
206 fn test_high_eviction_rate_all_evictions() {
207 let mut session = KvCacheSessionTrace::default();
209 for i in 0..5 {
210 session.add_step(KvCacheStateTrace {
211 step: i,
212 evictions_this_step: 3,
213 ..Default::default()
214 });
215 }
216 assert!(session.has_high_eviction_rate());
217 }
218
219 #[test]
220 fn test_high_eviction_rate_single_step_with_eviction() {
221 let mut session = KvCacheSessionTrace::default();
223 session.add_step(KvCacheStateTrace {
224 step: 0,
225 evictions_this_step: 1,
226 ..Default::default()
227 });
228 assert!(session.has_high_eviction_rate());
229 }
230
231 #[test]
232 fn test_high_eviction_rate_single_step_without_eviction() {
233 let mut session = KvCacheSessionTrace::default();
235 session.add_step(KvCacheStateTrace {
236 step: 0,
237 evictions_this_step: 0,
238 ..Default::default()
239 });
240 assert!(!session.has_high_eviction_rate());
241 }
242}