1use std::collections::{HashMap, VecDeque};
11
12#[derive(Debug, Clone, Default)]
14pub struct CacheStats {
15 pub hits: u64,
16 pub misses: u64,
17 pub predictions: u64,
18 pub prediction_hits: u64,
19 pub evictions: u64,
20}
21
22impl CacheStats {
23 #[must_use]
25 pub fn hit_rate(&self) -> f64 {
26 let total = self.hits + self.misses;
27 if total == 0 {
28 0.0
29 } else {
30 self.hits as f64 / total as f64
31 }
32 }
33
34 #[must_use]
36 pub fn prediction_accuracy(&self) -> f64 {
37 if self.predictions == 0 {
38 0.0
39 } else {
40 self.prediction_hits as f64 / self.predictions as f64
41 }
42 }
43}
44
45struct Entry<V> {
47 value: V,
48 order: u64,
50}
51
52pub struct PredictiveCache<V> {
56 max_size: usize,
57 prediction_depth: usize,
58 max_history: usize,
59
60 cache: HashMap<String, Entry<V>>,
61 lru_counter: u64,
62
63 access_history: VecDeque<String>,
64 access_patterns: HashMap<String, HashMap<String, u64>>,
66
67 prewarmed: HashMap<String, bool>,
69
70 pub stats: CacheStats,
71}
72
73impl<V: Clone> PredictiveCache<V> {
74 #[must_use]
80 pub fn new(max_size: usize, prediction_depth: usize) -> Self {
81 Self {
82 max_size,
83 prediction_depth,
84 max_history: 100,
85 cache: HashMap::new(),
86 lru_counter: 0,
87 access_history: VecDeque::with_capacity(100),
88 access_patterns: HashMap::new(),
89 prewarmed: HashMap::new(),
90 stats: CacheStats::default(),
91 }
92 }
93
94 pub fn get(&mut self, key: &str) -> Option<V> {
99 if self.prewarmed.remove(key).is_some() {
101 self.stats.prediction_hits += 1;
102 }
103
104 let found = self.cache.get(key).map(|e| e.value.clone());
105
106 if let Some(value) = found {
107 self.stats.hits += 1;
108 self.lru_counter += 1;
109 if let Some(entry) = self.cache.get_mut(key) {
110 entry.order = self.lru_counter;
111 }
112
113 self.record_access(key);
114 self.predict_next(key);
115
116 Some(value)
117 } else {
118 self.stats.misses += 1;
119 self.record_access(key);
120 None
121 }
122 }
123
124 pub fn set(&mut self, key: &str, value: V) {
128 self.lru_counter += 1;
129
130 if let Some(entry) = self.cache.get_mut(key) {
131 entry.value = value;
132 entry.order = self.lru_counter;
133 return;
134 }
135
136 self.cache.insert(
137 key.to_string(),
138 Entry {
139 value,
140 order: self.lru_counter,
141 },
142 );
143
144 if self.cache.len() > self.max_size {
146 self.evict_lru();
147 }
148 }
149
150 pub fn prewarm<F>(&mut self, loader: F, keys: &[String])
152 where
153 F: Fn(&str) -> Option<V>,
154 {
155 for key in keys {
156 if !self.cache.contains_key(key) {
157 if let Some(value) = loader(key) {
158 self.set(key, value);
159 }
160 }
161 }
162 }
163
164 pub fn invalidate(&mut self, key: &str) {
166 self.cache.remove(key);
167 self.prewarmed.remove(key);
168 }
169
170 pub fn clear(&mut self) {
172 self.cache.clear();
173 self.prewarmed.clear();
174 self.access_history.clear();
175 self.access_patterns.clear();
176 self.stats = CacheStats::default();
177 }
178
179 #[must_use]
181 pub fn len(&self) -> usize {
182 self.cache.len()
183 }
184
185 #[must_use]
187 pub fn is_empty(&self) -> bool {
188 self.cache.is_empty()
189 }
190
191 #[must_use]
193 pub fn likely_next(&self, current_key: &str, top_n: usize) -> Vec<(String, f64)> {
194 match self.access_patterns.get(current_key) {
195 None => Vec::new(),
196 Some(transitions) => {
197 let total: u64 = transitions.values().sum();
198 if total == 0 {
199 return Vec::new();
200 }
201 let mut result: Vec<(String, f64)> = transitions
202 .iter()
203 .map(|(k, &count)| (k.clone(), count as f64 / total as f64))
204 .collect();
205 result.sort_by(|a, b| b.1.partial_cmp(&a.1).unwrap_or(std::cmp::Ordering::Equal));
206 result.truncate(top_n);
207 result
208 }
209 }
210 }
211
212 #[must_use]
214 pub fn hot_keys(&self, top_n: usize) -> Vec<(String, u64)> {
215 let mut counts: HashMap<String, u64> = HashMap::new();
216 for key in &self.access_history {
217 *counts.entry(key.clone()).or_insert(0) += 1;
218 }
219 let mut result: Vec<(String, u64)> = counts.into_iter().collect();
220 result.sort_by_key(|x| std::cmp::Reverse(x.1));
221 result.truncate(top_n);
222 result
223 }
224
225 fn evict_lru(&mut self) {
226 if self.cache.is_empty() {
227 return;
228 }
229 let min_key = self
231 .cache
232 .iter()
233 .min_by_key(|(_, e)| e.order)
234 .map(|(k, _)| k.clone());
235
236 if let Some(key) = min_key {
237 self.cache.remove(&key);
238 self.stats.evictions += 1;
239 }
240 }
241
242 fn record_access(&mut self, key: &str) {
243 self.access_history.push_back(key.to_string());
244 if self.access_history.len() > self.max_history {
245 self.access_history.pop_front();
246 }
247
248 if self.access_history.len() >= 2 {
250 let prev_key = self
251 .access_history
252 .get(self.access_history.len() - 2)
253 .cloned();
254 if let Some(prev) = prev_key {
255 *self
256 .access_patterns
257 .entry(prev)
258 .or_default()
259 .entry(key.to_string())
260 .or_insert(0) += 1;
261 }
262 }
263 }
264
265 fn predict_next(&mut self, current_key: &str) {
266 let transitions = match self.access_patterns.get(current_key) {
267 None => return,
268 Some(t) => t.clone(),
269 };
270
271 let total: u64 = transitions.values().sum();
272 if total == 0 {
273 return;
274 }
275
276 let mut likely: Vec<(String, f64)> = transitions
277 .iter()
278 .map(|(k, &count)| (k.clone(), count as f64 / total as f64))
279 .collect();
280 likely.sort_by(|a, b| b.1.partial_cmp(&a.1).unwrap_or(std::cmp::Ordering::Equal));
281 likely.truncate(self.prediction_depth);
282
283 for (next_key, probability) in likely {
284 if probability > 0.3 {
285 self.prewarmed.insert(next_key, true);
286 self.stats.predictions += 1;
287 }
288 }
289 }
290}
291
292impl<V: Clone> Default for PredictiveCache<V> {
293 fn default() -> Self {
294 Self::new(1000, 3)
295 }
296}
297
298#[cfg(test)]
299mod tests {
300 use super::*;
301
302 #[test]
303 fn cache_miss_then_hit() {
304 let mut cache: PredictiveCache<String> = PredictiveCache::new(10, 3);
305 assert!(cache.get("missing").is_none());
306 assert_eq!(cache.stats.misses, 1);
307
308 cache.set("key", "value".to_string());
309 assert_eq!(cache.get("key"), Some("value".to_string()));
310 assert_eq!(cache.stats.hits, 1);
311 }
312
313 #[test]
314 fn cache_eviction() {
315 let mut cache: PredictiveCache<i32> = PredictiveCache::new(3, 1);
316 cache.set("a", 1);
317 cache.set("b", 2);
318 cache.set("c", 3);
319 cache.set("d", 4); assert_eq!(cache.len(), 3);
322 assert!(cache.get("a").is_none());
323 assert!(cache.get("d").is_some());
324 assert_eq!(cache.stats.evictions, 1);
325 }
326
327 #[test]
328 fn cache_lru_order_updates_on_get() {
329 let mut cache: PredictiveCache<i32> = PredictiveCache::new(3, 1);
330 cache.set("a", 1);
331 cache.set("b", 2);
332 cache.set("c", 3);
333
334 let _ = cache.get("a");
336 cache.set("d", 4); assert!(cache.get("a").is_some());
339 assert!(cache.get("b").is_none());
340 }
341
342 #[test]
343 fn cache_markov_prediction() {
344 let mut cache: PredictiveCache<i32> = PredictiveCache::new(10, 3);
345
346 for key in &["a", "b", "c", "a", "b", "c"] {
348 cache.set(key, 1);
349 let _ = cache.get(key);
350 }
351
352 let likely = cache.likely_next("a", 5);
354 assert!(!likely.is_empty());
355 assert_eq!(likely[0].0, "b");
356 }
357
358 #[test]
359 fn cache_prediction_hit_tracking() {
360 let mut cache: PredictiveCache<i32> = PredictiveCache::new(10, 3);
361
362 cache.set("a", 1);
364 let _ = cache.get("a");
365 cache.set("b", 2);
366 let _ = cache.get("b");
367
368 let _ = cache.get("a");
370 assert!(cache.stats.predictions > 0);
371
372 let _ = cache.get("b");
374 assert!(cache.stats.prediction_hits > 0);
375 }
376
377 #[test]
378 fn cache_hot_keys() {
379 let mut cache: PredictiveCache<i32> = PredictiveCache::new(10, 1);
380 for _ in 0..5 {
381 let _ = cache.get("hot");
382 }
383 for _ in 0..2 {
384 let _ = cache.get("warm");
385 }
386
387 let hot = cache.hot_keys(10);
388 assert_eq!(hot[0].0, "hot");
389 assert!(hot[0].1 > hot[1].1);
390 }
391
392 #[test]
393 fn cache_invalidate() {
394 let mut cache: PredictiveCache<i32> = PredictiveCache::new(10, 1);
395 cache.set("a", 1);
396 cache.invalidate("a");
397 assert!(cache.get("a").is_none());
398 }
399
400 #[test]
401 fn cache_clear() {
402 let mut cache: PredictiveCache<i32> = PredictiveCache::new(10, 1);
403 cache.set("a", 1);
404 cache.set("b", 2);
405 let _ = cache.get("a");
406 cache.clear();
407 assert!(cache.is_empty());
408 assert_eq!(cache.stats.hits, 0);
409 }
410
411 #[test]
412 fn cache_prewarm() {
413 let mut cache: PredictiveCache<i32> = PredictiveCache::new(10, 1);
414 let loader = |key: &str| -> Option<i32> { i32::try_from(key.len()).ok() };
415 cache.prewarm(loader, &["alpha".to_string(), "beta".to_string()]);
416 assert_eq!(cache.get("alpha"), Some(5));
417 assert_eq!(cache.get("beta"), Some(4));
418 }
419
420 #[test]
421 fn cache_stats_hit_rate() {
422 let mut cache: PredictiveCache<i32> = PredictiveCache::new(10, 1);
423 cache.set("a", 1);
424 let _ = cache.get("a"); let _ = cache.get("a"); let _ = cache.get("b"); assert!((cache.stats.hit_rate() - 2.0 / 3.0).abs() < 0.01);
429 }
430
431 #[test]
432 fn cache_likely_next_empty_for_unknown() {
433 let cache: PredictiveCache<i32> = PredictiveCache::new(10, 3);
434 assert!(cache.likely_next("unknown", 5).is_empty());
435 }
436}