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}