Skip to main content

cache_rs/metrics/
mod.rs

1//! Cache Metrics System
2//!
3//! Provides a flexible metrics system for cache algorithms using BTreeMap-based
4//! metrics reporting. Each cache algorithm can track its own specific metrics
5//! while implementing a common CacheMetrics trait.
6//!
7//! # Why BTreeMap over HashMap?
8//!
9//! BTreeMap is used instead of HashMap for several critical reasons:
10//! - **Deterministic ordering**: Metrics always appear in consistent order
11//! - **Reproducible output**: Essential for testing and benchmarking comparisons
12//! - **Stable serialization**: JSON/CSV exports have predictable key ordering
13//! - **Better debugging**: Consistent output makes logs more readable
14//!
15//! The performance difference (O(log n) vs O(1)) is negligible with ~15 metric keys,
16//! but the deterministic behavior is invaluable for a simulation system.
17
18extern crate alloc;
19
20use alloc::collections::BTreeMap;
21use alloc::string::{String, ToString};
22
23// Re-export algorithm-specific metrics
24pub mod gdsf;
25pub mod lfu;
26pub mod lfuda;
27pub mod lru;
28pub mod slru;
29
30pub use gdsf::GdsfCacheMetrics;
31pub use lfu::LfuCacheMetrics;
32pub use lfuda::LfudaCacheMetrics;
33pub use lru::LruCacheMetrics;
34pub use slru::SlruCacheMetrics;
35
36/// Common metrics tracked by all cache algorithms
37#[derive(Debug, Default, Clone)]
38pub struct CoreCacheMetrics {
39    /// Total number of requests (gets) made to the cache
40    pub requests: u64,
41
42    /// Number of requests that resulted in cache hits
43    pub cache_hits: u64,
44
45    /// Total bytes of data requested from the cache (hits + misses)
46    pub total_bytes_requested: u64,
47
48    /// Total bytes served directly from cache (cache hits only)
49    pub bytes_served_from_cache: u64,
50
51    /// Total bytes written/stored into the cache
52    pub bytes_written_to_cache: u64,
53
54    /// Number of items evicted from the cache due to capacity constraints
55    pub evictions: u64,
56
57    /// Current size of data stored in the cache (in bytes)
58    pub cache_size_bytes: u64,
59
60    /// Maximum allowed cache size (in bytes) - the capacity limit
61    pub max_cache_size_bytes: u64,
62}
63
64impl CoreCacheMetrics {
65    /// Creates a new CoreCacheMetrics instance with the specified maximum cache size
66    ///
67    /// # Arguments
68    /// * `max_cache_size_bytes` - The maximum allowed cache size in bytes
69    pub fn new(max_cache_size_bytes: u64) -> Self {
70        Self {
71            max_cache_size_bytes,
72            ..Default::default()
73        }
74    }
75
76    /// Records a cache hit - when requested data was found in the cache
77    ///
78    /// This increments total requests, cache hits, total bytes requested,
79    /// and bytes served from cache.
80    ///
81    /// # Arguments
82    /// * `object_size` - Size of the object that was served from cache (in bytes)
83    pub fn record_hit(&mut self, object_size: u64) {
84        self.requests += 1;
85        self.cache_hits += 1;
86        self.total_bytes_requested += object_size;
87        self.bytes_served_from_cache += object_size;
88    }
89
90    /// Records a cache miss - when requested data was not found in the cache
91    ///
92    /// This increments total requests and total bytes requested.
93    /// Cache misses are calculated as (requests - cache_hits).
94    ///
95    /// # Arguments
96    /// * `object_size` - Size of the object that was requested but not in cache (in bytes)
97    pub fn record_miss(&mut self, object_size: u64) {
98        self.requests += 1;
99        self.total_bytes_requested += object_size;
100        // Note: cache misses can be calculated as (requests - cache_hits)
101    }
102
103    /// Records an eviction - when an item is removed from cache due to capacity constraints
104    ///
105    /// This increments the eviction counter and decreases the current cache size.
106    ///
107    /// # Arguments
108    /// * `evicted_size` - Size of the evicted object (in bytes)
109    pub fn record_eviction(&mut self, evicted_size: u64) {
110        self.evictions += 1;
111        self.cache_size_bytes = self.cache_size_bytes.saturating_sub(evicted_size);
112    }
113
114    /// Records a user-initiated removal — when an item is explicitly removed via `remove()`.
115    ///
116    /// Unlike [`record_eviction()`](Self::record_eviction), this does **not** increment the eviction counter.
117    /// Use this for operations where the user explicitly removes an entry rather than the cache
118    /// evicting it to make room.
119    ///
120    /// # Arguments
121    /// * `removed_size` - Size of the removed object (in bytes)
122    pub fn record_removal(&mut self, removed_size: u64) {
123        self.cache_size_bytes = self.cache_size_bytes.saturating_sub(removed_size);
124    }
125
126    /// Records an insertion - when new data is written to the cache
127    ///
128    /// This increases the current cache size and tracks bytes written to cache.
129    ///
130    /// # Arguments
131    /// * `object_size` - Size of the object being inserted (in bytes)
132    pub fn record_insertion(&mut self, object_size: u64) {
133        self.cache_size_bytes += object_size;
134        self.bytes_written_to_cache += object_size;
135    }
136
137    /// Records a size change for an existing cache entry
138    ///
139    /// This adjusts the current cache size when an existing entry's size changes.
140    ///
141    /// # Arguments
142    /// * `old_size` - Previous size of the object (in bytes)
143    /// * `new_size` - New size of the object (in bytes)
144    pub fn record_size_change(&mut self, old_size: u64, new_size: u64) {
145        self.cache_size_bytes = self.cache_size_bytes - old_size + new_size;
146    }
147
148    /// Calculates the cache hit rate as a percentage
149    ///
150    /// # Returns
151    /// A value between 0.0 and 1.0 representing the hit rate, or 0.0 if no requests have been made
152    pub fn hit_rate(&self) -> f64 {
153        if self.requests > 0 {
154            self.cache_hits as f64 / self.requests as f64
155        } else {
156            0.0
157        }
158    }
159
160    /// Calculates the cache miss rate as a percentage
161    ///
162    /// # Returns
163    /// A value between 0.0 and 1.0 representing the miss rate, or 0.0 if no requests have been made
164    pub fn miss_rate(&self) -> f64 {
165        if self.requests > 0 {
166            (self.requests - self.cache_hits) as f64 / self.requests as f64
167        } else {
168            0.0
169        }
170    }
171
172    /// Calculates the byte hit rate - ratio of bytes served from cache vs total bytes requested
173    ///
174    /// This metric shows how much of the requested data volume was served from cache.
175    ///
176    /// # Returns
177    /// A value between 0.0 and 1.0 representing the byte hit rate, or 0.0 if no bytes have been requested
178    pub fn byte_hit_rate(&self) -> f64 {
179        if self.total_bytes_requested > 0 {
180            self.bytes_served_from_cache as f64 / self.total_bytes_requested as f64
181        } else {
182            0.0
183        }
184    }
185
186    /// Calculates cache utilization - how full the cache is relative to its maximum capacity
187    ///
188    /// # Returns
189    /// A value between 0.0 and 1.0 representing cache utilization, or 0.0 if max capacity is 0
190    pub fn cache_utilization(&self) -> f64 {
191        if self.max_cache_size_bytes > 0 {
192            self.cache_size_bytes as f64 / self.max_cache_size_bytes as f64
193        } else {
194            0.0
195        }
196    }
197
198    /// Convert core metrics to BTreeMap for reporting
199    ///
200    /// Uses BTreeMap to ensure deterministic, consistent ordering of metrics
201    /// which is critical for reproducible testing and comparison results.
202    ///
203    /// # Returns
204    /// A BTreeMap containing all core metrics with consistent key ordering
205    pub fn to_btreemap(&self) -> BTreeMap<String, f64> {
206        let mut metrics = BTreeMap::new();
207
208        // Basic counters (alphabetical order for consistency)
209        metrics.insert("cache_hits".to_string(), self.cache_hits as f64);
210        metrics.insert("evictions".to_string(), self.evictions as f64);
211        metrics.insert("requests".to_string(), self.requests as f64);
212
213        // Calculated metrics
214        metrics.insert(
215            "cache_misses".to_string(),
216            (self.requests - self.cache_hits) as f64,
217        );
218
219        // Rates (0.0 to 1.0)
220        metrics.insert("hit_rate".to_string(), self.hit_rate());
221        metrics.insert("miss_rate".to_string(), self.miss_rate());
222        metrics.insert("byte_hit_rate".to_string(), self.byte_hit_rate());
223
224        // Bytes
225        metrics.insert(
226            "bytes_served_from_cache".to_string(),
227            self.bytes_served_from_cache as f64,
228        );
229        metrics.insert(
230            "bytes_written_to_cache".to_string(),
231            self.bytes_written_to_cache as f64,
232        );
233        metrics.insert(
234            "total_bytes_requested".to_string(),
235            self.total_bytes_requested as f64,
236        );
237
238        // Size and utilization
239        metrics.insert("cache_size_bytes".to_string(), self.cache_size_bytes as f64);
240        metrics.insert(
241            "max_cache_size_bytes".to_string(),
242            self.max_cache_size_bytes as f64,
243        );
244        metrics.insert("cache_utilization".to_string(), self.cache_utilization());
245
246        // Derived metrics
247        if self.requests > 0 {
248            metrics.insert(
249                "avg_object_size".to_string(),
250                self.total_bytes_requested as f64 / self.requests as f64,
251            );
252            metrics.insert(
253                "eviction_rate".to_string(),
254                self.evictions as f64 / self.requests as f64,
255            );
256        }
257
258        metrics
259    }
260}
261
262/// Trait that all cache algorithms must implement for metrics reporting
263///
264/// This trait provides a uniform interface for retrieving metrics from any cache implementation.
265/// It allows the simulation system to collect and compare metrics across different cache algorithms.
266///
267/// The trait uses BTreeMap to ensure deterministic ordering of metrics, which is essential
268/// for reproducible benchmarks and consistent test results.
269pub trait CacheMetrics {
270    /// Returns all metrics as key-value pairs in deterministic order
271    ///
272    /// The returned BTreeMap contains all relevant metrics for the cache algorithm,
273    /// including both core metrics and any algorithm-specific metrics.
274    /// Keys are sorted alphabetically for consistent output.
275    ///
276    /// # Returns
277    /// A BTreeMap where keys are metric names and values are metric values as f64
278    fn metrics(&self) -> BTreeMap<String, f64>;
279
280    /// Algorithm name for identification
281    ///
282    /// # Returns
283    /// A static string identifying the cache algorithm (e.g., "LRU", "LFU", "SLRU")
284    fn algorithm_name(&self) -> &'static str;
285}