Skip to main content

linkrs_metrics/
utils.rs

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    /// Record one fence revalidation of a cache hit (pass or fail).
59    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}