cache_manager.rs
1 use crate::cache::{CompactionResult, VerificationCache}; 2 use anyhow::Result; 3 use serde::{Deserialize, Serialize}; 4 use std::path::PathBuf; 5 6 /// Cache management operations for CLI and programmatic use 7 pub struct CacheManager { 8 cache: VerificationCache, 9 } 10 11 #[derive(Debug, Clone, Serialize, Deserialize)] 12 pub struct CacheInfo { 13 pub cache_dir: PathBuf, 14 pub total_entries: usize, 15 pub valid_entries: usize, 16 pub expired_entries: usize, 17 pub total_size_bytes: u64, 18 pub disk_size_bytes: u64, 19 } 20 21 #[derive(Debug, Clone, Serialize, Deserialize)] 22 pub struct CacheHealthReport { 23 pub info: CacheInfo, 24 pub integrity_errors: Vec<String>, 25 pub recommendations: Vec<String>, 26 } 27 28 impl CacheManager { 29 /// Create a new cache manager with default cache directory 30 pub fn new() -> Self { 31 Self { 32 cache: VerificationCache::new(), 33 } 34 } 35 36 /// Create a new cache manager with custom cache directory 37 pub fn with_cache_dir(cache_dir: PathBuf) -> Self { 38 Self { 39 cache: VerificationCache::with_cache_dir(cache_dir), 40 } 41 } 42 43 /// Get comprehensive cache information 44 pub fn info(&self) -> Result<CacheInfo> { 45 let stats = self.cache.statistics(); 46 let disk_size = self.cache.disk_size().unwrap_or(0); 47 48 Ok(CacheInfo { 49 cache_dir: stats.cache_dir, 50 total_entries: stats.total_entries, 51 valid_entries: stats.valid_entries, 52 expired_entries: stats.expired_entries, 53 total_size_bytes: stats.total_size_bytes, 54 disk_size_bytes: disk_size, 55 }) 56 } 57 58 /// Clean up expired cache entries 59 pub fn cleanup(&mut self) -> Result<CleanupResult> { 60 let initial_info = self.info()?; 61 let expired_removed = self.cache.cleanup_expired()?; 62 let final_info = self.info()?; 63 64 Ok(CleanupResult { 65 entries_removed: expired_removed, 66 size_freed: initial_info 67 .disk_size_bytes 68 .saturating_sub(final_info.disk_size_bytes), 69 entries_before: initial_info.total_entries, 70 entries_after: final_info.total_entries, 71 }) 72 } 73 74 /// Clear all cache entries 75 pub fn clear(&mut self) -> Result<ClearResult> { 76 let initial_info = self.info()?; 77 self.cache.clear(); 78 79 Ok(ClearResult { 80 entries_removed: initial_info.total_entries, 81 size_freed: initial_info.disk_size_bytes, 82 }) 83 } 84 85 /// Compact cache by removing expired entries and optimizing storage 86 pub fn compact(&mut self) -> Result<CompactionResult> { 87 self.cache.compact() 88 } 89 90 /// Validate cache integrity and provide health report 91 pub fn health_check(&self) -> Result<CacheHealthReport> { 92 let info = self.info()?; 93 let integrity_errors = self.cache.validate_integrity()?; 94 let mut recommendations = Vec::new(); 95 96 // Generate recommendations based on cache state 97 if info.expired_entries > 0 { 98 recommendations.push(format!( 99 "Consider running cleanup to remove {} expired entries", 100 info.expired_entries 101 )); 102 } 103 104 if info.disk_size_bytes > 1_000_000_000 { 105 // > 1GB 106 recommendations.push( 107 "Cache size is large (>1GB). Consider running compact to optimize storage" 108 .to_string(), 109 ); 110 } 111 112 if !integrity_errors.is_empty() { 113 recommendations.push( 114 "Cache integrity issues detected. Consider clearing and rebuilding cache" 115 .to_string(), 116 ); 117 } 118 119 if info.total_entries == 0 { 120 recommendations.push("Cache is empty. No action needed".to_string()); 121 } 122 123 Ok(CacheHealthReport { 124 info, 125 integrity_errors, 126 recommendations, 127 }) 128 } 129 130 /// Repair cache by removing corrupted entries 131 pub fn repair(&mut self) -> Result<RepairResult> { 132 let initial_info = self.info()?; 133 let integrity_errors = self.cache.validate_integrity()?; 134 135 if integrity_errors.is_empty() { 136 return Ok(RepairResult { 137 corrupted_entries_removed: 0, 138 entries_before: initial_info.total_entries, 139 entries_after: initial_info.total_entries, 140 size_freed: 0, 141 }); 142 } 143 144 // For now, we'll clear the entire cache if there are integrity issues 145 // In a more sophisticated implementation, we could selectively remove corrupted entries 146 let clear_result = self.clear()?; 147 148 Ok(RepairResult { 149 corrupted_entries_removed: clear_result.entries_removed, 150 entries_before: initial_info.total_entries, 151 entries_after: 0, 152 size_freed: clear_result.size_freed, 153 }) 154 } 155 156 /// Get cache statistics for monitoring 157 pub fn statistics(&self) -> CacheStatistics { 158 let stats = self.cache.statistics(); 159 let disk_size = self.cache.disk_size().unwrap_or(0); 160 161 CacheStatistics { 162 total_entries: stats.total_entries, 163 valid_entries: stats.valid_entries, 164 expired_entries: stats.expired_entries, 165 memory_size_bytes: stats.total_size_bytes, 166 disk_size_bytes: disk_size, 167 cache_dir: stats.cache_dir, 168 } 169 } 170 171 /// Load cache from disk 172 pub fn load(&mut self) -> Result<()> { 173 self.cache.load_from_disk() 174 } 175 176 /// Save cache to disk 177 pub fn save(&self) -> Result<()> { 178 self.cache.save_to_disk() 179 } 180 181 /// Get the underlying cache for advanced operations 182 pub fn cache(&self) -> &VerificationCache { 183 &self.cache 184 } 185 186 /// Get mutable access to the underlying cache 187 pub fn cache_mut(&mut self) -> &mut VerificationCache { 188 &mut self.cache 189 } 190 } 191 192 #[derive(Debug, Clone, Serialize, Deserialize)] 193 pub struct CleanupResult { 194 pub entries_removed: usize, 195 pub size_freed: u64, 196 pub entries_before: usize, 197 pub entries_after: usize, 198 } 199 200 #[derive(Debug, Clone, Serialize, Deserialize)] 201 pub struct ClearResult { 202 pub entries_removed: usize, 203 pub size_freed: u64, 204 } 205 206 #[derive(Debug, Clone, Serialize, Deserialize)] 207 pub struct RepairResult { 208 pub corrupted_entries_removed: usize, 209 pub entries_before: usize, 210 pub entries_after: usize, 211 pub size_freed: u64, 212 } 213 214 #[derive(Debug, Clone, Serialize, Deserialize)] 215 pub struct CacheStatistics { 216 pub total_entries: usize, 217 pub valid_entries: usize, 218 pub expired_entries: usize, 219 pub memory_size_bytes: u64, 220 pub disk_size_bytes: u64, 221 pub cache_dir: PathBuf, 222 } 223 224 impl Default for CacheManager { 225 fn default() -> Self { 226 Self::new() 227 } 228 } 229 230 #[cfg(test)] 231 mod tests { 232 use super::*; 233 use crate::cache::{ 234 CacheEntry, CacheKey, CacheMetadata, ConfigHash, ContentHash, ToolVersions, 235 }; 236 use crate::types::{Layer, LayerResult, Status}; 237 use std::time::Duration; 238 use tempfile::TempDir; 239 240 #[test] 241 fn test_cache_manager_info() { 242 let temp_dir = TempDir::new().unwrap(); 243 let cache_dir = temp_dir.path().join("cache"); 244 245 let manager = CacheManager::with_cache_dir(cache_dir.clone()); 246 let info = manager.info().unwrap(); 247 248 assert_eq!(info.cache_dir, cache_dir); 249 assert_eq!(info.total_entries, 0); 250 assert_eq!(info.valid_entries, 0); 251 assert_eq!(info.expired_entries, 0); 252 } 253 254 #[test] 255 fn test_cache_manager_cleanup() { 256 let temp_dir = TempDir::new().unwrap(); 257 let cache_dir = temp_dir.path().join("cache"); 258 259 let mut manager = CacheManager::with_cache_dir(cache_dir); 260 261 // Add some test entries (some expired) 262 let cache_key = CacheKey { 263 content_hash: ContentHash("test_hash".to_string()), 264 config_hash: ConfigHash("config_hash".to_string()), 265 tool_versions: ToolVersions { 266 ferris_proof: "0.1.0".to_string(), 267 external_tools: vec![], 268 }, 269 layer: Layer::PropertyBased, 270 }; 271 272 let expired_entry = CacheEntry { 273 result: LayerResult { 274 layer: Layer::PropertyBased, 275 status: Status::Success, 276 violations: vec![], 277 execution_time: Duration::from_millis(100), 278 tool_outputs: vec![], 279 }, 280 timestamp: chrono::Utc::now() - chrono::Duration::seconds(10), 281 ttl: Duration::from_secs(5), // Expired 282 metadata: CacheMetadata { 283 file_size: 1024, 284 execution_time: Duration::from_millis(100), 285 memory_usage: 512 * 1024 * 1024, 286 cache_hit_count: 0, 287 }, 288 }; 289 290 manager.cache_mut().store(cache_key, expired_entry); 291 292 let cleanup_result = manager.cleanup().unwrap(); 293 assert_eq!(cleanup_result.entries_removed, 1); 294 assert_eq!(cleanup_result.entries_before, 1); 295 assert_eq!(cleanup_result.entries_after, 0); 296 } 297 298 #[test] 299 fn test_cache_manager_clear() { 300 let temp_dir = TempDir::new().unwrap(); 301 let cache_dir = temp_dir.path().join("cache"); 302 303 let mut manager = CacheManager::with_cache_dir(cache_dir); 304 305 // Add a test entry 306 let cache_key = CacheKey { 307 content_hash: ContentHash("test_hash".to_string()), 308 config_hash: ConfigHash("config_hash".to_string()), 309 tool_versions: ToolVersions { 310 ferris_proof: "0.1.0".to_string(), 311 external_tools: vec![], 312 }, 313 layer: Layer::PropertyBased, 314 }; 315 316 let cache_entry = CacheEntry { 317 result: LayerResult { 318 layer: Layer::PropertyBased, 319 status: Status::Success, 320 violations: vec![], 321 execution_time: Duration::from_millis(100), 322 tool_outputs: vec![], 323 }, 324 timestamp: chrono::Utc::now(), 325 ttl: Duration::from_secs(3600), 326 metadata: CacheMetadata { 327 file_size: 1024, 328 execution_time: Duration::from_millis(100), 329 memory_usage: 512 * 1024 * 1024, 330 cache_hit_count: 0, 331 }, 332 }; 333 334 manager.cache_mut().store(cache_key, cache_entry); 335 336 let clear_result = manager.clear().unwrap(); 337 assert_eq!(clear_result.entries_removed, 1); 338 339 let info_after = manager.info().unwrap(); 340 assert_eq!(info_after.total_entries, 0); 341 } 342 343 #[test] 344 fn test_cache_manager_health_check() { 345 let temp_dir = TempDir::new().unwrap(); 346 let cache_dir = temp_dir.path().join("cache"); 347 348 let manager = CacheManager::with_cache_dir(cache_dir); 349 let health_report = manager.health_check().unwrap(); 350 351 assert!(health_report.integrity_errors.is_empty()); 352 assert!(!health_report.recommendations.is_empty()); 353 assert!(health_report.recommendations[0].contains("Cache is empty")); 354 } 355 }