diff --git a/gauge.go b/gauge.go index 9bbbce2..3573e14 100644 --- a/gauge.go +++ b/gauge.go @@ -17,7 +17,7 @@ import ( // - foo{bar="baz",aaa="b"} // // f must be safe for concurrent calls. -// if f is nil, then it is expected that the gauge value is changed via Gauge.Set() call. +// if f is nil, then it is expected that the gauge value is changed via Set(), Inc(), Dec() and Add() calls. // // The returned gauge is safe to use from concurrent goroutines. // @@ -55,6 +55,38 @@ func (g *Gauge) Set(v float64) { atomic.StoreUint64(&g.valueBits, n) } +// Inc increments g by 1. +// +// The g must be created with nil callback in order to be able to call this function. +func (g *Gauge) Inc() { + g.Add(1) +} + +// Dec decrements g by 1. +// +// The g must be created with nil callback in order to be able to call this function. +func (g *Gauge) Dec() { + g.Add(-1) +} + +// Add adds fAdd to g. fAdd may be positive and negative. +// +// The g must be created with nil callback in order to be able to call this function. +func (g *Gauge) Add(fAdd float64) { + if g.f != nil { + panic(fmt.Errorf("cannot call Set on gauge created with non-nil callback")) + } + for { + n := atomic.LoadUint64(&g.valueBits) + f := math.Float64frombits(n) + fNew := f + fAdd + nNew := math.Float64bits(fNew) + if atomic.CompareAndSwapUint64(&g.valueBits, n, nNew) { + break + } + } +} + func (g *Gauge) marshalTo(prefix string, w io.Writer) { v := g.Get() if float64(int64(v)) == v { diff --git a/gauge_test.go b/gauge_test.go index 484a4c9..f428fb0 100644 --- a/gauge_test.go +++ b/gauge_test.go @@ -15,6 +15,18 @@ func TestGaugeError(t *testing.T) { g := GetOrCreateGauge("GetOrCreateGauge_nil_callback", func() float64 { return 123 }) g.Set(42) }) + expectPanic(t, "GetOrCreateGauge_Add_non-nil-callback", func() { + g := GetOrCreateGauge("GetOrCreateGauge_nil_callback", func() float64 { return 123 }) + g.Add(42) + }) + expectPanic(t, "GetOrCreateGauge_Inc_non-nil-callback", func() { + g := GetOrCreateGauge("GetOrCreateGauge_nil_callback", func() float64 { return 123 }) + g.Inc() + }) + expectPanic(t, "GetOrCreateGauge_Dec_non-nil-callback", func() { + g := GetOrCreateGauge("GetOrCreateGauge_nil_callback", func() float64 { return 123 }) + g.Dec() + }) } func TestGaugeSet(t *testing.T) { @@ -29,6 +41,49 @@ func TestGaugeSet(t *testing.T) { } } +func TestGaugeIncDec(t *testing.T) { + s := NewSet() + g := s.NewGauge("foo", nil) + if n := g.Get(); n != 0 { + t.Fatalf("unexpected gauge value: %g; expecting 0", n) + } + for i := 1; i <= 100; i++ { + g.Inc() + if n := g.Get(); n != float64(i) { + t.Fatalf("unexpected gauge value %g; expecting %d", n, i) + } + } + for i := 99; i >= 0; i-- { + g.Dec() + if n := g.Get(); n != float64(i) { + t.Fatalf("unexpected gauge value %g; expecting %d", n, i) + } + } +} + +func TestGaugeIncDecConcurrenc(t *testing.T) { + s := NewSet() + g := s.NewGauge("foo", nil) + + workers := 5 + var wg sync.WaitGroup + for i := 0; i < workers; i++ { + wg.Add(1) + go func() { + for i := 0; i < 100; i++ { + g.Inc() + g.Dec() + } + wg.Done() + }() + } + wg.Wait() + + if n := g.Get(); n != 0 { + t.Fatalf("unexpected gauge value %g; want 0", n) + } +} + func TestGaugeSerial(t *testing.T) { name := "GaugeSerial" n := 1.23