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}