cache_rs/metrics/lfuda.rs
1//! LFUDA Cache Metrics
2//!
3//! Metrics specific to the LFUDA (Least Frequently Used with Dynamic Aging) cache algorithm.
4
5extern crate alloc;
6
7use super::{CacheMetrics, CoreCacheMetrics};
8use alloc::collections::BTreeMap;
9use alloc::string::{String, ToString};
10
11/// LFUDA-specific metrics (extends CoreCacheMetrics)
12///
13/// This struct contains metrics specific to the LFUDA (Least Frequently Used with Dynamic Aging)
14/// cache algorithm. LFUDA combines frequency tracking with aging to prevent old frequently-used
15/// items from blocking new items indefinitely.
16#[derive(Debug, Clone)]
17pub struct LfudaCacheMetrics {
18 /// Core metrics common to all cache algorithms
19 pub core: CoreCacheMetrics,
20
21 /// Current global age value
22 pub global_age: u64,
23
24 /// Total number of aging events (when global age is increased)
25 pub total_aging_events: u64,
26
27 /// Current minimum priority in the cache
28 pub min_priority: u64,
29
30 /// Current maximum priority in the cache
31 pub max_priority: u64,
32
33 /// Total number of frequency increments (every cache hit increases frequency)
34 pub total_frequency_increments: u64,
35
36 /// Number of items that benefited from aging (had their effective priority boosted)
37 pub items_benefited_from_aging: u64,
38
39 /// Total age value distributed to items at insertion time
40 pub total_age_distributed: u64,
41}
42
43impl LfudaCacheMetrics {
44 /// Creates a new LfudaCacheMetrics instance with the specified maximum cache size
45 ///
46 /// # Arguments
47 /// * `max_cache_size_bytes` - The maximum allowed cache size in bytes
48 pub fn new(max_cache_size_bytes: u64) -> Self {
49 Self {
50 core: CoreCacheMetrics::new(max_cache_size_bytes),
51 global_age: 0,
52 total_aging_events: 0,
53 min_priority: 0,
54 max_priority: 0,
55 total_frequency_increments: 0,
56 items_benefited_from_aging: 0,
57 total_age_distributed: 0,
58 }
59 }
60
61 /// Records an aging event (when global age is increased due to eviction)
62 ///
63 /// # Arguments
64 /// * `new_global_age` - The new global age value
65 pub fn record_aging_event(&mut self, new_global_age: u64) {
66 self.total_aging_events += 1;
67 self.global_age = new_global_age;
68 }
69
70 /// Records a frequency increment (when an item is accessed and its frequency increases)
71 ///
72 /// # Arguments
73 /// * `new_priority` - The new priority value for the accessed item
74 pub fn record_frequency_increment(&mut self, new_priority: u64) {
75 self.total_frequency_increments += 1;
76
77 // Update min/max priority tracking
78 if self.min_priority == 0 || new_priority < self.min_priority {
79 self.min_priority = new_priority;
80 }
81 if new_priority > self.max_priority {
82 self.max_priority = new_priority;
83 }
84 }
85
86 /// Records when an item benefits from aging (gets age boost at insertion)
87 ///
88 /// # Arguments
89 /// * `age_benefit` - The age value added to the item's priority
90 pub fn record_aging_benefit(&mut self, age_benefit: u64) {
91 self.items_benefited_from_aging += 1;
92 self.total_age_distributed += age_benefit;
93 }
94
95 /// Calculates the average aging benefit per item
96 ///
97 /// # Returns
98 /// Average age benefit per item that received aging boost, or 0.0 if none
99 pub fn average_aging_benefit(&self) -> f64 {
100 if self.items_benefited_from_aging > 0 {
101 self.total_age_distributed as f64 / self.items_benefited_from_aging as f64
102 } else {
103 0.0
104 }
105 }
106
107 /// Calculates the priority range (max - min)
108 ///
109 /// # Returns
110 /// The range of priorities currently in the cache
111 pub fn priority_range(&self) -> u64 {
112 self.max_priority.saturating_sub(self.min_priority)
113 }
114
115 /// Calculates the aging effectiveness (aging events / evictions)
116 ///
117 /// # Returns
118 /// How often evictions trigger aging events, or 0.0 if no evictions
119 pub fn aging_effectiveness(&self) -> f64 {
120 if self.core.evictions > 0 {
121 self.total_aging_events as f64 / self.core.evictions as f64
122 } else {
123 0.0
124 }
125 }
126
127 /// Calculates the frequency advantage due to aging
128 ///
129 /// # Returns
130 /// How much of the total priority comes from aging vs raw frequency
131 pub fn aging_contribution_ratio(&self) -> f64 {
132 if self.total_frequency_increments > 0 && self.total_age_distributed > 0 {
133 self.total_age_distributed as f64
134 / (self.total_frequency_increments + self.total_age_distributed) as f64
135 } else {
136 0.0
137 }
138 }
139
140 /// Converts LFUDA metrics to a BTreeMap for reporting
141 ///
142 /// This method returns all metrics relevant to the LFUDA cache algorithm,
143 /// including both core metrics and LFUDA-specific aging and priority metrics.
144 ///
145 /// Uses BTreeMap to ensure consistent, deterministic ordering of metrics.
146 ///
147 /// # Returns
148 /// A BTreeMap containing all LFUDA cache metrics as key-value pairs
149 pub fn to_btreemap(&self) -> BTreeMap<String, f64> {
150 let mut metrics = self.core.to_btreemap();
151
152 // LFUDA-specific aging metrics
153 metrics.insert("global_age".to_string(), self.global_age as f64);
154 metrics.insert(
155 "total_aging_events".to_string(),
156 self.total_aging_events as f64,
157 );
158 metrics.insert(
159 "aging_effectiveness".to_string(),
160 self.aging_effectiveness(),
161 );
162
163 // Priority metrics
164 metrics.insert("min_priority".to_string(), self.min_priority as f64);
165 metrics.insert("max_priority".to_string(), self.max_priority as f64);
166 metrics.insert("priority_range".to_string(), self.priority_range() as f64);
167
168 // Frequency metrics
169 metrics.insert(
170 "total_frequency_increments".to_string(),
171 self.total_frequency_increments as f64,
172 );
173
174 // Aging benefit metrics
175 metrics.insert(
176 "items_benefited_from_aging".to_string(),
177 self.items_benefited_from_aging as f64,
178 );
179 metrics.insert(
180 "total_age_distributed".to_string(),
181 self.total_age_distributed as f64,
182 );
183 metrics.insert(
184 "average_aging_benefit".to_string(),
185 self.average_aging_benefit(),
186 );
187 metrics.insert(
188 "aging_contribution_ratio".to_string(),
189 self.aging_contribution_ratio(),
190 );
191
192 // Rate metrics
193 if self.core.requests > 0 {
194 metrics.insert(
195 "aging_event_rate".to_string(),
196 self.total_aging_events as f64 / self.core.requests as f64,
197 );
198 metrics.insert(
199 "frequency_increment_rate".to_string(),
200 self.total_frequency_increments as f64 / self.core.requests as f64,
201 );
202 metrics.insert(
203 "aging_benefit_rate".to_string(),
204 self.items_benefited_from_aging as f64 / self.core.requests as f64,
205 );
206 }
207
208 metrics
209 }
210}
211
212impl CacheMetrics for LfudaCacheMetrics {
213 /// Returns all LFUDA cache metrics as key-value pairs in deterministic order
214 ///
215 /// # Returns
216 /// A BTreeMap containing all metrics tracked by this LFUDA cache instance
217 fn metrics(&self) -> BTreeMap<String, f64> {
218 self.to_btreemap()
219 }
220
221 /// Returns the algorithm name for this cache implementation
222 ///
223 /// # Returns
224 /// "LFUDA" - identifying this as a Least Frequently Used with Dynamic Aging cache
225 fn algorithm_name(&self) -> &'static str {
226 "LFUDA"
227 }
228}