@@ -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) ;
@@ -553,6 +605,29 @@ impl<'gctx> Timings<'gctx> {
553
605
554
606
/// Render the table of all units.
555
607
fn write_unit_table ( & self , f : & mut impl Write ) -> CargoResult < ( ) > {
608
+ let mut units: Vec < & UnitTime > = self . unit_times . iter ( ) . collect ( ) ;
609
+ units. sort_unstable_by ( |a, b| b. duration . partial_cmp ( & a. duration ) . unwrap ( ) ) ;
610
+
611
+ // We can have a bunch of situations here.
612
+ // - -Zsection-timings is enabled, and we received some custom sections, in which
613
+ // case we use them to determine the headers.
614
+ // - We have at least one rmeta time, so we hard-code Frontend and Codegen headers.
615
+ // - We only have total durations, so we don't add any additional headers.
616
+ let aggregated: Vec < AggregatedSections > =
617
+ units. iter ( ) . map ( |u| u. aggregate_sections ( ) ) . collect ( ) ;
618
+
619
+ let headers: Vec < String > = if aggregated
620
+ . iter ( )
621
+ . any ( |s| matches ! ( s, AggregatedSections :: OnlyMetadataTime { .. } ) )
622
+ {
623
+ vec ! [
624
+ FRONTEND_SECTION_NAME . to_string( ) ,
625
+ CODEGEN_SECTION_NAME . to_string( ) ,
626
+ ]
627
+ } else {
628
+ vec ! [ ]
629
+ } ;
630
+
556
631
write ! (
557
632
f,
558
633
r#"
@@ -562,20 +637,48 @@ impl<'gctx> Timings<'gctx> {
562
637
<th></th>
563
638
<th>Unit</th>
564
639
<th>Total</th>
565
- <th>Codegen</th>
640
+ {headers}
566
641
<th>Features</th>
567
642
</tr>
568
643
</thead>
569
644
<tbody>
570
- "#
645
+ "# ,
646
+ headers = headers. iter( ) . map( |h| format!( "<th>{h}</th>" ) ) . join( "\n " )
571
647
) ?;
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 ( ) {
648
+
649
+ for ( i, ( unit, aggregated_sections) ) in units. iter ( ) . zip ( aggregated) . enumerate ( ) {
650
+ let format_duration = |section : Option < SectionData > | match section {
651
+ Some ( section) => {
652
+ let duration = section. duration ( ) ;
653
+ let pct = ( duration / unit. duration ) * 100.0 ;
654
+ format ! ( "{duration:.1}s ({:.0}%)" , pct)
655
+ }
576
656
None => "" . to_string ( ) ,
577
- Some ( ( _rt, ctime, cent) ) => format ! ( "{:.1}s ({:.0}%)" , ctime, cent) ,
578
657
} ;
658
+
659
+ // This is a bit complex, as we assume the most general option - we can have an
660
+ // arbitrary set of headers, and an arbitrary set of sections per unit, so we always
661
+ // initiate the cells to be empty, and then try to find a corresponding column for which
662
+ // we might have data.
663
+ let mut cells: HashMap < & str , SectionData > = Default :: default ( ) ;
664
+
665
+ match & aggregated_sections {
666
+ AggregatedSections :: OnlyMetadataTime { frontend, codegen } => {
667
+ cells. insert ( FRONTEND_SECTION_NAME , * frontend) ;
668
+ cells. insert ( CODEGEN_SECTION_NAME , * codegen) ;
669
+ }
670
+ AggregatedSections :: OnlyTotalDuration => { }
671
+ } ;
672
+ let cells = headers
673
+ . iter ( )
674
+ . map ( |header| {
675
+ format ! (
676
+ "<td>{}</td>" ,
677
+ format_duration( cells. remove( header. as_str( ) ) )
678
+ )
679
+ } )
680
+ . join ( "\n " ) ;
681
+
579
682
let features = unit. unit . features . join ( ", " ) ;
580
683
write ! (
581
684
f,
@@ -584,16 +687,14 @@ impl<'gctx> Timings<'gctx> {
584
687
<td>{}.</td>
585
688
<td>{}{}</td>
586
689
<td>{:.1}s</td>
587
- <td>{}</td>
588
- <td>{}</td>
690
+ {cells}
691
+ <td>{features }</td>
589
692
</tr>
590
693
"# ,
591
694
i + 1 ,
592
695
unit. name_ver( ) ,
593
696
unit. target,
594
697
unit. duration,
595
- codegen,
596
- features,
597
698
) ?;
598
699
}
599
700
write ! ( f, "</tbody>\n </table>\n " ) ?;
@@ -602,15 +703,6 @@ impl<'gctx> Timings<'gctx> {
602
703
}
603
704
604
705
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
706
fn name_ver ( & self ) -> String {
615
707
format ! ( "{} v{}" , self . unit. pkg. name( ) , self . unit. pkg. version( ) )
616
708
}
0 commit comments