Re: [PATCH v2] perf test: Add test for events re-armed with PERF_EVENT_IOC_REFRESH

From: Ian Rogers

Date: Wed Oct 21 2026 - 13:39:03 EST


On Wed, Oct 21, 2026 at 7:17 AM Francesco Nigro <nigro.fra@xxxxxxxxx> wrote:
>
> Open a sampling event with sample_period 1, re-arm it with
> PERF_EVENT_IOC_REFRESH from its overflow signal handler, as
> self-profilers such as async-profiler do, and check that each of 1000
> events delivers a signal. One case uses a software event (page faults),
> the other the syscalls:sys_enter_getppid tracepoint. The signal is a
> real-time one, so overflows cannot be coalesced, and the page fault
> buffer is marked MADV_NOHUGEPAGE so that each touched page faults once.
>
> The tracepoint case fails since
> commit 18dbcbfabfff ("perf: Fix the POLL_HUP delivery breakage"):
> only the first overflow is delivered. The re-arming ioctl succeeds,
> but the event count stays at 1. It passes on the parent commit. The
> software case passes on both.
>
> The test was written with an AI coding assistant (Claude) from a
> standalone reproducer; I reviewed it and ran it in QEMU on current
> mainline, on that commit and on its parent.
>
> Link: https://github.com/async-profiler/async-profiler/issues/1813
> Signed-off-by: Francesco Nigro <nigro.fra@xxxxxxxxx>

Thanks Francesco, testing locally I see a failure on 7.1.6:
```
$ sudo /tmp/perf/perf test -v IOC_REFRESH
Not taking turns using the perf mlock budget: nothing can exhaust it
20: Event re-armed with PERF_EVENT_IOC_REFRESH :
20.1: Software event : Ok
---- start ----
test child forked, pid 187381
1000 events, count 1, 1 overflows (1 POLL_HUP), re-arm errno 0
---- end(-1) ----
20.2: Tracepoint event : FAILED!

=== Test Summary ===
Passed main tests : 0
Passed subtests : 1
Skipped tests : 0
Failed tests : 1
List of failed tests:
20.2: Tracepoint event
```
I believe this is currently expected until the kernel is fixed, which
is somewhat unfortunate. The code looks okay and the build isn't
regressed.

Reviewed-by: Ian Rogers <irogers@xxxxxxxxxx>

Thanks!
Ian



