summaryrefslogtreecommitdiff
path: root/internal
diff options
context:
space:
mode:
authorPaul Buetow <paul@buetow.org>2026-05-19 20:29:31 +0300
committerPaul Buetow <paul@buetow.org>2026-05-19 20:29:31 +0300
commit11a8642b7035ff558fb84d7761e93525c84e4908 (patch)
treeaa1f501fcf8f3a5474d26658731782e061cccc15 /internal
parentc67b34fca467fc4e5e8aba7a1b8929d8aa55a833 (diff)
z6: add KindPoll wiring for poll/select ready counts
Diffstat (limited to 'internal')
-rw-r--r--internal/c/generated_tracepoints.c63
-rw-r--r--internal/c/generated_tracepoints_result.txt8
-rw-r--r--internal/c/types.h12
-rw-r--r--internal/event/interface_assertions.go3
-rw-r--r--internal/eventloop_exit.go11
-rw-r--r--internal/eventloop_polling_test.go64
-rw-r--r--internal/eventloop_runtime.go7
-rw-r--r--internal/generate/bpfhandler.go17
-rw-r--r--internal/generate/classify.go9
-rw-r--r--internal/generate/classify_test.go36
-rw-r--r--internal/generate/codegen_test.go24
-rw-r--r--internal/generate/kindregistry.go1
-rw-r--r--internal/generate/testdata.go111
-rw-r--r--internal/streamrow/row_test.go18
-rw-r--r--internal/types/fastdecode.go24
-rw-r--r--internal/types/fastdecode_test.go41
-rw-r--r--internal/types/generated_types.go71
-rw-r--r--internal/types/types_test.go25
18 files changed, 528 insertions, 17 deletions
diff --git a/internal/c/generated_tracepoints.c b/internal/c/generated_tracepoints.c
index cc7de62..393954c 100644
--- a/internal/c/generated_tracepoints.c
+++ b/internal/c/generated_tracepoints.c
@@ -6794,22 +6794,33 @@ int handle_sys_exit_dup(struct syscall_trace_exit *ctx) {
return 0;
}
-/// sys_enter_select is a struct null_event
+/// sys_enter_select is a struct poll_event
SEC("tracepoint/syscalls/sys_enter_select")
int handle_sys_enter_select(struct syscall_trace_enter *ctx) {
__u32 pid, tid;
if (filter(&pid, &tid))
return 0;
- struct null_event *ev = bpf_ringbuf_reserve(&event_map, sizeof(struct null_event), 0);
+ struct poll_event *ev = bpf_ringbuf_reserve(&event_map, sizeof(struct poll_event), 0);
if (!ev)
return 0;
- ev->event_type = ENTER_NULL_EVENT;
+ ev->event_type = ENTER_POLL_EVENT;
ev->trace_id = SYS_ENTER_SELECT;
ev->pid = pid;
ev->tid = tid;
ev->time = bpf_ktime_get_boot_ns();
+ ev->nfds = (__s32)ctx->args[0];
+ ev->timeout_ns = -1;
+ if (ctx->args[4] != 0) {
+ struct __ior_timeval {
+ __s64 tv_sec;
+ __s64 tv_usec;
+ } tv = {};
+ if (bpf_probe_read_user(&tv, sizeof(tv), (void *)ctx->args[4]) == 0) {
+ ev->timeout_ns = tv.tv_sec * 1000000000LL + tv.tv_usec * 1000LL;
+ }
+ }
bpf_ringbuf_submit(ev, 0);
return 0;
@@ -6838,22 +6849,33 @@ int handle_sys_exit_select(struct syscall_trace_exit *ctx) {
return 0;
}
-/// sys_enter_pselect6 is a struct null_event
+/// sys_enter_pselect6 is a struct poll_event
SEC("tracepoint/syscalls/sys_enter_pselect6")
int handle_sys_enter_pselect6(struct syscall_trace_enter *ctx) {
__u32 pid, tid;
if (filter(&pid, &tid))
return 0;
- struct null_event *ev = bpf_ringbuf_reserve(&event_map, sizeof(struct null_event), 0);
+ struct poll_event *ev = bpf_ringbuf_reserve(&event_map, sizeof(struct poll_event), 0);
if (!ev)
return 0;
- ev->event_type = ENTER_NULL_EVENT;
+ ev->event_type = ENTER_POLL_EVENT;
ev->trace_id = SYS_ENTER_PSELECT6;
ev->pid = pid;
ev->tid = tid;
ev->time = bpf_ktime_get_boot_ns();
+ ev->nfds = (__s32)ctx->args[0];
+ ev->timeout_ns = -1;
+ if (ctx->args[4] != 0) {
+ struct __ior_timespec {
+ __s64 tv_sec;
+ __s64 tv_nsec;
+ } ts = {};
+ if (bpf_probe_read_user(&ts, sizeof(ts), (void *)ctx->args[4]) == 0) {
+ ev->timeout_ns = ts.tv_sec * 1000000000LL + ts.tv_nsec;
+ }
+ }
bpf_ringbuf_submit(ev, 0);
return 0;
@@ -6882,22 +6904,28 @@ int handle_sys_exit_pselect6(struct syscall_trace_exit *ctx) {
return 0;
}
-/// sys_enter_poll is a struct null_event
+/// sys_enter_poll is a struct poll_event
SEC("tracepoint/syscalls/sys_enter_poll")
int handle_sys_enter_poll(struct syscall_trace_enter *ctx) {
__u32 pid, tid;
if (filter(&pid, &tid))
return 0;
- struct null_event *ev = bpf_ringbuf_reserve(&event_map, sizeof(struct null_event), 0);
+ struct poll_event *ev = bpf_ringbuf_reserve(&event_map, sizeof(struct poll_event), 0);
if (!ev)
return 0;
- ev->event_type = ENTER_NULL_EVENT;
+ ev->event_type = ENTER_POLL_EVENT;
ev->trace_id = SYS_ENTER_POLL;
ev->pid = pid;
ev->tid = tid;
ev->time = bpf_ktime_get_boot_ns();
+ ev->nfds = (__s32)ctx->args[1];
+ ev->timeout_ns = -1;
+ __s32 timeout_ms = (__s32)ctx->args[2];
+ if (timeout_ms >= 0) {
+ ev->timeout_ns = ((__s64)timeout_ms) * 1000000LL;
+ }
bpf_ringbuf_submit(ev, 0);
return 0;
@@ -6926,22 +6954,33 @@ int handle_sys_exit_poll(struct syscall_trace_exit *ctx) {
return 0;
}
-/// sys_enter_ppoll is a struct null_event
+/// sys_enter_ppoll is a struct poll_event
SEC("tracepoint/syscalls/sys_enter_ppoll")
int handle_sys_enter_ppoll(struct syscall_trace_enter *ctx) {
__u32 pid, tid;
if (filter(&pid, &tid))
return 0;
- struct null_event *ev = bpf_ringbuf_reserve(&event_map, sizeof(struct null_event), 0);
+ struct poll_event *ev = bpf_ringbuf_reserve(&event_map, sizeof(struct poll_event), 0);
if (!ev)
return 0;
- ev->event_type = ENTER_NULL_EVENT;
+ ev->event_type = ENTER_POLL_EVENT;
ev->trace_id = SYS_ENTER_PPOLL;
ev->pid = pid;
ev->tid = tid;
ev->time = bpf_ktime_get_boot_ns();
+ ev->nfds = (__s32)ctx->args[1];
+ ev->timeout_ns = -1;
+ if (ctx->args[2] != 0) {
+ struct __ior_timespec {
+ __s64 tv_sec;
+ __s64 tv_nsec;
+ } ts = {};
+ if (bpf_probe_read_user(&ts, sizeof(ts), (void *)ctx->args[2]) == 0) {
+ ev->timeout_ns = ts.tv_sec * 1000000000LL + ts.tv_nsec;
+ }
+ }
bpf_ringbuf_submit(ev, 0);
return 0;
diff --git a/internal/c/generated_tracepoints_result.txt b/internal/c/generated_tracepoints_result.txt
index 312c412..4ba2dc8 100644
--- a/internal/c/generated_tracepoints_result.txt
+++ b/internal/c/generated_tracepoints_result.txt
@@ -216,8 +216,8 @@ sys_enter_pivot_root is a struct null_event
sys_enter_pkey_alloc is a struct null_event
sys_enter_pkey_free is a struct null_event
sys_enter_pkey_mprotect is a struct null_event
-sys_enter_poll is a struct null_event
-sys_enter_ppoll is a struct null_event
+sys_enter_poll is a struct poll_event
+sys_enter_ppoll is a struct poll_event
sys_enter_prctl is a struct null_event
sys_enter_pread64 is a struct fd_event
sys_enter_preadv is a struct fd_event
@@ -227,7 +227,7 @@ sys_enter_process_madvise is a struct null_event
sys_enter_process_mrelease is a struct null_event
sys_enter_process_vm_readv is a struct null_event
sys_enter_process_vm_writev is a struct null_event
-sys_enter_pselect6 is a struct null_event
+sys_enter_pselect6 is a struct poll_event
sys_enter_ptrace is a struct null_event
sys_enter_pwrite64 is a struct fd_event
sys_enter_pwritev is a struct fd_event
@@ -274,7 +274,7 @@ sys_enter_sched_setparam is a struct null_event
sys_enter_sched_setscheduler is a struct null_event
sys_enter_sched_yield is a struct null_event
sys_enter_seccomp is a struct null_event
-sys_enter_select is a struct null_event
+sys_enter_select is a struct poll_event
sys_enter_semctl is a struct null_event
sys_enter_semget is a struct null_event
sys_enter_semop is a struct null_event
diff --git a/internal/c/types.h b/internal/c/types.h
index bcc88d2..128d4e9 100644
--- a/internal/c/types.h
+++ b/internal/c/types.h
@@ -33,6 +33,8 @@
#define EXIT_EVENTFD_EVENT 28
#define ENTER_EPOLL_CTL_EVENT 29
#define EXIT_EPOLL_CTL_EVENT 30
+#define ENTER_POLL_EVENT 31
+#define EXIT_POLL_EVENT 32
#define UNCLASSIFIED 0
#define READ_CLASSIFIED 1
@@ -195,3 +197,13 @@ struct epoll_ctl_event {
__s32 fd;
__u32 events;
};
+
+struct poll_event {
+ __u32 event_type;
+ __u32 trace_id;
+ __u64 time;
+ __u32 pid;
+ __u32 tid;
+ __s32 nfds;
+ __s64 timeout_ns;
+};
diff --git a/internal/event/interface_assertions.go b/internal/event/interface_assertions.go
index 15782f8..8a1c40d 100644
--- a/internal/event/interface_assertions.go
+++ b/internal/event/interface_assertions.go
@@ -59,4 +59,7 @@ var (
// *types.EpollCtlEvent carries epoll control metadata (epfd/op/target-fd/events).
_ Event = (*types.EpollCtlEvent)(nil)
+
+ // *types.PollEvent carries poll/select argument metadata (nfds and timeout).
+ _ Event = (*types.PollEvent)(nil)
)
diff --git a/internal/eventloop_exit.go b/internal/eventloop_exit.go
index 1fd6746..239724b 100644
--- a/internal/eventloop_exit.go
+++ b/internal/eventloop_exit.go
@@ -38,6 +38,8 @@ func (e *eventLoop) handleTracepointExit(ep *event.Pair) bool {
return e.handleEventfdExit(ep, ev)
case *types.EpollCtlEvent:
return e.handleEpollCtlExit(ep, ev)
+ case *types.PollEvent:
+ return e.handlePollExit(ep, ev)
case *types.NullEvent:
return e.handleNullExit(ep, ev)
case *types.FcntlEvent:
@@ -395,6 +397,15 @@ func (e *eventLoop) handleEpollCtlExit(ep *event.Pair, epollCtlEv *types.EpollCt
return true
}
+func (e *eventLoop) handlePollExit(ep *event.Pair, pollEv *types.PollEvent) bool {
+ ep.Comm = e.comm(pollEv.GetTid())
+ if !e.Filter().MatchPair(ep) {
+ ep.Recycle()
+ return false
+ }
+ return true
+}
+
func pipeDescriptorName(flags, fd0, fd1 int32) string {
return fmt.Sprintf("pipe:%d:%d:%d", flags, fd0, fd1)
}
diff --git a/internal/eventloop_polling_test.go b/internal/eventloop_polling_test.go
index 9d807d5..5158b53 100644
--- a/internal/eventloop_polling_test.go
+++ b/internal/eventloop_polling_test.go
@@ -80,4 +80,68 @@ func TestInitRawHandlersRegistersPollingEvents(t *testing.T) {
if _, ok := el.rawHandlers[types.ENTER_EPOLL_CTL_EVENT]; !ok {
t.Fatal("ENTER_EPOLL_CTL_EVENT handler is not registered")
}
+ if _, ok := el.rawHandlers[types.ENTER_POLL_EVENT]; !ok {
+ t.Fatal("ENTER_POLL_EVENT handler is not registered")
+ }
+}
+
+func TestHandlePollExitCarriesCommAndAppliesFilter(t *testing.T) {
+ t.Run("accepted", func(t *testing.T) {
+ el := mustNewEventLoop(t, eventLoopConfig{})
+ enter := &types.PollEvent{
+ EventType: types.ENTER_POLL_EVENT,
+ TraceId: types.SYS_ENTER_POLL,
+ Time: 300,
+ Pid: 120,
+ Tid: 121,
+ Nfds: 2,
+ TimeoutNs: 5_000_000,
+ }
+ exit := &types.RetEvent{
+ EventType: types.EXIT_RET_EVENT,
+ TraceId: types.SYS_EXIT_POLL,
+ Time: 320,
+ Ret: 1,
+ Pid: 120,
+ Tid: 121,
+ }
+ ep := &event.Pair{EnterEv: enter, ExitEv: exit}
+
+ if ok := el.handlePollExit(ep, enter); !ok {
+ t.Fatal("handlePollExit returned false")
+ }
+ if ep.Comm != "" {
+ t.Fatalf("expected empty comm for unresolved tid, got %q", ep.Comm)
+ }
+ })
+
+ t.Run("filtered", func(t *testing.T) {
+ el := mustNewEventLoop(t, eventLoopConfig{
+ filter: globalfilter.Filter{
+ Syscall: &globalfilter.StringFilter{Pattern: "openat"},
+ },
+ })
+ enter := &types.PollEvent{
+ EventType: types.ENTER_POLL_EVENT,
+ TraceId: types.SYS_ENTER_POLL,
+ Time: 300,
+ Pid: 120,
+ Tid: 121,
+ Nfds: 2,
+ TimeoutNs: 5_000_000,
+ }
+ exit := &types.RetEvent{
+ EventType: types.EXIT_RET_EVENT,
+ TraceId: types.SYS_EXIT_POLL,
+ Time: 320,
+ Ret: 1,
+ Pid: 120,
+ Tid: 121,
+ }
+ ep := &event.Pair{EnterEv: enter, ExitEv: exit}
+
+ if ok := el.handlePollExit(ep, enter); ok {
+ t.Fatal("handlePollExit should reject pair due to filter mismatch")
+ }
+ })
}
diff --git a/internal/eventloop_runtime.go b/internal/eventloop_runtime.go
index 135f7c1..3c1217b 100644
--- a/internal/eventloop_runtime.go
+++ b/internal/eventloop_runtime.go
@@ -342,6 +342,13 @@ func (e *eventLoop) registerPollingHandlers() {
}
e.tracepointEntered(epollCtlEv)
}
+ e.rawHandlers[types.ENTER_POLL_EVENT] = func(raw []byte, _ chan<- *event.Pair) {
+ pollEv, ok := decodeRawEvent(e, types.ENTER_POLL_EVENT, raw, types.NewPollEventFast)
+ if !ok {
+ return
+ }
+ e.tracepointEntered(pollEv)
+ }
}
func decodeRawEvent[T any](e *eventLoop, eventType types.EventType, raw []byte, decode func([]byte) *T) (*T, bool) {
diff --git a/internal/generate/bpfhandler.go b/internal/generate/bpfhandler.go
index 944025a..20859ee 100644
--- a/internal/generate/bpfhandler.go
+++ b/internal/generate/bpfhandler.go
@@ -85,6 +85,8 @@ func generateExtra(tp GeneratedTracepoint, isEnter bool) string {
return generateExtraEventfd(f, isEnter)
case KindEpollCtl:
return generateExtraEpollCtl()
+ case KindPoll:
+ return generateExtraPoll(f.Name)
case KindOpen:
return generateExtraOpen(f)
case KindPathname:
@@ -205,6 +207,21 @@ func generateExtraEpollCtl() string {
return " ev->epfd = (__s32)ctx->args[0];\n ev->op = (__s32)ctx->args[1];\n ev->fd = (__s32)ctx->args[2];\n ev->events = 0;\n if (ctx->args[3] != 0) {\n __u32 user_events = 0;\n if (bpf_probe_read_user(&user_events, sizeof(user_events), (void *)ctx->args[3]) == 0) {\n ev->events = user_events;\n }\n }\n"
}
+func generateExtraPoll(name string) string {
+ switch name {
+ case "sys_enter_poll":
+ return " ev->nfds = (__s32)ctx->args[1];\n ev->timeout_ns = -1;\n __s32 timeout_ms = (__s32)ctx->args[2];\n if (timeout_ms >= 0) {\n ev->timeout_ns = ((__s64)timeout_ms) * 1000000LL;\n }\n"
+ case "sys_enter_ppoll":
+ return " ev->nfds = (__s32)ctx->args[1];\n ev->timeout_ns = -1;\n if (ctx->args[2] != 0) {\n struct __ior_timespec {\n __s64 tv_sec;\n __s64 tv_nsec;\n } ts = {};\n if (bpf_probe_read_user(&ts, sizeof(ts), (void *)ctx->args[2]) == 0) {\n ev->timeout_ns = ts.tv_sec * 1000000000LL + ts.tv_nsec;\n }\n }\n"
+ case "sys_enter_select":
+ return " ev->nfds = (__s32)ctx->args[0];\n ev->timeout_ns = -1;\n if (ctx->args[4] != 0) {\n struct __ior_timeval {\n __s64 tv_sec;\n __s64 tv_usec;\n } tv = {};\n if (bpf_probe_read_user(&tv, sizeof(tv), (void *)ctx->args[4]) == 0) {\n ev->timeout_ns = tv.tv_sec * 1000000000LL + tv.tv_usec * 1000LL;\n }\n }\n"
+ case "sys_enter_pselect6":
+ return " ev->nfds = (__s32)ctx->args[0];\n ev->timeout_ns = -1;\n if (ctx->args[4] != 0) {\n struct __ior_timespec {\n __s64 tv_sec;\n __s64 tv_nsec;\n } ts = {};\n if (bpf_probe_read_user(&ts, sizeof(ts), (void *)ctx->args[4]) == 0) {\n ev->timeout_ns = ts.tv_sec * 1000000000LL + ts.tv_nsec;\n }\n }\n"
+ default:
+ return " ev->nfds = -1;\n ev->timeout_ns = -1;\n"
+ }
+}
+
// eventStructName returns the C struct name for a TracepointKind. The mapping
// is driven by kindRegistry so adding a new kind only requires a registry entry.
func eventStructName(kind TracepointKind) string {
diff --git a/internal/generate/classify.go b/internal/generate/classify.go
index 21a8bf9..7c391fb 100644
--- a/internal/generate/classify.go
+++ b/internal/generate/classify.go
@@ -21,6 +21,7 @@ const (
KindPipe
KindEventfd
KindEpollCtl
+ KindPoll
)
type RetClassification string
@@ -139,6 +140,14 @@ func classifyNameOnly(name string) (ClassificationResult, bool) {
return ClassificationResult{Kind: KindFd}, true
case "sys_enter_epoll_ctl":
return ClassificationResult{Kind: KindEpollCtl}, true
+ case "sys_enter_poll":
+ return ClassificationResult{Kind: KindPoll}, true
+ case "sys_enter_ppoll":
+ return ClassificationResult{Kind: KindPoll}, true
+ case "sys_enter_select":
+ return ClassificationResult{Kind: KindPoll}, true
+ case "sys_enter_pselect6":
+ return ClassificationResult{Kind: KindPoll}, true
}
if strings.HasPrefix(name, "sys_enter_io_") {
return ClassificationResult{Kind: KindNull}, true
diff --git a/internal/generate/classify_test.go b/internal/generate/classify_test.go
index 74d4143..367d7c8 100644
--- a/internal/generate/classify_test.go
+++ b/internal/generate/classify_test.go
@@ -397,6 +397,34 @@ func TestClassifyEpollPwait2(t *testing.T) {
}
}
+func TestClassifyPoll(t *testing.T) {
+ r := classifyFromData(t, FormatPoll)
+ if r.Kind != KindPoll {
+ t.Errorf("poll: got kind %d, want KindPoll", r.Kind)
+ }
+}
+
+func TestClassifyPpoll(t *testing.T) {
+ r := classifyFromData(t, FormatPpoll)
+ if r.Kind != KindPoll {
+ t.Errorf("ppoll: got kind %d, want KindPoll", r.Kind)
+ }
+}
+
+func TestClassifySelect(t *testing.T) {
+ r := classifyFromData(t, FormatSelect)
+ if r.Kind != KindPoll {
+ t.Errorf("select: got kind %d, want KindPoll", r.Kind)
+ }
+}
+
+func TestClassifyPselect6(t *testing.T) {
+ r := classifyFromData(t, FormatPselect6)
+ if r.Kind != KindPoll {
+ t.Errorf("pselect6: got kind %d, want KindPoll", r.Kind)
+ }
+}
+
func TestClassifyKillRequiresGenerationFallback(t *testing.T) {
r := classifyFromData(t, FormatKill)
if r.Kind != KindNone {
@@ -445,6 +473,10 @@ func TestClassifySyscallPairAccepted(t *testing.T) {
{"epoll_wait", FormatEpollWait, FormatExitEpollWait, KindFd},
{"epoll_pwait", FormatEpollPwait, FormatExitEpollPwait, KindFd},
{"epoll_pwait2", FormatEpollPwait2, FormatExitEpollPwait2, KindFd},
+ {"poll", FormatPoll, FormatExitPoll, KindPoll},
+ {"ppoll", FormatPpoll, FormatExitPpoll, KindPoll},
+ {"select", FormatSelect, FormatExitSelect, KindPoll},
+ {"pselect6", FormatPselect6, FormatExitPselect6, KindPoll},
{"kill", FormatKill, FormatExitKill, KindNull},
}
@@ -478,6 +510,10 @@ func TestClassifySyscallPairEmitsAllFamilies(t *testing.T) {
{"eventfd2", FormatEventfd2, FormatExitEventfd2, FamilyIPC},
{"epoll_ctl", FormatEpollCtl, FormatExitEpollCtl, FamilyPolling},
{"epoll_wait", FormatEpollWait, FormatExitEpollWait, FamilyPolling},
+ {"poll", FormatPoll, FormatExitPoll, FamilyPolling},
+ {"ppoll", FormatPpoll, FormatExitPpoll, FamilyPolling},
+ {"select", FormatSelect, FormatExitSelect, FamilyPolling},
+ {"pselect6", FormatPselect6, FormatExitPselect6, FamilyPolling},
{"kill", FormatKill, FormatExitKill, FamilySignals},
}
diff --git a/internal/generate/codegen_test.go b/internal/generate/codegen_test.go
index 23b5a2a..a3baed7 100644
--- a/internal/generate/codegen_test.go
+++ b/internal/generate/codegen_test.go
@@ -278,6 +278,26 @@ func TestGenerateEpollWaitHandlerUsesEpollFd(t *testing.T) {
requireContains(t, output, "ev->fd = (__s32)ctx->args[0];")
}
+func TestGeneratePollHandlerCapturesNfdsAndTimeout(t *testing.T) {
+ output := generateFromPair(t, FormatPoll, FormatExitPoll)
+
+ requireContains(t, output, "struct poll_event *ev")
+ requireContains(t, output, "ev->event_type = ENTER_POLL_EVENT;")
+ requireContains(t, output, "ev->nfds = (__s32)ctx->args[1];")
+ requireContains(t, output, "ev->timeout_ns = ((__s64)timeout_ms) * 1000000LL;")
+ requireContains(t, output, "ev->event_type = EXIT_RET_EVENT;")
+}
+
+func TestGeneratePselect6HandlerCapturesTimeoutPointer(t *testing.T) {
+ output := generateFromPair(t, FormatPselect6, FormatExitPselect6)
+
+ requireContains(t, output, "struct poll_event *ev")
+ requireContains(t, output, "ev->event_type = ENTER_POLL_EVENT;")
+ requireContains(t, output, "ev->nfds = (__s32)ctx->args[0];")
+ requireContains(t, output, "if (ctx->args[4] != 0) {")
+ requireContains(t, output, "ev->timeout_ns = ts.tv_sec * 1000000000LL + ts.tv_nsec;")
+}
+
func TestGenerateNameToHandleAtHandler(t *testing.T) {
output := generateFromPair(t, FormatNameToHandleAt, FormatExitNameToHandleAt)
@@ -391,6 +411,7 @@ func TestGenerateAllEventTypes(t *testing.T) {
{KindPipe, "ENTER_PIPE_EVENT", "EXIT_PIPE_EVENT"},
{KindEventfd, "ENTER_EVENTFD_EVENT", "EXIT_EVENTFD_EVENT"},
{KindEpollCtl, "ENTER_EPOLL_CTL_EVENT", "EXIT_EPOLL_CTL_EVENT"},
+ {KindPoll, "ENTER_POLL_EVENT", "EXIT_POLL_EVENT"},
}
for _, tt := range tests {
@@ -423,6 +444,7 @@ func TestEventStructNames(t *testing.T) {
{KindPipe, "pipe_event"},
{KindEventfd, "eventfd_event"},
{KindEpollCtl, "epoll_ctl_event"},
+ {KindPoll, "poll_event"},
}
for _, tt := range tests {
@@ -441,7 +463,7 @@ func TestEnterReject(t *testing.T) {
t.Error("KindNone should be enter-rejected")
}
- accepted := []TracepointKind{KindFd, KindOpen, KindPathname, KindName, KindFcntl, KindNull, KindDup3, KindOpenByHandleAt, KindSocket, KindSocketpair, KindAccept, KindPipe, KindEventfd, KindEpollCtl}
+ accepted := []TracepointKind{KindFd, KindOpen, KindPathname, KindName, KindFcntl, KindNull, KindDup3, KindOpenByHandleAt, KindSocket, KindSocketpair, KindAccept, KindPipe, KindEventfd, KindEpollCtl, KindPoll}
for _, k := range accepted {
if isEnterRejected(k) {
t.Errorf("kind %d should NOT be enter-rejected", k)
diff --git a/internal/generate/kindregistry.go b/internal/generate/kindregistry.go
index 07f9d57..e8efe99 100644
--- a/internal/generate/kindregistry.go
+++ b/internal/generate/kindregistry.go
@@ -31,6 +31,7 @@ var kindRegistry = map[TracepointKind]kindMeta{
KindPipe: {structName: "pipe_event", enterAccepted: true},
KindEventfd: {structName: "eventfd_event", enterAccepted: true},
KindEpollCtl: {structName: "epoll_ctl_event", enterAccepted: true},
+ KindPoll: {structName: "poll_event", enterAccepted: true},
// KindNone is intentionally absent: it represents "unclassified" and is
// never enter-accepted. lookupKind returns the zero kindMeta (enterAccepted=false)
// for any unregistered kind, so KindNone is implicitly rejected.
diff --git a/internal/generate/testdata.go b/internal/generate/testdata.go
index 9b9496b..e3e3036 100644
--- a/internal/generate/testdata.go
+++ b/internal/generate/testdata.go
@@ -1186,3 +1186,114 @@ format:
print fmt: "0x%lx", REC->ret
`
+
+const FormatPoll = `name: sys_enter_poll
+ID: 915
+format:
+ field:unsigned short common_type; offset:0; size:2; signed:0;
+ field:unsigned char common_flags; offset:2; size:1; signed:0;
+ field:unsigned char common_preempt_count; offset:3; size:1; signed:0;
+ field:int common_pid; offset:4; size:4; signed:1;
+
+ field:int __syscall_nr; offset:8; size:4; signed:1;
+ field:struct pollfd * ufds; offset:16; size:8; signed:0;
+ field:unsigned int nfds; offset:24; size:8; signed:0;
+ field:int timeout; offset:32; size:8; signed:0;
+`
+
+const FormatExitPoll = `name: sys_exit_poll
+ID: 914
+format:
+ field:unsigned short common_type; offset:0; size:2; signed:0;
+ field:unsigned char common_flags; offset:2; size:1; signed:0;
+ field:unsigned char common_preempt_count; offset:3; size:1; signed:0;
+ field:int common_pid; offset:4; size:4; signed:1;
+
+ field:int __syscall_nr; offset:8; size:4; signed:1;
+ field:long ret; offset:16; size:8; signed:1;
+`
+
+const FormatPpoll = `name: sys_enter_ppoll
+ID: 913
+format:
+ field:unsigned short common_type; offset:0; size:2; signed:0;
+ field:unsigned char common_flags; offset:2; size:1; signed:0;
+ field:unsigned char common_preempt_count; offset:3; size:1; signed:0;
+ field:int common_pid; offset:4; size:4; signed:1;
+
+ field:int __syscall_nr; offset:8; size:4; signed:1;
+ field:struct pollfd * ufds; offset:16; size:8; signed:0;
+ field:unsigned int nfds; offset:24; size:8; signed:0;
+ field:const struct __kernel_timespec * tmo_p; offset:32; size:8; signed:0;
+ field:const sigset_t * sigmask; offset:40; size:8; signed:0;
+ field:size_t sigsetsize; offset:48; size:8; signed:0;
+`
+
+const FormatExitPpoll = `name: sys_exit_ppoll
+ID: 912
+format:
+ field:unsigned short common_type; offset:0; size:2; signed:0;
+ field:unsigned char common_flags; offset:2; size:1; signed:0;
+ field:unsigned char common_preempt_count; offset:3; size:1; signed:0;
+ field:int common_pid; offset:4; size:4; signed:1;
+
+ field:int __syscall_nr; offset:8; size:4; signed:1;
+ field:long ret; offset:16; size:8; signed:1;
+`
+
+const FormatSelect = `name: sys_enter_select
+ID: 919
+format:
+ field:unsigned short common_type; offset:0; size:2; signed:0;
+ field:unsigned char common_flags; offset:2; size:1; signed:0;
+ field:unsigned char common_preempt_count; offset:3; size:1; signed:0;
+ field:int common_pid; offset:4; size:4; signed:1;
+
+ field:int __syscall_nr; offset:8; size:4; signed:1;
+ field:int n; offset:16; size:8; signed:0;
+ field:fd_set * inp; offset:24; size:8; signed:0;
+ field:fd_set * outp; offset:32; size:8; signed:0;
+ field:fd_set * exp; offset:40; size:8; signed:0;
+ field:struct timeval * tvp; offset:48; size:8; signed:0;
+`
+
+const FormatExitSelect = `name: sys_exit_select
+ID: 918
+format:
+ field:unsigned short common_type; offset:0; size:2; signed:0;
+ field:unsigned char common_flags; offset:2; size:1; signed:0;
+ field:unsigned char common_preempt_count; offset:3; size:1; signed:0;
+ field:int common_pid; offset:4; size:4; signed:1;
+
+ field:int __syscall_nr; offset:8; size:4; signed:1;
+ field:long ret; offset:16; size:8; signed:1;
+`
+
+const FormatPselect6 = `name: sys_enter_pselect6
+ID: 917
+format:
+ field:unsigned short common_type; offset:0; size:2; signed:0;
+ field:unsigned char common_flags; offset:2; size:1; signed:0;
+ field:unsigned char common_preempt_count; offset:3; size:1; signed:0;
+ field:int common_pid; offset:4; size:4; signed:1;
+
+ field:int __syscall_nr; offset:8; size:4; signed:1;
+ field:int n; offset:16; size:8; signed:0;
+ field:fd_set * inp; offset:24; size:8; signed:0;
+ field:fd_set * outp; offset:32; size:8; signed:0;
+ field:fd_set * exp; offset:40; size:8; signed:0;
+ field:const struct __kernel_timespec * tsp; offset:48; size:8; signed:0;
+ field:void * sig; offset:56; size:8; signed:0;
+`
+
+const FormatExitPselect6 = `name: sys_exit_pselect6
+ID: 916
+format:
+ field:unsigned short common_type; offset:0; size:2; signed:0;
+ field:unsigned char common_flags; offset:2; size:1; signed:0;
+ field:unsigned char common_preempt_count; offset:3; size:1; signed:0;
+ field:int common_pid; offset:4; size:4; signed:1;
+
+ field:int __syscall_nr; offset:8; size:4; signed:1;
+ field:long ret; offset:16; size:8; signed:1;
+`
diff --git a/internal/streamrow/row_test.go b/internal/streamrow/row_test.go
index 53f96c5..b239969 100644
--- a/internal/streamrow/row_test.go
+++ b/internal/streamrow/row_test.go
@@ -118,6 +118,24 @@ func TestNewCarriesReadyCountForEpollWait(t *testing.T) {
}
}
+func TestNewCarriesReadyCountForPoll(t *testing.T) {
+ enter := &types.PollEvent{TraceId: types.SYS_ENTER_POLL, Time: 3000, Pid: 22, Tid: 23, Nfds: 1, TimeoutNs: 100_000_000}
+ exit := &types.RetEvent{TraceId: types.SYS_EXIT_POLL, Time: 3100, Ret: 1, Pid: 22, Tid: 23}
+ pair := event.NewPair(enter)
+ pair.ExitEv = exit
+
+ got := New(24, pair)
+ if got.Syscall != "poll" || got.FD != UnknownFD {
+ t.Fatalf("Syscall/FD = %q/%d, want poll/%d", got.Syscall, got.FD, UnknownFD)
+ }
+ if got.RetVal != 1 || got.IsError {
+ t.Fatalf("RetVal/IsError = %d/%v, want 1/false", got.RetVal, got.IsError)
+ }
+ if got.Bytes != 0 {
+ t.Fatalf("Bytes = %d, want 0 for poll ready-count events", got.Bytes)
+ }
+}
+
// TestRowValueAccessors verifies that all typed accessor methods return the
// underlying field values set on a Row.
func TestRowValueAccessors(t *testing.T) {
diff --git a/internal/types/fastdecode.go b/internal/types/fastdecode.go
index 5cb7241..9c0ac9c 100644
--- a/internal/types/fastdecode.go
+++ b/internal/types/fastdecode.go
@@ -22,6 +22,8 @@ const (
eventfdEventSize = 40
eventfdEventSizeV1 = 36
epollCtlEventSize = 40
+ pollEventSize = 40
+ pollEventSizeV1 = 36
)
func NewOpenEventFast(raw []byte) *OpenEvent {
@@ -315,3 +317,25 @@ func NewEpollCtlEventFast(raw []byte) *EpollCtlEvent {
e.Events = binary.LittleEndian.Uint32(raw[36:40])
return e
}
+
+func NewPollEventFast(raw []byte) *PollEvent {
+ if len(raw) < pollEventSizeV1 {
+ return nil
+ }
+ if len(raw) != pollEventSize && len(raw) != pollEventSizeV1 {
+ return NewPollEvent(raw)
+ }
+ p := poolOfPollEvents.Get().(*PollEvent)
+ p.EventType = EventType(binary.LittleEndian.Uint32(raw[0:4]))
+ p.TraceId = TraceId(binary.LittleEndian.Uint32(raw[4:8]))
+ p.Time = binary.LittleEndian.Uint64(raw[8:16])
+ p.Pid = binary.LittleEndian.Uint32(raw[16:20])
+ p.Tid = binary.LittleEndian.Uint32(raw[20:24])
+ p.Nfds = int32(binary.LittleEndian.Uint32(raw[24:28]))
+ timeoutOffset := 28
+ if len(raw) == pollEventSize {
+ timeoutOffset = 32
+ }
+ p.TimeoutNs = int64(binary.LittleEndian.Uint64(raw[timeoutOffset : timeoutOffset+8]))
+ return p
+}
diff --git a/internal/types/fastdecode_test.go b/internal/types/fastdecode_test.go
index 321f98c..272e6d4 100644
--- a/internal/types/fastdecode_test.go
+++ b/internal/types/fastdecode_test.go
@@ -205,6 +205,19 @@ func TestFastDecodersMatchGeneratedDecoders(t *testing.T) {
t.Fatalf("epoll_ctl decode mismatch")
}
})
+
+ t.Run("PollEvent", func(t *testing.T) {
+ ev := &PollEvent{EventType: ENTER_POLL_EVENT, TraceId: SYS_ENTER_POLL, Time: 1, Pid: 2, Tid: 3, Nfds: 4, TimeoutNs: 5_000_000}
+ raw, _ := ev.Bytes()
+
+ slow := NewPollEvent(raw)
+ fast := NewPollEventFast(raw)
+ defer slow.Recycle()
+ defer fast.Recycle()
+ if !slow.Equals(fast) {
+ t.Fatalf("poll decode mismatch")
+ }
+ })
}
func TestNewSocketpairEventFastKernelLayout(t *testing.T) {
@@ -327,6 +340,33 @@ func TestNewEventfdEventFastKernelLayout(t *testing.T) {
}
}
+func TestNewPollEventFastKernelLayout(t *testing.T) {
+ raw := make([]byte, pollEventSize)
+ binary.LittleEndian.PutUint32(raw[0:4], uint32(ENTER_POLL_EVENT))
+ binary.LittleEndian.PutUint32(raw[4:8], uint32(SYS_ENTER_POLL))
+ binary.LittleEndian.PutUint64(raw[8:16], 1)
+ binary.LittleEndian.PutUint32(raw[16:20], 2)
+ binary.LittleEndian.PutUint32(raw[20:24], 3)
+ binary.LittleEndian.PutUint32(raw[24:28], uint32(8))
+ binary.LittleEndian.PutUint64(raw[32:40], uint64(75_000_000))
+
+ fast := NewPollEventFast(raw)
+ if fast == nil {
+ t.Fatalf("expected decoded poll event for kernel layout payload")
+ }
+ defer fast.Recycle()
+
+ if fast.EventType != ENTER_POLL_EVENT ||
+ fast.TraceId != SYS_ENTER_POLL ||
+ fast.Time != 1 ||
+ fast.Pid != 2 ||
+ fast.Tid != 3 ||
+ fast.Nfds != 8 ||
+ fast.TimeoutNs != 75_000_000 {
+ t.Fatalf("unexpected poll decode: %#v", fast)
+ }
+}
+
func TestFastDecodersReturnNilOnShortPayload(t *testing.T) {
cases := []struct {
name string
@@ -347,6 +387,7 @@ func TestFastDecodersReturnNilOnShortPayload(t *testing.T) {
{name: "PipeEvent", decode: func(raw []byte) bool { return NewPipeEventFast(raw) == nil }},
{name: "EventfdEvent", decode: func(raw []byte) bool { return NewEventfdEventFast(raw) == nil }},
{name: "EpollCtlEvent", decode: func(raw []byte) bool { return NewEpollCtlEventFast(raw) == nil }},
+ {name: "PollEvent", decode: func(raw []byte) bool { return NewPollEventFast(raw) == nil }},
}
for _, tc := range cases {
diff --git a/internal/types/generated_types.go b/internal/types/generated_types.go
index 9876a80..6f95c7a 100644
--- a/internal/types/generated_types.go
+++ b/internal/types/generated_types.go
@@ -98,6 +98,8 @@ const ENTER_EVENTFD_EVENT = 27
const EXIT_EVENTFD_EVENT = 28
const ENTER_EPOLL_CTL_EVENT = 29
const EXIT_EPOLL_CTL_EVENT = 30
+const ENTER_POLL_EVENT = 31
+const EXIT_POLL_EVENT = 32
const UNCLASSIFIED = 0
const READ_CLASSIFIED = 1
const WRITE_CLASSIFIED = 2
@@ -1877,3 +1879,72 @@ func (e *EpollCtlEvent) Bytes() ([]byte, error) {
func (e *EpollCtlEvent) Recycle() {
poolOfEpollCtlEvents.Put(e)
}
+
+type PollEvent struct {
+ EventType EventType
+ TraceId TraceId
+ Time uint64
+ Pid uint32
+ Tid uint32
+ Nfds int32
+ TimeoutNs int64
+}
+
+func (p PollEvent) String() string {
+ return fmt.Sprintf("EventType:%v TraceId:%v Time:%v Pid:%v Tid:%v Nfds:%v TimeoutNs:%v", p.EventType, p.TraceId, p.Time, p.Pid, p.Tid, p.Nfds, p.TimeoutNs)
+}
+
+func (p PollEvent) Equals(other any) bool {
+ otherConcrete, ok := other.(*PollEvent)
+ if !ok {
+ return false
+ }
+ return p.EventType == otherConcrete.EventType && p.TraceId == otherConcrete.TraceId && p.Time == otherConcrete.Time && p.Pid == otherConcrete.Pid && p.Tid == otherConcrete.Tid && p.Nfds == otherConcrete.Nfds && p.TimeoutNs == otherConcrete.TimeoutNs
+}
+
+func (p *PollEvent) GetEventType() EventType {
+ return p.EventType
+}
+
+func (p *PollEvent) GetTraceId() TraceId {
+ return p.TraceId
+}
+
+func (p *PollEvent) GetPid() uint32 {
+ return p.Pid
+}
+
+func (p *PollEvent) GetTid() uint32 {
+ return p.Tid
+}
+
+func (p *PollEvent) GetTime() uint64 {
+ return p.Time
+}
+
+var poolOfPollEvents = sync.Pool{
+ New: func() any { return &PollEvent{} },
+}
+
+func NewPollEvent(raw []byte) *PollEvent {
+ p := poolOfPollEvents.Get().(*PollEvent)
+ if err := binary.Read(bytes.NewReader(raw), binary.LittleEndian, p); err != nil {
+ *p = PollEvent{}
+ poolOfPollEvents.Put(p)
+ return nil
+ }
+ return p
+}
+
+func (p *PollEvent) Bytes() ([]byte, error) {
+ buf := new(bytes.Buffer)
+ err := binary.Write(buf, binary.LittleEndian, p)
+ if err != nil {
+ return nil, err
+ }
+ return buf.Bytes(), nil
+}
+
+func (p *PollEvent) Recycle() {
+ poolOfPollEvents.Put(p)
+}
diff --git a/internal/types/types_test.go b/internal/types/types_test.go
index 3e75de2..0134ea0 100644
--- a/internal/types/types_test.go
+++ b/internal/types/types_test.go
@@ -298,6 +298,31 @@ func TestEpollCtlEventSerialization(t *testing.T) {
assertEquals(t, epollCtlEv1.Events, epollCtlEv2.Events)
}
+func TestPollEventSerialization(t *testing.T) {
+ pollEv1 := PollEvent{
+ EventType: ENTER_POLL_EVENT,
+ TraceId: SYS_ENTER_POLL,
+ Time: 7890,
+ Pid: 42,
+ Tid: 43,
+ Nfds: 6,
+ TimeoutNs: 250_000_000,
+ }
+ bytes, err := pollEv1.Bytes()
+ if err != nil {
+ t.Error(err)
+ }
+ pollEv2 := NewPollEvent(bytes)
+
+ assertEquals(t, pollEv1.EventType, pollEv2.EventType)
+ assertEquals(t, pollEv1.TraceId, pollEv2.TraceId)
+ assertEquals(t, pollEv1.Time, pollEv2.Time)
+ assertEquals(t, pollEv1.Pid, pollEv2.Pid)
+ assertEquals(t, pollEv1.Tid, pollEv2.Tid)
+ assertEquals(t, pollEv1.Nfds, pollEv2.Nfds)
+ assertEquals(t, pollEv1.TimeoutNs, pollEv2.TimeoutNs)
+}
+
func TestEqualsDifferentTypes(t *testing.T) {
openEv := OpenEvent{EventType: ENTER_OPEN_EVENT, TraceId: SYS_ENTER_OPENAT, Time: 1, Pid: 1, Tid: 1}
nullEv := NullEvent{EventType: ENTER_NULL_EVENT, TraceId: SYS_ENTER_SYNC, Time: 1, Pid: 1, Tid: 1}