rust_serv/memory_cache/
stats.rs1use std::sync::atomic::{AtomicU64, Ordering};
4
5#[derive(Debug, Default)]
7pub struct CacheStats {
8 hits: AtomicU64,
10 misses: AtomicU64,
12 evictions: AtomicU64,
14 entries: AtomicU64,
16 total_size: AtomicU64,
18 expired: AtomicU64,
20}
21
22impl CacheStats {
23 pub fn new() -> Self {
25 Self::default()
26 }
27
28 pub fn record_hit(&self) {
30 self.hits.fetch_add(1, Ordering::Relaxed);
31 }
32
33 pub fn record_miss(&self) {
35 self.misses.fetch_add(1, Ordering::Relaxed);
36 }
37
38 pub fn record_eviction(&self) {
40 self.evictions.fetch_add(1, Ordering::Relaxed);
41 }
42
43 pub fn record_expired(&self) {
45 self.expired.fetch_add(1, Ordering::Relaxed);
46 }
47
48 pub fn add_entry(&self, size: usize) {
50 self.entries.fetch_add(1, Ordering::Relaxed);
51 self.total_size.fetch_add(size as u64, Ordering::Relaxed);
52 }
53
54 pub fn remove_entry(&self, size: usize) {
56 self.entries.fetch_sub(1, Ordering::Relaxed);
57 self.total_size.fetch_sub(size as u64, Ordering::Relaxed);
58 }
59
60 pub fn update_size(&self, delta: i64) {
62 if delta >= 0 {
63 self.total_size.fetch_add(delta as u64, Ordering::Relaxed);
64 } else {
65 self.total_size.fetch_sub((-delta) as u64, Ordering::Relaxed);
66 }
67 }
68
69 pub fn hits(&self) -> u64 {
71 self.hits.load(Ordering::Relaxed)
72 }
73
74 pub fn misses(&self) -> u64 {
76 self.misses.load(Ordering::Relaxed)
77 }
78
79 pub fn evictions(&self) -> u64 {
81 self.evictions.load(Ordering::Relaxed)
82 }
83
84 pub fn entries(&self) -> u64 {
86 self.entries.load(Ordering::Relaxed)
87 }
88
89 pub fn total_size(&self) -> u64 {
91 self.total_size.load(Ordering::Relaxed)
92 }
93
94 pub fn expired(&self) -> u64 {
96 self.expired.load(Ordering::Relaxed)
97 }
98
99 pub fn hit_rate(&self) -> f64 {
101 let hits = self.hits();
102 let misses = self.misses();
103 let total = hits + misses;
104 if total == 0 {
105 0.0
106 } else {
107 hits as f64 / total as f64
108 }
109 }
110
111 pub fn total_requests(&self) -> u64 {
113 self.hits() + self.misses()
114 }
115
116 pub fn reset(&self) {
118 self.hits.store(0, Ordering::Relaxed);
119 self.misses.store(0, Ordering::Relaxed);
120 self.evictions.store(0, Ordering::Relaxed);
121 self.entries.store(0, Ordering::Relaxed);
122 self.total_size.store(0, Ordering::Relaxed);
123 self.expired.store(0, Ordering::Relaxed);
124 }
125}
126
127#[cfg(test)]
128mod tests {
129 use super::*;
130
131 #[test]
132 fn test_cache_stats_creation() {
133 let stats = CacheStats::new();
134 assert_eq!(stats.hits(), 0);
135 assert_eq!(stats.misses(), 0);
136 assert_eq!(stats.evictions(), 0);
137 assert_eq!(stats.entries(), 0);
138 assert_eq!(stats.total_size(), 0);
139 }
140
141 #[test]
142 fn test_record_hit() {
143 let stats = CacheStats::new();
144 stats.record_hit();
145 stats.record_hit();
146 stats.record_hit();
147 assert_eq!(stats.hits(), 3);
148 }
149
150 #[test]
151 fn test_record_miss() {
152 let stats = CacheStats::new();
153 stats.record_miss();
154 stats.record_miss();
155 assert_eq!(stats.misses(), 2);
156 }
157
158 #[test]
159 fn test_record_eviction() {
160 let stats = CacheStats::new();
161 stats.record_eviction();
162 assert_eq!(stats.evictions(), 1);
163 }
164
165 #[test]
166 fn test_record_expired() {
167 let stats = CacheStats::new();
168 stats.record_expired();
169 stats.record_expired();
170 assert_eq!(stats.expired(), 2);
171 }
172
173 #[test]
174 fn test_add_remove_entry() {
175 let stats = CacheStats::new();
176
177 stats.add_entry(100);
178 assert_eq!(stats.entries(), 1);
179 assert_eq!(stats.total_size(), 100);
180
181 stats.add_entry(200);
182 assert_eq!(stats.entries(), 2);
183 assert_eq!(stats.total_size(), 300);
184
185 stats.remove_entry(100);
186 assert_eq!(stats.entries(), 1);
187 assert_eq!(stats.total_size(), 200);
188 }
189
190 #[test]
191 fn test_update_size_positive() {
192 let stats = CacheStats::new();
193 stats.update_size(100);
194 assert_eq!(stats.total_size(), 100);
195 }
196
197 #[test]
198 fn test_update_size_negative() {
199 let stats = CacheStats::new();
200 stats.add_entry(200);
201 stats.update_size(-50);
202 assert_eq!(stats.total_size(), 150);
203 }
204
205 #[test]
206 fn test_hit_rate_no_requests() {
207 let stats = CacheStats::new();
208 assert_eq!(stats.hit_rate(), 0.0);
209 }
210
211 #[test]
212 fn test_hit_rate_all_hits() {
213 let stats = CacheStats::new();
214 stats.record_hit();
215 stats.record_hit();
216 assert_eq!(stats.hit_rate(), 1.0);
217 }
218
219 #[test]
220 fn test_hit_rate_all_misses() {
221 let stats = CacheStats::new();
222 stats.record_miss();
223 stats.record_miss();
224 assert_eq!(stats.hit_rate(), 0.0);
225 }
226
227 #[test]
228 fn test_hit_rate_mixed() {
229 let stats = CacheStats::new();
230 stats.record_hit();
231 stats.record_hit();
232 stats.record_miss();
233 stats.record_miss();
234 assert!((stats.hit_rate() - 0.5).abs() < 0.001);
236 }
237
238 #[test]
239 fn test_total_requests() {
240 let stats = CacheStats::new();
241 stats.record_hit();
242 stats.record_hit();
243 stats.record_miss();
244 assert_eq!(stats.total_requests(), 3);
245 }
246
247 #[test]
248 fn test_reset() {
249 let stats = CacheStats::new();
250 stats.record_hit();
251 stats.record_miss();
252 stats.add_entry(100);
253 stats.record_eviction();
254
255 stats.reset();
256
257 assert_eq!(stats.hits(), 0);
258 assert_eq!(stats.misses(), 0);
259 assert_eq!(stats.entries(), 0);
260 assert_eq!(stats.total_size(), 0);
261 assert_eq!(stats.evictions(), 0);
262 }
263
264 #[test]
265 fn test_concurrent_access() {
266 use std::sync::Arc;
267 use std::thread;
268
269 let stats = Arc::new(CacheStats::new());
270 let mut handles = vec![];
271
272 for _ in 0..10 {
273 let stats_clone = Arc::clone(&stats);
274 handles.push(thread::spawn(move || {
275 stats_clone.record_hit();
276 stats_clone.record_miss();
277 }));
278 }
279
280 for handle in handles {
281 handle.join().unwrap();
282 }
283
284 assert_eq!(stats.hits(), 10);
285 assert_eq!(stats.misses(), 10);
286 }
287}