1use dashmap::DashMap;
4use serde::{Deserialize, Serialize};
5use std::sync::atomic::{AtomicU64, Ordering};
6use std::sync::Arc;
7use std::time::{SystemTime, UNIX_EPOCH};
8
9pub struct GlobalStats {
11 pub total_requests: AtomicU64,
13
14 pub hits: AtomicU64,
16
17 pub misses: AtomicU64,
19
20 pub stale_hits: AtomicU64,
22
23 pub stores: AtomicU64,
25
26 pub invalidations: AtomicU64,
28
29 key_hits: Arc<DashMap<String, KeyHitStats>>,
31
32 pub uptime_start: SystemTime,
34}
35
36impl GlobalStats {
37 pub fn new() -> Self {
39 Self {
40 total_requests: AtomicU64::new(0),
41 hits: AtomicU64::new(0),
42 misses: AtomicU64::new(0),
43 stale_hits: AtomicU64::new(0),
44 stores: AtomicU64::new(0),
45 invalidations: AtomicU64::new(0),
46 key_hits: Arc::new(DashMap::new()),
47 uptime_start: SystemTime::now(),
48 }
49 }
50
51 pub fn record_hit(&self, key: &str) {
53 self.total_requests.fetch_add(1, Ordering::Relaxed);
54 self.hits.fetch_add(1, Ordering::Relaxed);
55 self.record_key_hit(key);
56 }
57
58 pub fn record_miss(&self, _key: &str) {
60 self.total_requests.fetch_add(1, Ordering::Relaxed);
61 self.misses.fetch_add(1, Ordering::Relaxed);
62 }
63
64 pub fn record_stale_hit(&self, key: &str) {
66 self.total_requests.fetch_add(1, Ordering::Relaxed);
67 self.stale_hits.fetch_add(1, Ordering::Relaxed);
68 self.record_key_hit(key);
69 }
70
71 pub fn record_store(&self, _key: &str) {
73 self.stores.fetch_add(1, Ordering::Relaxed);
74 }
75
76 pub fn record_invalidation(&self) {
78 self.invalidations.fetch_add(1, Ordering::Relaxed);
79 }
80
81 fn record_key_hit(&self, key: &str) {
83 self.key_hits
84 .entry(key.to_string())
85 .or_insert_with(KeyHitStats::new)
86 .increment();
87 }
88
89 pub fn hot_keys(&self, limit: usize) -> Vec<HotKeyInfo> {
91 let mut keys: Vec<_> = self
92 .key_hits
93 .iter()
94 .map(|entry| HotKeyInfo {
95 key: entry.key().clone(),
96 hits: entry.value().hits(),
97 last_accessed: entry.value().last_accessed,
98 })
99 .collect();
100
101 keys.sort_by(|a, b| b.hits.cmp(&a.hits));
102 keys.truncate(limit);
103 keys
104 }
105
106 pub fn snapshot(&self) -> StatsSnapshot {
108 let total = self.total_requests.load(Ordering::Relaxed);
109 let hits = self.hits.load(Ordering::Relaxed);
110 let misses = self.misses.load(Ordering::Relaxed);
111
112 let hit_rate = if total > 0 {
113 hits as f64 / total as f64
114 } else {
115 0.0
116 };
117
118 let miss_rate = if total > 0 {
119 misses as f64 / total as f64
120 } else {
121 0.0
122 };
123
124 let uptime = SystemTime::now()
125 .duration_since(self.uptime_start)
126 .unwrap_or_default()
127 .as_secs();
128
129 StatsSnapshot {
130 total_requests: total,
131 hits,
132 misses,
133 stale_hits: self.stale_hits.load(Ordering::Relaxed),
134 stores: self.stores.load(Ordering::Relaxed),
135 invalidations: self.invalidations.load(Ordering::Relaxed),
136 hit_rate,
137 miss_rate,
138 uptime_seconds: uptime,
139 }
140 }
141
142 pub fn reset(&self) {
144 self.total_requests.store(0, Ordering::Relaxed);
145 self.hits.store(0, Ordering::Relaxed);
146 self.misses.store(0, Ordering::Relaxed);
147 self.stale_hits.store(0, Ordering::Relaxed);
148 self.stores.store(0, Ordering::Relaxed);
149 self.invalidations.store(0, Ordering::Relaxed);
150 self.key_hits.clear();
151 }
152}
153
154impl Default for GlobalStats {
155 fn default() -> Self {
156 Self::new()
157 }
158}
159
160struct KeyHitStats {
162 hits: AtomicU64,
163 last_accessed: SystemTime,
164}
165
166impl KeyHitStats {
167 fn new() -> Self {
168 Self {
169 hits: AtomicU64::new(0),
170 last_accessed: SystemTime::now(),
171 }
172 }
173
174 fn increment(&self) {
175 self.hits.fetch_add(1, Ordering::Relaxed);
176 }
177
178 fn hits(&self) -> u64 {
179 self.hits.load(Ordering::Relaxed)
180 }
181}
182
183#[derive(Debug, Clone, Serialize, Deserialize)]
185pub struct HotKeyInfo {
186 pub key: String,
187 pub hits: u64,
188 #[serde(serialize_with = "serialize_system_time")]
189 pub last_accessed: SystemTime,
190}
191
192#[derive(Debug, Clone, Serialize, Deserialize)]
194pub struct StatsSnapshot {
195 pub total_requests: u64,
196 pub hits: u64,
197 pub misses: u64,
198 pub stale_hits: u64,
199 pub stores: u64,
200 pub invalidations: u64,
201 pub hit_rate: f64,
202 pub miss_rate: f64,
203 pub uptime_seconds: u64,
204}
205
206fn serialize_system_time<S>(time: &SystemTime, serializer: S) -> Result<S::Ok, S::Error>
208where
209 S: serde::Serializer,
210{
211 let duration = time.duration_since(UNIX_EPOCH).unwrap_or_default();
212 let secs = duration.as_secs();
213 let timestamp =
214 chrono::DateTime::from_timestamp(secs as i64, 0).unwrap_or(chrono::DateTime::UNIX_EPOCH);
215 serializer.serialize_str(×tamp.to_rfc3339())
216}
217
218#[cfg(test)]
219mod tests {
220 use super::*;
221
222 #[test]
223 fn global_stats_initial_state() {
224 let stats = GlobalStats::new();
225 let snapshot = stats.snapshot();
226
227 assert_eq!(snapshot.total_requests, 0);
228 assert_eq!(snapshot.hits, 0);
229 assert_eq!(snapshot.misses, 0);
230 assert_eq!(snapshot.hit_rate, 0.0);
231 }
232
233 #[test]
234 fn global_stats_record_operations() {
235 let stats = GlobalStats::new();
236
237 stats.record_hit("key1");
238 stats.record_miss("key2");
239 stats.record_stale_hit("key3");
240 stats.record_store("key4");
241 stats.record_invalidation();
242
243 let snapshot = stats.snapshot();
244 assert_eq!(snapshot.total_requests, 3); assert_eq!(snapshot.hits, 1);
246 assert_eq!(snapshot.misses, 1);
247 assert_eq!(snapshot.stale_hits, 1);
248 assert_eq!(snapshot.stores, 1);
249 assert_eq!(snapshot.invalidations, 1);
250 }
251
252 #[test]
253 fn global_stats_hit_rate_calculation() {
254 let stats = GlobalStats::new();
255
256 stats.record_hit("key1");
257 stats.record_hit("key2");
258 stats.record_miss("key3");
259
260 let snapshot = stats.snapshot();
261 assert_eq!(snapshot.total_requests, 3);
262 assert!((snapshot.hit_rate - 0.666).abs() < 0.01);
263 assert!((snapshot.miss_rate - 0.333).abs() < 0.01);
264 }
265
266 #[test]
267 fn global_stats_hot_keys() {
268 let stats = GlobalStats::new();
269
270 for _ in 0..10 {
272 stats.record_hit("hot_key");
273 }
274 for _ in 0..3 {
275 stats.record_hit("warm_key");
276 }
277 stats.record_hit("cold_key");
278
279 let hot_keys = stats.hot_keys(2);
280 assert_eq!(hot_keys.len(), 2);
281 assert_eq!(hot_keys[0].key, "hot_key");
282 assert_eq!(hot_keys[0].hits, 10);
283 assert_eq!(hot_keys[1].key, "warm_key");
284 assert_eq!(hot_keys[1].hits, 3);
285 }
286
287 #[test]
288 fn global_stats_reset() {
289 let stats = GlobalStats::new();
290
291 stats.record_hit("key1");
292 stats.record_miss("key2");
293
294 stats.reset();
295
296 let snapshot = stats.snapshot();
297 assert_eq!(snapshot.total_requests, 0);
298 assert_eq!(snapshot.hits, 0);
299 assert_eq!(snapshot.misses, 0);
300 }
301
302 #[test]
303 fn stats_snapshot_serialization() {
304 let snapshot = StatsSnapshot {
305 total_requests: 100,
306 hits: 80,
307 misses: 20,
308 stale_hits: 5,
309 stores: 90,
310 invalidations: 10,
311 hit_rate: 0.8,
312 miss_rate: 0.2,
313 uptime_seconds: 3600,
314 };
315
316 let json = serde_json::to_string(&snapshot).unwrap();
317 assert!(json.contains("\"total_requests\":100"));
318 assert!(json.contains("\"hit_rate\":0.8"));
319 }
320}