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()
|