summaryrefslogtreecommitdiff
path: root/internal
diff options
context:
space:
mode:
authorPaul Buetow <paul@buetow.org>2026-05-13 10:21:56 +0300
committerPaul Buetow <paul@buetow.org>2026-05-13 10:21:56 +0300
commit7c15d6058cf56e8c7801259f1f842a3a010c5f41 (patch)
tree0b9f85dadade5958a281ab6abaa51dcb620603f2 /internal
parent42d7821dc8d81781f2c3cfc269e99c0ee1dbd017 (diff)
fix: guard Pair.CalculateDurations against uint64 underflow on clock skew
BPF timestamps can be non-monotonic across CPUs (NTP step, TSC skew). When exit < enter or enter < prevPairTime the uint64 subtraction wraps to a huge value, corrupting latency histograms and flamegraph weights. Clamp both Duration and DurationToPrev to 0 instead of underflowing. Add TestPairCalculateDurationsNegativeDelta to cover this case. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Diffstat (limited to 'internal')
-rw-r--r--internal/event/pair.go23
-rw-r--r--internal/event/pair_test.go30
2 files changed, 50 insertions, 3 deletions
diff --git a/internal/event/pair.go b/internal/event/pair.go
index 4d3f342..c31fd90 100644
--- a/internal/event/pair.go
+++ b/internal/event/pair.go
@@ -36,12 +36,29 @@ func NewPair(enterEv Event) *Pair {
}
func (e *Pair) CalculateDurations(prevPairTime uint64) {
- // Duration is syscall runtime: exit(current) - enter(current).
- e.Duration = e.ExitEv.GetTime() - e.EnterEv.GetTime()
+ exitTime := e.ExitEv.GetTime()
+ enterTime := e.EnterEv.GetTime()
+
+ // Guard against uint64 underflow caused by non-monotonic BPF timestamps
+ // (e.g. cross-CPU clock skew or NTP adjustments). When exit < enter the
+ // syscall duration cannot be measured reliably; treat it as zero rather
+ // than wrapping around to an astronomically large value.
+ if exitTime >= enterTime {
+ e.Duration = exitTime - enterTime
+ } else {
+ e.Duration = 0
+ }
+
if prevPairTime > 0 {
// DurationToPrev is the inter-syscall gap on the same TID:
// enter(current) - exit(previous).
- e.DurationToPrev = e.EnterEv.GetTime() - prevPairTime
+ // Apply the same underflow guard: if the previous exit timestamp
+ // is ahead of this enter (clock skew), clamp the gap to zero.
+ if enterTime >= prevPairTime {
+ e.DurationToPrev = enterTime - prevPairTime
+ } else {
+ e.DurationToPrev = 0
+ }
}
}
diff --git a/internal/event/pair_test.go b/internal/event/pair_test.go
index eb033dc..9aa3e11 100644
--- a/internal/event/pair_test.go
+++ b/internal/event/pair_test.go
@@ -56,6 +56,36 @@ func TestPairCalculateDurationsWithPreviousExit(t *testing.T) {
}
}
+// TestPairCalculateDurationsNegativeDelta verifies that non-monotonic BPF
+// timestamps (exit < enter due to cross-CPU clock skew) do not cause uint64
+// underflow. Both Duration and DurationToPrev must clamp to zero.
+func TestPairCalculateDurationsNegativeDelta(t *testing.T) {
+ enter := &types.OpenEvent{
+ Time: 2000,
+ Pid: 1,
+ Tid: 2,
+ }
+ // Simulate clock skew: exit timestamp is earlier than enter.
+ exit := &types.RetEvent{
+ Time: 1900,
+ Pid: 1,
+ Tid: 2,
+ Ret: 0,
+ }
+
+ pair := NewPair(enter)
+ pair.ExitEv = exit
+ // prevPairTime > enterTime also triggers underflow in DurationToPrev.
+ pair.CalculateDurations(3000)
+
+ if pair.Duration != 0 {
+ t.Fatalf("Duration = %d, want 0 when exit < enter (underflow guard)", pair.Duration)
+ }
+ if pair.DurationToPrev != 0 {
+ t.Fatalf("DurationToPrev = %d, want 0 when enter < prevPairTime (underflow guard)", pair.DurationToPrev)
+ }
+}
+
func TestPairRecycleHandlesMissingExitEvent(t *testing.T) {
pair := NewPair(&types.OpenEvent{
Time: 1000,