use std::collections::{BTreeMap, HashMap, HashSet, VecDeque};
use std::time::{Duration, Instant};
#[derive(Debug, Clone)]
struct L2Entry {
data: Vec<u8>,
access_count: u32,
last_accessed: Instant,
created_at: Instant,
size: usize,
}
#[derive(Debug, Clone, Default)]
pub struct L2Stats {
pub hits: u64,
pub misses: u64,
pub evictions: u64,
pub memory_usage_bytes: usize,
pub entries: usize,
pub lru_evictions: u64,
pub lfu_evictions: u64,
}
impl L2Stats {
pub fn hit_rate(&self) -> f64 {
let total: _ = self.hits + self.misses;
if total > 0 {
self.hits as f64 / total as f64
} else {
0.0
}
}
pub fn memory_usage_mb(&self) -> f64 {
self.memory_usage_bytes as f64 / (1024.0 * 1024.0)
}
}
pub struct L2SmartCache {
entries: HashMap<CacheKey, L2Entry>,
lru_queue: VecDeque<CacheKey>,
lfu_tree: BTreeMap<(u32, CacheKey), CacheKey>,
stats: L2Stats,
max_size_bytes: usize,
max_entries: usize,
lru_weight: f64,
lfu_weight: f64,
}
impl L2SmartCache {
pub fn new() -> Self {
Self {
entries: HashMap::new(),
lru_queue: VecDeque::new(),
lfu_tree: BTreeMap::new(),
stats: L2Stats::default(),
max_size_bytes: 512 * 1024 * 1024, max_entries: 4096,
lru_weight: 0.3, lfu_weight: 0.7, }
}
pub fn put(&mut self, key: CacheKey, data: &[u8]) {
let data_size: _ = data.len();
if self.entries.len() >= self.max_entries || self.stats.memory_usage_bytes + data_size > self.max_size_bytes {
self.evict_entries(data_size);
}
let entry: _ = L2Entry {
data: data.to_vec(),
access_count: 0,
last_accessed: Instant::now(),
created_at: Instant::now(),
size: data_size,
};
self.entries.insert(key, entry);
self.lru_queue.retain(|&k| k != key);
self.lru_queue.push_back(key);
self.update_lfu(key);
self.stats.entries = self.entries.len();
self.stats.memory_usage_bytes += data_size;
}
pub fn get(&mut self, key: CacheKey) -> Option<Vec<u8>> {
let data: _ = if let Some(entry) = self.entries.get_mut(&key) {
entry.access_count += 1;
entry.last_accessed = Instant::now();
Some(entry.data.clone())
} else {
None
};
if data.is_some() {
self.lru_queue.retain(|&k| k != key);
self.lru_queue.push_back(key);
self.update_lfu(key);
self.stats.hits += 1;
} else {
self.stats.misses += 1;
}
data
}
pub fn invalidate(&mut self, key: CacheKey) {
if let Some(entry) = self.entries.remove(&key) {
self.lru_queue.retain(|&k| k != key);
self.remove_from_lfu(key);
self.stats.memory_usage_bytes -= entry.size;
self.stats.entries = self.entries.len();
}
}
pub fn gc(&mut self) {
let cutoff: _ = Instant::now() - Duration::from_secs(3600);
let keys_to_remove: Vec<CacheKey> = self.entries
.iter()
.filter(|(_, entry)| entry.created_at < cutoff && entry.access_count < 5)
.map(|(&key, _)| key)
.collect();
for key in keys_to_remove {
self.invalidate(key);
}
}
pub fn get_stats(&self) -> L2Stats {
self.stats.clone()
}
fn update_lfu(&mut self, key: CacheKey) {
if let Some(entry) = self.entries.get(&key).cloned() {
self.remove_from_lfu(key);
self.lfu_tree.insert((entry.access_count, key), key);
}
}
fn remove_from_lfu(&mut self, key: CacheKey) {
if let Some(entry) = self.entries.get(&key) {
self.lfu_tree.remove(&(entry.access_count, key));
}
}
fn evict_entries(&mut self, required_bytes: usize) {
let target_free: _ = required_bytes * 2;
let mut freed_bytes = 0;
let eviction_strategy: _ = self.determine_eviction_strategy();
match eviction_strategy {
EvictionStrategy::LRU => {
while freed_bytes < target_free && !self.lru_queue.is_empty() {
if let Some(key) = self.lru_queue.pop_front() {
if let Some(entry) = self.entries.remove(&key) {
freed_bytes += entry.size;
self.stats.lru_evictions += 1;
self.stats.evictions += 1;
}
self.remove_from_lfu(key);
}
}
}
EvictionStrategy::LFU => {
while freed_bytes < target_free && !self.lfu_tree.is_empty() {
if let Some((_, &key)) = self.lfu_tree.iter().next() {
if let Some(entry) = self.entries.remove(&key) {
freed_bytes += entry.size;
self.stats.lfu_evictions += 1;
self.stats.evictions += 1;
}
self.lru_queue.retain(|&k| k != key);
}
}
}
EvictionStrategy::Hybrid => {
while freed_bytes < target_free && !self.entries.is_empty() {
let lru_key: _ = self.lru_queue.front().cloned();
let lfu_key: _ = self.lfu_tree.iter().next().map(|(_, &k)| k);
let lru_score: _ = lru_key.map(|k| self.calculate_lru_score(k)).unwrap_or(0.0);
let lfu_score: _ = lfu_key.map(|k| self.calculate_lfu_score(k)).unwrap_or(0.0);
let key_to_evict: _ = if lru_score < lfu_score {
lru_key
} else {
lfu_key
};
if let Some(key) = key_to_evict {
if let Some(entry) = self.entries.remove(&key) {
freed_bytes += entry.size;
self.stats.evictions += 1;
}
self.lru_queue.retain(|&k| k != key);
self.remove_from_lfu(key);
} else {
break;
}
}
}
}
self.stats.entries = self.entries.len();
self.stats.memory_usage_bytes = self.entries.values().map(|e| e.size).sum();
}
fn determine_eviction_strategy(&self) -> EvictionStrategy {
let avg_access_count: _ = if !self.entries.is_empty() {
self.entries.values().map(|e| e.access_count).sum::<u32>() as f64 / self.entries.len() as f64
} else {
0.0
};
let recent_access_ratio: _ = if !self.lru_queue.is_empty() {
let recent_keys: std::collections::HashSet<_> = self.lru_queue.iter().take(self.lru_queue.len() / 2).collect();
let recent_count: _ = self.entries.values().filter(|e| recent_keys.contains(&self.find_key_by_entry(e))).count();
recent_count as f64 / self.entries.len() as f64
} else {
0.0
};
if avg_access_count > 10.0 {
EvictionStrategy::LFU } else if recent_access_ratio > 0.7 {
EvictionStrategy::LRU } else {
EvictionStrategy::Hybrid }
}
fn find_key_by_entry(&self, entry: &L2Entry) -> CacheKey {
0 }
fn calculate_lru_score(&self, key: CacheKey) -> f64 {
if let Some(entry) = self.entries.get(&key) {
let age: _ = entry.last_accessed.elapsed().as_secs();
1.0 / (age as f64 + 1.0)
} else {
0.0
}
}
fn calculate_lfu_score(&self, key: CacheKey) -> f64 {
if let Some(entry) = self.entries.get(&key) {
let access_count: _ = entry.access_count as f64;
let time_factor: _ = entry.last_accessed.elapsed().as_secs() as f64 / 3600.0; access_count / (time_factor + 1.0)
} else {
0.0
}
}
pub fn size(&self) -> usize {
self.entries.len()
}
pub fn is_empty(&self) -> bool {
self.entries.is_empty()
}
}
#[derive(Debug, Clone, Copy)]
enum EvictionStrategy {
LRU,
LFU,
Hybrid,
}
impl Default for L2SmartCache {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
#[test]
fn test_l2_basic_operations() {
let mut cache = L2SmartCache::new();
cache.put(123, b"console.log('test');");
let result: _ = cache.get(123);
assert!(result.is_some());
assert_eq!(result.unwrap(), b"console.log('test');");
}
#[test]
fn test_l2_access_counting() {
let mut cache = L2SmartCache::new();
cache.put(1, b"script1");
cache.get(1);
cache.get(1);
let stats: _ = cache.get_stats();
assert_eq!(stats.hits, 2);
}
#[test]
fn test_l2_invalidation() {
let mut cache = L2SmartCache::new();
cache.put(1, b"test");
assert!(cache.get(1).is_some());
cache.invalidate(1);
assert!(cache.get(1).is_none());
}
#[test]
fn test_l2_eviction() {
let mut cache = L2SmartCache::new();
for i in 0..100 {
cache.put(i, b"data");
}
let stats: _ = cache.get_stats();
assert!(stats.evictions > 0);
}
}