1use crate::error::{OptimError, Result};
8use crate::performance_regression_detector::{
9 MetricType as RegressionMetricType, MetricValue, PerformanceMeasurement,
10};
11use serde::{Deserialize, Serialize};
12use std::collections::HashMap;
13use std::time::{Duration, SystemTime};
14
15use super::config::{
16 ComparisonOperator, GateEvaluationStrategy, GateFailureAction, GateFailureHandling,
17 GateFailureNotificationConfig, GateSeverity, GateType, MetricGate, MetricType,
18 PerformanceGatesConfig,
19};
20use super::test_execution::{CiCdTestResult, TestSuiteStatistics};
21
22#[derive(Debug, Clone)]
24pub struct PerformanceGateEvaluator {
25 pub config: PerformanceGatesConfig,
27 pub baseline_metrics: HashMap<MetricType, BaselineMetric>,
29 pub evaluation_history: Vec<GateEvaluationResult>,
31 pub gate_states: HashMap<MetricType, GateState>,
33 pub trend_analyzer: PerformanceTrendAnalyzer,
35 pub alert_manager: AlertManager,
37}
38
39#[derive(Debug, Clone, Serialize, Deserialize)]
41pub struct BaselineMetric {
42 pub metric_type: MetricType,
44 pub baseline_value: f64,
46 pub variance_threshold: f64,
48 pub confidence_interval: (f64, f64),
50 pub sample_size: usize,
52 pub last_updated: SystemTime,
54 pub statistical_significance: f64,
56}
57
58#[derive(Debug, Clone, Serialize, Deserialize)]
60pub struct GateState {
61 pub metric_type: MetricType,
63 pub status: GateStatus,
65 pub current_value: f64,
67 pub baseline_value: f64,
69 pub percentage_change: f64,
71 pub evaluated_at: SystemTime,
73 pub gate_config: MetricGate,
75 pub evaluation_details: GateEvaluationDetails,
77}
78
79#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
81pub enum GateStatus {
82 Passed,
84 Failed,
86 Warning,
88 Skipped,
90 Error,
92 NotEvaluated,
94}
95
96#[derive(Debug, Clone, Serialize, Deserialize)]
98pub struct GateEvaluationDetails {
99 pub evaluation_method: String,
101 pub statistical_tests: Vec<StatisticalTestResult>,
103 pub confidence_level: f64,
105 pub p_value: Option<f64>,
107 pub effect_size: Option<f64>,
109 pub metadata: HashMap<String, String>,
111}
112
113#[derive(Debug, Clone, Serialize, Deserialize)]
115pub struct StatisticalTestResult {
116 pub test_name: String,
118 pub test_statistic: f64,
120 pub p_value: f64,
122 pub degrees_of_freedom: Option<u32>,
124 pub conclusion: TestConclusion,
126}
127
128#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
130pub enum TestConclusion {
131 Significant,
133 NotSignificant,
135 Inconclusive,
137}
138
139#[derive(Debug, Clone, Serialize, Deserialize)]
141pub struct GateEvaluationResult {
142 pub evaluation_id: String,
144 pub timestamp: SystemTime,
146 pub overall_status: OverallGateStatus,
148 pub gate_results: Vec<IndividualGateResult>,
150 pub summary: GateEvaluationSummary,
152 pub actions_taken: Vec<GateAction>,
154 pub evaluation_duration: Duration,
156}
157
158#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
160pub enum OverallGateStatus {
161 AllPassed,
163 SomeFailed,
165 AllFailed,
167 Skipped,
169 Error,
171}
172
173#[derive(Debug, Clone, Serialize, Deserialize)]
175pub struct IndividualGateResult {
176 pub metric_type: MetricType,
178 pub status: GateStatus,
180 pub current_value: f64,
182 pub threshold_value: f64,
184 pub percentage_deviation: f64,
186 pub severity: GateSeverity,
188 pub failure_reason: Option<String>,
190 pub details: GateEvaluationDetails,
192}
193
194#[derive(Debug, Clone, Serialize, Deserialize)]
196pub struct GateEvaluationSummary {
197 pub total_gates: usize,
199 pub gates_passed: usize,
201 pub gates_failed: usize,
203 pub gates_warnings: usize,
205 pub gates_skipped: usize,
207 pub critical_failures: usize,
209 pub improvements_detected: usize,
211 pub regression_severity: RegressionSeverity,
213}
214
215#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord)]
217pub enum RegressionSeverity {
218 None,
220 Minor,
222 Moderate,
224 Major,
226 Critical,
228}
229
230#[derive(Debug, Clone, Serialize, Deserialize)]
232pub struct GateAction {
233 pub action_type: GateActionType,
235 pub description: String,
237 pub timestamp: SystemTime,
239 pub result: ActionResult,
241}
242
243#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
245pub enum GateActionType {
246 SendNotification,
248 CreateIssue,
250 FailBuild,
252 MarkUnstable,
254 LogWarning,
256 UpdateBaseline,
258 OverrideGate,
260}
261
262#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
264pub enum ActionResult {
265 Success,
267 Failed,
269 Skipped,
271}
272
273#[derive(Debug, Clone)]
275pub struct PerformanceTrendAnalyzer {
276 pub performance_history: Vec<PerformanceDataPoint>,
278 pub config: TrendAnalysisConfig,
280 pub detected_trends: HashMap<MetricType, PerformanceTrend>,
282}
283
284#[derive(Debug, Clone, Serialize, Deserialize)]
286pub struct PerformanceDataPoint {
287 pub timestamp: SystemTime,
289 pub metric_type: MetricType,
291 pub value: f64,
293 pub context: HashMap<String, String>,
295 pub quality_score: f64,
297}
298
299#[derive(Debug, Clone, Serialize, Deserialize)]
301pub struct TrendAnalysisConfig {
302 pub min_data_points: usize,
304 pub window_size: usize,
306 pub sensitivity: f64,
308 pub confidence_level: f64,
310 pub enable_seasonal_adjustment: bool,
312}
313
314#[derive(Debug, Clone, Serialize, Deserialize)]
316pub struct PerformanceTrend {
317 pub metric_type: MetricType,
319 pub direction: TrendDirection,
321 pub strength: f64,
323 pub significance: f64,
325 pub slope: f64,
327 pub duration: Duration,
329 pub confidence_interval: (f64, f64),
331 pub last_updated: SystemTime,
333}
334
335#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
337pub enum TrendDirection {
338 Improving,
340 Degrading,
342 Stable,
344 Volatile,
346}
347
348#[derive(Debug, Clone)]
350pub struct AlertManager {
351 pub config: AlertConfiguration,
353 pub active_alerts: HashMap<String, PerformanceAlert>,
355 pub alert_history: Vec<PerformanceAlert>,
357 pub escalation_rules: Vec<EscalationRule>,
359}
360
361#[derive(Debug, Clone, Serialize, Deserialize)]
363pub struct AlertConfiguration {
364 pub enabled: bool,
366 pub severity_threshold: GateSeverity,
368 pub cooldown_period: Duration,
370 pub max_alerts_per_hour: u32,
372 pub alert_channels: Vec<AlertChannel>,
374}
375
376#[derive(Debug, Clone, Serialize, Deserialize)]
378pub struct PerformanceAlert {
379 pub id: String,
381 pub alert_type: AlertType,
383 pub severity: AlertSeverity,
385 pub title: String,
387 pub description: String,
389 pub affected_metrics: Vec<MetricType>,
391 pub timestamp: SystemTime,
393 pub status: AlertStatus,
395 pub gate_evaluation_id: Option<String>,
397 pub metadata: HashMap<String, String>,
399}
400
401#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
403pub enum AlertType {
404 PerformanceRegression,
406 ThresholdBreach,
408 TrendChange,
410 GateFailure,
412 SystemAnomaly,
414}
415
416#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord)]
418pub enum AlertSeverity {
419 Info,
421 Warning,
423 Error,
425 Critical,
427}
428
429#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
431pub enum AlertStatus {
432 Active,
434 Acknowledged,
436 Resolved,
438 Suppressed,
440}
441
442#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
444pub enum AlertChannel {
445 Email,
447 Slack,
449 Webhook,
451 Dashboard,
453 Log,
455}
456
457#[derive(Debug, Clone, Serialize, Deserialize)]
459pub struct EscalationRule {
460 pub name: String,
462 pub trigger_condition: EscalationTrigger,
464 pub delay: Duration,
466 pub target_channels: Vec<AlertChannel>,
468 pub target_recipients: Vec<String>,
470 pub enabled: bool,
472}
473
474#[derive(Debug, Clone, Serialize, Deserialize)]
476pub enum EscalationTrigger {
477 TimeElapsed(Duration),
479 SeverityLevel(AlertSeverity),
481 MultipleFailures(u32),
483 Custom(String),
485}
486
487#[derive(Debug, Clone, Serialize, Deserialize)]
489pub struct GateResult {
490 pub status: OverallGateStatus,
492 pub gate_results: Vec<IndividualGateResult>,
494 pub summary: GateEvaluationSummary,
496 pub timestamp: SystemTime,
498}
499
500impl PerformanceGateEvaluator {
501 pub fn new(config: PerformanceGatesConfig) -> Result<Self> {
503 Ok(Self {
504 config,
505 baseline_metrics: HashMap::new(),
506 evaluation_history: Vec::new(),
507 gate_states: HashMap::new(),
508 trend_analyzer: PerformanceTrendAnalyzer::new(TrendAnalysisConfig::default())?,
509 alert_manager: AlertManager::new(AlertConfiguration::default())?,
510 })
511 }
512
513 pub fn evaluate_gates(
515 &mut self,
516 test_results: &[CiCdTestResult],
517 statistics: &TestSuiteStatistics,
518 ) -> Result<GateResult> {
519 let evaluation_id = uuid::Uuid::new_v4().to_string();
520 let start_time = SystemTime::now();
521
522 let current_metrics = self.extract_metrics_from_results(test_results)?;
524
525 self.update_trend_analysis(¤t_metrics)?;
527
528 let mut gate_results = Vec::new();
530 let mut overall_status = OverallGateStatus::AllPassed;
531
532 for (metric_type, gate_config) in &self.config.metric_gates {
533 if !gate_config.enabled {
534 continue;
535 }
536
537 if let Some(current_value) = current_metrics.get(metric_type) {
538 let gate_result =
539 self.evaluate_individual_gate(metric_type, *current_value, gate_config)?;
540
541 if gate_result.status == GateStatus::Failed {
542 overall_status = match overall_status {
543 OverallGateStatus::AllPassed => OverallGateStatus::SomeFailed,
544 OverallGateStatus::SomeFailed => OverallGateStatus::SomeFailed,
545 _ => OverallGateStatus::AllFailed,
546 };
547 }
548
549 gate_results.push(gate_result);
550 }
551 }
552
553 let summary = self.create_evaluation_summary(&gate_results);
555
556 let actions_taken = if overall_status != OverallGateStatus::AllPassed {
558 self.handle_gate_failures(&gate_results)?
559 } else {
560 Vec::new()
561 };
562
563 let evaluation_result = GateEvaluationResult {
565 evaluation_id: evaluation_id.clone(),
566 timestamp: start_time,
567 overall_status: overall_status.clone(),
568 gate_results: gate_results.clone(),
569 summary: summary.clone(),
570 actions_taken,
571 evaluation_duration: SystemTime::now()
572 .duration_since(start_time)
573 .unwrap_or_default(),
574 };
575
576 self.evaluation_history.push(evaluation_result);
578
579 self.update_gate_states(&gate_results)?;
581
582 self.generate_alerts(&gate_results)?;
584
585 Ok(GateResult {
586 status: overall_status,
587 gate_results,
588 summary,
589 timestamp: start_time,
590 })
591 }
592
593 fn extract_metrics_from_results(
595 &self,
596 test_results: &[CiCdTestResult],
597 ) -> Result<HashMap<MetricType, f64>> {
598 let mut metrics = HashMap::new();
599
600 if !test_results.is_empty() {
602 let execution_times: Vec<f64> = test_results
604 .iter()
605 .filter_map(|r| r.duration)
606 .map(|d| d.as_secs_f64())
607 .collect();
608
609 if !execution_times.is_empty() {
610 let avg_execution_time =
611 execution_times.iter().sum::<f64>() / execution_times.len() as f64;
612 metrics.insert(MetricType::ExecutionTime, avg_execution_time);
613 }
614
615 let memory_usage: Vec<f64> = test_results
617 .iter()
618 .map(|r| r.resource_usage.peak_memory_mb)
619 .collect();
620
621 if !memory_usage.is_empty() {
622 let avg_memory = memory_usage.iter().sum::<f64>() / memory_usage.len() as f64;
623 metrics.insert(MetricType::MemoryUsage, avg_memory);
624 }
625
626 let cpu_usage: Vec<f64> = test_results
628 .iter()
629 .map(|r| r.resource_usage.peak_cpu_percent)
630 .collect();
631
632 if !cpu_usage.is_empty() {
633 let avg_cpu = cpu_usage.iter().sum::<f64>() / cpu_usage.len() as f64;
634 metrics.insert(MetricType::CpuUsage, avg_cpu);
635 }
636
637 let total_duration: f64 = execution_times.iter().sum();
639 if total_duration > 0.0 {
640 let throughput = test_results.len() as f64 / total_duration;
641 metrics.insert(MetricType::Throughput, throughput);
642 }
643 }
644
645 Ok(metrics)
646 }
647
648 fn evaluate_individual_gate(
650 &self,
651 metric_type: &MetricType,
652 current_value: f64,
653 gate_config: &MetricGate,
654 ) -> Result<IndividualGateResult> {
655 let baseline_value = self.get_baseline_value(metric_type);
656 let threshold_value = self.calculate_threshold(baseline_value, gate_config);
657
658 let status = match gate_config.gate_type {
659 GateType::Absolute => {
660 self.evaluate_absolute_gate(current_value, threshold_value, &gate_config.operator)
661 }
662 GateType::Relative => self.evaluate_relative_gate(
663 current_value,
664 baseline_value,
665 gate_config.threshold,
666 &gate_config.operator,
667 ),
668 GateType::Statistical => {
669 self.evaluate_statistical_gate(metric_type, current_value, baseline_value)?
670 }
671 GateType::Trend => self.evaluate_trend_gate(metric_type, current_value)?,
672 };
673
674 let percentage_deviation = if baseline_value != 0.0 {
675 ((current_value - baseline_value) / baseline_value) * 100.0
676 } else {
677 0.0
678 };
679
680 let failure_reason = if status == GateStatus::Failed {
681 Some(self.create_failure_reason(
682 metric_type,
683 current_value,
684 threshold_value,
685 &gate_config.operator,
686 ))
687 } else {
688 None
689 };
690
691 Ok(IndividualGateResult {
692 metric_type: metric_type.clone(),
693 status,
694 current_value,
695 threshold_value,
696 percentage_deviation,
697 severity: gate_config.severity.clone(),
698 failure_reason,
699 details: self.create_evaluation_details(metric_type, current_value, baseline_value)?,
700 })
701 }
702
703 fn evaluate_absolute_gate(
705 &self,
706 current_value: f64,
707 threshold: f64,
708 operator: &ComparisonOperator,
709 ) -> GateStatus {
710 let passed = match operator {
711 ComparisonOperator::LessThan => current_value < threshold,
712 ComparisonOperator::LessThanOrEqual => current_value <= threshold,
713 ComparisonOperator::GreaterThan => current_value > threshold,
714 ComparisonOperator::GreaterThanOrEqual => current_value >= threshold,
715 ComparisonOperator::Equal => (current_value - threshold).abs() < f64::EPSILON,
716 ComparisonOperator::NotEqual => (current_value - threshold).abs() >= f64::EPSILON,
717 };
718
719 if passed {
720 GateStatus::Passed
721 } else {
722 GateStatus::Failed
723 }
724 }
725
726 fn evaluate_relative_gate(
728 &self,
729 current_value: f64,
730 baseline_value: f64,
731 threshold: f64,
732 operator: &ComparisonOperator,
733 ) -> GateStatus {
734 if baseline_value == 0.0 {
735 return GateStatus::Error;
736 }
737
738 let percentage_change = ((current_value - baseline_value) / baseline_value).abs();
739
740 let passed = match operator {
741 ComparisonOperator::LessThanOrEqual => percentage_change <= threshold,
742 ComparisonOperator::LessThan => percentage_change < threshold,
743 _ => false, };
745
746 if passed {
747 GateStatus::Passed
748 } else {
749 GateStatus::Failed
750 }
751 }
752
753 fn evaluate_statistical_gate(
755 &self,
756 metric_type: &MetricType,
757 current_value: f64,
758 baseline_value: f64,
759 ) -> Result<GateStatus> {
760 let difference = (current_value - baseline_value).abs();
762 let relative_difference = if baseline_value != 0.0 {
763 difference / baseline_value
764 } else {
765 difference
766 };
767
768 if relative_difference > 0.1 {
770 Ok(GateStatus::Failed)
772 } else {
773 Ok(GateStatus::Passed)
774 }
775 }
776
777 fn evaluate_trend_gate(
779 &self,
780 metric_type: &MetricType,
781 current_value: f64,
782 ) -> Result<GateStatus> {
783 if let Some(trend) = self.trend_analyzer.detected_trends.get(metric_type) {
784 match trend.direction {
785 TrendDirection::Degrading if trend.strength > 0.7 => Ok(GateStatus::Failed),
786 TrendDirection::Degrading if trend.strength > 0.4 => Ok(GateStatus::Warning),
787 _ => Ok(GateStatus::Passed),
788 }
789 } else {
790 Ok(GateStatus::Passed)
791 }
792 }
793
794 fn get_baseline_value(&self, metric_type: &MetricType) -> f64 {
796 self.baseline_metrics
797 .get(metric_type)
798 .map(|baseline| baseline.baseline_value)
799 .unwrap_or(0.0)
800 }
801
802 fn calculate_threshold(&self, baseline_value: f64, gate_config: &MetricGate) -> f64 {
804 match gate_config.gate_type {
805 GateType::Absolute => gate_config.threshold,
806 GateType::Relative => baseline_value * (1.0 + gate_config.threshold),
807 _ => gate_config.threshold,
808 }
809 }
810
811 fn create_failure_reason(
813 &self,
814 metric_type: &MetricType,
815 current_value: f64,
816 threshold: f64,
817 operator: &ComparisonOperator,
818 ) -> String {
819 format!(
820 "{:?} value {:.2} does not satisfy {:?} {:.2}",
821 metric_type, current_value, operator, threshold
822 )
823 }
824
825 fn create_evaluation_details(
827 &self,
828 metric_type: &MetricType,
829 current_value: f64,
830 baseline_value: f64,
831 ) -> Result<GateEvaluationDetails> {
832 Ok(GateEvaluationDetails {
833 evaluation_method: "threshold_comparison".to_string(),
834 statistical_tests: Vec::new(), confidence_level: 0.95,
836 p_value: None,
837 effect_size: Some((current_value - baseline_value).abs()),
838 metadata: HashMap::new(),
839 })
840 }
841
842 fn create_evaluation_summary(
844 &self,
845 gate_results: &[IndividualGateResult],
846 ) -> GateEvaluationSummary {
847 let total_gates = gate_results.len();
848 let gates_passed = gate_results
849 .iter()
850 .filter(|r| r.status == GateStatus::Passed)
851 .count();
852 let gates_failed = gate_results
853 .iter()
854 .filter(|r| r.status == GateStatus::Failed)
855 .count();
856 let gates_warnings = gate_results
857 .iter()
858 .filter(|r| r.status == GateStatus::Warning)
859 .count();
860 let gates_skipped = gate_results
861 .iter()
862 .filter(|r| r.status == GateStatus::Skipped)
863 .count();
864 let critical_failures = gate_results
865 .iter()
866 .filter(|r| r.status == GateStatus::Failed && r.severity == GateSeverity::Critical)
867 .count();
868 let improvements_detected = gate_results
869 .iter()
870 .filter(|r| r.percentage_deviation < -5.0) .count();
872
873 let regression_severity = if critical_failures > 0 {
874 RegressionSeverity::Critical
875 } else if gates_failed > total_gates / 2 {
876 RegressionSeverity::Major
877 } else if gates_failed > 0 {
878 RegressionSeverity::Moderate
879 } else {
880 RegressionSeverity::None
881 };
882
883 GateEvaluationSummary {
884 total_gates,
885 gates_passed,
886 gates_failed,
887 gates_warnings,
888 gates_skipped,
889 critical_failures,
890 improvements_detected,
891 regression_severity,
892 }
893 }
894
895 fn handle_gate_failures(
897 &mut self,
898 gate_results: &[IndividualGateResult],
899 ) -> Result<Vec<GateAction>> {
900 let mut actions = Vec::new();
901
902 let action_type = match self.config.failure_handling.failure_action {
904 GateFailureAction::FailBuild => GateActionType::FailBuild,
905 GateFailureAction::MarkUnstable => GateActionType::MarkUnstable,
906 GateFailureAction::LogWarning => GateActionType::LogWarning,
907 GateFailureAction::NotifyOnly => GateActionType::SendNotification,
908 };
909
910 let action = GateAction {
911 action_type,
912 description: format!(
913 "Gate failure action triggered for {} failed gates",
914 gate_results
915 .iter()
916 .filter(|r| r.status == GateStatus::Failed)
917 .count()
918 ),
919 timestamp: SystemTime::now(),
920 result: ActionResult::Success, };
922
923 actions.push(action);
924
925 if self.config.failure_handling.notifications.send_email
927 || self.config.failure_handling.notifications.send_slack
928 || self.config.failure_handling.notifications.send_webhooks
929 {
930 let notification_action = GateAction {
931 action_type: GateActionType::SendNotification,
932 description: "Notification sent for gate failures".to_string(),
933 timestamp: SystemTime::now(),
934 result: ActionResult::Success,
935 };
936 actions.push(notification_action);
937 }
938
939 Ok(actions)
940 }
941
942 fn update_gate_states(&mut self, gate_results: &[IndividualGateResult]) -> Result<()> {
944 for result in gate_results {
945 let baseline_value = self.get_baseline_value(&result.metric_type);
946
947 let gate_state = GateState {
948 metric_type: result.metric_type.clone(),
949 status: result.status.clone(),
950 current_value: result.current_value,
951 baseline_value,
952 percentage_change: result.percentage_deviation,
953 evaluated_at: SystemTime::now(),
954 gate_config: self
955 .config
956 .metric_gates
957 .get(&result.metric_type)
958 .cloned()
959 .unwrap_or(MetricGate {
960 gate_type: GateType::Absolute,
961 threshold: 0.0,
962 operator: ComparisonOperator::LessThanOrEqual,
963 severity: GateSeverity::Warning,
964 enabled: true,
965 }),
966 evaluation_details: result.details.clone(),
967 };
968
969 self.gate_states
970 .insert(result.metric_type.clone(), gate_state);
971 }
972
973 Ok(())
974 }
975
976 fn update_trend_analysis(&mut self, metrics: &HashMap<MetricType, f64>) -> Result<()> {
978 let timestamp = SystemTime::now();
979
980 for (metric_type, value) in metrics {
981 let data_point = PerformanceDataPoint {
982 timestamp,
983 metric_type: metric_type.clone(),
984 value: *value,
985 context: HashMap::new(),
986 quality_score: 1.0,
987 };
988
989 self.trend_analyzer.add_data_point(data_point)?;
990 }
991
992 self.trend_analyzer.analyze_trends()?;
994
995 Ok(())
996 }
997
998 fn generate_alerts(&mut self, gate_results: &[IndividualGateResult]) -> Result<()> {
1000 for result in gate_results {
1001 if result.status == GateStatus::Failed {
1002 let alert_severity = match result.severity {
1003 GateSeverity::Critical => AlertSeverity::Critical,
1004 GateSeverity::Error => AlertSeverity::Error,
1005 GateSeverity::Warning => AlertSeverity::Warning,
1006 GateSeverity::Info => AlertSeverity::Info,
1007 };
1008
1009 let alert = PerformanceAlert {
1010 id: uuid::Uuid::new_v4().to_string(),
1011 alert_type: AlertType::GateFailure,
1012 severity: alert_severity,
1013 title: format!("Performance Gate Failed: {:?}", result.metric_type),
1014 description: result
1015 .failure_reason
1016 .clone()
1017 .unwrap_or_else(|| "Gate failed".to_string()),
1018 affected_metrics: vec![result.metric_type.clone()],
1019 timestamp: SystemTime::now(),
1020 status: AlertStatus::Active,
1021 gate_evaluation_id: None,
1022 metadata: HashMap::new(),
1023 };
1024
1025 self.alert_manager.add_alert(alert)?;
1026 }
1027 }
1028
1029 Ok(())
1030 }
1031
1032 pub fn set_baseline(&mut self, metric_type: MetricType, baseline: BaselineMetric) {
1034 self.baseline_metrics.insert(metric_type, baseline);
1035 }
1036
1037 pub fn get_evaluation_history(&self) -> &[GateEvaluationResult] {
1039 &self.evaluation_history
1040 }
1041
1042 pub fn get_gate_states(&self) -> &HashMap<MetricType, GateState> {
1044 &self.gate_states
1045 }
1046}
1047
1048impl PerformanceTrendAnalyzer {
1049 pub fn new(config: TrendAnalysisConfig) -> Result<Self> {
1051 Ok(Self {
1052 performance_history: Vec::new(),
1053 config,
1054 detected_trends: HashMap::new(),
1055 })
1056 }
1057
1058 pub fn add_data_point(&mut self, data_point: PerformanceDataPoint) -> Result<()> {
1060 self.performance_history.push(data_point);
1061
1062 let max_points = self.config.window_size * 2;
1064 if self.performance_history.len() > max_points {
1065 self.performance_history
1066 .drain(0..self.performance_history.len() - max_points);
1067 }
1068
1069 Ok(())
1070 }
1071
1072 pub fn analyze_trends(&mut self) -> Result<()> {
1074 let metrics: std::collections::HashSet<MetricType> = self
1075 .performance_history
1076 .iter()
1077 .map(|dp| dp.metric_type.clone())
1078 .collect();
1079
1080 for metric_type in metrics {
1081 let metric_data: Vec<&PerformanceDataPoint> = self
1082 .performance_history
1083 .iter()
1084 .filter(|dp| dp.metric_type == metric_type)
1085 .collect();
1086
1087 if metric_data.len() >= self.config.min_data_points {
1088 let trend = self.detect_trend(&metric_data)?;
1089 self.detected_trends.insert(metric_type, trend);
1090 }
1091 }
1092
1093 Ok(())
1094 }
1095
1096 fn detect_trend(&self, data: &[&PerformanceDataPoint]) -> Result<PerformanceTrend> {
1098 let values: Vec<f64> = data.iter().map(|dp| dp.value).collect();
1099
1100 let n = values.len() as f64;
1102 let x_values: Vec<f64> = (0..values.len()).map(|i| i as f64).collect();
1103
1104 let sum_x = x_values.iter().sum::<f64>();
1105 let sum_y = values.iter().sum::<f64>();
1106 let sum_xy = x_values
1107 .iter()
1108 .zip(values.iter())
1109 .map(|(x, y)| x * y)
1110 .sum::<f64>();
1111 let sum_x2 = x_values.iter().map(|x| x * x).sum::<f64>();
1112
1113 let slope = (n * sum_xy - sum_x * sum_y) / (n * sum_x2 - sum_x * sum_x);
1114
1115 let direction = if slope > self.config.sensitivity {
1116 TrendDirection::Improving
1117 } else if slope < -self.config.sensitivity {
1118 TrendDirection::Degrading
1119 } else {
1120 TrendDirection::Stable
1121 };
1122
1123 let strength = slope.abs().min(1.0);
1124
1125 Ok(PerformanceTrend {
1126 metric_type: data[0].metric_type.clone(),
1127 direction,
1128 strength,
1129 significance: 0.8, slope,
1131 duration: data
1132 .last()
1133 .expect("unwrap failed")
1134 .timestamp
1135 .duration_since(data[0].timestamp)
1136 .unwrap_or_default(),
1137 confidence_interval: (slope - 0.1, slope + 0.1), last_updated: SystemTime::now(),
1139 })
1140 }
1141}
1142
1143impl AlertManager {
1144 pub fn new(config: AlertConfiguration) -> Result<Self> {
1146 Ok(Self {
1147 config,
1148 active_alerts: HashMap::new(),
1149 alert_history: Vec::new(),
1150 escalation_rules: Vec::new(),
1151 })
1152 }
1153
1154 pub fn add_alert(&mut self, alert: PerformanceAlert) -> Result<()> {
1156 self.active_alerts.insert(alert.id.clone(), alert.clone());
1157 self.alert_history.push(alert);
1158 Ok(())
1159 }
1160
1161 pub fn acknowledge_alert(&mut self, alert_id: &str) -> Result<()> {
1163 if let Some(alert) = self.active_alerts.get_mut(alert_id) {
1164 alert.status = AlertStatus::Acknowledged;
1165 }
1166 Ok(())
1167 }
1168
1169 pub fn resolve_alert(&mut self, alert_id: &str) -> Result<()> {
1171 if let Some(alert) = self.active_alerts.remove(alert_id) {
1172 let mut resolved_alert = alert;
1174 resolved_alert.status = AlertStatus::Resolved;
1175 self.alert_history.push(resolved_alert);
1176 }
1177 Ok(())
1178 }
1179
1180 pub fn get_active_alerts(&self) -> Vec<&PerformanceAlert> {
1182 self.active_alerts.values().collect()
1183 }
1184}
1185
1186impl Default for TrendAnalysisConfig {
1189 fn default() -> Self {
1190 Self {
1191 min_data_points: 10,
1192 window_size: 50,
1193 sensitivity: 0.1,
1194 confidence_level: 0.95,
1195 enable_seasonal_adjustment: false,
1196 }
1197 }
1198}
1199
1200impl Default for AlertConfiguration {
1201 fn default() -> Self {
1202 Self {
1203 enabled: true,
1204 severity_threshold: GateSeverity::Warning,
1205 cooldown_period: Duration::from_secs(300), max_alerts_per_hour: 10,
1207 alert_channels: vec![AlertChannel::Log],
1208 }
1209 }
1210}
1211
1212#[cfg(test)]
1213mod tests {
1214 use super::*;
1215
1216 #[test]
1217 fn test_gate_evaluator_creation() {
1218 let config = PerformanceGatesConfig::default();
1219 let evaluator = PerformanceGateEvaluator::new(config);
1220 assert!(evaluator.is_ok());
1221 }
1222
1223 #[test]
1224 fn test_gate_status_equality() {
1225 assert_eq!(GateStatus::Passed, GateStatus::Passed);
1226 assert_ne!(GateStatus::Passed, GateStatus::Failed);
1227 }
1228
1229 #[test]
1230 fn test_trend_direction() {
1231 assert_eq!(TrendDirection::Improving, TrendDirection::Improving);
1232 assert_ne!(TrendDirection::Improving, TrendDirection::Degrading);
1233 }
1234
1235 #[test]
1236 fn test_alert_severity_ordering() {
1237 assert!(AlertSeverity::Info < AlertSeverity::Warning);
1238 assert!(AlertSeverity::Warning < AlertSeverity::Error);
1239 assert!(AlertSeverity::Error < AlertSeverity::Critical);
1240 }
1241
1242 #[test]
1243 fn test_baseline_metric() {
1244 let baseline = BaselineMetric {
1245 metric_type: MetricType::ExecutionTime,
1246 baseline_value: 1.0,
1247 variance_threshold: 0.1,
1248 confidence_interval: (0.9, 1.1),
1249 sample_size: 100,
1250 last_updated: SystemTime::now(),
1251 statistical_significance: 0.95,
1252 };
1253
1254 assert_eq!(baseline.baseline_value, 1.0);
1255 assert_eq!(baseline.sample_size, 100);
1256 }
1257}