* [PATCH] perf: add timerfd timeout exit-code handling
@ 2026-04-26 18:28 Malcom Gilbert
2026-04-27 5:48 ` Namhyung Kim
0 siblings, 1 reply; 3+ messages in thread
From: Malcom Gilbert @ 2026-04-26 18:28 UTC (permalink / raw)
To: linux-perf-users, linux-kernel
Assisted-by: OpenAI:gpt-5.3-codex-spark
Signed-off-by: Malcom Gilbert <malcomgilbert@gmail.com>
---
tools/perf/Documentation/perf-record.txt | 4 +
tools/perf/builtin-record.c | 94 ++++++++++++++++++++++++
tools/perf/util/record.h | 1 +
3 files changed, 99 insertions(+)
diff --git a/tools/perf/Documentation/perf-record.txt
b/tools/perf/Documentation/perf-record.txt
index 178f483140ed..ea48f96fe11a 100644
--- a/tools/perf/Documentation/perf-record.txt
+++ b/tools/perf/Documentation/perf-record.txt
@@ -554,6 +554,10 @@ When processing pre-existing threads
/proc/XXX/mmap, it may take a long time,
because the file may be huge. A time out is needed in such cases.
This option sets the time out limit. The default value is 500 ms.
+--timeout=<seconds>::
+Stop recording after the given number of seconds.
+If `perf record` stops from timeout, it exits with status 124.
+
--switch-events::
Record context switch events i.e. events of type PERF_RECORD_SWITCH or
PERF_RECORD_SWITCH_CPU_WIDE. In some cases (e.g. Intel PT, CoreSight
or Arm SPE)
diff --git a/tools/perf/builtin-record.c b/tools/perf/builtin-record.c
index 4a5eba498c02..e666d1d475a4 100644
--- a/tools/perf/builtin-record.c
+++ b/tools/perf/builtin-record.c
@@ -71,6 +71,9 @@
#ifdef HAVE_EVENTFD_SUPPORT
#include <sys/eventfd.h>
#endif
+#ifdef HAVE_TIMERFD_SUPPORT
+#include <sys/timerfd.h>
+#endif
#include <sys/mman.h>
#include <sys/wait.h>
#include <sys/types.h>
@@ -154,6 +157,9 @@ struct pollfd_index_map {
struct record {
struct perf_tool tool;
struct record_opts opts;
+ int timeout_fd;
+ int timeout_pos;
+ bool timed_out;
u64 bytes_written;
u64 thread_bytes_written;
struct perf_data data;
@@ -721,6 +727,77 @@ static void sigsegv_handler(int sig)
sighandler_dump_stack(sig);
}
+static int record__setup_timeout(struct record *rec)
+{
+#ifndef HAVE_TIMERFD_SUPPORT
+ pr_err("perf record --timeout is not supported on this platform\n");
+ return -1;
+#else
+ struct itimerspec new_value = {};
+ int fd;
+ int pos;
+
+ if (!rec->opts.timeout)
+ return 0;
+
+ fd = timerfd_create(CLOCK_MONOTONIC, TFD_NONBLOCK);
+ if (fd < 0) {
+ pr_err("Failed to create timeout timerfd, error: %m\n");
+ return -1;
+ }
+
+ new_value.it_value.tv_sec = rec->opts.timeout;
+
+ if (timerfd_settime(fd, 0, &new_value, NULL) != 0) {
+ pr_err("Failed to start timeout timer, error: %m\n");
+ close(fd);
+ return -1;
+ }
+
+ /* Recv all timer expirations in one-shot mode. */
+ pos = fdarray__add(&thread->pollfd, fd, POLLIN, fdarray_flag__nonfilterable);
+ if (pos < 0) {
+ pr_err("Failed to add timeout timerfd to poll list\n");
+ close(fd);
+ return pos;
+ }
+
+ rec->timeout_fd = fd;
+ rec->timeout_pos = pos;
+
+ return 0;
+#endif
+}
+
+static int record__process_timeout_event(struct record *rec)
+{
+ uint64_t expirations;
+ int pos = rec->timeout_pos;
+ struct pollfd *timeout_pollfd;
+
+ if (!thread || !thread->pollfd.entries || pos < 0)
+ return 0;
+ if (pos >= thread->pollfd.nr)
+ return 0;
+
+ timeout_pollfd = &thread->pollfd.entries[pos];
+ if (!(timeout_pollfd->revents & POLLIN))
+ return 0;
+
+ if (read(rec->timeout_fd, &expirations, sizeof(expirations)) < 0) {
+ if (errno == EAGAIN)
+ return 0;
+ pr_err("failed to read timeout timerfd, error: %m\n");
+ return -1;
+ }
+ (void)expirations;
+
+ rec->timed_out = true;
+ done = 1;
+
+ return 0;
+}
+
static void record__sig_exit(void)
{
if (signr == -1)
@@ -2438,6 +2515,9 @@ static int __cmd_record(struct record *rec, int
argc, const char **argv)
float ratio = 0;
enum evlist_ctl_cmd cmd = EVLIST_CTL_CMD_UNSUPPORTED;
struct perf_env *env;
+ rec->timeout_fd = -1;
+ rec->timeout_pos = -1;
+ rec->timed_out = false;
atexit(record__sig_exit);
signal(SIGCHLD, sig_handler);
@@ -2647,6 +2727,10 @@ static int __cmd_record(struct record *rec, int
argc, const char **argv)
if (rec->off_cpu)
evlist__enable_evsel(rec->evlist, (char *)OFFCPU_EVENT);
+ err = record__setup_timeout(rec);
+ if (err)
+ goto out_child;
+
/*
* Let the child rip
*/
@@ -2814,6 +2898,10 @@ static int __cmd_record(struct record *rec, int
argc, const char **argv)
err = record__update_evlist_pollfd_from_thread(rec, rec->evlist, thread);
if (err)
goto out_child;
+
+ err = record__process_timeout_event(rec);
+ if (err)
+ goto out_child;
}
if (evlist__ctlfd_process(rec->evlist, &cmd) > 0) {
@@ -2919,6 +3007,10 @@ static int __cmd_record(struct record *rec, int
argc, const char **argv)
if (rec->off_cpu)
rec->bytes_written += off_cpu_write(rec->session);
+ if (rec->timed_out) {
+ signr = -1;
+ status = 124;
+ }
record__read_lost_samples(rec);
/* this will be recalculated during process_buildids() */
@@ -3639,6 +3731,8 @@ static struct option __record_options[] = {
"\t\t\t Optionally send control command completion ('ack\\n')
to ack-fd descriptor.\n"
"\t\t\t Alternatively, ctl-fifo / ack-fifo will be opened and
used as ctl-fd / ack-fd.",
parse_control_option),
+ OPT_UINTEGER(0, "timeout", &record.opts.timeout,
+ "Stop recording after the given number of seconds"),
OPT_CALLBACK(0, "synth", &record.opts, "no|all|task|mmap|cgroup",
"Fine-tune event synthesis: default=all", parse_record_synth_option),
OPT_STRING_OPTARG_SET(0, "debuginfod", &record.debuginfod.urls,
diff --git a/tools/perf/util/record.h b/tools/perf/util/record.h
index 93627c9a7338..576513ed3b60 100644
--- a/tools/perf/util/record.h
+++ b/tools/perf/util/record.h
@@ -80,6 +80,7 @@ struct record_opts {
int ctl_fd_ack;
bool ctl_fd_close;
int synth;
+ unsigned int timeout;
int threads_spec;
const char *threads_user_spec;
u64 off_cpu_thresh_ns;
--
2.43.0
Malcom
^ permalink raw reply related [flat|nested] 3+ messages in thread* Re: [PATCH] perf: add timerfd timeout exit-code handling
2026-04-26 18:28 [PATCH] perf: add timerfd timeout exit-code handling Malcom Gilbert
@ 2026-04-27 5:48 ` Namhyung Kim
0 siblings, 0 replies; 3+ messages in thread
From: Namhyung Kim @ 2026-04-27 5:48 UTC (permalink / raw)
To: Malcom Gilbert; +Cc: linux-perf-users, linux-kernel
Hello,
On Sun, Apr 26, 2026 at 01:28:18PM -0500, Malcom Gilbert wrote:
> Assisted-by: OpenAI:gpt-5.3-codex-spark
> Signed-off-by: Malcom Gilbert <malcomgilbert@gmail.com>
Looks like this patch or your system has a problem with indentation.
Please fix.
Thanks,
Namhyung
> ---
> tools/perf/Documentation/perf-record.txt | 4 +
> tools/perf/builtin-record.c | 94 ++++++++++++++++++++++++
> tools/perf/util/record.h | 1 +
> 3 files changed, 99 insertions(+)
>
> diff --git a/tools/perf/Documentation/perf-record.txt
> b/tools/perf/Documentation/perf-record.txt
> index 178f483140ed..ea48f96fe11a 100644
> --- a/tools/perf/Documentation/perf-record.txt
> +++ b/tools/perf/Documentation/perf-record.txt
> @@ -554,6 +554,10 @@ When processing pre-existing threads
> /proc/XXX/mmap, it may take a long time,
> because the file may be huge. A time out is needed in such cases.
> This option sets the time out limit. The default value is 500 ms.
>
> +--timeout=<seconds>::
> +Stop recording after the given number of seconds.
> +If `perf record` stops from timeout, it exits with status 124.
> +
> --switch-events::
> Record context switch events i.e. events of type PERF_RECORD_SWITCH or
> PERF_RECORD_SWITCH_CPU_WIDE. In some cases (e.g. Intel PT, CoreSight
> or Arm SPE)
> diff --git a/tools/perf/builtin-record.c b/tools/perf/builtin-record.c
> index 4a5eba498c02..e666d1d475a4 100644
> --- a/tools/perf/builtin-record.c
> +++ b/tools/perf/builtin-record.c
> @@ -71,6 +71,9 @@
> #ifdef HAVE_EVENTFD_SUPPORT
> #include <sys/eventfd.h>
> #endif
> +#ifdef HAVE_TIMERFD_SUPPORT
> +#include <sys/timerfd.h>
> +#endif
> #include <sys/mman.h>
> #include <sys/wait.h>
> #include <sys/types.h>
> @@ -154,6 +157,9 @@ struct pollfd_index_map {
> struct record {
> struct perf_tool tool;
> struct record_opts opts;
> + int timeout_fd;
> + int timeout_pos;
> + bool timed_out;
> u64 bytes_written;
> u64 thread_bytes_written;
> struct perf_data data;
> @@ -721,6 +727,77 @@ static void sigsegv_handler(int sig)
> sighandler_dump_stack(sig);
> }
>
> +static int record__setup_timeout(struct record *rec)
> +{
> +#ifndef HAVE_TIMERFD_SUPPORT
> + pr_err("perf record --timeout is not supported on this platform\n");
> + return -1;
> +#else
> + struct itimerspec new_value = {};
> + int fd;
> + int pos;
> +
> + if (!rec->opts.timeout)
> + return 0;
> +
> + fd = timerfd_create(CLOCK_MONOTONIC, TFD_NONBLOCK);
> + if (fd < 0) {
> + pr_err("Failed to create timeout timerfd, error: %m\n");
> + return -1;
> + }
> +
> + new_value.it_value.tv_sec = rec->opts.timeout;
> +
> + if (timerfd_settime(fd, 0, &new_value, NULL) != 0) {
> + pr_err("Failed to start timeout timer, error: %m\n");
> + close(fd);
> + return -1;
> + }
> +
> + /* Recv all timer expirations in one-shot mode. */
> + pos = fdarray__add(&thread->pollfd, fd, POLLIN, fdarray_flag__nonfilterable);
> + if (pos < 0) {
> + pr_err("Failed to add timeout timerfd to poll list\n");
> + close(fd);
> + return pos;
> + }
> +
> + rec->timeout_fd = fd;
> + rec->timeout_pos = pos;
> +
> + return 0;
> +#endif
> +}
> +
> +static int record__process_timeout_event(struct record *rec)
> +{
> + uint64_t expirations;
> + int pos = rec->timeout_pos;
> + struct pollfd *timeout_pollfd;
> +
> + if (!thread || !thread->pollfd.entries || pos < 0)
> + return 0;
> + if (pos >= thread->pollfd.nr)
> + return 0;
> +
> + timeout_pollfd = &thread->pollfd.entries[pos];
> + if (!(timeout_pollfd->revents & POLLIN))
> + return 0;
> +
> + if (read(rec->timeout_fd, &expirations, sizeof(expirations)) < 0) {
> + if (errno == EAGAIN)
> + return 0;
> + pr_err("failed to read timeout timerfd, error: %m\n");
> + return -1;
> + }
> + (void)expirations;
> +
> + rec->timed_out = true;
> + done = 1;
> +
> + return 0;
> +}
> +
> static void record__sig_exit(void)
> {
> if (signr == -1)
> @@ -2438,6 +2515,9 @@ static int __cmd_record(struct record *rec, int
> argc, const char **argv)
> float ratio = 0;
> enum evlist_ctl_cmd cmd = EVLIST_CTL_CMD_UNSUPPORTED;
> struct perf_env *env;
> + rec->timeout_fd = -1;
> + rec->timeout_pos = -1;
> + rec->timed_out = false;
>
> atexit(record__sig_exit);
> signal(SIGCHLD, sig_handler);
> @@ -2647,6 +2727,10 @@ static int __cmd_record(struct record *rec, int
> argc, const char **argv)
> if (rec->off_cpu)
> evlist__enable_evsel(rec->evlist, (char *)OFFCPU_EVENT);
>
> + err = record__setup_timeout(rec);
> + if (err)
> + goto out_child;
> +
> /*
> * Let the child rip
> */
> @@ -2814,6 +2898,10 @@ static int __cmd_record(struct record *rec, int
> argc, const char **argv)
> err = record__update_evlist_pollfd_from_thread(rec, rec->evlist, thread);
> if (err)
> goto out_child;
> +
> + err = record__process_timeout_event(rec);
> + if (err)
> + goto out_child;
> }
>
> if (evlist__ctlfd_process(rec->evlist, &cmd) > 0) {
> @@ -2919,6 +3007,10 @@ static int __cmd_record(struct record *rec, int
> argc, const char **argv)
>
> if (rec->off_cpu)
> rec->bytes_written += off_cpu_write(rec->session);
> + if (rec->timed_out) {
> + signr = -1;
> + status = 124;
> + }
>
> record__read_lost_samples(rec);
> /* this will be recalculated during process_buildids() */
> @@ -3639,6 +3731,8 @@ static struct option __record_options[] = {
> "\t\t\t Optionally send control command completion ('ack\\n')
> to ack-fd descriptor.\n"
> "\t\t\t Alternatively, ctl-fifo / ack-fifo will be opened and
> used as ctl-fd / ack-fd.",
> parse_control_option),
> + OPT_UINTEGER(0, "timeout", &record.opts.timeout,
> + "Stop recording after the given number of seconds"),
> OPT_CALLBACK(0, "synth", &record.opts, "no|all|task|mmap|cgroup",
> "Fine-tune event synthesis: default=all", parse_record_synth_option),
> OPT_STRING_OPTARG_SET(0, "debuginfod", &record.debuginfod.urls,
> diff --git a/tools/perf/util/record.h b/tools/perf/util/record.h
> index 93627c9a7338..576513ed3b60 100644
> --- a/tools/perf/util/record.h
> +++ b/tools/perf/util/record.h
> @@ -80,6 +80,7 @@ struct record_opts {
> int ctl_fd_ack;
> bool ctl_fd_close;
> int synth;
> + unsigned int timeout;
> int threads_spec;
> const char *threads_user_spec;
> u64 off_cpu_thresh_ns;
> --
> 2.43.0
>
>
> Malcom
^ permalink raw reply [flat|nested] 3+ messages in thread
[parent not found: <CAJ_TQUz0kNV-pUzrcNM7ou3xjPdOU73z+qBAWmP7GUW2BRvpHA@mail.gmail.com>]
* Re: [PATCH] perf: add timerfd timeout exit-code handling
[not found] <CAJ_TQUz0kNV-pUzrcNM7ou3xjPdOU73z+qBAWmP7GUW2BRvpHA@mail.gmail.com>
@ 2026-04-26 18:58 ` Ian Rogers
0 siblings, 0 replies; 3+ messages in thread
From: Ian Rogers @ 2026-04-26 18:58 UTC (permalink / raw)
To: Malcom Gilbert
Cc: peterz, mingo, acme, namhyung, mark.rutland, alexander.shishkin,
jolsa, adrian.hunter, james.clark, linux-perf-users, linux-kernel
On Sun, Apr 26, 2026 at 11:21 AM Malcom Gilbert <malcomgilbert@gmail.com> wrote:
>
> Assisted-by: OpenAI:gpt-5.3-codex-spark
> Signed-off-by: Malcom Gilbert <malcomgilbert@gmail.com>
Thanks Malcolm, the commit message is empty, do you want to describe
what you are trying to achieve here? When recording system-wide, it is
common to use "perf record -a sleep 10" which limits perf to 10
seconds of data.
Thanks,
Ian
> ---
> tools/perf/Documentation/perf-record.txt | 4 +
> tools/perf/builtin-record.c | 94 ++++++++++++++++++++++++
> tools/perf/util/record.h | 1 +
> 3 files changed, 99 insertions(+)
>
> diff --git a/tools/perf/Documentation/perf-record.txt b/tools/perf/Documentation/perf-record.txt
> index 178f483140ed..ea48f96fe11a 100644
> --- a/tools/perf/Documentation/perf-record.txt
> +++ b/tools/perf/Documentation/perf-record.txt
> @@ -554,6 +554,10 @@ When processing pre-existing threads /proc/XXX/mmap, it may take a long time,
> because the file may be huge. A time out is needed in such cases.
> This option sets the time out limit. The default value is 500 ms.
>
> +--timeout=<seconds>::
> +Stop recording after the given number of seconds.
> +If `perf record` stops from timeout, it exits with status 124.
> +
> --switch-events::
> Record context switch events i.e. events of type PERF_RECORD_SWITCH or
> PERF_RECORD_SWITCH_CPU_WIDE. In some cases (e.g. Intel PT, CoreSight or Arm SPE)
> diff --git a/tools/perf/builtin-record.c b/tools/perf/builtin-record.c
> index 4a5eba498c02..e666d1d475a4 100644
> --- a/tools/perf/builtin-record.c
> +++ b/tools/perf/builtin-record.c
> @@ -71,6 +71,9 @@
> #ifdef HAVE_EVENTFD_SUPPORT
> #include <sys/eventfd.h>
> #endif
> +#ifdef HAVE_TIMERFD_SUPPORT
> +#include <sys/timerfd.h>
> +#endif
> #include <sys/mman.h>
> #include <sys/wait.h>
> #include <sys/types.h>
> @@ -154,6 +157,9 @@ struct pollfd_index_map {
> struct record {
> struct perf_tool tool;
> struct record_opts opts;
> + int timeout_fd;
> + int timeout_pos;
> + bool timed_out;
> u64 bytes_written;
> u64 thread_bytes_written;
> struct perf_data data;
> @@ -721,6 +727,77 @@ static void sigsegv_handler(int sig)
> sighandler_dump_stack(sig);
> }
>
> +static int record__setup_timeout(struct record *rec)
> +{
> +#ifndef HAVE_TIMERFD_SUPPORT
> + pr_err("perf record --timeout is not supported on this platform\n");
> + return -1;
> +#else
> + struct itimerspec new_value = {};
> + int fd;
> + int pos;
> +
> + if (!rec->opts.timeout)
> + return 0;
> +
> + fd = timerfd_create(CLOCK_MONOTONIC, TFD_NONBLOCK);
> + if (fd < 0) {
> + pr_err("Failed to create timeout timerfd, error: %m\n");
> + return -1;
> + }
> +
> + new_value.it_value.tv_sec = rec->opts.timeout;
> +
> + if (timerfd_settime(fd, 0, &new_value, NULL) != 0) {
> + pr_err("Failed to start timeout timer, error: %m\n");
> + close(fd);
> + return -1;
> + }
> +
> + /* Recv all timer expirations in one-shot mode. */
> + pos = fdarray__add(&thread->pollfd, fd, POLLIN, fdarray_flag__nonfilterable);
> + if (pos < 0) {
> + pr_err("Failed to add timeout timerfd to poll list\n");
> + close(fd);
> + return pos;
> + }
> +
> + rec->timeout_fd = fd;
> + rec->timeout_pos = pos;
> +
> + return 0;
> +#endif
> +}
> +
> +static int record__process_timeout_event(struct record *rec)
> +{
> + uint64_t expirations;
> + int pos = rec->timeout_pos;
> + struct pollfd *timeout_pollfd;
> +
> + if (!thread || !thread->pollfd.entries || pos < 0)
> + return 0;
> + if (pos >= thread->pollfd.nr)
> + return 0;
> +
> + timeout_pollfd = &thread->pollfd.entries[pos];
> + if (!(timeout_pollfd->revents & POLLIN))
> + return 0;
> +
> + if (read(rec->timeout_fd, &expirations, sizeof(expirations)) < 0) {
> + if (errno == EAGAIN)
> + return 0;
> + pr_err("failed to read timeout timerfd, error: %m\n");
> + return -1;
> + }
> + (void)expirations;
> +
> + rec->timed_out = true;
> + done = 1;
> +
> + return 0;
> +}
> +
> static void record__sig_exit(void)
> {
> if (signr == -1)
> @@ -2438,6 +2515,9 @@ static int __cmd_record(struct record *rec, int argc, const char **argv)
> float ratio = 0;
> enum evlist_ctl_cmd cmd = EVLIST_CTL_CMD_UNSUPPORTED;
> struct perf_env *env;
> + rec->timeout_fd = -1;
> + rec->timeout_pos = -1;
> + rec->timed_out = false;
>
> atexit(record__sig_exit);
> signal(SIGCHLD, sig_handler);
> @@ -2647,6 +2727,10 @@ static int __cmd_record(struct record *rec, int argc, const char **argv)
> if (rec->off_cpu)
> evlist__enable_evsel(rec->evlist, (char *)OFFCPU_EVENT);
>
> + err = record__setup_timeout(rec);
> + if (err)
> + goto out_child;
> +
> /*
> * Let the child rip
> */
> @@ -2814,6 +2898,10 @@ static int __cmd_record(struct record *rec, int argc, const char **argv)
> err = record__update_evlist_pollfd_from_thread(rec, rec->evlist, thread);
> if (err)
> goto out_child;
> +
> + err = record__process_timeout_event(rec);
> + if (err)
> + goto out_child;
> }
>
> if (evlist__ctlfd_process(rec->evlist, &cmd) > 0) {
> @@ -2919,6 +3007,10 @@ static int __cmd_record(struct record *rec, int argc, const char **argv)
>
> if (rec->off_cpu)
> rec->bytes_written += off_cpu_write(rec->session);
> + if (rec->timed_out) {
> + signr = -1;
> + status = 124;
> + }
>
> record__read_lost_samples(rec);
> /* this will be recalculated during process_buildids() */
> @@ -3639,6 +3731,8 @@ static struct option __record_options[] = {
> "\t\t\t Optionally send control command completion ('ack\\n') to ack-fd descriptor.\n"
> "\t\t\t Alternatively, ctl-fifo / ack-fifo will be opened and used as ctl-fd / ack-fd.",
> parse_control_option),
> + OPT_UINTEGER(0, "timeout", &record.opts.timeout,
> + "Stop recording after the given number of seconds"),
> OPT_CALLBACK(0, "synth", &record.opts, "no|all|task|mmap|cgroup",
> "Fine-tune event synthesis: default=all", parse_record_synth_option),
> OPT_STRING_OPTARG_SET(0, "debuginfod", &record.debuginfod.urls,
> diff --git a/tools/perf/util/record.h b/tools/perf/util/record.h
> index 93627c9a7338..576513ed3b60 100644
> --- a/tools/perf/util/record.h
> +++ b/tools/perf/util/record.h
> @@ -80,6 +80,7 @@ struct record_opts {
> int ctl_fd_ack;
> bool ctl_fd_close;
> int synth;
> + unsigned int timeout;
> int threads_spec;
> const char *threads_user_spec;
> u64 off_cpu_thresh_ns;
> --
> 2.43.0
>
>
>
>
> Malcom
^ permalink raw reply [flat|nested] 3+ messages in thread
end of thread, other threads:[~2026-04-27 5:48 UTC | newest]
Thread overview: 3+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-04-26 18:28 [PATCH] perf: add timerfd timeout exit-code handling Malcom Gilbert
2026-04-27 5:48 ` Namhyung Kim
[not found] <CAJ_TQUz0kNV-pUzrcNM7ou3xjPdOU73z+qBAWmP7GUW2BRvpHA@mail.gmail.com>
2026-04-26 18:58 ` Ian Rogers
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox