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 -= evicted_size;
112    }
113
114    /// Records an insertion - when new data is written to the cache
115    ///
116    /// This increases the current cache size and tracks bytes written to cache.
117    ///
118    /// # Arguments
119    /// * `object_size` - Size of the object being inserted (in bytes)
120    pub fn record_insertion(&mut self, object_size: u64) {
121        self.cache_size_bytes += object_size;
122        self.bytes_written_to_cache += object_size;
123    }
124
125    /// Records a size change for an existing cache entry
126    ///
127    /// This adjusts the current cache size when an existing entry's size changes.
128    ///
129    /// # Arguments
130    /// * `old_size` - Previous size of the object (in bytes)
131    /// * `new_size` - New size of the object (in bytes)
132    pub fn record_size_change(&mut self, old_size: u64, new_size: u64) {
133        self.cache_size_bytes = self.cache_size_bytes - old_size + new_size;
134    }
135
136    /// Calculates the cache hit rate as a percentage
137    ///
138    /// # Returns
139    /// A value between 0.0 and 1.0 representing the hit rate, or 0.0 if no requests have been made
140    pub fn hit_rate(&self) -> f64 {
141        if self.requests > 0 {
142            self.cache_hits as f64 / self.requests as f64
143        } else {
144            0.0
145        }
146    }
147
148    /// Calculates the cache miss rate as a percentage
149    ///
150    /// # Returns
151    /// A value between 0.0 and 1.0 representing the miss rate, or 0.0 if no requests have been made
152    pub fn miss_rate(&self) -> f64 {
153        if self.requests > 0 {
154            (self.requests - self.cache_hits) as f64 / self.requests as f64
155        } else {
156            0.0
157        }
158    }
159
160    /// Calculates the byte hit rate - ratio of bytes served from cache vs total bytes requested
161    ///
162    /// This metric shows how much of the requested data volume was served from cache.
163    ///
164    /// # Returns
165    /// A value between 0.0 and 1.0 representing the byte hit rate, or 0.0 if no bytes have been requested
166    pub fn byte_hit_rate(&self) -> f64 {
167        if self.total_bytes_requested > 0 {
168            self.bytes_served_from_cache as f64 / self.total_bytes_requested as f64
169        } else {
170            0.0
171        }
172    }
173
174    /// Calculates cache utilization - how full the cache is relative to its maximum capacity
175    ///
176    /// # Returns
177    /// A value between 0.0 and 1.0 representing cache utilization, or 0.0 if max capacity is 0
178    pub fn cache_utilization(&self) -> f64 {
179        if self.max_cache_size_bytes > 0 {
180            self.cache_size_bytes as f64 / self.max_cache_size_bytes as f64
181        } else {
182            0.0
183        }
184    }
185
186    /// Convert core metrics to BTreeMap for reporting
187    ///
188    /// Uses BTreeMap to ensure deterministic, consistent ordering of metrics
189    /// which is critical for reproducible testing and comparison results.
190    ///
191    /// # Returns
192    /// A BTreeMap containing all core metrics with consistent key ordering
193    pub fn to_btreemap(&self) -> BTreeMap<String, f64> {
194        let mut metrics = BTreeMap::new();
195
196        // Basic counters (alphabetical order for consistency)
197        metrics.insert("cache_hits".to_string(), self.cache_hits as f64);
198        metrics.insert("evictions".to_string(), self.evictions as f64);
199        metrics.insert("requests".to_string(), self.requests as f64);
200
201        // Calculated metrics
202        metrics.insert(
203            "cache_misses".to_string(),
204            (self.requests - self.cache_hits) as f64,
205        );
206
207        // Rates (0.0 to 1.0)
208        metrics.insert("hit_rate".to_string(), self.hit_rate());
209        metrics.insert("miss_rate".to_string(), self.miss_rate());
210        metrics.insert("byte_hit_rate".to_string(), self.byte_hit_rate());
211
212        // Bytes
213        metrics.insert(
214            "bytes_served_from_cache".to_string(),
215            self.bytes_served_from_cache as f64,
216        );
217        metrics.insert(
218            "bytes_written_to_cache".to_string(),
219            self.bytes_written_to_cache as f64,
220        );
221        metrics.insert(
222            "total_bytes_requested".to_string(),
223            self.total_bytes_requested as f64,
224        );
225
226        // Size and utilization
227        metrics.insert("cache_size_bytes".to_string(), self.cache_size_bytes as f64);
228        metrics.insert(
229            "max_cache_size_bytes".to_string(),
230            self.max_cache_size_bytes as f64,
231        );
232        metrics.insert("cache_utilization".to_string(), self.cache_utilization());
233
234        // Derived metrics
235        if self.requests > 0 {
236            metrics.insert(
237                "avg_object_size".to_string(),
238                self.total_bytes_requested as f64 / self.requests as f64,
239            );
240            metrics.insert(
241                "eviction_rate".to_string(),
242                self.evictions as f64 / self.requests as f64,
243            );
244        }
245
246        metrics
247    }
248}
249
250/// Trait that all cache algorithms must implement for metrics reporting
251///
252/// This trait provides a uniform interface for retrieving metrics from any cache implementation.
253/// It allows the simulation system to collect and compare metrics across different cache algorithms.
254///
255/// The trait uses BTreeMap to ensure deterministic ordering of metrics, which is essential
256/// for reproducible benchmarks and consistent test results.
257pub trait CacheMetrics {
258    /// Returns all metrics as key-value pairs in deterministic order
259    ///
260    /// The returned BTreeMap contains all relevant metrics for the cache algorithm,
261    /// including both core metrics and any algorithm-specific metrics.
262    /// Keys are sorted alphabetically for consistent output.
263    ///
264    /// # Returns
265    /// A BTreeMap where keys are metric names and values are metric values as f64
266    fn metrics(&self) -> BTreeMap<String, f64>;
267
268    /// Algorithm name for identification
269    ///
270    /// # Returns
271    /// A static string identifying the cache algorithm (e.g., "LRU", "LFU", "SLRU")
272    fn algorithm_name(&self) -> &'static str;
273}