1use std::sync::atomic::{AtomicU64, Ordering};
2use std::time::Duration;
3
4pub fn calculate_cache_hit_rate(hits: u64, misses: u64) -> f64 {
5 let total = hits + misses;
6 if total > 0 {
7 hits as f64 / total as f64
8 } else {
9 0.0
10 }
11}
12
13#[derive(Debug, Default)]
14pub struct CacheStats {
15 hits: AtomicU64,
16 misses: AtomicU64,
17 evictions: AtomicU64,
18 expirations: AtomicU64,
19 insertions: AtomicU64,
20 rejections: AtomicU64,
21 invalidations: AtomicU64,
22 revalidations: AtomicU64,
23}
24
25impl CacheStats {
26 pub fn new() -> Self {
27 Self::default()
28 }
29
30 pub fn record_hit(&self) {
31 self.hits.fetch_add(1, Ordering::Relaxed);
32 }
33
34 pub fn record_miss(&self) {
35 self.misses.fetch_add(1, Ordering::Relaxed);
36 }
37
38 pub fn record_eviction(&self) {
39 self.evictions.fetch_add(1, Ordering::Relaxed);
40 }
41
42 pub fn record_expiration(&self) {
43 self.expirations.fetch_add(1, Ordering::Relaxed);
44 }
45
46 pub fn record_insertion(&self) {
47 self.insertions.fetch_add(1, Ordering::Relaxed);
48 }
49
50 pub fn record_rejection(&self) {
51 self.rejections.fetch_add(1, Ordering::Relaxed);
52 }
53
54 pub fn record_invalidation(&self) {
55 self.invalidations.fetch_add(1, Ordering::Relaxed);
56 }
57
58 pub fn record_revalidation(&self) {
60 self.revalidations.fetch_add(1, Ordering::Relaxed);
61 }
62
63 pub fn record_hits(&self, count: u64) {
64 self.hits.fetch_add(count, Ordering::Relaxed);
65 }
66
67 pub fn record_misses(&self, count: u64) {
68 self.misses.fetch_add(count, Ordering::Relaxed);
69 }
70
71 pub fn hits(&self) -> u64 {
72 self.hits.load(Ordering::Relaxed)
73 }
74
75 pub fn misses(&self) -> u64 {
76 self.misses.load(Ordering::Relaxed)
77 }
78
79 pub fn evictions(&self) -> u64 {
80 self.evictions.load(Ordering::Relaxed)
81 }
82
83 pub fn expirations(&self) -> u64 {
84 self.expirations.load(Ordering::Relaxed)
85 }
86
87 pub fn insertions(&self) -> u64 {
88 self.insertions.load(Ordering::Relaxed)
89 }
90
91 pub fn rejections(&self) -> u64 {
92 self.rejections.load(Ordering::Relaxed)
93 }
94
95 pub fn invalidations(&self) -> u64 {
96 self.invalidations.load(Ordering::Relaxed)
97 }
98
99 pub fn revalidations(&self) -> u64 {
100 self.revalidations.load(Ordering::Relaxed)
101 }
102
103 pub fn total_requests(&self) -> u64 {
104 self.hits() + self.misses()
105 }
106
107 pub fn hit_rate(&self) -> f64 {
108 calculate_cache_hit_rate(self.hits(), self.misses())
109 }
110
111 pub fn reset(&self) {
112 self.hits.store(0, Ordering::Relaxed);
113 self.misses.store(0, Ordering::Relaxed);
114 self.evictions.store(0, Ordering::Relaxed);
115 self.expirations.store(0, Ordering::Relaxed);
116 self.insertions.store(0, Ordering::Relaxed);
117 self.rejections.store(0, Ordering::Relaxed);
118 self.invalidations.store(0, Ordering::Relaxed);
119 self.revalidations.store(0, Ordering::Relaxed);
120 }
121}
122
123impl Clone for CacheStats {
124 fn clone(&self) -> Self {
125 Self {
126 hits: AtomicU64::new(self.hits()),
127 misses: AtomicU64::new(self.misses()),
128 evictions: AtomicU64::new(self.evictions()),
129 expirations: AtomicU64::new(self.expirations()),
130 insertions: AtomicU64::new(self.insertions()),
131 rejections: AtomicU64::new(self.rejections()),
132 invalidations: AtomicU64::new(self.invalidations()),
133 revalidations: AtomicU64::new(self.revalidations()),
134 }
135 }
136}
137
138pub trait TimeConversion {
139 fn as_micros(&self) -> u64;
140
141 fn as_millis_f64(&self) -> f64 {
142 self.as_micros() as f64 / 1000.0
143 }
144
145 fn as_seconds_f64(&self) -> f64 {
146 self.as_micros() as f64 / 1_000_000.0
147 }
148}
149
150impl TimeConversion for Duration {
151 fn as_micros(&self) -> u64 {
152 self.as_micros() as u64
153 }
154}
155
156impl TimeConversion for u64 {
157 fn as_micros(&self) -> u64 {
158 *self
159 }
160}
161
162pub fn calculate_average(total: f64, count: u64) -> f64 {
163 if count == 0 {
164 0.0
165 } else {
166 total / count as f64
167 }
168}
169
170pub fn micros_to_millis(micros: u64) -> f64 {
171 micros as f64 / 1000.0
172}
173
174pub fn duration_to_micros(duration: Duration) -> u64 {
175 duration.as_micros() as u64
176}
177
178pub fn format_duration(micros: u64) -> String {
179 if micros >= 1_000_000 {
180 format!("{:.2}s", micros as f64 / 1_000_000.0)
181 } else if micros >= 1_000 {
182 format!("{:.2}ms", micros as f64 / 1_000.0)
183 } else {
184 format!("{}us", micros)
185 }
186}
187
188#[cfg(test)]
189mod tests {
190 use super::*;
191
192 #[test]
193 fn test_calculate_cache_hit_rate() {
194 assert_eq!(calculate_cache_hit_rate(90, 10), 0.9);
195 assert_eq!(calculate_cache_hit_rate(0, 0), 0.0);
196 assert_eq!(calculate_cache_hit_rate(100, 0), 1.0);
197 }
198
199 #[test]
200 fn test_calculate_average() {
201 assert_eq!(calculate_average(100.0, 10), 10.0);
202 assert_eq!(calculate_average(0.0, 0), 0.0);
203 }
204
205 #[test]
206 fn test_micros_to_millis() {
207 assert_eq!(micros_to_millis(1000), 1.0);
208 assert_eq!(micros_to_millis(500), 0.5);
209 assert_eq!(micros_to_millis(0), 0.0);
210 }
211
212 #[test]
213 fn test_duration_to_micros() {
214 assert_eq!(duration_to_micros(Duration::from_micros(100)), 100);
215 assert_eq!(duration_to_micros(Duration::from_millis(1)), 1000);
216 }
217
218 #[test]
219 fn test_format_duration() {
220 assert_eq!(format_duration(500), "500us");
221 assert_eq!(format_duration(1500), "1.50ms");
222 assert_eq!(format_duration(1_500_000), "1.50s");
223 }
224}