Skip to content

Commit 70534da

Browse files
stellaraccidentGroverkss
authored andcommitted
[python] Overhaul iree.build console output and error handling. (iree-org#19314)
* Status updates look more like what would be expected from something like ninja or bazel: * Short update one liners wink past * If some actions are taking a long time, a multi-line display is shown with the worst lagging ones * Per action errors and dependence errors are now tracked in the build graph and reported correctly (vs triggering the last chance exception handler). * The last chance exception handler for true program errors now prints its exception verbosely prior to blocking waiting for the executor to shutdown (this was causing exception swallowing). Signed-off-by: Stella Laurenzo <[email protected]>
1 parent c70fa0f commit 70534da

File tree

7 files changed

+464
-94
lines changed

7 files changed

+464
-94
lines changed

compiler/bindings/python/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -261,6 +261,7 @@ SOURCES
261261
__main__.py
262262
args.py
263263
compile_actions.py
264+
console.py
264265
executor.py
265266
lang.py
266267
main.py
Lines changed: 152 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,152 @@
1+
# Copyright 2024 The IREE Authors
2+
#
3+
# Licensed under the Apache License v2.0 with LLVM Exceptions.
4+
# See https://llvm.org/LICENSE.txt for license information.
5+
# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6+
7+
from typing import IO
8+
9+
import shutil
10+
import textwrap
11+
import threading
12+
import traceback
13+
from iree.build.executor import BuildDependency, ProgressReporter
14+
15+
16+
class ConsoleProgressReporter(ProgressReporter):
17+
def __init__(
18+
self,
19+
out: IO,
20+
*,
21+
rich_console: bool = True,
22+
long_display_time_threshold: int = 5,
23+
):
24+
self.out = out
25+
self.rich_console = rich_console
26+
self.long_display_time_threshold = long_display_time_threshold
27+
self.display_lines: list[str] = []
28+
self.inflight_deps: set[BuildDependency] = set()
29+
self.finished_deps: set[BuildDependency] = set()
30+
self.most_recent_dep: BuildDependency | None = None
31+
self.all_deps: set[BuildDependency] = set()
32+
self.poller_thread: threading.Thread | None = None
33+
self.lock = threading.RLock()
34+
self.exit_poller_event = threading.Event()
35+
36+
@property
37+
def started_count(self) -> int:
38+
return len(self.finished_deps) + len(self.inflight_deps)
39+
40+
def reset_display(self):
41+
# Clean all known displayed lines.
42+
if not self.rich_console:
43+
return
44+
for line in reversed(self.display_lines):
45+
print(f"\033[A{' ' * len(line)}", file=self.out, end="\r")
46+
47+
def draw_display(self):
48+
for line in self.display_lines:
49+
print(line, file=self.out)
50+
51+
def refresh(self):
52+
current_deps = list(self.inflight_deps)
53+
if not current_deps:
54+
return
55+
new_display_lines = []
56+
if not self.rich_console:
57+
if not self.most_recent_dep:
58+
return
59+
progress_prefix = f"[{self.started_count + 1}/{len(self.all_deps)}]"
60+
new_display_lines.append(f"{progress_prefix} {self.most_recent_dep}")
61+
else:
62+
current_deps.sort(key=lambda dep: dep.execution_time)
63+
active_deps = [d for d in current_deps if d.invoke_time is not None]
64+
if not active_deps:
65+
active_deps = current_deps
66+
focus_dep = active_deps[0]
67+
longest_time = active_deps[-1].execution_time
68+
69+
progress_prefix = f"[{self.started_count + 1}/{len(self.all_deps)}]"
70+
if longest_time > self.long_display_time_threshold:
71+
# Do a long display.
72+
long_count = 15
73+
report_count = min(long_count, len(active_deps))
74+
report_deps = active_deps[-report_count:]
75+
new_display_lines.append(
76+
f"{progress_prefix} Waiting for long running actions:"
77+
)
78+
for dep in report_deps:
79+
new_display_lines.append(
80+
f" {dep} ({round(dep.execution_time)}s)"
81+
)
82+
remaining_count = len(active_deps) - report_count
83+
if remaining_count > 0:
84+
new_display_lines.append(f" ... and {remaining_count} more")
85+
else:
86+
# Summary display
87+
new_display_lines.append(f"{progress_prefix} {focus_dep}")
88+
89+
# Reduce flicker by only refreshing if changed.
90+
if new_display_lines != self.display_lines:
91+
self.reset_display()
92+
self.display_lines.clear()
93+
self.display_lines.extend(new_display_lines)
94+
self.draw_display()
95+
96+
def start_graph(self, all_deps: set[BuildDependency]):
97+
with self.lock:
98+
self.all_deps.update(all_deps)
99+
self.inflight_deps.clear()
100+
self.finished_deps.clear()
101+
if self.rich_console:
102+
self.poller_thread = threading.Thread(
103+
target=self._poll, name="ConsolePoller", daemon=True
104+
)
105+
self.poller_thread.start()
106+
107+
def start_dep(self, dep: BuildDependency):
108+
with self.lock:
109+
self.inflight_deps.add(dep)
110+
self.most_recent_dep = dep
111+
self.refresh()
112+
113+
def finish_dep(self, dep: BuildDependency):
114+
with self.lock:
115+
self.finished_deps.add(dep)
116+
if dep in self.inflight_deps:
117+
self.inflight_deps.remove(dep)
118+
self.refresh()
119+
120+
def report_failure(self, dep: "BuildDependency"):
121+
if dep.is_dependence_failure:
122+
return
123+
with self.lock:
124+
self.reset_display()
125+
self.display_lines.clear()
126+
print(f"ERROR: Building '{dep}' failed:", file=self.out)
127+
if dep.failure:
128+
failure_formatted = "".join(traceback.format_exception(dep.failure))
129+
print(f"{textwrap.indent(failure_formatted, ' ')}\n", file=self.out)
130+
131+
def end_graph(self):
132+
if self.rich_console:
133+
self.exit_poller_event.set()
134+
self.poller_thread.join()
135+
with self.lock:
136+
self.reset_display()
137+
self.display_lines.clear()
138+
139+
success_count = 0
140+
failed_count = 0
141+
for dep in self.finished_deps:
142+
if dep.failure:
143+
failed_count += 1
144+
else:
145+
success_count += 1
146+
if failed_count == 0:
147+
print(f"Successfully built {success_count} actions", file=self.out)
148+
149+
def _poll(self):
150+
while not self.exit_poller_event.wait(timeout=1):
151+
with self.lock:
152+
self.refresh()

0 commit comments

Comments
 (0)