@@ -13,10 +13,12 @@ use collector::Bound;
13
13
use serde:: { Deserialize , Serialize } ;
14
14
15
15
use database:: CommitType ;
16
+ use std:: cmp;
16
17
use std:: collections:: { HashMap , HashSet } ;
17
18
use std:: error:: Error ;
18
19
use std:: fmt:: Write ;
19
20
use std:: hash:: Hash ;
21
+ use std:: iter;
20
22
use std:: sync:: Arc ;
21
23
22
24
type BoxedError = Box < dyn Error + Send + Sync > ;
@@ -95,7 +97,7 @@ pub async fn handle_triage(
95
97
. clone ( )
96
98
. summarize_by_category ( & benchmark_map) ;
97
99
let mut result = String :: from ( "**Summary**:\n \n " ) ;
98
- write_summary_table ( & primary, & secondary, false , true , & mut result) ;
100
+ write_summary_table ( & primary, & secondary, true , & mut result) ;
99
101
result
100
102
}
101
103
None => String :: from ( "**ERROR**: no data found for end bound" ) ,
@@ -330,7 +332,7 @@ impl ArtifactComparisonSummary {
330
332
let cmp = |b1 : & TestResultComparison , b2 : & TestResultComparison | {
331
333
b1. relative_change ( )
332
334
. partial_cmp ( & b2. relative_change ( ) )
333
- . unwrap_or ( std :: cmp:: Ordering :: Equal )
335
+ . unwrap_or ( cmp:: Ordering :: Equal )
334
336
} ;
335
337
relevant_comparisons. sort_by ( cmp) ;
336
338
@@ -498,7 +500,7 @@ impl ArtifactComparisonSummary {
498
500
let most_neg = self . most_negative_change ( ) . unwrap ( ) ;
499
501
let most_pos_abs = most_pos. relative_change ( ) . abs ( ) ;
500
502
let most_neg_abs = most_neg. relative_change ( ) . abs ( ) ;
501
- if most_neg_abs. partial_cmp ( & most_pos_abs) == Some ( std :: cmp:: Ordering :: Greater ) {
503
+ if most_neg_abs. partial_cmp ( & most_pos_abs) == Some ( cmp:: Ordering :: Greater ) {
502
504
Some ( most_neg)
503
505
} else {
504
506
Some ( most_pos)
@@ -547,7 +549,7 @@ async fn write_triage_summary(
547
549
let link = & compare_link ( start, end) ;
548
550
write ! ( & mut result, " [(Comparison Link)]({})\n \n " , link) . unwrap ( ) ;
549
551
550
- write_summary_table ( & primary, & secondary, false , true , & mut result) ;
552
+ write_summary_table ( & primary, & secondary, true , & mut result) ;
551
553
552
554
result
553
555
}
@@ -556,7 +558,6 @@ async fn write_triage_summary(
556
558
pub fn write_summary_table (
557
559
primary : & ArtifactComparisonSummary ,
558
560
secondary : & ArtifactComparisonSummary ,
559
- with_footnotes : bool ,
560
561
include_metric : bool ,
561
562
result : & mut String ,
562
563
) {
@@ -569,7 +570,7 @@ pub fn write_summary_table(
569
570
. map ( |m| format ! ( "({})" , m. metric. as_str( ) ) )
570
571
} )
571
572
. flatten ( )
572
- . unwrap_or_else ( || String :: from ( " " ) ) ;
573
+ . unwrap_or_else ( || " " . to_string ( ) ) ;
573
574
574
575
fn render_stat < F : FnOnce ( ) -> Option < f64 > > ( count : usize , calculate : F ) -> String {
575
576
let value = if count > 0 { calculate ( ) } else { None } ;
@@ -588,10 +589,10 @@ pub fn write_summary_table(
588
589
}
589
590
590
591
// (label, mean, max, count)
591
- let mut column_data = vec ! [ ] ;
592
+ let mut columns = vec ! [ ] ;
592
593
593
594
// label
594
- column_data . push ( vec ! [
595
+ columns . push ( vec ! [
595
596
"Regressions ❌ <br /> (primary)" . to_string( ) ,
596
597
"Regressions ❌ <br /> (secondary)" . to_string( ) ,
597
598
"Improvements ✅ <br /> (primary)" . to_string( ) ,
@@ -600,7 +601,7 @@ pub fn write_summary_table(
600
601
] ) ;
601
602
602
603
// mean
603
- column_data . push ( vec ! [
604
+ columns . push ( vec ! [
604
605
render_stat( primary. num_regressions, || {
605
606
Some ( primary. arithmetic_mean_of_regressions( ) )
606
607
} ) ,
@@ -622,7 +623,7 @@ pub fn write_summary_table(
622
623
623
624
// range
624
625
let rel_change = |r : Option < & TestResultComparison > | r. unwrap ( ) . relative_change ( ) * 100.0 ;
625
- column_data . push ( vec ! [
626
+ columns . push ( vec ! [
626
627
render_range( primary. num_regressions, || {
627
628
(
628
629
rel_change( primary. smallest_regression( ) ) ,
@@ -656,7 +657,7 @@ pub fn write_summary_table(
656
657
] ) ;
657
658
658
659
// count
659
- column_data . push ( vec ! [
660
+ columns . push ( vec ! [
660
661
primary. num_regressions. to_string( ) ,
661
662
secondary. num_regressions. to_string( ) ,
662
663
primary. num_improvements. to_string( ) ,
@@ -668,40 +669,70 @@ pub fn write_summary_table(
668
669
// easy to read for anyone who is viewing the Markdown source.
669
670
let column_labels = [
670
671
metric,
671
- format ! ( "mean{}" , if with_footnotes { "[^1]" } else { "" } ) ,
672
+ "mean" . to_string ( ) ,
672
673
"range" . to_string ( ) ,
673
- format ! ( "count{}" , if with_footnotes { "[^2]" } else { "" } ) ,
674
+ "count" . to_string ( ) ,
674
675
] ;
675
- let counts: Vec < usize > = column_labels. iter ( ) . map ( |s| s. chars ( ) . count ( ) ) . collect ( ) ;
676
- for column in & column_labels {
677
- write ! ( result, "| {} " , column) . unwrap ( ) ;
676
+
677
+ // Calculate the console width of a string, allowing for double-width
678
+ // chars. The `unicode_width` crate does this properly, but with only two
679
+ // emoji in use we can do it easily ourselves.
680
+ let width = |s : & str | {
681
+ s. chars ( )
682
+ . map ( |c| if c == '❌' || c == '✅' { 2 } else { 1 } )
683
+ . sum ( )
684
+ } ;
685
+
686
+ // Get the width of each column.
687
+ let column_widths: Vec < usize > = column_labels
688
+ . iter ( )
689
+ . zip ( columns. iter ( ) )
690
+ . map ( |( column_label, column) | {
691
+ // Get the maximum width of the column data cells and the column label.
692
+ column
693
+ . iter ( )
694
+ . chain ( iter:: once ( column_label) )
695
+ . map ( |cell| width ( cell) )
696
+ . max ( )
697
+ . unwrap ( )
698
+ } )
699
+ . collect ( ) ;
700
+
701
+ // Write column labels.
702
+ for ( column_label, & column_width) in column_labels. iter ( ) . zip ( column_widths. iter ( ) ) {
703
+ write ! (
704
+ result,
705
+ "| {}{} " ,
706
+ column_label,
707
+ " " . repeat( column_width - width( column_label) )
708
+ )
709
+ . unwrap ( ) ;
678
710
}
679
711
result. push_str ( "|\n " ) ;
680
- for & count in & counts {
681
- write ! ( result, "|:{}:" , "-" . repeat( count) ) . unwrap ( ) ;
712
+
713
+ // Write lines under the column labels.
714
+ for & column_width in & column_widths {
715
+ write ! ( result, "|:{}:" , "-" . repeat( column_width) ) . unwrap ( ) ;
682
716
}
683
717
result. push_str ( "|\n " ) ;
684
718
685
- for row in 0 ..5 {
686
- let row_data = column_data. iter ( ) . map ( |rows| rows[ row] . clone ( ) ) ;
687
- debug_assert_eq ! ( row_data. len( ) , column_labels. len( ) ) ;
688
- for ( column, & count) in row_data. zip ( & counts) {
689
- write ! ( result, "| {:<1$} " , column, count) . unwrap ( ) ;
719
+ // Write the column data.
720
+ for row_idx in 0 ..5 {
721
+ let row = columns. iter ( ) . map ( |rows| rows[ row_idx] . clone ( ) ) ;
722
+ assert_eq ! ( row. len( ) , column_labels. len( ) ) ;
723
+ for ( cell, & column_width) in row. zip ( column_widths. iter ( ) ) {
724
+ write ! (
725
+ result,
726
+ "| {}{} " ,
727
+ cell,
728
+ " " . repeat( column_width - width( & cell) )
729
+ )
730
+ . unwrap ( ) ;
690
731
}
691
732
result. push_str ( "|\n " ) ;
692
733
}
693
734
}
694
735
695
- pub fn write_summary_table_footer ( result : & mut String ) {
696
- writeln ! (
697
- result,
698
- r#"
699
- [^1]: *the arithmetic mean of the percent change*
700
- [^2]: *number of relevant changes*"#
701
- )
702
- . unwrap ( ) ;
703
- }
704
-
705
736
/// Compare two bounds on a given stat
706
737
///
707
738
/// Returns Ok(None) when no data for the end bound is present
@@ -1051,7 +1082,7 @@ impl HistoricalData {
1051
1082
. zip ( self . data . iter ( ) )
1052
1083
. map ( |( d, & r) | d / r)
1053
1084
. collect :: < Vec < _ > > ( ) ;
1054
- deltas. sort_by ( |d1, d2| d1. partial_cmp ( d2) . unwrap_or ( std :: cmp:: Ordering :: Equal ) ) ;
1085
+ deltas. sort_by ( |d1, d2| d1. partial_cmp ( d2) . unwrap_or ( cmp:: Ordering :: Equal ) ) ;
1055
1086
deltas
1056
1087
}
1057
1088
@@ -1253,15 +1284,15 @@ impl TestResultComparison {
1253
1284
}
1254
1285
}
1255
1286
1256
- impl std :: cmp:: PartialEq for TestResultComparison {
1287
+ impl cmp:: PartialEq for TestResultComparison {
1257
1288
fn eq ( & self , other : & Self ) -> bool {
1258
1289
self . benchmark == other. benchmark
1259
1290
&& self . profile == other. profile
1260
1291
&& self . scenario == other. scenario
1261
1292
}
1262
1293
}
1263
1294
1264
- impl std :: cmp:: Eq for TestResultComparison { }
1295
+ impl cmp:: Eq for TestResultComparison { }
1265
1296
1266
1297
impl std:: hash:: Hash for TestResultComparison {
1267
1298
fn hash < H : std:: hash:: Hasher > ( & self , state : & mut H ) {
@@ -1437,11 +1468,13 @@ mod tests {
1437
1468
( Category :: Primary , 1.0 , 3.0 ) ,
1438
1469
] ,
1439
1470
r#"
1440
- | Regressions ❌ <br /> (primary) | 146.7% | [100.0%, 200.0%] | 3 |
1441
- | Regressions ❌ <br /> (secondary) | - | - | 0 |
1442
- | Improvements ✅ <br /> (primary) | - | - | 0 |
1443
- | Improvements ✅ <br /> (secondary) | - | - | 0 |
1444
- | All ❌✅ (primary) | 146.7% | [100.0%, 200.0%] | 3 |
1471
+ | (instructions:u) | mean | range | count |
1472
+ |:----------------------------------:|:------:|:----------------:|:-----:|
1473
+ | Regressions ❌ <br /> (primary) | 146.7% | [100.0%, 200.0%] | 3 |
1474
+ | Regressions ❌ <br /> (secondary) | - | - | 0 |
1475
+ | Improvements ✅ <br /> (primary) | - | - | 0 |
1476
+ | Improvements ✅ <br /> (secondary) | - | - | 0 |
1477
+ | All ❌✅ (primary) | 146.7% | [100.0%, 200.0%] | 3 |
1445
1478
"#
1446
1479
. trim_start ( ) ,
1447
1480
) ;
@@ -1456,11 +1489,13 @@ mod tests {
1456
1489
( Category :: Primary , 4.0 , 1.0 ) ,
1457
1490
] ,
1458
1491
r#"
1459
- | Regressions ❌ <br /> (primary) | - | - | 0 |
1460
- | Regressions ❌ <br /> (secondary) | - | - | 0 |
1461
- | Improvements ✅ <br /> (primary) | -71.7% | [-80.0%, -60.0%] | 3 |
1462
- | Improvements ✅ <br /> (secondary) | - | - | 0 |
1463
- | All ❌✅ (primary) | -71.7% | [-80.0%, -60.0%] | 3 |
1492
+ | (instructions:u) | mean | range | count |
1493
+ |:----------------------------------:|:------:|:----------------:|:-----:|
1494
+ | Regressions ❌ <br /> (primary) | - | - | 0 |
1495
+ | Regressions ❌ <br /> (secondary) | - | - | 0 |
1496
+ | Improvements ✅ <br /> (primary) | -71.7% | [-80.0%, -60.0%] | 3 |
1497
+ | Improvements ✅ <br /> (secondary) | - | - | 0 |
1498
+ | All ❌✅ (primary) | -71.7% | [-80.0%, -60.0%] | 3 |
1464
1499
"#
1465
1500
. trim_start ( ) ,
1466
1501
) ;
@@ -1475,11 +1510,13 @@ mod tests {
1475
1510
( Category :: Secondary , 4.0 , 1.0 ) ,
1476
1511
] ,
1477
1512
r#"
1478
- | Regressions ❌ <br /> (primary) | - | - | 0 |
1479
- | Regressions ❌ <br /> (secondary) | - | - | 0 |
1480
- | Improvements ✅ <br /> (primary) | - | - | 0 |
1481
- | Improvements ✅ <br /> (secondary) | -71.7% | [-80.0%, -60.0%] | 3 |
1482
- | All ❌✅ (primary) | - | - | 0 |
1513
+ | (instructions:u) | mean | range | count |
1514
+ |:----------------------------------:|:------:|:----------------:|:-----:|
1515
+ | Regressions ❌ <br /> (primary) | - | - | 0 |
1516
+ | Regressions ❌ <br /> (secondary) | - | - | 0 |
1517
+ | Improvements ✅ <br /> (primary) | - | - | 0 |
1518
+ | Improvements ✅ <br /> (secondary) | -71.7% | [-80.0%, -60.0%] | 3 |
1519
+ | All ❌✅ (primary) | - | - | 0 |
1483
1520
"#
1484
1521
. trim_start ( ) ,
1485
1522
) ;
@@ -1494,11 +1531,13 @@ mod tests {
1494
1531
( Category :: Secondary , 1.0 , 3.0 ) ,
1495
1532
] ,
1496
1533
r#"
1497
- | Regressions ❌ <br /> (primary) | - | - | 0 |
1498
- | Regressions ❌ <br /> (secondary) | 146.7% | [100.0%, 200.0%] | 3 |
1499
- | Improvements ✅ <br /> (primary) | - | - | 0 |
1500
- | Improvements ✅ <br /> (secondary) | - | - | 0 |
1501
- | All ❌✅ (primary) | - | - | 0 |
1534
+ | (instructions:u) | mean | range | count |
1535
+ |:----------------------------------:|:------:|:----------------:|:-----:|
1536
+ | Regressions ❌ <br /> (primary) | - | - | 0 |
1537
+ | Regressions ❌ <br /> (secondary) | 146.7% | [100.0%, 200.0%] | 3 |
1538
+ | Improvements ✅ <br /> (primary) | - | - | 0 |
1539
+ | Improvements ✅ <br /> (secondary) | - | - | 0 |
1540
+ | All ❌✅ (primary) | - | - | 0 |
1502
1541
"#
1503
1542
. trim_start ( ) ,
1504
1543
) ;
@@ -1514,11 +1553,13 @@ mod tests {
1514
1553
( Category :: Primary , 4.0 , 1.0 ) ,
1515
1554
] ,
1516
1555
r#"
1517
- | Regressions ❌ <br /> (primary) | 150.0% | [100.0%, 200.0%] | 2 |
1518
- | Regressions ❌ <br /> (secondary) | - | - | 0 |
1519
- | Improvements ✅ <br /> (primary) | -62.5% | [-75.0%, -50.0%] | 2 |
1520
- | Improvements ✅ <br /> (secondary) | - | - | 0 |
1521
- | All ❌✅ (primary) | 43.8% | [-75.0%, 200.0%] | 4 |
1556
+ | (instructions:u) | mean | range | count |
1557
+ |:----------------------------------:|:------:|:----------------:|:-----:|
1558
+ | Regressions ❌ <br /> (primary) | 150.0% | [100.0%, 200.0%] | 2 |
1559
+ | Regressions ❌ <br /> (secondary) | - | - | 0 |
1560
+ | Improvements ✅ <br /> (primary) | -62.5% | [-75.0%, -50.0%] | 2 |
1561
+ | Improvements ✅ <br /> (secondary) | - | - | 0 |
1562
+ | All ❌✅ (primary) | 43.8% | [-75.0%, 200.0%] | 4 |
1522
1563
"#
1523
1564
. trim_start ( ) ,
1524
1565
) ;
@@ -1536,11 +1577,13 @@ mod tests {
1536
1577
( Category :: Primary , 4.0 , 1.0 ) ,
1537
1578
] ,
1538
1579
r#"
1539
- | Regressions ❌ <br /> (primary) | 150.0% | [100.0%, 200.0%] | 2 |
1540
- | Regressions ❌ <br /> (secondary) | 100.0% | [100.0%, 100.0%] | 1 |
1541
- | Improvements ✅ <br /> (primary) | -62.5% | [-75.0%, -50.0%] | 2 |
1542
- | Improvements ✅ <br /> (secondary) | -66.7% | [-66.7%, -66.7%] | 1 |
1543
- | All ❌✅ (primary) | 43.8% | [-75.0%, 200.0%] | 4 |
1580
+ | (instructions:u) | mean | range | count |
1581
+ |:----------------------------------:|:------:|:----------------:|:-----:|
1582
+ | Regressions ❌ <br /> (primary) | 150.0% | [100.0%, 200.0%] | 2 |
1583
+ | Regressions ❌ <br /> (secondary) | 100.0% | [100.0%, 100.0%] | 1 |
1584
+ | Improvements ✅ <br /> (primary) | -62.5% | [-75.0%, -50.0%] | 2 |
1585
+ | Improvements ✅ <br /> (secondary) | -66.7% | [-66.7%, -66.7%] | 1 |
1586
+ | All ❌✅ (primary) | 43.8% | [-75.0%, 200.0%] | 4 |
1544
1587
"#
1545
1588
. trim_start ( ) ,
1546
1589
) ;
@@ -1554,11 +1597,13 @@ mod tests {
1554
1597
( Category :: Primary , 5.0 , 6.0 ) ,
1555
1598
] ,
1556
1599
r#"
1557
- | Regressions ❌ <br /> (primary) | 20.0% | [20.0%, 20.0%] | 1 |
1558
- | Regressions ❌ <br /> (secondary) | - | - | 0 |
1559
- | Improvements ✅ <br /> (primary) | -50.0% | [-50.0%, -50.0%] | 1 |
1560
- | Improvements ✅ <br /> (secondary) | - | - | 0 |
1561
- | All ❌✅ (primary) | -15.0% | [-50.0%, 20.0%] | 2 |
1600
+ | (instructions:u) | mean | range | count |
1601
+ |:----------------------------------:|:------:|:----------------:|:-----:|
1602
+ | Regressions ❌ <br /> (primary) | 20.0% | [20.0%, 20.0%] | 1 |
1603
+ | Regressions ❌ <br /> (secondary) | - | - | 0 |
1604
+ | Improvements ✅ <br /> (primary) | -50.0% | [-50.0%, -50.0%] | 1 |
1605
+ | Improvements ✅ <br /> (secondary) | - | - | 0 |
1606
+ | All ❌✅ (primary) | -15.0% | [-50.0%, 20.0%] | 2 |
1562
1607
"#
1563
1608
. trim_start ( ) ,
1564
1609
) ;
@@ -1572,11 +1617,13 @@ mod tests {
1572
1617
( Category :: Primary , 6.0 , 5.0 ) ,
1573
1618
] ,
1574
1619
r#"
1575
- | Regressions ❌ <br /> (primary) | 100.0% | [100.0%, 100.0%] | 1 |
1576
- | Regressions ❌ <br /> (secondary) | - | - | 0 |
1577
- | Improvements ✅ <br /> (primary) | -16.7% | [-16.7%, -16.7%] | 1 |
1578
- | Improvements ✅ <br /> (secondary) | - | - | 0 |
1579
- | All ❌✅ (primary) | 41.7% | [-16.7%, 100.0%] | 2 |
1620
+ | (instructions:u) | mean | range | count |
1621
+ |:----------------------------------:|:------:|:----------------:|:-----:|
1622
+ | Regressions ❌ <br /> (primary) | 100.0% | [100.0%, 100.0%] | 1 |
1623
+ | Regressions ❌ <br /> (secondary) | - | - | 0 |
1624
+ | Improvements ✅ <br /> (primary) | -16.7% | [-16.7%, -16.7%] | 1 |
1625
+ | Improvements ✅ <br /> (secondary) | - | - | 0 |
1626
+ | All ❌✅ (primary) | 41.7% | [-16.7%, 100.0%] | 2 |
1580
1627
"#
1581
1628
. trim_start ( ) ,
1582
1629
) ;
@@ -1625,8 +1672,14 @@ mod tests {
1625
1672
let secondary = ArtifactComparisonSummary :: summarize ( secondary_comparisons) ;
1626
1673
1627
1674
let mut result = String :: new ( ) ;
1628
- write_summary_table ( & primary, & secondary, true , true , & mut result) ;
1629
- let header = "| (instructions:u) | mean[^1] | range | count[^2] |\n |:----------------:|:--------:|:-----:|:---------:|\n " ;
1630
- assert_eq ! ( result, format!( "{header}{expected}" ) ) ;
1675
+ write_summary_table ( & primary, & secondary, true , & mut result) ;
1676
+ // We don't use `assert_eq!` here because it stringifies the arguments,
1677
+ // making the tables hard to read when printed.
1678
+ if result != expected {
1679
+ panic ! (
1680
+ "output mismatch:\n expected:\n {}actual:\n {}" ,
1681
+ expected, result
1682
+ ) ;
1683
+ }
1631
1684
}
1632
1685
}
0 commit comments