Skip to main content

ferrous_forge/performance/
cache.rs

1//! Caching strategies for performance optimization
2
3use dashmap::DashMap;
4use std::hash::Hash;
5use std::sync::Arc;
6use std::time::{Duration, Instant};
7use tracing::{debug, trace};
8
9/// Generic cache entry with expiration
10#[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
29/// Thread-safe cache with TTL support
30pub 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    /// Create a new cache with specified TTL
46    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    /// Get a value from the cache
56    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                // Remove expired entry
64                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    /// Insert a value into the cache
77    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    /// Clear all cache entries
84    pub fn clear(&self) {
85        self.storage.clear();
86        debug!("Cache cleared");
87    }
88
89    /// Remove expired entries
90    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    /// Get cache hit rate
97    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    /// Get cache statistics
110    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/// Cache statistics
121#[derive(Debug, Clone)]
122pub struct CacheStats {
123    /// Number of entries in cache
124    pub entries: usize,
125    /// Number of cache hits
126    pub hits: u64,
127    /// Number of cache misses
128    pub misses: u64,
129    /// Hit rate (0.0 - 1.0)
130    pub hit_rate: f64,
131}
132
133/// Validation result cache
134pub struct ValidationCache {
135    cache: Cache<String, Vec<crate::validation::Violation>>,
136}
137
138impl ValidationCache {
139    /// Create a new validation cache
140    pub fn new(ttl: Duration) -> Self {
141        Self {
142            cache: Cache::new(ttl),
143        }
144    }
145
146    /// Get cached validation results
147    pub fn get_results(&self, file_path: &str) -> Option<Vec<crate::validation::Violation>> {
148        self.cache.get(&file_path.to_string())
149    }
150
151    /// Cache validation results
152    pub fn cache_results(&self, file_path: String, violations: Vec<crate::validation::Violation>) {
153        self.cache.insert(file_path, violations);
154    }
155
156    /// Get cache statistics
157    pub fn stats(&self) -> CacheStats {
158        self.cache.stats()
159    }
160
161    /// Clear the cache
162    pub fn clear(&self) {
163        self.cache.clear();
164    }
165}
166
167/// File content cache for lazy parsing
168pub struct FileCache {
169    cache: Cache<std::path::PathBuf, String>,
170}
171
172impl FileCache {
173    /// Create a new file cache
174    pub fn new(ttl: Duration) -> Self {
175        Self {
176            cache: Cache::new(ttl),
177        }
178    }
179
180    /// Get cached file content
181    pub fn get_content(&self, path: &std::path::Path) -> Option<String> {
182        self.cache.get(&path.to_path_buf())
183    }
184
185    /// Cache file content
186    pub fn cache_content(&self, path: std::path::PathBuf, content: String) {
187        self.cache.insert(path, content);
188    }
189
190    /// Get cache statistics
191    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()); // Hit
224        let _ = cache.get(&"key2".to_string()); // Miss
225
226        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}