Skip to content

Commit 7f9cfce

Browse files
committed
Make timer hierarchical
1 parent 7bd8fbb commit 7f9cfce

File tree

1 file changed

+104
-54
lines changed

1 file changed

+104
-54
lines changed

src/ci/stage-build.py

+104-54
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,9 @@
1515
import time
1616
import traceback
1717
import urllib.request
18-
from collections import OrderedDict
1918
from io import StringIO
2019
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
2221

2322
PGO_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+
207228
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
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

Comments
 (0)