ferrous_forge/performance/
cache.rs1use dashmap::DashMap;
4use std::hash::Hash;
5use std::sync::Arc;
6use std::time::{Duration, Instant};
7use tracing::{debug, trace};
8
9#[derive(Debug, Clone)]
11struct CacheEntry<T> {
12 value: T,
13 expires_at: Instant,
14}
15
16impl<T> CacheEntry<T> {
17 fn new(value: T, ttl: Duration) -> Self {
18 Self {
19 value,
20 expires_at: Instant::now() + ttl,
21 }
22 }
23
24 fn is_expired(&self) -> bool {
25 Instant::now() >= self.expires_at
26 }
27}
28
29pub struct Cache<K, V>
31where
32 K: Eq + Hash,
33{
34 storage: Arc<DashMap<K, CacheEntry<V>>>,
35 ttl: Duration,
36 hits: Arc<std::sync::atomic::AtomicU64>,
37 misses: Arc<std::sync::atomic::AtomicU64>,
38}
39
40impl<K, V> Cache<K, V>
41where
42 K: Eq + Hash + Clone,
43 V: Clone,
44{
45 pub fn new(ttl: Duration) -> Self {
47 Self {
48 storage: Arc::new(DashMap::new()),
49 ttl,
50 hits: Arc::new(std::sync::atomic::AtomicU64::new(0)),
51 misses: Arc::new(std::sync::atomic::AtomicU64::new(0)),
52 }
53 }
54
55 pub fn get(&self, key: &K) -> Option<V> {
57 if let Some(entry) = self.storage.get(key) {
58 if !entry.is_expired() {
59 self.hits.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
60 trace!("Cache hit for key");
61 return Some(entry.value.clone());
62 } else {
63 drop(entry);
65 self.storage.remove(key);
66 debug!("Cache entry expired, removing");
67 }
68 }
69
70 self.misses
71 .fetch_add(1, std::sync::atomic::Ordering::Relaxed);
72 trace!("Cache miss for key");
73 None
74 }
75
76 pub fn insert(&self, key: K, value: V) {
78 let entry = CacheEntry::new(value, self.ttl);
79 self.storage.insert(key, entry);
80 trace!("Cached value inserted");
81 }
82
83 pub fn clear(&self) {
85 self.storage.clear();
86 debug!("Cache cleared");
87 }
88
89 pub fn evict_expired(&self) {
91 let now = Instant::now();
92 self.storage.retain(|_, entry| entry.expires_at > now);
93 debug!("Evicted expired cache entries");
94 }
95
96 pub fn hit_rate(&self) -> f64 {
98 let hits = self.hits.load(std::sync::atomic::Ordering::Relaxed);
99 let misses = self.misses.load(std::sync::atomic::Ordering::Relaxed);
100 let total = hits + misses;
101
102 if total > 0 {
103 hits as f64 / total as f64
104 } else {
105 0.0
106 }
107 }
108
109 pub fn stats(&self) -> CacheStats {
111 CacheStats {
112 entries: self.storage.len(),
113 hits: self.hits.load(std::sync::atomic::Ordering::Relaxed),
114 misses: self.misses.load(std::sync::atomic::Ordering::Relaxed),
115 hit_rate: self.hit_rate(),
116 }
117 }
118}
119
120#[derive(Debug, Clone)]
122pub struct CacheStats {
123 pub entries: usize,
125 pub hits: u64,
127 pub misses: u64,
129 pub hit_rate: f64,
131}
132
133pub struct ValidationCache {
135 cache: Cache<String, Vec<crate::validation::Violation>>,
136}
137
138impl ValidationCache {
139 pub fn new(ttl: Duration) -> Self {
141 Self {
142 cache: Cache::new(ttl),
143 }
144 }
145
146 pub fn get_results(&self, file_path: &str) -> Option<Vec<crate::validation::Violation>> {
148 self.cache.get(&file_path.to_string())
149 }
150
151 pub fn cache_results(&self, file_path: String, violations: Vec<crate::validation::Violation>) {
153 self.cache.insert(file_path, violations);
154 }
155
156 pub fn stats(&self) -> CacheStats {
158 self.cache.stats()
159 }
160
161 pub fn clear(&self) {
163 self.cache.clear();
164 }
165}
166
167pub struct FileCache {
169 cache: Cache<std::path::PathBuf, String>,
170}
171
172impl FileCache {
173 pub fn new(ttl: Duration) -> Self {
175 Self {
176 cache: Cache::new(ttl),
177 }
178 }
179
180 pub fn get_content(&self, path: &std::path::Path) -> Option<String> {
182 self.cache.get(&path.to_path_buf())
183 }
184
185 pub fn cache_content(&self, path: std::path::PathBuf, content: String) {
187 self.cache.insert(path, content);
188 }
189
190 pub fn stats(&self) -> CacheStats {
192 self.cache.stats()
193 }
194}
195
196#[cfg(test)]
197mod tests {
198 use super::*;
199
200 #[test]
201 fn test_cache_basic_operations() {
202 let cache = Cache::<String, String>::new(Duration::from_secs(60));
203
204 cache.insert("key1".to_string(), "value1".to_string());
205 assert_eq!(cache.get(&"key1".to_string()), Some("value1".to_string()));
206 assert_eq!(cache.get(&"key2".to_string()), None);
207 }
208
209 #[test]
210 fn test_cache_expiration() {
211 let cache = Cache::<String, String>::new(Duration::from_millis(1));
212
213 cache.insert("key1".to_string(), "value1".to_string());
214 std::thread::sleep(Duration::from_millis(2));
215 assert_eq!(cache.get(&"key1".to_string()), None);
216 }
217
218 #[test]
219 fn test_cache_statistics() {
220 let cache = Cache::<String, String>::new(Duration::from_secs(60));
221
222 cache.insert("key1".to_string(), "value1".to_string());
223 let _ = cache.get(&"key1".to_string()); let _ = cache.get(&"key2".to_string()); let stats = cache.stats();
227 assert_eq!(stats.entries, 1);
228 assert_eq!(stats.hits, 1);
229 assert_eq!(stats.misses, 1);
230 assert_eq!(stats.hit_rate, 0.5);
231 }
232
233 #[test]
234 fn test_validation_cache() {
235 let cache = ValidationCache::new(Duration::from_secs(60));
236 let violations = vec![];
237
238 cache.cache_results("test.rs".to_string(), violations.clone());
239 assert_eq!(cache.get_results("test.rs"), Some(violations));
240 }
241}