6
6
import contextlib
7
7
import getpass
8
8
import glob
9
+ import json
9
10
import logging
10
11
import os
11
12
import pprint
17
18
import urllib .request
18
19
from io import StringIO
19
20
from pathlib import Path
20
- from typing import Callable , Dict , Iterable , Iterator , List , Optional , Tuple , Union
21
+ from typing import Callable , ContextManager , Dict , Iterable , Iterator , List , Optional , \
22
+ Tuple , Union
21
23
22
24
PGO_HOST = os .environ ["PGO_HOST" ]
23
25
@@ -115,6 +117,9 @@ def supports_bolt(self) -> bool:
115
117
def llvm_bolt_profile_merged_file (self ) -> Path :
116
118
return self .opt_artifacts () / "bolt.profdata"
117
119
120
+ def metrics_path (self ) -> Path :
121
+ return self .build_root () / "build" / "metrics.json"
122
+
118
123
119
124
class LinuxPipeline (Pipeline ):
120
125
def checkout_path (self ) -> Path :
@@ -208,32 +213,27 @@ def get_timestamp() -> float:
208
213
209
214
210
215
Duration = float
211
- TimerSection = Union [Duration , "Timer" ]
212
216
213
217
214
- def iterate_sections ( section : TimerSection , name : str , level : int = 0 ) -> Iterator [
218
+ def iterate_timers ( timer : "Timer" , name : str , level : int = 0 ) -> Iterator [
215
219
Tuple [int , str , Duration ]]:
216
220
"""
217
- Hierarchically iterate the sections of a timer, in a depth-first order.
221
+ Hierarchically iterate the children of a timer, in a depth-first order.
218
222
"""
219
- if isinstance (section , Duration ):
220
- yield (level , name , section )
221
- elif isinstance (section , Timer ):
222
- yield (level , name , section .total_duration ())
223
- for (child_name , child_section ) in section .sections :
224
- yield from iterate_sections (child_section , child_name , level = level + 1 )
225
- else :
226
- assert False
223
+ yield (level , name , timer .total_duration ())
224
+ for (child_name , child_timer ) in timer .children :
225
+ yield from iterate_timers (child_timer , child_name , level = level + 1 )
227
226
228
227
229
228
class Timer :
230
229
def __init__ (self , parent_names : Tuple [str , ...] = ()):
231
- self .sections : List [Tuple [str , TimerSection ]] = []
230
+ self .children : List [Tuple [str , Timer ]] = []
232
231
self .section_active = False
233
232
self .parent_names = parent_names
233
+ self .duration_excluding_children : Duration = 0
234
234
235
235
@contextlib .contextmanager
236
- def section (self , name : str ) -> "Timer" :
236
+ def section (self , name : str ) -> ContextManager [ "Timer" ] :
237
237
assert not self .section_active
238
238
self .section_active = True
239
239
@@ -252,33 +252,26 @@ def section(self, name: str) -> "Timer":
252
252
end = get_timestamp ()
253
253
duration = end - start
254
254
255
- if child_timer .has_children ():
256
- self .sections .append ((name , child_timer ))
257
- else :
258
- self .sections .append ((name , duration ))
255
+ child_timer .duration_excluding_children = duration - child_timer .total_duration ()
256
+ self .add_child (name , child_timer )
259
257
if exc is None :
260
258
LOGGER .info (f"Section `{ full_name } ` ended: OK ({ duration :.2f} s)" )
261
259
else :
262
260
LOGGER .info (f"Section `{ full_name } ` ended: FAIL ({ duration :.2f} s)" )
263
261
self .section_active = False
264
262
265
263
def total_duration (self ) -> Duration :
266
- duration = 0
267
- for (_ , section ) in self .sections :
268
- if isinstance (section , Duration ):
269
- duration += section
270
- else :
271
- duration += section .total_duration ()
272
- return duration
264
+ return self .duration_excluding_children + sum (
265
+ c .total_duration () for (_ , c ) in self .children )
273
266
274
267
def has_children (self ) -> bool :
275
- return len (self .sections ) > 0
268
+ return len (self .children ) > 0
276
269
277
270
def print_stats (self ):
278
271
rows = []
279
- for (child_name , child_section ) in self .sections :
280
- for (level , name , duration ) in iterate_sections ( child_section , child_name , level = 0 ):
281
- label = f"{ ' ' * level } { name } :"
272
+ for (child_name , child_timer ) in self .children :
273
+ for (level , name , duration ) in iterate_timers ( child_timer , child_name , level = 0 ):
274
+ label = f"{ ' ' * level } { name } :"
282
275
rows .append ((label , duration ))
283
276
284
277
# Empty row
@@ -306,6 +299,60 @@ def print_stats(self):
306
299
print (divider , file = output , end = "" )
307
300
LOGGER .info (f"Timer results\n { output .getvalue ()} " )
308
301
302
+ def add_child (self , name : str , timer : "Timer" ):
303
+ self .children .append ((name , timer ))
304
+
305
+ def add_duration (self , name : str , duration : Duration ):
306
+ timer = Timer (parent_names = self .parent_names + (name ,))
307
+ timer .duration_excluding_children = duration
308
+ self .add_child (name , timer )
309
+
310
+
311
+ class BuildStep :
312
+ def __init__ (self , type : str , children : List ["BuildStep" ], duration : float ):
313
+ self .type = type
314
+ self .children = children
315
+ self .duration = duration
316
+
317
+ def find_all_by_type (self , type : str ) -> Iterator ["BuildStep" ]:
318
+ if type == self .type :
319
+ yield self
320
+ for child in self .children :
321
+ yield from child .find_all_by_type (type )
322
+
323
+ def __repr__ (self ):
324
+ return f"BuildStep(type={ self .type } , duration={ self .duration } , children={ len (self .children )} )"
325
+
326
+
327
+ def load_last_metrics (path : Path ) -> BuildStep :
328
+ """
329
+ Loads the metrics of the most recent bootstrap execution from a metrics.json file.
330
+ """
331
+ with open (path , "r" ) as f :
332
+ metrics = json .load (f )
333
+ invocation = metrics ["invocations" ][- 1 ]
334
+
335
+ def parse (entry ) -> Optional [BuildStep ]:
336
+ if "kind" not in entry or entry ["kind" ] != "rustbuild_step" :
337
+ return None
338
+ type = entry .get ("type" , "" )
339
+ duration = entry .get ("duration_excluding_children_sec" , 0 )
340
+ children = []
341
+
342
+ for child in entry .get ("children" , ()):
343
+ step = parse (child )
344
+ if step is not None :
345
+ children .append (step )
346
+ duration += step .duration
347
+ return BuildStep (type = type , children = children , duration = duration )
348
+
349
+ children = [parse (child ) for child in invocation .get ("children" , ())]
350
+ return BuildStep (
351
+ type = "root" ,
352
+ children = children ,
353
+ duration = invocation .get ("duration_including_children_sec" , 0 )
354
+ )
355
+
309
356
310
357
@contextlib .contextmanager
311
358
def change_cwd (dir : Path ):
@@ -645,7 +692,7 @@ def print_binary_sizes(pipeline: Pipeline):
645
692
with StringIO () as output :
646
693
for path in paths :
647
694
path_str = f"{ path .name } :"
648
- print (f"{ path_str :<30 } { format_bytes (path .stat ().st_size ):>14} " , file = output )
695
+ print (f"{ path_str :<50 } { format_bytes (path .stat ().st_size ):>14} " , file = output )
649
696
LOGGER .info (f"Rustc binary size\n { output .getvalue ()} " )
650
697
651
698
@@ -659,6 +706,44 @@ def print_free_disk_space(pipeline: Pipeline):
659
706
f"Free disk space: { format_bytes (free )} out of total { format_bytes (total )} ({ (used / total ) * 100 :.2f} % used)" )
660
707
661
708
709
+ def log_metrics (step : BuildStep ):
710
+ substeps : List [Tuple [int , BuildStep ]] = []
711
+
712
+ def visit (step : BuildStep , level : int ):
713
+ substeps .append ((level , step ))
714
+ for child in step .children :
715
+ visit (child , level = level + 1 )
716
+
717
+ visit (step , 0 )
718
+
719
+ output = StringIO ()
720
+ for (level , step ) in substeps :
721
+ label = f"{ '.' * level } { step .type } "
722
+ print (f"{ label :<65} { step .duration :>8.2f} s" , file = output )
723
+ logging .info (f"Build step durations\n { output .getvalue ()} " )
724
+
725
+
726
+ def record_metrics (pipeline : Pipeline , timer : Timer ):
727
+ metrics = load_last_metrics (pipeline .metrics_path ())
728
+ if metrics is None :
729
+ return
730
+ llvm_steps = tuple (metrics .find_all_by_type ("bootstrap::native::Llvm" ))
731
+ assert len (llvm_steps ) > 0
732
+ llvm_duration = sum (step .duration for step in llvm_steps )
733
+
734
+ rustc_steps = tuple (metrics .find_all_by_type ("bootstrap::compile::Rustc" ))
735
+ assert len (rustc_steps ) > 0
736
+ rustc_duration = sum (step .duration for step in rustc_steps )
737
+
738
+ # The LLVM step is part of the Rustc step
739
+ rustc_duration -= llvm_duration
740
+
741
+ timer .add_duration ("LLVM" , llvm_duration )
742
+ timer .add_duration ("Rustc" , rustc_duration )
743
+
744
+ log_metrics (metrics )
745
+
746
+
662
747
def execute_build_pipeline (timer : Timer , pipeline : Pipeline , final_build_args : List [str ]):
663
748
# Clear and prepare tmp directory
664
749
shutil .rmtree (pipeline .opt_artifacts (), ignore_errors = True )
@@ -668,12 +753,13 @@ def execute_build_pipeline(timer: Timer, pipeline: Pipeline, final_build_args: L
668
753
669
754
# Stage 1: Build rustc + PGO instrumented LLVM
670
755
with timer .section ("Stage 1 (LLVM PGO)" ) as stage1 :
671
- with stage1 .section ("Build rustc and LLVM" ):
756
+ with stage1 .section ("Build rustc and LLVM" ) as rustc_build :
672
757
build_rustc (pipeline , args = [
673
758
"--llvm-profile-generate"
674
759
], env = dict (
675
760
LLVM_PROFILE_DIR = str (pipeline .llvm_profile_dir_root () / "prof-%p" )
676
761
))
762
+ record_metrics (pipeline , rustc_build )
677
763
678
764
with stage1 .section ("Gather profiles" ):
679
765
gather_llvm_profiles (pipeline )
@@ -687,11 +773,12 @@ def execute_build_pipeline(timer: Timer, pipeline: Pipeline, final_build_args: L
687
773
688
774
# Stage 2: Build PGO instrumented rustc + LLVM
689
775
with timer .section ("Stage 2 (rustc PGO)" ) as stage2 :
690
- with stage2 .section ("Build rustc and LLVM" ):
776
+ with stage2 .section ("Build rustc and LLVM" ) as rustc_build :
691
777
build_rustc (pipeline , args = [
692
778
"--rust-profile-generate" ,
693
779
pipeline .rustc_profile_dir_root ()
694
780
])
781
+ record_metrics (pipeline , rustc_build )
695
782
696
783
with stage2 .section ("Gather profiles" ):
697
784
gather_rustc_profiles (pipeline )
@@ -706,12 +793,14 @@ def execute_build_pipeline(timer: Timer, pipeline: Pipeline, final_build_args: L
706
793
# Stage 3: Build rustc + BOLT instrumented LLVM
707
794
if pipeline .supports_bolt ():
708
795
with timer .section ("Stage 3 (LLVM BOLT)" ) as stage3 :
709
- with stage3 .section ("Build rustc and LLVM" ):
796
+ with stage3 .section ("Build rustc and LLVM" ) as rustc_build :
710
797
build_rustc (pipeline , args = [
711
798
"--llvm-profile-use" ,
712
799
pipeline .llvm_profile_merged_file (),
713
800
"--llvm-bolt-profile-generate" ,
714
801
])
802
+ record_metrics (pipeline , rustc_build )
803
+
715
804
with stage3 .section ("Gather profiles" ):
716
805
gather_llvm_bolt_profiles (pipeline )
717
806
@@ -723,8 +812,9 @@ def execute_build_pipeline(timer: Timer, pipeline: Pipeline, final_build_args: L
723
812
]
724
813
725
814
# Stage 4: Build PGO optimized rustc + PGO/BOLT optimized LLVM
726
- with timer .section ("Stage 4 (final build)" ):
815
+ with timer .section ("Stage 4 (final build)" ) as stage4 :
727
816
cmd (final_build_args )
817
+ record_metrics (pipeline , stage4 )
728
818
729
819
730
820
if __name__ == "__main__" :
0 commit comments