xref: /linux/tools/testing/selftests/rseq/rseq-slice-hist.py (revision 23b0f90ba871f096474e1c27c3d14f455189d2d9)
1#!/usr/bin/python3
2
3#
4# trace-cmd record -e hrtimer_start -e hrtimer_cancel -e hrtimer_expire_entry -- $cmd
5#
6
7from tracecmd import *
8
9def load_kallsyms(file_path='/proc/kallsyms'):
10    """
11    Parses /proc/kallsyms into a dictionary.
12    Returns: { address_int: symbol_name }
13    """
14    kallsyms_map = {}
15
16    try:
17        with open(file_path, 'r') as f:
18            for line in f:
19                # The format is: [address] [type] [name] [module]
20                parts = line.split()
21                if len(parts) < 3:
22                    continue
23
24                addr = int(parts[0], 16)
25                name = parts[2]
26
27                kallsyms_map[addr] = name
28
29    except PermissionError:
30        print(f"Error: Permission denied reading {file_path}. Try running with sudo.")
31    except FileNotFoundError:
32        print(f"Error: {file_path} not found.")
33
34    return kallsyms_map
35
36ksyms = load_kallsyms()
37
38# pending[timer_ptr] = {'ts': timestamp, 'comm': comm}
39pending = {}
40
41# histograms[comm][bucket] = count
42histograms = {}
43
44class OnlineHarmonicMean:
45    def __init__(self):
46        self.n = 0          # Count of elements
47        self.S = 0.0        # Cumulative sum of reciprocals
48
49    def update(self, x):
50        if x == 0:
51            raise ValueError("Harmonic mean is undefined for zero.")
52
53        self.n += 1
54        self.S += 1.0 / x
55        return self.n / self.S
56
57    @property
58    def mean(self):
59        return self.n / self.S if self.n > 0 else 0
60
61ohms = {}
62
63def handle_start(record):
64    func_name = ksyms[record.num_field("function")]
65    if "rseq_slice_expired" in func_name:
66        timer_ptr = record.num_field("hrtimer")
67        pending[timer_ptr] = {
68            'ts': record.ts,
69            'comm': record.comm
70        }
71    return None
72
73def handle_cancel(record):
74    timer_ptr = record.num_field("hrtimer")
75
76    if timer_ptr in pending:
77        start_data = pending.pop(timer_ptr)
78        duration_ns = record.ts - start_data['ts']
79        duration_us = duration_ns // 1000
80
81        comm = start_data['comm']
82
83        if comm not in ohms:
84            ohms[comm] = OnlineHarmonicMean()
85
86        ohms[comm].update(duration_ns)
87
88        if comm not in histograms:
89            histograms[comm] = {}
90
91        histograms[comm][duration_us] = histograms[comm].get(duration_us, 0) + 1
92    return None
93
94def handle_expire(record):
95    timer_ptr = record.num_field("hrtimer")
96
97    if timer_ptr in pending:
98        start_data = pending.pop(timer_ptr)
99        comm = start_data['comm']
100
101        if comm not in histograms:
102            histograms[comm] = {}
103
104        # Record -1 bucket for expired (failed to cancel)
105        histograms[comm][-1] = histograms[comm].get(-1, 0) + 1
106    return None
107
108if __name__ == "__main__":
109    t = Trace("trace.dat")
110    for cpu in range(0, t.cpus):
111        ev = t.read_event(cpu)
112        while ev:
113            if "hrtimer_start" in ev.name:
114                handle_start(ev)
115            if "hrtimer_cancel" in ev.name:
116                handle_cancel(ev)
117            if "hrtimer_expire_entry" in ev.name:
118                handle_expire(ev)
119
120            ev = t.read_event(cpu)
121
122    print("\n" + "="*40)
123    print("RSEQ SLICE HISTOGRAM (us)")
124    print("="*40)
125    for comm, buckets in histograms.items():
126        print(f"\nTask: {comm}    Mean: {ohms[comm].mean:.3f} ns")
127        print(f"  {'Latency (us)':<15} | {'Count'}")
128        print(f"  {'-'*30}")
129        # Sort buckets numerically, putting -1 at the top
130        for bucket in sorted(buckets.keys()):
131            label = "EXPIRED" if bucket == -1 else f"{bucket} us"
132            print(f"  {label:<15} | {buckets[bucket]}")
133