> ---
>
> Notes:
> v2:
> - Mark the page fault buffer MADV_NOHUGEPAGE: with transparent_hugepage=always
> the software case got 489 of 1000 overflows (reported by Sashiko).
> - Use a real-time signal instead of SIGIO, so overflows cannot be coalesced.
> - Print the event count, POLL_HUP signals and the re-arm ioctl's errno.
> - Skip only on EACCES/EPERM, check the setup calls, accept >= 1000
> overflows, close the event before restoring the signal handler.
>
> v1: https://lore.kernel.org/r/20261021131006.2101248-1-nigro.fra@xxxxxxxxx
>
> To run it on any x86_64 box with virtme-ng:
>
> vng --kconfig && make -j$(nproc)
> make -C tools/perf NO_LIBTRACEEVENT=1
> vng -- tools/perf/perf test -vv REFRESH
>
> Tracepoint case, 2 vCPUs:
>
> b320789d6883 (parent) Ok: count 1000, 1000 overflows (1000 POLL_HUP)
> 18dbcbfabfff FAILED: count 1, 1 overflows (1 POLL_HUP), re-arm errno 0
> a804c5742bff (master) FAILED: count 1, 1 overflows (1 POLL_HUP), re-arm errno 0
>
> The software case passes on all three, and on master with
> transparent_hugepage=always.
>
> Leo Yan's series "perf: Revert direct PMU stop for refresh limit"
> reverts 18dbcbfabfff for a different reason; I haven't tested it
> against this:
> https://lore.kernel.org/r/20260512-arm_cs_clean_perf_handle-v1-0-75ff373ecd22@xxxxxxx
>
> #regzbot introduced: 18dbcbfabfff
>
> tools/perf/tests/Build | 1 +
> tools/perf/tests/builtin-test.c | 1 +
> tools/perf/tests/event_refresh.c | 162 +++++++++++++++++++++++++++++++
> tools/perf/tests/tests.h | 1 +
> 4 files changed, 165 insertions(+)
> create mode 100644 tools/perf/tests/event_refresh.c
>
> diff --git a/tools/perf/tests/Build b/tools/perf/tests/Build
> index 66944a4f4..199e10c8c 100644
> --- a/tools/perf/tests/Build
> +++ b/tools/perf/tests/Build
> @@ -23,6 +23,7 @@ perf-test-y += hists_output.o
> perf-test-y += hists_cumulate.o
> perf-test-y += bp_signal.o
> perf-test-y += bp_signal_overflow.o
> +perf-test-y += event_refresh.o
> perf-test-y += bp_account.o
> perf-test-y += wp.o
> perf-test-y += task-exit.o
> diff --git a/tools/perf/tests/builtin-test.c b/tools/perf/tests/builtin-test.c
> index 4d0784b16..2b10f4d7f 100644
> --- a/tools/perf/tests/builtin-test.c
> +++ b/tools/perf/tests/builtin-test.c
> @@ -98,6 +98,7 @@ static struct test_suite *generic_tests[] = {
> &suite__hists_link,
> &suite__bp_signal,
> &suite__bp_signal_overflow,
> + &suite__event_refresh,
> &suite__bp_accounting,
> &suite__wp,
> &suite__task_exit,
> diff --git a/tools/perf/tests/event_refresh.c b/tools/perf/tests/event_refresh.c
> new file mode 100644
> index 000000000..726e5de4c
> --- /dev/null
> +++ b/tools/perf/tests/event_refresh.c
> @@ -0,0 +1,162 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * Re-arm a sampling event from its overflow signal handler with
> + * PERF_EVENT_IOC_REFRESH and check that every overflow is delivered.
> + */
> +#include <errno.h>
> +#include <fcntl.h>
> +#include <inttypes.h>
> +#include <signal.h>
> +#include <string.h>
> +#include <unistd.h>
> +#include <sys/ioctl.h>
> +#include <sys/mman.h>
> +#include <sys/syscall.h>
> +#include <linux/perf_event.h>
> +
> +#include "cloexec.h"
> +#include "debug.h"
> +#include "tests.h"
> +#include "tp_pmu.h"
> +#include <internal/lib.h> // page_size
> +#include "../perf-sys.h"
> +
> +#define NR_EVENTS 1000
> +/* A real-time signal is queued, so no overflow can be coalesced away. */
> +#define REFRESH_SIGNAL (SIGRTMIN + 1)
> +
> +static volatile sig_atomic_t overflows, hups, refresh_errno;
> +
> +static void sig_handler(int signum __maybe_unused, siginfo_t *info,
> + void *uc __maybe_unused)
> +{
> + int saved_errno = errno;
> +
> + overflows++;
> + if (info->si_code == POLL_HUP)
> + hups++;
> + if (ioctl(info->si_fd, PERF_EVENT_IOC_REFRESH, 1) < 0)
> + refresh_errno = errno;
> + errno = saved_errno;
> +}
> +
> +/* Count overflow signals for NR_EVENTS calls of trigger(), period 1. */
> +static int count_overflows(struct perf_event_attr *attr, void (*trigger)(int i))
> +{
> + struct sigaction sa, old_sa;
> + uint64_t count = 0;
> + int fd, i, err = TEST_FAIL;
> +
> + attr->size = sizeof(*attr);
> + attr->sample_period = 1;
> + attr->disabled = 1;
> +
> + fd = sys_perf_event_open(attr, 0, -1, -1, perf_event_open_cloexec_flag());
> + if (fd < 0) {
> + int open_errno = errno;
> +
> + pr_debug("failed opening event: %s\n", strerror(open_errno));
> + return open_errno == EACCES || open_errno == EPERM ? TEST_SKIP : TEST_FAIL;
> + }
> +
> + memset(&sa, 0, sizeof(sa));
> + sa.sa_sigaction = sig_handler;
> + sa.sa_flags = SA_SIGINFO;
> + if (sigaction(REFRESH_SIGNAL, &sa, &old_sa) < 0) {
> + pr_debug("failed setting up signal handler\n");
> + close(fd);
> + return TEST_FAIL;
> + }
> +
> + if (fcntl(fd, F_SETFL, O_RDWR | O_NONBLOCK | O_ASYNC) < 0 ||
> + fcntl(fd, F_SETSIG, REFRESH_SIGNAL) < 0 ||
> + fcntl(fd, F_SETOWN, getpid()) < 0) {
> + pr_debug("failed setting up signal delivery: %s\n", strerror(errno));
> + goto out;
> + }
> +
> + overflows = 0;
> + hups = 0;
> + refresh_errno = 0;
> + if (ioctl(fd, PERF_EVENT_IOC_REFRESH, 1) < 0) {
> + pr_debug("PERF_EVENT_IOC_REFRESH failed: %s\n", strerror(errno));
> + goto out;
> + }
> + for (i = 0; i < NR_EVENTS; i++)
> + trigger(i);
> + ioctl(fd, PERF_EVENT_IOC_DISABLE, 0);
> +
> + if (read(fd, &count, sizeof(count)) != sizeof(count))
> + pr_debug("failed to read the event count\n");
> + pr_debug("%d events, count %" PRIu64 ", %d overflows (%d POLL_HUP), re-arm errno %d\n",
> + NR_EVENTS, count, overflows, hups, refresh_errno);
> + /* Unrelated page faults can only add overflows, never hide one. */
> + if (overflows >= NR_EVENTS)
> + err = TEST_OK;
> +out:
> + /* Close first: a late signal must still find our handler. */
> + close(fd);
> + sigaction(REFRESH_SIGNAL, &old_sa, NULL);
> + return err;
> +}
> +
> +static char *pages;
> +
> +static void touch_page(int i)
> +{
> + pages[(long)i * page_size] = 1;
> +}
> +
> +static int test__event_refresh_sw(struct test_suite *test __maybe_unused,
> + int subtest __maybe_unused)
> +{
> + struct perf_event_attr attr = {
> + .type = PERF_TYPE_SOFTWARE,
> + .config = PERF_COUNT_SW_PAGE_FAULTS,
> + };
> + int err;
> +
> + pages = mmap(NULL, (long)NR_EVENTS * page_size, PROT_READ | PROT_WRITE,
> + MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
> + if (pages == MAP_FAILED)
> + return TEST_FAIL;
> + /* One fault per touched page: a huge page would cover many of them. */
> + madvise(pages, (long)NR_EVENTS * page_size, MADV_NOHUGEPAGE);
> +
> + err = count_overflows(&attr, touch_page);
> + munmap(pages, (long)NR_EVENTS * page_size);
> + return err;
> +}
> +
> +static void call_getppid(int i __maybe_unused)
> +{
> + syscall(SYS_getppid);
> +}
> +
> +static int test__event_refresh_tp(struct test_suite *test __maybe_unused,
> + int subtest __maybe_unused)
> +{
> + struct perf_event_attr attr = {
> + .type = PERF_TYPE_TRACEPOINT,
> + };
> + int id = tp_pmu__id("syscalls", "sys_enter_getppid");
> +
> + if (id < 0) {
> + pr_debug("no syscalls:sys_enter_getppid tracepoint\n");
> + return TEST_SKIP;
> + }
> + attr.config = id;
> +
> + return count_overflows(&attr, call_getppid);
> +}
> +
> +static struct test_case tests__event_refresh[] = {
> + TEST_CASE_REASON("Software event", event_refresh_sw, "permissions"),
> + TEST_CASE_REASON("Tracepoint event", event_refresh_tp, "permissions"),
> + { .name = NULL, }
> +};
> +
> +struct test_suite suite__event_refresh = {
> + .desc = "Event re-armed with PERF_EVENT_IOC_REFRESH",
> + .test_cases = tests__event_refresh,
> +};
> diff --git a/tools/perf/tests/tests.h b/tools/perf/tests/tests.h
> index cee9e6b62..ecc25b5a3 100644
> --- a/tools/perf/tests/tests.h
> +++ b/tools/perf/tests/tests.h
> @@ -124,6 +124,7 @@ DECLARE_SUITE(parse_events);
> DECLARE_SUITE(hists_link);
> DECLARE_SUITE(bp_signal);
> DECLARE_SUITE(bp_signal_overflow);
> +DECLARE_SUITE(event_refresh);
> DECLARE_SUITE(bp_accounting);
> DECLARE_SUITE(wp);
> DECLARE_SUITE(task_exit);
> --
> 2.55.0
>