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,48 +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 ))
236282
237- # 57 is the width of the whole table
238- divider = "-" * 57
283+ # Empty row
284+ rows .append (("" , "" ))
285+
286+ total_duration_label = "Total duration:"
287+ total_duration = self .total_duration ()
288+ rows .append ((total_duration_label , humantime (total_duration )))
289+
290+ space_after_label = 2
291+ max_label_length = max (16 , max (len (label ) for (label , _ ) in rows )) + space_after_label
292+
293+ table_width = max_label_length + 23
294+ divider = "-" * table_width
239295
240296 with StringIO () as output :
241297 print (divider , file = output )
242- for (name , duration ) in self . stages . items () :
243- pct = (duration / total_duration ) * 100
244- name_str = f" { name } :"
245- print ( f"{ name_str :<34 } { duration :>12.2f} s ({ pct :>5.2f} %)" , file = output )
246-
247- total_duration_label = "Total duration:"
248- print (f"{ total_duration_label :<34 } { total_duration :>12.2f } s " , 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 )
249305 print (divider , file = output , end = "" )
250306 LOGGER .info (f"Timer results\n { output .getvalue ()} " )
251307
@@ -265,6 +321,21 @@ def change_cwd(dir: Path):
265321 os .chdir (cwd )
266322
267323
324+ def humantime (time_s : float ) -> str :
325+ hours = time_s // 3600
326+ time_s = time_s % 3600
327+ minutes = time_s // 60
328+ seconds = time_s % 60
329+
330+ result = ""
331+ if hours > 0 :
332+ result += f"{ int (hours )} h "
333+ if minutes > 0 :
334+ result += f"{ int (minutes )} m "
335+ result += f"{ round (seconds )} s"
336+ return result
337+
338+
268339def move_path (src : Path , dst : Path ):
269340 LOGGER .info (f"Moving `{ src } ` to `{ dst } `" )
270341 shutil .move (src , dst )
@@ -585,15 +656,16 @@ def execute_build_pipeline(timer: Timer, pipeline: Pipeline, final_build_args: L
585656 pipeline .build_rustc_perf ()
586657
587658 # Stage 1: Build rustc + PGO instrumented LLVM
588- with timer .stage ("Build rustc (LLVM PGO)" ):
589- build_rustc (pipeline , args = [
590- "--llvm-profile-generate"
591- ], env = dict (
592- LLVM_PROFILE_DIR = str (pipeline .llvm_profile_dir_root () / "prof-%p" )
593- ))
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+ ))
594666
595- with timer . stage ("Gather profiles (LLVM PGO) " ):
596- gather_llvm_profiles (pipeline )
667+ with stage1 . section ("Gather profiles" ):
668+ gather_llvm_profiles (pipeline )
597669
598670 clear_llvm_files (pipeline )
599671 final_build_args += [
@@ -602,14 +674,15 @@ def execute_build_pipeline(timer: Timer, pipeline: Pipeline, final_build_args: L
602674 ]
603675
604676 # Stage 2: Build PGO instrumented rustc + LLVM
605- with timer .stage ("Build rustc (rustc PGO)" ):
606- build_rustc (pipeline , args = [
607- "--rust-profile-generate" ,
608- pipeline .rustc_profile_dir_root ()
609- ])
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+ ])
610683
611- with timer . stage ("Gather profiles (rustc PGO) " ):
612- gather_rustc_profiles (pipeline )
684+ with stage2 . section ("Gather profiles" ):
685+ gather_rustc_profiles (pipeline )
613686
614687 clear_llvm_files (pipeline )
615688 final_build_args += [
@@ -619,14 +692,15 @@ def execute_build_pipeline(timer: Timer, pipeline: Pipeline, final_build_args: L
619692
620693 # Stage 3: Build rustc + BOLT instrumented LLVM
621694 if pipeline .supports_bolt ():
622- with timer .stage ("Build rustc (LLVM BOLT)" ):
623- build_rustc (pipeline , args = [
624- "--llvm-profile-use" ,
625- pipeline .llvm_profile_merged_file (),
626- "--llvm-bolt-profile-generate" ,
627- ])
628- with timer .stage ("Gather profiles (LLVM BOLT)" ):
629- 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 )
630704
631705 clear_llvm_files (pipeline )
632706 final_build_args += [
@@ -635,7 +709,7 @@ def execute_build_pipeline(timer: Timer, pipeline: Pipeline, final_build_args: L
635709 ]
636710
637711 # Stage 4: Build PGO optimized rustc + PGO/BOLT optimized LLVM
638- with timer .stage ( "Final build" ):
712+ with timer .section ( "Stage 4 (final build) " ):
639713 cmd (final_build_args )
640714
641715
0 commit comments