agentic_evolve_core/cache/
metrics.rs1use std::sync::atomic::{AtomicU64, AtomicUsize, Ordering};
4
5use serde::{Deserialize, Serialize};
6
7pub struct CacheMetrics {
9 hit_count: AtomicU64,
10 miss_count: AtomicU64,
11 eviction_count: AtomicU64,
12 current_size: AtomicUsize,
13}
14
15#[derive(Debug, Clone, Serialize, Deserialize)]
17pub struct CacheMetricsSnapshot {
18 pub hit_count: u64,
19 pub miss_count: u64,
20 pub eviction_count: u64,
21 pub current_size: usize,
22 pub hit_rate: f64,
23}
24
25impl CacheMetrics {
26 pub fn new() -> Self {
28 Self {
29 hit_count: AtomicU64::new(0),
30 miss_count: AtomicU64::new(0),
31 eviction_count: AtomicU64::new(0),
32 current_size: AtomicUsize::new(0),
33 }
34 }
35
36 pub fn record_hit(&self) {
38 self.hit_count.fetch_add(1, Ordering::Relaxed);
39 }
40
41 pub fn record_miss(&self) {
43 self.miss_count.fetch_add(1, Ordering::Relaxed);
44 }
45
46 pub fn record_eviction(&self) {
48 self.eviction_count.fetch_add(1, Ordering::Relaxed);
49 }
50
51 pub fn set_size(&self, size: usize) {
53 self.current_size.store(size, Ordering::Relaxed);
54 }
55
56 pub fn hit_count(&self) -> u64 {
58 self.hit_count.load(Ordering::Relaxed)
59 }
60
61 pub fn miss_count(&self) -> u64 {
63 self.miss_count.load(Ordering::Relaxed)
64 }
65
66 pub fn eviction_count(&self) -> u64 {
68 self.eviction_count.load(Ordering::Relaxed)
69 }
70
71 pub fn current_size(&self) -> usize {
73 self.current_size.load(Ordering::Relaxed)
74 }
75
76 pub fn hit_rate(&self) -> f64 {
80 let hits = self.hit_count() as f64;
81 let total = hits + self.miss_count() as f64;
82 if total == 0.0 {
83 0.0
84 } else {
85 hits / total
86 }
87 }
88
89 pub fn snapshot(&self) -> CacheMetricsSnapshot {
91 CacheMetricsSnapshot {
92 hit_count: self.hit_count(),
93 miss_count: self.miss_count(),
94 eviction_count: self.eviction_count(),
95 current_size: self.current_size(),
96 hit_rate: self.hit_rate(),
97 }
98 }
99}
100
101impl Default for CacheMetrics {
102 fn default() -> Self {
103 Self::new()
104 }
105}
106
107#[cfg(test)]
108mod tests {
109 use super::*;
110
111 #[test]
112 fn new_metrics_are_zero() {
113 let m = CacheMetrics::new();
114 assert_eq!(m.hit_count(), 0);
115 assert_eq!(m.miss_count(), 0);
116 assert_eq!(m.eviction_count(), 0);
117 assert_eq!(m.current_size(), 0);
118 }
119
120 #[test]
121 fn hit_rate_empty_is_zero() {
122 let m = CacheMetrics::new();
123 assert_eq!(m.hit_rate(), 0.0);
124 }
125
126 #[test]
127 fn hit_rate_all_hits() {
128 let m = CacheMetrics::new();
129 m.record_hit();
130 m.record_hit();
131 assert_eq!(m.hit_rate(), 1.0);
132 }
133
134 #[test]
135 fn hit_rate_mixed() {
136 let m = CacheMetrics::new();
137 m.record_hit();
138 m.record_miss();
139 assert!((m.hit_rate() - 0.5).abs() < f64::EPSILON);
140 }
141
142 #[test]
143 fn eviction_count_tracks() {
144 let m = CacheMetrics::new();
145 m.record_eviction();
146 m.record_eviction();
147 assert_eq!(m.eviction_count(), 2);
148 }
149
150 #[test]
151 fn set_size_works() {
152 let m = CacheMetrics::new();
153 m.set_size(42);
154 assert_eq!(m.current_size(), 42);
155 }
156
157 #[test]
158 fn snapshot_captures_state() {
159 let m = CacheMetrics::new();
160 m.record_hit();
161 m.record_miss();
162 m.set_size(10);
163 let snap = m.snapshot();
164 assert_eq!(snap.hit_count, 1);
165 assert_eq!(snap.miss_count, 1);
166 assert_eq!(snap.current_size, 10);
167 assert!((snap.hit_rate - 0.5).abs() < f64::EPSILON);
168 }
169}