1515import time
1616import traceback
1717import urllib .request
18- from collections import OrderedDict
1918from io import StringIO
2019from pathlib import Path
21- from typing import Callable , Dict , Iterable , List , Optional , Union
20+ from typing import Callable , Dict , Iterable , Iterator , List , Optional , Tuple , Union
2221
2322PGO_HOST = os .environ ["PGO_HOST" ]
2423
@@ -204,57 +203,105 @@ def supports_bolt(self) -> bool:
204203 return False
205204
206205
206+ def get_timestamp () -> float :
207+ return time .time ()
208+
209+
210+ Duration = float
211+ TimerSection = Union [Duration , "Timer" ]
212+
213+
214+ def iterate_sections (section : TimerSection , name : str , level : int = 0 ) -> Iterator [Tuple [int , str , Duration ]]:
215+ """
216+ Hierarchically iterate the sections of a timer, in a depth-first order.
217+ """
218+ if isinstance (section , Duration ):
219+ yield (level , name , section )
220+ elif isinstance (section , Timer ):
221+ yield (level , name , section .total_duration ())
222+ for (child_name , child_section ) in section .sections :
223+ yield from iterate_sections (child_section , child_name , level = level + 1 )
224+ else :
225+ assert False
226+
227+
207228class Timer :
208- def __init__ (self ):
209- # We want this dictionary to be ordered by insertion.
210- # We use `OrderedDict` for compatibility with older Python versions.
211- self .stages = OrderedDict ()
229+ def __init__ (self , parent_names : Tuple [ str , ...] = () ):
230+ self . sections : List [ Tuple [ str , TimerSection ]] = []
231+ self . section_active = False
232+ self .parent_names = parent_names
212233
213234 @contextlib .contextmanager
214- def stage (self , name : str ):
215- assert name not in self .stages
235+ def section (self , name : str ) -> "Timer" :
236+ assert not self .section_active
237+ self .section_active = True
216238
217- start = time . time ()
239+ start = get_timestamp ()
218240 exc = None
241+
242+ child_timer = Timer (parent_names = self .parent_names + (name , ))
243+ full_name = " > " .join (child_timer .parent_names )
219244 try :
220- LOGGER .info (f"Stage `{ name } ` starts" )
221- yield
245+ LOGGER .info (f"Section `{ full_name } ` starts" )
246+ yield child_timer
222247 except BaseException as exception :
223248 exc = exception
224249 raise
225250 finally :
226- end = time . time ()
251+ end = get_timestamp ()
227252 duration = end - start
228- self .stages [name ] = duration
253+
254+ if child_timer .has_children ():
255+ self .sections .append ((name , child_timer ))
256+ else :
257+ self .sections .append ((name , duration ))
229258 if exc is None :
230- LOGGER .info (f"Stage `{ name } ` ended: OK ({ duration :.2f} s)" )
259+ LOGGER .info (f"Section `{ full_name } ` ended: OK ({ duration :.2f} s)" )
260+ else :
261+ LOGGER .info (f"Section `{ full_name } ` ended: FAIL ({ duration :.2f} s)" )
262+ self .section_active = False
263+
264+ def total_duration (self ) -> Duration :
265+ duration = 0
266+ for (_ , section ) in self .sections :
267+ if isinstance (section , Duration ):
268+ duration += section
231269 else :
232- LOGGER .info (f"Stage `{ name } ` ended: FAIL ({ duration :.2f} s)" )
270+ duration += section .total_duration ()
271+ return duration
272+
273+ def has_children (self ) -> bool :
274+ return len (self .sections ) > 0
233275
234276 def print_stats (self ):
235- total_duration = sum (self .stages .values ())
277+ rows = []
278+ for (child_name , child_section ) in self .sections :
279+ for (level , name , duration ) in iterate_sections (child_section , child_name , level = 0 ):
280+ label = f"{ ' ' * level } { name } :"
281+ rows .append ((label , duration ))
282+
283+ # Empty row
284+ rows .append (("" , "" ))
236285
237286 total_duration_label = "Total duration:"
287+ total_duration = self .total_duration ()
288+ rows .append ((total_duration_label , humantime (total_duration )))
238289
239- # 1 is for ":", 2 is horizontal space
240- max_label_length = max (16 , max (
241- len (label ) for label in list (self .stages .keys ()) + [total_duration_label [:- 1 ]]
242- )) + 1 + 2
290+ space_after_label = 2
291+ max_label_length = max (16 , max (len (label ) for (label , _ ) in rows )) + space_after_label
243292
244293 table_width = max_label_length + 23
245294 divider = "-" * table_width
246295
247296 with StringIO () as output :
248297 print (divider , file = output )
249- for (name , duration ) in self .stages .items ():
250- pct = (duration / total_duration ) * 100
251- name_str = f"{ name } :"
252- print (f"{ name_str :<{max_label_length }} { duration :>12.2f} s ({ pct :>5.2f} %)" ,
253- file = output )
254-
255- print (file = output )
256- print (f"{ total_duration_label :<{max_label_length }} { humantime (total_duration ):>22} " ,
257- file = output )
298+ for (label , duration ) in rows :
299+ if isinstance (duration , Duration ):
300+ pct = (duration / total_duration ) * 100
301+ value = f"{ duration :>12.2f} s ({ pct :>5.2f} %)"
302+ else :
303+ value = f"{ duration :>{len (total_duration_label ) + 7 }} "
304+ print (f"{ label :<{max_label_length }} { value } " , file = output )
258305 print (divider , file = output , end = "" )
259306 LOGGER .info (f"Timer results\n { output .getvalue ()} " )
260307
@@ -274,7 +321,7 @@ def change_cwd(dir: Path):
274321 os .chdir (cwd )
275322
276323
277- def humantime (time_s : int ) -> str :
324+ def humantime (time_s : float ) -> str :
278325 hours = time_s // 3600
279326 time_s = time_s % 3600
280327 minutes = time_s // 60
@@ -609,15 +656,16 @@ def execute_build_pipeline(timer: Timer, pipeline: Pipeline, final_build_args: L
609656 pipeline .build_rustc_perf ()
610657
611658 # Stage 1: Build rustc + PGO instrumented LLVM
612- with timer .stage ("Build rustc (LLVM PGO)" ):
613- build_rustc (pipeline , args = [
614- "--llvm-profile-generate"
615- ], env = dict (
616- LLVM_PROFILE_DIR = str (pipeline .llvm_profile_dir_root () / "prof-%p" )
617- ))
659+ with timer .section ("Stage 1 (LLVM PGO)" ) as stage1 :
660+ with stage1 .section ("Build rustc and LLVM" ):
661+ build_rustc (pipeline , args = [
662+ "--llvm-profile-generate"
663+ ], env = dict (
664+ LLVM_PROFILE_DIR = str (pipeline .llvm_profile_dir_root () / "prof-%p" )
665+ ))
618666
619- with timer . stage ("Gather profiles (LLVM PGO) " ):
620- gather_llvm_profiles (pipeline )
667+ with stage1 . section ("Gather profiles" ):
668+ gather_llvm_profiles (pipeline )
621669
622670 clear_llvm_files (pipeline )
623671 final_build_args += [
@@ -626,14 +674,15 @@ def execute_build_pipeline(timer: Timer, pipeline: Pipeline, final_build_args: L
626674 ]
627675
628676 # Stage 2: Build PGO instrumented rustc + LLVM
629- with timer .stage ("Build rustc (rustc PGO)" ):
630- build_rustc (pipeline , args = [
631- "--rust-profile-generate" ,
632- pipeline .rustc_profile_dir_root ()
633- ])
677+ with timer .section ("Stage 2 (rustc PGO)" ) as stage2 :
678+ with stage2 .section ("Build rustc and LLVM" ):
679+ build_rustc (pipeline , args = [
680+ "--rust-profile-generate" ,
681+ pipeline .rustc_profile_dir_root ()
682+ ])
634683
635- with timer . stage ("Gather profiles (rustc PGO) " ):
636- gather_rustc_profiles (pipeline )
684+ with stage2 . section ("Gather profiles" ):
685+ gather_rustc_profiles (pipeline )
637686
638687 clear_llvm_files (pipeline )
639688 final_build_args += [
@@ -643,14 +692,15 @@ def execute_build_pipeline(timer: Timer, pipeline: Pipeline, final_build_args: L
643692
644693 # Stage 3: Build rustc + BOLT instrumented LLVM
645694 if pipeline .supports_bolt ():
646- with timer .stage ("Build rustc (LLVM BOLT)" ):
647- build_rustc (pipeline , args = [
648- "--llvm-profile-use" ,
649- pipeline .llvm_profile_merged_file (),
650- "--llvm-bolt-profile-generate" ,
651- ])
652- with timer .stage ("Gather profiles (LLVM BOLT)" ):
653- gather_llvm_bolt_profiles (pipeline )
695+ with timer .section ("Stage 3 (LLVM BOLT)" ) as stage3 :
696+ with stage3 .section ("Build rustc and LLVM" ):
697+ build_rustc (pipeline , args = [
698+ "--llvm-profile-use" ,
699+ pipeline .llvm_profile_merged_file (),
700+ "--llvm-bolt-profile-generate" ,
701+ ])
702+ with stage3 .section ("Gather profiles" ):
703+ gather_llvm_bolt_profiles (pipeline )
654704
655705 clear_llvm_files (pipeline )
656706 final_build_args += [
@@ -659,7 +709,7 @@ def execute_build_pipeline(timer: Timer, pipeline: Pipeline, final_build_args: L
659709 ]
660710
661711 # Stage 4: Build PGO optimized rustc + PGO/BOLT optimized LLVM
662- with timer .stage ( "Final build" ):
712+ with timer .section ( "Stage 4 (final build) " ):
663713 cmd (final_build_args )
664714
665715
0 commit comments