use crate::{TuningConfig, UnifiedCacheStats};
use serde::{Deserialize, Serialize};
use std::{
collections::VecDeque,
sync::{Arc, RwLock},
time::SystemTime,
};
use tokio::time::Interval;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PerformanceSnapshot {
pub timestamp: SystemTime,
pub hit_rate: f64,
pub l1_hit_rate: f64,
pub l2_hit_rate: f64,
pub memory_usage_percent: f64,
pub disk_usage_percent: f64,
pub avg_get_latency_us: f64,
pub avg_put_latency_us: f64,
pub ops_per_second: f64,
pub eviction_rate: f64,
pub promotion_rate: f64,
}
impl Default for PerformanceSnapshot {
fn default() -> Self {
Self {
timestamp: SystemTime::now(),
hit_rate: 0.0,
l1_hit_rate: 0.0,
l2_hit_rate: 0.0,
memory_usage_percent: 0.0,
disk_usage_percent: 0.0,
avg_get_latency_us: 0.0,
avg_put_latency_us: 0.0,
ops_per_second: 0.0,
eviction_rate: 0.0,
promotion_rate: 0.0,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TuningRecommendation {
pub parameter: String,
pub current_value: f64,
pub recommended_value: f64,
pub confidence: f64,
pub expected_improvement: f64,
pub reason: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AdaptiveTuningStats {
pub tuning_operations: u64,
pub successful_tunings: u64,
pub failed_tunings: u64,
pub success_rate: f64,
pub avg_improvement: f64,
pub current_confidence: f64,
pub last_tuning: SystemTime,
}
impl Default for AdaptiveTuningStats {
fn default() -> Self {
Self {
tuning_operations: 0,
successful_tunings: 0,
failed_tunings: 0,
success_rate: 0.0,
avg_improvement: 0.0,
current_confidence: 0.5,
last_tuning: SystemTime::UNIX_EPOCH,
}
}
}
#[derive(Debug)]
pub struct AdaptiveTuner {
config: TuningConfig,
performance_history: Arc<RwLock<VecDeque<PerformanceSnapshot>>>,
current_params: Arc<RwLock<TuningParameters>>,
stats: Arc<RwLock<AdaptiveTuningStats>>,
_tuning_interval: Option<Interval>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TuningParameters {
pub l1_size_multiplier: f64,
pub l2_size_multiplier: f64,
pub promotion_threshold: u64,
pub eviction_aggressiveness: f64,
pub ttl_multiplier: f64,
pub preheating_aggressiveness: f64,
}
impl Default for TuningParameters {
fn default() -> Self {
Self {
l1_size_multiplier: 1.0,
l2_size_multiplier: 1.0,
promotion_threshold: 3,
eviction_aggressiveness: 0.5,
ttl_multiplier: 1.0,
preheating_aggressiveness: 0.3,
}
}
}
impl AdaptiveTuner {
pub fn new(config: TuningConfig) -> Self {
Self {
config,
performance_history: Arc::new(RwLock::new(VecDeque::new())),
current_params: Arc::new(RwLock::new(TuningParameters::default())),
stats: Arc::new(RwLock::new(AdaptiveTuningStats::default())),
_tuning_interval: None,
}
}
pub async fn record_performance(&self, stats: &UnifiedCacheStats) {
let snapshot = PerformanceSnapshot {
timestamp: SystemTime::now(),
hit_rate: stats.overall_stats.overall_hit_rate,
l1_hit_rate: if stats.l1_stats.hits + stats.l1_stats.misses > 0 {
stats.l1_stats.hits as f64 / (stats.l1_stats.hits + stats.l1_stats.misses) as f64
} else {
0.0
},
l2_hit_rate: if stats.l2_stats.hits + stats.l2_stats.misses > 0 {
stats.l2_stats.hits as f64 / (stats.l2_stats.hits + stats.l2_stats.misses) as f64
} else {
0.0
},
memory_usage_percent: if stats.l1_stats.max_usage_bytes > 0 {
stats.l1_stats.usage_bytes as f64 / stats.l1_stats.max_usage_bytes as f64 * 100.0
} else {
0.0
},
disk_usage_percent: if stats.l2_stats.max_usage_bytes > 0 {
stats.l2_stats.usage_bytes as f64 / stats.l2_stats.max_usage_bytes as f64 * 100.0
} else {
0.0
},
avg_get_latency_us: stats.performance_metrics.avg_get_latency_us,
avg_put_latency_us: stats.performance_metrics.avg_put_latency_us,
ops_per_second: stats.performance_metrics.ops_per_second,
eviction_rate: stats.l1_stats.evictions as f64 + stats.l2_stats.evictions as f64,
promotion_rate: stats.overall_stats.promotions as f64,
};
let mut history = self.performance_history.write().unwrap();
history.push_back(snapshot);
let max_history = self.config.performance_window_size;
while history.len() > max_history {
history.pop_front();
}
}
pub async fn analyze_and_tune(&self) -> Vec<TuningRecommendation> {
if !self.config.enable_adaptive_tuning {
return Vec::new();
}
let history = {
let history_guard = self.performance_history.read().unwrap();
if history_guard.len() < self.config.min_samples_for_tuning {
return Vec::new();
}
history_guard.clone()
};
let mut recommendations = Vec::new();
if let Some(hit_rate_rec) = self.analyze_hit_rate(&history) {
recommendations.push(hit_rate_rec);
}
if let Some(memory_rec) = self.analyze_memory_usage(&history) {
recommendations.push(memory_rec);
}
if let Some(latency_rec) = self.analyze_latency(&history) {
recommendations.push(latency_rec);
}
if let Some(eviction_rec) = self.analyze_eviction_patterns(&history) {
recommendations.push(eviction_rec);
}
self.apply_recommendations(&recommendations).await;
recommendations
}
fn analyze_hit_rate(
&self,
history: &VecDeque<PerformanceSnapshot>,
) -> Option<TuningRecommendation> {
if history.len() < 3 {
return None;
}
let recent_hit_rate = history
.iter()
.rev()
.take(3)
.map(|s| s.hit_rate)
.sum::<f64>()
/ 3.0;
let older_hit_rate = history.iter().take(3).map(|s| s.hit_rate).sum::<f64>() / 3.0;
if recent_hit_rate < older_hit_rate - 0.05 && recent_hit_rate < self.config.target_hit_rate
{
let current_params = self.current_params.read().unwrap();
let new_multiplier = (current_params.l1_size_multiplier * 1.2).min(2.0);
return Some(TuningRecommendation {
parameter: "l1_size_multiplier".to_string(),
current_value: current_params.l1_size_multiplier,
recommended_value: new_multiplier,
confidence: 0.8,
expected_improvement: 0.1,
reason: "Hit rate declining, increasing L1 cache size".to_string(),
});
}
None
}
fn analyze_memory_usage(
&self,
history: &VecDeque<PerformanceSnapshot>,
) -> Option<TuningRecommendation> {
let avg_memory_usage =
history.iter().map(|s| s.memory_usage_percent).sum::<f64>() / history.len() as f64;
if avg_memory_usage > 90.0 {
let current_params = self.current_params.read().unwrap();
let new_aggressiveness = (current_params.eviction_aggressiveness + 0.1).min(1.0);
return Some(TuningRecommendation {
parameter: "eviction_aggressiveness".to_string(),
current_value: current_params.eviction_aggressiveness,
recommended_value: new_aggressiveness,
confidence: 0.9,
expected_improvement: 0.05,
reason: "High memory usage, increasing eviction aggressiveness".to_string(),
});
}
if avg_memory_usage < 50.0 {
let current_params = self.current_params.read().unwrap();
let new_aggressiveness = (current_params.eviction_aggressiveness - 0.1).max(0.1);
return Some(TuningRecommendation {
parameter: "eviction_aggressiveness".to_string(),
current_value: current_params.eviction_aggressiveness,
recommended_value: new_aggressiveness,
confidence: 0.7,
expected_improvement: 0.03,
reason: "Low memory usage, reducing eviction aggressiveness".to_string(),
});
}
None
}
fn analyze_latency(
&self,
history: &VecDeque<PerformanceSnapshot>,
) -> Option<TuningRecommendation> {
if history.len() < 5 {
return None;
}
let recent_latency = history
.iter()
.rev()
.take(3)
.map(|s| s.avg_get_latency_us)
.sum::<f64>()
/ 3.0;
let baseline_latency = history
.iter()
.take(3)
.map(|s| s.avg_get_latency_us)
.sum::<f64>()
/ 3.0;
if recent_latency > baseline_latency * 1.5 && recent_latency > 1000.0 {
let current_params = self.current_params.read().unwrap();
let new_ttl_multiplier = (current_params.ttl_multiplier * 0.8).max(0.5);
return Some(TuningRecommendation {
parameter: "ttl_multiplier".to_string(),
current_value: current_params.ttl_multiplier,
recommended_value: new_ttl_multiplier,
confidence: 0.7,
expected_improvement: 0.2,
reason: "High latency detected, reducing TTL to improve cache freshness"
.to_string(),
});
}
None
}
fn analyze_eviction_patterns(
&self,
history: &VecDeque<PerformanceSnapshot>,
) -> Option<TuningRecommendation> {
let avg_eviction_rate =
history.iter().map(|s| s.eviction_rate).sum::<f64>() / history.len() as f64;
if avg_eviction_rate > 100.0 {
let current_params = self.current_params.read().unwrap();
let new_multiplier = (current_params.l2_size_multiplier * 1.3).min(3.0);
return Some(TuningRecommendation {
parameter: "l2_size_multiplier".to_string(),
current_value: current_params.l2_size_multiplier,
recommended_value: new_multiplier,
confidence: 0.8,
expected_improvement: 0.15,
reason: "High eviction rate, increasing L2 cache size".to_string(),
});
}
None
}
async fn apply_recommendations(&self, recommendations: &[TuningRecommendation]) {
let mut params = self.current_params.write().unwrap();
let mut stats = self.stats.write().unwrap();
for rec in recommendations {
if rec.confidence >= self.config.min_confidence_for_auto_tuning {
match rec.parameter.as_str() {
"l1_size_multiplier" => params.l1_size_multiplier = rec.recommended_value,
"l2_size_multiplier" => params.l2_size_multiplier = rec.recommended_value,
"promotion_threshold" => {
params.promotion_threshold = rec.recommended_value as u64
}
"eviction_aggressiveness" => {
params.eviction_aggressiveness = rec.recommended_value
}
"ttl_multiplier" => params.ttl_multiplier = rec.recommended_value,
"preheating_aggressiveness" => {
params.preheating_aggressiveness = rec.recommended_value
}
_ => continue,
}
stats.tuning_operations += 1;
stats.last_tuning = SystemTime::now();
}
}
}
pub fn get_current_parameters(&self) -> TuningParameters {
self.current_params.read().unwrap().clone()
}
pub fn get_stats(&self) -> AdaptiveTuningStats {
let mut stats = self.stats.read().unwrap().clone();
if stats.tuning_operations > 0 {
stats.success_rate = stats.successful_tunings as f64 / stats.tuning_operations as f64;
}
stats
}
pub async fn record_tuning_outcome(&self, improved: bool, improvement: f64) {
let mut stats = self.stats.write().unwrap();
if improved {
stats.successful_tunings += 1;
stats.avg_improvement = (stats.avg_improvement * (stats.successful_tunings - 1) as f64
+ improvement)
/ stats.successful_tunings as f64;
} else {
stats.failed_tunings += 1;
}
if stats.tuning_operations > 0 {
stats.current_confidence =
stats.successful_tunings as f64 / stats.tuning_operations as f64;
}
}
}