Skip to main content

iqdb_cache/
stats.rs

1//! Cache hit/miss accounting.
2
3/// A point-in-time snapshot of a [`CachedIndex`](crate::CachedIndex)'s cache.
4///
5/// Returned by [`CachedIndex::cache_stats`](crate::CachedIndex::cache_stats).
6/// `hits` and `misses` are monotonic counters over the cache's lifetime;
7/// `len` and `capacity` describe its current occupancy. Use
8/// [`hit_rate`](CacheStats::hit_rate) to turn the counters into a ratio for
9/// tuning.
10///
11/// # Examples
12///
13/// ```
14/// use iqdb_cache::CacheStats;
15///
16/// let stats = CacheStats {
17///     hits: 75,
18///     misses: 25,
19///     evictions: 8,
20///     len: 64,
21///     capacity: 128,
22/// };
23/// assert_eq!(stats.lookups(), 100);
24/// assert!((stats.hit_rate() - 0.75).abs() < f64::EPSILON);
25/// ```
26#[derive(Debug, Clone, Copy, PartialEq, Eq)]
27#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
28pub struct CacheStats {
29    /// Lookups served from the cache.
30    pub hits: u64,
31    /// Lookups that missed and fell through to the wrapped index.
32    pub misses: u64,
33    /// Entries discarded by the eviction policy to make room over the cache's
34    /// lifetime.
35    pub evictions: u64,
36    /// Entries currently held.
37    pub len: usize,
38    /// Maximum entries the cache will hold; `0` means caching is disabled.
39    pub capacity: usize,
40}
41
42impl CacheStats {
43    /// Total lookups observed: `hits + misses` (saturating).
44    ///
45    /// # Examples
46    ///
47    /// ```
48    /// use iqdb_cache::CacheStats;
49    ///
50    /// let stats = CacheStats { hits: 3, misses: 1, evictions: 0, len: 4, capacity: 8 };
51    /// assert_eq!(stats.lookups(), 4);
52    /// ```
53    #[inline]
54    #[must_use]
55    pub fn lookups(&self) -> u64 {
56        self.hits.saturating_add(self.misses)
57    }
58
59    /// The fraction of lookups served from cache, in `0.0..=1.0`.
60    ///
61    /// Returns `0.0` when there have been no lookups, so the result is always
62    /// finite and safe to display.
63    ///
64    /// # Examples
65    ///
66    /// ```
67    /// use iqdb_cache::CacheStats;
68    ///
69    /// let warm = CacheStats { hits: 9, misses: 1, evictions: 0, len: 10, capacity: 16 };
70    /// assert!((warm.hit_rate() - 0.9).abs() < 1e-9);
71    ///
72    /// let cold = CacheStats { hits: 0, misses: 0, evictions: 0, len: 0, capacity: 16 };
73    /// assert_eq!(cold.hit_rate(), 0.0);
74    /// ```
75    #[inline]
76    #[must_use]
77    pub fn hit_rate(&self) -> f64 {
78        let total = self.lookups();
79        if total == 0 {
80            0.0
81        } else {
82            self.hits as f64 / total as f64
83        }
84    }
85}