[PATCH v10 19/19] perf python: Add LiveSession helper

From: Ian Rogers

Date: Fri Jun 05 2026 - 04:04:32 EST


Add LiveSession class in tools/perf/python/perf_live.py to support
live event collection using perf.evlist and perf.parse_events,
avoiding the need to fork a separate perf record process.

Signed-off-by: Ian Rogers <irogers@xxxxxxxxxx>
Assisted-by: Gemini:gemini-3.1-pro-preview
---
v2:

1. Fixed File Descriptor Leak: I moved self.evlist.mmap() inside the
try block so that if it raises an exception, the finally block will
still be executed and call self.evlist.close() , preventing file
descriptor leaks.

2. Handled InterruptedError in poll() : I wrapped the poll() call in a
try-except block to catch InterruptedError and continue the
loop. This prevents the live session from crashing on non-fatal
signals like SIGWINCH .

3. Added evlist.config() : I added a call to self.evlist.config() in
the constructor after parse_events() . This applies the default
record options to the events, enabling sampling and setting up
PERF_SAMPLE_* fields so that the kernel will actually generate
PERF_RECORD_SAMPLE events.

4. Enable the evlist and be robust to exceptions from reading unsupported
events like mmap2.

v8:
- Drain all events from a CPU before moving to the next.
---
tools/perf/python/perf_live.py | 56 ++++++++++++++++++++++
tools/perf/tests/shell/lib/setup_python.sh | 12 +++++
2 files changed, 68 insertions(+)
create mode 100755 tools/perf/python/perf_live.py

diff --git a/tools/perf/python/perf_live.py b/tools/perf/python/perf_live.py
new file mode 100755
index 000000000000..46522964cb79
--- /dev/null
+++ b/tools/perf/python/perf_live.py
@@ -0,0 +1,56 @@
+# SPDX-License-Identifier: GPL-2.0
+"""
+Live event session helper using perf.evlist.
+
+This module provides a LiveSession class that allows running a callback
+for each event collected live from the system, similar to perf.session
+but without requiring a perf.data file.
+"""
+
+import perf
+
+
+class LiveSession:
+ """Represents a live event collection session."""
+
+ def __init__(self, event_string: str, sample_callback):
+ self.event_string = event_string
+ self.sample_callback = sample_callback
+ # Create a cpu map for all online CPUs
+ self.cpus = perf.cpu_map()
+ # Parse events and set maps
+ self.evlist = perf.parse_events(self.event_string, self.cpus)
+ self.evlist.config()
+
+ def run(self):
+ """Run the live session."""
+ try:
+ self.evlist.open()
+ self.evlist.mmap()
+ self.evlist.enable()
+
+ while True:
+ # Poll for events with 100ms timeout
+ try:
+ self.evlist.poll(100)
+ except InterruptedError:
+ continue
+ for cpu in self.cpus:
+ for _ in range(1000): # Limit to 1000 events per CPU per poll to prevent starvation
+ try:
+ event = self.evlist.read_on_cpu(cpu)
+ if event is None:
+ break
+ if event.type == perf.RECORD_SAMPLE:
+ self.sample_callback(event)
+ except OSError as e:
+ # OS/mmap errors are unrecoverable and will cause busy-looping if ignored
+ raise e
+ except Exception as e:
+ # Parsing or type errors are recoverable as the event was consumed
+ import sys
+ print(f"Error parsing event on CPU {cpu}: {e}", file=sys.stderr)
+ except KeyboardInterrupt:
+ pass
+ finally:
+ self.evlist.close()
diff --git a/tools/perf/tests/shell/lib/setup_python.sh b/tools/perf/tests/shell/lib/setup_python.sh
index a58e5536f2ed..86c523207c09 100644
--- a/tools/perf/tests/shell/lib/setup_python.sh
+++ b/tools/perf/tests/shell/lib/setup_python.sh
@@ -14,3 +14,15 @@ then
echo Skipping test, python not detected please set environment variable PYTHON.
exit 2
fi
+
+# Set PYTHONPATH to find the in-tree built perf.so first, avoiding system-wide perf.so
+if [ "x$PYTHONPATH" = "x" ]; then
+ # If PERF_EXEC_PATH is set (pointing to the build directory), prioritize it
+ if [ -n "$PERF_EXEC_PATH" ] && [ -d "$PERF_EXEC_PATH/python" ]; then
+ export PYTHONPATH="$PERF_EXEC_PATH/python"
+ elif [ -d "$(dirname "$0")/../../python" ]; then
+ export PYTHONPATH="$(dirname "$0")/../../python"
+ elif [ -d "$(dirname "$0")/../python" ]; then
+ export PYTHONPATH="$(dirname "$0")/../python"
+ fi
+fi
--
2.54.0.1032.g2f8565e1d1-goog