@@ -13,6 +13,7 @@ use crate::util::{CargoResult, GlobalContext};
13
13
use anyhow:: Context as _;
14
14
use cargo_util:: paths;
15
15
use indexmap:: IndexMap ;
16
+ use itertools:: Itertools ;
16
17
use std:: collections:: HashMap ;
17
18
use std:: io:: { BufWriter , Write } ;
18
19
use std:: thread:: available_parallelism;
@@ -90,11 +91,35 @@ struct UnitTime {
90
91
unlocked_units : Vec < Unit > ,
91
92
/// Same as `unlocked_units`, but unlocked by rmeta.
92
93
unlocked_rmeta_units : Vec < Unit > ,
94
+ /// Individual compilation section durations, gathered from `--json=timings`.
93
95
/// IndexMap is used to keep original insertion order, we want to be able to tell which
94
96
/// sections were started in which order.
95
97
sections : IndexMap < String , CompilationSection > ,
96
98
}
97
99
100
+ const FRONTEND_SECTION_NAME : & str = "Frontend" ;
101
+ const CODEGEN_SECTION_NAME : & str = "Codegen" ;
102
+
103
+ impl UnitTime {
104
+ fn aggregate_sections ( & self ) -> AggregatedSections {
105
+ let end = self . duration ;
106
+
107
+ if let Some ( rmeta) = self . rmeta_time {
108
+ // We only know when the rmeta time was generated
109
+ AggregatedSections :: OnlyMetadataTime {
110
+ frontend : SectionData {
111
+ start : 0.0 ,
112
+ end : rmeta,
113
+ } ,
114
+ codegen : SectionData { start : rmeta, end } ,
115
+ }
116
+ } else {
117
+ // We only know the total duration
118
+ AggregatedSections :: OnlyTotalDuration
119
+ }
120
+ }
121
+ }
122
+
98
123
/// Periodic concurrency tracking information.
99
124
#[ derive( serde:: Serialize ) ]
100
125
struct Concurrency {
@@ -109,6 +134,33 @@ struct Concurrency {
109
134
inactive : usize ,
110
135
}
111
136
137
+ /// Postprocessed section data that has both start and an end.
138
+ #[ derive( Copy , Clone , serde:: Serialize ) ]
139
+ struct SectionData {
140
+ /// Start (relative to the start of the unit)
141
+ start : f64 ,
142
+ /// End (relative to the start of the unit)
143
+ end : f64 ,
144
+ }
145
+
146
+ impl SectionData {
147
+ fn duration ( & self ) -> f64 {
148
+ ( self . end - self . start ) . max ( 0.0 )
149
+ }
150
+ }
151
+
152
+ /// Contains post-processed data of individual compilation sections.
153
+ #[ derive( serde:: Serialize ) ]
154
+ enum AggregatedSections {
155
+ /// We only know when .rmeta was generated, so we can distill frontend and codegen time.
156
+ OnlyMetadataTime {
157
+ frontend : SectionData ,
158
+ codegen : SectionData ,
159
+ } ,
160
+ /// We know only the total duration
161
+ OnlyTotalDuration ,
162
+ }
163
+
112
164
impl < ' gctx > Timings < ' gctx > {
113
165
pub fn new ( bcx : & BuildContext < ' _ , ' gctx > , root_units : & [ Unit ] ) -> Timings < ' gctx > {
114
166
let has_report = |what| bcx. build_config . timing_outputs . contains ( & what) ;
@@ -480,6 +532,7 @@ impl<'gctx> Timings<'gctx> {
480
532
. enumerate ( )
481
533
. map ( |( i, ut) | ( ut. unit . clone ( ) , i) )
482
534
. collect ( ) ;
535
+
483
536
#[ derive( serde:: Serialize ) ]
484
537
struct UnitData {
485
538
i : usize ,
@@ -553,6 +606,29 @@ impl<'gctx> Timings<'gctx> {
553
606
554
607
/// Render the table of all units.
555
608
fn write_unit_table ( & self , f : & mut impl Write ) -> CargoResult < ( ) > {
609
+ let mut units: Vec < & UnitTime > = self . unit_times . iter ( ) . collect ( ) ;
610
+ units. sort_unstable_by ( |a, b| b. duration . partial_cmp ( & a. duration ) . unwrap ( ) ) ;
611
+
612
+ // We can have a bunch of situations here.
613
+ // - -Zsection-timings is enabled, and we received some custom sections, in which
614
+ // case we use them to determine the headers.
615
+ // - We have at least one rmeta time, so we hard-code Frontend and Codegen headers.
616
+ // - We only have total durations, so we don't add any additional headers.
617
+ let aggregated: Vec < AggregatedSections > =
618
+ units. iter ( ) . map ( |u| u. aggregate_sections ( ) ) . collect ( ) ;
619
+
620
+ let headers: Vec < String > = if aggregated
621
+ . iter ( )
622
+ . any ( |s| matches ! ( s, AggregatedSections :: OnlyMetadataTime { .. } ) )
623
+ {
624
+ vec ! [
625
+ FRONTEND_SECTION_NAME . to_string( ) ,
626
+ CODEGEN_SECTION_NAME . to_string( ) ,
627
+ ]
628
+ } else {
629
+ vec ! [ ]
630
+ } ;
631
+
556
632
write ! (
557
633
f,
558
634
r#"
@@ -562,20 +638,48 @@ impl<'gctx> Timings<'gctx> {
562
638
<th></th>
563
639
<th>Unit</th>
564
640
<th>Total</th>
565
- <th>Codegen</th>
641
+ {headers}
566
642
<th>Features</th>
567
643
</tr>
568
644
</thead>
569
645
<tbody>
570
- "#
646
+ "# ,
647
+ headers = headers. iter( ) . map( |h| format!( "<th>{h}</th>" ) ) . join( "\n " )
571
648
) ?;
572
- let mut units: Vec < & UnitTime > = self . unit_times . iter ( ) . collect ( ) ;
573
- units. sort_unstable_by ( |a, b| b. duration . partial_cmp ( & a. duration ) . unwrap ( ) ) ;
574
- for ( i, unit) in units. iter ( ) . enumerate ( ) {
575
- let codegen = match unit. codegen_time ( ) {
649
+
650
+ for ( i, ( unit, aggregated_sections) ) in units. iter ( ) . zip ( aggregated) . enumerate ( ) {
651
+ let format_duration = |section : Option < SectionData > | match section {
652
+ Some ( section) => {
653
+ let duration = section. duration ( ) ;
654
+ let pct = ( duration / unit. duration ) * 100.0 ;
655
+ format ! ( "{duration:.1}s ({:.0}%)" , pct)
656
+ }
576
657
None => "" . to_string ( ) ,
577
- Some ( ( _rt, ctime, cent) ) => format ! ( "{:.1}s ({:.0}%)" , ctime, cent) ,
578
658
} ;
659
+
660
+ // This is a bit complex, as we assume the most general option - we can have an
661
+ // arbitrary set of headers, and an arbitrary set of sections per unit, so we always
662
+ // initiate the cells to be empty, and then try to find a corresponding column for which
663
+ // we might have data.
664
+ let mut cells: HashMap < & str , SectionData > = Default :: default ( ) ;
665
+
666
+ match & aggregated_sections {
667
+ AggregatedSections :: OnlyMetadataTime { frontend, codegen } => {
668
+ cells. insert ( FRONTEND_SECTION_NAME , * frontend) ;
669
+ cells. insert ( CODEGEN_SECTION_NAME , * codegen) ;
670
+ }
671
+ AggregatedSections :: OnlyTotalDuration => { }
672
+ } ;
673
+ let cells = headers
674
+ . iter ( )
675
+ . map ( |header| {
676
+ format ! (
677
+ "<td>{}</td>" ,
678
+ format_duration( cells. remove( header. as_str( ) ) )
679
+ )
680
+ } )
681
+ . join ( "\n " ) ;
682
+
579
683
let features = unit. unit . features . join ( ", " ) ;
580
684
write ! (
581
685
f,
@@ -584,16 +688,14 @@ impl<'gctx> Timings<'gctx> {
584
688
<td>{}.</td>
585
689
<td>{}{}</td>
586
690
<td>{:.1}s</td>
587
- <td>{}</td>
588
- <td>{}</td>
691
+ {cells}
692
+ <td>{features }</td>
589
693
</tr>
590
694
"# ,
591
695
i + 1 ,
592
696
unit. name_ver( ) ,
593
697
unit. target,
594
698
unit. duration,
595
- codegen,
596
- features,
597
699
) ?;
598
700
}
599
701
write ! ( f, "</tbody>\n </table>\n " ) ?;
@@ -602,15 +704,6 @@ impl<'gctx> Timings<'gctx> {
602
704
}
603
705
604
706
impl UnitTime {
605
- /// Returns the codegen time as (`rmeta_time`, `codegen_time`, percent of total)
606
- fn codegen_time ( & self ) -> Option < ( f64 , f64 , f64 ) > {
607
- self . rmeta_time . map ( |rmeta_time| {
608
- let ctime = self . duration - rmeta_time;
609
- let cent = ( ctime / self . duration ) * 100.0 ;
610
- ( rmeta_time, ctime, cent)
611
- } )
612
- }
613
-
614
707
fn name_ver ( & self ) -> String {
615
708
format ! ( "{} v{}" , self . unit. pkg. name( ) , self . unit. pkg. version( ) )
616
709
}
0 commit comments