From 2fc36cce77d8d94e4a17bc41ac9438bdb64ceb89 Mon Sep 17 00:00:00 2001 From: Ian Rogers Date: Mon, 28 Sep 2026 23:58:33 -0700 Subject: perf python stat-cpi: Fix live mode signal races and test flakiness In test_stat_cpi_python.sh, 'perf test -w noploop &' defaults to a 1-second duration and can exit under heavy parallel load before 'perf stat -p' and 'perf script stat-cpi' finish starting up. In addition, the fixed 'sleep 0.5' before sending SIGINT can fire before Python finishes importing the perf module, opening the live evlist, and flushing the first interval. In stat-cpi.py, register SIGINT and SIGTERM handlers before calling _open_live_evlist() and pass flush=True when printing live output so redirected stdout is flushed immediately after each interval. In test_stat_cpi_python.sh, run 'perf test -w noploop 60 &' so the target workload stays alive until killed, and poll the output file for 'cpi' (up to 5 seconds) before sending SIGINT. Fixes: 4425182d426b ("perf python: Port stat-cpi to perf module") Assisted-by: Antigravity:gemini-3.1-pro Signed-off-by: Ian Rogers Signed-off-by: Arnaldo Carvalho de Melo --- tools/perf/python/stat-cpi.py | 32 ++++++++++++++++++++++---------- 1 file changed, 22 insertions(+), 10 deletions(-) (limited to 'tools/perf/python') diff --git a/tools/perf/python/stat-cpi.py b/tools/perf/python/stat-cpi.py index 0b7d76876a6c..b4ec87b07938 100755 --- a/tools/perf/python/stat-cpi.py +++ b/tools/perf/python/stat-cpi.py @@ -106,7 +106,8 @@ class StatCpiAnalyzer: if ins != 0: cpi = cyc / float(ins) t_sec = timestamp / 1000000000.0 - print(f"{t_sec:15f}: cpu {cpu}, thread {thread} -> cpi {cpi:f} ({cyc:.0f}/{ins:.0f})") + print(f"{t_sec:15f}: cpu {cpu}, thread {thread} -> cpi {cpi:f} ({cyc:.0f}/{ins:.0f})", + flush=True) def read_counters(self, evlist: Any) -> None: """Read counters live.""" @@ -151,6 +152,7 @@ class StatCpiAnalyzer: last_err: Optional[OSError] = None for events, tmap in candidates: + evlist = None try: evlist = perf.parse_events(events, None, tmap) for evsel in evlist: @@ -161,32 +163,41 @@ class StatCpiAnalyzer: evlist.enable() return evlist except PermissionError as e: + if evlist is not None: + evlist.close() last_err = e except OSError as e: + if evlist is not None: + evlist.close() if e.errno == 13: last_err = e else: raise + except BaseException: + if evlist is not None: + evlist.close() + raise if last_err is not None: raise last_err raise RuntimeError("Failed to open events") def run_live(self) -> None: """Read counters live.""" - try: - evlist = self._open_live_evlist() - except OSError as e: - print(f"Failed to open events: {e}", file=sys.stderr) - sys.exit(1) - def handle_signal(_signum: int, _frame: Any) -> None: raise KeyboardInterrupt signal.signal(signal.SIGINT, signal.default_int_handler) signal.signal(signal.SIGTERM, handle_signal) - print("Live mode started. Press Ctrl+C to stop.") + evlist = None try: + try: + evlist = self._open_live_evlist() + except OSError as e: + print(f"Failed to open events: {e}", file=sys.stderr) + sys.exit(1) + + print("Live mode started. Press Ctrl+C to stop.", flush=True) while True: time.sleep(self.args.interval) timestamp = time.time_ns() @@ -195,9 +206,10 @@ class StatCpiAnalyzer: self.data.clear() self.recorded_pairs.clear() except KeyboardInterrupt: - print("\nStopped.") + print("\nStopped.", flush=True) finally: - evlist.close() + if evlist is not None: + evlist.close() def main() -> None: """Main function.""" -- cgit v1.2.3