use anyhow::Result;
use chrono::{DateTime, Utc};
use parking_lot::RwLock;
use rustc_hash::FxHashMap;
use serde::{Deserialize, Serialize};
use std::collections::VecDeque;
use std::sync::atomic::{AtomicU64, AtomicUsize, Ordering};
use std::sync::Arc;
use std::time::{Duration, Instant};
use tracing::info;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum EvictionPolicy {
LRU,
LFU,
TTL,
FIFO,
Random,
Adaptive,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub enum CacheTier {
L1,
L2,
L3,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AccessPattern {
pub frequency: f64,
pub temporal_locality: f64,
pub spatial_locality: f64,
pub entropy: f64,
pub last_access: DateTime<Utc>,
pub access_count: u64,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AdvancedCacheConfig {
pub eviction_policy: EvictionPolicy,
pub enable_multi_tier: bool,
pub enable_predictive: bool,
pub enable_collaborative: bool,
pub tier_memory_limits: FxHashMap<CacheTier, usize>,
pub warming_config: CacheWarmingConfig,
pub performance_config: PerformanceConfig,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CacheWarmingConfig {
pub auto_warm: bool,
pub max_warm_time: Duration,
pub warm_patterns: Vec<String>,
pub dependency_warming: bool,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PerformanceConfig {
pub compression_enabled: bool,
pub compression_level: u32,
pub background_cleanup: bool,
pub cleanup_interval: Duration,
pub stats_enabled: bool,
}
impl Default for AdvancedCacheConfig {
fn default() -> Self {
let mut tier_limits = FxHashMap::default();
tier_limits.insert(CacheTier::L1, 64 * 1024 * 1024); tier_limits.insert(CacheTier::L2, 256 * 1024 * 1024); tier_limits.insert(CacheTier::L3, 1024 * 1024 * 1024);
Self {
eviction_policy: EvictionPolicy::Adaptive,
enable_multi_tier: true,
enable_predictive: true,
enable_collaborative: false, tier_memory_limits: tier_limits,
warming_config: CacheWarmingConfig {
auto_warm: true,
max_warm_time: Duration::from_secs(30),
warm_patterns: vec![
"**/*.rs".to_string(),
"**/Cargo.toml".to_string(),
"**/*.md".to_string(),
],
dependency_warming: true,
},
performance_config: PerformanceConfig {
compression_enabled: true,
compression_level: 6,
background_cleanup: true,
cleanup_interval: Duration::from_secs(60),
stats_enabled: true,
},
}
}
}
#[derive(Debug, Clone)]
struct AdaptiveCacheEntry<T> {
value: Arc<T>,
pattern: AccessPattern,
size: usize,
tier: CacheTier,
created_at: DateTime<Utc>,
expires_at: Option<DateTime<Utc>>,
}
impl<T> AdaptiveCacheEntry<T> {}
pub struct AdaptiveCache<K, V>
where
K: Clone + Eq + std::hash::Hash + Send + Sync + 'static,
V: Clone + Send + Sync + 'static,
{
config: AdvancedCacheConfig,
l1_cache: Arc<RwLock<FxHashMap<K, AdaptiveCacheEntry<V>>>>,
l2_cache: Arc<RwLock<FxHashMap<K, AdaptiveCacheEntry<V>>>>,
l3_cache: Arc<RwLock<FxHashMap<K, AdaptiveCacheEntry<V>>>>,
access_patterns: Arc<RwLock<FxHashMap<K, AccessPattern>>>,
stats: Arc<RwLock<AdaptiveCacheStats>>,
predictor: Arc<CachePredictor<K>>,
}
#[derive(Debug, Default)]
pub struct AdaptiveCacheStats {
pub tier_stats: FxHashMap<CacheTier, TierStats>,
pub pattern_stats: PatternStats,
pub performance: PerformanceStats,
pub warming_stats: WarmingStats,
}
#[derive(Debug, Default)]
pub struct TierStats {
pub entry_count: usize,
pub memory_usage: usize,
pub hits: AtomicU64,
pub misses: AtomicU64,
pub evictions: AtomicU64,
pub avg_access_time: Duration,
}
#[derive(Debug, Default)]
pub struct PatternStats {
pub avg_frequency: f64,
pub avg_temporal_locality: f64,
pub avg_spatial_locality: f64,
pub adaptations: AtomicU64,
}
#[derive(Debug, Default)]
pub struct PerformanceStats {
pub avg_lookup_time: Duration,
pub avg_insert_time: Duration,
pub warming_time: Duration,
pub compression_efficiency: f32,
pub cleanup_operations: AtomicU64,
}
#[derive(Debug, Default)]
pub struct WarmingStats {
pub files_warmed: AtomicUsize,
pub warming_success_rate: f64,
pub total_warming_time: Duration,
pub predictive_hits: AtomicU64,
}
pub struct CachePredictor<K>
where
K: Clone + Eq + std::hash::Hash,
{
access_history: RwLock<VecDeque<K>>,
patterns: RwLock<FxHashMap<Vec<K>, f64>>,
confidence_threshold: f64,
}
impl<K, V> AdaptiveCache<K, V>
where
K: Clone + Eq + std::hash::Hash + Send + Sync + 'static,
V: Clone + Send + Sync + 'static,
{
#[must_use]
pub fn new(config: AdvancedCacheConfig) -> Self {
let mut tier_stats = FxHashMap::default();
tier_stats.insert(CacheTier::L1, TierStats::default());
tier_stats.insert(CacheTier::L2, TierStats::default());
tier_stats.insert(CacheTier::L3, TierStats::default());
Self {
config,
l1_cache: Arc::new(RwLock::new(FxHashMap::default())),
l2_cache: Arc::new(RwLock::new(FxHashMap::default())),
l3_cache: Arc::new(RwLock::new(FxHashMap::default())),
access_patterns: Arc::new(RwLock::new(FxHashMap::default())),
stats: Arc::new(RwLock::new(AdaptiveCacheStats {
tier_stats,
..Default::default()
})),
predictor: Arc::new(CachePredictor::new(0.8)),
}
}
pub async fn get(&self, key: &K) -> Option<Arc<V>> {
let start = Instant::now();
if let Some(entry) = self.get_from_tier(key, CacheTier::L1) {
self.record_hit(CacheTier::L1, start.elapsed());
self.update_access_pattern(key);
return Some(entry.value);
}
if let Some(entry) = self.get_from_tier(key, CacheTier::L2) {
self.record_hit(CacheTier::L2, start.elapsed());
if self.should_promote(&entry.pattern) {
let _ = self.promote_to_l1(key, &entry).await;
}
self.update_access_pattern(key);
return Some(entry.value);
}
if let Some(entry) = self.get_from_tier(key, CacheTier::L3) {
self.record_hit(CacheTier::L3, start.elapsed());
if self.should_promote(&entry.pattern) {
if entry.pattern.frequency > 0.7 {
let _ = self.promote_to_l1(key, &entry).await;
} else if entry.pattern.frequency > 0.3 {
let _ = self.promote_to_l2(key, &entry).await;
}
}
self.update_access_pattern(key);
return Some(entry.value);
}
self.record_miss();
None
}
pub async fn put(&self, key: K, value: V) -> Result<()> {
let start = Instant::now();
let value_arc = Arc::new(value);
let size = std::mem::size_of::<V>();
let tier = self.determine_initial_tier(&key, size);
let entry = AdaptiveCacheEntry {
value: value_arc,
pattern: self.get_or_create_pattern(&key),
size,
tier,
created_at: Utc::now(),
expires_at: self.calculate_expiration(tier),
};
match tier {
CacheTier::L1 => self.insert_l1(key, entry).await?,
CacheTier::L2 => self.insert_l2(key, entry).await?,
CacheTier::L3 => self.insert_l3(key, entry).await?,
}
self.record_insert_time(start.elapsed());
Ok(())
}
pub async fn remove(&self, key: &K) -> Option<Arc<V>> {
let l1_removed = self.l1_cache.write().remove(key);
let l2_removed = self.l2_cache.write().remove(key);
let l3_removed = self.l3_cache.write().remove(key);
l1_removed
.or(l2_removed)
.or(l3_removed)
.map(|entry| entry.value)
}
pub async fn clear(&self) -> Result<()> {
self.l1_cache.write().clear();
self.l2_cache.write().clear();
self.l3_cache.write().clear();
self.access_patterns.write().clear();
let mut stats = self.stats.write();
for tier_stats in stats.tier_stats.values_mut() {
tier_stats.hits.store(0, Ordering::Relaxed);
tier_stats.misses.store(0, Ordering::Relaxed);
tier_stats.evictions.store(0, Ordering::Relaxed);
}
Ok(())
}
#[must_use]
pub fn get_stats(&self) -> AdaptiveCacheStats {
let _stats = self.stats.read();
AdaptiveCacheStats {
tier_stats: FxHashMap::default(), ..Default::default()
}
}
pub async fn warm_cache(&self, warm_keys: Vec<K>) -> Result<usize> {
let start = Instant::now();
let mut warmed_count = 0;
for key in warm_keys {
if let Some(_predicted_value) = self.predictor.predict_value(&key) {
warmed_count += 1;
}
}
let warming_time = start.elapsed();
self.stats.write().warming_stats.total_warming_time = warming_time;
self.stats
.write()
.warming_stats
.files_warmed
.store(warmed_count, Ordering::Relaxed);
info!(
"Cache warming completed: {} entries in {:?}",
warmed_count, warming_time
);
Ok(warmed_count)
}
pub async fn background_maintenance(&self) -> Result<()> {
if !self.config.performance_config.background_cleanup {
return Ok(());
}
self.cleanup_expired_entries().await?;
self.optimize_cache_layout().await?;
self.update_global_patterns();
self.stats
.write()
.performance
.cleanup_operations
.fetch_add(1, Ordering::Relaxed);
Ok(())
}
fn get_from_tier(&self, key: &K, tier: CacheTier) -> Option<AdaptiveCacheEntry<V>> {
match tier {
CacheTier::L1 => self.l1_cache.read().get(key).cloned(),
CacheTier::L2 => self.l2_cache.read().get(key).cloned(),
CacheTier::L3 => self.l3_cache.read().get(key).cloned(),
}
}
fn should_promote(&self, pattern: &AccessPattern) -> bool {
pattern.frequency > 0.5 || pattern.temporal_locality > 0.7
}
async fn promote_to_l1(&self, key: &K, entry: &AdaptiveCacheEntry<V>) -> Result<()> {
let mut promoted_entry = entry.clone();
promoted_entry.tier = CacheTier::L1;
self.insert_l1(key.clone(), promoted_entry).await
}
async fn promote_to_l2(&self, key: &K, entry: &AdaptiveCacheEntry<V>) -> Result<()> {
let mut promoted_entry = entry.clone();
promoted_entry.tier = CacheTier::L2;
self.insert_l2(key.clone(), promoted_entry).await
}
fn determine_initial_tier(&self, _key: &K, size: usize) -> CacheTier {
if size < 64 * 1024 {
CacheTier::L1
} else if size < 1024 * 1024 {
CacheTier::L2
} else {
CacheTier::L3
}
}
fn get_or_create_pattern(&self, key: &K) -> AccessPattern {
self.access_patterns
.read()
.get(key)
.cloned()
.unwrap_or_else(|| AccessPattern {
frequency: 0.0,
temporal_locality: 0.0,
spatial_locality: 0.0,
entropy: 0.0,
last_access: Utc::now(),
access_count: 0,
})
}
fn calculate_expiration(&self, tier: CacheTier) -> Option<DateTime<Utc>> {
if matches!(self.config.eviction_policy, EvictionPolicy::TTL) {
let ttl = match tier {
CacheTier::L1 => Duration::from_secs(300), CacheTier::L2 => Duration::from_secs(1800), CacheTier::L3 => Duration::from_secs(3600), };
Some(Utc::now() + chrono::Duration::from_std(ttl).unwrap())
} else {
None
}
}
async fn insert_l1(&self, key: K, entry: AdaptiveCacheEntry<V>) -> Result<()> {
let mut cache = self.l1_cache.write();
let max_size = *self
.config
.tier_memory_limits
.get(&CacheTier::L1)
.unwrap_or(&(64 * 1024 * 1024));
if self.calculate_tier_size(&cache) + entry.size > max_size {
self.evict_from_tier(&mut cache, CacheTier::L1)?;
}
cache.insert(key, entry);
Ok(())
}
async fn insert_l2(&self, key: K, entry: AdaptiveCacheEntry<V>) -> Result<()> {
let mut cache = self.l2_cache.write();
let max_size = *self
.config
.tier_memory_limits
.get(&CacheTier::L2)
.unwrap_or(&(256 * 1024 * 1024));
if self.calculate_tier_size(&cache) + entry.size > max_size {
self.evict_from_tier(&mut cache, CacheTier::L2)?;
}
cache.insert(key, entry);
Ok(())
}
async fn insert_l3(&self, key: K, entry: AdaptiveCacheEntry<V>) -> Result<()> {
let mut cache = self.l3_cache.write();
let max_size = *self
.config
.tier_memory_limits
.get(&CacheTier::L3)
.unwrap_or(&(1024 * 1024 * 1024));
if self.calculate_tier_size(&cache) + entry.size > max_size {
self.evict_from_tier(&mut cache, CacheTier::L3)?;
}
cache.insert(key, entry);
Ok(())
}
fn calculate_tier_size(&self, cache: &FxHashMap<K, AdaptiveCacheEntry<V>>) -> usize {
cache.values().map(|entry| entry.size).sum()
}
fn evict_from_tier(
&self,
cache: &mut FxHashMap<K, AdaptiveCacheEntry<V>>,
tier: CacheTier,
) -> Result<()> {
if cache.is_empty() {
return Ok(());
}
match self.config.eviction_policy {
EvictionPolicy::LRU => self.evict_lru(cache),
EvictionPolicy::LFU => self.evict_lfu(cache),
EvictionPolicy::TTL => self.evict_ttl(cache),
EvictionPolicy::FIFO => self.evict_fifo(cache),
EvictionPolicy::Random => self.evict_random(cache),
EvictionPolicy::Adaptive => self.evict_adaptive(cache),
}
if let Some(tier_stats) = self.stats.read().tier_stats.get(&tier) {
tier_stats.evictions.fetch_add(1, Ordering::Relaxed);
}
Ok(())
}
fn evict_lru(&self, cache: &mut FxHashMap<K, AdaptiveCacheEntry<V>>) {
if let Some(oldest_key) = cache
.iter()
.min_by_key(|(_, entry)| entry.pattern.last_access)
.map(|(key, _)| key.clone())
{
cache.remove(&oldest_key);
}
}
fn evict_lfu(&self, cache: &mut FxHashMap<K, AdaptiveCacheEntry<V>>) {
if let Some(least_used_key) = cache
.iter()
.min_by_key(|(_, entry)| entry.pattern.access_count)
.map(|(key, _)| key.clone())
{
cache.remove(&least_used_key);
}
}
fn evict_ttl(&self, cache: &mut FxHashMap<K, AdaptiveCacheEntry<V>>) {
let now = Utc::now();
let expired_keys: Vec<_> = cache
.iter()
.filter(|(_, entry)| entry.expires_at.is_some_and(|exp| exp < now))
.map(|(key, _)| key.clone())
.collect();
for key in expired_keys {
cache.remove(&key);
}
if !cache.is_empty() {
self.evict_lru(cache);
}
}
fn evict_fifo(&self, cache: &mut FxHashMap<K, AdaptiveCacheEntry<V>>) {
if let Some(oldest_key) = cache
.iter()
.min_by_key(|(_, entry)| entry.created_at)
.map(|(key, _)| key.clone())
{
cache.remove(&oldest_key);
}
}
fn evict_random(&self, cache: &mut FxHashMap<K, AdaptiveCacheEntry<V>>) {
if let Some(key) = cache.keys().next().cloned() {
cache.remove(&key);
}
}
fn evict_adaptive(&self, cache: &mut FxHashMap<K, AdaptiveCacheEntry<V>>) {
if let Some(victim_key) = cache
.iter()
.min_by(|(_, a), (_, b)| {
let score_a = self.calculate_eviction_score(&a.pattern);
let score_b = self.calculate_eviction_score(&b.pattern);
score_a
.partial_cmp(&score_b)
.unwrap_or(std::cmp::Ordering::Equal)
})
.map(|(key, _)| key.clone())
{
cache.remove(&victim_key);
}
}
fn calculate_eviction_score(&self, pattern: &AccessPattern) -> f64 {
let recency_weight = 0.4;
let frequency_weight = 0.4;
let locality_weight = 0.2;
let recency_score = {
let age = Utc::now().signed_duration_since(pattern.last_access);
1.0 - (age.num_seconds() as f64 / 3600.0).min(1.0) };
recency_weight * recency_score
+ frequency_weight * pattern.frequency
+ locality_weight * (pattern.temporal_locality + pattern.spatial_locality) / 2.0
}
fn record_hit(&self, tier: CacheTier, _access_time: Duration) {
if let Some(tier_stats) = self.stats.read().tier_stats.get(&tier) {
tier_stats.hits.fetch_add(1, Ordering::Relaxed);
}
}
fn record_miss(&self) {
for tier_stats in self.stats.read().tier_stats.values() {
tier_stats.misses.fetch_add(1, Ordering::Relaxed);
}
}
fn record_insert_time(&self, _insert_time: Duration) {
}
fn update_access_pattern(&self, key: &K) {
let mut patterns = self.access_patterns.write();
if let Some(pattern) = patterns.get_mut(key) {
pattern.access_count += 1;
pattern.last_access = Utc::now();
pattern.frequency = (pattern.frequency * 0.9 + 0.1).min(1.0);
}
}
async fn cleanup_expired_entries(&self) -> Result<()> {
let now = Utc::now();
{
let mut cache = self.l1_cache.write();
cache.retain(|_, entry| entry.expires_at.map_or(true, |exp| exp > now));
}
{
let mut cache = self.l2_cache.write();
cache.retain(|_, entry| entry.expires_at.map_or(true, |exp| exp > now));
}
{
let mut cache = self.l3_cache.write();
cache.retain(|_, entry| entry.expires_at.map_or(true, |exp| exp > now));
}
Ok(())
}
async fn optimize_cache_layout(&self) -> Result<()> {
Ok(())
}
fn update_global_patterns(&self) {
let patterns = self.access_patterns.read();
let mut stats = self.stats.write();
if !patterns.is_empty() {
stats.pattern_stats.avg_frequency =
patterns.values().map(|p| p.frequency).sum::<f64>() / patterns.len() as f64;
stats.pattern_stats.avg_temporal_locality =
patterns.values().map(|p| p.temporal_locality).sum::<f64>() / patterns.len() as f64;
stats.pattern_stats.avg_spatial_locality =
patterns.values().map(|p| p.spatial_locality).sum::<f64>() / patterns.len() as f64;
}
}
}
impl<K> CachePredictor<K>
where
K: Clone + Eq + std::hash::Hash,
{
#[must_use]
pub fn new(confidence_threshold: f64) -> Self {
Self {
access_history: RwLock::new(VecDeque::new()),
patterns: RwLock::new(FxHashMap::default()),
confidence_threshold,
}
}
pub fn record_access(&self, key: K) {
let mut history = self.access_history.write();
history.push_back(key);
if history.len() > 1000 {
history.pop_front();
}
self.update_patterns(&history);
}
pub fn predict_next(&self, current_sequence: &[K]) -> Vec<K> {
let patterns = self.patterns.read();
let mut predictions = Vec::new();
for (pattern, confidence) in patterns.iter() {
if *confidence > self.confidence_threshold
&& pattern.len() > current_sequence.len()
&& pattern.starts_with(current_sequence)
{
predictions.push(pattern[current_sequence.len()].clone());
}
}
predictions
}
pub fn predict_value(&self, _key: &K) -> Option<()> {
None
}
fn update_patterns(&self, history: &VecDeque<K>) {
let mut patterns = self.patterns.write();
for window_size in 2..=5.min(history.len()) {
for window in history.iter().collect::<Vec<_>>().windows(window_size) {
let pattern: Vec<K> = window.iter().map(|k| (*k).clone()).collect();
*patterns.entry(pattern).or_insert(0.0) += 1.0;
}
}
let total_patterns = patterns.len() as f64;
for confidence in patterns.values_mut() {
*confidence /= total_patterns;
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[tokio::test]
async fn test_adaptive_cache_basic_operations() -> Result<()> {
let config = AdvancedCacheConfig::default();
let cache: AdaptiveCache<String, String> = AdaptiveCache::new(config);
cache.put("key1".to_string(), "value1".to_string()).await?;
let result = cache.get(&"key1".to_string()).await;
assert!(result.is_some());
assert_eq!(result.unwrap().as_ref(), "value1");
Ok(())
}
#[tokio::test]
async fn test_cache_tiering() -> Result<()> {
let config = AdvancedCacheConfig::default();
let cache: AdaptiveCache<String, Vec<u8>> = AdaptiveCache::new(config);
let small_value = vec![0u8; 1024];
cache.put("small".to_string(), small_value).await?;
let large_value = vec![0u8; 2 * 1024 * 1024];
cache.put("large".to_string(), large_value).await?;
assert!(cache.get(&"small".to_string()).await.is_some());
assert!(cache.get(&"large".to_string()).await.is_some());
Ok(())
}
#[test]
fn test_eviction_policies() {
let mut cache = FxHashMap::default();
let adaptive_cache: AdaptiveCache<String, String> =
AdaptiveCache::new(AdvancedCacheConfig::default());
for i in 0..3 {
let entry = AdaptiveCacheEntry {
value: Arc::new(format!("value{}", i)),
pattern: AccessPattern {
frequency: i as f64 * 0.3,
temporal_locality: 0.5,
spatial_locality: 0.5,
entropy: 0.0,
last_access: Utc::now(),
access_count: i * 10,
},
size: 1024,
tier: CacheTier::L1,
created_at: Utc::now(),
expires_at: None,
};
cache.insert(format!("key{}", i), entry);
}
adaptive_cache.evict_lru(&mut cache);
assert_eq!(cache.len(), 2);
if let Some(_entry) = cache.get("key0") {
}
}
#[test]
fn test_cache_predictor() {
let predictor: CachePredictor<String> = CachePredictor::new(0.5);
predictor.record_access("file1.rs".to_string());
predictor.record_access("file2.rs".to_string());
predictor.record_access("file3.rs".to_string());
predictor.record_access("file1.rs".to_string());
predictor.record_access("file2.rs".to_string());
let predictions = predictor.predict_next(&["file1.rs".to_string()]);
assert!(!predictions.is_empty());
}
#[test]
fn test_cache_config() {
let config = AdvancedCacheConfig::default();
assert_eq!(config.eviction_policy, EvictionPolicy::Adaptive);
assert!(config.enable_multi_tier);
assert!(config.enable_predictive);
assert!(!config.enable_collaborative); }
}
#[cfg(test)]
mod property_tests {
use proptest::prelude::*;
proptest! {
#[test]
fn basic_property_stability(_input in ".*") {
prop_assert!(true);
}
#[test]
fn module_consistency_check(_x in 0u32..1000) {
prop_assert!(_x < 1001);
}
}
}