@@ -13,6 +13,7 @@ use crate::util::{CargoResult, GlobalContext};
1313use  anyhow:: Context  as  _; 
1414use  cargo_util:: paths; 
1515use  indexmap:: IndexMap ; 
16+ use  itertools:: Itertools ; 
1617use  std:: collections:: HashMap ; 
1718use  std:: io:: { BufWriter ,  Write } ; 
1819use  std:: thread:: available_parallelism; 
@@ -90,11 +91,75 @@ struct UnitTime {
9091unlocked_units :  Vec < Unit > , 
9192    /// Same as `unlocked_units`, but unlocked by rmeta. 
9293unlocked_rmeta_units :  Vec < Unit > , 
94+     /// Individual compilation section durations, gathered from `--json=timings`. 
9395/// IndexMap is used to keep original insertion order, we want to be able to tell which 
9496/// sections were started in which order. 
9597sections :  IndexMap < String ,  CompilationSection > , 
9698} 
9799
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+     } 
161+ } 
162+ 
98163/// Periodic concurrency tracking information. 
99164#[ derive( serde:: Serialize ) ]  
100165struct  Concurrency  { 
@@ -109,6 +174,35 @@ struct Concurrency {
109174inactive :  usize , 
110175} 
111176
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+ 
112206impl < ' gctx >  Timings < ' gctx >  { 
113207    pub  fn  new ( bcx :  & BuildContext < ' _ ,  ' gctx > ,  root_units :  & [ Unit ] )  -> Timings < ' gctx >  { 
114208        let  has_report = |what| bcx. build_config . timing_outputs . contains ( & what) ; 
@@ -480,6 +574,7 @@ impl<'gctx> Timings<'gctx> {
480574            . enumerate ( ) 
481575            . map ( |( i,  ut) | ( ut. unit . clone ( ) ,  i) ) 
482576            . collect ( ) ; 
577+ 
483578        #[ derive( serde:: Serialize ) ]  
484579        struct  UnitData  { 
485580            i :  usize , 
@@ -553,6 +648,58 @@ impl<'gctx> Timings<'gctx> {
553648
554649    /// Render the table of all units. 
555650fn  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+ 
556703        write ! ( 
557704            f, 
558705            r#" 
@@ -562,20 +709,53 @@ impl<'gctx> Timings<'gctx> {
562709      <th></th> 
563710      <th>Unit</th> 
564711      <th>Total</th> 
565-       <th>Codegen</th>  
712+       {headers}  
566713      <th>Features</th> 
567714    </tr> 
568715  </thead> 
569716  <tbody> 
570- "# 
717+ "# , 
718+             headers = headers. iter( ) . map( |h| format!( "<th>{h}</th>" ) ) . join( "\n " ) 
571719        ) ?; 
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 ( )  { 
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+                 } 
576728                None  => "" . to_string ( ) , 
577-                 Some ( ( _rt,  ctime,  cent) )  => format ! ( "{:.1}s ({:.0}%)" ,  ctime,  cent) , 
578729            } ; 
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+ 
579759            let  features = unit. unit . features . join ( ", " ) ; 
580760            write ! ( 
581761                f, 
@@ -584,16 +764,14 @@ impl<'gctx> Timings<'gctx> {
584764  <td>{}.</td> 
585765  <td>{}{}</td> 
586766  <td>{:.1}s</td> 
587-   <td>{}</td>  
588-   <td>{}</td> 
767+   {cells}  
768+   <td>{features }</td> 
589769</tr> 
590770"# , 
591771                i + 1 , 
592772                unit. name_ver( ) , 
593773                unit. target, 
594774                unit. duration, 
595-                 codegen, 
596-                 features, 
597775            ) ?; 
598776        } 
599777        write ! ( f,  "</tbody>\n </table>\n " ) ?; 
@@ -602,15 +780,6 @@ impl<'gctx> Timings<'gctx> {
602780} 
603781
604782impl  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- 
614783    fn  name_ver ( & self )  -> String  { 
615784        format ! ( "{} v{}" ,  self . unit. pkg. name( ) ,  self . unit. pkg. version( ) ) 
616785    } 
0 commit comments