1use serde::{Deserialize, Serialize};
7use std::sync::Arc;
8use std::sync::atomic::{AtomicU64, Ordering};
9use std::time::{Duration, Instant};
10
11#[derive(Debug, Clone)]
13pub struct CacheStats {
14 hits: Arc<AtomicU64>,
16 misses: Arc<AtomicU64>,
18 evictions: Arc<AtomicU64>,
20 bytes_saved: Arc<AtomicU64>,
22 bytes_written: Arc<AtomicU64>,
24 bytes_evicted: Arc<AtomicU64>,
26 read_operations: Arc<AtomicU64>,
28 write_operations: Arc<AtomicU64>,
30 delete_operations: Arc<AtomicU64>,
32 start_time: Instant,
34}
35
36#[derive(Debug, Clone, Serialize, Deserialize)]
38pub struct CacheStatsSnapshot {
39 pub hits: u64,
41 pub misses: u64,
43 pub evictions: u64,
45 pub bytes_saved: u64,
47 pub bytes_written: u64,
49 pub bytes_evicted: u64,
51 pub read_operations: u64,
53 pub write_operations: u64,
55 pub delete_operations: u64,
57 pub hit_rate: f64,
59 pub miss_rate: f64,
61 pub total_operations: u64,
63 pub uptime_seconds: u64,
65}
66
67#[derive(Debug, Clone, Serialize, Deserialize)]
69pub struct CacheReport {
70 pub stats: CacheStatsSnapshot,
72 pub avg_bytes_per_hit: f64,
74 pub avg_bytes_per_write: f64,
76 pub bandwidth_savings_ratio: f64,
78 pub operations_per_second: f64,
80 pub bytes_per_second_saved: f64,
82 pub effectiveness_score: f64,
84}
85
86impl Default for CacheStats {
87 fn default() -> Self {
88 Self::new()
89 }
90}
91
92impl CacheStats {
93 pub fn new() -> Self {
95 Self {
96 hits: Arc::new(AtomicU64::new(0)),
97 misses: Arc::new(AtomicU64::new(0)),
98 evictions: Arc::new(AtomicU64::new(0)),
99 bytes_saved: Arc::new(AtomicU64::new(0)),
100 bytes_written: Arc::new(AtomicU64::new(0)),
101 bytes_evicted: Arc::new(AtomicU64::new(0)),
102 read_operations: Arc::new(AtomicU64::new(0)),
103 write_operations: Arc::new(AtomicU64::new(0)),
104 delete_operations: Arc::new(AtomicU64::new(0)),
105 start_time: Instant::now(),
106 }
107 }
108
109 pub fn record_hit(&self, bytes: u64) {
111 self.hits.fetch_add(1, Ordering::Relaxed);
112 self.bytes_saved.fetch_add(bytes, Ordering::Relaxed);
113 self.read_operations.fetch_add(1, Ordering::Relaxed);
114 }
115
116 pub fn record_miss(&self) {
118 self.misses.fetch_add(1, Ordering::Relaxed);
119 self.read_operations.fetch_add(1, Ordering::Relaxed);
120 }
121
122 pub fn record_eviction(&self, bytes: u64) {
124 self.evictions.fetch_add(1, Ordering::Relaxed);
125 self.bytes_evicted.fetch_add(bytes, Ordering::Relaxed);
126 }
127
128 pub fn record_write(&self, bytes: u64) {
130 self.bytes_written.fetch_add(bytes, Ordering::Relaxed);
131 self.write_operations.fetch_add(1, Ordering::Relaxed);
132 }
133
134 pub fn record_delete(&self) {
136 self.delete_operations.fetch_add(1, Ordering::Relaxed);
137 }
138
139 pub fn hits(&self) -> u64 {
141 self.hits.load(Ordering::Relaxed)
142 }
143
144 pub fn misses(&self) -> u64 {
146 self.misses.load(Ordering::Relaxed)
147 }
148
149 pub fn evictions(&self) -> u64 {
151 self.evictions.load(Ordering::Relaxed)
152 }
153
154 pub fn bytes_saved(&self) -> u64 {
156 self.bytes_saved.load(Ordering::Relaxed)
157 }
158
159 pub fn bytes_written(&self) -> u64 {
161 self.bytes_written.load(Ordering::Relaxed)
162 }
163
164 pub fn bytes_evicted(&self) -> u64 {
166 self.bytes_evicted.load(Ordering::Relaxed)
167 }
168
169 pub fn hit_rate(&self) -> f64 {
171 let hits = self.hits();
172 let total = hits + self.misses();
173
174 if total == 0 {
175 0.0
176 } else {
177 (hits as f64 / total as f64) * 100.0
178 }
179 }
180
181 pub fn miss_rate(&self) -> f64 {
183 let misses = self.misses();
184 let total = self.hits() + misses;
185
186 if total == 0 {
187 0.0
188 } else {
189 (misses as f64 / total as f64) * 100.0
190 }
191 }
192
193 pub fn total_operations(&self) -> u64 {
195 self.hits() + self.misses()
196 }
197
198 pub fn uptime(&self) -> Duration {
200 self.start_time.elapsed()
201 }
202
203 pub fn reset(&self) {
205 self.hits.store(0, Ordering::Relaxed);
206 self.misses.store(0, Ordering::Relaxed);
207 self.evictions.store(0, Ordering::Relaxed);
208 self.bytes_saved.store(0, Ordering::Relaxed);
209 self.bytes_written.store(0, Ordering::Relaxed);
210 self.bytes_evicted.store(0, Ordering::Relaxed);
211 self.read_operations.store(0, Ordering::Relaxed);
212 self.write_operations.store(0, Ordering::Relaxed);
213 self.delete_operations.store(0, Ordering::Relaxed);
214 }
215
216 pub fn snapshot(&self) -> CacheStatsSnapshot {
218 let hits = self.hits();
219 let misses = self.misses();
220 let total_ops = hits + misses;
221 let uptime_secs = self.uptime().as_secs();
222
223 CacheStatsSnapshot {
224 hits,
225 misses,
226 evictions: self.evictions(),
227 bytes_saved: self.bytes_saved(),
228 bytes_written: self.bytes_written(),
229 bytes_evicted: self.bytes_evicted(),
230 read_operations: self.read_operations.load(Ordering::Relaxed),
231 write_operations: self.write_operations.load(Ordering::Relaxed),
232 delete_operations: self.delete_operations.load(Ordering::Relaxed),
233 hit_rate: self.hit_rate(),
234 miss_rate: self.miss_rate(),
235 total_operations: total_ops,
236 uptime_seconds: uptime_secs,
237 }
238 }
239
240 pub fn report(&self) -> CacheReport {
242 let stats = self.snapshot();
243 let uptime_secs = stats.uptime_seconds as f64;
244
245 let avg_bytes_per_hit = if stats.hits > 0 {
247 stats.bytes_saved as f64 / stats.hits as f64
248 } else {
249 0.0
250 };
251
252 let avg_bytes_per_write = if stats.write_operations > 0 {
253 stats.bytes_written as f64 / stats.write_operations as f64
254 } else {
255 0.0
256 };
257
258 let total_bytes_served = stats.bytes_saved + (stats.misses * avg_bytes_per_hit as u64);
260 let bandwidth_savings_ratio = if total_bytes_served > 0 {
261 stats.bytes_saved as f64 / total_bytes_served as f64
262 } else {
263 0.0
264 };
265
266 let operations_per_second = if uptime_secs > 0.0 {
268 stats.total_operations as f64 / uptime_secs
269 } else {
270 0.0
271 };
272
273 let bytes_per_second_saved = if uptime_secs > 0.0 {
274 stats.bytes_saved as f64 / uptime_secs
275 } else {
276 0.0
277 };
278
279 let hit_rate_score = stats.hit_rate;
281 let bandwidth_score = bandwidth_savings_ratio * 100.0;
282 let effectiveness_score = (hit_rate_score * 0.7) + (bandwidth_score * 0.3);
283
284 CacheReport {
285 stats,
286 avg_bytes_per_hit,
287 avg_bytes_per_write,
288 bandwidth_savings_ratio,
289 operations_per_second,
290 bytes_per_second_saved,
291 effectiveness_score,
292 }
293 }
294}
295
296impl CacheStatsSnapshot {
297 pub fn format_bytes(bytes: u64) -> String {
299 const UNITS: &[&str] = &["B", "KB", "MB", "GB", "TB"];
300 let mut size = bytes as f64;
301 let mut unit_index = 0;
302
303 while size >= 1024.0 && unit_index < UNITS.len() - 1 {
304 size /= 1024.0;
305 unit_index += 1;
306 }
307
308 if unit_index == 0 {
309 format!("{} {}", bytes, UNITS[unit_index])
310 } else {
311 format!("{:.2} {}", size, UNITS[unit_index])
312 }
313 }
314
315 pub fn format_uptime(&self) -> String {
317 let secs = self.uptime_seconds;
318 let days = secs / 86400;
319 let hours = (secs % 86400) / 3600;
320 let minutes = (secs % 3600) / 60;
321 let seconds = secs % 60;
322
323 if days > 0 {
324 format!("{days}d {hours}h {minutes}m {seconds}s")
325 } else if hours > 0 {
326 format!("{hours}h {minutes}m {seconds}s")
327 } else if minutes > 0 {
328 format!("{minutes}m {seconds}s")
329 } else {
330 format!("{seconds}s")
331 }
332 }
333}
334
335impl std::fmt::Display for CacheStatsSnapshot {
336 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
337 writeln!(f, "Cache Statistics:")?;
338 writeln!(
339 f,
340 " Operations: {} hits, {} misses ({:.1}% hit rate)",
341 self.hits, self.misses, self.hit_rate
342 )?;
343 writeln!(
344 f,
345 " Bandwidth: {} saved, {} written",
346 Self::format_bytes(self.bytes_saved),
347 Self::format_bytes(self.bytes_written)
348 )?;
349 writeln!(
350 f,
351 " Evictions: {} entries ({} bytes)",
352 self.evictions,
353 Self::format_bytes(self.bytes_evicted)
354 )?;
355 writeln!(f, " Uptime: {}", self.format_uptime())?;
356 Ok(())
357 }
358}
359
360impl std::fmt::Display for CacheReport {
361 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
362 writeln!(f, "Cache Performance Report:")?;
363 writeln!(f, "{}", self.stats)?;
364 writeln!(f, "Performance Metrics:")?;
365 writeln!(f, " Average bytes per hit: {:.1}", self.avg_bytes_per_hit)?;
366 writeln!(
367 f,
368 " Average bytes per write: {:.1}",
369 self.avg_bytes_per_write
370 )?;
371 writeln!(
372 f,
373 " Bandwidth savings: {:.1}%",
374 self.bandwidth_savings_ratio * 100.0
375 )?;
376 writeln!(
377 f,
378 " Operations per second: {:.1}",
379 self.operations_per_second
380 )?;
381 writeln!(
382 f,
383 " Bytes per second saved: {}",
384 CacheStatsSnapshot::format_bytes(self.bytes_per_second_saved as u64)
385 )?;
386 writeln!(
387 f,
388 " Effectiveness score: {:.1}/100",
389 self.effectiveness_score
390 )?;
391 Ok(())
392 }
393}
394
395#[cfg(test)]
396mod tests {
397 use super::*;
398 use std::thread;
399 use std::time::Duration as StdDuration;
400
401 #[test]
402 fn test_cache_stats_creation() {
403 let stats = CacheStats::new();
404 assert_eq!(stats.hits(), 0);
405 assert_eq!(stats.misses(), 0);
406 assert_eq!(stats.evictions(), 0);
407 assert_eq!(stats.bytes_saved(), 0);
408 }
409
410 #[test]
411 fn test_record_operations() {
412 let stats = CacheStats::new();
413
414 stats.record_hit(1024);
416 stats.record_hit(2048);
417 assert_eq!(stats.hits(), 2);
418 assert_eq!(stats.bytes_saved(), 3072);
419
420 stats.record_miss();
422 stats.record_miss();
423 assert_eq!(stats.misses(), 2);
424
425 stats.record_eviction(512);
427 assert_eq!(stats.evictions(), 1);
428 assert_eq!(stats.bytes_evicted(), 512);
429
430 stats.record_write(4096);
432 assert_eq!(stats.bytes_written(), 4096);
433 }
434
435 #[test]
436 fn test_hit_miss_rates() {
437 let stats = CacheStats::new();
438
439 assert_eq!(stats.hit_rate(), 0.0);
441 assert_eq!(stats.miss_rate(), 0.0);
442
443 stats.record_hit(100);
445 stats.record_hit(200);
446 stats.record_hit(300);
447 stats.record_miss();
448
449 assert!((stats.hit_rate() - 75.0).abs() < 0.001);
450 assert!((stats.miss_rate() - 25.0).abs() < 0.001);
451 assert_eq!(stats.total_operations(), 4);
452 }
453
454 #[test]
455 fn test_stats_reset() {
456 let stats = CacheStats::new();
457
458 stats.record_hit(1000);
460 stats.record_miss();
461 stats.record_eviction(500);
462 stats.record_write(2000);
463
464 assert_eq!(stats.hits(), 1);
466 assert_eq!(stats.misses(), 1);
467 assert_eq!(stats.evictions(), 1);
468 assert_eq!(stats.bytes_saved(), 1000);
469 assert_eq!(stats.bytes_written(), 2000);
470
471 stats.reset();
473 assert_eq!(stats.hits(), 0);
474 assert_eq!(stats.misses(), 0);
475 assert_eq!(stats.evictions(), 0);
476 assert_eq!(stats.bytes_saved(), 0);
477 assert_eq!(stats.bytes_written(), 0);
478 }
479
480 #[test]
481 fn test_snapshot() {
482 let stats = CacheStats::new();
483
484 stats.record_hit(500);
486 stats.record_hit(1500);
487 stats.record_miss();
488 stats.record_write(1000);
489 stats.record_eviction(200);
490
491 let snapshot = stats.snapshot();
492 assert_eq!(snapshot.hits, 2);
493 assert_eq!(snapshot.misses, 1);
494 assert_eq!(snapshot.evictions, 1);
495 assert_eq!(snapshot.bytes_saved, 2000);
496 assert_eq!(snapshot.bytes_written, 1000);
497 assert_eq!(snapshot.bytes_evicted, 200);
498 assert!((snapshot.hit_rate - 66.666).abs() < 0.01);
499 assert_eq!(snapshot.total_operations, 3);
500 }
501
502 #[test]
503 fn test_report_generation() {
504 let stats = CacheStats::new();
505
506 stats.record_hit(1024); stats.record_hit(2048); stats.record_hit(4096); stats.record_miss(); stats.record_write(8192); let report = stats.report();
514
515 assert_eq!(report.stats.hits, 3);
517 assert_eq!(report.stats.misses, 1);
518 assert_eq!(report.stats.bytes_saved, 7168); assert!((report.avg_bytes_per_hit - 2389.33).abs() < 0.01); assert_eq!(report.avg_bytes_per_write, 8192.0);
523 assert!(report.effectiveness_score > 0.0);
524 assert!(report.effectiveness_score <= 100.0);
525 }
526
527 #[test]
528 fn test_format_bytes() {
529 assert_eq!(CacheStatsSnapshot::format_bytes(0), "0 B");
530 assert_eq!(CacheStatsSnapshot::format_bytes(512), "512 B");
531 assert_eq!(CacheStatsSnapshot::format_bytes(1024), "1.00 KB");
532 assert_eq!(CacheStatsSnapshot::format_bytes(1536), "1.50 KB");
533 assert_eq!(CacheStatsSnapshot::format_bytes(1048576), "1.00 MB");
534 assert_eq!(CacheStatsSnapshot::format_bytes(1073741824), "1.00 GB");
535 }
536
537 #[test]
538 fn test_concurrent_access() {
539 let stats = Arc::new(CacheStats::new());
540 let mut handles = vec![];
541
542 for i in 0..10 {
544 let stats_clone = Arc::clone(&stats);
545 let handle = thread::spawn(move || {
546 for j in 0..100 {
547 stats_clone.record_hit((i * 100 + j) as u64);
548 stats_clone.record_miss();
549 stats_clone.record_write(1000);
550 }
551 });
552 handles.push(handle);
553 }
554
555 for handle in handles {
557 handle.join().unwrap();
558 }
559
560 assert_eq!(stats.hits(), 1000);
562 assert_eq!(stats.misses(), 1000);
563 assert_eq!(stats.bytes_written(), 1000000); assert!((stats.hit_rate() - 50.0).abs() < 0.001);
565 }
566
567 #[test]
568 fn test_uptime_tracking() {
569 let stats = CacheStats::new();
570
571 thread::sleep(StdDuration::from_millis(10));
573
574 let uptime = stats.uptime();
575 assert!(uptime.as_millis() >= 10);
576
577 let snapshot = stats.snapshot();
578 assert!(snapshot.uptime_seconds < 3600); }
581
582 #[test]
583 fn test_display_formatting() {
584 let stats = CacheStats::new();
585 stats.record_hit(1024);
586 stats.record_miss();
587
588 let snapshot = stats.snapshot();
589 let display_output = format!("{snapshot}");
590
591 assert!(display_output.contains("Cache Statistics:"));
592 assert!(display_output.contains("1 hits, 1 misses"));
593 assert!(display_output.contains("1.00 KB saved"));
594
595 let report = stats.report();
596 let report_output = format!("{report}");
597
598 assert!(report_output.contains("Cache Performance Report:"));
599 assert!(report_output.contains("Performance Metrics:"));
600 }
601}