use std::collections::VecDeque;
use std::sync::atomic::{AtomicU8, AtomicU64, Ordering};
use std::sync::{Arc, RwLock};
use std::time::Instant;
use rustc_hash::FxHashMap;
const MAX_HISTORY: usize = 256;
const DEFAULT_QOS_WINDOW_MS: u64 = 1000;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[repr(u8)]
pub enum QosLevel {
Critical = 0,
High = 1,
Normal = 2,
Low = 3,
Background = 4,
}
const _: () = {
assert!(QosLevel::Critical.priority() == QosLevel::Critical as u8);
assert!(QosLevel::High.priority() == QosLevel::High as u8);
assert!(QosLevel::Normal.priority() == QosLevel::Normal as u8);
assert!(QosLevel::Low.priority() == QosLevel::Low as u8);
assert!(QosLevel::Background.priority() == QosLevel::Background as u8);
};
impl QosLevel {
pub const fn priority(&self) -> u8 {
*self as u8
}
pub fn as_str(&self) -> &'static str {
match self {
QosLevel::Critical => "critical",
QosLevel::High => "high",
QosLevel::Normal => "normal",
QosLevel::Low => "low",
QosLevel::Background => "background",
}
}
pub fn from_priority(p: u8) -> Self {
match p {
0 => QosLevel::Critical,
1 => QosLevel::High,
2 => QosLevel::Normal,
3 => QosLevel::Low,
_ => QosLevel::Background,
}
}
#[inline]
pub fn to_u8(self) -> u8 {
self.priority()
}
#[inline]
pub fn from_u8(v: u8) -> Self {
Self::from_priority(v)
}
}
#[derive(Debug, Clone, Copy)]
pub struct LoadSample {
pub timestamp: Instant,
pub cpu_usage: f64,
pub queue_depth: u64,
pub latency_p99: u64,
pub throughput: u64,
}
#[derive(Debug, Clone)]
pub struct LoadHistory {
samples: VecDeque<LoadSample>,
max_size: usize,
}
impl LoadHistory {
pub fn new(max_size: usize) -> Self {
Self {
samples: VecDeque::with_capacity(max_size),
max_size,
}
}
pub fn record(&mut self, sample: LoadSample) {
if self.samples.len() >= self.max_size {
self.samples.pop_front();
}
self.samples.push_back(sample);
}
pub fn avg_cpu(&self) -> f64 {
if self.samples.is_empty() {
return 0.0;
}
let sum: f64 = self.samples.iter().map(|s| s.cpu_usage).sum();
sum / self.samples.len() as f64
}
pub fn avg_latency_p99(&self) -> u64 {
if self.samples.is_empty() {
return 0;
}
let sum: u64 = self.samples.iter().map(|s| s.latency_p99).sum();
sum / self.samples.len() as u64
}
pub fn max_queue_depth(&self) -> u64 {
self.samples.iter().map(|s| s.queue_depth).max().unwrap_or(0)
}
pub fn avg_throughput(&self) -> u64 {
if self.samples.is_empty() {
return 0;
}
let sum: u64 = self.samples.iter().map(|s| s.throughput).sum();
sum / self.samples.len() as u64
}
pub fn trend(&self) -> LoadTrend {
if self.samples.len() < 4 {
return LoadTrend::Stable;
}
let mid = self.samples.len() / 2;
let (mut first_sum, mut first_cnt) = (0.0f64, 0usize);
for s in self.samples.iter().take(mid) {
first_sum += s.cpu_usage;
first_cnt += 1;
}
let (mut second_sum, mut second_cnt) = (0.0f64, 0usize);
for s in self.samples.iter().skip(mid) {
second_sum += s.cpu_usage;
second_cnt += 1;
}
let first_avg = if first_cnt > 0 { first_sum / first_cnt as f64 } else { 0.0 };
let second_avg = if second_cnt > 0 { second_sum / second_cnt as f64 } else { 0.0 };
let delta = second_avg - first_avg;
if delta > 0.15 {
LoadTrend::Increasing
} else if delta < -0.15 {
LoadTrend::Decreasing
} else {
LoadTrend::Stable
}
}
pub fn len(&self) -> usize {
self.samples.len()
}
pub fn is_empty(&self) -> bool {
self.samples.is_empty()
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum LoadTrend {
Increasing,
Decreasing,
Stable,
}
pub struct AdaptiveQos {
level: AtomicU8,
window_ms: u64,
cpu_high_threshold: f64,
cpu_low_threshold: f64,
latency_high_ms: u64,
latency_low_ms: u64,
}
impl std::fmt::Debug for AdaptiveQos {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("AdaptiveQos")
.field("level", &self.current_level())
.field("window_ms", &self.window_ms)
.field("cpu_high_threshold", &self.cpu_high_threshold)
.field("cpu_low_threshold", &self.cpu_low_threshold)
.field("latency_high_ms", &self.latency_high_ms)
.field("latency_low_ms", &self.latency_low_ms)
.finish()
}
}
impl Clone for AdaptiveQos {
#[inline]
fn clone(&self) -> Self {
Self {
level: AtomicU8::new(self.level.load(Ordering::Acquire)),
window_ms: self.window_ms,
cpu_high_threshold: self.cpu_high_threshold,
cpu_low_threshold: self.cpu_low_threshold,
latency_high_ms: self.latency_high_ms,
latency_low_ms: self.latency_low_ms,
}
}
}
impl AdaptiveQos {
pub fn new() -> Self {
Self {
level: AtomicU8::new(QosLevel::Normal.to_u8()),
window_ms: DEFAULT_QOS_WINDOW_MS,
cpu_high_threshold: 0.85,
cpu_low_threshold: 0.30,
latency_high_ms: 100,
latency_low_ms: 10,
}
}
pub fn with_window(mut self, ms: u64) -> Self {
self.window_ms = ms;
self
}
pub fn with_cpu_thresholds(mut self, low: f64, high: f64) -> Self {
self.cpu_low_threshold = low;
self.cpu_high_threshold = high;
self
}
pub fn with_latency_thresholds(mut self, low_ms: u64, high_ms: u64) -> Self {
self.latency_low_ms = low_ms;
self.latency_high_ms = high_ms;
self
}
#[inline]
pub fn current_level(&self) -> QosLevel {
QosLevel::from_u8(self.level.load(Ordering::Acquire))
}
#[inline]
pub fn set_level(&self, level: QosLevel) {
self.level.store(level.to_u8(), Ordering::Release);
}
pub fn evaluate(&self, history: &LoadHistory) -> QosLevel {
let avg_cpu = history.avg_cpu();
let avg_latency = history.avg_latency_p99();
let trend = history.trend();
let current = self.current_level();
let high_threshold = match current {
QosLevel::Critical | QosLevel::High => self.cpu_high_threshold - 0.10,
_ => self.cpu_high_threshold,
};
let low_threshold = match current {
QosLevel::Low | QosLevel::Background => self.cpu_low_threshold + 0.05,
_ => self.cpu_low_threshold - 0.05,
};
let new_level = if avg_cpu > high_threshold || avg_latency > self.latency_high_ms {
match trend {
LoadTrend::Increasing => QosLevel::Critical,
_ => QosLevel::High,
}
} else if avg_cpu > 0.60 || avg_latency > self.latency_low_ms * 2 {
QosLevel::Normal
} else if avg_cpu < low_threshold && avg_latency < self.latency_low_ms {
QosLevel::Low
} else {
QosLevel::Normal
};
self.level.store(new_level.to_u8(), Ordering::Release);
new_level
}
pub fn effective_worker_count(&self, current: usize, history: &LoadHistory) -> usize {
let avg_cpu = history.avg_cpu();
let trend = history.trend();
match trend {
LoadTrend::Increasing => {
if avg_cpu > self.cpu_high_threshold {
current.saturating_mul(2)
} else if avg_cpu > 0.70 {
current.saturating_add((current / 4).max(1))
} else {
current
}
}
LoadTrend::Decreasing => {
if avg_cpu < self.cpu_low_threshold {
current.saturating_div(2).max(1)
} else if avg_cpu < 0.40 {
current.saturating_sub((current / 4).max(1)).max(1)
} else {
current
}
}
LoadTrend::Stable => current,
}
}
}
impl Default for AdaptiveQos {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone)]
pub struct CacheAccessPattern {
key: Box<str>,
access_count: u64,
last_access: Instant,
predicted_hot: bool,
}
#[derive(Debug)]
pub struct CacheWarmupPredictor {
patterns: FxHashMap<Box<str>, CacheAccessPattern>,
hot_threshold: u64,
decay_factor: f64,
}
impl CacheWarmupPredictor {
pub fn new() -> Self {
Self {
patterns: FxHashMap::default(),
hot_threshold: 10,
decay_factor: 0.95,
}
}
pub fn with_threshold(threshold: u64) -> Self {
Self {
patterns: FxHashMap::default(),
hot_threshold: threshold,
decay_factor: 0.95,
}
}
pub fn with_hot_threshold(mut self, threshold: u64) -> Self {
self.hot_threshold = threshold;
self
}
pub fn with_decay_factor(mut self, factor: f64) -> Self {
self.decay_factor = factor;
self
}
pub fn record_access(&mut self, key: &str) {
let now = Instant::now();
if let Some(pattern) = self.patterns.get_mut(key) {
pattern.access_count = (pattern.access_count as f64 * self.decay_factor + 1.0).round() as u64;
pattern.last_access = now;
pattern.predicted_hot = pattern.access_count >= self.hot_threshold;
} else {
let key_box: Box<str> = key.into();
self.patterns.insert(key_box.clone(), CacheAccessPattern {
key: key_box,
access_count: 1,
last_access: now,
predicted_hot: false,
});
}
}
pub fn get_hot_keys(&self) -> Vec<&str> {
let mut hot: Vec<(&str, u64)> = self.patterns
.values()
.filter(|p| p.predicted_hot)
.map(|p| (&*p.key, p.access_count))
.collect();
hot.sort_unstable_by_key(|b| std::cmp::Reverse(b.1));
hot.into_iter().map(|(s, _)| s).collect()
}
pub fn predict_warmup(&self) -> Vec<Box<str>> {
self.get_hot_keys()
.into_iter()
.map(|s| s.into())
.collect()
}
pub fn prune(&mut self, max_patterns: usize) {
if self.patterns.len() <= max_patterns {
return;
}
let mut entries: Vec<_> = self.patterns.drain().collect();
entries.sort_unstable_by_key(|b| std::cmp::Reverse(b.1.access_count));
entries.truncate(max_patterns);
self.patterns = entries.into_iter().collect();
}
pub fn pattern_count(&self) -> usize {
self.patterns.len()
}
}
impl Default for CacheWarmupPredictor {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug)]
pub struct AutoOptimizer {
history: Arc<RwLock<LoadHistory>>,
qos: Arc<AdaptiveQos>,
predictor: Arc<RwLock<CacheWarmupPredictor>>,
replan_countdown: AtomicU64,
replan_interval_ms: u64,
}
impl AutoOptimizer {
pub fn new(replan_interval_ms: u64) -> Self {
let replan_interval_ms = replan_interval_ms.max(1);
Self {
history: Arc::new(RwLock::new(LoadHistory::new(MAX_HISTORY))),
qos: Arc::new(AdaptiveQos::new()),
predictor: Arc::new(RwLock::new(CacheWarmupPredictor::new())),
replan_countdown: AtomicU64::new(replan_interval_ms),
replan_interval_ms,
}
}
pub fn with_hot_threshold(mut self, threshold: u64) -> Self {
self.predictor = Arc::new(RwLock::new(CacheWarmupPredictor::with_threshold(threshold)));
self
}
pub fn record_sample(&self, sample: LoadSample) {
if let Ok(mut history) = self.history.write() {
history.record(sample);
}
}
pub fn record_cache_access(&self, key: &str) {
if let Ok(mut predictor) = self.predictor.write() {
predictor.record_access(key);
}
}
pub fn evaluate_qos(&self) -> QosLevel {
if let Ok(history) = self.history.read() {
self.qos.evaluate(&history)
} else {
QosLevel::Normal
}
}
pub fn should_replan(&self) -> bool {
loop {
let cur = self.replan_countdown.load(Ordering::Relaxed);
if cur <= 1 {
if self
.replan_countdown
.compare_exchange(
cur,
self.replan_interval_ms,
Ordering::Relaxed,
Ordering::Relaxed,
)
.is_ok()
{
return true;
}
} else if self
.replan_countdown
.compare_exchange(cur, cur - 1, Ordering::Relaxed, Ordering::Relaxed)
.is_ok()
{
return false;
}
}
}
pub fn reset_replan_timer(&self) {
self.replan_countdown.store(self.replan_interval_ms, Ordering::Relaxed);
}
pub fn get_optimal_worker_count(&self, current: usize) -> usize {
if let Ok(history) = self.history.read() {
self.qos.effective_worker_count(current, &history)
} else {
current
}
}
pub fn get_hot_cache_keys(&self) -> Vec<Box<str>> {
if let Ok(predictor) = self.predictor.read() {
predictor.predict_warmup()
} else {
Vec::new()
}
}
#[inline]
pub fn set_qos_level(&self, level: QosLevel) {
self.qos.set_level(level);
}
#[inline]
pub fn current_qos_level(&self) -> QosLevel {
self.qos.current_level()
}
pub fn history_snapshot(&self) -> Option<LoadHistory> {
self.history.read().ok().map(|h| (*h).clone())
}
}
impl Default for AutoOptimizer {
fn default() -> Self {
Self::new(5000)
}
}
impl Clone for AutoOptimizer {
fn clone(&self) -> Self {
Self {
history: Arc::clone(&self.history),
qos: Arc::clone(&self.qos),
predictor: Arc::clone(&self.predictor),
replan_countdown: AtomicU64::new(self.replan_countdown.load(Ordering::Relaxed)),
replan_interval_ms: self.replan_interval_ms,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_qos_level() {
assert_eq!(QosLevel::Critical.priority(), 0);
assert_eq!(QosLevel::Background.priority(), 4);
assert_eq!(QosLevel::from_priority(2), QosLevel::Normal);
}
#[test]
fn test_load_history() {
let mut history = LoadHistory::new(10);
for i in 0..5 {
history.record(LoadSample {
timestamp: Instant::now(),
cpu_usage: 0.5 + (i as f64 * 0.1),
queue_depth: 100 + i * 50,
latency_p99: 10 + i * 5,
throughput: 1000 + i * 200,
});
}
assert_eq!(history.len(), 5);
assert!(history.avg_cpu() > 0.5);
assert!(history.max_queue_depth() >= 300);
assert!(history.avg_throughput() > 1000);
}
#[test]
fn test_load_history_overflow() {
let mut history = LoadHistory::new(3);
for i in 0..10 {
history.record(LoadSample {
timestamp: Instant::now(),
cpu_usage: i as f64 * 0.1,
queue_depth: i as u64,
latency_p99: i as u64,
throughput: i as u64,
});
}
assert_eq!(history.len(), 3);
}
#[test]
fn test_load_trend_stable() {
let mut history = LoadHistory::new(10);
for _ in 0..8 {
history.record(LoadSample {
timestamp: Instant::now(),
cpu_usage: 0.5,
queue_depth: 100,
latency_p99: 50,
throughput: 1000,
});
}
assert_eq!(history.trend(), LoadTrend::Stable);
}
#[test]
fn test_load_trend_increasing() {
let mut history = LoadHistory::new(10);
for i in 0..8 {
history.record(LoadSample {
timestamp: Instant::now(),
cpu_usage: 0.2 + (i as f64 * 0.1),
queue_depth: 100,
latency_p99: 50,
throughput: 1000,
});
}
assert_eq!(history.trend(), LoadTrend::Increasing);
}
#[test]
fn test_adaptive_qos_evaluation() {
let qos = AdaptiveQos::new();
assert_eq!(qos.current_level(), QosLevel::Normal);
let mut history = LoadHistory::new(10);
for _ in 0..8 {
history.record(LoadSample {
timestamp: Instant::now(),
cpu_usage: 0.95,
queue_depth: 500,
latency_p99: 200,
throughput: 5000,
});
}
let level = qos.evaluate(&history);
assert!(matches!(level, QosLevel::Critical | QosLevel::High));
}
#[test]
fn test_adaptive_qos_worker_scaling() {
let qos = AdaptiveQos::new();
let mut history = LoadHistory::new(10);
for i in 0..8 {
history.record(LoadSample {
timestamp: Instant::now(),
cpu_usage: 0.3 + (i as f64 * 0.1),
queue_depth: 50,
latency_p99: 5,
throughput: 200,
});
}
let worker_count = qos.effective_worker_count(4, &history);
assert!(worker_count >= 4);
}
#[test]
fn test_cache_warmup_predictor() {
let mut predictor = CacheWarmupPredictor::new().with_hot_threshold(5);
for _ in 0..10 {
predictor.record_access("key_hot");
}
for _ in 0..2 {
predictor.record_access("key_cold");
}
let hot_keys = predictor.get_hot_keys();
assert!(hot_keys.contains(&"key_hot"));
}
#[test]
fn test_cache_warmup_predictor_prune() {
let mut predictor = CacheWarmupPredictor::new();
for i in 0..200 {
predictor.record_access(&format!("key_{}", i));
}
assert!(predictor.pattern_count() > 100);
predictor.prune(50);
assert_eq!(predictor.pattern_count(), 50);
}
#[test]
fn test_cache_warmup_default_threshold_triggers() {
let mut predictor = CacheWarmupPredictor::new();
for _ in 0..10 {
predictor.record_access("default_hot");
}
assert!(
predictor.get_hot_keys().contains(&"default_hot"),
"默认阈值必须低于衰减不动点使预热可触发"
);
}
#[test]
fn test_effective_worker_count_single_worker_lower_bound() {
let mut history = LoadHistory::new(10);
for i in 0..8u64 {
history.record(LoadSample {
timestamp: Instant::now(),
cpu_usage: 0.9 - (i as f64 * 0.1),
queue_depth: 1,
latency_p99: 5,
throughput: 10,
});
}
assert_eq!(history.trend(), LoadTrend::Decreasing);
let qos = AdaptiveQos::new();
let n = qos.effective_worker_count(1, &history);
assert!(n >= 1, "单 worker 低负载下界必须 >= 1,实际 {n}");
}
#[test]
fn test_auto_optimizer() {
let optimizer = AutoOptimizer::new(100).with_hot_threshold(3);
optimizer.record_sample(LoadSample {
timestamp: Instant::now(),
cpu_usage: 0.75,
queue_depth: 200,
latency_p99: 50,
throughput: 5000,
});
optimizer.record_cache_access("session_abc");
optimizer.record_cache_access("session_abc");
optimizer.record_cache_access("session_abc");
let level = optimizer.evaluate_qos();
assert!(matches!(level, QosLevel::Normal | QosLevel::High));
let hot_keys = optimizer.get_hot_cache_keys();
assert!(!hot_keys.is_empty());
}
#[test]
fn test_auto_optimizer_replan() {
let optimizer = AutoOptimizer::new(2);
assert!(!optimizer.should_replan());
assert!(optimizer.should_replan());
assert!(!optimizer.should_replan());
optimizer.reset_replan_timer();
assert!(!optimizer.should_replan());
assert!(optimizer.should_replan());
}
#[test]
fn test_should_replan_concurrent_no_underflow() {
use std::sync::Arc;
let optimizer = Arc::new(AutoOptimizer::new(4));
let mut handles = Vec::new();
for _ in 0..8 {
let opt = Arc::clone(&optimizer);
handles.push(std::thread::spawn(move || {
let mut trues = 0u32;
for _ in 0..1000 {
if opt.should_replan() {
trues += 1;
}
}
trues
}));
}
let mut total_trues = 0u32;
for h in handles {
total_trues += h.join().unwrap();
}
assert!(
total_trues > 1000,
"并发触发次数异常(疑似下溢卡死): {}",
total_trues
);
optimizer.reset_replan_timer();
assert!(!optimizer.should_replan());
}
#[test]
fn test_auto_optimizer_clone() {
let optimizer = AutoOptimizer::new(100);
optimizer.set_qos_level(QosLevel::High);
let cloned = optimizer.clone();
assert_eq!(cloned.current_qos_level(), QosLevel::High);
}
#[test]
fn test_auto_optimizer_clone_inherits_replan_countdown() {
let optimizer = AutoOptimizer::new(4);
assert!(!optimizer.should_replan());
assert!(!optimizer.should_replan());
assert!(!optimizer.should_replan());
let cloned = optimizer.clone();
assert!(cloned.should_replan(), "clone 未继承 replan_countdown 当前值");
}
#[test]
fn test_qos_level_discriminants_locked() {
assert_eq!(QosLevel::Critical as u8, 0);
assert_eq!(QosLevel::High as u8, 1);
assert_eq!(QosLevel::Normal as u8, 2);
assert_eq!(QosLevel::Low as u8, 3);
assert_eq!(QosLevel::Background as u8, 4);
for level in [
QosLevel::Critical,
QosLevel::High,
QosLevel::Normal,
QosLevel::Low,
QosLevel::Background,
] {
assert_eq!(level.priority(), level as u8);
assert_eq!(level.to_u8(), level as u8);
assert!(!level.as_str().is_empty());
}
}
#[test]
fn test_qos_level_u8_roundtrip() {
for level in [
QosLevel::Critical,
QosLevel::High,
QosLevel::Normal,
QosLevel::Low,
QosLevel::Background,
] {
assert_eq!(QosLevel::from_u8(level.to_u8()), level);
}
assert_eq!(QosLevel::from_u8(255), QosLevel::Background);
assert_eq!(QosLevel::from_u8(5), QosLevel::Background);
}
#[test]
fn test_adaptive_qos_set_level_atomic() {
let qos = AdaptiveQos::new();
assert_eq!(qos.current_level(), QosLevel::Normal);
qos.set_level(QosLevel::Critical);
assert_eq!(qos.current_level(), QosLevel::Critical);
qos.set_level(QosLevel::Background);
assert_eq!(qos.current_level(), QosLevel::Background);
}
#[test]
fn test_adaptive_qos_clone_preserves_level() {
let qos = AdaptiveQos::new();
qos.set_level(QosLevel::High);
let cloned = qos.clone();
assert_eq!(cloned.current_level(), QosLevel::High);
cloned.set_level(QosLevel::Low);
assert_eq!(qos.current_level(), QosLevel::High);
assert_eq!(cloned.current_level(), QosLevel::Low);
}
}