Re: [PATCH perf-tools-next v9 1/6] perf ftrace: Optimise __cmd_ftrace() stack frame with strbuf streaming

From: Aaron Tomlin

Date: Thu Oct 22 2026 - 10:31:44 EST


On Wed, Oct 21, 2026 at 04:56:04PM -0700, Namhyung Kim wrote:
> On Tue, Oct 20, 2026 at 10:33:32PM -0400, Aaron Tomlin wrote:
> > In __cmd_ftrace(), incoming trace data from trace_pipe was historically
> > buffered in a fixed 4096-byte array allocated directly on the stack
> > frame. Furthermore, reads were written directly to stdout via fwrite()
> > in arbitrary chunk sizes, rather than as discrete, well-delimited
> > trace lines.
> >
> > Refactor __cmd_ftrace() to employ dynamic heap allocation for the read
> > buffer alongside a dynamic struct strbuf to accumulate streamed
> > characters into whole lines. Pre-allocating the strbuf ensures that
> > line processing incurs zero heap reallocations during steady-state
> > polling, while the stack frame of __cmd_ftrace() is reduced by over 96%
> > (down to ~160 bytes). Robust error and EOF detection on stdout and the
> > trace stream ensures polling terminates cleanly upon pipeline or pager
> > exit, while retrying on non-fatal EAGAIN and EINTR conditions prevents
> > premature termination on non-blocking reads or signal interruptions.
>
> I think it's better to split the error handling into its own commit
> maybe with a Fixes tag. But I'm curious if it's possible to get EAGAIN
> on read() after poll().
>
> Thanks,
> Namhyung

Hi Namhyung

I agree with splitting the error handling and signal resilience into a
dedicated commit ahead of the stack frame and strbuf streaming change.
The error handling and non-blocking retry logic is an independent bugfix
applicable to stable kernels.

Regarding your question about whether read() can return EAGAIN after
poll(), yes, it can. With trace_pipe configured for non-blocking I/O in
__cmd_ftrace():

fcntl(trace_fd, F_SETFL, O_NONBLOCK);

There are indeed several [not limited to the following] scenarios in the
kernel where read() returns -EAGAIN after poll() reports POLLIN:

1. The kernel implementation of non-blocking read on trace_pipe:

When read() is invoked on trace_pipe, tracing_read_pipe() calls
tracing_wait_pipe() in kernel/trace/trace.c:

static int tracing_wait_pipe(struct file *filp)
{
struct trace_iterator *iter = filp->private_data;

while (trace_empty(iter)) {
if ((filp->f_flags & O_NONBLOCK)) {
return -EAGAIN;
}
...
}
return 1;
}

Whenever trace_empty(iter) evaluates to true on a non-blocking
descriptor, the kernel explicitly returns -EAGAIN.

2. TRACE_ITER(BLOCK) trace option:

See trace_poll() in kernel/trace/trace.c:

static __poll_t
trace_poll(struct trace_iterator *iter, struct file *filp, poll_table *poll_table)
{
struct trace_array *tr = iter->tr;

if (trace_buffer_iter(iter, iter->cpu_file))
return EPOLLIN | EPOLLRDNORM;

if (tr->trace_flags & TRACE_ITER(BLOCK))
/* Always select as readable when in blocking mode */
return EPOLLIN | EPOLLRDNORM;
else
return ring_buffer_poll_wait(iter->array_buffer->buffer, iter->cpu_file,
filp, poll_table, iter->tr->buffer_percent);
}

If block mode is set on the trace instance (e.g. via
trace_options), trace_poll() unconditionally returns EPOLLIN |
EPOLLRDNORM regardless of whether the ring buffer contains any
data. The subsequent non-blocking read() immediately hits the
trace_empty() check in tracing_wait_pipe() and returns -EAGAIN.

I will split this into two commits for v10.

Kind regards,
--
Aaron Tomlin