diff options
| author | Ian Rogers <irogers@google.com> | 2026-09-25 23:20:00 -0700 |
|---|---|---|
| committer | Arnaldo Carvalho de Melo <acme@redhat.com> | 2026-09-26 20:49:30 +0200 |
| commit | 97c70711fc5b7446e66340dcf8b40dd31c27b5d3 (patch) | |
| tree | 4fc4bc12bcc6ac8a10b65c1c6a558eb7e863d351 /tools/perf/python | |
| parent | 56d9f05dce1dd091c0ca2683aaa2d70fdc1884a3 (diff) | |
perf python: Port wakeup-latency from Perl to perf module
Replace the legacy Perl script wakeup-latency.pl with a standalone
Python script in tools/perf/python/wakeup-latency.py using the perf
Python module.
Improvements compared to the legacy Perl script:
- Remove the dependency on libperl and Perf::Trace::Util.
- Track wakeup timestamps per-task (keyed by the wakee PID on
sched:sched_wakeup and matched against next_pid on sched:sched_switch)
rather than per-CPU, avoiding mixed latencies when multiple wakeups
occur on a CPU before a context switch.
- Guard print_totals() when total_wakeups == 0 (printing 'N/A' instead
of dividing by zero when a trace contains no matched wakeup/switch
pairs).
- Add argparse CLI options (-i/--input) and full type annotations.
Add a shell test
(test_wakeup_latency_python.sh) to verify the standalone script.
Assisted-by: Antigravity:gemini-3.1-pro
Signed-off-by: Ian Rogers <irogers@google.com>
Signed-off-by: Arnaldo Carvalho de Melo <acme@redhat.com>
Diffstat (limited to 'tools/perf/python')
| -rwxr-xr-x | tools/perf/python/wakeup-latency.py | 103 |
1 files changed, 103 insertions, 0 deletions
diff --git a/tools/perf/python/wakeup-latency.py b/tools/perf/python/wakeup-latency.py new file mode 100755 index 000000000000..f77045d02734 --- /dev/null +++ b/tools/perf/python/wakeup-latency.py @@ -0,0 +1,103 @@ +#!/usr/bin/env python3 +# SPDX-License-Identifier: GPL-2.0-only +"""Display avg/min/max wakeup latency.""" +from __future__ import annotations + +import argparse +from collections import defaultdict +import sys +from typing import Optional, Dict +import perf + +class WakeupLatency: + """Tracks and displays wakeup latency statistics.""" + def __init__(self) -> None: + self.last_wakeup: Dict[int, int] = defaultdict(int) + self.max_wakeup_latency: int = 0 + self.min_wakeup_latency: Optional[int] = None + self.total_wakeup_latency = 0 + self.total_wakeups = 0 + self.unhandled: Dict[str, int] = defaultdict(int) + self.session: Optional[perf.session] = None + + def process_event(self, sample: perf.sample_event) -> None: + """Process events.""" + event_name = str(sample.evsel) + if event_name.startswith("evsel(") and event_name.endswith(")"): + event_name = event_name[6:-1] + sample_time = sample.sample_time + + if "sched:sched_wakeup" in event_name: + try: + pid = sample.pid + self.last_wakeup[pid] = sample_time + except AttributeError: + self.unhandled[event_name] += 1 + elif "sched:sched_switch" in event_name: + try: + next_pid = sample.next_pid + wakeup_ts = self.last_wakeup.get(next_pid, 0) + if wakeup_ts: + latency = sample_time - wakeup_ts + self.max_wakeup_latency = max(self.max_wakeup_latency, latency) + if self.min_wakeup_latency is None: + self.min_wakeup_latency = latency + else: + self.min_wakeup_latency = min(self.min_wakeup_latency, latency) + self.total_wakeup_latency += latency + self.total_wakeups += 1 + del self.last_wakeup[next_pid] + except AttributeError: + self.unhandled[event_name] += 1 + else: + self.unhandled[event_name] += 1 + + def print_totals(self) -> None: + """Print summary statistics.""" + print("wakeup_latency stats:\n") + print(f"total_wakeups: {self.total_wakeups}") + if self.total_wakeups: + avg = self.total_wakeup_latency // self.total_wakeups + print(f"avg_wakeup_latency (ns): {avg}") + print(f"min_wakeup_latency (ns): {self.min_wakeup_latency}") + print(f"max_wakeup_latency (ns): {self.max_wakeup_latency}") + else: + print("avg_wakeup_latency (ns): N/A") + print("min_wakeup_latency (ns): N/A") + print("max_wakeup_latency (ns): N/A") + + if self.unhandled: + print("\nunhandled events:\n") + print(f"{'event':<40s} {'count':>10s}") + print(f"{'-'*40} {'-'*10}") + for event_name, count in self.unhandled.items(): + print(f"{event_name:<40s} {count:10d}") + + def run(self, input_file: str) -> None: + """Run the session.""" + self.session = perf.session(perf.data(input_file), sample=self.process_event) + try: + self.session.process_events() + except KeyboardInterrupt: + pass + finally: + # Break the reference cycle between self.session and the bound + # self.process_event callback so the C perf.session object is freed. + self.session = None + self.print_totals() + +def main() -> None: + """Main function.""" + parser = argparse.ArgumentParser(description="Trace wakeup latency") + parser.add_argument("-i", "--input", default="perf.data", help="Input file") + args = parser.parse_args() + + analyzer = WakeupLatency() + try: + analyzer.run(args.input) + except IOError as e: + print(e, file=sys.stderr) + sys.exit(1) + +if __name__ == "__main__": + main() |
