1+ use std:: collections:: HashMap ;
12use std:: fs;
23use std:: path:: PathBuf ;
34
@@ -8,7 +9,9 @@ use crate::types::estimates::Estimates;
89pub struct BenchmarkComparison {
910 pub name : String ,
1011 pub change_percentage : f64 ,
11- pub exceeds_limit : bool ,
12+ pub exceeds_regression_limit : bool ,
13+ pub absolute_time_ns : f64 ,
14+ pub exceeds_absolute_limit : bool ,
1215}
1316
1417type RegressionError = ( String , Vec < BenchmarkComparison > ) ;
@@ -37,6 +40,29 @@ fn load_change_estimates(bench_name: &str) -> Estimates {
3740 } )
3841}
3942
43+ /// Loads absolute timing estimates from criterion's new directory for a given benchmark.
44+ /// Panics if the estimates file doesn't exist.
45+ fn load_absolute_estimates ( bench_name : & str ) -> Estimates {
46+ let estimates_path =
47+ PathBuf :: from ( "target/criterion" ) . join ( bench_name) . join ( "new/estimates.json" ) ;
48+
49+ if !estimates_path. exists ( ) {
50+ panic ! (
51+ "Estimates file not found for benchmark '{}': {}\n This likely means the benchmark \
52+ hasn't been run yet. Run the benchmark before using comparison features.",
53+ bench_name,
54+ estimates_path. display( )
55+ ) ;
56+ }
57+
58+ let data = fs:: read_to_string ( & estimates_path)
59+ . unwrap_or_else ( |e| panic ! ( "Failed to read {}: {}" , estimates_path. display( ) , e) ) ;
60+
61+ serde_json:: from_str ( & data) . unwrap_or_else ( |e| {
62+ panic ! ( "Failed to deserialize {}: {}\n Content: {}" , estimates_path. display( ) , e, data)
63+ } )
64+ }
65+
4066/// Converts change estimates to percentage.
4167/// The mean.point_estimate in change/estimates.json represents fractional change
4268/// (e.g., 0.0706 = 7.06% change).
@@ -46,33 +72,48 @@ pub(crate) fn get_regression_percentage(change_estimates: &Estimates) -> f64 {
4672
4773/// Checks all benchmarks for regressions against a specified limit.
4874/// Returns a vector of comparison results for all benchmarks.
49- /// If any benchmark exceeds the regression limit, returns an error with detailed results.
50- /// Panics if change file is not found for any benchmark.
75+ /// If any benchmark exceeds the regression limit or absolute time threshold , returns an error with
76+ /// detailed results. Panics if change file is not found for any benchmark.
5177pub fn check_regressions (
5278 bench_names : & [ & str ] ,
5379 regression_limit : f64 ,
80+ absolute_time_ns_limits : & HashMap < String , f64 > ,
5481) -> BenchmarkComparisonsResult {
5582 let mut results = Vec :: new ( ) ;
5683 let mut exceeded_count = 0 ;
5784
5885 for bench_name in bench_names {
5986 let change_estimates = load_change_estimates ( bench_name) ;
6087 let change_percentage = get_regression_percentage ( & change_estimates) ;
61- let exceeds_limit = change_percentage > regression_limit;
88+ let exceeds_regression_limit = change_percentage > regression_limit;
89+
90+ // Load absolute timing estimates.
91+ let absolute_estimates = load_absolute_estimates ( bench_name) ;
92+ let absolute_time_ns = absolute_estimates. mean . point_estimate ;
93+
94+ // Check if this benchmark has a specific absolute time limit.
95+ let exceeds_absolute_limit =
96+ if let Some ( & threshold) = absolute_time_ns_limits. get ( * bench_name) {
97+ absolute_time_ns > threshold
98+ } else {
99+ false
100+ } ;
62101
63- if exceeds_limit {
102+ if exceeds_regression_limit || exceeds_absolute_limit {
64103 exceeded_count += 1 ;
65104 }
66105
67106 results. push ( BenchmarkComparison {
68107 name : bench_name. to_string ( ) ,
69108 change_percentage,
70- exceeds_limit,
109+ exceeds_regression_limit,
110+ absolute_time_ns,
111+ exceeds_absolute_limit,
71112 } ) ;
72113 }
73114
74115 if exceeded_count > 0 {
75- let error_msg = format ! ( "{} benchmark(s) exceeded regression threshold!" , exceeded_count) ;
116+ let error_msg = format ! ( "{} benchmark(s) exceeded threshold(s) !" , exceeded_count) ;
76117 Err ( ( error_msg, results) )
77118 } else {
78119 Ok ( results)
0 commit comments