Skip to main content

cbtop/regression_pipeline/
analysis.rs

1//! Regression analysis types for comparing benchmark results.
2
3use super::metrics::BenchmarkMetric;
4use super::types::PipelineStatus;
5
6use super::metrics::BenchmarkResults;
7
8/// Comparison result for a single metric
9#[derive(Debug, Clone)]
10pub struct MetricRegression {
11    /// Metric name
12    pub name: String,
13    /// Baseline mean
14    pub baseline_mean: f64,
15    /// Current mean
16    pub current_mean: f64,
17    /// Percent change
18    pub percent_change: f64,
19    /// Whether this is a regression
20    pub is_regression: bool,
21    /// Whether this is a warning
22    pub is_warning: bool,
23    /// Unit
24    pub unit: String,
25}
26
27impl MetricRegression {
28    /// Create from two metrics
29    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        // For latency-like metrics (lower is better), positive change is regression
45        // For throughput-like metrics (higher is better), negative change is regression
46        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 // Increase is bad
52        } else {
53            -percent_change // Decrease is bad
54        };
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/// Complete regression analysis result
70#[derive(Debug, Clone)]
71pub struct RegressionAnalysis {
72    /// Baseline results
73    pub baseline: BenchmarkResults,
74    /// Current results
75    pub current: BenchmarkResults,
76    /// Per-metric regressions
77    pub regressions: Vec<MetricRegression>,
78    /// Overall status
79    pub status: PipelineStatus,
80    /// Analysis duration in milliseconds
81    pub analysis_duration_ms: u64,
82    /// Summary message
83    pub summary: String,
84}
85
86impl RegressionAnalysis {
87    /// Count significant regressions
88    pub fn regression_count(&self) -> usize {
89        self.regressions.iter().filter(|r| r.is_regression).count()
90    }
91
92    /// Count warnings
93    pub fn warning_count(&self) -> usize {
94        self.regressions.iter().filter(|r| r.is_warning).count()
95    }
96
97    /// Count improvements
98    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                // Check if change is improvement
104                let is_latency = r.name.contains("latency") || r.name.contains("time");
105                if is_latency {
106                    r.percent_change < 0.0 // Decrease is good
107                } else {
108                    r.percent_change > 0.0 // Increase is good
109                }
110            })
111            .count()
112    }
113
114    /// Get worst regression
115    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}