summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--internal/eventloop.go32
-rw-r--r--internal/eventloop_cleanup_test.go42
-rw-r--r--internal/eventloop_exit.go7
-rw-r--r--internal/eventloop_state.go80
-rw-r--r--internal/eventloop_test.go4
5 files changed, 151 insertions, 14 deletions
diff --git a/internal/eventloop.go b/internal/eventloop.go
index 645f6af..87f99ed 100644
--- a/internal/eventloop.go
+++ b/internal/eventloop.go
@@ -13,11 +13,12 @@ import (
const sysEnterNameToHandleAtName = "name_to_handle_at"
const (
- defaultCommLookupWorkers = 4
- defaultCommLookupQueueSize = 512
- defaultMaxPendingEnterEvs = 16384
- defaultMaxProcFdCacheSize = 8192
- cacheTrimDivisor = 4
+ defaultCommLookupWorkers = 4
+ defaultCommLookupQueueSize = 512
+ defaultMaxPendingEnterEvs = 16384
+ defaultMaxPendingHandleEntries = 8192
+ defaultMaxProcFdCacheSize = 8192
+ cacheTrimDivisor = 4
)
type eventLoopConfig struct {
@@ -38,9 +39,9 @@ type rawEventHandler func(raw []byte, ch chan<- *event.Pair)
type eventLoop struct {
filter globalfilter.Filter
- pairs pairTracker // enter/exit pairing state and inter-syscall duration tracking
- pendingHandles map[uint32]string // TID → pathname from name_to_handle_at, for open_by_handle_at correlation
- fdTracker *fdTracker // fd table and procfs resolution cache
+ pairs pairTracker // enter/exit pairing state and inter-syscall duration tracking
+ pendingHandles *pendingHandleTracker // TID → pathname from name_to_handle_at, for open_by_handle_at correlation
+ fdTracker *fdTracker // fd table and procfs resolution cache
commResolver *commResolver
rawHandlers map[types.EventType]rawEventHandler
printCb func(ep *event.Pair) // Callback to print the event
@@ -66,7 +67,7 @@ func newEventLoop(cfg eventLoopConfig) (*eventLoop, error) {
el := &eventLoop{
filter: cfg.filter.Clone(),
pairs: newPairTracker(),
- pendingHandles: make(map[uint32]string),
+ pendingHandles: newPendingHandleTracker(),
fdTracker: fdState,
commResolver: commState,
rawHandlers: make(map[types.EventType]rawEventHandler),
@@ -118,6 +119,19 @@ func (e *eventLoop) fdState() *fdTracker {
return e.fdTracker
}
+func (e *eventLoop) pendingHandleState() *pendingHandleTracker {
+ if e.pendingHandles == nil {
+ e.pendingHandles = newPendingHandleTracker()
+ }
+ if e.pendingHandles.paths == nil {
+ e.pendingHandles.paths = make(map[uint32]string)
+ }
+ if e.pendingHandles.pathAges == nil {
+ e.pendingHandles.pathAges = make(map[uint32]uint64)
+ }
+ return e.pendingHandles
+}
+
func (e *eventLoop) commState() *commResolver {
if e.commResolver == nil {
e.commResolver = newCommResolver(nil)
diff --git a/internal/eventloop_cleanup_test.go b/internal/eventloop_cleanup_test.go
index 1d3a6fb..7c8604b 100644
--- a/internal/eventloop_cleanup_test.go
+++ b/internal/eventloop_cleanup_test.go
@@ -3,7 +3,9 @@ package internal
import (
"testing"
+ "ior/internal/event"
"ior/internal/file"
+ "ior/internal/types"
)
func TestTracepointEnteredPrunesOldestPendingPairs(t *testing.T) {
@@ -88,3 +90,43 @@ func TestProcFdCacheRetainsRecentlyUsedEntries(t *testing.T) {
t.Fatalf("proc fd cache metadata size = %d, want 2", got)
}
}
+
+func TestPendingHandleTrackerRetainsRecentlyUsedEntries(t *testing.T) {
+ tracker := newPendingHandleTracker()
+ tracker.maxCacheSize = 2
+
+ tracker.set(defaultTid, "/tmp/handle-one")
+ tracker.set(defaultTid+1, "/tmp/handle-two")
+ tracker.set(defaultTid+2, "/tmp/handle-three")
+
+ if _, ok := tracker.paths[defaultTid]; ok {
+ t.Fatalf("expected oldest pending handle to be evicted")
+ }
+ if _, ok := tracker.paths[defaultTid+1]; !ok {
+ t.Fatalf("expected newer pending handle to be retained")
+ }
+ if _, ok := tracker.paths[defaultTid+2]; !ok {
+ t.Fatalf("expected newest pending handle to be retained")
+ }
+ if got := len(tracker.pathAges); got != 2 {
+ t.Fatalf("pending handle metadata size = %d, want 2", got)
+ }
+}
+
+func TestOpenByHandleAtFailureClearsPendingHandle(t *testing.T) {
+ el := mustNewEventLoop(t, eventLoopConfig{})
+
+ _, enterNameRaw := makeEnterPathEvent(t, defaulTime, defaultPid, defaultTid, "/tmp/handle.txt", types.SYS_ENTER_NAME_TO_HANDLE_AT)
+ el.tracepointEntered(types.NewPathEvent(enterNameRaw))
+ _, exitNameRaw := makeExitRetEvent(t, defaulTime+1, defaultPid, defaultTid, types.SYS_EXIT_NAME_TO_HANDLE_AT, 0)
+ el.tracepointExited(types.NewRetEvent(exitNameRaw), make(chan *event.Pair, 1))
+
+ _, enterOpenRaw := makeEnterOpenByHandleAtEvent(t, defaulTime+2, defaultPid, defaultTid, 0)
+ el.tracepointEntered(types.NewOpenByHandleAtEvent(enterOpenRaw))
+ _, exitOpenRaw := makeExitRetEvent(t, defaulTime+3, defaultPid, defaultTid, types.SYS_EXIT_OPEN_BY_HANDLE_AT, -1)
+ el.tracepointExited(types.NewRetEvent(exitOpenRaw), make(chan *event.Pair, 1))
+
+ if _, ok := el.pendingHandleState().paths[defaultTid]; ok {
+ t.Fatalf("expected pending handle to be cleared after failed open_by_handle_at")
+ }
+}
diff --git a/internal/eventloop_exit.go b/internal/eventloop_exit.go
index c74ecea..e97688a 100644
--- a/internal/eventloop_exit.go
+++ b/internal/eventloop_exit.go
@@ -70,7 +70,7 @@ func (e *eventLoop) handlePathExit(ep *event.Pair, pathEv *types.PathEvent) bool
ep.Recycle()
return false
}
- e.pendingHandles[pathEv.GetTid()] = types.StringValue(pathEv.Pathname[:])
+ e.pendingHandleState().set(pathEv.GetTid(), types.StringValue(pathEv.Pathname[:]))
ep.Recycle()
return false
}
@@ -175,18 +175,19 @@ func (e *eventLoop) handleOpenByHandleAtExit(ep *event.Pair, openByHandleEv *typ
tid := openByHandleEv.GetTid()
retEvent, ok := ep.ExitEv.(*types.RetEvent)
if !ok {
+ e.pendingHandleState().delete(tid)
e.recyclePair(ep, "Dropped malformed open_by_handle_at exit event")
return false
}
fd := int32(retEvent.Ret)
if fd < 0 {
+ e.pendingHandleState().delete(tid)
ep.Recycle()
return false
}
- if pathname, ok := e.pendingHandles[tid]; ok {
- delete(e.pendingHandles, tid)
+ if pathname, ok := e.pendingHandleState().consume(tid); ok {
fdFile := file.NewFd(fd, pathname, openByHandleEv.Flags)
e.fdState().set(fd, fdFile)
ep.File = fdFile
diff --git a/internal/eventloop_state.go b/internal/eventloop_state.go
index 9622fd1..5cb0a78 100644
--- a/internal/eventloop_state.go
+++ b/internal/eventloop_state.go
@@ -18,6 +18,15 @@ type fdTracker struct {
age uint64 // monotonic counter for LRU ordering
}
+// pendingHandleTracker holds unresolved name_to_handle_at pathnames keyed by
+// TID until the corresponding open_by_handle_at exit consumes them.
+type pendingHandleTracker struct {
+ paths map[uint32]string
+ pathAges map[uint32]uint64
+ maxCacheSize int
+ age uint64
+}
+
func newFDTracker(files map[int32]file.File) *fdTracker {
if files == nil {
files = make(map[int32]file.File)
@@ -29,6 +38,13 @@ func newFDTracker(files map[int32]file.File) *fdTracker {
}
}
+func newPendingHandleTracker() *pendingHandleTracker {
+ return &pendingHandleTracker{
+ paths: make(map[uint32]string),
+ pathAges: make(map[uint32]uint64),
+ }
+}
+
func (t *fdTracker) get(fd int32) (file.File, bool) {
f, ok := t.files[fd]
return f, ok
@@ -135,6 +151,50 @@ func (t *fdTracker) deleteCacheKey(key uint64) {
delete(t.procFdAges, key)
}
+func (t *pendingHandleTracker) set(tid uint32, pathname string) {
+ if t.paths == nil {
+ t.paths = make(map[uint32]string)
+ t.pathAges = make(map[uint32]uint64)
+ }
+ t.age++
+ t.paths[tid] = pathname
+ t.pathAges[tid] = t.age
+ t.prune()
+}
+
+func (t *pendingHandleTracker) consume(tid uint32) (string, bool) {
+ pathname, ok := t.paths[tid]
+ if !ok {
+ return "", false
+ }
+ delete(t.paths, tid)
+ delete(t.pathAges, tid)
+ return pathname, true
+}
+
+func (t *pendingHandleTracker) delete(tid uint32) {
+ delete(t.paths, tid)
+ delete(t.pathAges, tid)
+}
+
+func (t *pendingHandleTracker) prune() {
+ if t.paths == nil {
+ return
+ }
+ limit := t.limit()
+ if len(t.paths) <= limit {
+ return
+ }
+ trimOldestPendingHandles(t.paths, t.pathAges, trimTarget(limit))
+}
+
+func (t *pendingHandleTracker) limit() int {
+ if t.maxCacheSize > 0 {
+ return t.maxCacheSize
+ }
+ return defaultMaxPendingHandleEntries
+}
+
// pairTracker holds the state for matching sys_enter events to their sys_exit
// counterparts and computing inter-syscall durations per TID.
type pairTracker struct {
@@ -257,6 +317,26 @@ func trimOldestProcFdEntries(state map[uint64]*file.FdFile, ages map[uint64]uint
}
}
+func trimOldestPendingHandles(state map[uint32]string, ages map[uint32]uint64, targetSize int) {
+ excess := len(state) - targetSize
+ if excess <= 0 {
+ return
+ }
+ type pendingHandleAge struct {
+ tid uint32
+ age uint64
+ }
+ oldest := make([]pendingHandleAge, 0, len(state))
+ for tid := range state {
+ oldest = append(oldest, pendingHandleAge{tid: tid, age: ages[tid]})
+ }
+ slices.SortFunc(oldest, func(a, b pendingHandleAge) int { return cmp.Compare(a.age, b.age) })
+ for _, entry := range oldest[:excess] {
+ delete(state, entry.tid)
+ delete(ages, entry.tid)
+ }
+}
+
func trimTarget(limit int) int {
target := limit - limit/cacheTrimDivisor
if target < 1 {
diff --git a/internal/eventloop_test.go b/internal/eventloop_test.go
index 98f2209..473a107 100644
--- a/internal/eventloop_test.go
+++ b/internal/eventloop_test.go
@@ -2339,7 +2339,7 @@ func makeNameToHandleAtTestData(t *testing.T) (td testData) {
verifyFileDescriptor(t, el, fd, pathname)
// Verify that the pending handle has been consumed
- if _, ok := el.pendingHandles[defaultTid]; ok {
+ if _, ok := el.pendingHandleState().paths[defaultTid]; ok {
t.Errorf("Expected pending handle for tid %d to be consumed", defaultTid)
}
})
@@ -2376,7 +2376,7 @@ func makeNameToHandleAtFailureTestData(t *testing.T) (td testData) {
t.Errorf("Expected open_by_handle_at to not use failed name_to_handle_at path")
}
- if _, ok := el.pendingHandles[defaultTid]; ok {
+ if _, ok := el.pendingHandleState().paths[defaultTid]; ok {
t.Errorf("Expected no pending handle for tid %d after failure", defaultTid)
}
})