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}