From 61b04b46cdabf9288f8519a4fb22791c3efe2288 Mon Sep 17 00:00:00 2001 From: Lukasz Raczylo Date: Fri, 19 Jun 2026 22:19:21 +0100 Subject: [PATCH] fix(metrics): ignore negative Counter.Add to keep counters monotonic Counter.Add applied any delta, so a negative value would decrement a counter that must be monotonic, corrupting rate()-style downstream math. Ignore delta<0. Adds TestCounterRejectsNegativeAdd. --- internal/metrics/metrics.go | 7 ++++++- internal/metrics/metrics_test.go | 10 ++++++++++ 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/internal/metrics/metrics.go b/internal/metrics/metrics.go index 534273a..107b131 100644 --- a/internal/metrics/metrics.go +++ b/internal/metrics/metrics.go @@ -33,8 +33,13 @@ func (c *Counter) Inc() { c.value.Add(1) } -// Add adds the given value to the counter. +// Add adds the given value to the counter. Counters are monotonic, so a +// negative delta (which would corrupt rate()-style calculations downstream) is +// ignored rather than applied. func (c *Counter) Add(delta int64) { + if delta < 0 { + return + } c.value.Add(delta) } diff --git a/internal/metrics/metrics_test.go b/internal/metrics/metrics_test.go index a483fec..0f4ce0c 100644 --- a/internal/metrics/metrics_test.go +++ b/internal/metrics/metrics_test.go @@ -71,6 +71,16 @@ func TestCounter(t *testing.T) { } } +// Counters are monotonic: a negative Add must be ignored, not applied. +func TestCounterRejectsNegativeAdd(t *testing.T) { + c := NewCounter("mono_counter", "test", nil) + c.Add(5) + c.Add(-3) + if got := c.Value(); got != 5 { + t.Errorf("negative Add should be ignored: Value() = %d, want 5", got) + } +} + func TestCounterConcurrency(t *testing.T) { c := NewCounter("concurrent_counter", "test", nil) var wg sync.WaitGroup