Re: [PATCH v2 23/58] perf python: Add LiveSession helper

sashiko-bot
2026-04-23T04:31:12Z

Sashiko AI review found 4 potential issue(s):
- [High] Catching exceptions for unsupported events without consuming them from the ring buffer causes an infinite loop and CPU lockup.
- [High] Failure to completely drain the perf ring buffer upon waking from poll() causes massive overhead and dropped events.
- [Medium] Indiscriminate swallowing of all exceptions in the event processing loop masks user bugs.
- [Medium] File descriptors and mapped memory are leaked if an asynchronous signal (e.g., KeyboardInterrupt) is delivered between resource acquisition and the try block.
--

commit cf300f60bb414d0c0ad67cd1aba7027d55a4b157
Author: Ian Rogers <irogers@google.com>

perf python: Add LiveSession helper

This commit adds a LiveSession class in tools/perf/python/perf_live.py
to support live event collection using perf.evlist and perf.parse_events.
It avoids the need to fork a separate perf record process.

> diff --git a/tools/perf/python/perf_live.py b/tools/perf/python/perf_live.py
> new file mode 100755
> index 0000000000000..d1dcbab1150b9
> --- /dev/null
> +++ b/tools/perf/python/perf_live.py

[ ... ]

> @@ -0,0 +1,48 @@

[ ... ]

> + def run(self):
> + """Run the live session."""
> + self.evlist.open()
> + try:

Does this sequence leak file descriptors and mapped memory if an asynchronous
signal like KeyboardInterrupt is delivered between open() and the try block?

If the signal occurs immediately after open() successfully returns but
before execution enters the try block, the exception will bypass the
finally block.

Could this prevent explicit cleanup via self.evlist.close()?

> + 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:
> + try:
> + event = self.evlist.read_on_cpu(cpu)

Does this code leave events in the ring buffer?

The live event collection loop calls read_on_cpu() exactly once per
CPU and then immediately re-enters the poll() system call.

Lockless ring buffers usually require the consumer to fully drain all
pending events to maintain synchronization with the kernel producer. If a
burst of events occurs, reading them one by one will invoke a poll
context switch for every single event.

Could this massive processing overhead cause the userspace consumer to
fall drastically behind the kernel, causing the ring buffer to overflow
and result in dropped events?

> + if event and event.type == perf.RECORD_SAMPLE:
> + self.sample_callback(event)
> + except Exception:
> + pass

Could this exception block cause an infinite loop and CPU lockup if an
unsupported event type is encountered?

If read_on_cpu() encounters an unsupported event type (such as
PERF_RECORD_MMAP2), the underlying C extension might raise a TypeError
and return early. This early return would skip the perf_mmap__consume()
call, meaning the kernel's ring buffer data_tail pointer is not advanced.

The Python script would then catch the exception, ignore it, and loop
back to poll(). Since the buffer still contains the unconsumed event,
poll() returns instantly, leading to a tight loop.

Additionally, does this mask user errors in the callback?

If the caller-provided sample_callback contains logical errors or raises
standard library exceptions, this block will silently swallow them. Users
will see no output and no stack trace, making it difficult to debug
malfunctioning callbacks.

> + except KeyboardInterrupt:
> + pass
> + finally:
> + self.evlist.close()

--
Sashiko AI review · https://sashiko.dev/#/patchset/20260423035526.1537178-1-irogers@google.com?part=23