diff options
| -rw-r--r-- | cmd/ioworkload/scenario_polling.go | 121 | ||||
| -rw-r--r-- | cmd/ioworkload/scenarios.go | 1 | ||||
| -rw-r--r-- | integrationtests/polling_test.go | 64 | ||||
| -rw-r--r-- | internal/c/generated_tracepoints.c | 36 | ||||
| -rw-r--r-- | internal/c/generated_tracepoints_result.txt | 8 | ||||
| -rw-r--r-- | internal/c/types.h | 14 | ||||
| -rw-r--r-- | internal/event/interface_assertions.go | 3 | ||||
| -rw-r--r-- | internal/eventloop_exit.go | 12 | ||||
| -rw-r--r-- | internal/eventloop_polling_test.go | 83 | ||||
| -rw-r--r-- | internal/eventloop_runtime.go | 11 | ||||
| -rw-r--r-- | internal/generate/bpfhandler.go | 6 | ||||
| -rw-r--r-- | internal/generate/classify.go | 9 | ||||
| -rw-r--r-- | internal/generate/classify_test.go | 34 | ||||
| -rw-r--r-- | internal/generate/codegen_test.go | 27 | ||||
| -rw-r--r-- | internal/generate/kindregistry.go | 1 | ||||
| -rw-r--r-- | internal/generate/testdata.go | 128 | ||||
| -rw-r--r-- | internal/streamrow/row_test.go | 19 | ||||
| -rw-r--r-- | internal/types/fastdecode.go | 21 | ||||
| -rw-r--r-- | internal/types/fastdecode_test.go | 14 | ||||
| -rw-r--r-- | internal/types/generated_types.go | 73 | ||||
| -rw-r--r-- | internal/types/types_test.go | 29 |
21 files changed, 697 insertions, 17 deletions
diff --git a/cmd/ioworkload/scenario_polling.go b/cmd/ioworkload/scenario_polling.go new file mode 100644 index 0000000..6a97f57 --- /dev/null +++ b/cmd/ioworkload/scenario_polling.go @@ -0,0 +1,121 @@ +package main + +import ( + "fmt" + "runtime" + "syscall" + "time" + "unsafe" + + "golang.org/x/sys/unix" +) + +func pollingEpoll() error { + epfd, err := unix.EpollCreate1(unix.EPOLL_CLOEXEC) + if err != nil { + return fmt.Errorf("epoll_create1: %w", err) + } + defer syscall.Close(epfd) //nolint:errcheck + + var pipefd [2]int + if err := syscall.Pipe(pipefd[:]); err != nil { + return fmt.Errorf("pipe: %w", err) + } + defer syscall.Close(pipefd[0]) //nolint:errcheck + defer syscall.Close(pipefd[1]) //nolint:errcheck + + event := unix.EpollEvent{Events: unix.EPOLLIN, Fd: int32(pipefd[0])} + if err := unix.EpollCtl(epfd, unix.EPOLL_CTL_ADD, pipefd[0], &event); err != nil { + return fmt.Errorf("epoll_ctl add: %w", err) + } + + deadline := time.Now().Add(5 * time.Second) + for time.Now().Before(deadline) { + if err := waitAndDrain(epfd, pipefd, callEpollWait); err != nil { + return err + } + if err := waitAndDrain(epfd, pipefd, callEpollPwait); err != nil { + return err + } + if err := waitAndDrain(epfd, pipefd, callEpollPwait2); err != nil { + return err + } + } + + return nil +} + +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) + } + events := make([]unix.EpollEvent, 4) + ready, err := waitFn(epfd, events) + if err != nil { + return err + } + if ready < 1 { + return fmt.Errorf("epoll wait returned %d ready events", ready) + } + var buf [1]byte + if _, err := syscall.Read(pipefd[0], buf[:]); err != nil { + return fmt.Errorf("drain wake byte: %w", err) + } + return nil +} + +func callEpollWait(epfd int, events []unix.EpollEvent) (int, error) { + eventPtr := unsafe.Pointer(&events[0]) + r1, _, errno := syscall.RawSyscall6( + syscall.SYS_EPOLL_WAIT, + uintptr(epfd), + uintptr(eventPtr), + uintptr(len(events)), + uintptr(100), + 0, + 0, + ) + runtime.KeepAlive(events) + if errno != 0 { + return 0, fmt.Errorf("epoll_wait: %w", errno) + } + return int(r1), nil +} + +func callEpollPwait(epfd int, events []unix.EpollEvent) (int, error) { + eventPtr := unsafe.Pointer(&events[0]) + r1, _, errno := syscall.RawSyscall6( + syscall.SYS_EPOLL_PWAIT, + uintptr(epfd), + uintptr(eventPtr), + uintptr(len(events)), + uintptr(100), + 0, + 0, + ) + runtime.KeepAlive(events) + if errno != 0 { + return 0, fmt.Errorf("epoll_pwait: %w", errno) + } + return int(r1), nil +} + +func callEpollPwait2(epfd int, events []unix.EpollEvent) (int, error) { + eventPtr := unsafe.Pointer(&events[0]) + timeout := unix.Timespec{Sec: 0, Nsec: 100 * 1_000_000} + r1, _, errno := syscall.RawSyscall6( + unix.SYS_EPOLL_PWAIT2, + uintptr(epfd), + uintptr(eventPtr), + uintptr(len(events)), + uintptr(unsafe.Pointer(&timeout)), + 0, + 0, + ) + runtime.KeepAlive(events) + runtime.KeepAlive(timeout) + if errno != 0 { + return 0, fmt.Errorf("epoll_pwait2: %w", errno) + } + return int(r1), nil +} diff --git a/cmd/ioworkload/scenarios.go b/cmd/ioworkload/scenarios.go index b9c08d3..6a6e40b 100644 --- a/cmd/ioworkload/scenarios.go +++ b/cmd/ioworkload/scenarios.go @@ -34,6 +34,7 @@ var scenarios = map[string]func() error{ "pipe2-basic": pipe2Basic, "eventfd-basic": eventfdBasic, "eventfd2-basic": eventfd2Basic, + "polling-epoll": pollingEpoll, "family-mixed": familyMixed, "close-basic": closeBasic, "close-range": closeRange, diff --git a/integrationtests/polling_test.go b/integrationtests/polling_test.go new file mode 100644 index 0000000..af3806a --- /dev/null +++ b/integrationtests/polling_test.go @@ -0,0 +1,64 @@ +package integrationtests + +import "testing" + +const ( + pollingParquetDuration = 10 + pollingWorkloadStartupEnv = "IOR_WORKLOAD_STARTUP_DELAY_MS=1000" +) + +func TestPollingEpollTracepoints(t *testing.T) { + h := newTestHarness(t) + h.WorkloadEnv = []string{pollingWorkloadStartupEnv} + result, pid, err := h.Run("polling-epoll", defaultDuration) + if err != nil { + t.Fatalf("run scenario polling-epoll: %v", err) + } + + AssertNoUnexpectedPID(t, result, pid) + AssertNoUnexpectedComm(t, result, "ioworkload") + AssertEventsPresent(t, result, []ExpectedEvent{ + {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_epoll_pwait2", Comm: "ioworkload", MinCount: 1}, + }) +} + +func TestPollingEpollReadyCountInParquet(t *testing.T) { + h := newTestHarness(t) + h.WorkloadEnv = []string{pollingWorkloadStartupEnv} + path, pid, err := h.RunParquet("polling-epoll", pollingParquetDuration) + if err != nil { + t.Fatalf("run polling-epoll parquet scenario: %v", err) + } + + rows := filterRecordsByPID(readParquetRecords(t, path), uint32(pid)) + if len(rows) == 0 { + t.Fatalf("expected parquet rows for workload PID %d", pid) + } + + wantReadyCount := map[string]bool{ + "epoll_wait": false, + "epoll_pwait": false, + "epoll_pwait2": false, + } + for _, row := range rows { + _, tracked := wantReadyCount[row.Syscall] + if !tracked { + continue + } + if row.Ret > 0 { + wantReadyCount[row.Syscall] = true + } + if row.Bytes != 0 { + t.Fatalf("%s bytes = %d, want 0 for ready-count events", row.Syscall, row.Bytes) + } + } + + for syscall, ok := range wantReadyCount { + if !ok { + t.Fatalf("expected %s row with positive ready-count ret in parquet output", syscall) + } + } +} diff --git a/internal/c/generated_tracepoints.c b/internal/c/generated_tracepoints.c index c14c61e..cc7de62 100644 --- a/internal/c/generated_tracepoints.c +++ b/internal/c/generated_tracepoints.c @@ -3987,23 +3987,32 @@ int handle_sys_exit_epoll_create(struct syscall_trace_exit *ctx) { return 0; } -/// sys_enter_epoll_ctl is a struct fd_event +/// sys_enter_epoll_ctl is a struct epoll_ctl_event SEC("tracepoint/syscalls/sys_enter_epoll_ctl") int handle_sys_enter_epoll_ctl(struct syscall_trace_enter *ctx) { __u32 pid, tid; if (filter(&pid, &tid)) return 0; - struct fd_event *ev = bpf_ringbuf_reserve(&event_map, sizeof(struct fd_event), 0); + struct epoll_ctl_event *ev = bpf_ringbuf_reserve(&event_map, sizeof(struct epoll_ctl_event), 0); if (!ev) return 0; - ev->event_type = ENTER_FD_EVENT; + ev->event_type = ENTER_EPOLL_CTL_EVENT; ev->trace_id = SYS_ENTER_EPOLL_CTL; ev->pid = pid; ev->tid = tid; ev->time = bpf_ktime_get_boot_ns(); + ev->epfd = (__s32)ctx->args[0]; + ev->op = (__s32)ctx->args[1]; ev->fd = (__s32)ctx->args[2]; + ev->events = 0; + if (ctx->args[3] != 0) { + __u32 user_events = 0; + if (bpf_probe_read_user(&user_events, sizeof(user_events), (void *)ctx->args[3]) == 0) { + ev->events = user_events; + } + } bpf_ringbuf_submit(ev, 0); return 0; @@ -4032,22 +4041,23 @@ int handle_sys_exit_epoll_ctl(struct syscall_trace_exit *ctx) { return 0; } -/// sys_enter_epoll_wait is a struct null_event +/// sys_enter_epoll_wait is a struct fd_event SEC("tracepoint/syscalls/sys_enter_epoll_wait") int handle_sys_enter_epoll_wait(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 fd_event *ev = bpf_ringbuf_reserve(&event_map, sizeof(struct fd_event), 0); if (!ev) return 0; - ev->event_type = ENTER_NULL_EVENT; + ev->event_type = ENTER_FD_EVENT; ev->trace_id = SYS_ENTER_EPOLL_WAIT; ev->pid = pid; ev->tid = tid; ev->time = bpf_ktime_get_boot_ns(); + ev->fd = (__s32)ctx->args[0]; bpf_ringbuf_submit(ev, 0); return 0; @@ -4076,22 +4086,23 @@ int handle_sys_exit_epoll_wait(struct syscall_trace_exit *ctx) { return 0; } -/// sys_enter_epoll_pwait is a struct null_event +/// sys_enter_epoll_pwait is a struct fd_event SEC("tracepoint/syscalls/sys_enter_epoll_pwait") int handle_sys_enter_epoll_pwait(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 fd_event *ev = bpf_ringbuf_reserve(&event_map, sizeof(struct fd_event), 0); if (!ev) return 0; - ev->event_type = ENTER_NULL_EVENT; + ev->event_type = ENTER_FD_EVENT; ev->trace_id = SYS_ENTER_EPOLL_PWAIT; ev->pid = pid; ev->tid = tid; ev->time = bpf_ktime_get_boot_ns(); + ev->fd = (__s32)ctx->args[0]; bpf_ringbuf_submit(ev, 0); return 0; @@ -4120,22 +4131,23 @@ int handle_sys_exit_epoll_pwait(struct syscall_trace_exit *ctx) { return 0; } -/// sys_enter_epoll_pwait2 is a struct null_event +/// sys_enter_epoll_pwait2 is a struct fd_event SEC("tracepoint/syscalls/sys_enter_epoll_pwait2") int handle_sys_enter_epoll_pwait2(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 fd_event *ev = bpf_ringbuf_reserve(&event_map, sizeof(struct fd_event), 0); if (!ev) return 0; - ev->event_type = ENTER_NULL_EVENT; + ev->event_type = ENTER_FD_EVENT; ev->trace_id = SYS_ENTER_EPOLL_PWAIT2; ev->pid = pid; ev->tid = tid; ev->time = bpf_ktime_get_boot_ns(); + ev->fd = (__s32)ctx->args[0]; bpf_ringbuf_submit(ev, 0); return 0; diff --git a/internal/c/generated_tracepoints_result.txt b/internal/c/generated_tracepoints_result.txt index 8f2564c..312c412 100644 --- a/internal/c/generated_tracepoints_result.txt +++ b/internal/c/generated_tracepoints_result.txt @@ -34,10 +34,10 @@ sys_enter_dup2 is a struct fd_event sys_enter_dup3 is a struct dup3_event sys_enter_epoll_create is a struct null_event sys_enter_epoll_create1 is a struct null_event -sys_enter_epoll_ctl is a struct fd_event -sys_enter_epoll_pwait is a struct null_event -sys_enter_epoll_pwait2 is a struct null_event -sys_enter_epoll_wait is a struct null_event +sys_enter_epoll_ctl is a struct epoll_ctl_event +sys_enter_epoll_pwait is a struct fd_event +sys_enter_epoll_pwait2 is a struct fd_event +sys_enter_epoll_wait is a struct fd_event sys_enter_eventfd is a struct eventfd_event sys_enter_eventfd2 is a struct eventfd_event sys_enter_execve is a struct path_event diff --git a/internal/c/types.h b/internal/c/types.h index 3e06040..bcc88d2 100644 --- a/internal/c/types.h +++ b/internal/c/types.h @@ -31,6 +31,8 @@ #define EXIT_PIPE_EVENT 26 #define ENTER_EVENTFD_EVENT 27 #define EXIT_EVENTFD_EVENT 28 +#define ENTER_EPOLL_CTL_EVENT 29 +#define EXIT_EPOLL_CTL_EVENT 30 #define UNCLASSIFIED 0 #define READ_CLASSIFIED 1 @@ -181,3 +183,15 @@ struct eventfd_event { __s32 flags; __s64 ret; }; + +struct epoll_ctl_event { + __u32 event_type; + __u32 trace_id; + __u64 time; + __u32 pid; + __u32 tid; + __s32 epfd; + __s32 op; + __s32 fd; + __u32 events; +}; diff --git a/internal/event/interface_assertions.go b/internal/event/interface_assertions.go index 8fa6ae9..15782f8 100644 --- a/internal/event/interface_assertions.go +++ b/internal/event/interface_assertions.go @@ -56,4 +56,7 @@ var ( // *types.EventfdEvent carries eventfd flags and the returned descriptor. _ Event = (*types.EventfdEvent)(nil) + + // *types.EpollCtlEvent carries epoll control metadata (epfd/op/target-fd/events). + _ Event = (*types.EpollCtlEvent)(nil) ) diff --git a/internal/eventloop_exit.go b/internal/eventloop_exit.go index 345424c..1fd6746 100644 --- a/internal/eventloop_exit.go +++ b/internal/eventloop_exit.go @@ -36,6 +36,8 @@ func (e *eventLoop) handleTracepointExit(ep *event.Pair) bool { return e.handlePipeExit(ep, ev) case *types.EventfdEvent: return e.handleEventfdExit(ep, ev) + case *types.EpollCtlEvent: + return e.handleEpollCtlExit(ep, ev) case *types.NullEvent: return e.handleNullExit(ep, ev) case *types.FcntlEvent: @@ -383,6 +385,16 @@ func (e *eventLoop) handleEventfdExit(ep *event.Pair, eventfdEv *types.EventfdEv return true } +func (e *eventLoop) handleEpollCtlExit(ep *event.Pair, epollCtlEv *types.EpollCtlEvent) bool { + ep.File = e.fdState().resolve(epollCtlEv.Epfd, epollCtlEv.Pid) + ep.Comm = e.comm(epollCtlEv.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 new file mode 100644 index 0000000..9d807d5 --- /dev/null +++ b/internal/eventloop_polling_test.go @@ -0,0 +1,83 @@ +package internal + +import ( + "testing" + + "ior/internal/event" + "ior/internal/file" + "ior/internal/globalfilter" + "ior/internal/types" +) + +func TestHandleEpollCtlExitUsesEpollInstanceFd(t *testing.T) { + el := mustNewEventLoop(t, eventLoopConfig{}) + el.fdState().set(41, file.NewFd(41, "anon_inode:[eventpoll]", -1)) + + enter := &types.EpollCtlEvent{ + EventType: types.ENTER_EPOLL_CTL_EVENT, + TraceId: types.SYS_ENTER_EPOLL_CTL, + Time: 100, + Pid: 90, + Tid: 91, + Epfd: 41, + Op: 1, + Fd: 42, + Events: 1, + } + exit := &types.RetEvent{ + EventType: types.EXIT_RET_EVENT, + TraceId: types.SYS_EXIT_EPOLL_CTL, + Time: 200, + Ret: 0, + Pid: 90, + Tid: 91, + } + ep := &event.Pair{EnterEv: enter, ExitEv: exit} + + if ok := el.handleEpollCtlExit(ep, enter); !ok { + t.Fatal("handleEpollCtlExit returned false") + } + if ep.File == nil || ep.File.FD() != 41 { + t.Fatalf("expected resolved epoll instance fd 41, got file=%v", ep.File) + } +} + +func TestHandleEpollCtlExitAppliesPairFilter(t *testing.T) { + el := mustNewEventLoop(t, eventLoopConfig{ + filter: globalfilter.Filter{ + Syscall: &globalfilter.StringFilter{Pattern: "openat"}, + }, + }) + + enter := &types.EpollCtlEvent{ + EventType: types.ENTER_EPOLL_CTL_EVENT, + TraceId: types.SYS_ENTER_EPOLL_CTL, + Time: 100, + Pid: 92, + Tid: 93, + Epfd: 51, + Op: 1, + Fd: 52, + Events: 1, + } + exit := &types.RetEvent{ + EventType: types.EXIT_RET_EVENT, + TraceId: types.SYS_EXIT_EPOLL_CTL, + Time: 200, + Ret: 0, + Pid: 92, + Tid: 93, + } + ep := &event.Pair{EnterEv: enter, ExitEv: exit} + + if ok := el.handleEpollCtlExit(ep, enter); ok { + t.Fatal("handleEpollCtlExit should reject pair due to filter mismatch") + } +} + +func TestInitRawHandlersRegistersPollingEvents(t *testing.T) { + el := mustNewEventLoop(t, eventLoopConfig{}) + if _, ok := el.rawHandlers[types.ENTER_EPOLL_CTL_EVENT]; !ok { + t.Fatal("ENTER_EPOLL_CTL_EVENT handler is not registered") + } +} diff --git a/internal/eventloop_runtime.go b/internal/eventloop_runtime.go index cce7299..135f7c1 100644 --- a/internal/eventloop_runtime.go +++ b/internal/eventloop_runtime.go @@ -148,6 +148,7 @@ func (e *eventLoop) initRawHandlers() { e.registerMiscHandlers() e.registerSocketHandlers() e.registerIPCHandlers() + e.registerPollingHandlers() } // registerOpenHandlers wires enter/exit handlers for open-family events. @@ -333,6 +334,16 @@ func (e *eventLoop) registerIPCHandlers() { } } +func (e *eventLoop) registerPollingHandlers() { + e.rawHandlers[types.ENTER_EPOLL_CTL_EVENT] = func(raw []byte, _ chan<- *event.Pair) { + epollCtlEv, ok := decodeRawEvent(e, types.ENTER_EPOLL_CTL_EVENT, raw, types.NewEpollCtlEventFast) + if !ok { + return + } + e.tracepointEntered(epollCtlEv) + } +} + func decodeRawEvent[T any](e *eventLoop, eventType types.EventType, raw []byte, decode func([]byte) *T) (*T, bool) { decoded := decode(raw) if decoded == nil { diff --git a/internal/generate/bpfhandler.go b/internal/generate/bpfhandler.go index 1a6b210..944025a 100644 --- a/internal/generate/bpfhandler.go +++ b/internal/generate/bpfhandler.go @@ -83,6 +83,8 @@ func generateExtra(tp GeneratedTracepoint, isEnter bool) string { return generateExtraPipe(f, isEnter) case KindEventfd: return generateExtraEventfd(f, isEnter) + case KindEpollCtl: + return generateExtraEpollCtl() case KindOpen: return generateExtraOpen(f) case KindPathname: @@ -199,6 +201,10 @@ func generateExtraEventfd(f *Format, isEnter bool) string { return " __s32 flags = 0;\n __s32 *pending = bpf_map_lookup_elem(&eventfd_flags_map, &tid);\n if (pending) {\n flags = *pending;\n bpf_map_delete_elem(&eventfd_flags_map, &tid);\n }\n ev->flags = flags;\n ev->ret = ctx->ret;\n" } +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" +} + // 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 0bc00b3..21a8bf9 100644 --- a/internal/generate/classify.go +++ b/internal/generate/classify.go @@ -20,6 +20,7 @@ const ( KindAccept KindPipe KindEventfd + KindEpollCtl ) type RetClassification string @@ -130,6 +131,14 @@ func classifyNameOnly(name string) (ClassificationResult, bool) { return ClassificationResult{Kind: KindFd}, true case "sys_enter_setsockopt": return ClassificationResult{Kind: KindFd}, true + case "sys_enter_epoll_wait": + return ClassificationResult{Kind: KindFd}, true + case "sys_enter_epoll_pwait": + return ClassificationResult{Kind: KindFd}, true + case "sys_enter_epoll_pwait2": + return ClassificationResult{Kind: KindFd}, true + case "sys_enter_epoll_ctl": + return ClassificationResult{Kind: KindEpollCtl}, 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 09989b8..74d4143 100644 --- a/internal/generate/classify_test.go +++ b/internal/generate/classify_test.go @@ -369,6 +369,34 @@ func TestClassifyExitEventfd2(t *testing.T) { } } +func TestClassifyEpollCtl(t *testing.T) { + r := classifyFromData(t, FormatEpollCtl) + if r.Kind != KindEpollCtl { + t.Errorf("epoll_ctl: got kind %d, want KindEpollCtl", r.Kind) + } +} + +func TestClassifyEpollWait(t *testing.T) { + r := classifyFromData(t, FormatEpollWait) + if r.Kind != KindFd { + t.Errorf("epoll_wait: got kind %d, want KindFd", r.Kind) + } +} + +func TestClassifyEpollPwait(t *testing.T) { + r := classifyFromData(t, FormatEpollPwait) + if r.Kind != KindFd { + t.Errorf("epoll_pwait: got kind %d, want KindFd", r.Kind) + } +} + +func TestClassifyEpollPwait2(t *testing.T) { + r := classifyFromData(t, FormatEpollPwait2) + if r.Kind != KindFd { + t.Errorf("epoll_pwait2: got kind %d, want KindFd", r.Kind) + } +} + func TestClassifyKillRequiresGenerationFallback(t *testing.T) { r := classifyFromData(t, FormatKill) if r.Kind != KindNone { @@ -413,6 +441,10 @@ func TestClassifySyscallPairAccepted(t *testing.T) { {"pipe2", FormatPipe2, FormatExitPipe2, KindPipe}, {"eventfd", FormatEventfd, FormatExitEventfd, KindEventfd}, {"eventfd2", FormatEventfd2, FormatExitEventfd2, KindEventfd}, + {"epoll_ctl", FormatEpollCtl, FormatExitEpollCtl, KindEpollCtl}, + {"epoll_wait", FormatEpollWait, FormatExitEpollWait, KindFd}, + {"epoll_pwait", FormatEpollPwait, FormatExitEpollPwait, KindFd}, + {"epoll_pwait2", FormatEpollPwait2, FormatExitEpollPwait2, KindFd}, {"kill", FormatKill, FormatExitKill, KindNull}, } @@ -444,6 +476,8 @@ func TestClassifySyscallPairEmitsAllFamilies(t *testing.T) { {"pipe2", FormatPipe2, FormatExitPipe2, FamilyIPC}, {"eventfd", FormatEventfd, FormatExitEventfd, FamilyIPC}, {"eventfd2", FormatEventfd2, FormatExitEventfd2, FamilyIPC}, + {"epoll_ctl", FormatEpollCtl, FormatExitEpollCtl, FamilyPolling}, + {"epoll_wait", FormatEpollWait, FormatExitEpollWait, FamilyPolling}, {"kill", FormatKill, FormatExitKill, FamilySignals}, } diff --git a/internal/generate/codegen_test.go b/internal/generate/codegen_test.go index 1c99314..23b5a2a 100644 --- a/internal/generate/codegen_test.go +++ b/internal/generate/codegen_test.go @@ -255,6 +255,29 @@ func TestGenerateEventfdHandler(t *testing.T) { requireContains(t, output, "ev->ret = ctx->ret;") } +func TestGenerateEpollCtlHandler(t *testing.T) { + output := generateFromPair(t, FormatEpollCtl, FormatExitEpollCtl) + + requireContains(t, output, "struct epoll_ctl_event *ev") + requireContains(t, output, "ev->event_type = ENTER_EPOLL_CTL_EVENT;") + requireContains(t, output, "ev->epfd = (__s32)ctx->args[0];") + requireContains(t, output, "ev->op = (__s32)ctx->args[1];") + requireContains(t, output, "ev->fd = (__s32)ctx->args[2];") + requireContains(t, output, "ev->events = 0;") + requireContains(t, output, "if (ctx->args[3] != 0) {") + requireContains(t, output, "bpf_probe_read_user(&user_events, sizeof(user_events), (void *)ctx->args[3])") + requireContains(t, output, "ev->event_type = EXIT_RET_EVENT;") +} + +func TestGenerateEpollWaitHandlerUsesEpollFd(t *testing.T) { + output := generateFromPair(t, FormatEpollWait, FormatExitEpollWait) + + requireContains(t, output, "struct fd_event *ev") + requireContains(t, output, "ev->event_type = ENTER_FD_EVENT;") + requireContains(t, output, "ev->trace_id = SYS_ENTER_EPOLL_WAIT;") + requireContains(t, output, "ev->fd = (__s32)ctx->args[0];") +} + func TestGenerateNameToHandleAtHandler(t *testing.T) { output := generateFromPair(t, FormatNameToHandleAt, FormatExitNameToHandleAt) @@ -367,6 +390,7 @@ func TestGenerateAllEventTypes(t *testing.T) { {KindAccept, "ENTER_ACCEPT_EVENT", "EXIT_ACCEPT_EVENT"}, {KindPipe, "ENTER_PIPE_EVENT", "EXIT_PIPE_EVENT"}, {KindEventfd, "ENTER_EVENTFD_EVENT", "EXIT_EVENTFD_EVENT"}, + {KindEpollCtl, "ENTER_EPOLL_CTL_EVENT", "EXIT_EPOLL_CTL_EVENT"}, } for _, tt := range tests { @@ -398,6 +422,7 @@ func TestEventStructNames(t *testing.T) { {KindAccept, "accept_event"}, {KindPipe, "pipe_event"}, {KindEventfd, "eventfd_event"}, + {KindEpollCtl, "epoll_ctl_event"}, } for _, tt := range tests { @@ -416,7 +441,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} + accepted := []TracepointKind{KindFd, KindOpen, KindPathname, KindName, KindFcntl, KindNull, KindDup3, KindOpenByHandleAt, KindSocket, KindSocketpair, KindAccept, KindPipe, KindEventfd, KindEpollCtl} 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 17f7542..07f9d57 100644 --- a/internal/generate/kindregistry.go +++ b/internal/generate/kindregistry.go @@ -30,6 +30,7 @@ var kindRegistry = map[TracepointKind]kindMeta{ KindAccept: {structName: "accept_event", enterAccepted: true}, KindPipe: {structName: "pipe_event", enterAccepted: true}, KindEventfd: {structName: "eventfd_event", enterAccepted: true}, + KindEpollCtl: {structName: "epoll_ctl_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 b34f98d..9b9496b 100644 --- a/internal/generate/testdata.go +++ b/internal/generate/testdata.go @@ -1058,3 +1058,131 @@ format: print fmt: "0x%lx", REC->ret ` + +const FormatEpollCtl = `name: sys_enter_epoll_ctl +ID: 1079 +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 epfd; offset:16; size:8; signed:0; + field:int op; offset:24; size:8; signed:0; + field:int fd; offset:32; size:8; signed:0; + field:struct epoll_event * event; offset:40; size:8; signed:0; + +print fmt: "epfd: 0x%08lx, op: 0x%08lx, fd: 0x%08lx, event: 0x%08lx", ((unsigned long)(REC->epfd)), ((unsigned long)(REC->op)), ((unsigned long)(REC->fd)), ((unsigned long)(REC->event)) +` + +const FormatExitEpollCtl = `name: sys_exit_epoll_ctl +ID: 1078 +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; + +print fmt: "0x%lx", REC->ret +` + +const FormatEpollWait = `name: sys_enter_epoll_wait +ID: 1077 +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 epfd; offset:16; size:8; signed:0; + field:struct epoll_event * events; offset:24; size:8; signed:0; + field:int maxevents; offset:32; size:8; signed:0; + field:int timeout; offset:40; size:8; signed:0; + +print fmt: "epfd: 0x%08lx, events: 0x%08lx, maxevents: 0x%08lx, timeout: 0x%08lx", ((unsigned long)(REC->epfd)), ((unsigned long)(REC->events)), ((unsigned long)(REC->maxevents)), ((unsigned long)(REC->timeout)) +` + +const FormatExitEpollWait = `name: sys_exit_epoll_wait +ID: 1076 +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; + +print fmt: "0x%lx", REC->ret +` + +const FormatEpollPwait = `name: sys_enter_epoll_pwait +ID: 1075 +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 epfd; offset:16; size:8; signed:0; + field:struct epoll_event * events; offset:24; size:8; signed:0; + field:int maxevents; offset:32; size:8; signed:0; + field:int timeout; offset:40; size:8; signed:0; + field:sigset_t * sigmask; offset:48; size:8; signed:0; + field:size_t sigsetsize; offset:56; size:8; signed:0; + +print fmt: "epfd: 0x%08lx, events: 0x%08lx, maxevents: 0x%08lx, timeout: 0x%08lx, sigmask: 0x%08lx, sigsetsize: 0x%08lx", ((unsigned long)(REC->epfd)), ((unsigned long)(REC->events)), ((unsigned long)(REC->maxevents)), ((unsigned long)(REC->timeout)), ((unsigned long)(REC->sigmask)), ((unsigned long)(REC->sigsetsize)) +` + +const FormatExitEpollPwait = `name: sys_exit_epoll_pwait +ID: 1074 +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; + +print fmt: "0x%lx", REC->ret +` + +const FormatEpollPwait2 = `name: sys_enter_epoll_pwait2 +ID: 1073 +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 epfd; offset:16; size:8; signed:0; + field:struct epoll_event * events; offset:24; size:8; signed:0; + field:int maxevents; offset:32; size:8; signed:0; + field:const struct __kernel_timespec * timeout; offset:40; size:8; signed:0; + field:const sigset_t * sigmask; offset:48; size:8; signed:0; + field:size_t sigsetsize; offset:56; size:8; signed:0; + +print fmt: "epfd: 0x%08lx, events: 0x%08lx, maxevents: 0x%08lx, timeout: 0x%08lx, sigmask: 0x%08lx, sigsetsize: 0x%08lx", ((unsigned long)(REC->epfd)), ((unsigned long)(REC->events)), ((unsigned long)(REC->maxevents)), ((unsigned long)(REC->timeout)), ((unsigned long)(REC->sigmask)), ((unsigned long)(REC->sigsetsize)) +` + +const FormatExitEpollPwait2 = `name: sys_exit_epoll_pwait2 +ID: 1072 +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; + +print fmt: "0x%lx", REC->ret +` diff --git a/internal/streamrow/row_test.go b/internal/streamrow/row_test.go index 304aa12..53f96c5 100644 --- a/internal/streamrow/row_test.go +++ b/internal/streamrow/row_test.go @@ -99,6 +99,25 @@ func TestNewWarningPopu |
