Skip to main content

prax_query/data_cache/
stats.rs

1//! Cache statistics and metrics.
2
3use std::sync::atomic::{AtomicU64, Ordering};
4use std::time::{Duration, Instant};
5
6/// Thread-safe cache metrics collector.
7pub 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    /// Create a new metrics collector.
27    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    /// Record a cache hit.
42    #[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    /// Record a cache miss.
50    #[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    /// Record a write operation.
58    #[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    /// Record a delete operation.
66    #[inline]
67    pub fn record_delete(&self) {
68        self.deletes.fetch_add(1, Ordering::Relaxed);
69    }
70
71    /// Record an error.
72    #[inline]
73    pub fn record_error(&self) {
74        self.errors.fetch_add(1, Ordering::Relaxed);
75    }
76
77    /// Get a snapshot of the current stats.
78    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: total_hit_time_ns
100                .checked_div(hits)
101                .map(Duration::from_nanos)
102                .unwrap_or(Duration::ZERO),
103            avg_miss_time: total_miss_time_ns
104                .checked_div(misses)
105                .map(Duration::from_nanos)
106                .unwrap_or(Duration::ZERO),
107            avg_write_time: total_write_time_ns
108                .checked_div(writes)
109                .map(Duration::from_nanos)
110                .unwrap_or(Duration::ZERO),
111            uptime: self.created_at.elapsed(),
112            entries: 0, // Filled by backend
113            memory_bytes: None,
114        }
115    }
116
117    /// Reset all metrics.
118    pub fn reset(&self) {
119        self.hits.store(0, Ordering::Relaxed);
120        self.misses.store(0, Ordering::Relaxed);
121        self.writes.store(0, Ordering::Relaxed);
122        self.deletes.store(0, Ordering::Relaxed);
123        self.errors.store(0, Ordering::Relaxed);
124        self.total_hit_time_ns.store(0, Ordering::Relaxed);
125        self.total_miss_time_ns.store(0, Ordering::Relaxed);
126        self.total_write_time_ns.store(0, Ordering::Relaxed);
127    }
128}
129
130/// A snapshot of cache statistics.
131#[derive(Debug, Clone)]
132pub struct CacheStats {
133    /// Number of cache hits.
134    pub hits: u64,
135    /// Number of cache misses.
136    pub misses: u64,
137    /// Number of write operations.
138    pub writes: u64,
139    /// Number of delete operations.
140    pub deletes: u64,
141    /// Number of errors.
142    pub errors: u64,
143    /// Hit rate (0.0 - 1.0).
144    pub hit_rate: f64,
145    /// Average time for cache hits.
146    pub avg_hit_time: Duration,
147    /// Average time for cache misses.
148    pub avg_miss_time: Duration,
149    /// Average time for writes.
150    pub avg_write_time: Duration,
151    /// Time since cache was created.
152    pub uptime: Duration,
153    /// Number of entries in cache.
154    pub entries: usize,
155    /// Memory usage in bytes (if available).
156    pub memory_bytes: Option<usize>,
157}
158
159impl Default for CacheStats {
160    fn default() -> Self {
161        Self {
162            hits: 0,
163            misses: 0,
164            writes: 0,
165            deletes: 0,
166            errors: 0,
167            hit_rate: 0.0,
168            avg_hit_time: Duration::ZERO,
169            avg_miss_time: Duration::ZERO,
170            avg_write_time: Duration::ZERO,
171            uptime: Duration::ZERO,
172            entries: 0,
173            memory_bytes: None,
174        }
175    }
176}
177
178impl CacheStats {
179    /// Total number of operations.
180    pub fn total_ops(&self) -> u64 {
181        self.hits + self.misses + self.writes + self.deletes
182    }
183
184    /// Requests per second.
185    pub fn ops_per_second(&self) -> f64 {
186        if self.uptime.as_secs_f64() > 0.0 {
187            self.total_ops() as f64 / self.uptime.as_secs_f64()
188        } else {
189            0.0
190        }
191    }
192
193    /// Format as a human-readable string.
194    pub fn summary(&self) -> String {
195        format!(
196            "Cache Stats: {} hits, {} misses ({:.1}% hit rate), {} entries, uptime {:?}",
197            self.hits,
198            self.misses,
199            self.hit_rate * 100.0,
200            self.entries,
201            self.uptime
202        )
203    }
204}
205
206impl std::fmt::Display for CacheStats {
207    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
208        write!(f, "{}", self.summary())
209    }
210}
211
212#[cfg(test)]
213mod tests {
214    use super::*;
215
216    #[test]
217    fn test_metrics_recording() {
218        let metrics = CacheMetrics::new();
219
220        metrics.record_hit(Duration::from_micros(100));
221        metrics.record_hit(Duration::from_micros(200));
222        metrics.record_miss(Duration::from_micros(500));
223
224        let stats = metrics.snapshot();
225        assert_eq!(stats.hits, 2);
226        assert_eq!(stats.misses, 1);
227        assert!((stats.hit_rate - 0.666).abs() < 0.01);
228    }
229
230    #[test]
231    fn test_stats_ops_per_second() {
232        let stats = CacheStats {
233            hits: 1000,
234            misses: 100,
235            writes: 50,
236            deletes: 10,
237            uptime: Duration::from_secs(10),
238            ..Default::default()
239        };
240
241        assert_eq!(stats.total_ops(), 1160);
242        assert!((stats.ops_per_second() - 116.0).abs() < 0.1);
243    }
244}