@@ -13,7 +13,6 @@ use collector::Bound;
13
13
use serde:: { Deserialize , Serialize } ;
14
14
15
15
use database:: CommitType ;
16
- use std:: cmp:: Ordering ;
17
16
use std:: collections:: { HashMap , HashSet } ;
18
17
use std:: error:: Error ;
19
18
use std:: fmt:: Write ;
@@ -294,7 +293,7 @@ impl Metric {
294
293
///
295
294
/// This summary only includes changes that are significant and relevant (as determined by a change's magnitude).
296
295
pub struct ArtifactComparisonSummary {
297
- /// Relevant comparisons ordered by magnitude from largest to smallest
296
+ /// Relevant comparisons ordered from most negative to most positive
298
297
relevant_comparisons : Vec < TestResultComparison > ,
299
298
/// The cached number of comparisons that are improvements
300
299
num_improvements : usize ,
@@ -321,9 +320,8 @@ impl ArtifactComparisonSummary {
321
320
. collect :: < Vec < _ > > ( ) ;
322
321
323
322
let cmp = |b1 : & TestResultComparison , b2 : & TestResultComparison | {
324
- b2. relative_change ( )
325
- . abs ( )
326
- . partial_cmp ( & b1. relative_change ( ) . abs ( ) )
323
+ b1. relative_change ( )
324
+ . partial_cmp ( & b2. relative_change ( ) )
327
325
. unwrap_or ( std:: cmp:: Ordering :: Equal )
328
326
} ;
329
327
relevant_comparisons. sort_by ( cmp) ;
@@ -439,16 +437,42 @@ impl ArtifactComparisonSummary {
439
437
. filter ( |c| c. is_regression ( ) )
440
438
}
441
439
440
+ // This is the most negative result.
442
441
fn largest_improvement ( & self ) -> Option < & TestResultComparison > {
443
442
self . relevant_comparisons
444
443
. iter ( )
445
444
. find ( |s| s. is_improvement ( ) )
446
445
}
447
446
447
+ // This is the least negative result.
448
+ fn smallest_improvement ( & self ) -> Option < & TestResultComparison > {
449
+ self . relevant_comparisons
450
+ . iter ( )
451
+ . rfind ( |s| s. is_improvement ( ) )
452
+ }
453
+
454
+ // This is the most positive result.
448
455
fn largest_regression ( & self ) -> Option < & TestResultComparison > {
456
+ self . relevant_comparisons
457
+ . iter ( )
458
+ . rfind ( |s| s. is_regression ( ) )
459
+ }
460
+
461
+ // This is the least positive result.
462
+ fn smallest_regression ( & self ) -> Option < & TestResultComparison > {
449
463
self . relevant_comparisons . iter ( ) . find ( |s| s. is_regression ( ) )
450
464
}
451
465
466
+ // This may be an improvement or a regression.
467
+ fn most_positive_change ( & self ) -> Option < & TestResultComparison > {
468
+ self . relevant_comparisons . last ( )
469
+ }
470
+
471
+ // This may be an improvement or a regression.
472
+ fn most_negative_change ( & self ) -> Option < & TestResultComparison > {
473
+ self . relevant_comparisons . first ( )
474
+ }
475
+
452
476
/// The relevance level of the entire comparison
453
477
pub fn is_relevant ( & self ) -> bool {
454
478
!self . is_empty ( )
@@ -459,7 +483,19 @@ impl ArtifactComparisonSummary {
459
483
}
460
484
461
485
pub fn largest_change ( & self ) -> Option < & TestResultComparison > {
462
- self . relevant_comparisons . first ( )
486
+ if self . num_changes ( ) == 0 {
487
+ None
488
+ } else {
489
+ let most_pos = self . most_positive_change ( ) . unwrap ( ) ;
490
+ let most_neg = self . most_negative_change ( ) . unwrap ( ) ;
491
+ let most_pos_abs = most_pos. relative_change ( ) . abs ( ) ;
492
+ let most_neg_abs = most_neg. relative_change ( ) . abs ( ) ;
493
+ if most_neg_abs. partial_cmp ( & most_pos_abs) == Some ( std:: cmp:: Ordering :: Greater ) {
494
+ Some ( most_neg)
495
+ } else {
496
+ Some ( most_pos)
497
+ }
498
+ }
463
499
}
464
500
}
465
501
@@ -534,6 +570,15 @@ pub fn write_summary_table(
534
570
. unwrap_or_else ( || "-" . to_string ( ) )
535
571
}
536
572
573
+ fn render_range < F : FnOnce ( ) -> ( f64 , f64 ) > ( count : usize , calculate : F ) -> String {
574
+ if count > 0 {
575
+ let ( a, b) = calculate ( ) ;
576
+ format ! ( "[{a:.1}%, {b:.1}%]" )
577
+ } else {
578
+ "-" . to_string ( )
579
+ }
580
+ }
581
+
537
582
// (label, mean, max, count)
538
583
let mut column_data = vec ! [ ] ;
539
584
@@ -567,54 +612,39 @@ pub fn write_summary_table(
567
612
} ,
568
613
] ) ;
569
614
570
- let largest_change = if primary. is_empty ( ) {
571
- "-" . to_string ( )
572
- } else {
573
- let largest_improvement = primary
574
- . largest_improvement ( )
575
- . map ( |c| c. relative_change ( ) )
576
- . unwrap_or ( 0.0 ) ;
577
- let largest_regression = primary
578
- . largest_regression ( )
579
- . map ( |c| c. relative_change ( ) )
580
- . unwrap_or ( 0.0 ) ;
581
- let change = if largest_improvement
582
- . abs ( )
583
- . partial_cmp ( & largest_regression. abs ( ) )
584
- . unwrap_or ( Ordering :: Equal )
585
- != Ordering :: Less
586
- {
587
- largest_improvement
588
- } else {
589
- largest_regression
590
- } ;
591
-
592
- format ! ( "{:.1}%" , change * 100.0 )
593
- } ;
594
-
595
- // max
615
+ // range
616
+ let rel_change = |r : Option < & TestResultComparison > | r. unwrap ( ) . relative_change ( ) * 100.0 ;
596
617
column_data. push ( vec ! [
597
- render_stat( primary. num_regressions, || {
598
- primary
599
- . largest_regression( )
600
- . map( |r| r. relative_change( ) * 100.0 )
618
+ render_range( primary. num_regressions, || {
619
+ (
620
+ rel_change( primary. smallest_regression( ) ) ,
621
+ rel_change( primary. largest_regression( ) ) ,
622
+ )
601
623
} ) ,
602
- render_stat( secondary. num_regressions, || {
603
- secondary
604
- . largest_regression( )
605
- . map( |r| r. relative_change( ) * 100.0 )
624
+ render_range( secondary. num_regressions, || {
625
+ (
626
+ rel_change( secondary. smallest_regression( ) ) ,
627
+ rel_change( secondary. largest_regression( ) ) ,
628
+ )
606
629
} ) ,
607
- render_stat( primary. num_improvements, || {
608
- primary
609
- . largest_improvement( )
610
- . map( |r| r. relative_change( ) * 100.0 )
630
+ render_range( primary. num_improvements, || {
631
+ (
632
+ rel_change( primary. largest_improvement( ) ) ,
633
+ rel_change( primary. smallest_improvement( ) ) ,
634
+ )
611
635
} ) ,
612
- render_stat( secondary. num_improvements, || {
613
- secondary
614
- . largest_improvement( )
615
- . map( |r| r. relative_change( ) * 100.0 )
636
+ render_range( secondary. num_improvements, || {
637
+ (
638
+ rel_change( secondary. largest_improvement( ) ) ,
639
+ rel_change( secondary. smallest_improvement( ) ) ,
640
+ )
641
+ } ) ,
642
+ render_range( primary. num_regressions + primary. num_improvements, || {
643
+ (
644
+ rel_change( primary. most_negative_change( ) ) ,
645
+ rel_change( primary. most_positive_change( ) ) ,
646
+ )
616
647
} ) ,
617
- largest_change,
618
648
] ) ;
619
649
620
650
// count
@@ -631,7 +661,7 @@ pub fn write_summary_table(
631
661
let column_labels = [
632
662
metric,
633
663
format ! ( "mean{}" , if with_footnotes { "[^1]" } else { "" } ) ,
634
- "max " . to_string ( ) ,
664
+ "range " . to_string ( ) ,
635
665
format ! ( "count{}" , if with_footnotes { "[^2]" } else { "" } ) ,
636
666
] ;
637
667
let counts: Vec < usize > = column_labels. iter ( ) . map ( |s| s. chars ( ) . count ( ) ) . collect ( ) ;
@@ -1385,11 +1415,11 @@ mod tests {
1385
1415
( Category :: Primary , 1.0 , 3.0 ) ,
1386
1416
] ,
1387
1417
r#"
1388
- | Regressions ❌ <br /> (primary) | 146.7% | 200.0% | 3 |
1389
- | Regressions ❌ <br /> (secondary) | - | - | 0 |
1390
- | Improvements ✅ <br /> (primary) | - | - | 0 |
1391
- | Improvements ✅ <br /> (secondary) | - | - | 0 |
1392
- | All ❌✅ (primary) | 146.7% | 200.0% | 3 |
1418
+ | Regressions ❌ <br /> (primary) | 146.7% | [100.0%, 200.0%] | 3 |
1419
+ | Regressions ❌ <br /> (secondary) | - | - | 0 |
1420
+ | Improvements ✅ <br /> (primary) | - | - | 0 |
1421
+ | Improvements ✅ <br /> (secondary) | - | - | 0 |
1422
+ | All ❌✅ (primary) | 146.7% | [100.0%, 200.0%] | 3 |
1393
1423
"#
1394
1424
. trim_start ( ) ,
1395
1425
) ;
@@ -1404,11 +1434,11 @@ mod tests {
1404
1434
( Category :: Primary , 4.0 , 1.0 ) ,
1405
1435
] ,
1406
1436
r#"
1407
- | Regressions ❌ <br /> (primary) | - | - | 0 |
1408
- | Regressions ❌ <br /> (secondary) | - | - | 0 |
1409
- | Improvements ✅ <br /> (primary) | -71.7% | -80.0% | 3 |
1410
- | Improvements ✅ <br /> (secondary) | - | - | 0 |
1411
- | All ❌✅ (primary) | -71.7% | -80.0% | 3 |
1437
+ | Regressions ❌ <br /> (primary) | - | - | 0 |
1438
+ | Regressions ❌ <br /> (secondary) | - | - | 0 |
1439
+ | Improvements ✅ <br /> (primary) | -71.7% | [ -80.0%, -60.0%] | 3 |
1440
+ | Improvements ✅ <br /> (secondary) | - | - | 0 |
1441
+ | All ❌✅ (primary) | -71.7% | [ -80.0%, -60.0%] | 3 |
1412
1442
"#
1413
1443
. trim_start ( ) ,
1414
1444
) ;
@@ -1423,11 +1453,11 @@ mod tests {
1423
1453
( Category :: Secondary , 4.0 , 1.0 ) ,
1424
1454
] ,
1425
1455
r#"
1426
- | Regressions ❌ <br /> (primary) | - | - | 0 |
1427
- | Regressions ❌ <br /> (secondary) | - | - | 0 |
1428
- | Improvements ✅ <br /> (primary) | - | - | 0 |
1429
- | Improvements ✅ <br /> (secondary) | -71.7% | -80.0% | 3 |
1430
- | All ❌✅ (primary) | - | - | 0 |
1456
+ | Regressions ❌ <br /> (primary) | - | - | 0 |
1457
+ | Regressions ❌ <br /> (secondary) | - | - | 0 |
1458
+ | Improvements ✅ <br /> (primary) | - | - | 0 |
1459
+ | Improvements ✅ <br /> (secondary) | -71.7% | [ -80.0%, -60.0%] | 3 |
1460
+ | All ❌✅ (primary) | - | - | 0 |
1431
1461
"#
1432
1462
. trim_start ( ) ,
1433
1463
) ;
@@ -1442,11 +1472,11 @@ mod tests {
1442
1472
( Category :: Secondary , 1.0 , 3.0 ) ,
1443
1473
] ,
1444
1474
r#"
1445
- | Regressions ❌ <br /> (primary) | - | - | 0 |
1446
- | Regressions ❌ <br /> (secondary) | 146.7% | 200.0% | 3 |
1447
- | Improvements ✅ <br /> (primary) | - | - | 0 |
1448
- | Improvements ✅ <br /> (secondary) | - | - | 0 |
1449
- | All ❌✅ (primary) | - | - | 0 |
1475
+ | Regressions ❌ <br /> (primary) | - | - | 0 |
1476
+ | Regressions ❌ <br /> (secondary) | 146.7% | [100.0%, 200.0%] | 3 |
1477
+ | Improvements ✅ <br /> (primary) | - | - | 0 |
1478
+ | Improvements ✅ <br /> (secondary) | - | - | 0 |
1479
+ | All ❌✅ (primary) | - | - | 0 |
1450
1480
"#
1451
1481
. trim_start ( ) ,
1452
1482
) ;
@@ -1462,11 +1492,11 @@ mod tests {
1462
1492
( Category :: Primary , 4.0 , 1.0 ) ,
1463
1493
] ,
1464
1494
r#"
1465
- | Regressions ❌ <br /> (primary) | 150.0% | 200.0% | 2 |
1466
- | Regressions ❌ <br /> (secondary) | - | - | 0 |
1467
- | Improvements ✅ <br /> (primary) | -62.5% | -75.0% | 2 |
1468
- | Improvements ✅ <br /> (secondary) | - | - | 0 |
1469
- | All ❌✅ (primary) | 43.8% | 200.0% | 4 |
1495
+ | Regressions ❌ <br /> (primary) | 150.0% | [100.0%, 200.0%] | 2 |
1496
+ | Regressions ❌ <br /> (secondary) | - | - | 0 |
1497
+ | Improvements ✅ <br /> (primary) | -62.5% | [ -75.0%, -50.0%] | 2 |
1498
+ | Improvements ✅ <br /> (secondary) | - | - | 0 |
1499
+ | All ❌✅ (primary) | 43.8% | [-75.0%, 200.0%] | 4 |
1470
1500
"#
1471
1501
. trim_start ( ) ,
1472
1502
) ;
@@ -1484,11 +1514,11 @@ mod tests {
1484
1514
( Category :: Primary , 4.0 , 1.0 ) ,
1485
1515
] ,
1486
1516
r#"
1487
- | Regressions ❌ <br /> (primary) | 150.0% | 200.0% | 2 |
1488
- | Regressions ❌ <br /> (secondary) | 100.0% | 100.0% | 1 |
1489
- | Improvements ✅ <br /> (primary) | -62.5% | -75.0% | 2 |
1490
- | Improvements ✅ <br /> (secondary) | -66.7% | -66.7% | 1 |
1491
- | All ❌✅ (primary) | 43.8% | 200.0% | 4 |
1517
+ | Regressions ❌ <br /> (primary) | 150.0% | [100.0%, 200.0%] | 2 |
1518
+ | Regressions ❌ <br /> (secondary) | 100.0% | [ 100.0%, 100.0%] | 1 |
1519
+ | Improvements ✅ <br /> (primary) | -62.5% | [ -75.0%, -50.0%] | 2 |
1520
+ | Improvements ✅ <br /> (secondary) | -66.7% | [ -66.7%, -66.7%] | 1 |
1521
+ | All ❌✅ (primary) | 43.8% | [-75.0%, 200.0%] | 4 |
1492
1522
"#
1493
1523
. trim_start ( ) ,
1494
1524
) ;
@@ -1502,11 +1532,11 @@ mod tests {
1502
1532
( Category :: Primary , 5.0 , 6.0 ) ,
1503
1533
] ,
1504
1534
r#"
1505
- | Regressions ❌ <br /> (primary) | 20.0% | 20.0% | 1 |
1506
- | Regressions ❌ <br /> (secondary) | - | - | 0 |
1507
- | Improvements ✅ <br /> (primary) | -50.0% | -50.0% | 1 |
1508
- | Improvements ✅ <br /> (secondary) | - | - | 0 |
1509
- | All ❌✅ (primary) | -15.0% | -50.0% | 2 |
1535
+ | Regressions ❌ <br /> (primary) | 20.0% | [ 20.0%, 20.0%] | 1 |
1536
+ | Regressions ❌ <br /> (secondary) | - | - | 0 |
1537
+ | Improvements ✅ <br /> (primary) | -50.0% | [ -50.0%, -50.0%] | 1 |
1538
+ | Improvements ✅ <br /> (secondary) | - | - | 0 |
1539
+ | All ❌✅ (primary) | -15.0% | [ -50.0%, 20.0%] | 2 |
1510
1540
"#
1511
1541
. trim_start ( ) ,
1512
1542
) ;
@@ -1520,11 +1550,11 @@ mod tests {
1520
1550
( Category :: Primary , 6.0 , 5.0 ) ,
1521
1551
] ,
1522
1552
r#"
1523
- | Regressions ❌ <br /> (primary) | 100.0% | 100.0% | 1 |
1524
- | Regressions ❌ <br /> (secondary) | - | - | 0 |
1525
- | Improvements ✅ <br /> (primary) | -16.7% | -16.7% | 1 |
1526
- | Improvements ✅ <br /> (secondary) | - | - | 0 |
1527
- | All ❌✅ (primary) | 41.7% | 100.0% | 2 |
1553
+ | Regressions ❌ <br /> (primary) | 100.0% | [ 100.0%, 100.0%] | 1 |
1554
+ | Regressions ❌ <br /> (secondary) | - | - | 0 |
1555
+ | Improvements ✅ <br /> (primary) | -16.7% | [ -16.7%, -16.7%] | 1 |
1556
+ | Improvements ✅ <br /> (secondary) | - | - | 0 |
1557
+ | All ❌✅ (primary) | 41.7% | [-16.7%, 100.0%] | 2 |
1528
1558
"#
1529
1559
. trim_start ( ) ,
1530
1560
) ;
@@ -1574,7 +1604,7 @@ mod tests {
1574
1604
1575
1605
let mut result = String :: new ( ) ;
1576
1606
write_summary_table ( & primary, & secondary, true , true , & mut result) ;
1577
- let header = "| (instructions:u) | mean[^1] | max | count[^2] |\n |:----------------:|:--------:|:---:|:---------:|\n " ;
1607
+ let header = "| (instructions:u) | mean[^1] | range | count[^2] |\n |:----------------:|:--------:|:-- ---:|:---------:|\n " ;
1578
1608
assert_eq ! ( result, format!( "{header}{expected}" ) ) ;
1579
1609
}
1580
1610
}
0 commit comments