summaryrefslogtreecommitdiff
path: root/internal/probemanager
diff options
context:
space:
mode:
authorPaul Buetow <paul@buetow.org>2026-05-08 19:43:33 +0300
committerPaul Buetow <paul@buetow.org>2026-05-08 19:43:33 +0300
commitf86699a94bdde7d973ba5d6fa3e7ca4ab2f234fb (patch)
treec2e11bfa4fdac965623a8058716c514fce507eba /internal/probemanager
parentc41a38ef55bb80681a6cc0b2161f8e84bfabcf17 (diff)
add duration metric, tolerate missing tracepoints, ship el8 build
- Bubbles, treemap, icicle, and the live flamegraph 'b' cycle now include syscall duration (sum) as a third metric alongside events and bytes. Statsengine snapshots expose TotalLatencyNs to support this. - AttachAll takes an optional warn callback. Production passes one so older kernels that lack newer tracepoints log a warning and keep going instead of aborting startup. - Dockerfile.el8 + scripts/build-with-docker-el8.sh + mage buildDockerEl8 produce ior.el8, a static binary built against Rocky Linux 8 glibc for RHEL/Rocky/Alma 8 hosts. - README.md documents installing mage and the new el8 target.
Diffstat (limited to 'internal/probemanager')
-rw-r--r--internal/probemanager/manager.go17
-rw-r--r--internal/probemanager/manager_test.go59
2 files changed, 64 insertions, 12 deletions
diff --git a/internal/probemanager/manager.go b/internal/probemanager/manager.go
index 288af41..677762b 100644
--- a/internal/probemanager/manager.go
+++ b/internal/probemanager/manager.go
@@ -79,7 +79,17 @@ func (m *Manager) Register(syscall string, pair TracepointPair) {
}
// AttachAll registers and attaches all tracepoint pairs selected by shouldAttach.
-func (m *Manager) AttachAll(shouldAttach func(string) bool, tpNames []string) error {
+//
+// If onAttachError is non-nil, per-syscall attach failures are reported through
+// the callback and AttachAll continues with the remaining tracepoints. This is
+// the desired mode in production: when running a binary built on a newer kernel
+// against an older one, some syscalls' tracepoints may be absent and the
+// corresponding attach call returns ENOENT. The error is recorded on the
+// probe entry (visible via States()) regardless of the callback.
+//
+// If onAttachError is nil, AttachAll preserves the strict legacy behavior and
+// returns the first attach error to the caller. Tests rely on this mode.
+func (m *Manager) AttachAll(shouldAttach func(string) bool, tpNames []string, onAttachError func(syscall string, err error)) error {
if m == nil {
return errors.New("probe manager is nil")
}
@@ -94,7 +104,10 @@ func (m *Manager) AttachAll(shouldAttach func(string) bool, tpNames []string) er
continue
}
if err := m.Attach(syscall); err != nil {
- return err
+ if onAttachError == nil {
+ return err
+ }
+ onAttachError(syscall, err)
}
}
return nil
diff --git a/internal/probemanager/manager_test.go b/internal/probemanager/manager_test.go
index dc0c474..2beb11e 100644
--- a/internal/probemanager/manager_test.go
+++ b/internal/probemanager/manager_test.go
@@ -97,7 +97,7 @@ func TestManagerAttachAllToggleAndCounts(t *testing.T) {
err := mgr.AttachAll(func(tp string) bool { return tp == "sys_enter_read" || tp == "sys_exit_read" }, []string{
"sys_enter_read", "sys_exit_read", "sys_enter_write", "sys_exit_write",
- })
+ }, nil)
if err != nil {
t.Fatalf("AttachAll returned error: %v", err)
}
@@ -210,7 +210,7 @@ func TestManagerAttachWaitsForDetachBeforeReturning(t *testing.T) {
errs: map[string]error{},
}
mgr := NewManager(attacher)
- if err := mgr.AttachAll(nil, []string{"sys_enter_close", "sys_exit_close"}); err != nil {
+ if err := mgr.AttachAll(nil, []string{"sys_enter_close", "sys_exit_close"}, nil); err != nil {
t.Fatalf("AttachAll returned error: %v", err)
}
@@ -281,7 +281,7 @@ func TestManagerCloseWaitsForDetachAndDoesNotDoubleDestroy(t *testing.T) {
errs: map[string]error{},
}
mgr := NewManager(attacher)
- if err := mgr.AttachAll(nil, []string{"sys_enter_close", "sys_exit_close"}); err != nil {
+ if err := mgr.AttachAll(nil, []string{"sys_enter_close", "sys_exit_close"}, nil); err != nil {
t.Fatalf("AttachAll returned error: %v", err)
}
@@ -337,7 +337,7 @@ func TestManagerDetachDestroysLinks(t *testing.T) {
errs: map[string]error{},
}
mgr := NewManager(attacher)
- if err := mgr.AttachAll(nil, []string{"sys_enter_close", "sys_exit_close"}); err != nil {
+ if err := mgr.AttachAll(nil, []string{"sys_enter_close", "sys_exit_close"}, nil); err != nil {
t.Fatalf("AttachAll returned error: %v", err)
}
if err := mgr.Detach("close"); err != nil {
@@ -359,7 +359,7 @@ func TestManagerDetachFailureKeepsActiveStateForUndetachedLink(t *testing.T) {
errs: map[string]error{},
}
mgr := NewManager(attacher)
- if err := mgr.AttachAll(nil, []string{"sys_enter_close", "sys_exit_close"}); err != nil {
+ if err := mgr.AttachAll(nil, []string{"sys_enter_close", "sys_exit_close"}, nil); err != nil {
t.Fatalf("AttachAll returned error: %v", err)
}
@@ -388,7 +388,7 @@ func TestManagerClosePreventsFurtherOperations(t *testing.T) {
errs: map[string]error{},
}
mgr := NewManager(attacher)
- if err := mgr.AttachAll(nil, []string{"sys_enter_open", "sys_exit_open"}); err != nil {
+ if err := mgr.AttachAll(nil, []string{"sys_enter_open", "sys_exit_open"}, nil); err != nil {
t.Fatalf("AttachAll returned error: %v", err)
}
if err := mgr.Close(); err != nil {
@@ -407,7 +407,7 @@ func TestManagerAttachAllReturnsProgramError(t *testing.T) {
},
}
mgr := NewManager(attacher)
- err := mgr.AttachAll(nil, []string{"sys_enter_read", "sys_exit_read"})
+ err := mgr.AttachAll(nil, []string{"sys_enter_read", "sys_exit_read"}, nil)
if err == nil {
t.Fatalf("expected attach error")
}
@@ -417,6 +417,45 @@ func TestManagerAttachAllReturnsProgramError(t *testing.T) {
}
}
+// When onAttachError is supplied, AttachAll should report each per-syscall
+// failure through the callback and continue attaching the remaining probes.
+// This is the path that lets a binary built on a newer kernel run on an older
+// one where some tracepoints don't exist.
+func TestManagerAttachAllWarnAndContinue(t *testing.T) {
+ attacher := &fakeAttacher{
+ programs: map[string]*fakeProgram{
+ "handle_sys_enter_write": {},
+ "handle_sys_exit_write": {},
+ },
+ errs: map[string]error{
+ "handle_sys_enter_read": errors.New("no such tracepoint"),
+ },
+ }
+ mgr := NewManager(attacher)
+
+ var warned []string
+ warn := func(syscall string, err error) {
+ warned = append(warned, syscall+":"+err.Error())
+ }
+ err := mgr.AttachAll(nil, []string{
+ "sys_enter_read", "sys_exit_read",
+ "sys_enter_write", "sys_exit_write",
+ }, warn)
+ if err != nil {
+ t.Fatalf("AttachAll returned error despite warn callback: %v", err)
+ }
+ if len(warned) != 1 {
+ t.Fatalf("expected exactly 1 warning, got %d (%v)", len(warned), warned)
+ }
+ if !strings.Contains(warned[0], "read") || !strings.Contains(warned[0], "no such tracepoint") {
+ t.Fatalf("unexpected warning text: %q", warned[0])
+ }
+ active, total := mgr.ActiveCount()
+ if active != 1 || total != 2 {
+ t.Fatalf("expected write attached and read skipped, got active=%d total=%d", active, total)
+ }
+}
+
func TestManagerAttachAllPicksUpNewTracepointsOnLaterCall(t *testing.T) {
attacher := &fakeAttacher{
programs: map[string]*fakeProgram{
@@ -429,7 +468,7 @@ func TestManagerAttachAllPicksUpNewTracepointsOnLaterCall(t *testing.T) {
}
mgr := NewManager(attacher)
- if err := mgr.AttachAll(nil, []string{"sys_enter_read", "sys_exit_read"}); err != nil {
+ if err := mgr.AttachAll(nil, []string{"sys_enter_read", "sys_exit_read"}, nil); err != nil {
t.Fatalf("AttachAll(read) returned error: %v", err)
}
states := mgr.States()
@@ -437,7 +476,7 @@ func TestManagerAttachAllPicksUpNewTracepointsOnLaterCall(t *testing.T) {
t.Fatalf("expected only read after first call, got %+v", states)
}
- if err := mgr.AttachAll(nil, []string{"sys_enter_read", "sys_exit_read", "sys_enter_write", "sys_exit_write"}); err != nil {
+ if err := mgr.AttachAll(nil, []string{"sys_enter_read", "sys_exit_read", "sys_enter_write", "sys_exit_write"}, nil); err != nil {
t.Fatalf("AttachAll(read+write) returned error: %v", err)
}
states = mgr.States()
@@ -458,7 +497,7 @@ func TestManagerIsActiveReflectsCurrentState(t *testing.T) {
errs: map[string]error{},
}
mgr := NewManager(attacher)
- if err := mgr.AttachAll(nil, []string{"sys_enter_read", "sys_exit_read"}); err != nil {
+ if err := mgr.AttachAll(nil, []string{"sys_enter_read", "sys_exit_read"}, nil); err != nil {
t.Fatalf("AttachAll returned error: %v", err)
}
if !mgr.IsActive("read") {