cbtop/regression_pipeline/
analysis.rs1use super::metrics::BenchmarkMetric;
4use super::types::PipelineStatus;
5
6use super::metrics::BenchmarkResults;
7
8#[derive(Debug, Clone)]
10pub struct MetricRegression {
11 pub name: String,
13 pub baseline_mean: f64,
15 pub current_mean: f64,
17 pub percent_change: f64,
19 pub is_regression: bool,
21 pub is_warning: bool,
23 pub unit: String,
25}
26
27impl MetricRegression {
28 pub fn from_metrics(
30 baseline: &BenchmarkMetric,
31 current: &BenchmarkMetric,
32 regression_threshold: f64,
33 warning_threshold: f64,
34 ) -> Self {
35 let baseline_mean = baseline.mean();
36 let current_mean = current.mean();
37
38 let percent_change = if baseline_mean.abs() > 1e-10 {
39 ((current_mean - baseline_mean) / baseline_mean) * 100.0
40 } else {
41 0.0
42 };
43
44 let is_latency_metric = baseline.name.contains("latency")
47 || baseline.name.contains("time")
48 || baseline.name.contains("duration");
49
50 let regression_change = if is_latency_metric {
51 percent_change } else {
53 -percent_change };
55
56 Self {
57 name: baseline.name.clone(),
58 baseline_mean,
59 current_mean,
60 percent_change,
61 is_regression: regression_change >= regression_threshold,
62 is_warning: regression_change >= warning_threshold
63 && regression_change < regression_threshold,
64 unit: baseline.unit.clone(),
65 }
66 }
67}
68
69#[derive(Debug, Clone)]
71pub struct RegressionAnalysis {
72 pub baseline: BenchmarkResults,
74 pub current: BenchmarkResults,
76 pub regressions: Vec<MetricRegression>,
78 pub status: PipelineStatus,
80 pub analysis_duration_ms: u64,
82 pub summary: String,
84}
85
86impl RegressionAnalysis {
87 pub fn regression_count(&self) -> usize {
89 self.regressions.iter().filter(|r| r.is_regression).count()
90 }
91
92 pub fn warning_count(&self) -> usize {
94 self.regressions.iter().filter(|r| r.is_warning).count()
95 }
96
97 pub fn improvement_count(&self) -> usize {
99 self.regressions
100 .iter()
101 .filter(|r| !r.is_regression && !r.is_warning && r.percent_change.abs() > 1.0)
102 .filter(|r| {
103 let is_latency = r.name.contains("latency") || r.name.contains("time");
105 if is_latency {
106 r.percent_change < 0.0 } else {
108 r.percent_change > 0.0 }
110 })
111 .count()
112 }
113
114 pub fn worst_regression(&self) -> Option<&MetricRegression> {
116 self.regressions
117 .iter()
118 .filter(|r| r.is_regression)
119 .max_by(|a, b| {
120 a.percent_change
121 .abs()
122 .partial_cmp(&b.percent_change.abs())
123 .unwrap_or(std::cmp::Ordering::Equal)
124 })
125 }
126}