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