summaryrefslogtreecommitdiff
path: root/internal/eventloop_state.go
diff options
context:
space:
mode:
Diffstat (limited to 'internal/eventloop_state.go')
-rw-r--r--internal/eventloop_state.go217
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 {