use crate::cache::{
CacheConfig, CacheInvalidator, CacheLayer, CacheSystem, CacheWarmer, MaintenanceConfig,
WarmingStrategy,
};
use anyhow::{Context, Result};
use clap::Subcommand;
use std::path::{Path, PathBuf};
use std::sync::Arc;
#[derive(Debug, Subcommand)]
pub enum CacheCommand {
Stats {
#[arg(long, short)]
detailed: bool,
},
Clear {
#[arg(long, short, default_value = "all")]
layer: String,
},
Warm {
#[arg(long, short)]
queries_file: Option<PathBuf>,
#[arg(long = "query", short)]
queries: Vec<String>,
},
Invalidate {
#[arg(long, short)]
all: bool,
#[arg(long, short)]
pattern: Option<String>,
#[arg(long, short)]
layer: Option<String>,
#[arg(long, short)]
file: Option<PathBuf>,
},
Maintenance {
#[arg(long, short)]
continuous: bool,
#[arg(long, default_value = "60")]
interval: u64,
},
}
impl CacheCommand {
pub async fn execute(&self) -> Result<()> {
match self {
Self::Stats { detailed } => self.show_stats(*detailed).await,
Self::Clear { layer } => self.clear_cache(layer).await,
Self::Warm {
queries_file,
queries,
} => self.warm_cache(queries_file.as_deref(), queries).await,
Self::Invalidate {
all,
pattern,
layer,
file,
} => {
self.invalidate_cache(*all, pattern.as_deref(), layer.as_deref(), file.as_deref())
.await
}
Self::Maintenance {
continuous,
interval,
} => self.run_maintenance(*continuous, *interval).await,
}
}
async fn show_stats(&self, detailed: bool) -> Result<()> {
let cache = Arc::new(CacheSystem::new(CacheConfig::default()));
let stats = cache.stats().await;
println!("\n{}", "=".repeat(80));
println!("Cache Statistics");
println!("{}\n", "=".repeat(80));
println!("Overall:");
println!(
" Hit Rate: {:.1}% ({} ops)",
stats.overall_hit_rate() * 100.0,
stats.total_operations()
);
println!(" Total Size: {:.1} MB", stats.total_size_mb());
println!(" Total Evictions: {}", stats.total_evictions());
println!(
" Effectiveness: {}",
if stats.is_effective() {
"✓ YES"
} else {
"✗ NO"
}
);
println!(
" Memory Target: {}",
if stats.is_within_memory_target() {
"✓ OK"
} else {
"✗ EXCEEDED"
}
);
if detailed {
println!("\n{}", "-".repeat(80));
println!("Layer Details:\n");
println!("L1 Query Cache:");
println!(
" Hit Rate: {:.1}% ({} hits / {} ops)",
stats.l1_query.hit_rate() * 100.0,
stats.l1_query.hits,
stats.l1_query.total_operations()
);
println!(" Size: {:.1} MB", stats.l1_query.size_mb());
println!(" Evictions: {}", stats.l1_query.evictions);
println!(" Expirations: {}", stats.l1_query.expirations);
println!(" Insertions: {}", stats.l1_query.insertions);
println!("\nL2 Embedding Cache:");
println!(
" Hit Rate: {:.1}% ({} hits / {} ops)",
stats.l2_embedding.hit_rate() * 100.0,
stats.l2_embedding.hits,
stats.l2_embedding.total_operations()
);
println!(" Size: {:.1} MB", stats.l2_embedding.size_mb());
println!(" Evictions: {}", stats.l2_embedding.evictions);
println!(" Expirations: {}", stats.l2_embedding.expirations);
println!(" Insertions: {}", stats.l2_embedding.insertions);
println!("\nL3 Context Cache:");
println!(
" Hit Rate: {:.1}% ({} hits / {} ops)",
stats.l3_context.hit_rate() * 100.0,
stats.l3_context.hits,
stats.l3_context.total_operations()
);
println!(" Size: {:.1} MB", stats.l3_context.size_mb());
println!(" Evictions: {}", stats.l3_context.evictions);
println!(" Expirations: {}", stats.l3_context.expirations);
println!(" Insertions: {}", stats.l3_context.insertions);
println!("\nParse Tree Cache:");
println!(
" Hit Rate: {:.1}% ({} hits / {} ops)",
stats.parse_tree.hit_rate() * 100.0,
stats.parse_tree.hits,
stats.parse_tree.total_operations()
);
println!(" Size: {:.1} MB", stats.parse_tree.size_mb());
println!(" Evictions: {}", stats.parse_tree.evictions);
println!(" Expirations: {}", stats.parse_tree.expirations);
println!(" Insertions: {}", stats.parse_tree.insertions);
}
println!("\n{}", "=".repeat(80));
Ok(())
}
async fn clear_cache(&self, layer: &str) -> Result<()> {
let cache = Arc::new(CacheSystem::new(CacheConfig::default()));
let invalidator = CacheInvalidator::new(Arc::clone(&cache));
let cache_layer = CacheLayer::from_str(layer)
.with_context(|| format!("Invalid cache layer: {}", layer))?;
println!("Clearing cache: {}", cache_layer.name());
let stats = invalidator.invalidate_layers(&[cache_layer]).await?;
println!("Cache cleared successfully:");
println!(" Query cache: {} layers", stats.query_invalidated);
println!(" Embedding cache: {} layers", stats.embedding_invalidated);
println!(" Context cache: {} layers", stats.context_invalidated);
println!(" Parse tree: {} layers", stats.parse_tree_invalidated);
println!(" Total: {} layers", stats.total_invalidated());
Ok(())
}
async fn warm_cache(&self, queries_file: Option<&Path>, queries: &[String]) -> Result<()> {
let cache = Arc::new(CacheSystem::new(CacheConfig::default()));
let mut warmer = CacheWarmer::new(Arc::clone(&cache), WarmingStrategy::Manual);
if let Some(file_path) = queries_file {
println!("Loading queries from: {}", file_path.display());
warmer.load_queries_from_file(file_path).await?;
}
for query in queries {
warmer.add_query(query.clone());
}
if warmer.query_count() == 0 {
println!("No queries to warm. Use --queries-file or --query to specify queries.");
return Ok(());
}
println!("Warming cache with {} queries...", warmer.query_count());
let stats = warmer.warm().await?;
println!("\nCache warming complete:");
println!(" Warmed: {}", stats.warmed);
println!(" Already cached: {}", stats.already_cached);
println!(" Errors: {}", stats.errors);
println!(" Total processed: {}", stats.total_processed());
println!(" Effectiveness: {:.1}%", stats.effectiveness() * 100.0);
println!(
" Status: {}",
if stats.is_successful() {
"✓ SUCCESS"
} else {
"✗ ERRORS"
}
);
Ok(())
}
async fn invalidate_cache(
&self,
all: bool,
pattern: Option<&str>,
layer: Option<&str>,
file: Option<&Path>,
) -> Result<()> {
let cache = Arc::new(CacheSystem::new(CacheConfig::default()));
let invalidator = CacheInvalidator::new(Arc::clone(&cache));
if all {
println!("Invalidating all caches...");
let stats = invalidator.on_manual().await?;
println!(
"All caches invalidated: {} layers",
stats.total_invalidated()
);
} else if let Some(pattern_str) = pattern {
println!("Invalidating by pattern: {}", pattern_str);
let stats = invalidator.on_pattern(pattern_str).await?;
println!(
"Pattern invalidation complete: {} entries",
stats.total_invalidated()
);
} else if let Some(layer_str) = layer {
let cache_layer = CacheLayer::from_str(layer_str)
.with_context(|| format!("Invalid cache layer: {}", layer_str))?;
println!("Invalidating layer: {}", cache_layer.name());
let stats = invalidator.invalidate_layers(&[cache_layer]).await?;
println!("Layer invalidated: {} entries", stats.total_invalidated());
} else if let Some(file_path) = file {
println!("Invalidating for file change: {}", file_path.display());
let stats = invalidator.on_file_changed(file_path).await?;
println!(
"File change invalidation complete: {} entries",
stats.total_invalidated()
);
} else {
println!(
"No invalidation criteria specified. Use --all, --pattern, --layer, or --file."
);
}
Ok(())
}
async fn run_maintenance(&self, continuous: bool, interval: u64) -> Result<()> {
let cache = Arc::new(CacheSystem::new(CacheConfig::default()));
let config = MaintenanceConfig {
interval_secs: interval,
..Default::default()
};
use crate::cache::maintenance::CacheMaintenance;
let maintenance = CacheMaintenance::new(Arc::clone(&cache), config);
if continuous {
println!(
"Running cache maintenance continuously (interval: {}s)",
interval
);
println!("Press Ctrl+C to stop");
maintenance.run().await?;
} else {
println!("Running single cache maintenance cycle...");
let stats = cache.stats().await;
println!("\nMaintenance cycle complete:");
println!(" Hit Rate: {:.1}%", stats.overall_hit_rate() * 100.0);
println!(" Memory: {:.1} MB", stats.total_size_mb());
println!(" Operations: {}", stats.total_operations());
println!(" Evictions: {}", stats.total_evictions());
}
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_cache_layer_from_str() {
assert!(CacheLayer::from_str("l1").is_some());
assert!(CacheLayer::from_str("query").is_some());
assert!(CacheLayer::from_str("l2").is_some());
assert!(CacheLayer::from_str("embedding").is_some());
assert!(CacheLayer::from_str("l3").is_some());
assert!(CacheLayer::from_str("context").is_some());
assert!(CacheLayer::from_str("parse").is_some());
assert!(CacheLayer::from_str("all").is_some());
assert!(CacheLayer::from_str("invalid").is_none());
}
}