Skip to content

Commit 64db7fb

Browse files
authored
Rollup merge of rust-lang#107596 - Kobzol:stage-build-timer, r=Mark-Simulacrum
Add nicer output to PGO build timer This PR modifies the timer used in the PGO build script to contain nicer, hierarchical output of the individual build steps. It's not trivial to test locally, so I'll fire up a dist build right away. r? ``@Mark-Simulacrum``
2 parents 800221b + 7f9cfce commit 64db7fb

File tree

1 file changed

+123
-49
lines changed

1 file changed

+123
-49
lines changed

src/ci/stage-build.py

+123-49
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,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+
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))
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+
268339
def 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

Comments
 (0)