diff options
Diffstat (limited to 'internal/eventloop_state.go')
| -rw-r--r-- | internal/eventloop_state.go | 217 |
1 files changed, 125 insertions, 92 deletions
diff --git a/internal/eventloop_state.go b/internal/eventloop_state.go index cd6e428..9622fd1 100644 --- a/internal/eventloop_state.go +++ b/internal/eventloop_state.go @@ -1,21 +1,32 @@ package internal import ( - "sort" + "cmp" + "slices" "ior/internal/event" "ior/internal/file" ) +// fdTracker holds the process's open file-descriptor table and a procfs +// resolution cache for fds that were opened before tracing started. type fdTracker struct { - files map[int32]file.File + files map[int32]file.File + procFdCache map[uint64]*file.FdFile // procfs-resolved metadata for unknown FDs + procFdAges map[uint64]uint64 // access age per cache entry, for LRU eviction + maxCacheSize int // max entries before eviction; 0 = defaultMaxProcFdCacheSize + age uint64 // monotonic counter for LRU ordering } func newFDTracker(files map[int32]file.File) *fdTracker { if files == nil { files = make(map[int32]file.File) } - return &fdTracker{files: files} + return &fdTracker{ + files: files, + procFdCache: make(map[uint64]*file.FdFile), + procFdAges: make(map[uint64]uint64), + } } func (t *fdTracker) get(fd int32) (file.File, bool) { @@ -39,111 +50,168 @@ func (t *fdTracker) closeRangeFrom(first int32) { } } -func (e *eventLoop) resolveFdFile(fd int32, pid uint32) file.File { - if fdFile, ok := e.fdState().get(fd); ok { +// resolve returns the file.File for fd, checking the fd table first, then the +// procfs cache, and finally resolving via procfs and caching the result. +func (t *fdTracker) resolve(fd int32, pid uint32) file.File { + if fdFile, ok := t.get(fd); ok { return fdFile } if fd < 0 { return file.NewFd(fd, "", -1) } - - if cached, ok := e.cachedProcFdFile(fd, pid); ok { + if cached, ok := t.cachedProcFdFile(fd, pid); ok { return cached } - // Cache first procfs resolution to avoid repeated /proc lookups for hot unknown FDs. discovered := file.NewFdWithPid(fd, pid) - e.setProcFdCache(fd, pid, discovered) + t.setProcFdCache(fd, pid, discovered) return discovered } -func (e *eventLoop) cachedProcFdFile(fd int32, pid uint32) (*file.FdFile, bool) { +func (t *fdTracker) cachedProcFdFile(fd int32, pid uint32) (*file.FdFile, bool) { + if t.procFdCache == nil { + return nil, false + } key := procFdCacheKey(pid, fd) - cache, ok := e.procFdCacheState()[key] + cache, ok := t.procFdCache[key] if ok { - e.procFdCacheAgeState()[key] = e.nextCacheAge() + t.age++ + t.procFdAges[key] = t.age } return cache, ok } -func (e *eventLoop) setProcFdCache(fd int32, pid uint32, resolved *file.FdFile) { +func (t *fdTracker) setProcFdCache(fd int32, pid uint32, resolved *file.FdFile) { + if t.procFdCache == nil { + t.procFdCache = make(map[uint64]*file.FdFile) + t.procFdAges = make(map[uint64]uint64) + } key := procFdCacheKey(pid, fd) - e.procFdCacheState()[key] = resolved - e.procFdCacheAgeState()[key] = e.nextCacheAge() - e.pruneProcFdCache() + t.age++ + t.procFdCache[key] = resolved + t.procFdAges[key] = t.age + t.pruneCache() } -func (e *eventLoop) deleteProcFdCache(fd int32, pid uint32) { - e.deleteProcFdCacheKey(procFdCacheKey(pid, fd)) +func (t *fdTracker) deleteProcFdCache(fd int32, pid uint32) { + t.deleteCacheKey(procFdCacheKey(pid, fd)) } -func (e *eventLoop) deleteProcFdCacheFrom(first int32, pid uint32) { - cache := e.procFdCacheState() - for key := range cache { +func (t *fdTracker) deleteProcFdCacheFrom(first int32, pid uint32) { + if t.procFdCache == nil { + return + } + for key := range t.procFdCache { cachePid := uint32(key >> 32) cacheFd := int32(uint32(key)) if cachePid == pid && cacheFd >= first { - e.deleteProcFdCacheKey(key) + t.deleteCacheKey(key) } } } -func (e *eventLoop) procFdCacheState() map[uint64]*file.FdFile { - if e.procFdCache == nil { - e.procFdCache = make(map[uint64]*file.FdFile) +func (t *fdTracker) pruneCache() { + if t.procFdCache == nil { + return + } + limit := t.cacheLimit() + if len(t.procFdCache) <= limit { + return } - return e.procFdCache + trimOldestProcFdEntries(t.procFdCache, t.procFdAges, trimTarget(limit)) } -func (e *eventLoop) procFdCacheAgeState() map[uint64]uint64 { - if e.procFdCacheAges == nil { - e.procFdCacheAges = make(map[uint64]uint64) +func (t *fdTracker) cacheLimit() int { + if t.maxCacheSize > 0 { + return t.maxCacheSize } - return e.procFdCacheAges + return defaultMaxProcFdCacheSize } -func (e *eventLoop) enterEventAgeState() map[uint32]uint64 { - if e.enterEvAges == nil { - e.enterEvAges = make(map[uint32]uint64) - } - return e.enterEvAges +// deleteCacheKey removes a cache entry by its composite key. +// delete on a nil map is a no-op in Go, so this is safe even before any cache entries are set. +func (t *fdTracker) deleteCacheKey(key uint64) { + delete(t.procFdCache, key) + delete(t.procFdAges, key) } -func (e *eventLoop) enterEventState() map[uint32]*event.Pair { - if e.enterEvs == nil { - e.enterEvs = make(map[uint32]*event.Pair) +// pairTracker holds the state for matching sys_enter events to their sys_exit +// counterparts and computing inter-syscall durations per TID. +type pairTracker struct { + enters map[uint32]*event.Pair // pending enter events, keyed by TID + enterAges map[uint32]uint64 // insertion order per TID, for LRU eviction + prevTimes map[uint32]uint64 // previous pair's exit time per TID, for DurationToPrev + maxSize int // max pending enter events before pruning; 0 = default + age uint64 // monotonic counter for LRU ordering +} + +func newPairTracker() pairTracker { + return pairTracker{ + enters: make(map[uint32]*event.Pair), + enterAges: make(map[uint32]uint64), + prevTimes: make(map[uint32]uint64), } - return e.enterEvs } -func (e *eventLoop) setEnterEvent(enterEv event.Event) { +// set stores enterEv as a pending enter event for its TID, recycling any +// prior unmatched enter for the same TID, then prunes if over the limit. +// Maps are initialized lazily on first write; consume is safe on a nil map because +// Go map reads on nil return the zero value. +func (p *pairTracker) set(enterEv event.Event) { + if p.enters == nil { + p.enters = make(map[uint32]*event.Pair) + p.enterAges = make(map[uint32]uint64) + p.prevTimes = make(map[uint32]uint64) + } tid := enterEv.GetTid() pair := event.NewPair(enterEv) - if prev, ok := e.enterEventState()[tid]; ok && prev != nil { + if prev, ok := p.enters[tid]; ok && prev != nil { prev.Recycle() } - e.enterEventState()[tid] = pair - e.enterEventAgeState()[tid] = e.nextCacheAge() - e.prunePendingEnterEvents() + p.age++ + p.enters[tid] = pair + p.enterAges[tid] = p.age + p.prune() } -func (e *eventLoop) consumeEnterEvent(tid uint32) (*event.Pair, bool) { - pair, ok := e.enterEventState()[tid] +// consume removes and returns the pending enter pair for tid. +// Reading a nil map returns the zero value in Go, so this is safe before any set call. +func (p *pairTracker) consume(tid uint32) (*event.Pair, bool) { + pair, ok := p.enters[tid] if !ok { return nil, false } - delete(e.enterEventState(), tid) - delete(e.enterEventAgeState(), tid) + delete(p.enters, tid) + delete(p.enterAges, tid) return pair, true } -func (e *eventLoop) prunePendingEnterEvents() { - state := e.enterEventState() - limit := e.pendingEnterLimit() - if len(state) <= limit { +// prevTime returns the exit time of the previous pair for tid, used to compute DurationToPrev. +func (p *pairTracker) prevTime(tid uint32) uint64 { + return p.prevTimes[tid] +} + +// setPrevTime records the exit time of the most recent completed pair for tid. +func (p *pairTracker) setPrevTime(tid uint32, t uint64) { + if p.prevTimes == nil { + p.prevTimes = make(map[uint32]uint64) + } + p.prevTimes[tid] = t +} + +func (p *pairTracker) prune() { + limit := p.limit() + if len(p.enters) <= limit { return } - trimOldestPendingPairs(state, e.enterEventAgeState(), trimTarget(limit)) + trimOldestPendingPairs(p.enters, p.enterAges, trimTarget(limit)) +} + +func (p *pairTracker) limit() int { + if p.maxSize > 0 { + return p.maxSize + } + return defaultMaxPendingEnterEvs } func trimOldestPendingPairs(state map[uint32]*event.Pair, ages map[uint32]uint64, targetSize int) { @@ -157,10 +225,9 @@ func trimOldestPendingPairs(state map[uint32]*event.Pair, ages map[uint32]uint64 } oldest := make([]pendingPairAge, 0, len(state)) for tid := range state { - age := ages[tid] - oldest = append(oldest, pendingPairAge{tid: tid, age: age}) + oldest = append(oldest, pendingPairAge{tid: tid, age: ages[tid]}) } - sort.Slice(oldest, func(i, j int) bool { return oldest[i].age < oldest[j].age }) + slices.SortFunc(oldest, func(a, b pendingPairAge) int { return cmp.Compare(a.age, b.age) }) for _, entry := range oldest[:excess] { if pair, ok := state[entry.tid]; ok && pair != nil { pair.Recycle() @@ -170,15 +237,6 @@ func trimOldestPendingPairs(state map[uint32]*event.Pair, ages map[uint32]uint64 } } -func (e *eventLoop) pruneProcFdCache() { - state := e.procFdCacheState() - limit := e.procFdCacheLimit() - if len(state) <= limit { - return - } - trimOldestProcFdEntries(state, e.procFdCacheAgeState(), trimTarget(limit)) -} - func trimOldestProcFdEntries(state map[uint64]*file.FdFile, ages map[uint64]uint64, targetSize int) { excess := len(state) - targetSize if excess <= 0 { @@ -190,40 +248,15 @@ func trimOldestProcFdEntries(state map[uint64]*file.FdFile, ages map[uint64]uint } oldest := make([]procFdAge, 0, len(state)) for key := range state { - age := ages[key] - oldest = append(oldest, procFdAge{key: key, age: age}) + oldest = append(oldest, procFdAge{key: key, age: ages[key]}) } - sort.Slice(oldest, func(i, j int) bool { return oldest[i].age < oldest[j].age }) + slices.SortFunc(oldest, func(a, b procFdAge) int { return cmp.Compare(a.age, b.age) }) for _, entry := range oldest[:excess] { delete(state, entry.key) delete(ages, entry.key) } } -func (e *eventLoop) deleteProcFdCacheKey(key uint64) { - delete(e.procFdCacheState(), key) - delete(e.procFdCacheAgeState(), key) -} - -func (e *eventLoop) nextCacheAge() uint64 { - e.cacheAge++ - return e.cacheAge -} - -func (e *eventLoop) pendingEnterLimit() int { - if e.maxPendingEnterEvs > 0 { - return e.maxPendingEnterEvs - } - return defaultMaxPendingEnterEvs -} - -func (e *eventLoop) procFdCacheLimit() int { - if e.maxProcFdCacheSize > 0 { - return e.maxProcFdCacheSize - } - return defaultMaxProcFdCacheSize -} - func trimTarget(limit int) int { target := limit - limit/cacheTrimDivisor if target < 1 { |
