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: if hits > 0 {
100 Duration::from_nanos(total_hit_time_ns / hits)
101 } else {
102 Duration::ZERO
103 },
104 avg_miss_time: if misses > 0 {
105 Duration::from_nanos(total_miss_time_ns / misses)
106 } else {
107 Duration::ZERO
108 },
109 avg_write_time: if writes > 0 {
110 Duration::from_nanos(total_write_time_ns / writes)
111 } else {
112 Duration::ZERO
113 },
114 uptime: self.created_at.elapsed(),
115 entries: 0, memory_bytes: None,
117 }
118 }
119
120 pub fn reset(&self) {
122 self.hits.store(0, Ordering::Relaxed);
123 self.misses.store(0, Ordering::Relaxed);
124 self.writes.store(0, Ordering::Relaxed);
125 self.deletes.store(0, Ordering::Relaxed);
126 self.errors.store(0, Ordering::Relaxed);
127 self.total_hit_time_ns.store(0, Ordering::Relaxed);
128 self.total_miss_time_ns.store(0, Ordering::Relaxed);
129 self.total_write_time_ns.store(0, Ordering::Relaxed);
130 }
131}
132
133#[derive(Debug, Clone)]
135pub struct CacheStats {
136 pub hits: u64,
138 pub misses: u64,
140 pub writes: u64,
142 pub deletes: u64,
144 pub errors: u64,
146 pub hit_rate: f64,
148 pub avg_hit_time: Duration,
150 pub avg_miss_time: Duration,
152 pub avg_write_time: Duration,
154 pub uptime: Duration,
156 pub entries: usize,
158 pub memory_bytes: Option<usize>,
160}
161
162impl Default for CacheStats {
163 fn default() -> Self {
164 Self {
165 hits: 0,
166 misses: 0,
167 writes: 0,
168 deletes: 0,
169 errors: 0,
170 hit_rate: 0.0,
171 avg_hit_time: Duration::ZERO,
172 avg_miss_time: Duration::ZERO,
173 avg_write_time: Duration::ZERO,
174 uptime: Duration::ZERO,
175 entries: 0,
176 memory_bytes: None,
177 }
178 }
179}
180
181impl CacheStats {
182 pub fn total_ops(&self) -> u64 {
184 self.hits + self.misses + self.writes + self.deletes
185 }
186
187 pub fn ops_per_second(&self) -> f64 {
189 if self.uptime.as_secs_f64() > 0.0 {
190 self.total_ops() as f64 / self.uptime.as_secs_f64()
191 } else {
192 0.0
193 }
194 }
195
196 pub fn summary(&self) -> String {
198 format!(
199 "Cache Stats: {} hits, {} misses ({:.1}% hit rate), {} entries, uptime {:?}",
200 self.hits,
201 self.misses,
202 self.hit_rate * 100.0,
203 self.entries,
204 self.uptime
205 )
206 }
207}
208
209impl std::fmt::Display for CacheStats {
210 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
211 write!(f, "{}", self.summary())
212 }
213}
214
215#[cfg(test)]
216mod tests {
217 use super::*;
218
219 #[test]
220 fn test_metrics_recording() {
221 let metrics = CacheMetrics::new();
222
223 metrics.record_hit(Duration::from_micros(100));
224 metrics.record_hit(Duration::from_micros(200));
225 metrics.record_miss(Duration::from_micros(500));
226
227 let stats = metrics.snapshot();
228 assert_eq!(stats.hits, 2);
229 assert_eq!(stats.misses, 1);
230 assert!((stats.hit_rate - 0.666).abs() < 0.01);
231 }
232
233 #[test]
234 fn test_stats_ops_per_second() {
235 let stats = CacheStats {
236 hits: 1000,
237 misses: 100,
238 writes: 50,
239 deletes: 10,
240 uptime: Duration::from_secs(10),
241 ..Default::default()
242 };
243
244 assert_eq!(stats.total_ops(), 1160);
245 assert!((stats.ops_per_second() - 116.0).abs() < 0.1);
246 }
247}
248