[<prev] [next>] [<thread-prev] [thread-next>] [day] [month] [year] [list]
Message-ID: <20260121143208.340549136@infradead.org>
Date: Wed, 21 Jan 2026 15:25:02 +0100
From: Peter Zijlstra <peterz@...radead.org>
To: tglx@...utronix.de,
mathieu.desnoyers@...icios.com
Cc: linux-kernel@...r.kernel.org,
peterz@...radead.org,
paulmck@...nel.org,
boqun.feng@...il.com,
corbet@....net,
prakash.sangappa@...cle.com,
vineethr@...ux.ibm.com,
kprateek.nayak@....com,
rostedt@...dmis.org,
bigeasy@...utronix.de,
arnd@...db.de,
rdunlap@...radead.org,
rongevarg@...il.com,
longman@...hat.com
Subject: [PATCH 5/5] selftests/rseq: Add rseq slice histogram script
A script that processes trace-cmd data and generates a histogram of
rseq slice_ext durations for the recorded workload.
Signed-off-by: Peter Zijlstra (Intel) <peterz@...radead.org>
---
Documentation/userspace-api/rseq.rst | 3
tools/testing/selftests/rseq/rseq-slice-hist.py | 132 ++++++++++++++++++++++++
2 files changed, 135 insertions(+)
--- a/Documentation/userspace-api/rseq.rst
+++ b/Documentation/userspace-api/rseq.rst
@@ -83,6 +83,9 @@ determined by debugfs:rseq/slice_ext_nse
is the minimum value. It can be incremented to 50 usecs, however doing so
can/will affect the minimum scheduling latency.
+Any proposed changes to this default will have to come with a selftest and
+rseq-slice-hist.py output that shows the new value has merrit.
+
The kernel indicates the grant by clearing rseq::slice_ctrl::request and
setting rseq::slice_ctrl::granted to 1. If there is a reschedule of the
thread after granting the extension, the kernel clears the granted bit to
--- /dev/null
+++ b/tools/testing/selftests/rseq/rseq-slice-hist.py
@@ -0,0 +1,132 @@
+#!/usr/bin/python3
+
+#
+# trace-cmd record -e hrtimer_start -e hrtimer_cancel -e hrtimer_expire_entry -- $cmd
+#
+
+from tracecmd import *
+
+def load_kallsyms(file_path='/proc/kallsyms'):
+ """
+ Parses /proc/kallsyms into a dictionary.
+ Returns: { address_int: symbol_name }
+ """
+ kallsyms_map = {}
+
+ try:
+ with open(file_path, 'r') as f:
+ for line in f:
+ # The format is: [address] [type] [name] [module]
+ parts = line.split()
+ if len(parts) < 3:
+ continue
+
+ addr = int(parts[0], 16)
+ name = parts[2]
+
+ kallsyms_map[addr] = name
+
+ except PermissionError:
+ print(f"Error: Permission denied reading {file_path}. Try running with sudo.")
+ except FileNotFoundError:
+ print(f"Error: {file_path} not found.")
+
+ return kallsyms_map
+
+ksyms = load_kallsyms()
+
+# pending[timer_ptr] = {'ts': timestamp, 'comm': comm}
+pending = {}
+
+# histograms[comm][bucket] = count
+histograms = {}
+
+class OnlineHarmonicMean:
+ def __init__(self):
+ self.n = 0 # Count of elements
+ self.S = 0.0 # Cumulative sum of reciprocals
+
+ def update(self, x):
+ if x == 0:
+ raise ValueError("Harmonic mean is undefined for zero.")
+
+ self.n += 1
+ self.S += 1.0 / x
+ return self.n / self.S
+
+ @property
+ def mean(self):
+ return self.n / self.S if self.n > 0 else 0
+
+ohms = {}
+
+def handle_start(record):
+ func_name = ksyms[record.num_field("function")]
+ if "rseq_slice_expired" in func_name:
+ timer_ptr = record.num_field("hrtimer")
+ pending[timer_ptr] = {
+ 'ts': record.ts,
+ 'comm': record.comm
+ }
+ return None
+
+def handle_cancel(record):
+ timer_ptr = record.num_field("hrtimer")
+
+ if timer_ptr in pending:
+ start_data = pending.pop(timer_ptr)
+ duration_ns = record.ts - start_data['ts']
+ duration_us = duration_ns // 1000
+
+ comm = start_data['comm']
+
+ if comm not in ohms:
+ ohms[comm] = OnlineHarmonicMean()
+
+ ohms[comm].update(duration_ns)
+
+ if comm not in histograms:
+ histograms[comm] = {}
+
+ histograms[comm][duration_us] = histograms[comm].get(duration_us, 0) + 1
+ return None
+
+def handle_expire(record):
+ timer_ptr = record.num_field("hrtimer")
+
+ if timer_ptr in pending:
+ start_data = pending.pop(timer_ptr)
+ comm = start_data['comm']
+
+ if comm not in histograms:
+ histograms[comm] = {}
+
+ # Record -1 bucket for expired (failed to cancel)
+ histograms[comm][-1] = histograms[comm].get(-1, 0) + 1
+ return None
+
+if __name__ == "__main__":
+ t = Trace("trace.dat")
+ for cpu in range(0, t.cpus):
+ ev = t.read_event(cpu)
+ while ev:
+ if "hrtimer_start" in ev.name:
+ handle_start(ev)
+ if "hrtimer_cancel" in ev.name:
+ handle_cancel(ev)
+ if "hrtimer_expire_entry" in ev.name:
+ handle_expire(ev)
+
+ ev = t.read_event(cpu)
+
+ print("\n" + "="*40)
+ print("RSEQ SLICE HISTOGRAM (us)")
+ print("="*40)
+ for comm, buckets in histograms.items():
+ print(f"\nTask: {comm} Mean: {ohms[comm].mean:.3f} ns")
+ print(f" {'Latency (us)':<15} | {'Count'}")
+ print(f" {'-'*30}")
+ # Sort buckets numerically, putting -1 at the top
+ for bucket in sorted(buckets.keys()):
+ label = "EXPIRED" if bucket == -1 else f"{bucket} us"
+ print(f" {label:<15} | {buckets[bucket]}")
Powered by blists - more mailing lists