term_guard/optimizer/
stats_cache.rs1use std::collections::HashMap;
4use std::time::{Duration, Instant};
5
6#[derive(Debug, Clone)]
8struct CacheEntry {
9 value: f64,
10 timestamp: Instant,
11}
12
13#[derive(Debug)]
15pub struct StatsCache {
16 cache: HashMap<String, CacheEntry>,
18 ttl: Duration,
20 max_entries: usize,
22}
23
24impl StatsCache {
25 pub fn new() -> Self {
27 Self {
28 cache: HashMap::new(),
29 ttl: Duration::from_secs(300), max_entries: 1000,
31 }
32 }
33
34 pub fn with_config(ttl: Duration, max_entries: usize) -> Self {
36 Self {
37 cache: HashMap::new(),
38 ttl,
39 max_entries,
40 }
41 }
42
43 pub fn get(&self, key: &str) -> Option<f64> {
45 self.cache.get(key).and_then(|entry| {
46 if entry.timestamp.elapsed() < self.ttl {
47 Some(entry.value)
48 } else {
49 None
50 }
51 })
52 }
53
54 pub fn set(&mut self, key: String, value: f64) {
56 if self.cache.len() >= self.max_entries {
58 self.evict_oldest();
59 }
60
61 self.cache.insert(
62 key,
63 CacheEntry {
64 value,
65 timestamp: Instant::now(),
66 },
67 );
68 }
69
70 pub fn clear(&mut self) {
72 self.cache.clear();
73 }
74
75 pub fn remove_expired(&mut self) {
77 let now = Instant::now();
78 self.cache
79 .retain(|_, entry| now.duration_since(entry.timestamp) < self.ttl);
80 }
81
82 pub fn size(&self) -> usize {
84 self.cache.len()
85 }
86
87 fn evict_oldest(&mut self) {
89 if let Some((oldest_key, _)) = self
90 .cache
91 .iter()
92 .min_by_key(|(_, entry)| entry.timestamp)
93 .map(|(k, v)| (k.clone(), v.clone()))
94 {
95 self.cache.remove(&oldest_key);
96 }
97 }
98
99 pub fn stats(&self) -> CacheStats {
101 let total_entries = self.cache.len();
102 let expired_entries = self
103 .cache
104 .values()
105 .filter(|entry| entry.timestamp.elapsed() >= self.ttl)
106 .count();
107
108 CacheStats {
109 total_entries,
110 expired_entries,
111 active_entries: total_entries - expired_entries,
112 }
113 }
114}
115
116#[derive(Debug)]
118pub struct CacheStats {
119 pub total_entries: usize,
121 pub expired_entries: usize,
123 pub active_entries: usize,
125}
126
127impl Default for StatsCache {
128 fn default() -> Self {
129 Self::new()
130 }
131}
132
133#[cfg(test)]
134mod tests {
135 use super::*;
136
137 #[test]
138 fn test_cache_basic_operations() {
139 let mut cache = StatsCache::new();
140
141 cache.set("test_key".to_string(), 42.0);
143 assert_eq!(cache.get("test_key"), Some(42.0));
144
145 assert_eq!(cache.get("missing_key"), None);
147 }
148
149 #[test]
150 fn test_cache_expiration() {
151 let mut cache = StatsCache::with_config(Duration::from_millis(100), 10);
152
153 cache.set("test_key".to_string(), 42.0);
154 assert_eq!(cache.get("test_key"), Some(42.0));
155
156 std::thread::sleep(Duration::from_millis(150));
158 assert_eq!(cache.get("test_key"), None);
159 }
160
161 #[test]
162 fn test_cache_eviction() {
163 let mut cache = StatsCache::with_config(Duration::from_secs(60), 2);
164
165 cache.set("key1".to_string(), 1.0);
166 cache.set("key2".to_string(), 2.0);
167
168 cache.set("key3".to_string(), 3.0);
170
171 assert_eq!(cache.size(), 2);
172 }
173
174 #[test]
175 fn test_cache_stats() {
176 let mut cache = StatsCache::new();
177
178 cache.set("key1".to_string(), 1.0);
179 cache.set("key2".to_string(), 2.0);
180
181 let stats = cache.stats();
182 assert_eq!(stats.total_entries, 2);
183 assert_eq!(stats.active_entries, 2);
184 assert_eq!(stats.expired_entries, 0);
185 }
186}