1use std::sync::atomic::{AtomicU64, Ordering};
4use std::time::{Duration, Instant};
5
6pub struct CacheMetrics {
8 hits: AtomicU64,
9 misses: AtomicU64,
10 writes: AtomicU64,
11 deletes: AtomicU64,
12 errors: AtomicU64,
13 total_hit_time_ns: AtomicU64,
14 total_miss_time_ns: AtomicU64,
15 total_write_time_ns: AtomicU64,
16 created_at: Instant,
17}
18
19impl Default for CacheMetrics {
20 fn default() -> Self {
21 Self::new()
22 }
23}
24
25impl CacheMetrics {
26 pub fn new() -> Self {
28 Self {
29 hits: AtomicU64::new(0),
30 misses: AtomicU64::new(0),
31 writes: AtomicU64::new(0),
32 deletes: AtomicU64::new(0),
33 errors: AtomicU64::new(0),
34 total_hit_time_ns: AtomicU64::new(0),
35 total_miss_time_ns: AtomicU64::new(0),
36 total_write_time_ns: AtomicU64::new(0),
37 created_at: Instant::now(),
38 }
39 }
40
41 #[inline]
43 pub fn record_hit(&self, duration: Duration) {
44 self.hits.fetch_add(1, Ordering::Relaxed);
45 self.total_hit_time_ns
46 .fetch_add(duration.as_nanos() as u64, Ordering::Relaxed);
47 }
48
49 #[inline]
51 pub fn record_miss(&self, duration: Duration) {
52 self.misses.fetch_add(1, Ordering::Relaxed);
53 self.total_miss_time_ns
54 .fetch_add(duration.as_nanos() as u64, Ordering::Relaxed);
55 }
56
57 #[inline]
59 pub fn record_write(&self, duration: Duration) {
60 self.writes.fetch_add(1, Ordering::Relaxed);
61 self.total_write_time_ns
62 .fetch_add(duration.as_nanos() as u64, Ordering::Relaxed);
63 }
64
65 #[inline]
67 pub fn record_delete(&self) {
68 self.deletes.fetch_add(1, Ordering::Relaxed);
69 }
70
71 #[inline]
73 pub fn record_error(&self) {
74 self.errors.fetch_add(1, Ordering::Relaxed);
75 }
76
77 pub fn snapshot(&self) -> CacheStats {
79 let hits = self.hits.load(Ordering::Relaxed);
80 let misses = self.misses.load(Ordering::Relaxed);
81 let writes = self.writes.load(Ordering::Relaxed);
82 let deletes = self.deletes.load(Ordering::Relaxed);
83 let errors = self.errors.load(Ordering::Relaxed);
84 let total_hit_time_ns = self.total_hit_time_ns.load(Ordering::Relaxed);
85 let total_miss_time_ns = self.total_miss_time_ns.load(Ordering::Relaxed);
86 let total_write_time_ns = self.total_write_time_ns.load(Ordering::Relaxed);
87
88 CacheStats {
89 hits,
90 misses,
91 writes,
92 deletes,
93 errors,
94 hit_rate: if hits + misses > 0 {
95 hits as f64 / (hits + misses) as f64
96 } else {
97 0.0
98 },
99 avg_hit_time: total_hit_time_ns
100 .checked_div(hits)
101 .map(Duration::from_nanos)
102 .unwrap_or(Duration::ZERO),
103 avg_miss_time: total_miss_time_ns
104 .checked_div(misses)
105 .map(Duration::from_nanos)
106 .unwrap_or(Duration::ZERO),
107 avg_write_time: total_write_time_ns
108 .checked_div(writes)
109 .map(Duration::from_nanos)
110 .unwrap_or(Duration::ZERO),
111 uptime: self.created_at.elapsed(),
112 entries: 0, memory_bytes: None,
114 }
115 }
116
117 pub fn reset(&self) {
119 self.hits.store(0, Ordering::Relaxed);
120 self.misses.store(0, Ordering::Relaxed);
121 self.writes.store(0, Ordering::Relaxed);
122 self.deletes.store(0, Ordering::Relaxed);
123 self.errors.store(0, Ordering::Relaxed);
124 self.total_hit_time_ns.store(0, Ordering::Relaxed);
125 self.total_miss_time_ns.store(0, Ordering::Relaxed);
126 self.total_write_time_ns.store(0, Ordering::Relaxed);
127 }
128}
129
130#[derive(Debug, Clone)]
132pub struct CacheStats {
133 pub hits: u64,
135 pub misses: u64,
137 pub writes: u64,
139 pub deletes: u64,
141 pub errors: u64,
143 pub hit_rate: f64,
145 pub avg_hit_time: Duration,
147 pub avg_miss_time: Duration,
149 pub avg_write_time: Duration,
151 pub uptime: Duration,
153 pub entries: usize,
155 pub memory_bytes: Option<usize>,
157}
158
159impl Default for CacheStats {
160 fn default() -> Self {
161 Self {
162 hits: 0,
163 misses: 0,
164 writes: 0,
165 deletes: 0,
166 errors: 0,
167 hit_rate: 0.0,
168 avg_hit_time: Duration::ZERO,
169 avg_miss_time: Duration::ZERO,
170 avg_write_time: Duration::ZERO,
171 uptime: Duration::ZERO,
172 entries: 0,
173 memory_bytes: None,
174 }
175 }
176}
177
178impl CacheStats {
179 pub fn total_ops(&self) -> u64 {
181 self.hits + self.misses + self.writes + self.deletes
182 }
183
184 pub fn ops_per_second(&self) -> f64 {
186 if self.uptime.as_secs_f64() > 0.0 {
187 self.total_ops() as f64 / self.uptime.as_secs_f64()
188 } else {
189 0.0
190 }
191 }
192
193 pub fn summary(&self) -> String {
195 format!(
196 "Cache Stats: {} hits, {} misses ({:.1}% hit rate), {} entries, uptime {:?}",
197 self.hits,
198 self.misses,
199 self.hit_rate * 100.0,
200 self.entries,
201 self.uptime
202 )
203 }
204}
205
206impl std::fmt::Display for CacheStats {
207 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
208 write!(f, "{}", self.summary())
209 }
210}
211
212#[cfg(test)]
213mod tests {
214 use super::*;
215
216 #[test]
217 fn test_metrics_recording() {
218 let metrics = CacheMetrics::new();
219
220 metrics.record_hit(Duration::from_micros(100));
221 metrics.record_hit(Duration::from_micros(200));
222 metrics.record_miss(Duration::from_micros(500));
223
224 let stats = metrics.snapshot();
225 assert_eq!(stats.hits, 2);
226 assert_eq!(stats.misses, 1);
227 assert!((stats.hit_rate - 0.666).abs() < 0.01);
228 }
229
230 #[test]
231 fn test_stats_ops_per_second() {
232 let stats = CacheStats {
233 hits: 1000,
234 misses: 100,
235 writes: 50,
236 deletes: 10,
237 uptime: Duration::from_secs(10),
238 ..Default::default()
239 };
240
241 assert_eq!(stats.total_ops(), 1160);
242 assert!((stats.ops_per_second() - 116.0).abs() < 0.1);
243 }
244}