15
15
import time
16
16
import traceback
17
17
import urllib .request
18
- from collections import OrderedDict
19
18
from io import StringIO
20
19
from 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
22
21
23
22
PGO_HOST = os .environ ["PGO_HOST" ]
24
23
@@ -204,57 +203,105 @@ def supports_bolt(self) -> bool:
204
203
return False
205
204
206
205
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
+
207
228
class 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
212
233
213
234
@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
216
238
217
- start = time . time ()
239
+ start = get_timestamp ()
218
240
exc = None
241
+
242
+ child_timer = Timer (parent_names = self .parent_names + (name , ))
243
+ full_name = " > " .join (child_timer .parent_names )
219
244
try :
220
- LOGGER .info (f"Stage `{ name } ` starts" )
221
- yield
245
+ LOGGER .info (f"Section `{ full_name } ` starts" )
246
+ yield child_timer
222
247
except BaseException as exception :
223
248
exc = exception
224
249
raise
225
250
finally :
226
- end = time . time ()
251
+ end = get_timestamp ()
227
252
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 ))
229
258
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
231
269
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
233
275
234
276
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 (("" , "" ))
236
285
237
286
total_duration_label = "Total duration:"
287
+ total_duration = self .total_duration ()
288
+ rows .append ((total_duration_label , humantime (total_duration )))
238
289
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
243
292
244
293
table_width = max_label_length + 23
245
294
divider = "-" * table_width
246
295
247
296
with StringIO () as output :
248
297
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 )
258
305
print (divider , file = output , end = "" )
259
306
LOGGER .info (f"Timer results\n { output .getvalue ()} " )
260
307
@@ -274,7 +321,7 @@ def change_cwd(dir: Path):
274
321
os .chdir (cwd )
275
322
276
323
277
- def humantime (time_s : int ) -> str :
324
+ def humantime (time_s : float ) -> str :
278
325
hours = time_s // 3600
279
326
time_s = time_s % 3600
280
327
minutes = time_s // 60
@@ -609,15 +656,16 @@ def execute_build_pipeline(timer: Timer, pipeline: Pipeline, final_build_args: L
609
656
pipeline .build_rustc_perf ()
610
657
611
658
# 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
+ ))
618
666
619
- with timer . stage ("Gather profiles (LLVM PGO) " ):
620
- gather_llvm_profiles (pipeline )
667
+ with stage1 . section ("Gather profiles" ):
668
+ gather_llvm_profiles (pipeline )
621
669
622
670
clear_llvm_files (pipeline )
623
671
final_build_args += [
@@ -626,14 +674,15 @@ def execute_build_pipeline(timer: Timer, pipeline: Pipeline, final_build_args: L
626
674
]
627
675
628
676
# 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
+ ])
634
683
635
- with timer . stage ("Gather profiles (rustc PGO) " ):
636
- gather_rustc_profiles (pipeline )
684
+ with stage2 . section ("Gather profiles" ):
685
+ gather_rustc_profiles (pipeline )
637
686
638
687
clear_llvm_files (pipeline )
639
688
final_build_args += [
@@ -643,14 +692,15 @@ def execute_build_pipeline(timer: Timer, pipeline: Pipeline, final_build_args: L
643
692
644
693
# Stage 3: Build rustc + BOLT instrumented LLVM
645
694
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 )
654
704
655
705
clear_llvm_files (pipeline )
656
706
final_build_args += [
@@ -659,7 +709,7 @@ def execute_build_pipeline(timer: Timer, pipeline: Pipeline, final_build_args: L
659
709
]
660
710
661
711
# Stage 4: Build PGO optimized rustc + PGO/BOLT optimized LLVM
662
- with timer .stage ( "Final build" ):
712
+ with timer .section ( "Stage 4 (final build) " ):
663
713
cmd (final_build_args )
664
714
665
715
0 commit comments