@@ -12,7 +12,7 @@ use collector::Bound;
12
12
use log:: debug;
13
13
use serde:: Serialize ;
14
14
15
- use std:: collections:: HashMap ;
15
+ use std:: collections:: { HashMap , HashSet } ;
16
16
use std:: error:: Error ;
17
17
use std:: hash:: Hash ;
18
18
use std:: sync:: Arc ;
@@ -96,12 +96,29 @@ pub async fn handle_compare(
96
96
let prev = comparison. prev ( & master_commits) ;
97
97
let next = comparison. next ( & master_commits) ;
98
98
let is_contiguous = comparison. is_contiguous ( & * conn, & master_commits) . await ;
99
+ let comparisons = comparison
100
+ . statistics
101
+ . into_iter ( )
102
+ . map ( |comparison| api:: comparison:: Comparison {
103
+ benchmark : comparison. benchmark . to_string ( ) ,
104
+ profile : comparison. profile . to_string ( ) ,
105
+ scenario : comparison. scenario . to_string ( ) ,
106
+ is_dodgy : comparison
107
+ . variance
108
+ . as_ref ( )
109
+ . map ( |v| v. is_dodgy ( ) )
110
+ . unwrap_or ( false ) ,
111
+ is_significant : comparison. is_significant ( ) ,
112
+ historical_statistics : comparison. variance . map ( |v| v. data ) ,
113
+ statistics : comparison. results ,
114
+ } )
115
+ . collect ( ) ;
99
116
100
117
Ok ( api:: comparison:: Response {
101
- variance : comparison. benchmark_variances . map ( |b| b. data ) ,
102
118
prev,
103
119
a : comparison. a . into ( ) ,
104
120
b : comparison. b . into ( ) ,
121
+ comparisons,
105
122
next,
106
123
is_contiguous,
107
124
} )
@@ -124,7 +141,7 @@ pub struct ComparisonSummary {
124
141
125
142
impl ComparisonSummary {
126
143
pub fn summarize_comparison ( comparison : & Comparison ) -> Option < ComparisonSummary > {
127
- let mut benchmarks = comparison. get_benchmarks ( ) ;
144
+ let mut benchmarks = comparison. get_benchmarks ( ) . collect :: < Vec < _ > > ( ) ;
128
145
// Skip empty commits, sometimes happens if there's a compiler bug or so.
129
146
if benchmarks. len ( ) == 0 {
130
147
return None ;
@@ -141,14 +158,14 @@ impl ComparisonSummary {
141
158
. min_by ( |& ( _, b1) , & ( _, b2) | cmp ( b1, b2) )
142
159
. filter ( |( _, c) | c. is_significant ( ) && !c. is_increase ( ) )
143
160
. map ( |( i, _) | i) ;
144
- let lo = lo. map ( |lo| benchmarks. remove ( lo) ) ;
161
+ let lo = lo. map ( |lo| benchmarks. remove ( lo) ) . cloned ( ) ;
145
162
let hi = benchmarks
146
163
. iter ( )
147
164
. enumerate ( )
148
165
. max_by ( |& ( _, b1) , & ( _, b2) | cmp ( b1, b2) )
149
166
. filter ( |( _, c) | c. is_significant ( ) && c. is_increase ( ) )
150
167
. map ( |( i, _) | i) ;
151
- let hi = hi. map ( |hi| benchmarks. remove ( hi) ) ;
168
+ let hi = hi. map ( |hi| benchmarks. remove ( hi) ) . cloned ( ) ;
152
169
153
170
Some ( ComparisonSummary { hi, lo } )
154
171
}
@@ -250,11 +267,30 @@ async fn compare_given_commits(
250
267
let mut responses = ctxt. statistic_series ( query. clone ( ) , aids) . await ?;
251
268
252
269
let conn = ctxt. conn ( ) . await ;
270
+ let statistics_for_a = statistics_from_series ( & mut responses) ;
271
+ let statistics_for_b = statistics_from_series ( & mut responses) ;
272
+
273
+ let variances = BenchmarkVariances :: calculate ( ctxt, a. clone ( ) , master_commits, stat) . await ?;
274
+ let statistics = statistics_for_a
275
+ . into_iter ( )
276
+ . filter_map ( |( test_case, a) | {
277
+ statistics_for_b
278
+ . get ( & test_case)
279
+ . map ( |& b| BenchmarkComparison {
280
+ benchmark : test_case. 0 ,
281
+ profile : test_case. 1 ,
282
+ scenario : test_case. 2 ,
283
+ variance : variances
284
+ . as_ref ( )
285
+ . and_then ( |v| v. data . get ( & test_case) . cloned ( ) ) ,
286
+ results : ( a, b) ,
287
+ } )
288
+ } )
289
+ . collect ( ) ;
253
290
Ok ( Some ( Comparison {
254
- benchmark_variances : BenchmarkVariances :: calculate ( ctxt, a. clone ( ) , master_commits, stat)
255
- . await ?,
256
- a : ArtifactData :: consume_one ( & * conn, a. clone ( ) , & mut responses, master_commits) . await ,
257
- b : ArtifactData :: consume_one ( & * conn, b. clone ( ) , & mut responses, master_commits) . await ,
291
+ a : ArtifactDescription :: for_artifact ( & * conn, a. clone ( ) , master_commits) . await ,
292
+ b : ArtifactDescription :: for_artifact ( & * conn, b. clone ( ) , master_commits) . await ,
293
+ statistics,
258
294
} ) )
259
295
}
260
296
@@ -280,37 +316,30 @@ fn previous_commits(
280
316
prevs
281
317
}
282
318
283
- /// Data associated with a specific artifact
319
+ /// Detailed description of a specific artifact
284
320
#[ derive( Debug , Clone ) ]
285
- pub struct ArtifactData {
321
+ pub struct ArtifactDescription {
286
322
/// The artifact in question
287
323
pub artifact : ArtifactId ,
288
324
/// The pr of the artifact if known
289
325
pub pr : Option < u32 > ,
290
- /// Statistics based on benchmark, profile and scenario
291
- pub statistics : StatisticsMap ,
292
326
/// Bootstrap data in the form "$crate" -> nanoseconds
293
327
pub bootstrap : HashMap < String , u64 > ,
294
328
}
295
329
296
- type StatisticsMap = HashMap < ( Benchmark , Profile , Scenario ) , f64 > ;
330
+ type StatisticsMap = HashMap < TestCase , f64 > ;
331
+ type TestCase = ( Benchmark , Profile , Scenario ) ;
297
332
298
- impl ArtifactData {
333
+ impl ArtifactDescription {
299
334
/// For the given `ArtifactId`, consume the first datapoint in each of the given `SeriesResponse`
300
335
///
301
336
/// It is assumed that the provided `ArtifactId` matches the artifact id of the next data
302
337
/// point for all of `SeriesResponse<T>`. If this is not true, this function will panic.
303
- async fn consume_one < ' a , T > (
338
+ async fn for_artifact (
304
339
conn : & dyn database:: Connection ,
305
340
artifact : ArtifactId ,
306
- series : & mut [ selector:: SeriesResponse < T > ] ,
307
341
master_commits : & [ collector:: MasterCommit ] ,
308
- ) -> Self
309
- where
310
- T : Iterator < Item = ( ArtifactId , Option < f64 > ) > ,
311
- {
312
- let statistics = statistics_from_series ( series) ;
313
-
342
+ ) -> Self {
314
343
let bootstrap = conn
315
344
. get_bootstrap ( & [ conn. artifact_id ( & artifact) . await ] )
316
345
. await ;
@@ -345,7 +374,6 @@ impl ArtifactData {
345
374
Self {
346
375
pr,
347
376
artifact,
348
- statistics,
349
377
bootstrap,
350
378
}
351
379
}
@@ -372,16 +400,9 @@ where
372
400
stats
373
401
}
374
402
375
- impl From < ArtifactData > for api:: comparison:: ArtifactData {
376
- fn from ( data : ArtifactData ) -> Self {
377
- let mut stats: HashMap < String , Vec < ( String , f64 ) > > = HashMap :: new ( ) ;
378
- for ( ( benchmark, profile, scenario) , value) in data. statistics {
379
- stats
380
- . entry ( format ! ( "{}-{}" , benchmark, profile) )
381
- . or_default ( )
382
- . push ( ( scenario. to_string ( ) , value) )
383
- }
384
- api:: comparison:: ArtifactData {
403
+ impl From < ArtifactDescription > for api:: comparison:: ArtifactDescription {
404
+ fn from ( data : ArtifactDescription ) -> Self {
405
+ api:: comparison:: ArtifactDescription {
385
406
commit : match data. artifact . clone ( ) {
386
407
ArtifactId :: Commit ( c) => c. sha ,
387
408
ArtifactId :: Tag ( t) => t,
@@ -392,20 +413,17 @@ impl From<ArtifactData> for api::comparison::ArtifactData {
392
413
None
393
414
} ,
394
415
pr : data. pr ,
395
- data : stats,
396
416
bootstrap : data. bootstrap ,
397
417
}
398
418
}
399
419
}
400
420
401
421
// A comparison of two artifacts
402
422
pub struct Comparison {
403
- /// Data on how variable benchmarks have historically been
404
- ///
405
- /// Is `None` if we cannot determine historical variance
406
- pub benchmark_variances : Option < BenchmarkVariances > ,
407
- pub a : ArtifactData ,
408
- pub b : ArtifactData ,
423
+ pub a : ArtifactDescription ,
424
+ pub b : ArtifactDescription ,
425
+ /// Statistics based on test case
426
+ pub statistics : HashSet < BenchmarkComparison > ,
409
427
}
410
428
411
429
impl Comparison {
@@ -437,34 +455,16 @@ impl Comparison {
437
455
next_commit ( & self . b . artifact , master_commits) . map ( |c| c. sha . clone ( ) )
438
456
}
439
457
440
- fn get_benchmarks ( & self ) -> Vec < BenchmarkComparison > {
441
- let mut result = Vec :: new ( ) ;
442
- for ( & ( benchmark, profile, scenario) , & a) in self . a . statistics . iter ( ) {
443
- if profile == Profile :: Doc {
444
- continue ;
445
- }
446
-
447
- if let Some ( & b) = self . b . statistics . get ( & ( benchmark, profile, scenario) ) {
448
- result. push ( BenchmarkComparison {
449
- benchmark,
450
- profile,
451
- scenario,
452
- results : ( a, b) ,
453
- } )
454
- }
455
- }
456
-
457
- result
458
+ fn get_benchmarks ( & self ) -> impl Iterator < Item = & BenchmarkComparison > {
459
+ self . statistics . iter ( ) . filter ( |b| b. profile != Profile :: Doc )
458
460
}
459
461
}
460
462
461
463
/// A description of the amount of variance a certain benchmark is historically
462
464
/// experiencing at a given point in time.
463
465
pub struct BenchmarkVariances {
464
- /// Variance data on a per benchmark basis
465
- /// Key: $benchmark-$profile-$cache
466
- /// Value: `BenchmarkVariance`
467
- pub data : HashMap < String , BenchmarkVariance > ,
466
+ /// Variance data on a per test case basis
467
+ pub data : HashMap < ( Benchmark , Profile , Scenario ) , BenchmarkVariance > ,
468
468
}
469
469
470
470
impl BenchmarkVariances {
@@ -493,21 +493,18 @@ impl BenchmarkVariances {
493
493
. statistic_series ( query, previous_commits. clone ( ) )
494
494
. await ?;
495
495
496
- let mut variance_data: HashMap < String , BenchmarkVariance > = HashMap :: new ( ) ;
496
+ let mut variance_data: HashMap < ( Benchmark , Profile , Scenario ) , BenchmarkVariance > =
497
+ HashMap :: new ( ) ;
497
498
for _ in previous_commits. iter ( ) {
498
- let series_data = statistics_from_series ( & mut previous_commit_series) ;
499
- for ( ( bench, profile, scenario) , value) in series_data {
500
- variance_data
501
- . entry ( format ! ( "{}-{}-{}" , bench, profile, scenario) )
502
- . or_default ( )
503
- . push ( value) ;
499
+ for ( test_case, stat) in statistics_from_series ( & mut previous_commit_series) {
500
+ variance_data. entry ( test_case) . or_default ( ) . push ( stat) ;
504
501
}
505
502
}
506
503
if variance_data. len ( ) < Self :: MIN_PREVIOUS_COMMITS {
507
504
return Ok ( None ) ;
508
505
}
509
506
510
- for ( bench, results) in variance_data. iter_mut ( ) {
507
+ for ( ( bench, _ , _ ) , results) in variance_data. iter_mut ( ) {
511
508
debug ! ( "Calculating variance for: {}" , bench) ;
512
509
results. calculate_description ( ) ;
513
510
}
@@ -576,6 +573,15 @@ impl BenchmarkVariance {
576
573
self . description = BenchmarkVarianceDescription :: Noisy ( percent_change) ;
577
574
}
578
575
}
576
+
577
+ /// Whether we can trust this benchmark or not
578
+ fn is_dodgy ( & self ) -> bool {
579
+ matches ! (
580
+ self . description,
581
+ BenchmarkVarianceDescription :: Noisy ( _)
582
+ | BenchmarkVarianceDescription :: HighlyVariable ( _)
583
+ )
584
+ }
579
585
}
580
586
581
587
#[ derive( Debug , Clone , Copy , Serialize ) ]
@@ -626,11 +632,12 @@ pub fn next_commit<'a>(
626
632
}
627
633
628
634
// A single comparison based on benchmark and cache state
629
- #[ derive( Debug ) ]
635
+ #[ derive( Debug , Clone ) ]
630
636
pub struct BenchmarkComparison {
631
637
benchmark : Benchmark ,
632
638
profile : Profile ,
633
639
scenario : Scenario ,
640
+ variance : Option < BenchmarkVariance > ,
634
641
results : ( f64 , f64 ) ,
635
642
}
636
643
@@ -706,6 +713,22 @@ impl BenchmarkComparison {
706
713
. unwrap ( ) ;
707
714
}
708
715
}
716
+ impl std:: cmp:: PartialEq for BenchmarkComparison {
717
+ fn eq ( & self , other : & Self ) -> bool {
718
+ self . benchmark == other. benchmark
719
+ && self . profile == other. profile
720
+ && self . scenario == other. scenario
721
+ }
722
+ }
723
+ impl std:: cmp:: Eq for BenchmarkComparison { }
724
+
725
+ impl std:: hash:: Hash for BenchmarkComparison {
726
+ fn hash < H : std:: hash:: Hasher > ( & self , state : & mut H ) {
727
+ self . benchmark . hash ( state) ;
728
+ self . profile . hash ( state) ;
729
+ self . scenario . hash ( state) ;
730
+ }
731
+ }
709
732
710
733
// The direction of a performance change
711
734
#[ derive( PartialEq , Eq , Hash ) ]
0 commit comments