summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPaul Buetow <paul@buetow.org>2026-05-22 09:58:59 +0300
committerPaul Buetow <paul@buetow.org>2026-05-22 09:58:59 +0300
commit88b9ea74d880207c8af46b3defc03a0777293a83 (patch)
tree50f3076bd93bd556f8f01ba7759ef872c36617d9
parentb0914837b96afb99971e600d6c46e55875f04854 (diff)
4c extract event kind runtime registry
-rw-r--r--internal/eventloop.go9
-rw-r--r--internal/eventloop_error_handling_test.go24
-rw-r--r--internal/eventloop_exit.go198
-rw-r--r--internal/eventloop_kinds.go141
-rw-r--r--internal/eventloop_kinds_test.go158
-rw-r--r--internal/eventloop_runtime.go296
6 files changed, 390 insertions, 436 deletions
diff --git a/internal/eventloop.go b/internal/eventloop.go
index 71c7982..a29d99f 100644
--- a/internal/eventloop.go
+++ b/internal/eventloop.go
@@ -62,6 +62,7 @@ type eventLoop struct {
commResolver *commResolver
outputFormatter // pair-emission and warning-notification callbacks (embedded collaborator)
rawHandlers map[types.EventType]rawEventHandler
+ exitHandlers map[types.EventType]runtimeExitHandler
cfg eventLoopConfig
aggregateSink syscallAggregateSink
aggregateSrc syscallAggregateSource
@@ -110,15 +111,17 @@ func newEventLoop(cfg eventLoopConfig) (*eventLoop, error) {
outputFormatter: outputFormatter{
printCb: func(ep *event.Pair) { fmt.Println(ep); ep.Recycle() },
},
- rawHandlers: make(map[types.EventType]rawEventHandler),
- cfg: cfg,
- done: make(chan struct{}),
+ rawHandlers: make(map[types.EventType]rawEventHandler),
+ exitHandlers: make(map[types.EventType]runtimeExitHandler),
+ cfg: cfg,
+ done: make(chan struct{}),
}
if el.cfg.aggregateDrainEvery <= 0 {
el.cfg.aggregateDrainEvery = defaultAggregateDrainEvery
}
el.SetFilter(cfg.filter)
el.initRawHandlers()
+ el.initRuntimeEventKinds()
el.configureOutputCallback()
el.seedTrackedPidComm()
return el, nil
diff --git a/internal/eventloop_error_handling_test.go b/internal/eventloop_error_handling_test.go
index 0851ff6..4ac5525 100644
--- a/internal/eventloop_error_handling_test.go
+++ b/internal/eventloop_error_handling_test.go
@@ -146,28 +146,8 @@ func TestProcessRawEventMalformedKnownTypeDoesNotPanicAndNotifies(t *testing.T)
}
}
-func TestDecodeRawEventMalformedNotifies(t *testing.T) {
- el := mustNewEventLoop(t, eventLoopConfig{})
- warnings := make(chan string, 1)
- el.warningCb = func(message string) { warnings <- message }
-
- decoded, ok := decodeRawEvent(el, types.ENTER_OPEN_EVENT, []byte{byte(types.ENTER_OPEN_EVENT)}, types.NewOpenEventFast)
- if ok || decoded != nil {
- t.Fatalf("expected malformed raw event decode to fail, got ok=%v decoded=%v", ok, decoded)
- }
-
- select {
- case msg := <-warnings:
- if msg == "" {
- t.Fatalf("expected non-empty warning message")
- }
- default:
- t.Fatalf("expected warning notification")
- }
-}
-
-// unknownEvent is a minimal event.Event stub used to exercise the default
-// branch of handleTracepointExit (an enter-event type not covered by any case).
+// unknownEvent is a minimal event.Event stub used to exercise the missing
+// runtime-kind path in handleTracepointExit.
type unknownEvent struct{}
func (unknownEvent) String() string { return "unknownEvent" }
diff --git a/internal/eventloop_exit.go b/internal/eventloop_exit.go
index 1212de6..cb11074 100644
--- a/internal/eventloop_exit.go
+++ b/internal/eventloop_exit.go
@@ -10,58 +10,41 @@ import (
"ior/internal/types"
)
-// handleTracepointExit routes a completed enter/exit pair to the appropriate
-// handler using a type switch, avoiding reflection on the hot event path.
+func (e *eventLoop) initRuntimeEventKinds() {
+ if e.exitHandlers == nil {
+ e.exitHandlers = make(map[types.EventType]runtimeExitHandler)
+ }
+ if len(e.exitHandlers) != 0 {
+ return
+ }
+ for _, kind := range runtimeEventKinds() {
+ e.exitHandlers[kind.enterEventType] = kind.exit
+ }
+}
+
+// handleTracepointExit routes a completed enter/exit pair to the runtime
+// handler registered for the enter event kind.
func (e *eventLoop) handleTracepointExit(ep *event.Pair) bool {
- switch ev := ep.EnterEv.(type) {
- case *types.OpenEvent:
- return e.handleOpenExit(ep, ev)
- case *types.ExecEvent:
- return e.handleExecExit(ep, ev)
- case *types.NameEvent:
- return e.handleNameExit(ep, ev)
- case *types.PathEvent:
- return e.handlePathExit(ep, ev)
- case *types.FdEvent:
- return e.handleFdExit(ep, ev)
- case *types.Dup3Event:
- return e.handleDup3Exit(ep, ev)
- case *types.OpenByHandleAtEvent:
- return e.handleOpenByHandleAtExit(ep, ev)
- case *types.SocketEvent:
- return e.handleSocketExit(ep, ev)
- case *types.SocketpairEvent:
- return e.handleSocketpairExit(ep, ev)
- case *types.AcceptEvent:
- return e.handleAcceptExit(ep, ev)
- case *types.PipeEvent:
- return e.handlePipeExit(ep, ev)
- case *types.EventfdEvent:
- return e.handleEventfdExit(ep, ev)
- case *types.EpollCtlEvent:
- return e.handleEpollCtlExit(ep, ev)
- case *types.PollEvent:
- return e.handlePollExit(ep, ev)
- case *types.TwoFdEvent:
- return e.handleTwoFdExit(ep, ev)
- case *types.MemEvent:
- return e.handleMemExit(ep, ev)
- case *types.SleepEvent:
- return e.handleSleepExit(ep, ev)
- case *types.KeyctlEvent:
- return e.handleKeyctlExit(ep, ev)
- case *types.PtraceEvent:
- return e.handlePtraceExit(ep, ev)
- case *types.PerfOpenEvent:
- return e.handlePerfOpenExit(ep, ev)
- case *types.NullEvent:
- return e.handleNullExit(ep, ev)
- case *types.FcntlEvent:
- return e.handleFcntlExit(ep, ev)
- default:
+ e.initRuntimeEventKinds()
+ eventType, ok := eventTypeForRuntimeEvent(ep.EnterEv)
+ if !ok {
+ e.recyclePair(ep, "Dropped malformed enter event")
+ return false
+ }
+ handler, ok := e.exitHandlers[eventType]
+ if !ok {
e.recyclePair(ep, "Dropped malformed enter event")
return false
}
+ return handler(e, ep)
+}
+
+func eventTypeForRuntimeEvent(ev event.Event) (types.EventType, bool) {
+ typed, ok := ev.(interface{ GetEventType() types.EventType })
+ if !ok {
+ return 0, false
+ }
+ return typed.GetEventType(), true
}
func (e *eventLoop) handleOpenExit(ep *event.Pair, openEv *types.OpenEvent) bool {
@@ -94,11 +77,7 @@ func (e *eventLoop) handleExecExit(ep *event.Pair, execEv *types.ExecEvent) bool
ep.Comm = comm
ep.File = file.NewPathname(execEv.Filename[:])
e.setCachedComm(execEv.Tid, comm)
- if !e.Filter().MatchPair(ep) {
- ep.Recycle()
- return false
- }
- return true
+ return e.finishPair(ep)
}
func (e *eventLoop) handleNameExit(ep *event.Pair, nameEv *types.NameEvent) bool {
@@ -146,8 +125,7 @@ func (e *eventLoop) handleFdExit(ep *event.Pair, fdEv *types.FdEvent) bool {
ep.File = e.fdState().resolve(fd, fdEv.Pid)
e.applyFdCloseState(ep, fd, fdEv.Pid)
ep.Comm = e.comm(fdEv.GetTid())
- if !e.Filter().MatchPair(ep) {
- ep.Recycle()
+ if !e.finishPair(ep) {
return false
}
if ok := e.applyFdTransferOp(ep, fdEv); !ok {
@@ -209,8 +187,7 @@ func (e *eventLoop) handleDup3Exit(ep *event.Pair, dup3Ev *types.Dup3Event) bool
fd := int32(dup3Ev.Fd)
ep.File = e.fdState().resolve(fd, dup3Ev.Pid)
ep.Comm = e.comm(dup3Ev.GetTid())
- if !e.Filter().MatchPair(ep) {
- ep.Recycle()
+ if !e.finishPair(ep) {
return false
}
@@ -273,11 +250,7 @@ func (e *eventLoop) handleSocketExit(ep *event.Pair, socketEv *types.SocketEvent
ep.File = fdFile
}
ep.Comm = e.comm(socketEv.GetTid())
- if !e.Filter().MatchPair(ep) {
- ep.Recycle()
- return false
- }
- return true
+ return e.finishPair(ep)
}
func (e *eventLoop) handleSocketpairExit(ep *event.Pair, socketpairEv *types.SocketpairEvent) bool {
@@ -315,11 +288,7 @@ func (e *eventLoop) handleSocketpairExit(ep *event.Pair, socketpairEv *types.Soc
}
}
ep.Comm = e.comm(socketpairEv.GetTid())
- if !e.Filter().MatchPair(ep) {
- ep.Recycle()
- return false
- }
- return true
+ return e.finishPair(ep)
}
func (e *eventLoop) handleAcceptExit(ep *event.Pair, acceptEv *types.AcceptEvent) bool {
@@ -338,11 +307,7 @@ func (e *eventLoop) handleAcceptExit(ep *event.Pair, acceptEv *types.AcceptEvent
ep.File = listening
}
ep.Comm = e.comm(acceptEv.GetTid())
- if !e.Filter().MatchPair(ep) {
- ep.Recycle()
- return false
- }
- return true
+ return e.finishPair(ep)
}
func socketDescriptorName(family, typ, protocol int32) string {
@@ -386,11 +351,7 @@ func (e *eventLoop) handlePipeExit(ep *event.Pair, pipeEv *types.PipeEvent) bool
}
}
ep.Comm = e.comm(pipeEv.GetTid())
- if !e.Filter().MatchPair(ep) {
- ep.Recycle()
- return false
- }
- return true
+ return e.finishPair(ep)
}
func (e *eventLoop) handleEventfdExit(ep *event.Pair, eventfdEv *types.EventfdEvent) bool {
@@ -410,76 +371,37 @@ func (e *eventLoop) handleEventfdExit(ep *event.Pair, eventfdEv *types.EventfdEv
ep.File = fdFile
}
ep.Comm = e.comm(eventfdEv.GetTid())
- if !e.Filter().MatchPair(ep) {
- ep.Recycle()
- return false
- }
- return true
+ return e.finishPair(ep)
}
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
+ return e.finishPairForTid(ep, epollCtlEv.GetTid())
}
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
+ return e.finishPairForTid(ep, pollEv.GetTid())
}
func (e *eventLoop) handleTwoFdExit(ep *event.Pair, twoFdEv *types.TwoFdEvent) bool {
ep.File = e.fdState().resolve(twoFdEv.FdA, twoFdEv.Pid)
- ep.Comm = e.comm(twoFdEv.GetTid())
- if !e.Filter().MatchPair(ep) {
- ep.Recycle()
- return false
- }
- return true
+ return e.finishPairForTid(ep, twoFdEv.GetTid())
}
func (e *eventLoop) handleMemExit(ep *event.Pair, memEv *types.MemEvent) bool {
- ep.Comm = e.comm(memEv.GetTid())
- if !e.Filter().MatchPair(ep) {
- ep.Recycle()
- return false
- }
- return true
+ return e.finishPairForTid(ep, memEv.GetTid())
}
func (e *eventLoop) handleSleepExit(ep *event.Pair, sleepEv *types.SleepEvent) bool {
- ep.Comm = e.comm(sleepEv.GetTid())
- if !e.Filter().MatchPair(ep) {
- ep.Recycle()
- return false
- }
- return true
+ return e.finishPairForTid(ep, sleepEv.GetTid())
}
func (e *eventLoop) handleKeyctlExit(ep *event.Pair, keyctlEv *types.KeyctlEvent) bool {
- ep.Comm = e.comm(keyctlEv.GetTid())
- if !e.Filter().MatchPair(ep) {
- ep.Recycle()
- return false
- }
- return true
+ return e.finishPairForTid(ep, keyctlEv.GetTid())
}
func (e *eventLoop) handlePtraceExit(ep *event.Pair, ptraceEv *types.PtraceEvent) bool {
- ep.Comm = e.comm(ptraceEv.GetTid())
- if !e.Filter().MatchPair(ep) {
- ep.Recycle()
- return false
- }
- return true
+ return e.finishPairForTid(ep, ptraceEv.GetTid())
}
func (e *eventLoop) handlePerfOpenExit(ep *event.Pair, perfOpenEv *types.PerfOpenEvent) bool {
@@ -495,11 +417,7 @@ func (e *eventLoop) handlePerfOpenExit(ep *event.Pair, perfOpenEv *types.PerfOpe
ep.File = fdFile
}
ep.Comm = e.comm(perfOpenEv.GetTid())
- if !e.Filter().MatchPair(ep) {
- ep.Recycle()
- return false
- }
- return true
+ return e.finishPair(ep)
}
func pipeDescriptorName(flags, fd0, fd1 int32) string {
@@ -576,19 +494,14 @@ func (e *eventLoop) handleNullExit(ep *event.Pair, nullEv *types.NullEvent) bool
}
}
ep.Comm = e.comm(nullEv.GetTid())
- if !e.Filter().MatchPair(ep) {
- ep.Recycle()
- return false
- }
- return true
+ return e.finishPair(ep)
}
func (e *eventLoop) handleFcntlExit(ep *event.Pair, fcntlEv *types.FcntlEvent) bool {
ep.Comm = e.comm(fcntlEv.GetTid())
fd := int32(fcntlEv.Fd)
ep.File = e.fdState().resolve(fd, fcntlEv.Pid)
- if !e.Filter().MatchPair(ep) {
- ep.Recycle()
+ if !e.finishPair(ep) {
return false
}
@@ -634,6 +547,19 @@ func (e *eventLoop) registerDup(fdFile *file.FdFile, newFd int32, extraFlags int
e.fdState().set(newFd, duppedFdFile)
}
+func (e *eventLoop) finishPairForTid(ep *event.Pair, tid uint32) bool {
+ ep.Comm = e.comm(tid)
+ return e.finishPair(ep)
+}
+
+func (e *eventLoop) finishPair(ep *event.Pair) bool {
+ if e.Filter().MatchPair(ep) {
+ return true
+ }
+ ep.Recycle()
+ return false
+}
+
// recyclePair notifies about the problem described by warning, then returns ep
// to the pool. It is a convenience helper used throughout the exit handlers to
// keep the error path concise.
diff --git a/internal/eventloop_kinds.go b/internal/eventloop_kinds.go
new file mode 100644
index 0000000..e7d9f4a
--- /dev/null
+++ b/internal/eventloop_kinds.go
@@ -0,0 +1,141 @@
+package internal
+
+import (
+ "ior/internal/event"
+ "ior/internal/globalfilter"
+ "ior/internal/types"
+)
+
+type runtimeEventDecoder func(raw []byte) event.Event
+type runtimeExitHandler func(e *eventLoop, ep *event.Pair) bool
+type runtimeEnterFilter func(filter globalfilter.Filter, ev event.Event) bool
+
+type runtimeEventKind struct {
+ enterEventType types.EventType
+ exit runtimeExitHandler
+}
+
+type rawEventDirection uint8
+
+const (
+ rawEnterEvent rawEventDirection = iota
+ rawExitEvent
+)
+
+type rawRuntimeEvent struct {
+ eventType types.EventType
+ direction rawEventDirection
+ decode runtimeEventDecoder
+ filter runtimeEnterFilter
+}
+
+type eventPtr[T any] interface {
+ *T
+ event.Event
+}
+
+func rawDecoder[T any, P eventPtr[T]](decode func([]byte) P) runtimeEventDecoder {
+ return func(raw []byte) event.Event {
+ ev := decode(raw)
+ if ev == nil {
+ return nil
+ }
+ return ev
+ }
+}
+
+func typedRuntimeExit[T any, P eventPtr[T]](handle func(*eventLoop, *event.Pair, P) bool) runtimeExitHandler {
+ return func(e *eventLoop, ep *event.Pair) bool {
+ ev, ok := ep.EnterEv.(P)
+ if !ok {
+ e.recyclePair(ep, "Dropped malformed enter event")
+ return false
+ }
+ return handle(e, ep, ev)
+ }
+}
+
+func runtimeEventKinds() []runtimeEventKind {
+ return []runtimeEventKind{
+ {enterEventType: types.ENTER_OPEN_EVENT, exit: typedRuntimeExit((*eventLoop).handleOpenExit)},
+ {enterEventType: types.ENTER_EXEC_EVENT, exit: typedRuntimeExit((*eventLoop).handleExecExit)},
+ {enterEventType: types.ENTER_NAME_EVENT, exit: typedRuntimeExit((*eventLoop).handleNameExit)},
+ {enterEventType: types.ENTER_PATH_EVENT, exit: typedRuntimeExit((*eventLoop).handlePathExit)},
+ {enterEventType: types.ENTER_FD_EVENT, exit: typedRuntimeExit((*eventLoop).handleFdExit)},
+ {enterEventType: types.ENTER_DUP3_EVENT, exit: typedRuntimeExit((*eventLoop).handleDup3Exit)},
+ {enterEventType: types.ENTER_OPEN_BY_HANDLE_AT_EVENT, exit: typedRuntimeExit((*eventLoop).handleOpenByHandleAtExit)},
+ {enterEventType: types.ENTER_SOCKET_EVENT, exit: typedRuntimeExit((*eventLoop).handleSocketExit)},
+ {enterEventType: types.ENTER_SOCKETPAIR_EVENT, exit: typedRuntimeExit((*eventLoop).handleSocketpairExit)},
+ {enterEventType: types.ENTER_ACCEPT_EVENT, exit: typedRuntimeExit((*eventLoop).handleAcceptExit)},
+ {enterEventType: types.ENTER_PIPE_EVENT, exit: typedRuntimeExit((*eventLoop).handlePipeExit)},
+ {enterEventType: types.ENTER_EVENTFD_EVENT, exit: typedRuntimeExit((*eventLoop).handleEventfdExit)},
+ {enterEventType: types.ENTER_EPOLL_CTL_EVENT, exit: typedRuntimeExit((*eventLoop).handleEpollCtlExit)},
+ {enterEventType: types.ENTER_POLL_EVENT, exit: typedRuntimeExit((*eventLoop).handlePollExit)},
+ {enterEventType: types.ENTER_TWO_FD_EVENT, exit: typedRuntimeExit((*eventLoop).handleTwoFdExit)},
+ {enterEventType: types.ENTER_MEM_EVENT, exit: typedRuntimeExit((*eventLoop).handleMemExit)},
+ {enterEventType: types.ENTER_SLEEP_EVENT, exit: typedRuntimeExit((*eventLoop).handleSleepExit)},
+ {enterEventType: types.ENTER_KEYCTL_EVENT, exit: typedRuntimeExit((*eventLoop).handleKeyctlExit)},
+ {enterEventType: types.ENTER_PTRACE_EVENT, exit: typedRuntimeExit((*eventLoop).handlePtraceExit)},
+ {enterEventType: types.ENTER_PERF_OPEN_EVENT, exit: typedRuntimeExit((*eventLoop).handlePerfOpenExit)},
+ {enterEventType: types.ENTER_NULL_EVENT, exit: typedRuntimeExit((*eventLoop).handleNullExit)},
+ {enterEventType: types.ENTER_FCNTL_EVENT, exit: typedRuntimeExit((*eventLoop).handleFcntlExit)},
+ }
+}
+
+func rawRuntimeEvents() []rawRuntimeEvent {
+ return []rawRuntimeEvent{
+ enterRaw(types.ENTER_OPEN_EVENT, rawDecoder[types.OpenEvent](types.NewOpenEventFast), matchRawOpenEvent),
+ exitRaw(types.EXIT_OPEN_EVENT, rawDecoder[types.RetEvent](types.NewRetEventFast)),
+ enterRaw(types.ENTER_FD_EVENT, rawDecoder[types.FdEvent](types.NewFdEventFast), nil),
+ exitRaw(types.EXIT_FD_EVENT, rawDecoder[types.FdEvent](types.NewFdEventFast)),
+ enterRaw(types.ENTER_NULL_EVENT, rawDecoder[types.NullEvent](types.NewNullEventFast), nil),
+ exitRaw(types.EXIT_NULL_EVENT, rawDecoder[types.NullEvent](types.NewNullEventFast)),
+ exitRaw(types.EXIT_RET_EVENT, rawDecoder[types.RetEvent](types.NewRetEventFast)),
+ enterRaw(types.ENTER_NAME_EVENT, rawDecoder[types.NameEvent](types.NewNameEventFast), matchRawNameEvent),
+ enterRaw(types.ENTER_PATH_EVENT, rawDecoder[types.PathEvent](types.NewPathEventFast), matchRawPathEvent),
+ enterRaw(types.ENTER_FCNTL_EVENT, rawDecoder[types.FcntlEvent](types.NewFcntlEventFast), nil),
+ enterRaw(types.ENTER_OPEN_BY_HANDLE_AT_EVENT, rawDecoder[types.OpenByHandleAtEvent](types.NewOpenByHandleAtEventFast), nil),
+ enterRaw(types.ENTER_DUP3_EVENT, rawDecoder[types.Dup3Event](types.NewDup3EventFast), nil),
+ enterRaw(types.ENTER_SOCKET_EVENT, rawDecoder[types.SocketEvent](types.NewSocketEventFast), nil),
+ enterRaw(types.ENTER_SOCKETPAIR_EVENT, rawDecoder[types.SocketpairEvent](types.NewSocketpairEventFast), nil),
+ exitRaw(types.EXIT_SOCKETPAIR_EVENT, rawDecoder[types.SocketpairEvent](types.NewSocketpairEventFast)),
+ enterRaw(types.ENTER_ACCEPT_EVENT, rawDecoder[types.AcceptEvent](types.NewAcceptEventFast), nil),
+ exitRaw(types.EXIT_ACCEPT_EVENT, rawDecoder[types.AcceptEvent](types.NewAcceptEventFast)),
+ enterRaw(types.ENTER_PIPE_EVENT, rawDecoder[types.PipeEvent](types.NewPipeEventFast), nil),
+ exitRaw(types.EXIT_PIPE_EVENT, rawDecoder[types.PipeEvent](types.NewPipeEventFast)),
+ enterRaw(types.ENTER_EVENTFD_EVENT, rawDecoder[types.EventfdEvent](types.NewEventfdEventFast), nil),
+ exitRaw(types.EXIT_EVENTFD_EVENT, rawDecoder[types.EventfdEvent](types.NewEventfdEventFast)),
+ enterRaw(types.ENTER_EPOLL_CTL_EVENT, rawDecoder[types.EpollCtlEvent](types.NewEpollCtlEventFast), nil),
+ enterRaw(types.ENTER_POLL_EVENT, rawDecoder[types.PollEvent](types.NewPollEventFast), nil),
+ enterRaw(types.ENTER_TWO_FD_EVENT, rawDecoder[types.TwoFdEvent](types.NewTwoFdEventFast), nil),
+ enterRaw(types.ENTER_MEM_EVENT, rawDecoder[types.MemEvent](types.NewMemEventFast), nil),
+ enterRaw(types.ENTER_SLEEP_EVENT, rawDecoder[types.SleepEvent](types.NewSleepEventFast), nil),
+ enterRaw(types.ENTER_EXEC_EVENT, rawDecoder[types.ExecEvent](types.NewExecEventFast), nil),
+ enterRaw(types.ENTER_KEYCTL_EVENT, rawDecoder[types.KeyctlEvent](types.NewKeyctlEventFast), nil),
+ enterRaw(types.ENTER_PTRACE_EVENT, rawDecoder[types.PtraceEvent](types.NewPtraceEventFast), nil),
+ enterRaw(types.ENTER_PERF_OPEN_EVENT, rawDecoder[types.PerfOpenEvent](types.NewPerfOpenEventFast), nil),
+ }
+}
+
+func enterRaw(eventType types.EventType, decode runtimeEventDecoder, filter runtimeEnterFilter) rawRuntimeEvent {
+ return rawRuntimeEvent{eventType: eventType, direction: rawEnterEvent, decode: decode, filter: filter}
+}
+
+func exitRaw(eventType types.EventType, decode runtimeEventDecoder) rawRuntimeEvent {
+ return rawRuntimeEvent{eventType: eventType, direction: rawExitEvent, decode: decode}
+}
+
+func matchRawOpenEvent(filter globalfilter.Filter, ev event.Event) bool {
+ openEv, ok := ev.(*types.OpenEvent)
+ return ok && filter.MatchOpenEvent(openEv)
+}
+
+func matchRawNameEvent(filter globalfilter.Filter, ev event.Event) bool {
+ nameEv, ok := ev.(*types.NameEvent)
+ return ok && filter.MatchNameEvent(nameEv)
+}
+
+func matchRawPathEvent(filter globalfilter.Filter, ev event.Event) bool {
+ pathEv, ok := ev.(*types.PathEvent)
+ return ok && filter.MatchPathEvent(pathEv)
+}
diff --git a/internal/eventloop_kinds_test.go b/internal/eventloop_kinds_test.go
new file mode 100644
index 0000000..4207233
--- /dev/null
+++ b/internal/eventloop_kinds_test.go
@@ -0,0 +1,158 @@
+package internal
+
+import (
+ "testing"
+
+ "ior/internal/event"
+ "ior/internal/file"
+ "ior/internal/globalfilter"
+ "ior/internal/types"
+)
+
+func TestHandleTracepointExitDispatchesViaRuntimeKindRegistry(t *testing.T) {
+ el := mustNewEventLoop(t, eventLoopConfig{})
+ called := false
+ el.exitHandlers = map[types.EventType]runtimeExitHandler{
+ types.ENTER_FD_EVENT: func(_ *eventLoop, ep *event.Pair) bool {
+ called = true
+ ep.Comm = "registry"
+ return true
+ },
+ }
+
+ ep := &event.Pair{
+ EnterEv: &types.FdEvent{EventType: types.ENTER_FD_EVENT, TraceId: types.SYS_ENTER_READ, Tid: defaultTid},
+ ExitEv: &types.RetEvent{TraceId: types.SYS_EXIT_READ, Tid: defaultTid},
+ }
+
+ if ok := el.handleTracepointExit(ep); !ok {
+ t.Fatal("handleTracepointExit returned false")
+ }
+ if !called {
+ t.Fatal("registered runtime kind handler was not called")
+ }
+ if ep.Comm != "registry" {
+ t.Fatalf("ep.Comm = %q, want registry", ep.Comm)
+ }
+}
+
+func TestHandleTracepointExitRejectsMismatchedRuntimeKind(t *testing.T) {
+ el := mustNewEventLoop(t, eventLoopConfig{})
+ warnings := make(chan string, 1)
+ el.warningCb = func(message string) { warnings <- message }
+
+ ep := &event.Pair{
+ EnterEv: &types.NullEvent{EventType: types.ENTER_FD_EVENT, TraceId: types.SYS_ENTER_READ, Tid: defaultTid},
+ ExitEv: &types.RetEvent{TraceId: types.SYS_EXIT_READ, Tid: defaultTid},
+ }
+
+ if ok := el.handleTracepointExit(ep); ok {
+ t.Fatal("expected mismatched runtime kind to be rejected")
+ }
+
+ select {
+ case msg := <-warnings:
+ if msg != "Dropped malformed enter event" {
+ t.Fatalf("unexpected warning %q", msg)
+ }
+ default:
+ t.Fatal("expected warning for mismatched runtime kind")
+ }
+}
+
+func TestTracepointExitedFinalizesRuntimeKindPair(t *testing.T) {
+ el := mustNewEventLoop(t, eventLoopConfig{})
+ fd := int32(42)
+ tracked := file.NewFd(fd, "/tmp/read-source", 0)
+ el.fdState().set(fd, tracked)
+
+ enter := &types.FdEvent{
+ EventType: types.ENTER_FD_EVENT,
+ TraceId: types.SYS_ENTER_READ,
+ Time: 1000,
+ Pid: defaultPid,
+ Tid: defaultTid,
+ Fd: fd,
+ }
+ exit := &types.RetEvent{
+ EventType: types.EXIT_RET_EVENT,
+ TraceId: types.SYS_EXIT_READ,
+ Time: 1256,
+ Pid: defaultPid,
+ Tid: defaultTid,
+ Ret: 128,
+ RetType: types.READ_CLASSIFIED,
+ }
+
+ el.tracepointEntered(enter)
+ out := make(chan *event.Pair, 1)
+ el.tracepointExited(exit, out)
+
+ select {
+ case ep := <-out:
+ if ep.Bytes != 128 {
+ t.Fatalf("ep.Bytes = %d, want 128", ep.Bytes)
+ }
+ if ep.Duration != 256 {
+ t.Fatalf("ep.Duration = %d, want 256", ep.Duration)
+ }
+ if ep.File == tracked {
+ t.Fatal("expected emitted fd file to be frozen as a duplicate")
+ }
+ if got := ep.FileName(); got != "/tmp/read-source" {
+ t.Fatalf("ep.FileName() = %q, want /tmp/read-source", got)
+ }
+ default:
+ t.Fatal("expected finalized pair to be emitted")
+ }
+}
+
+func TestRawRuntimeEventHandlerAppliesEnterFilter(t *testing.T) {
+ el := mustNewEventLoop(t, eventLoopConfig{
+ filter: globalfilter.Filter{
+ File: &globalfilter.StringFilter{Pattern: "keep"},
+ },
+ })
+
+ _, raw := makeEnterPathEvent(t, defaulTime, defaultPid, defaultTid, "/tmp/drop", types.SYS_ENTER_NEWSTAT)
+ el.processRawEvent(raw, make(chan *event.Pair, 1))
+
+ if _, ok := el.pairs.enters[defaultTid]; ok {
+ t.Fatalf("path enter event should have been rejected by the raw enter filter")
+ }
+}
+
+func TestRuntimeEventKindRegistriesHaveUniqueCoverage(t *testing.T) {
+ exitHandlers := make(map[types.EventType]struct{})
+ for _, kind := range runtimeEventKinds() {
+ if _, exists := exitHandlers[kind.enterEventType]; exists {
+ t.Fatalf("duplicate runtime event kind for %v", kind.enterEventType)
+ }
+ exitHandlers[kind.enterEventType] = struct{}{}
+ if kind.exit == nil {
+ t.Fatalf("nil runtime exit handler for %v", kind.enterEventType)
+ }
+ }
+
+ rawHandlers := make(map[types.EventType]rawEventDirection)
+ for _, rawEvent := range rawRuntimeEvents() {
+ if _, exists := rawHandlers[rawEvent.eventType]; exists {
+ t.Fatalf("duplicate raw runtime event for %v", rawEvent.eventType)
+ }
+ rawHandlers[rawEvent.eventType] = rawEvent.direction
+ if rawEvent.decode == nil {
+ t.Fatalf("nil raw decoder for %v", rawEvent.eventType)
+ }
+ if rawEvent.direction == rawEnterEvent {
+ if _, exists := exitHandlers[rawEvent.eventType]; !exists {
+ t.Fatalf("enter raw event %v has no runtime exit handler", rawEvent.eventType)
+ }
+ }
+ }
+
+ for enterEventType := range exitHandlers {
+ if direction, exists := rawHandlers[enterEventType]; !exists || direction != rawEnterEvent {
+ t.Fatalf("runtime event kind %v has no enter raw handler", enterEventType)
+ }
+ }
+}
diff --git a/internal/eventloop_runtime.go b/internal/eventloop_runtime.go
index e8971d4..24214ca 100644
--- a/internal/eventloop_runtime.go
+++ b/internal/eventloop_runtime.go
@@ -219,10 +219,9 @@ func (e *eventLoop) processRawEvent(raw []byte, ch chan<- *event.Pair) {
handler(raw, ch)
}
-// initRawHandlers registers all BPF event-type dispatch callbacks. It is
-// idempotent: a second call after the map is populated is a no-op. Handlers
-// are grouped by event class (open, fd, null, ret, name/path, misc) so that
-// each helper stays under 30 lines.
+// initRawHandlers registers all BPF event-type dispatch callbacks from the
+// runtime event-kind table. It is idempotent: a second call after the map is
+// populated is a no-op.
func (e *eventLoop) initRawHandlers() {
if e.rawHandlers == nil {
e.rawHandlers = make(map[types.EventType]rawEventHandler)
@@ -230,290 +229,33 @@ func (e *eventLoop) initRawHandlers() {
if len(e.rawHandlers) != 0 {
return
}
- e.registerOpenHandlers()
- e.registerFdHandlers()
- e.registerNullHandlers()
- e.registerRetHandlers()
- e.registerNamePathHandlers()
- e.registerMiscHandlers()
- e.registerSocketHandlers()
- e.registerIPCHandlers()
- e.registerPollingHandlers()
- e.registerTwoFdHandlers()
- e.registerMemoryHandlers()
- e.registerSleepHandlers()
- e.registerProcessHandlers()
- e.registerSecurityHandlers()
-}
-
-// registerOpenHandlers wires enter/exit handlers for open-family events.
-func (e *eventLoop) registerOpenHandlers() {
- e.rawHandlers[types.ENTER_OPEN_EVENT] = func(raw []byte, _ chan<- *event.Pair) {
- openEv, ok := decodeRawEvent(e, types.ENTER_OPEN_EVENT, raw, types.NewOpenEventFast)
- if !ok {
- return
- }
- if e.Filter().MatchOpenEvent(openEv) {
- e.tracepointEntered(openEv)
- }
- }
- e.rawHandlers[types.EXIT_OPEN_EVENT] = func(raw []byte, ch chan<- *event.Pair) {
- retEv, ok := decodeRawEvent(e, types.EXIT_OPEN_EVENT, raw, types.NewRetEventFast)
- if !ok {
- return
- }
- e.tracepointExited(retEv, ch)
- }
-}
-
-// registerFdHandlers wires enter/exit handlers for fd-family events (read/write/close…).
-func (e *eventLoop) registerFdHandlers() {
- e.rawHandlers[types.ENTER_FD_EVENT] = func(raw []byte, _ chan<- *event.Pair) {
- fdEv, ok := decodeRawEvent(e, types.ENTER_FD_EVENT, raw, types.NewFdEventFast)
- if !ok {
- return
- }
- e.tracepointEntered(fdEv)
- }
- e.rawHandlers[types.EXIT_FD_EVENT] = func(raw []byte, ch chan<- *event.Pair) {
- fdEv, ok := decodeRawEvent(e, types.EXIT_FD_EVENT, raw, types.NewFdEventFast)
- if !ok {
- return
- }
- e.tracepointExited(fdEv, ch)
- }
-}
-
-// registerNullHandlers wires enter/exit handlers for syscalls with no interesting arguments.
-func (e *eventLoop) registerNullHandlers() {
- e.rawHandlers[types.ENTER_NULL_EVENT] = func(raw []byte, _ chan<- *event.Pair) {
- nullEv, ok := decodeRawEvent(e, types.ENTER_NULL_EVENT, raw, types.NewNullEventFast)
- if !ok {
- return
- }
- e.tracepointEntered(nullEv)
- }
- e.rawHandlers[types.EXIT_NULL_EVENT] = func(raw []byte, ch chan<- *event.Pair) {
- nullEv, ok := decodeRawEvent(e, types.EXIT_NULL_EVENT, raw, types.NewNullEventFast)
- if !ok {
- return
- }
- e.tracepointExited(nullEv, ch)
- }
-}
-
-// registerRetHandlers wires the exit handler for generic return-value events.
-func (e *eventLoop) registerRetHandlers() {
- e.rawHandlers[types.EXIT_RET_EVENT] = func(raw []byte, ch chan<- *event.Pair) {
- retEv, ok := decodeRawEvent(e, types.EXIT_RET_EVENT, raw, types.NewRetEventFast)
- if !ok {
- return
- }
- e.tracepointExited(retEv, ch)
- }
-}
-
-// registerNamePathHandlers wires enter handlers for name- and path-carrying events.
-func (e *eventLoop) registerNamePathHandlers() {
- e.rawHandlers[types.ENTER_NAME_EVENT] = func(raw []byte, _ chan<- *event.Pair) {
- nameEv, ok := decodeRawEvent(e, types.ENTER_NAME_EVENT, raw, types.NewNameEventFast)
- if !ok {
- return
- }
- if e.Filter().MatchNameEvent(nameEv) {
- e.tracepointEntered(nameEv)
- }
- }
- e.rawHandlers[types.ENTER_PATH_EVENT] = func(raw []byte, _ chan<- *event.Pair) {
- pathEv, ok := decodeRawEvent(e, types.ENTER_PATH_EVENT, raw, types.NewPathEventFast)
- if !ok {
- return
- }
- if e.Filter().MatchPathEvent(pathEv) {
- e.tracepointEntered(pathEv)
- }
- }
-}
-
-// registerMiscHandlers wires enter handlers for fcntl, open_by_handle_at, and dup3.
-func (e *eventLoop) registerMiscHandlers() {
- e.rawHandlers[types.ENTER_FCNTL_EVENT] = func(raw []byte, _ chan<- *event.Pair) {
- fcntlEv, ok := decodeRawEvent(e, types.ENTER_FCNTL_EVENT, raw, types.NewFcntlEventFast)
- if !ok {
- return
- }
- e.tracepointEntered(fcntlEv)
- }
- e.rawHandlers[types.ENTER_OPEN_BY_HANDLE_AT_EVENT] = func(raw []byte, _ chan<- *event.Pair) {
- openByHandleEv, ok := decodeRawEvent(e, types.ENTER_OPEN_BY_HANDLE_AT_EVENT, raw, types.NewOpenByHandleAtEventFast)
- if !ok {
- return
- }
- e.tracepointEntered(openByHandleEv)
- }
- e.rawHandlers[types.ENTER_DUP3_EVENT] = func(raw []byte, _ chan<- *event.Pair) {
- dup3Ev, ok := decodeRawEvent(e, types.ENTER_DUP3_EVENT, raw, types.NewDup3EventFast)
- if !ok {
- return
- }
- e.tracepointEntered(dup3Ev)
- }
-}
-
-func (e *eventLoop) registerSocketHandlers() {
- e.rawHandlers[types.ENTER_SOCKET_EVENT] = func(raw []byte, _ chan<- *event.Pair) {
- socketEv, ok := decodeRawEvent(e, types.ENTER_SOCKET_EVENT, raw, types.NewSocketEventFast)
- if !ok {
- return
- }
- e.tracepointEntered(socketEv)
- }
- e.rawHandlers[types.ENTER_SOCKETPAIR_EVENT] = func(raw []byte, _ chan<- *event.Pair) {
- socketpairEv, ok := decodeRawEvent(e, types.ENTER_SOCKETPAIR_EVENT, raw, types.NewSocketpairEventFast)
- if !ok {
- return
- }
- e.tracepointEntered(socketpairEv)
- }
- e.rawHandlers[types.EXIT_SOCKETPAIR_EVENT] = func(raw []byte, ch chan<- *event.Pair) {
- socketpairEv, ok := decodeRawEvent(e, types.EXIT_SOCKETPAIR_EVENT, raw, types.NewSocketpairEventFast)
- if !ok {
- return
- }
- e.tracepointExited(socketpairEv, ch)
- }
- e.rawHandlers[types.ENTER_ACCEPT_EVENT] = func(raw []byte, _ chan<- *event.Pair) {
- acceptEv, ok := decodeRawEvent(e, types.ENTER_ACCEPT_EVENT, raw, types.NewAcceptEventFast)
- if !ok {
- return
- }
- e.tracepointEntered(acceptEv)
- }
- e.rawHandlers[types.EXIT_ACCEPT_EVENT] = func(raw []byte, ch chan<- *event.Pair) {
- acceptEv, ok := decodeRawEvent(e, types.EXIT_ACCEPT_EVENT, raw, types.NewAcceptEventFast)
- if !ok {
- return
- }
- e.tracepointExited(acceptEv, ch)
- }
-}
-
-func (e *eventLoop) registerIPCHandlers() {
- e.rawHandlers[types.ENTER_PIPE_EVENT] = func(raw []byte, _ chan<- *event.Pair) {
- pipeEv, ok := decodeRawEvent(e, types.ENTER_PIPE_EVENT, raw, types.NewPipeEventFast)
- if !ok {
- return
- }
- e.tracepointEntered(pipeEv)
- }
- e.rawHandlers[types.EXIT_PIPE_EVENT] = func(raw []byte, ch chan<- *event.Pair) {
- pipeEv, ok := decodeRawEvent(e, types.EXIT_PIPE_EVENT, raw, types.NewPipeEventFast)
- if !ok {
- return
- }
- e.tracepointExited(pipeEv, ch)
- }
- e.rawHandlers[types.ENTER_EVENTFD_EVENT] = func(raw []byte, _ chan<- *event.Pair) {
- eventfdEv, ok := decodeRawEvent(e, types.ENTER_EVENTFD_EVENT, raw, types.NewEventfdEventFast)
- if !ok {
- return
- }
- e.tracepointEntered(eventfdEv)
- }
- e.rawHandlers[types.EXIT_EVENTFD_EVENT] = func(raw []byte, ch chan<- *event.Pair) {
- eventfdEv, ok := decodeRawEvent(e, types.EXIT_EVENTFD_EVENT, raw, types.NewEventfdEventFast)
- if !ok {
- return
- }
- e.tracepointExited(eventfdEv, ch)
- }
-}
-
-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)
- }
- 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 (e *eventLoop) registerTwoFdHandlers() {
- e.rawHandlers[types.ENTER_TWO_FD_EVENT] = func(raw []byte, _ chan<- *event.Pair) {
- twoFdEv, ok := decodeRawEvent(e, types.ENTER_TWO_FD_EVENT, raw, types.NewTwoFdEventFast)
- if !ok {
- return
- }
- e.tracepointEntered(twoFdEv)
- }
-}
-
-func (e *eventLoop) registerMemoryHandlers() {
- e.rawHandlers[types.ENTER_MEM_EVENT] = func(raw []byte, _ chan<- *event.Pair) {
- memEv, ok := decodeRawEvent(e, types.ENTER_MEM_EVENT, raw, types.NewMemEventFast)
- if !ok {
- return
- }
- e.tracepointEntered(memEv)
- }
-}
-
-func (e *eventLoop) registerSleepHandlers() {
- e.rawHandlers[types.ENTER_SLEEP_EVENT] = func(raw []byte, _ chan<- *event.Pair) {
- sleepEv, ok := decodeRawEvent(e, types.ENTER_SLEEP_EVENT, raw, types.NewSleepEventFast)
- if !ok {
- return
- }
- e.tracepointEntered(sleepEv)
- }
-}
-
-func (e *eventLoop) registerProcessHandlers() {
- e.rawHandlers[types.ENTER_EXEC_EVENT] = func(raw []byte, _ chan<- *event.Pair) {
- execEv, ok := decodeRawEvent(e, types.ENTER_EXEC_EVENT, raw, types.NewExecEventFast)
- if !ok {
- return
- }
- e.tracepointEntered(execEv)
+ for _, rawEvent := range rawRuntimeEvents() {
+ e.rawHandlers[rawEvent.eventType] = e.rawRuntimeEventHandler(rawEvent)
}
}
-func (e *eventLoop) registerSecurityHandlers() {
- e.rawHandlers[types.ENTER_KEYCTL_EVENT] = func(raw []byte, _ chan<- *event.Pair) {
- keyctlEv, ok := decodeRawEvent(e, types.ENTER_KEYCTL_EVENT, raw, types.NewKeyctlEventFast)
+func (e *eventLoop) rawRuntimeEventHandler(rawEvent rawRuntimeEvent) rawEventHandler {
+ return func(raw []byte, ch chan<- *event.Pair) {
+ ev, ok := e.decodeRuntimeEvent(rawEvent, raw)
if !ok {
return
}
- e.tracepointEntered(keyctlEv)
- }
- e.rawHandlers[types.ENTER_PTRACE_EVENT] = func(raw []byte, _ chan<- *event.Pair) {
- ptraceEv, ok := decodeRawEvent(e, types.ENTER_PTRACE_EVENT, raw, types.NewPtraceEventFast)
- if !ok {
+ if rawEvent.direction == rawExitEvent {
+ e.tracepointExited(ev, ch)
return
}
- e.tracepointEntered(ptraceEv)
- }
- e.rawHandlers[types.ENTER_PERF_OPEN_EVENT] = func(raw []byte, _ chan<- *event.Pair) {
- perfOpenEv, ok := decodeRawEvent(e, types.ENTER_PERF_OPEN_EVENT, raw, types.NewPerfOpenEventFast)
- if !ok {
+ if rawEvent.filter != nil && !rawEvent.filter(e.Filter(), ev) {
+ ev.Recycle()
return
}
- e.tracepointEntered(perfOpenEv)
+ e.tracepointEntered(ev)
}
}
-func decodeRawEvent[T any](e *eventLoop, eventType types.EventType, raw []byte, decode func([]byte) *T) (*T, bool) {
- decoded := decode(raw)
+func (e *eventLoop) decodeRuntimeEvent(rawEvent rawRuntimeEvent, raw []byte) (event.Event, bool) {
+ decoded := rawEvent.decode(raw)
if decoded == nil {
- e.dropMalformedRawEvent(eventType, raw)
+ e.dropMalformedRawEvent(rawEvent.eventType, raw)
return nil, false
}
return decoded, true
@@ -564,6 +306,11 @@ func (e *eventLoop) tracepointExited(exitEv event.Event, ch chan<- *event.Pair)
if !e.handleTracepointExit(ep) {
return
}
+ e.finalizeTracepointPair(ep)
+ ch <- ep
+}
+
+func (e *eventLoop) finalizeTracepointPair(ep *event.Pair) {
applyRetBytes(ep)
applyAddressSpaceBytes(ep)
applyRequestedSleepNs(ep)
@@ -571,7 +318,6 @@ func (e *eventLoop) tracepointExited(exitEv event.Event, ch chan<- *event.Pair)
ep.CalculateDurations(e.pairs.prevTime(tid))
e.pairs.setPrevTime(tid, ep.ExitEv.GetTime())
e.freezePairForEmission(ep)
- ch <- ep
}
func (e *eventLoop) freezePairForEmission(ep *event.Pair) {