@@ -13,10 +13,12 @@ use collector::Bound;
1313use serde:: { Deserialize , Serialize } ;
1414
1515use database:: CommitType ;
16+ use std:: cmp;
1617use std:: collections:: { HashMap , HashSet } ;
1718use std:: error:: Error ;
1819use std:: fmt:: Write ;
1920use std:: hash:: Hash ;
21+ use std:: iter;
2022use std:: sync:: Arc ;
2123
2224type BoxedError = Box < dyn Error + Send + Sync > ;
@@ -95,7 +97,7 @@ pub async fn handle_triage(
9597 . clone ( )
9698 . summarize_by_category ( & benchmark_map) ;
9799 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) ;
99101 result
100102 }
101103 None => String :: from ( "**ERROR**: no data found for end bound" ) ,
@@ -330,7 +332,7 @@ impl ArtifactComparisonSummary {
330332 let cmp = |b1 : & TestResultComparison , b2 : & TestResultComparison | {
331333 b1. relative_change ( )
332334 . partial_cmp ( & b2. relative_change ( ) )
333- . unwrap_or ( std :: cmp:: Ordering :: Equal )
335+ . unwrap_or ( cmp:: Ordering :: Equal )
334336 } ;
335337 relevant_comparisons. sort_by ( cmp) ;
336338
@@ -498,7 +500,7 @@ impl ArtifactComparisonSummary {
498500 let most_neg = self . most_negative_change ( ) . unwrap ( ) ;
499501 let most_pos_abs = most_pos. relative_change ( ) . abs ( ) ;
500502 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 ) {
502504 Some ( most_neg)
503505 } else {
504506 Some ( most_pos)
@@ -547,7 +549,7 @@ async fn write_triage_summary(
547549 let link = & compare_link ( start, end) ;
548550 write ! ( & mut result, " [(Comparison Link)]({})\n \n " , link) . unwrap ( ) ;
549551
550- write_summary_table ( & primary, & secondary, false , true , & mut result) ;
552+ write_summary_table ( & primary, & secondary, true , & mut result) ;
551553
552554 result
553555}
@@ -556,7 +558,6 @@ async fn write_triage_summary(
556558pub fn write_summary_table (
557559 primary : & ArtifactComparisonSummary ,
558560 secondary : & ArtifactComparisonSummary ,
559- with_footnotes : bool ,
560561 include_metric : bool ,
561562 result : & mut String ,
562563) {
@@ -569,7 +570,7 @@ pub fn write_summary_table(
569570 . map ( |m| format ! ( "({})" , m. metric. as_str( ) ) )
570571 } )
571572 . flatten ( )
572- . unwrap_or_else ( || String :: from ( " " ) ) ;
573+ . unwrap_or_else ( || " " . to_string ( ) ) ;
573574
574575 fn render_stat < F : FnOnce ( ) -> Option < f64 > > ( count : usize , calculate : F ) -> String {
575576 let value = if count > 0 { calculate ( ) } else { None } ;
@@ -588,10 +589,10 @@ pub fn write_summary_table(
588589 }
589590
590591 // (label, mean, max, count)
591- let mut column_data = vec ! [ ] ;
592+ let mut columns = vec ! [ ] ;
592593
593594 // label
594- column_data . push ( vec ! [
595+ columns . push ( vec ! [
595596 "Regressions ❌ <br /> (primary)" . to_string( ) ,
596597 "Regressions ❌ <br /> (secondary)" . to_string( ) ,
597598 "Improvements ✅ <br /> (primary)" . to_string( ) ,
@@ -600,7 +601,7 @@ pub fn write_summary_table(
600601 ] ) ;
601602
602603 // mean
603- column_data . push ( vec ! [
604+ columns . push ( vec ! [
604605 render_stat( primary. num_regressions, || {
605606 Some ( primary. arithmetic_mean_of_regressions( ) )
606607 } ) ,
@@ -622,7 +623,7 @@ pub fn write_summary_table(
622623
623624 // range
624625 let rel_change = |r : Option < & TestResultComparison > | r. unwrap ( ) . relative_change ( ) * 100.0 ;
625- column_data . push ( vec ! [
626+ columns . push ( vec ! [
626627 render_range( primary. num_regressions, || {
627628 (
628629 rel_change( primary. smallest_regression( ) ) ,
@@ -656,7 +657,7 @@ pub fn write_summary_table(
656657 ] ) ;
657658
658659 // count
659- column_data . push ( vec ! [
660+ columns . push ( vec ! [
660661 primary. num_regressions. to_string( ) ,
661662 secondary. num_regressions. to_string( ) ,
662663 primary. num_improvements. to_string( ) ,
@@ -668,40 +669,70 @@ pub fn write_summary_table(
668669 // easy to read for anyone who is viewing the Markdown source.
669670 let column_labels = [
670671 metric,
671- format ! ( "mean{}" , if with_footnotes { "[^1]" } else { "" } ) ,
672+ "mean" . to_string ( ) ,
672673 "range" . to_string ( ) ,
673- format ! ( "count{}" , if with_footnotes { "[^2]" } else { "" } ) ,
674+ "count" . to_string ( ) ,
674675 ] ;
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 ( ) ;
678710 }
679711 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 ( ) ;
682716 }
683717 result. push_str ( "|\n " ) ;
684718
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 ( ) ;
690731 }
691732 result. push_str ( "|\n " ) ;
692733 }
693734}
694735
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-
705736/// Compare two bounds on a given stat
706737///
707738/// Returns Ok(None) when no data for the end bound is present
@@ -1051,7 +1082,7 @@ impl HistoricalData {
10511082 . zip ( self . data . iter ( ) )
10521083 . map ( |( d, & r) | d / r)
10531084 . 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 ) ) ;
10551086 deltas
10561087 }
10571088
@@ -1253,15 +1284,15 @@ impl TestResultComparison {
12531284 }
12541285}
12551286
1256- impl std :: cmp:: PartialEq for TestResultComparison {
1287+ impl cmp:: PartialEq for TestResultComparison {
12571288 fn eq ( & self , other : & Self ) -> bool {
12581289 self . benchmark == other. benchmark
12591290 && self . profile == other. profile
12601291 && self . scenario == other. scenario
12611292 }
12621293}
12631294
1264- impl std :: cmp:: Eq for TestResultComparison { }
1295+ impl cmp:: Eq for TestResultComparison { }
12651296
12661297impl std:: hash:: Hash for TestResultComparison {
12671298 fn hash < H : std:: hash:: Hasher > ( & self , state : & mut H ) {
@@ -1437,11 +1468,13 @@ mod tests {
14371468 ( Category :: Primary , 1.0 , 3.0 ) ,
14381469 ] ,
14391470 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 |
14451478"#
14461479 . trim_start ( ) ,
14471480 ) ;
@@ -1456,11 +1489,13 @@ mod tests {
14561489 ( Category :: Primary , 4.0 , 1.0 ) ,
14571490 ] ,
14581491 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 |
14641499"#
14651500 . trim_start ( ) ,
14661501 ) ;
@@ -1475,11 +1510,13 @@ mod tests {
14751510 ( Category :: Secondary , 4.0 , 1.0 ) ,
14761511 ] ,
14771512 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 |
14831520"#
14841521 . trim_start ( ) ,
14851522 ) ;
@@ -1494,11 +1531,13 @@ mod tests {
14941531 ( Category :: Secondary , 1.0 , 3.0 ) ,
14951532 ] ,
14961533 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 |
15021541"#
15031542 . trim_start ( ) ,
15041543 ) ;
@@ -1514,11 +1553,13 @@ mod tests {
15141553 ( Category :: Primary , 4.0 , 1.0 ) ,
15151554 ] ,
15161555 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 |
15221563"#
15231564 . trim_start ( ) ,
15241565 ) ;
@@ -1536,11 +1577,13 @@ mod tests {
15361577 ( Category :: Primary , 4.0 , 1.0 ) ,
15371578 ] ,
15381579 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 |
15441587"#
15451588 . trim_start ( ) ,
15461589 ) ;
@@ -1554,11 +1597,13 @@ mod tests {
15541597 ( Category :: Primary , 5.0 , 6.0 ) ,
15551598 ] ,
15561599 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 |
15621607"#
15631608 . trim_start ( ) ,
15641609 ) ;
@@ -1572,11 +1617,13 @@ mod tests {
15721617 ( Category :: Primary , 6.0 , 5.0 ) ,
15731618 ] ,
15741619 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 |
15801627"#
15811628 . trim_start ( ) ,
15821629 ) ;
@@ -1625,8 +1672,14 @@ mod tests {
16251672 let secondary = ArtifactComparisonSummary :: summarize ( secondary_comparisons) ;
16261673
16271674 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+ }
16311684 }
16321685}
0 commit comments