memscope_rs/stack_trace/
cache.rs1use std::collections::HashMap;
2use std::sync::atomic::{AtomicUsize, Ordering};
3
4#[derive(Debug, Clone)]
6pub struct CacheStats {
7 pub total_lookups: usize,
9 pub cache_hits: usize,
11 pub cache_misses: usize,
13 pub cache_size: usize,
15 pub hit_ratio: f64,
17}
18
19pub struct StackTraceCache<T> {
21 cache: HashMap<u64, T>,
23 lookup_count: AtomicUsize,
25 hit_count: AtomicUsize,
27 max_size: usize,
29}
30
31impl<T> StackTraceCache<T> {
32 pub fn new(max_size: usize) -> Self {
34 Self {
35 cache: HashMap::new(),
36 lookup_count: AtomicUsize::new(0),
37 hit_count: AtomicUsize::new(0),
38 max_size,
39 }
40 }
41
42 pub fn get(&mut self, key: u64) -> Option<&T> {
44 self.lookup_count.fetch_add(1, Ordering::Relaxed);
45
46 if let Some(value) = self.cache.get(&key) {
47 self.hit_count.fetch_add(1, Ordering::Relaxed);
48 Some(value)
49 } else {
50 None
51 }
52 }
53
54 pub fn insert(&mut self, key: u64, value: T) {
56 if self.cache.len() >= self.max_size {
58 self.evict_oldest();
59 }
60
61 self.cache.insert(key, value);
62 }
63
64 pub fn clear(&mut self) {
66 self.cache.clear();
67 self.lookup_count.store(0, Ordering::Relaxed);
68 self.hit_count.store(0, Ordering::Relaxed);
69 }
70
71 pub fn stats(&self) -> CacheStats {
73 let lookups = self.lookup_count.load(Ordering::Relaxed);
74 let hits = self.hit_count.load(Ordering::Relaxed);
75 let misses = lookups.saturating_sub(hits);
76 let hit_ratio = if lookups > 0 {
77 hits as f64 / lookups as f64
78 } else {
79 0.0
80 };
81
82 CacheStats {
83 total_lookups: lookups,
84 cache_hits: hits,
85 cache_misses: misses,
86 cache_size: self.cache.len(),
87 hit_ratio,
88 }
89 }
90
91 pub fn size(&self) -> usize {
93 self.cache.len()
94 }
95
96 pub fn is_empty(&self) -> bool {
98 self.cache.is_empty()
99 }
100
101 fn evict_oldest(&mut self) {
103 if let Some(key) = self.cache.keys().next().copied() {
104 self.cache.remove(&key);
105 }
106 }
107}
108
109impl<T> Default for StackTraceCache<T> {
110 fn default() -> Self {
111 Self::new(1000) }
113}
114
115impl CacheStats {
116 pub fn is_performing_well(&self) -> bool {
118 self.hit_ratio >= 0.8 && self.total_lookups > 10
119 }
120
121 pub fn efficiency_description(&self) -> &'static str {
123 match self.hit_ratio {
124 x if x >= 0.9 => "Excellent",
125 x if x >= 0.8 => "Good",
126 x if x >= 0.6 => "Fair",
127 x if x >= 0.4 => "Poor",
128 _ => "Very Poor",
129 }
130 }
131}
132
133#[cfg(test)]
134mod tests {
135 use super::*;
136
137 #[test]
138 fn test_basic_cache_operations() {
139 let mut cache = StackTraceCache::new(3);
140
141 assert!(cache.is_empty());
142 assert_eq!(cache.size(), 0);
143
144 cache.insert(1, "first");
145 cache.insert(2, "second");
146
147 assert_eq!(cache.size(), 2);
148 assert!(!cache.is_empty());
149
150 assert_eq!(cache.get(1), Some(&"first"));
151 assert_eq!(cache.get(2), Some(&"second"));
152 assert_eq!(cache.get(3), None);
153 }
154
155 #[test]
156 fn test_cache_eviction() {
157 let mut cache = StackTraceCache::new(2);
158
159 cache.insert(1, "first");
160 cache.insert(2, "second");
161 assert_eq!(cache.size(), 2);
162
163 cache.insert(3, "third");
165 assert_eq!(cache.size(), 2);
166
167 let get1 = cache.get(1).is_some();
169 let get2 = cache.get(2).is_some();
170 let get3 = cache.get(3).is_some();
171 let remaining_count = [get1, get2, get3].iter().filter(|&&x| x).count();
172 assert_eq!(remaining_count, 2); }
174
175 #[test]
176 fn test_cache_stats() {
177 let mut cache = StackTraceCache::new(10);
178
179 cache.insert(1, "value1");
180 cache.insert(2, "value2");
181
182 cache.get(1); cache.get(2); cache.get(3); cache.get(1); let stats = cache.stats();
189 assert_eq!(stats.total_lookups, 4);
190 assert_eq!(stats.cache_hits, 3);
191 assert_eq!(stats.cache_misses, 1);
192 assert_eq!(stats.cache_size, 2);
193 assert!((stats.hit_ratio - 0.75).abs() < 0.01);
194 }
195
196 #[test]
197 fn test_cache_clear() {
198 let mut cache = StackTraceCache::new(10);
199
200 cache.insert(1, "value");
201 cache.get(1);
202
203 assert!(!cache.is_empty());
204 assert!(cache.stats().total_lookups > 0);
205
206 cache.clear();
207
208 assert!(cache.is_empty());
209 assert_eq!(cache.stats().total_lookups, 0);
210 assert_eq!(cache.stats().cache_hits, 0);
211 }
212
213 #[test]
214 fn test_efficiency_description() {
215 let excellent = CacheStats {
216 hit_ratio: 0.95,
217 total_lookups: 100,
218 cache_hits: 95,
219 cache_misses: 5,
220 cache_size: 50,
221 };
222 assert_eq!(excellent.efficiency_description(), "Excellent");
223
224 let poor = CacheStats {
225 hit_ratio: 0.45, total_lookups: 100,
227 cache_hits: 45,
228 cache_misses: 55,
229 cache_size: 20,
230 };
231 assert_eq!(poor.efficiency_description(), "Poor");
232 }
233}