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,48 +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 ))
236
282
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
239
295
240
296
with StringIO () as output :
241
297
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 )
249
305
print (divider , file = output , end = "" )
250
306
LOGGER .info (f"Timer results\n { output .getvalue ()} " )
251
307
@@ -265,6 +321,21 @@ def change_cwd(dir: Path):
265
321
os .chdir (cwd )
266
322
267
323
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
+
268
339
def move_path (src : Path , dst : Path ):
269
340
LOGGER .info (f"Moving `{ src } ` to `{ dst } `" )
270
341
shutil .move (src , dst )
@@ -585,15 +656,16 @@ def execute_build_pipeline(timer: Timer, pipeline: Pipeline, final_build_args: L
585
656
pipeline .build_rustc_perf ()
586
657
587
658
# 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
+ ))
594
666
595
- with timer . stage ("Gather profiles (LLVM PGO) " ):
596
- gather_llvm_profiles (pipeline )
667
+ with stage1 . section ("Gather profiles" ):
668
+ gather_llvm_profiles (pipeline )
597
669
598
670
clear_llvm_files (pipeline )
599
671
final_build_args += [
@@ -602,14 +674,15 @@ def execute_build_pipeline(timer: Timer, pipeline: Pipeline, final_build_args: L
602
674
]
603
675
604
676
# 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
+ ])
610
683
611
- with timer . stage ("Gather profiles (rustc PGO) " ):
612
- gather_rustc_profiles (pipeline )
684
+ with stage2 . section ("Gather profiles" ):
685
+ gather_rustc_profiles (pipeline )
613
686
614
687
clear_llvm_files (pipeline )
615
688
final_build_args += [
@@ -619,14 +692,15 @@ def execute_build_pipeline(timer: Timer, pipeline: Pipeline, final_build_args: L
619
692
620
693
# Stage 3: Build rustc + BOLT instrumented LLVM
621
694
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 )
630
704
631
705
clear_llvm_files (pipeline )
632
706
final_build_args += [
@@ -635,7 +709,7 @@ def execute_build_pipeline(timer: Timer, pipeline: Pipeline, final_build_args: L
635
709
]
636
710
637
711
# Stage 4: Build PGO optimized rustc + PGO/BOLT optimized LLVM
638
- with timer .stage ( "Final build" ):
712
+ with timer .section ( "Stage 4 (final build) " ):
639
713
cmd (final_build_args )
640
714
641
715
0 commit comments