@@ -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;
@@ -91,11 +92,72 @@ struct UnitTime {
91
92
/// Same as `unlocked_units`, but unlocked by rmeta.
92
93
unlocked_rmeta_units : Vec < Unit > ,
93
94
/// Individual compilation section durations, gathered from `--json=timings`.
94
- sections : HashMap < String , CompilationSection > ,
95
95
/// IndexMap is used to keep original insertion order, we want to be able to tell which
96
96
/// sections were started in which order.
97
97
sections : IndexMap < String , CompilationSection > ,
98
98
}
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 !self . sections . is_empty ( ) {
108
+ // We have some detailed compilation section timings, so we postprocess them
109
+ // Since it is possible that we do not have an end timestamp for a given compilation
110
+ // section, we need to iterate them and if an end is missing, we assign the end of
111
+ // the section to the start of the following section.
112
+
113
+ let mut sections = vec ! [ ] ;
114
+
115
+ // The frontend section is currently implicit in rustc, it is assumed to start at
116
+ // compilation start and end when codegen starts. So we hard-code it here.
117
+ let mut previous_section = (
118
+ FRONTEND_SECTION_NAME . to_string ( ) ,
119
+ CompilationSection {
120
+ start : 0.0 ,
121
+ end : None ,
122
+ } ,
123
+ ) ;
124
+ for ( name, section) in self . sections . clone ( ) {
125
+ // Store the previous section, potentially setting its end to the start of the
126
+ // current section.
127
+ sections. push ( (
128
+ previous_section. 0 . clone ( ) ,
129
+ SectionData {
130
+ start : previous_section. 1 . start ,
131
+ end : previous_section. 1 . end . unwrap_or ( section. start ) ,
132
+ } ,
133
+ ) ) ;
134
+ previous_section = ( name, section) ;
135
+ }
136
+ // Store the last section, potentially setting its end to the end of the whole
137
+ // compilation.
138
+ sections. push ( (
139
+ previous_section. 0 . clone ( ) ,
140
+ SectionData {
141
+ start : previous_section. 1 . start ,
142
+ end : previous_section. 1 . end . unwrap_or ( end) ,
143
+ } ,
144
+ ) ) ;
145
+
146
+ AggregatedSections :: Sections ( sections)
147
+ } else if let Some ( rmeta) = self . rmeta_time {
148
+ // We only know when the rmeta time was generated
149
+ AggregatedSections :: OnlyMetadataTime {
150
+ frontend : SectionData {
151
+ start : 0.0 ,
152
+ end : rmeta,
153
+ } ,
154
+ codegen : SectionData { start : rmeta, end } ,
155
+ }
156
+ } else {
157
+ // We only know the total duration
158
+ AggregatedSections :: OnlyTotalDuration
159
+ }
160
+ }
99
161
}
100
162
101
163
/// Periodic concurrency tracking information.
@@ -112,6 +174,35 @@ struct Concurrency {
112
174
inactive : usize ,
113
175
}
114
176
177
+ /// Postprocessed section data that has both start and an end.
178
+ #[ derive( Copy , Clone , serde:: Serialize ) ]
179
+ struct SectionData {
180
+ /// Start (relative to the start of the unit)
181
+ start : f64 ,
182
+ /// End (relative to the start of the unit)
183
+ end : f64 ,
184
+ }
185
+
186
+ impl SectionData {
187
+ fn duration ( & self ) -> f64 {
188
+ ( self . end - self . start ) . max ( 0.0 )
189
+ }
190
+ }
191
+
192
+ /// Contains post-processed data of individual compilation sections.
193
+ #[ derive( serde:: Serialize ) ]
194
+ enum AggregatedSections {
195
+ /// We know the names and durations of individual compilation sections
196
+ Sections ( Vec < ( String , SectionData ) > ) ,
197
+ /// We only know when .rmeta was generated, so we can distill frontend and codegen time.
198
+ OnlyMetadataTime {
199
+ frontend : SectionData ,
200
+ codegen : SectionData ,
201
+ } ,
202
+ /// We know only the total duration
203
+ OnlyTotalDuration ,
204
+ }
205
+
115
206
impl < ' gctx > Timings < ' gctx > {
116
207
pub fn new ( bcx : & BuildContext < ' _ , ' gctx > , root_units : & [ Unit ] ) -> Timings < ' gctx > {
117
208
let has_report = |what| bcx. build_config . timing_outputs . contains ( & what) ;
@@ -244,7 +335,7 @@ impl<'gctx> Timings<'gctx> {
244
335
mode : unit_time. unit . mode ,
245
336
duration : unit_time. duration ,
246
337
rmeta_time : unit_time. rmeta_time ,
247
- sections : unit_time. sections . clone ( ) ,
338
+ sections : unit_time. sections . clone ( ) . into_iter ( ) . collect ( ) ,
248
339
}
249
340
. to_json_string ( ) ;
250
341
crate :: drop_println!( self . gctx, "{}" , msg) ;
@@ -483,6 +574,7 @@ impl<'gctx> Timings<'gctx> {
483
574
. enumerate ( )
484
575
. map ( |( i, ut) | ( ut. unit . clone ( ) , i) )
485
576
. collect ( ) ;
577
+
486
578
#[ derive( serde:: Serialize ) ]
487
579
struct UnitData {
488
580
i : usize ,
@@ -556,6 +648,58 @@ impl<'gctx> Timings<'gctx> {
556
648
557
649
/// Render the table of all units.
558
650
fn write_unit_table ( & self , f : & mut impl Write ) -> CargoResult < ( ) > {
651
+ let mut units: Vec < & UnitTime > = self . unit_times . iter ( ) . collect ( ) ;
652
+ units. sort_unstable_by ( |a, b| b. duration . partial_cmp ( & a. duration ) . unwrap ( ) ) ;
653
+
654
+ // Make the first "letter" uppercase. We could probably just assume ASCII here, but this
655
+ // should be Unicode compatible.
656
+ fn capitalize ( s : & str ) -> String {
657
+ let first_char = s
658
+ . chars ( )
659
+ . next ( )
660
+ . map ( |c| c. to_uppercase ( ) . to_string ( ) )
661
+ . unwrap_or_default ( ) ;
662
+ format ! ( "{first_char}{}" , s. chars( ) . skip( 1 ) . collect:: <String >( ) )
663
+ }
664
+
665
+ // We can have a bunch of situations here.
666
+ // - -Zsection-timings is enabled, and we received some custom sections, in which
667
+ // case we use them to determine the headers.
668
+ // - We have at least one rmeta time, so we hard-code Frontend and Codegen headers.
669
+ // - We only have total durations, so we don't add any additional headers.
670
+ let aggregated: Vec < AggregatedSections > = units
671
+ . iter ( )
672
+ . map ( |u|
673
+ // Normalize the section names so that they are capitalized, so that we can later
674
+ // refer to them with the capitalized name both when computing headers and when
675
+ // looking up cells.
676
+ match u. aggregate_sections ( ) {
677
+ AggregatedSections :: Sections ( sections) => AggregatedSections :: Sections (
678
+ sections. into_iter ( )
679
+ . map ( |( name, data) | ( capitalize ( & name) , data) )
680
+ . collect ( )
681
+ ) ,
682
+ s => s
683
+ } )
684
+ . collect ( ) ;
685
+
686
+ let headers: Vec < String > = if let Some ( sections) = aggregated. iter ( ) . find_map ( |s| match s {
687
+ AggregatedSections :: Sections ( sections) => Some ( sections) ,
688
+ _ => None ,
689
+ } ) {
690
+ sections. into_iter ( ) . map ( |s| s. 0 . clone ( ) ) . collect ( )
691
+ } else if aggregated
692
+ . iter ( )
693
+ . any ( |s| matches ! ( s, AggregatedSections :: OnlyMetadataTime { .. } ) )
694
+ {
695
+ vec ! [
696
+ FRONTEND_SECTION_NAME . to_string( ) ,
697
+ CODEGEN_SECTION_NAME . to_string( ) ,
698
+ ]
699
+ } else {
700
+ vec ! [ ]
701
+ } ;
702
+
559
703
write ! (
560
704
f,
561
705
r#"
@@ -565,20 +709,53 @@ impl<'gctx> Timings<'gctx> {
565
709
<th></th>
566
710
<th>Unit</th>
567
711
<th>Total</th>
568
- <th>Codegen</th>
712
+ {headers}
569
713
<th>Features</th>
570
714
</tr>
571
715
</thead>
572
716
<tbody>
573
- "#
717
+ "# ,
718
+ headers = headers. iter( ) . map( |h| format!( "<th>{h}</th>" ) ) . join( "\n " )
574
719
) ?;
575
- let mut units: Vec < & UnitTime > = self . unit_times . iter ( ) . collect ( ) ;
576
- units. sort_unstable_by ( |a, b| b. duration . partial_cmp ( & a. duration ) . unwrap ( ) ) ;
577
- for ( i, unit) in units. iter ( ) . enumerate ( ) {
578
- let codegen = match unit. codegen_time ( ) {
720
+
721
+ for ( i, ( unit, aggregated_sections) ) in units. iter ( ) . zip ( aggregated) . enumerate ( ) {
722
+ let format_duration = |section : Option < SectionData > | match section {
723
+ Some ( section) => {
724
+ let duration = section. duration ( ) ;
725
+ let pct = ( duration / unit. duration ) * 100.0 ;
726
+ format ! ( "{duration:.1}s ({:.0}%)" , pct)
727
+ }
579
728
None => "" . to_string ( ) ,
580
- Some ( ( _rt, ctime, cent) ) => format ! ( "{:.1}s ({:.0}%)" , ctime, cent) ,
581
729
} ;
730
+
731
+ // This is a bit complex, as we assume the most general option - we can have an
732
+ // arbitrary set of headers, and an arbitrary set of sections per unit, so we always
733
+ // initiate the cells to be empty, and then try to find a corresponding column for which
734
+ // we might have data.
735
+ let mut cells: HashMap < & str , SectionData > = Default :: default ( ) ;
736
+
737
+ match & aggregated_sections {
738
+ AggregatedSections :: Sections ( sections) => {
739
+ for ( name, data) in sections {
740
+ cells. insert ( & name, * data) ;
741
+ }
742
+ }
743
+ AggregatedSections :: OnlyMetadataTime { frontend, codegen } => {
744
+ cells. insert ( FRONTEND_SECTION_NAME , * frontend) ;
745
+ cells. insert ( CODEGEN_SECTION_NAME , * codegen) ;
746
+ }
747
+ AggregatedSections :: OnlyTotalDuration => { }
748
+ } ;
749
+ let cells = headers
750
+ . iter ( )
751
+ . map ( |header| {
752
+ format ! (
753
+ "<td>{}</td>" ,
754
+ format_duration( cells. remove( header. as_str( ) ) )
755
+ )
756
+ } )
757
+ . join ( "\n " ) ;
758
+
582
759
let features = unit. unit . features . join ( ", " ) ;
583
760
write ! (
584
761
f,
@@ -587,16 +764,14 @@ impl<'gctx> Timings<'gctx> {
587
764
<td>{}.</td>
588
765
<td>{}{}</td>
589
766
<td>{:.1}s</td>
590
- <td>{}</td>
591
- <td>{}</td>
767
+ {cells}
768
+ <td>{features }</td>
592
769
</tr>
593
770
"# ,
594
771
i + 1 ,
595
772
unit. name_ver( ) ,
596
773
unit. target,
597
774
unit. duration,
598
- codegen,
599
- features,
600
775
) ?;
601
776
}
602
777
write ! ( f, "</tbody>\n </table>\n " ) ?;
@@ -605,15 +780,6 @@ impl<'gctx> Timings<'gctx> {
605
780
}
606
781
607
782
impl UnitTime {
608
- /// Returns the codegen time as (`rmeta_time`, `codegen_time`, percent of total)
609
- fn codegen_time ( & self ) -> Option < ( f64 , f64 , f64 ) > {
610
- self . rmeta_time . map ( |rmeta_time| {
611
- let ctime = self . duration - rmeta_time;
612
- let cent = ( ctime / self . duration ) * 100.0 ;
613
- ( rmeta_time, ctime, cent)
614
- } )
615
- }
616
-
617
783
fn name_ver ( & self ) -> String {
618
784
format ! ( "{} v{}" , self . unit. pkg. name( ) , self . unit. pkg. version( ) )
619
785
}
0 commit comments