summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--cmd/ioworkload/scenario_polling.go121
-rw-r--r--cmd/ioworkload/scenarios.go1
-rw-r--r--integrationtests/polling_test.go64
-rw-r--r--internal/c/generated_tracepoints.c36
-rw-r--r--internal/c/generated_tracepoints_result.txt8
-rw-r--r--internal/c/types.h14
-rw-r--r--internal/event/interface_assertions.go3
-rw-r--r--internal/eventloop_exit.go12
-rw-r--r--internal/eventloop_polling_test.go83
-rw-r--r--internal/eventloop_runtime.go11
-rw-r--r--internal/generate/bpfhandler.go6
-rw-r--r--internal/generate/classify.go9
-rw-r--r--internal/generate/classify_test.go34
-rw-r--r--internal/generate/codegen_test.go27
-rw-r--r--internal/generate/kindregistry.go1
-rw-r--r--internal/generate/testdata.go128
-rw-r--r--internal/streamrow/row_test.go19
-rw-r--r--internal/types/fastdecode.go21
-rw-r--r--internal/types/fastdecode_test.go14
-rw-r--r--internal/types/generated_types.go73
-rw-r--r--internal/types/types_test.go29
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