Skip to main content

xds_cache/
stats.rs

1//! Cache statistics and metrics.
2
3use std::sync::atomic::{AtomicU64, Ordering};
4
5/// Statistics for cache operations.
6///
7/// All counters are atomic and can be safely accessed from multiple threads.
8#[derive(Debug, Default)]
9pub struct CacheStats {
10    /// Number of snapshot sets.
11    snapshots_set: AtomicU64,
12    /// Number of snapshot gets (hits).
13    snapshot_hits: AtomicU64,
14    /// Number of snapshot gets (misses).
15    snapshot_misses: AtomicU64,
16    /// Number of snapshot clears.
17    snapshots_cleared: AtomicU64,
18    /// Number of watch notifications sent.
19    notifications_sent: AtomicU64,
20}
21
22impl CacheStats {
23    /// Create new cache statistics.
24    pub fn new() -> Self {
25        Self::default()
26    }
27
28    /// Record a snapshot set operation.
29    #[inline]
30    pub fn record_set(&self) {
31        self.snapshots_set.fetch_add(1, Ordering::Relaxed);
32    }
33
34    /// Record a snapshot hit.
35    #[inline]
36    pub fn record_hit(&self) {
37        self.snapshot_hits.fetch_add(1, Ordering::Relaxed);
38    }
39
40    /// Record a snapshot miss.
41    #[inline]
42    pub fn record_miss(&self) {
43        self.snapshot_misses.fetch_add(1, Ordering::Relaxed);
44    }
45
46    /// Record a snapshot clear.
47    #[inline]
48    pub fn record_clear(&self) {
49        self.snapshots_cleared.fetch_add(1, Ordering::Relaxed);
50    }
51
52    /// Record notifications sent.
53    #[inline]
54    pub fn record_notifications(&self, count: u64) {
55        self.notifications_sent.fetch_add(count, Ordering::Relaxed);
56    }
57
58    /// Get total snapshots set.
59    #[inline]
60    pub fn snapshots_set(&self) -> u64 {
61        self.snapshots_set.load(Ordering::Relaxed)
62    }
63
64    /// Get total snapshot hits.
65    #[inline]
66    pub fn snapshot_hits(&self) -> u64 {
67        self.snapshot_hits.load(Ordering::Relaxed)
68    }
69
70    /// Get total snapshot misses.
71    #[inline]
72    pub fn snapshot_misses(&self) -> u64 {
73        self.snapshot_misses.load(Ordering::Relaxed)
74    }
75
76    /// Get total snapshots cleared.
77    #[inline]
78    pub fn snapshots_cleared(&self) -> u64 {
79        self.snapshots_cleared.load(Ordering::Relaxed)
80    }
81
82    /// Get total notifications sent.
83    #[inline]
84    pub fn notifications_sent(&self) -> u64 {
85        self.notifications_sent.load(Ordering::Relaxed)
86    }
87
88    /// Calculate hit rate (0.0 to 1.0).
89    pub fn hit_rate(&self) -> f64 {
90        let hits = self.snapshot_hits() as f64;
91        let total = hits + self.snapshot_misses() as f64;
92        if total == 0.0 {
93            0.0
94        } else {
95            hits / total
96        }
97    }
98
99    /// Reset all statistics.
100    pub fn reset(&self) {
101        self.snapshots_set.store(0, Ordering::Relaxed);
102        self.snapshot_hits.store(0, Ordering::Relaxed);
103        self.snapshot_misses.store(0, Ordering::Relaxed);
104        self.snapshots_cleared.store(0, Ordering::Relaxed);
105        self.notifications_sent.store(0, Ordering::Relaxed);
106    }
107}
108
109#[cfg(test)]
110mod tests {
111    use super::*;
112
113    #[test]
114    fn cache_stats_basic() {
115        let stats = CacheStats::new();
116
117        stats.record_set();
118        stats.record_hit();
119        stats.record_hit();
120        stats.record_miss();
121
122        assert_eq!(stats.snapshots_set(), 1);
123        assert_eq!(stats.snapshot_hits(), 2);
124        assert_eq!(stats.snapshot_misses(), 1);
125        // 2 hits / 3 total = 2/3 ≈ 0.6667
126        let expected = 2.0_f64 / 3.0;
127        assert!((stats.hit_rate() - expected).abs() < f64::EPSILON * 10.0,
128            "hit_rate {} should be approximately {}", stats.hit_rate(), expected);
129    }
130
131    #[test]
132    fn cache_stats_reset() {
133        let stats = CacheStats::new();
134        stats.record_set();
135        stats.reset();
136        assert_eq!(stats.snapshots_set(), 0);
137    }
138}