Skip to main content

trustformers_debug/
regression_detector.rs

1//! AI-powered Performance Regression Detection System
2//!
3//! This module provides advanced statistical analysis and machine learning-based
4//! detection of performance regressions in model training and inference, enabling
5//! early detection of performance degradation with high accuracy.
6
7use anyhow::Result;
8use serde::{Deserialize, Serialize};
9use std::collections::{HashMap, VecDeque};
10use std::time::SystemTime;
11use tracing::info;
12use uuid::Uuid;
13
14/// Configuration for regression detection
15#[derive(Debug, Clone, Serialize, Deserialize)]
16pub struct RegressionDetectionConfig {
17    /// Enable regression detection
18    pub enable_detection: bool,
19    /// Minimum number of data points for analysis
20    pub min_data_points: usize,
21    /// Statistical significance threshold (p-value)
22    pub significance_threshold: f64,
23    /// Minimum performance degradation percentage to trigger alert
24    pub min_degradation_threshold: f64,
25    /// Maximum historical data window in hours
26    pub max_history_hours: u64,
27    /// Smoothing factor for exponential moving averages
28    pub ema_smoothing_factor: f64,
29    /// Enable advanced ML-based detection
30    pub enable_ml_detection: bool,
31    /// Confidence threshold for ML predictions
32    pub ml_confidence_threshold: f64,
33    /// Enable seasonal adjustment
34    pub enable_seasonal_adjustment: bool,
35    /// Enable outlier detection before regression analysis
36    pub enable_outlier_filtering: bool,
37}
38
39impl Default for RegressionDetectionConfig {
40    fn default() -> Self {
41        Self {
42            enable_detection: true,
43            min_data_points: 10,
44            significance_threshold: 0.05,
45            min_degradation_threshold: 5.0, // 5% degradation
46            max_history_hours: 24,
47            ema_smoothing_factor: 0.3,
48            enable_ml_detection: true,
49            ml_confidence_threshold: 0.8,
50            enable_seasonal_adjustment: true,
51            enable_outlier_filtering: true,
52        }
53    }
54}
55
56/// Types of metrics to monitor for regressions
57#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)]
58pub enum MetricType {
59    /// Training/inference latency
60    Latency,
61    /// Memory usage
62    MemoryUsage,
63    /// CPU utilization
64    CpuUtilization,
65    /// GPU utilization
66    GpuUtilization,
67    /// Throughput (operations per second)
68    Throughput,
69    /// Model accuracy/loss
70    ModelAccuracy,
71    /// Custom metric
72    Custom(String),
73}
74
75/// Performance metric data point
76#[derive(Debug, Clone, Serialize, Deserialize)]
77pub struct MetricDataPoint {
78    pub metric_type: MetricType,
79    pub value: f64,
80    pub timestamp: SystemTime,
81    pub session_id: Uuid,
82    pub metadata: HashMap<String, String>,
83}
84
85/// Historical metric series for analysis
86#[derive(Debug, Clone, Serialize, Deserialize)]
87pub struct MetricSeries {
88    pub metric_type: MetricType,
89    pub data_points: VecDeque<MetricDataPoint>,
90    pub baseline_statistics: BaselineStatistics,
91    pub last_updated: SystemTime,
92}
93
94/// Baseline statistics for comparison
95#[derive(Debug, Clone, Serialize, Deserialize)]
96pub struct BaselineStatistics {
97    pub mean: f64,
98    pub std_dev: f64,
99    pub median: f64,
100    pub percentile_95: f64,
101    pub percentile_99: f64,
102    pub trend_slope: f64,
103    pub seasonal_pattern: Option<Vec<f64>>,
104    pub sample_count: usize,
105    pub last_computed: SystemTime,
106}
107
108/// Regression detection result
109#[derive(Debug, Clone, Serialize, Deserialize)]
110pub struct RegressionDetection {
111    pub detection_id: Uuid,
112    pub metric_type: MetricType,
113    pub regression_type: RegressionType,
114    pub severity: RegressionSeverity,
115    pub confidence: f64,
116    pub degradation_percentage: f64,
117    pub statistical_significance: f64,
118    pub affected_period: (SystemTime, SystemTime),
119    pub root_cause_analysis: RootCauseAnalysis,
120    pub recommendations: Vec<String>,
121    pub detected_at: SystemTime,
122}
123
124/// Types of performance regressions
125#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
126pub enum RegressionType {
127    /// Sudden step change in performance
128    StepChange,
129    /// Gradual degradation over time
130    GradualDegradation,
131    /// Increased variance/instability
132    VarianceIncrease,
133    /// Periodic performance drops
134    PeriodicRegression,
135    /// Outlier-driven regression
136    OutlierRegression,
137    /// Complex multi-factorial regression
138    ComplexRegression,
139}
140
141/// Severity levels for regressions
142#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord)]
143pub enum RegressionSeverity {
144    Low,
145    Medium,
146    High,
147    Critical,
148}
149
150/// Root cause analysis results
151#[derive(Debug, Clone, Serialize, Deserialize)]
152pub struct RootCauseAnalysis {
153    pub likely_causes: Vec<PotentialCause>,
154    pub correlated_metrics: Vec<String>,
155    pub environmental_factors: Vec<String>,
156    pub change_points: Vec<SystemTime>,
157    pub anomaly_score: f64,
158}
159
160/// Potential cause for performance regression
161#[derive(Debug, Clone, Serialize, Deserialize)]
162pub struct PotentialCause {
163    pub cause_type: CauseType,
164    pub description: String,
165    pub confidence: f64,
166    pub supporting_evidence: Vec<String>,
167}
168
169/// Types of potential causes
170#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
171pub enum CauseType {
172    CodeChange,
173    DataChange,
174    ResourceContention,
175    HardwareIssue,
176    ConfigurationChange,
177    EnvironmentalFactor,
178    ModelDrift,
179    Unknown,
180}
181
182/// Advanced regression detector with ML capabilities
183pub struct RegressionDetector {
184    config: RegressionDetectionConfig,
185    metric_series: HashMap<MetricType, MetricSeries>,
186    anomaly_detector: AnomalyDetector,
187    trend_analyzer: TrendAnalyzer,
188    change_point_detector: ChangePointDetector,
189    seasonal_decomposer: SeasonalDecomposer,
190    ml_predictor: Option<MLPredictor>,
191    detection_history: VecDeque<RegressionDetection>,
192}
193
194/// Statistical anomaly detector
195#[derive(Debug)]
196struct AnomalyDetector {
197    z_score_threshold: f64,
198    iqr_multiplier: f64,
199    #[allow(dead_code)]
200    isolation_forest_threshold: f64,
201}
202
203impl AnomalyDetector {
204    fn new() -> Self {
205        Self {
206            z_score_threshold: 3.0,
207            iqr_multiplier: 1.5,
208            isolation_forest_threshold: 0.1,
209        }
210    }
211
212    /// Detect outliers using multiple methods
213    fn detect_outliers(&self, values: &[f64]) -> Vec<bool> {
214        if values.is_empty() {
215            return vec![];
216        }
217
218        let z_score_outliers = self.detect_z_score_outliers(values);
219        let iqr_outliers = self.detect_iqr_outliers(values);
220
221        // Combine methods using majority voting
222        z_score_outliers
223            .iter()
224            .zip(iqr_outliers.iter())
225            .map(|(&z_outlier, &iqr_outlier)| z_outlier || iqr_outlier)
226            .collect()
227    }
228
229    fn detect_z_score_outliers(&self, values: &[f64]) -> Vec<bool> {
230        let mean = values.iter().sum::<f64>() / values.len() as f64;
231        let variance = values.iter().map(|x| (x - mean).powi(2)).sum::<f64>() / values.len() as f64;
232        let std_dev = variance.sqrt();
233
234        values
235            .iter()
236            .map(|&value| {
237                if std_dev > 0.0 {
238                    ((value - mean) / std_dev).abs() > self.z_score_threshold
239                } else {
240                    false
241                }
242            })
243            .collect()
244    }
245
246    fn detect_iqr_outliers(&self, values: &[f64]) -> Vec<bool> {
247        let mut sorted_values = values.to_vec();
248        sorted_values.sort_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal));
249
250        let q1 = Self::percentile(&sorted_values, 25.0);
251        let q3 = Self::percentile(&sorted_values, 75.0);
252        let iqr = q3 - q1;
253
254        let lower_bound = q1 - self.iqr_multiplier * iqr;
255        let upper_bound = q3 + self.iqr_multiplier * iqr;
256
257        values.iter().map(|&value| value < lower_bound || value > upper_bound).collect()
258    }
259
260    fn percentile(sorted_values: &[f64], percentile: f64) -> f64 {
261        if sorted_values.is_empty() {
262            return 0.0;
263        }
264
265        let index = (percentile / 100.0) * (sorted_values.len() - 1) as f64;
266        let lower = index.floor() as usize;
267        let upper = index.ceil() as usize;
268
269        if lower == upper {
270            sorted_values[lower]
271        } else {
272            let weight = index - lower as f64;
273            sorted_values[lower] * (1.0 - weight) + sorted_values[upper] * weight
274        }
275    }
276}
277
278/// Trend analysis for regression detection
279#[derive(Debug)]
280struct TrendAnalyzer {
281    window_size: usize,
282    significance_threshold: f64,
283}
284
285impl TrendAnalyzer {
286    fn new(window_size: usize, significance_threshold: f64) -> Self {
287        Self {
288            window_size,
289            significance_threshold,
290        }
291    }
292
293    /// Detect trend changes using linear regression
294    fn detect_trend_change(&self, values: &[f64]) -> Option<TrendChangeResult> {
295        if values.len() < self.window_size {
296            return None;
297        }
298
299        let recent_values = &values[values.len() - self.window_size..];
300        let baseline_values = if values.len() >= 2 * self.window_size {
301            &values[values.len() - 2 * self.window_size..values.len() - self.window_size]
302        } else {
303            &values[0..values.len() - self.window_size]
304        };
305
306        let recent_slope = self.calculate_slope(recent_values);
307        let baseline_slope = self.calculate_slope(baseline_values);
308
309        let slope_change = recent_slope - baseline_slope;
310        let significance = self.calculate_trend_significance(recent_values, recent_slope);
311
312        if significance < self.significance_threshold {
313            Some(TrendChangeResult {
314                slope_change,
315                recent_slope,
316                baseline_slope,
317                significance,
318                is_regression: slope_change > 0.0, // Positive slope = performance degradation
319            })
320        } else {
321            None
322        }
323    }
324
325    fn calculate_slope(&self, values: &[f64]) -> f64 {
326        if values.len() < 2 {
327            return 0.0;
328        }
329
330        let n = values.len() as f64;
331        let sum_x = (0..values.len()).sum::<usize>() as f64;
332        let sum_y = values.iter().sum::<f64>();
333        let sum_xy = values.iter().enumerate().map(|(i, &y)| i as f64 * y).sum::<f64>();
334        let sum_x_squared = (0..values.len()).map(|i| (i as f64).powi(2)).sum::<f64>();
335
336        let denominator = n * sum_x_squared - sum_x.powi(2);
337        if denominator.abs() < 1e-10 {
338            0.0
339        } else {
340            (n * sum_xy - sum_x * sum_y) / denominator
341        }
342    }
343
344    fn calculate_trend_significance(&self, values: &[f64], slope: f64) -> f64 {
345        // Simplified t-test for trend significance
346        if values.len() < 3 {
347            return 1.0;
348        }
349
350        let n = values.len() as f64;
351        let mean_x = (values.len() - 1) as f64 / 2.0;
352        let ss_x = (0..values.len()).map(|i| (i as f64 - mean_x).powi(2)).sum::<f64>();
353
354        // Calculate residuals with proper intercept
355        let mean_y = values.iter().sum::<f64>() / n;
356        let intercept = mean_y - slope * mean_x;
357        let predicted: Vec<f64> = (0..values.len()).map(|i| intercept + slope * i as f64).collect();
358
359        let residuals: Vec<f64> = values
360            .iter()
361            .zip(predicted.iter())
362            .map(|(&actual, &pred)| actual - pred)
363            .collect();
364
365        let mse = residuals.iter().map(|&r| r.powi(2)).sum::<f64>() / (n - 2.0);
366        let se_slope = (mse / ss_x).sqrt();
367
368        if se_slope > 0.0 {
369            let t_stat = slope / se_slope;
370            // Simplified p-value approximation
371            let df = n - 2.0;
372            if df > 0.0 {
373                2.0 * (1.0 - Self::t_distribution_cdf(t_stat.abs(), df))
374            } else {
375                1.0
376            }
377        } else {
378            1.0
379        }
380    }
381
382    fn t_distribution_cdf(t: f64, df: f64) -> f64 {
383        // Simplified approximation of t-distribution CDF
384        // In practice, would use a proper statistical library
385        let x = t / (df + t.powi(2)).sqrt();
386        0.5 + 0.5 * x.atan() * (2.0 / std::f64::consts::PI)
387    }
388}
389
390#[derive(Debug)]
391#[allow(dead_code)]
392struct TrendChangeResult {
393    slope_change: f64,
394    #[allow(dead_code)]
395    recent_slope: f64,
396    baseline_slope: f64,
397    significance: f64,
398    is_regression: bool,
399}
400
401/// Change point detection using statistical methods
402#[derive(Debug)]
403struct ChangePointDetector {
404    min_segment_length: usize,
405    penalty_factor: f64,
406}
407
408impl ChangePointDetector {
409    fn new(min_segment_length: usize, penalty_factor: f64) -> Self {
410        Self {
411            min_segment_length,
412            penalty_factor,
413        }
414    }
415
416    /// Detect change points using CUSUM algorithm
417    fn detect_change_points(&self, values: &[f64]) -> Vec<usize> {
418        if values.len() < 2 * self.min_segment_length {
419            return vec![];
420        }
421
422        let mut change_points = vec![];
423        let mut current_start = 0;
424
425        while current_start + 2 * self.min_segment_length <= values.len() {
426            if let Some(change_point) = self.find_next_change_point(&values[current_start..]) {
427                let absolute_change_point = current_start + change_point;
428                change_points.push(absolute_change_point);
429                current_start = absolute_change_point + self.min_segment_length;
430            } else {
431                break;
432            }
433        }
434
435        change_points
436    }
437
438    fn find_next_change_point(&self, values: &[f64]) -> Option<usize> {
439        let n = values.len();
440        if n < 2 * self.min_segment_length {
441            return None;
442        }
443
444        let mut max_statistic = 0.0;
445        let mut best_change_point = None;
446
447        for t in self.min_segment_length..n - self.min_segment_length {
448            let statistic = self.cusum_statistic(values, t);
449            if statistic > max_statistic {
450                max_statistic = statistic;
451                best_change_point = Some(t);
452            }
453        }
454
455        // Apply penalty for multiple change points
456        let threshold = self.penalty_factor * (n as f64).ln();
457        if max_statistic > threshold {
458            best_change_point
459        } else {
460            None
461        }
462    }
463
464    fn cusum_statistic(&self, values: &[f64], change_point: usize) -> f64 {
465        let segment1 = &values[0..change_point];
466        let segment2 = &values[change_point..];
467
468        let mean1 = segment1.iter().sum::<f64>() / segment1.len() as f64;
469        let mean2 = segment2.iter().sum::<f64>() / segment2.len() as f64;
470        let overall_mean = values.iter().sum::<f64>() / values.len() as f64;
471
472        let n1 = segment1.len() as f64;
473        let n2 = segment2.len() as f64;
474        let n = values.len() as f64;
475
476        // Calculate variance
477        let variance = values.iter().map(|&x| (x - overall_mean).powi(2)).sum::<f64>() / (n - 1.0);
478
479        if variance > 0.0 {
480            (n1 * (mean1 - overall_mean).powi(2) + n2 * (mean2 - overall_mean).powi(2)) / variance
481        } else {
482            0.0
483        }
484    }
485}
486
487/// Seasonal decomposition for time series analysis
488#[derive(Debug)]
489struct SeasonalDecomposer {
490    period: usize,
491    enable_decomposition: bool,
492}
493
494impl SeasonalDecomposer {
495    fn new(period: usize) -> Self {
496        Self {
497            period,
498            enable_decomposition: true,
499        }
500    }
501
502    /// Decompose time series into trend, seasonal, and residual components
503    fn decompose(&self, values: &[f64]) -> Option<SeasonalComponents> {
504        if !self.enable_decomposition || values.len() < 2 * self.period {
505            return None;
506        }
507
508        let trend = self.extract_trend(values);
509        let detrended = self.subtract_series(values, &trend);
510        let seasonal = self.extract_seasonal(&detrended);
511        let residual = self.subtract_series(&detrended, &seasonal);
512
513        Some(SeasonalComponents {
514            trend,
515            seasonal,
516            residual,
517        })
518    }
519
520    fn extract_trend(&self, values: &[f64]) -> Vec<f64> {
521        // Moving average for trend extraction
522        let window_size = self.period;
523        let mut trend = vec![0.0; values.len()];
524
525        for i in 0..values.len() {
526            let start = i.saturating_sub(window_size / 2);
527            let end = std::cmp::min(i + window_size / 2 + 1, values.len());
528
529            let sum: f64 = values[start..end].iter().sum();
530            trend[i] = sum / (end - start) as f64;
531        }
532
533        trend
534    }
535
536    fn extract_seasonal(&self, detrended: &[f64]) -> Vec<f64> {
537        let mut seasonal = vec![0.0; detrended.len()];
538        let mut seasonal_pattern = vec![0.0; self.period];
539        let mut pattern_counts = vec![0usize; self.period];
540
541        // Calculate average seasonal pattern
542        for (i, &value) in detrended.iter().enumerate() {
543            let season_index = i % self.period;
544            seasonal_pattern[season_index] += value;
545            pattern_counts[season_index] += 1;
546        }
547
548        // Normalize by counts
549        for i in 0..self.period {
550            if pattern_counts[i] > 0 {
551                seasonal_pattern[i] /= pattern_counts[i] as f64;
552            }
553        }
554
555        // Apply seasonal pattern
556        for (i, seasonal_value) in seasonal.iter_mut().enumerate() {
557            *seasonal_value = seasonal_pattern[i % self.period];
558        }
559
560        seasonal
561    }
562
563    fn subtract_series(&self, series1: &[f64], series2: &[f64]) -> Vec<f64> {
564        series1.iter().zip(series2.iter()).map(|(&a, &b)| a - b).collect()
565    }
566}
567
568#[derive(Debug, Clone, Serialize, Deserialize)]
569struct SeasonalComponents {
570    trend: Vec<f64>,
571    seasonal: Vec<f64>,
572    residual: Vec<f64>,
573}
574
575/// ML-based predictor for advanced regression detection
576#[derive(Debug)]
577struct MLPredictor {
578    #[allow(dead_code)]
579    model_type: MLModelType,
580    feature_extractor: FeatureExtractor,
581    prediction_threshold: f64,
582}
583
584#[allow(dead_code)]
585#[derive(Debug)]
586enum MLModelType {
587    IsolationForest,
588    #[allow(dead_code)]
589    LSTM,
590    AutoEncoder,
591}
592
593#[derive(Debug)]
594struct FeatureExtractor {
595    window_size: usize,
596    statistical_features: bool,
597    frequency_features: bool,
598}
599
600impl MLPredictor {
601    fn new(model_type: MLModelType, prediction_threshold: f64) -> Self {
602        Self {
603            model_type,
604            feature_extractor: FeatureExtractor {
605                window_size: 50,
606                statistical_features: true,
607                frequency_features: true,
608            },
609            prediction_threshold,
610        }
611    }
612
613    /// Predict if current pattern indicates regression
614    fn predict_regression(&self, values: &[f64]) -> Option<MLPrediction> {
615        if values.len() < self.feature_extractor.window_size {
616            return None;
617        }
618
619        let features = self.feature_extractor.extract_features(values);
620
621        // Simplified ML prediction (in practice would use trained models)
622        let anomaly_score = self.calculate_anomaly_score(&features);
623        let confidence = self.calculate_confidence(&features);
624
625        if anomaly_score > self.prediction_threshold {
626            Some(MLPrediction {
627                anomaly_score,
628                confidence,
629                feature_importance: self.calculate_feature_importance(&features),
630                predicted_severity: self.predict_severity(anomaly_score),
631            })
632        } else {
633            None
634        }
635    }
636
637    fn calculate_anomaly_score(&self, features: &[f64]) -> f64 {
638        // Simplified anomaly scoring based on feature deviation
639        let mean = features.iter().sum::<f64>() / features.len() as f64;
640        let variance =
641            features.iter().map(|x| (x - mean).powi(2)).sum::<f64>() / features.len() as f64;
642
643        variance.sqrt() / (mean.abs() + 1e-6)
644    }
645
646    fn calculate_confidence(&self, features: &[f64]) -> f64 {
647        // Simplified confidence calculation
648        let feature_consistency = 1.0
649            - (features.iter().map(|&x| (x - features[0]).abs()).sum::<f64>()
650                / (features.len() as f64 * features[0].abs() + 1e-6));
651
652        feature_consistency.max(0.0).min(1.0)
653    }
654
655    fn calculate_feature_importance(&self, features: &[f64]) -> Vec<f64> {
656        // Simplified feature importance based on magnitude
657        let max_magnitude = features.iter().map(|x| x.abs()).fold(0.0, f64::max);
658
659        if max_magnitude > 0.0 {
660            features.iter().map(|&x| x.abs() / max_magnitude).collect()
661        } else {
662            vec![0.0; features.len()]
663        }
664    }
665
666    fn predict_severity(&self, anomaly_score: f64) -> RegressionSeverity {
667        if anomaly_score > 0.8 {
668            RegressionSeverity::Critical
669        } else if anomaly_score > 0.6 {
670            RegressionSeverity::High
671        } else if anomaly_score > 0.4 {
672            RegressionSeverity::Medium
673        } else {
674            RegressionSeverity::Low
675        }
676    }
677}
678
679impl FeatureExtractor {
680    fn extract_features(&self, values: &[f64]) -> Vec<f64> {
681        let mut features = Vec::new();
682
683        if self.statistical_features {
684            features.extend(self.extract_statistical_features(values));
685        }
686
687        if self.frequency_features {
688            features.extend(self.extract_frequency_features(values));
689        }
690
691        features
692    }
693
694    fn extract_statistical_features(&self, values: &[f64]) -> Vec<f64> {
695        let mean = values.iter().sum::<f64>() / values.len() as f64;
696        let variance = values.iter().map(|x| (x - mean).powi(2)).sum::<f64>() / values.len() as f64;
697        let std_dev = variance.sqrt();
698
699        let min = values.iter().fold(f64::INFINITY, |a, &b| a.min(b));
700        let max = values.iter().fold(f64::NEG_INFINITY, |a, &b| a.max(b));
701        let range = max - min;
702
703        // Skewness
704        let skewness = if std_dev > 0.0 {
705            values.iter().map(|x| ((x - mean) / std_dev).powi(3)).sum::<f64>() / values.len() as f64
706        } else {
707            0.0
708        };
709
710        // Kurtosis
711        let kurtosis = if std_dev > 0.0 {
712            values.iter().map(|x| ((x - mean) / std_dev).powi(4)).sum::<f64>() / values.len() as f64
713                - 3.0
714        } else {
715            0.0
716        };
717
718        vec![mean, std_dev, min, max, range, skewness, kurtosis]
719    }
720
721    fn extract_frequency_features(&self, values: &[f64]) -> Vec<f64> {
722        // Simplified frequency domain features
723        let mut features = Vec::new();
724
725        // Calculate differences to get "frequencies"
726        let differences: Vec<f64> = values.windows(2).map(|w| (w[1] - w[0]).abs()).collect();
727
728        if !differences.is_empty() {
729            let mean_diff = differences.iter().sum::<f64>() / differences.len() as f64;
730            let max_diff = differences.iter().fold(0.0f64, |a, &b| a.max(b));
731            features.extend([mean_diff, max_diff]);
732        }
733
734        features
735    }
736}
737
738#[derive(Debug, Clone, Serialize, Deserialize)]
739struct MLPrediction {
740    anomaly_score: f64,
741    confidence: f64,
742    feature_importance: Vec<f64>,
743    predicted_severity: RegressionSeverity,
744}
745
746impl RegressionDetector {
747    /// Create a new regression detector
748    pub fn new(config: RegressionDetectionConfig) -> Self {
749        let ml_predictor = if config.enable_ml_detection {
750            Some(MLPredictor::new(
751                MLModelType::IsolationForest,
752                config.ml_confidence_threshold,
753            ))
754        } else {
755            None
756        };
757
758        let trend_analyzer =
759            TrendAnalyzer::new(config.min_data_points, config.significance_threshold);
760
761        Self {
762            config,
763            metric_series: HashMap::new(),
764            anomaly_detector: AnomalyDetector::new(),
765            trend_analyzer,
766            change_point_detector: ChangePointDetector::new(5, 2.0),
767            seasonal_decomposer: SeasonalDecomposer::new(24), // Hourly patterns
768            ml_predictor,
769            detection_history: VecDeque::new(),
770        }
771    }
772
773    /// Add a new metric data point
774    pub fn add_metric_data_point(&mut self, data_point: MetricDataPoint) -> Result<()> {
775        let metric_type = data_point.metric_type.clone();
776        let max_data_points = (self.config.max_history_hours * 60) as usize; // Assume 1 point per minute
777        let min_data_points = self.config.min_data_points;
778
779        // Update series data
780        let data_points_len = {
781            let series =
782                self.metric_series.entry(metric_type.clone()).or_insert_with(|| MetricSeries {
783                    metric_type: metric_type.clone(),
784                    data_points: VecDeque::new(),
785                    baseline_statistics: BaselineStatistics::default(),
786                    last_updated: SystemTime::now(),
787                });
788
789            // Add data point
790            series.data_points.push_back(data_point);
791            series.last_updated = SystemTime::now();
792
793            // Maintain window size
794            while series.data_points.len() > max_data_points {
795                series.data_points.pop_front();
796            }
797
798            series.data_points.len()
799        };
800
801        // Update baseline statistics
802        self.update_baseline_statistics(&metric_type)?;
803
804        // Check for regressions
805        if data_points_len >= min_data_points {
806            if let Some(detection) = self.detect_regression(&metric_type)? {
807                self.detection_history.push_back(detection);
808
809                // Maintain detection history size
810                while self.detection_history.len() > 1000 {
811                    self.detection_history.pop_front();
812                }
813            }
814        }
815
816        Ok(())
817    }
818
819    /// Detect regressions for a specific metric
820    pub fn detect_regression(
821        &mut self,
822        metric_type: &MetricType,
823    ) -> Result<Option<RegressionDetection>> {
824        let series = match self.metric_series.get(metric_type) {
825            Some(series) => series,
826            None => return Ok(None),
827        };
828
829        if series.data_points.len() < self.config.min_data_points {
830            return Ok(None);
831        }
832
833        let values: Vec<f64> = series.data_points.iter().map(|dp| dp.value).collect();
834
835        // Filter outliers if enabled
836        let filtered_values = if self.config.enable_outlier_filtering {
837            self.filter_outliers(&values)
838        } else {
839            values.clone()
840        };
841
842        // Multiple detection methods
843        let mut detections = Vec::new();
844
845        // 1. Statistical trend analysis
846        if let Some(trend_result) = self.trend_analyzer.detect_trend_change(&filtered_values) {
847            if trend_result.is_regression {
848                let severity = self.calculate_severity(trend_result.slope_change);
849                detections.push(RegressionDetection {
850                    detection_id: Uuid::new_v4(),
851                    metric_type: metric_type.clone(),
852                    regression_type: RegressionType::GradualDegradation,
853                    severity,
854                    confidence: 1.0 - trend_result.significance,
855                    degradation_percentage: trend_result.slope_change * 100.0,
856                    statistical_significance: trend_result.significance,
857                    affected_period: self.calculate_affected_period(series),
858                    root_cause_analysis: self.analyze_root_causes(series, &filtered_values),
859                    recommendations: self.generate_recommendations(
860                        &RegressionType::GradualDegradation,
861                        trend_result.slope_change,
862                    ),
863                    detected_at: SystemTime::now(),
864                });
865            }
866        }
867
868        // 2. Change point detection
869        let change_points = self.change_point_detector.detect_change_points(&filtered_values);
870        if !change_points.is_empty() {
871            let latest_change_point = change_points
872                .last()
873                .expect("change_points should not be empty after is_empty check");
874            let before = &filtered_values[0..*latest_change_point];
875            let after = &filtered_values[*latest_change_point..];
876
877            if !before.is_empty() && !after.is_empty() {
878                let before_mean = before.iter().sum::<f64>() / before.len() as f64;
879                let after_mean = after.iter().sum::<f64>() / after.len() as f64;
880                let degradation = ((after_mean - before_mean) / before_mean) * 100.0;
881
882                if degradation > self.config.min_degradation_threshold {
883                    detections.push(RegressionDetection {
884                        detection_id: Uuid::new_v4(),
885                        metric_type: metric_type.clone(),
886                        regression_type: RegressionType::StepChange,
887                        severity: self.calculate_severity(degradation / 100.0),
888                        confidence: 0.8,
889                        degradation_percentage: degradation,
890                        statistical_significance: 0.01, // High confidence for step changes
891                        affected_period: self.calculate_affected_period(series),
892                        root_cause_analysis: self.analyze_root_causes(series, &filtered_values),
893                        recommendations: self.generate_recommendations(
894                            &RegressionType::StepChange,
895                            degradation / 100.0,
896                        ),
897                        detected_at: SystemTime::now(),
898                    });
899                }
900            }
901        }
902
903        // 3. ML-based detection
904        if let Some(ref ml_predictor) = self.ml_predictor {
905            if let Some(ml_prediction) = ml_predictor.predict_regression(&filtered_values) {
906                detections.push(RegressionDetection {
907                    detection_id: Uuid::new_v4(),
908                    metric_type: metric_type.clone(),
909                    regression_type: RegressionType::ComplexRegression,
910                    severity: ml_prediction.predicted_severity,
911                    confidence: ml_prediction.confidence,
912                    degradation_percentage: ml_prediction.anomaly_score * 100.0,
913                    statistical_significance: 1.0 - ml_prediction.confidence,
914                    affected_period: self.calculate_affected_period(series),
915                    root_cause_analysis: self.analyze_root_causes(series, &filtered_values),
916                    recommendations: self.generate_recommendations(
917                        &RegressionType::ComplexRegression,
918                        ml_prediction.anomaly_score,
919                    ),
920                    detected_at: SystemTime::now(),
921                });
922            }
923        }
924
925        // Return the most severe detection
926        if let Some(detection) = detections.into_iter().max_by_key(|d| d.severity.clone()) {
927            info!(
928                "Regression detected for {:?}: {:.2}% degradation",
929                metric_type, detection.degradation_percentage
930            );
931            Ok(Some(detection))
932        } else {
933            Ok(None)
934        }
935    }
936
937    /// Get recent regression detections
938    pub fn get_recent_detections(&self, limit: usize) -> Vec<RegressionDetection> {
939        self.detection_history.iter().rev().take(limit).cloned().collect()
940    }
941
942    /// Get regression detections for a specific metric
943    pub fn get_detections_for_metric(&self, metric_type: &MetricType) -> Vec<RegressionDetection> {
944        self.detection_history
945            .iter()
946            .filter(|d| &d.metric_type == metric_type)
947            .cloned()
948            .collect()
949    }
950
951    /// Update baseline statistics for a metric
952    fn update_baseline_statistics(&mut self, metric_type: &MetricType) -> Result<()> {
953        let series = self.metric_series.get_mut(metric_type).ok_or_else(|| {
954            anyhow::anyhow!("Metric type {:?} not found in metric_series", metric_type)
955        })?;
956        let values: Vec<f64> = series.data_points.iter().map(|dp| dp.value).collect();
957
958        if values.is_empty() {
959            return Ok(());
960        }
961
962        let mean = values.iter().sum::<f64>() / values.len() as f64;
963        let variance = values.iter().map(|x| (x - mean).powi(2)).sum::<f64>() / values.len() as f64;
964        let std_dev = variance.sqrt();
965
966        let mut sorted_values = values.clone();
967        sorted_values.sort_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal));
968
969        let median = AnomalyDetector::percentile(&sorted_values, 50.0);
970        let percentile_95 = AnomalyDetector::percentile(&sorted_values, 95.0);
971        let percentile_99 = AnomalyDetector::percentile(&sorted_values, 99.0);
972
973        let trend_slope = self.trend_analyzer.calculate_slope(&values);
974
975        let seasonal_pattern = if self.config.enable_seasonal_adjustment {
976            self.seasonal_decomposer
977                .decompose(&values)
978                .map(|components| components.seasonal)
979        } else {
980            None
981        };
982
983        series.baseline_statistics = BaselineStatistics {
984            mean,
985            std_dev,
986            median,
987            percentile_95,
988            percentile_99,
989            trend_slope,
990            seasonal_pattern,
991            sample_count: values.len(),
992            last_computed: SystemTime::now(),
993        };
994
995        Ok(())
996    }
997
998    fn filter_outliers(&self, values: &[f64]) -> Vec<f64> {
999        let outlier_mask = self.anomaly_detector.detect_outliers(values);
1000        values
1001            .iter()
1002            .zip(outlier_mask.iter())
1003            .filter(|(_, &is_outlier)| !is_outlier)
1004            .map(|(&value, _)| value)
1005            .collect()
1006    }
1007
1008    fn calculate_severity(&self, degradation_ratio: f64) -> RegressionSeverity {
1009        let degradation_percentage = degradation_ratio.abs() * 100.0;
1010
1011        if degradation_percentage > 50.0 {
1012            RegressionSeverity::Critical
1013        } else if degradation_percentage > 25.0 {
1014            RegressionSeverity::High
1015        } else if degradation_percentage > 10.0 {
1016            RegressionSeverity::Medium
1017        } else {
1018            RegressionSeverity::Low
1019        }
1020    }
1021
1022    fn calculate_affected_period(&self, series: &MetricSeries) -> (SystemTime, SystemTime) {
1023        let start = series.data_points.front().map(|dp| dp.timestamp).unwrap_or(SystemTime::now());
1024        let end = series.data_points.back().map(|dp| dp.timestamp).unwrap_or(SystemTime::now());
1025        (start, end)
1026    }
1027
1028    fn analyze_root_causes(&self, series: &MetricSeries, values: &[f64]) -> RootCauseAnalysis {
1029        let mut likely_causes = Vec::new();
1030        let correlated_metrics = Vec::new();
1031        let environmental_factors = Vec::new();
1032
1033        // Analyze patterns to identify potential causes
1034        let change_points = self.change_point_detector.detect_change_points(values);
1035        let change_point_timestamps: Vec<SystemTime> = change_points
1036            .iter()
1037            .filter_map(|&idx| series.data_points.get(idx).map(|dp| dp.timestamp))
1038            .collect();
1039
1040        // Check for sudden changes (potential code/config changes)
1041        if !change_points.is_empty() {
1042            likely_causes.push(PotentialCause {
1043                cause_type: CauseType::CodeChange,
1044                description: "Sudden performance change detected, possibly due to code deployment"
1045                    .to_string(),
1046                confidence: 0.7,
1047                supporting_evidence: vec![format!(
1048                    "Change point detected at {} locations",
1049                    change_points.len()
1050                )],
1051            });
1052        }
1053
1054        // Check for gradual degradation (potential resource issues)
1055        let trend_slope = self.trend_analyzer.calculate_slope(values);
1056        if trend_slope > 0.01 {
1057            likely_causes.push(PotentialCause {
1058                cause_type: CauseType::ResourceContention,
1059                description:
1060                    "Gradual performance degradation suggests resource contention or memory leaks"
1061                        .to_string(),
1062                confidence: 0.6,
1063                supporting_evidence: vec![format!("Positive trend slope: {:.4}", trend_slope)],
1064            });
1065        }
1066
1067        // Calculate anomaly score
1068        let mean = values.iter().sum::<f64>() / values.len() as f64;
1069        let variance = values.iter().map(|x| (x - mean).powi(2)).sum::<f64>() / values.len() as f64;
1070        let anomaly_score = variance.sqrt() / (mean + 1e-6);
1071
1072        RootCauseAnalysis {
1073            likely_causes,
1074            correlated_metrics,
1075            environmental_factors,
1076            change_points: change_point_timestamps,
1077            anomaly_score,
1078        }
1079    }
1080
1081    fn generate_recommendations(
1082        &self,
1083        regression_type: &RegressionType,
1084        degradation: f64,
1085    ) -> Vec<String> {
1086        let mut recommendations = Vec::new();
1087
1088        match regression_type {
1089            RegressionType::StepChange => {
1090                recommendations
1091                    .push("Investigate recent deployments or configuration changes".to_string());
1092                recommendations
1093                    .push("Review system logs around the time of performance change".to_string());
1094                recommendations
1095                    .push("Consider rolling back recent changes if possible".to_string());
1096            },
1097            RegressionType::GradualDegradation => {
1098                recommendations
1099                    .push("Monitor resource utilization (CPU, memory, disk)".to_string());
1100                recommendations.push("Check for memory leaks or resource exhaustion".to_string());
1101                recommendations
1102                    .push("Review long-running processes and background tasks".to_string());
1103            },
1104            RegressionType::VarianceIncrease => {
1105                recommendations
1106                    .push("Investigate system stability and hardware issues".to_string());
1107                recommendations.push("Check for intermittent network or I/O problems".to_string());
1108            },
1109            RegressionType::ComplexRegression => {
1110                recommendations.push("Perform detailed profiling and analysis".to_string());
1111                recommendations
1112                    .push("Investigate multiple potential causes simultaneously".to_string());
1113            },
1114            _ => {
1115                recommendations.push("Perform comprehensive system analysis".to_string());
1116            },
1117        }
1118
1119        if degradation > 0.5 {
1120            recommendations.push("URGENT: Consider immediate mitigation actions".to_string());
1121            recommendations.push("Alert on-call team for immediate investigation".to_string());
1122        } else if degradation > 0.25 {
1123            recommendations.push("Schedule investigation within 24 hours".to_string());
1124        }
1125
1126        recommendations
1127    }
1128}
1129
1130impl Default for BaselineStatistics {
1131    fn default() -> Self {
1132        Self {
1133            mean: 0.0,
1134            std_dev: 0.0,
1135            median: 0.0,
1136            percentile_95: 0.0,
1137            percentile_99: 0.0,
1138            trend_slope: 0.0,
1139            seasonal_pattern: None,
1140            sample_count: 0,
1141            last_computed: SystemTime::now(),
1142        }
1143    }
1144}
1145
1146/// Integration with main debug session
1147impl crate::DebugSession {
1148    /// Enable regression detection for this debug session
1149    pub async fn enable_regression_detection(
1150        &mut self,
1151        config: RegressionDetectionConfig,
1152    ) -> Result<RegressionDetector> {
1153        let detector = RegressionDetector::new(config);
1154        info!(
1155            "Enabled regression detection for debug session {}",
1156            self.id()
1157        );
1158        Ok(detector)
1159    }
1160}
1161
1162#[cfg(test)]
1163mod tests {
1164    use super::*;
1165
1166    #[tokio::test]
1167    async fn test_regression_detector_creation() {
1168        let config = RegressionDetectionConfig::default();
1169        let detector = RegressionDetector::new(config);
1170
1171        assert!(detector.metric_series.is_empty());
1172        assert!(detector.detection_history.is_empty());
1173    }
1174
1175    #[tokio::test]
1176    async fn test_add_metric_data_point() {
1177        let config = RegressionDetectionConfig::default();
1178        let mut detector = RegressionDetector::new(config);
1179
1180        let data_point = MetricDataPoint {
1181            metric_type: MetricType::Latency,
1182            value: 100.0,
1183            timestamp: SystemTime::now(),
1184            session_id: Uuid::new_v4(),
1185            metadata: HashMap::new(),
1186        };
1187
1188        assert!(detector.add_metric_data_point(data_point).is_ok());
1189        assert_eq!(detector.metric_series.len(), 1);
1190    }
1191
1192    #[test]
1193    fn test_anomaly_detection() {
1194        let detector = AnomalyDetector::new();
1195        let values = vec![1.0, 2.0, 3.0, 2.0, 1.0, 100.0]; // 100.0 is an outlier
1196
1197        let outliers = detector.detect_outliers(&values);
1198        assert_eq!(outliers.len(), values.len());
1199        assert!(outliers[5]); // Last value should be detected as outlier
1200    }
1201
1202    #[test]
1203    fn test_trend_analysis() {
1204        let analyzer = TrendAnalyzer::new(3, 0.9);
1205        let values = [1.0, 1.1, 1.2, 10.0, 20.0, 30.0];
1206
1207        // Test that trend analyzer can calculate slopes
1208        let recent_values = &values[3..6]; // [10.0, 20.0, 30.0]
1209        let baseline_values = &values[0..3]; // [1.0, 1.1, 1.2]
1210
1211        let recent_slope = analyzer.calculate_slope(recent_values);
1212        let baseline_slope = analyzer.calculate_slope(baseline_values);
1213
1214        // Recent slope should be much higher than baseline
1215        assert!(recent_slope > baseline_slope);
1216        assert!(recent_slope > 0.0);
1217    }
1218
1219    #[test]
1220    fn test_change_point_detection() {
1221        let detector = ChangePointDetector::new(3, 2.0);
1222        let values = vec![1.0, 1.0, 1.0, 1.0, 5.0, 5.0, 5.0, 5.0]; // Change at index 4
1223
1224        let change_points = detector.detect_change_points(&values);
1225        assert!(!change_points.is_empty());
1226    }
1227
1228    #[test]
1229    fn test_seasonal_decomposition() {
1230        let decomposer = SeasonalDecomposer::new(4);
1231        let values = vec![1.0, 2.0, 3.0, 4.0, 1.0, 2.0, 3.0, 4.0, 1.0, 2.0, 3.0, 4.0];
1232
1233        let components = decomposer.decompose(&values);
1234        assert!(components.is_some());
1235
1236        let comp = components.expect("operation failed in test");
1237        assert_eq!(comp.trend.len(), values.len());
1238        assert_eq!(comp.seasonal.len(), values.len());
1239        assert_eq!(comp.residual.len(), values.len());
1240    }
1241
1242    #[test]
1243    fn test_feature_extraction() {
1244        let extractor = FeatureExtractor {
1245            window_size: 10,
1246            statistical_features: true,
1247            frequency_features: true,
1248        };
1249
1250        let values = vec![1.0, 2.0, 3.0, 4.0, 5.0, 4.0, 3.0, 2.0, 1.0, 2.0];
1251        let features = extractor.extract_features(&values);
1252
1253        assert!(!features.is_empty());
1254        assert!(features.len() >= 7); // At least statistical features
1255    }
1256}