1use serde::{Deserialize, Serialize};
4use std::sync::{Arc, RwLock};
5use std::time::{Duration, SystemTime};
6
7#[derive(Debug, Clone, Serialize, Deserialize)]
9pub struct DetailedCacheStats {
10 pub hits: u64,
12 pub misses: u64,
14 pub invalidations: u64,
16 pub size_bytes: u64,
18 pub entry_count: usize,
20 pub avg_retrieval_time_ms: f64,
22 pub avg_store_time_ms: f64,
24 pub total_operation_time_ms: f64,
26 pub last_operation_time: Option<SystemTime>,
28 pub created_at: SystemTime,
30}
31
32impl DetailedCacheStats {
33 pub fn hit_rate(&self) -> f64 {
35 let total = self.hits + self.misses;
36 if total == 0 {
37 0.0
38 } else {
39 (self.hits as f64 / total as f64) * 100.0
40 }
41 }
42
43 pub fn miss_rate(&self) -> f64 {
45 100.0 - self.hit_rate()
46 }
47
48 pub fn invalidation_rate(&self) -> f64 {
50 let total = self.hits + self.misses;
51 if total == 0 {
52 0.0
53 } else {
54 (self.invalidations as f64 / total as f64) * 100.0
55 }
56 }
57
58 pub fn efficiency_score(&self) -> f64 {
61 let hit_rate = self.hit_rate();
62 let invalidation_rate = self.invalidation_rate();
63 (hit_rate - (invalidation_rate * 0.5)).max(0.0)
66 }
67
68 pub fn uptime(&self) -> Duration {
70 SystemTime::now()
71 .duration_since(self.created_at)
72 .unwrap_or(Duration::from_secs(0))
73 }
74}
75
76#[derive(Debug, Clone)]
78pub struct CacheOperationTimer {
79 start_time: SystemTime,
81}
82
83impl CacheOperationTimer {
84 pub fn start() -> Self {
86 Self {
87 start_time: SystemTime::now(),
88 }
89 }
90
91 pub fn elapsed_ms(&self) -> f64 {
93 self.start_time
94 .elapsed()
95 .unwrap_or(Duration::from_secs(0))
96 .as_secs_f64()
97 * 1000.0
98 }
99}
100
101#[derive(Debug, Clone)]
103pub struct CacheStatsTracker {
104 stats: Arc<RwLock<DetailedCacheStats>>,
105}
106
107impl CacheStatsTracker {
108 pub fn new() -> Self {
110 Self {
111 stats: Arc::new(RwLock::new(DetailedCacheStats {
112 hits: 0,
113 misses: 0,
114 invalidations: 0,
115 size_bytes: 0,
116 entry_count: 0,
117 avg_retrieval_time_ms: 0.0,
118 avg_store_time_ms: 0.0,
119 total_operation_time_ms: 0.0,
120 last_operation_time: None,
121 created_at: SystemTime::now(),
122 })),
123 }
124 }
125
126 pub fn record_hit(&self, retrieval_time_ms: f64) {
128 if let Ok(mut stats) = self.stats.write() {
129 stats.hits += 1;
130 stats.total_operation_time_ms += retrieval_time_ms;
131
132 let total_retrievals = stats.hits + stats.misses;
134 if total_retrievals > 0 {
135 stats.avg_retrieval_time_ms =
136 stats.total_operation_time_ms / total_retrievals as f64;
137 }
138
139 stats.last_operation_time = Some(SystemTime::now());
140 }
141 }
142
143 pub fn record_miss(&self) {
145 if let Ok(mut stats) = self.stats.write() {
146 stats.misses += 1;
147 stats.last_operation_time = Some(SystemTime::now());
148 }
149 }
150
151 pub fn record_store(&self, store_time_ms: f64, size_bytes: u64) {
153 if let Ok(mut stats) = self.stats.write() {
154 stats.total_operation_time_ms += store_time_ms;
155 stats.size_bytes = size_bytes;
156
157 let total_operations = stats.hits + stats.misses;
159 if total_operations > 0 {
160 stats.avg_store_time_ms = store_time_ms / total_operations as f64;
161 }
162
163 stats.last_operation_time = Some(SystemTime::now());
164 }
165 }
166
167 pub fn record_invalidation(&self) {
169 if let Ok(mut stats) = self.stats.write() {
170 stats.invalidations += 1;
171 stats.last_operation_time = Some(SystemTime::now());
172 }
173 }
174
175 pub fn set_entry_count(&self, count: usize) {
177 if let Ok(mut stats) = self.stats.write() {
178 stats.entry_count = count;
179 }
180 }
181
182 pub fn get_stats(&self) -> Option<DetailedCacheStats> {
184 self.stats.read().ok().map(|s| s.clone())
185 }
186
187 pub fn reset(&self) {
189 if let Ok(mut stats) = self.stats.write() {
190 stats.hits = 0;
191 stats.misses = 0;
192 stats.invalidations = 0;
193 stats.size_bytes = 0;
194 stats.entry_count = 0;
195 stats.avg_retrieval_time_ms = 0.0;
196 stats.avg_store_time_ms = 0.0;
197 stats.total_operation_time_ms = 0.0;
198 stats.last_operation_time = None;
199 stats.created_at = SystemTime::now();
200 }
201 }
202
203 pub fn summary(&self) -> String {
205 if let Some(stats) = self.get_stats() {
206 format!(
207 "Cache Statistics:\n Hits: {}\n Misses: {}\n Hit Rate: {:.2}%\n Invalidations: {}\n Entries: {}\n Size: {} bytes\n Avg Retrieval: {:.2}ms\n Avg Store: {:.2}ms\n Efficiency Score: {:.2}",
208 stats.hits,
209 stats.misses,
210 stats.hit_rate(),
211 stats.invalidations,
212 stats.entry_count,
213 stats.size_bytes,
214 stats.avg_retrieval_time_ms,
215 stats.avg_store_time_ms,
216 stats.efficiency_score()
217 )
218 } else {
219 "Cache Statistics: (unavailable)".to_string()
220 }
221 }
222}
223
224impl Default for CacheStatsTracker {
225 fn default() -> Self {
226 Self::new()
227 }
228}
229
230#[cfg(test)]
231mod tests {
232 use super::*;
233
234 #[test]
235 fn test_detailed_cache_stats_hit_rate() {
236 let stats = DetailedCacheStats {
237 hits: 75,
238 misses: 25,
239 invalidations: 0,
240 size_bytes: 1024,
241 entry_count: 10,
242 avg_retrieval_time_ms: 1.5,
243 avg_store_time_ms: 2.0,
244 total_operation_time_ms: 100.0,
245 last_operation_time: None,
246 created_at: SystemTime::now(),
247 };
248
249 assert_eq!(stats.hit_rate(), 75.0);
250 assert_eq!(stats.miss_rate(), 25.0);
251 }
252
253 #[test]
254 fn test_detailed_cache_stats_invalidation_rate() {
255 let stats = DetailedCacheStats {
256 hits: 80,
257 misses: 20,
258 invalidations: 10,
259 size_bytes: 1024,
260 entry_count: 10,
261 avg_retrieval_time_ms: 1.5,
262 avg_store_time_ms: 2.0,
263 total_operation_time_ms: 100.0,
264 last_operation_time: None,
265 created_at: SystemTime::now(),
266 };
267
268 assert_eq!(stats.invalidation_rate(), 10.0);
269 }
270
271 #[test]
272 fn test_detailed_cache_stats_efficiency_score() {
273 let stats = DetailedCacheStats {
274 hits: 90,
275 misses: 10,
276 invalidations: 5,
277 size_bytes: 1024,
278 entry_count: 10,
279 avg_retrieval_time_ms: 1.5,
280 avg_store_time_ms: 2.0,
281 total_operation_time_ms: 100.0,
282 last_operation_time: None,
283 created_at: SystemTime::now(),
284 };
285
286 let efficiency = stats.efficiency_score();
287 assert!((efficiency - 87.5).abs() < 0.01);
290 }
291
292 #[test]
293 fn test_cache_operation_timer() {
294 let timer = CacheOperationTimer::start();
295 std::thread::sleep(Duration::from_millis(10));
296 let elapsed = timer.elapsed_ms();
297 assert!(elapsed >= 10.0);
298 }
299
300 #[test]
301 fn test_cache_stats_tracker_record_hit() {
302 let tracker = CacheStatsTracker::new();
303 tracker.record_hit(1.5);
304
305 let stats = tracker.get_stats().unwrap();
306 assert_eq!(stats.hits, 1);
307 assert_eq!(stats.misses, 0);
308 }
309
310 #[test]
311 fn test_cache_stats_tracker_record_miss() {
312 let tracker = CacheStatsTracker::new();
313 tracker.record_miss();
314
315 let stats = tracker.get_stats().unwrap();
316 assert_eq!(stats.hits, 0);
317 assert_eq!(stats.misses, 1);
318 }
319
320 #[test]
321 fn test_cache_stats_tracker_record_store() {
322 let tracker = CacheStatsTracker::new();
323 tracker.record_store(2.0, 1024);
324
325 let stats = tracker.get_stats().unwrap();
326 assert_eq!(stats.size_bytes, 1024);
327 }
328
329 #[test]
330 fn test_cache_stats_tracker_record_invalidation() {
331 let tracker = CacheStatsTracker::new();
332 tracker.record_invalidation();
333
334 let stats = tracker.get_stats().unwrap();
335 assert_eq!(stats.invalidations, 1);
336 }
337
338 #[test]
339 fn test_cache_stats_tracker_reset() {
340 let tracker = CacheStatsTracker::new();
341 tracker.record_hit(1.5);
342 tracker.record_miss();
343 tracker.record_invalidation();
344
345 let stats_before = tracker.get_stats().unwrap();
346 assert!(stats_before.hits > 0 || stats_before.misses > 0);
347
348 tracker.reset();
349
350 let stats_after = tracker.get_stats().unwrap();
351 assert_eq!(stats_after.hits, 0);
352 assert_eq!(stats_after.misses, 0);
353 assert_eq!(stats_after.invalidations, 0);
354 }
355
356 #[test]
357 fn test_cache_stats_tracker_summary() {
358 let tracker = CacheStatsTracker::new();
359 tracker.record_hit(1.5);
360 tracker.record_miss();
361
362 let summary = tracker.summary();
363 assert!(summary.contains("Cache Statistics"));
364 assert!(summary.contains("Hits: 1"));
365 assert!(summary.contains("Misses: 1"));
366 }
367
368 #[test]
369 fn test_cache_stats_tracker_set_entry_count() {
370 let tracker = CacheStatsTracker::new();
371 tracker.set_entry_count(42);
372
373 let stats = tracker.get_stats().unwrap();
374 assert_eq!(stats.entry_count, 42);
375 }
376}