perf reports POLL_IN for overflows while an event still has refreshes
left, and POLL_HUP when the refresh count reaches zero and the event is
disabled.

Add a test to verify PERF_EVENT_IOC_REFRESH with a task-clock software
event. Use a real-time signal so notifications are queued instead of
being coalesced.

Each iteration sets the refresh count to 5 and waits for a POLL_HUP
notification after 5 task-clock overflows at a 1 microsecond interval.
The sequence is repeated 100 times, verifying that exactly one POLL_HUP
notification is delivered per iteration. The test is bounded by
the default selftest timeout (30 seconds).

    POLL_IN             POLL_HUP
    e1   e2   e3   e4   e5        |
    |    |    |    |    |         |
  --+----+----+----+----+---------+----------------------
    <-----      Iter 1      -----> <--- Iter2 ... --->

POLL_IN is allowed to be lower than the theoretical maximum because
overflow delivery is deferred through irq_work. If timers expire too
close to each other, pending irq_work may not run before the next timer
expires, so some POLL_IN notifications can be missed.

Signed-off-by: Leo Yan <[email protected]>
---
 tools/testing/selftests/perf_events/.gitignore     |   1 +
 tools/testing/selftests/perf_events/Makefile       |   3 +-
 .../testing/selftests/perf_events/refresh_signal.c | 120 +++++++++++++++++++++
 3 files changed, 123 insertions(+), 1 deletion(-)

diff --git a/tools/testing/selftests/perf_events/.gitignore 
b/tools/testing/selftests/perf_events/.gitignore
index 
4931b3b6bbd3971145b0e09b1fcdaf6cae9eb10e..e1bcf3ab8ab24bdb742a9cd593810c1c566d4b20
 100644
--- a/tools/testing/selftests/perf_events/.gitignore
+++ b/tools/testing/selftests/perf_events/.gitignore
@@ -3,3 +3,4 @@ sigtrap_threads
 remove_on_exec
 watermark_signal
 mmap
+refresh_signal
diff --git a/tools/testing/selftests/perf_events/Makefile 
b/tools/testing/selftests/perf_events/Makefile
index 
2e5d85770dfeadd909196dbf980fd334b9580477..e0591b1045f959476a0c5bb57e471a01006b66ee
 100644
--- a/tools/testing/selftests/perf_events/Makefile
+++ b/tools/testing/selftests/perf_events/Makefile
@@ -2,5 +2,6 @@
 CFLAGS += -Wl,-no-as-needed -Wall $(KHDR_INCLUDES)
 LDFLAGS += -lpthread
 
-TEST_GEN_PROGS := sigtrap_threads remove_on_exec watermark_signal mmap
+TEST_GEN_PROGS := sigtrap_threads remove_on_exec watermark_signal mmap \
+                 refresh_signal
 include ../lib.mk
diff --git a/tools/testing/selftests/perf_events/refresh_signal.c 
b/tools/testing/selftests/perf_events/refresh_signal.c
new file mode 100644
index 
0000000000000000000000000000000000000000..9bf05bcd888782fe8130579cc03602a8f39dd21c
--- /dev/null
+++ b/tools/testing/selftests/perf_events/refresh_signal.c
@@ -0,0 +1,120 @@
+// SPDX-License-Identifier: GPL-2.0
+#define _GNU_SOURCE
+
+#include <fcntl.h>
+#include <linux/perf_event.h>
+#include <poll.h>
+#include <signal.h>
+#include <sys/ioctl.h>
+#include <time.h>
+
+#include "../kselftest_harness.h"
+
+#define RT_SIG                 (SIGRTMIN + 1)
+
+#define EVENT_LIMIT            5
+#define ITERATIONS             100
+
+static struct {
+       volatile sig_atomic_t in;
+       volatile sig_atomic_t hup;
+} count;
+
+static void sigio_handler(int signo __maybe_unused, siginfo_t *info,
+                         void *ucontext __maybe_unused)
+{
+       switch (info->si_code) {
+       case POLL_IN:
+               count.in++;
+               break;
+       case POLL_HUP:
+               count.hup++;
+               break;
+       }
+}
+
+FIXTURE(refresh_signal)
+{
+       struct sigaction old_sa;
+       int fd;
+};
+
+FIXTURE_SETUP(refresh_signal)
+{
+       struct sigaction sa = { 0 };
+       struct perf_event_attr attr = { 0 };
+
+       sa.sa_sigaction = sigio_handler;
+       sa.sa_flags = SA_SIGINFO;
+       sigemptyset(&sa.sa_mask);
+
+       /* Use a real-time signal so notifications are reliably queued */
+       EXPECT_EQ(sigaction(RT_SIG, &sa, &self->old_sa), 0);
+
+       attr.size = sizeof(attr);
+       attr.type = PERF_TYPE_SOFTWARE;
+       attr.config = PERF_COUNT_SW_TASK_CLOCK;
+       attr.disabled = 1;
+       attr.sample_period = 1000;      /* 1 us */
+       attr.exclude_kernel = 1;
+       attr.exclude_hv = 1;
+
+       self->fd = syscall(__NR_perf_event_open, &attr, 0, -1, -1, 0);
+       ASSERT_NE(self->fd, -1);
+
+       /* Enable async notification */
+       ASSERT_EQ(fcntl(self->fd, F_SETFL, fcntl(self->fd, F_GETFL) | O_ASYNC), 
0);
+
+       /* Receive the signal for current process */
+       ASSERT_EQ(fcntl(self->fd, F_SETOWN, getpid()), 0);
+
+       /* Use signo instead of the default SIGIO */
+       ASSERT_EQ(fcntl(self->fd, F_SETSIG, RT_SIG), 0);
+}
+
+FIXTURE_TEARDOWN(refresh_signal)
+{
+       ASSERT_EQ(ioctl(self->fd, PERF_EVENT_IOC_DISABLE, 0), 0);
+
+       close(self->fd);
+       sigaction(RT_SIG, &self->old_sa, NULL);
+}
+
+TEST_F(refresh_signal, refresh_stress)
+{
+       int i;
+
+       ASSERT_EQ(ioctl(self->fd, PERF_EVENT_IOC_RESET, 0), 0);
+
+       for (i = 0; i < ITERATIONS; i++) {
+               sig_atomic_t old_count = count.hup;
+
+               /* Set event limit and the event is enabled */
+               ASSERT_EQ(ioctl(self->fd, PERF_EVENT_IOC_REFRESH, EVENT_LIMIT), 
0);
+
+               /*
+                * Wait for new the POLL_HUP notification. The test is bounded
+                * by the default timeout.
+                */
+               while (old_count == count.hup);
+       }
+
+       /*
+        * Events before EVENT_LIMIT are reported as POLL_IN. When the limit
+        * is reached, the final event is reported as POLL_HUP. The total
+        * number of events is scaled by the number of iterations.
+        *
+        * Events are delivered via irq_work. If timers expire too close to
+        * each other, irq_work may not run before the next timer fires,
+        * causing some POLL_IN events to be missed. Therefore, use a
+        * less-or-equal comparison for POLL_IN.
+        *
+        * The last event stops the timer, so the POLL_HUP signal must be
+        * observed once per iteration when the limit is reached.
+        */
+       EXPECT_LE(count.in, (EVENT_LIMIT - 1) * ITERATIONS);
+       EXPECT_GT(count.in, 0);
+       EXPECT_EQ(count.hup, ITERATIONS);
+}
+
+TEST_HARNESS_MAIN

-- 
2.34.1


Reply via email to