Skip to main content

cbtop/adaptive_ml/
threshold.rs

1//! Learned workload thresholds and configuration.
2
3use std::time::{Duration, Instant};
4
5use super::types::{TimeSeriesFeatures, WorkloadClass};
6
7/// Learned threshold for a specific workload
8#[derive(Debug, Clone)]
9pub struct LearnedWorkloadThreshold {
10    /// Workload class
11    pub workload_class: WorkloadClass,
12    /// Learned CV threshold
13    pub cv_threshold: f64,
14    /// Confidence in the threshold (0-1)
15    pub confidence: f64,
16    /// Number of training samples
17    pub training_samples: usize,
18    /// Last update time
19    pub last_updated: Instant,
20    /// Feature means for drift detection
21    pub feature_means: Vec<f64>,
22    /// Feature standard deviations for drift detection
23    pub feature_stds: Vec<f64>,
24}
25
26impl LearnedWorkloadThreshold {
27    /// Create a new learned threshold
28    pub fn new(workload_class: WorkloadClass) -> Self {
29        Self {
30            workload_class,
31            cv_threshold: workload_class.default_cv_threshold(),
32            confidence: 0.0,
33            training_samples: 0,
34            last_updated: Instant::now(),
35            feature_means: Vec::new(),
36            feature_stds: Vec::new(),
37        }
38    }
39
40    /// Update threshold from training data
41    pub fn update(&mut self, features: &TimeSeriesFeatures, is_anomaly: bool) {
42        self.training_samples += 1;
43        self.last_updated = Instant::now();
44
45        // Update threshold based on observed CV
46        if !is_anomaly {
47            // Normal sample: threshold should be above observed CV
48            let observed_cv = features.cv;
49            let margin = 1.2; // 20% margin above normal
50
51            // Weighted update
52            let weight = 0.1; // Learning rate
53            let target = observed_cv * margin;
54
55            if target > self.cv_threshold {
56                self.cv_threshold = self.cv_threshold * (1.0 - weight) + target * weight;
57            }
58        }
59
60        // Update confidence based on sample count
61        self.confidence = (self.training_samples as f64 / 100.0).min(1.0);
62
63        // Update feature statistics for drift detection
64        let fv = features.to_vec();
65        if self.feature_means.is_empty() {
66            self.feature_means = fv.clone();
67            self.feature_stds = vec![0.0; fv.len()];
68        } else {
69            // Online mean and variance update (Welford's algorithm)
70            let n = self.training_samples as f64;
71            for (i, &val) in fv.iter().enumerate() {
72                let delta = val - self.feature_means[i];
73                self.feature_means[i] += delta / n;
74                let delta2 = val - self.feature_means[i];
75                // Update variance estimate
76                if n > 1.0 {
77                    self.feature_stds[i] = ((n - 2.0) / (n - 1.0) * self.feature_stds[i].powi(2)
78                        + delta * delta2 / n)
79                        .sqrt();
80                }
81            }
82        }
83    }
84
85    /// Check if drift is detected
86    pub fn check_drift(&self, features: &TimeSeriesFeatures) -> Option<f64> {
87        if self.feature_means.is_empty() {
88            return None;
89        }
90
91        let fv = features.to_vec();
92        let mut max_zscore = 0.0_f64;
93
94        for (i, &val) in fv.iter().enumerate() {
95            if self.feature_stds[i] > 1e-10 {
96                let zscore = ((val - self.feature_means[i]) / self.feature_stds[i]).abs();
97                max_zscore = max_zscore.max(zscore);
98            }
99        }
100
101        if max_zscore > 3.0 {
102            Some(max_zscore)
103        } else {
104            None
105        }
106    }
107
108    /// Check if threshold is stale (needs re-calibration)
109    pub fn is_stale(&self, max_age: Duration) -> bool {
110        self.last_updated.elapsed() > max_age
111    }
112}
113
114/// Configuration for ML threshold system
115#[derive(Debug, Clone)]
116pub struct MlThresholdConfig {
117    /// Minimum training samples before using learned threshold
118    pub min_training_samples: usize,
119    /// Minimum confidence to use learned threshold
120    pub min_confidence: f64,
121    /// Maximum age before threshold is stale
122    pub max_threshold_age: Duration,
123    /// Drift detection z-score threshold
124    pub drift_zscore_threshold: f64,
125    /// Conservative threshold multiplier for cold start
126    pub cold_start_multiplier: f64,
127}
128
129impl Default for MlThresholdConfig {
130    fn default() -> Self {
131        Self {
132            min_training_samples: 50,
133            min_confidence: 0.7,
134            max_threshold_age: Duration::from_secs(24 * 60 * 60), // 24 hours
135            drift_zscore_threshold: 3.0,
136            cold_start_multiplier: 1.5,
137        }
138    }
139}