diff options
| author | Paul Buetow <paul@buetow.org> | 2026-03-18 08:51:33 +0200 |
|---|---|---|
| committer | Paul Buetow <paul@buetow.org> | 2026-03-18 08:51:33 +0200 |
| commit | 6ab80599c8f8ba688a0415ecbeb03e494ef31f04 (patch) | |
| tree | c0c831f29bddb1754629eb337f5ec8bc9efae983 /internal | |
| parent | d8ef4ac43d3039c3d583c1ac20c7dca6b69a6c92 (diff) | |
refactor: replace reflect.TypeOf dispatch in exit handlers with type switch (task 432)
The exitHandlers map keyed by reflect.Type was a reflection-based dispatch
table that incurred allocation and reflection overhead on every event pair
in the hot processing path. Replace it with a plain type switch in
handleTracepointExit, which the compiler resolves statically.
Removes: initExitHandlers, typeKey, mustBeType, newTypedExitHandler,
exitHandlerRegistry, the exitHandlers struct field, and the tracepointExitHandler
type alias — all dead after the switch. Fixes a pre-existing uint→uint64
mismatch in stats(). Updates tests to target the new default branch directly.
Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
Diffstat (limited to 'internal')
| -rw-r--r-- | internal/eventloop.go | 26 | ||||
| -rw-r--r-- | internal/eventloop_error_handling_test.go | 46 | ||||
| -rw-r--r-- | internal/eventloop_exit.go | 69 |
3 files changed, 56 insertions, 85 deletions
diff --git a/internal/eventloop.go b/internal/eventloop.go index f15c344..b7fe230 100644 --- a/internal/eventloop.go +++ b/internal/eventloop.go @@ -1,10 +1,7 @@ package internal -import "C" - import ( "fmt" - "reflect" "time" "ior/internal/event" @@ -38,7 +35,6 @@ type eventLoopConfig struct { } type rawEventHandler func(raw []byte, ch chan<- *event.Pair) -type tracepointExitHandler func(ep *event.Pair) bool type eventLoop struct { filter globalfilter.Filter @@ -51,7 +47,6 @@ type eventLoop struct { commResolver *commResolver prevPairTimes map[uint32]uint64 // Previous event's time (to calculate time differences between two events) rawHandlers map[types.EventType]rawEventHandler - exitHandlers map[reflect.Type]tracepointExitHandler printCb func(ep *event.Pair) // Callback to print the event warningCb func(message string) // Optional callback for non-fatal event processing warnings cfg eventLoopConfig @@ -86,13 +81,11 @@ func newEventLoop(cfg eventLoopConfig) (*eventLoop, error) { commResolver: commState, prevPairTimes: make(map[uint32]uint64), rawHandlers: make(map[types.EventType]rawEventHandler), - exitHandlers: make(map[reflect.Type]tracepointExitHandler), printCb: func(ep *event.Pair) { fmt.Println(ep); ep.Recycle() }, cfg: cfg, done: make(chan struct{}), } el.initRawHandlers() - el.initExitHandlers() el.configureOutputCallback() el.seedTrackedPidComm() return el, nil @@ -167,6 +160,19 @@ func (e *eventLoop) stats() string { <-e.done duration := time.Since(e.startTime) + secs := duration.Seconds() + // Guard against division by zero when called immediately after start. + rate := func(n uint64) float64 { + if secs <= 0 { + return 0 + } + return float64(n) / secs + } + mismatchPct := 0.0 + if e.numTracepoints > 0 { + mismatchPct = (float64(e.numTracepointMismatches) / float64(e.numTracepoints)) * 100 + } + stats := fmt.Sprintf( "Statistics:\n"+ "\tduration: %v\n"+ @@ -174,9 +180,9 @@ func (e *eventLoop) stats() string { "\tsyscalls: %d (%.2f/s)\n"+ "\tsyscalls after filter: %d (%.2f/s)\n", duration, - e.numTracepoints, float64(e.numTracepoints)/duration.Seconds(), e.numTracepointMismatches, (float64(e.numTracepointMismatches)/float64(e.numTracepoints))*100, - e.numSyscalls, float64(e.numSyscalls)/duration.Seconds(), - e.numSyscallsAfterFilter, float64(e.numSyscallsAfterFilter)/duration.Seconds(), + e.numTracepoints, rate(uint64(e.numTracepoints)), e.numTracepointMismatches, mismatchPct, + e.numSyscalls, rate(uint64(e.numSyscalls)), + e.numSyscallsAfterFilter, rate(uint64(e.numSyscallsAfterFilter)), ) return stats diff --git a/internal/eventloop_error_handling_test.go b/internal/eventloop_error_handling_test.go index b7dd282..5867417 100644 --- a/internal/eventloop_error_handling_test.go +++ b/internal/eventloop_error_handling_test.go @@ -166,44 +166,38 @@ func TestDecodeRawEventMalformedNotifies(t *testing.T) { } } -func TestMustBeTypeReturnsTypedEnterEvent(t *testing.T) { - el := mustNewEventLoop(t, eventLoopConfig{}) - enterEv, enterRaw := makeEnterOpenEvent(t, defaulTime, defaultPid, defaultTid) - ep := event.NewPair(types.NewOpenEvent(enterRaw)) - defer ep.Recycle() - - typed, ok := mustBeType[*types.OpenEvent](el, ep, "ignored") - if !ok { - t.Fatal("expected mustBeType to return the typed enter event") - } - if typed.GetTid() != enterEv.Tid { - t.Fatalf("mustBeType() returned tid %d, want %d", typed.GetTid(), enterEv.Tid) - } -} - -func TestMustBeTypeRecyclesMalformedPairAndNotifies(t *testing.T) { +// unknownEvent is a minimal event.Event stub used to exercise the default +// branch of handleTracepointExit (an enter-event type not covered by any case). +type unknownEvent struct{} + +func (unknownEvent) String() string { return "unknownEvent" } +func (unknownEvent) GetTraceId() types.TraceId { return 0 } +func (unknownEvent) GetPid() uint32 { return 0 } +func (unknownEvent) GetTid() uint32 { return 0 } +func (unknownEvent) GetTime() uint64 { return 0 } +func (unknownEvent) Equals(other any) bool { return false } +func (unknownEvent) Recycle() {} + +func TestHandleTracepointExitUnknownTypeDropsMalformedEvent(t *testing.T) { el := mustNewEventLoop(t, eventLoopConfig{}) warnings := make(chan string, 1) el.warningCb = func(message string) { warnings <- message } - _, enterRaw := makeEnterNullEvent(t, defaulTime, defaultPid, defaultTid, types.SYS_ENTER_SYNC) - ep := event.NewPair(types.NewNullEvent(enterRaw)) + // Wrap an unknown enter-event type to hit the default branch of the type switch. + ep := event.NewPair(unknownEvent{}) - typed, ok := mustBeType[*types.OpenEvent](el, ep, "Dropped malformed open enter event") - if ok || typed != nil { - t.Fatalf("expected mustBeType mismatch to fail, got ok=%v typed=%v", ok, typed) - } - if ep.EnterEv != nil || ep.ExitEv != nil { - t.Fatalf("expected malformed pair to be recycled") + ok := el.handleTracepointExit(ep) + if ok { + t.Fatal("expected handleTracepointExit to return false for unknown enter-event type") } select { case msg := <-warnings: - if msg != "Dropped malformed open enter event" { + if msg != "Dropped malformed enter event" { t.Fatalf("unexpected warning %q", msg) } default: - t.Fatalf("expected warning notification") + t.Fatal("expected warning notification for unknown enter-event type") } } diff --git a/internal/eventloop_exit.go b/internal/eventloop_exit.go index b0c0256..e40a3fd 100644 --- a/internal/eventloop_exit.go +++ b/internal/eventloop_exit.go @@ -3,7 +3,6 @@ package internal import ( "fmt" "os" - "reflect" "syscall" "ior/internal/event" @@ -11,58 +10,30 @@ import ( "ior/internal/types" ) -func (e *eventLoop) initExitHandlers() { - e.exitHandlers = map[reflect.Type]tracepointExitHandler{ - typeKey[*types.OpenEvent](): newTypedExitHandler(e, "Dropped malformed open enter event", e.handleOpenExit), - typeKey[*types.NameEvent](): newTypedExitHandler(e, "Dropped malformed name enter event", e.handleNameExit), - typeKey[*types.PathEvent](): newTypedExitHandler(e, "Dropped malformed path enter event", e.handlePathExit), - typeKey[*types.FdEvent](): newTypedExitHandler(e, "Dropped malformed fd enter event", e.handleFdExit), - typeKey[*types.Dup3Event](): newTypedExitHandler(e, "Dropped malformed dup3 enter event", e.handleDup3Exit), - typeKey[*types.OpenByHandleAtEvent](): newTypedExitHandler(e, "Dropped malformed open_by_handle_at enter event", e.handleOpenByHandleAtExit), - typeKey[*types.NullEvent](): newTypedExitHandler(e, "Dropped malformed null enter event", e.handleNullExit), - typeKey[*types.FcntlEvent](): newTypedExitHandler(e, "Dropped malformed fcntl enter event", e.handleFcntlExit), - } -} - -func mustBeType[T event.Event](e *eventLoop, ep *event.Pair, message string) (T, bool) { - enterEv, ok := ep.EnterEv.(T) - if !ok { - e.recyclePair(ep, message) - var zero T - return zero, false - } - return enterEv, true -} - -func newTypedExitHandler[T event.Event](e *eventLoop, message string, handle func(*event.Pair, T) bool) tracepointExitHandler { - return func(ep *event.Pair) bool { - enterEv, ok := mustBeType[T](e, ep, message) - if !ok { - return false - } - return handle(ep, enterEv) - } -} - -func typeKey[T any]() reflect.Type { - var zero T - return reflect.TypeOf(zero) -} - -func (e *eventLoop) exitHandlerRegistry() map[reflect.Type]tracepointExitHandler { - if e.exitHandlers == nil { - e.initExitHandlers() - } - return e.exitHandlers -} - +// 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) handleTracepointExit(ep *event.Pair) bool { - handler, ok := e.exitHandlerRegistry()[reflect.TypeOf(ep.EnterEv)] - if !ok { + switch ev := ep.EnterEv.(type) { + case *types.OpenEvent: + return e.handleOpenExit(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.NullEvent: + return e.handleNullExit(ep, ev) + case *types.FcntlEvent: + return e.handleFcntlExit(ep, ev) + default: e.recyclePair(ep, "Dropped malformed enter event") return false } - return handler(ep) } func (e *eventLoop) handleOpenExit(ep *event.Pair, openEv *types.OpenEvent) bool { |
