#!/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()