diff options
| author | Paul Buetow <paul@buetow.org> | 2026-05-19 20:29:31 +0300 |
|---|---|---|
| committer | Paul Buetow <paul@buetow.org> | 2026-05-19 20:29:31 +0300 |
| commit | 11a8642b7035ff558fb84d7761e93525c84e4908 (patch) | |
| tree | aa1f501fcf8f3a5474d26658731782e061cccc15 | |
| parent | c67b34fca467fc4e5e8aba7a1b8929d8aa55a833 (diff) | |
z6: add KindPoll wiring for poll/select ready counts
| -rw-r--r-- | cmd/ioworkload/scenario_polling.go | 114 | ||||
| -rw-r--r-- | cmd/ioworkload/scenario_polling_test.go | 64 | ||||
| -rw-r--r-- | integrationtests/polling_test.go | 15 | ||||
| -rw-r--r-- | internal/c/generated_tracepoints.c | 63 | ||||
| -rw-r--r-- | internal/c/generated_tracepoints_result.txt | 8 | ||||
| -rw-r--r-- | internal/c/types.h | 12 | ||||
| -rw-r--r-- | internal/event/interface_assertions.go | 3 | ||||
| -rw-r--r-- | internal/eventloop_exit.go | 11 | ||||
| -rw-r--r-- | internal/eventloop_polling_test.go | 64 | ||||
| -rw-r--r-- | internal/eventloop_runtime.go | 7 | ||||
| -rw-r--r-- | internal/generate/bpfhandler.go | 17 | ||||
| -rw-r--r-- | internal/generate/classify.go | 9 | ||||
| -rw-r--r-- | internal/generate/classify_test.go | 36 | ||||
| -rw-r--r-- | internal/generate/codegen_test.go | 24 | ||||
| -rw-r--r-- | internal/generate/kindregistry.go | 1 | ||||
| -rw-r--r-- | internal/generate/testdata.go | 111 | ||||
| -rw-r--r-- | internal/streamrow/row_test.go | 18 | ||||
| -rw-r--r-- | internal/types/fastdecode.go | 24 | ||||
| -rw-r--r-- | internal/types/fastdecode_test.go | 41 | ||||
| -rw-r--r-- | internal/types/generated_types.go | 71 | ||||
| -rw-r--r-- | internal/types/types_test.go | 25 |
21 files changed, 719 insertions, 19 deletions
diff --git a/cmd/ioworkload/scenario_polling.go b/cmd/ioworkload/scenario_polling.go index 1531511..e41ec9a 100644 --- a/cmd/ioworkload/scenario_polling.go +++ b/cmd/ioworkload/scenario_polling.go @@ -50,11 +50,40 @@ func pollingEpoll() error { pwait2Supported = false } } + if err := waitAndDrainReadiness(pipefd, callPoll); err != nil { + return err + } + if err := waitAndDrainReadiness(pipefd, callPpoll); err != nil { + return err + } + if err := waitAndDrainReadiness(pipefd, callSelect); err != nil { + return err + } + if err := waitAndDrainReadiness(pipefd, callPselect6); err != nil { + return err + } + time.Sleep(2 * time.Millisecond) } return nil } +type readinessWaitFn func(pipefd [2]int) (int, error) + +func waitAndDrainReadiness(pipefd [2]int, waitFn readinessWaitFn) error { + if _, err := syscall.Write(pipefd[1], []byte{1}); err != nil { + return fmt.Errorf("write wake byte: %w", err) + } + ready, err := waitFn(pipefd) + if err != nil { + return err + } + if ready < 1 { + return fmt.Errorf("polling wait returned %d ready events", ready) + } + return drainWakeByte(pipefd[0]) +} + func waitAndDrain(epfd int, pipefd [2]int, waitFn func(int, []unix.EpollEvent) (int, error)) error { if _, err := syscall.Write(pipefd[1], []byte{1}); err != nil { return fmt.Errorf("write wake byte: %w", err) @@ -134,6 +163,91 @@ func callEpollPwait2(epfd int, events []unix.EpollEvent) (int, error) { return int(r1), nil } +func callPoll(pipefd [2]int) (int, error) { + fds := []unix.PollFd{{Fd: int32(pipefd[0]), Events: unix.POLLIN}} + r1, _, errno := syscall.RawSyscall( + syscall.SYS_POLL, + uintptr(unsafe.Pointer(&fds[0])), + uintptr(len(fds)), + uintptr(100), + ) + runtime.KeepAlive(fds) + if errno != 0 { + return 0, fmt.Errorf("poll: %w", errno) + } + return int(r1), nil +} + +func callPpoll(pipefd [2]int) (int, error) { + fds := []unix.PollFd{{Fd: int32(pipefd[0]), Events: unix.POLLIN}} + timeout := unix.Timespec{Sec: 0, Nsec: 100 * 1_000_000} + r1, _, errno := syscall.RawSyscall6( + unix.SYS_PPOLL, + uintptr(unsafe.Pointer(&fds[0])), + uintptr(len(fds)), + uintptr(unsafe.Pointer(&timeout)), + 0, + 0, + 0, + ) + runtime.KeepAlive(fds) + runtime.KeepAlive(timeout) + if errno != 0 { + return 0, fmt.Errorf("ppoll: %w", errno) + } + return int(r1), nil +} + +type fdSet [16]uint64 + +func (s *fdSet) set(fd int) { + idx := fd / 64 + bit := uint(fd % 64) + s[idx] |= 1 << bit +} + +func callSelect(pipefd [2]int) (int, error) { + var readSet fdSet + readSet.set(pipefd[0]) + timeout := syscall.Timeval{Sec: 0, Usec: 100000} + r1, _, errno := syscall.RawSyscall6( + syscall.SYS_SELECT, + uintptr(pipefd[0]+1), + uintptr(unsafe.Pointer(&readSet)), + 0, + 0, + uintptr(unsafe.Pointer(&timeout)), + 0, + ) + runtime.KeepAlive(readSet) + runtime.KeepAlive(timeout) + if errno != 0 { + return 0, fmt.Errorf("select: %w", errno) + } + return int(r1), nil +} + +func callPselect6(pipefd [2]int) (int, error) { + var readSet fdSet + readSet.set(pipefd[0]) + timeout := unix.Timespec{Sec: 0, Nsec: 100 * 1_000_000} + r1, _, errno := syscall.RawSyscall6( + unix.SYS_PSELECT6, + uintptr(pipefd[0]+1), + uintptr(unsafe.Pointer(&readSet)), + 0, + 0, + uintptr(unsafe.Pointer(&timeout)), + 0, + ) + runtime.KeepAlive(readSet) + runtime.KeepAlive(timeout) + if errno != 0 { + return 0, fmt.Errorf("pselect6: %w", errno) + } + return int(r1), nil +} + func isUnsupportedEpollPwait2Err(err error) bool { if err == nil { return false diff --git a/cmd/ioworkload/scenario_polling_test.go b/cmd/ioworkload/scenario_polling_test.go index 83e6d7c..304602c 100644 --- a/cmd/ioworkload/scenario_polling_test.go +++ b/cmd/ioworkload/scenario_polling_test.go @@ -1,6 +1,7 @@ package main import ( + "errors" "fmt" "syscall" "testing" @@ -31,3 +32,66 @@ func TestIsUnsupportedEpollPwait2Err(t *testing.T) { }) } } + +func TestFdSetSetMarksBit(t *testing.T) { + t.Parallel() + + var set fdSet + set.set(65) + if set[1]&(1<<1) == 0 { + t.Fatalf("fd bit was not set: %#v", set) + } +} + +func TestClassicPollingSyscallsReturnReadyCount(t *testing.T) { + var pipefd [2]int + if err := syscall.Pipe(pipefd[:]); err != nil { + t.Fatalf("pipe: %v", err) + } + defer syscall.Close(pipefd[0]) //nolint:errcheck + defer syscall.Close(pipefd[1]) //nolint:errcheck + + waiters := []struct { + name string + fn readinessWaitFn + }{ + {name: "poll", fn: callPoll}, + {name: "ppoll", fn: callPpoll}, + {name: "select", fn: callSelect}, + {name: "pselect6", fn: callPselect6}, + } + + for _, waiter := range waiters { + t.Run(waiter.name, func(t *testing.T) { + if _, err := syscall.Write(pipefd[1], []byte{1}); err != nil { + t.Fatalf("write wake byte: %v", err) + } + + ready, err := waiter.fn(pipefd) + if err != nil { + if isUnsupportedPollingErr(err) { + t.Skipf("%s unsupported on this kernel: %v", waiter.name, err) + } + t.Fatalf("%s: %v", waiter.name, err) + } + if ready < 1 { + t.Fatalf("%s ready count = %d, want >=1", waiter.name, ready) + } + + if err := drainWakeByte(pipefd[0]); err != nil { + t.Fatalf("drain wake byte: %v", err) + } + }) + } +} + +func isUnsupportedPollingErr(err error) bool { + if err == nil { + return false + } + var errno syscall.Errno + if !errors.As(err, &errno) { + return false + } + return errno == syscall.ENOSYS || errno == syscall.ENOTSUP +} diff --git a/integrationtests/polling_test.go b/integrationtests/polling_test.go index cea535e..a09cab2 100644 --- a/integrationtests/polling_test.go +++ b/integrationtests/polling_test.go @@ -24,6 +24,10 @@ func TestPollingEpollTracepoints(t *testing.T) { {Tracepoint: "enter_epoll_ctl", Comm: "ioworkload", MinCount: 1}, {Tracepoint: "enter_epoll_wait", Comm: "ioworkload", MinCount: 1}, {Tracepoint: "enter_epoll_pwait", Comm: "ioworkload", MinCount: 1}, + {Tracepoint: "enter_poll", Comm: "ioworkload", MinCount: 1}, + {Tracepoint: "enter_ppoll", Comm: "ioworkload", MinCount: 1}, + {Tracepoint: "enter_select", Comm: "ioworkload", MinCount: 1}, + {Tracepoint: "enter_pselect6", Comm: "ioworkload", MinCount: 1}, }) if !hasTracepoint(result, "enter_epoll_pwait2") { @@ -44,12 +48,19 @@ func TestPollingEpollReadyCountInParquet(t *testing.T) { t.Fatalf("expected parquet rows for workload PID %d", pid) } - wantReadyCount := map[string]bool{"epoll_wait": false, "epoll_pwait": false} + wantReadyCount := map[string]bool{ + "epoll_wait": false, + "epoll_pwait": false, + "poll": false, + "ppoll": false, + "select": false, + "pselect6": false, + } var sawPwait2 bool var sawPwait2ReadyCount bool for _, row := range rows { switch row.Syscall { - case "epoll_wait", "epoll_pwait": + case "epoll_wait", "epoll_pwait", "poll", "ppoll", "select", "pselect6": if row.Ret > 0 { wantReadyCount[row.Syscall] = true } 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} |
