summaryrefslogtreecommitdiff
path: root/tools/perf/python/wakeup-latency.py
blob: f77045d02734eac65f472828e086e7397aed6ae7 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
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()