summaryrefslogtreecommitdiff
path: root/internal/eventloop.go
diff options
context:
space:
mode:
authorPaul Buetow <paul@buetow.org>2026-05-08 23:18:52 +0300
committerPaul Buetow <paul@buetow.org>2026-05-08 23:18:52 +0300
commit75c483ec6443f731cc6f2149c4738547eb602c6f (patch)
tree4875b619864a5eeeb8faff84f475c21382acb499 /internal/eventloop.go
parentf86699a94bdde7d973ba5d6fa3e7ca4ab2f234fb (diff)
swap global filter in place to skip BPF reattach
Changing the global filter used to call stopTrace + beginTraceCmd, which detached and re-attached every tracepoint and re-loaded the BPF object. On heavily loaded I/O systems that took several seconds and showed an 'Attaching tracepoints...' overlay each time. The probe set never depends on the global filter (ShouldIAttachTracepoint only reads CLI regex flags), so the restart was gratuitous. Now the eventloop holds its filter behind atomic.Pointer with SetFilter / Filter accessors, and the trace starter registers el.SetFilter via the runtime bindings as a SetLiveFilterSetter callback. applyGlobalFilter and undoGlobalFilter call runtime.applyLiveFilter first; only if no trace is running do they fall back to the full restart path.
Diffstat (limited to 'internal/eventloop.go')
-rw-r--r--internal/eventloop.go27
1 files changed, 25 insertions, 2 deletions
diff --git a/internal/eventloop.go b/internal/eventloop.go
index 87f99ed..fc82ee4 100644
--- a/internal/eventloop.go
+++ b/internal/eventloop.go
@@ -2,6 +2,7 @@ package internal
import (
"fmt"
+ "sync/atomic"
"time"
"ior/internal/event"
@@ -38,7 +39,11 @@ type eventLoopConfig struct {
type rawEventHandler func(raw []byte, ch chan<- *event.Pair)
type eventLoop struct {
- filter globalfilter.Filter
+ // filterPtr holds the active global filter. Stored as atomic.Pointer so
+ // the TUI can swap filters in place via SetFilter without tearing down
+ // and reattaching the BPF probes (the previous behavior caused a multi-
+ // second 'Attaching tracepoints' overlay every time the filter changed).
+ filterPtr atomic.Pointer[globalfilter.Filter]
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
@@ -57,6 +62,24 @@ type eventLoop struct {
done chan struct{}
}
+// Filter returns a snapshot of the currently active global filter. Each call
+// loads a single atomic pointer and returns the underlying value, so the
+// caller observes a consistent filter even if SetFilter races concurrently.
+func (e *eventLoop) Filter() globalfilter.Filter {
+ if p := e.filterPtr.Load(); p != nil {
+ return *p
+ }
+ return globalfilter.Filter{}
+}
+
+// SetFilter atomically replaces the active global filter. The replacement is
+// cloned so the caller can keep mutating its own filter without affecting
+// what the eventloop sees.
+func (e *eventLoop) SetFilter(filter globalfilter.Filter) {
+ cloned := filter.Clone()
+ e.filterPtr.Store(&cloned)
+}
+
func newEventLoop(cfg eventLoopConfig) (*eventLoop, error) {
fdState := configuredFDTracker(cfg.fdTracker)
commState := configuredCommResolver(cfg.commResolver)
@@ -65,7 +88,6 @@ func newEventLoop(cfg eventLoopConfig) (*eventLoop, error) {
}
el := &eventLoop{
- filter: cfg.filter.Clone(),
pairs: newPairTracker(),
pendingHandles: newPendingHandleTracker(),
fdTracker: fdState,
@@ -75,6 +97,7 @@ func newEventLoop(cfg eventLoopConfig) (*eventLoop, error) {
cfg: cfg,
done: make(chan struct{}),
}
+ el.SetFilter(cfg.filter)
el.initRawHandlers()
el.configureOutputCallback()
el.seedTrackedPidComm()