From c32ffd121f40843d8ce747536467bb091678fd87 Mon Sep 17 00:00:00 2001 From: beorn7 Date: Fri, 24 Jan 2020 13:34:44 +0100 Subject: [PATCH] Add tests for examplars Signed-off-by: beorn7 --- prometheus/counter.go | 6 ++- prometheus/counter_test.go | 62 +++++++++++++++++++++++++++++ prometheus/histogram.go | 5 ++- prometheus/histogram_test.go | 77 ++++++++++++++++++++++++++++++++++-- 4 files changed, 144 insertions(+), 6 deletions(-) diff --git a/prometheus/counter.go b/prometheus/counter.go index 1731823..b0b8d8a 100644 --- a/prometheus/counter.go +++ b/prometheus/counter.go @@ -70,7 +70,7 @@ func NewCounter(opts CounterOpts) Counter { nil, opts.ConstLabels, ) - result := &counter{desc: desc, labelPairs: desc.constLabelPairs} + result := &counter{desc: desc, labelPairs: desc.constLabelPairs, now: time.Now} result.init(result) // Init self-collection. return result } @@ -88,6 +88,8 @@ type counter struct { labelPairs []*dto.LabelPair exemplar atomic.Value // Containing nil or a *dto.Exemplar. + + now func() time.Time // To mock out time.Now() for testing. } func (c *counter) Desc() *Desc { @@ -140,7 +142,7 @@ func (c *counter) updateExemplar(v float64, l Labels) { if l == nil { return } - e, err := newExemplar(v, time.Now(), l) + e, err := newExemplar(v, c.now(), l) if err != nil { panic(err) } diff --git a/prometheus/counter_test.go b/prometheus/counter_test.go index fd98fb1..e2e31fc 100644 --- a/prometheus/counter_test.go +++ b/prometheus/counter_test.go @@ -17,6 +17,10 @@ import ( "fmt" "math" "testing" + "time" + + "github.com/golang/protobuf/proto" + "github.com/golang/protobuf/ptypes" dto "github.com/prometheus/client_model/go" ) @@ -210,3 +214,61 @@ func TestCounterAddSmall(t *testing.T) { t.Errorf("expected %q, got %q", expected, got) } } + +func TestCounterExemplar(t *testing.T) { + now := time.Now() + + counter := NewCounter(CounterOpts{ + Name: "test", + Help: "test help", + }).(*counter) + counter.now = func() time.Time { return now } + + ts, err := ptypes.TimestampProto(now) + if err != nil { + t.Fatal(err) + } + expectedExemplar := &dto.Exemplar{ + Label: []*dto.LabelPair{ + &dto.LabelPair{Name: proto.String("foo"), Value: proto.String("bar")}, + }, + Value: proto.Float64(42), + Timestamp: ts, + } + + counter.AddWithExemplar(42, Labels{"foo": "bar"}) + if expected, got := expectedExemplar.String(), counter.exemplar.Load().(*dto.Exemplar).String(); expected != got { + t.Errorf("expected exemplar %s, got %s.", expected, got) + } + + addExemplarWithInvalidLabel := func() (err error) { + defer func() { + if e := recover(); e != nil { + err = e.(error) + } + }() + // Should panic because of invalid label name. + counter.AddWithExemplar(42, Labels{":o)": "smile"}) + return nil + } + if addExemplarWithInvalidLabel() == nil { + t.Error("adding exemplar with invalid label succeeded") + } + + addExemplarWithOversizedLabels := func() (err error) { + defer func() { + if e := recover(); e != nil { + err = e.(error) + } + }() + // Should panic because of 65 runes. + counter.AddWithExemplar(42, Labels{ + "abcdefghijklmnopqrstuvwxyz": "26+16 characters", + "x1234567": "8+15 characters", + }) + return nil + } + if addExemplarWithOversizedLabels() == nil { + t.Error("adding exemplar with oversized labels succeeded") + } +} diff --git a/prometheus/histogram.go b/prometheus/histogram.go index edba0c5..5b14757 100644 --- a/prometheus/histogram.go +++ b/prometheus/histogram.go @@ -198,6 +198,7 @@ func newHistogram(desc *Desc, opts HistogramOpts, labelValues ...string) Histogr upperBounds: opts.Buckets, labelPairs: makeLabelPairs(desc, labelValues), counts: [2]*histogramCounts{{}, {}}, + now: time.Now, } for i, upperBound := range h.upperBounds { if i < len(h.upperBounds)-1 { @@ -266,6 +267,8 @@ type histogram struct { upperBounds []float64 labelPairs []*dto.LabelPair exemplars []atomic.Value // One more than buckets (to include +Inf), each a *dto.Exemplar. + + now func() time.Time // To mock out time.Now() for testing. } func (h *histogram) Desc() *Desc { @@ -397,7 +400,7 @@ func (h *histogram) updateExemplar(v float64, bucket int, l Labels) { if l == nil { return } - e, err := newExemplar(v, time.Now(), l) + e, err := newExemplar(v, h.now(), l) if err != nil { panic(err) } diff --git a/prometheus/histogram_test.go b/prometheus/histogram_test.go index 306eee9..561128d 100644 --- a/prometheus/histogram_test.go +++ b/prometheus/histogram_test.go @@ -22,6 +22,10 @@ import ( "sync" "testing" "testing/quick" + "time" + + "github.com/golang/protobuf/proto" + "github.com/golang/protobuf/ptypes" dto "github.com/prometheus/client_model/go" ) @@ -182,7 +186,11 @@ func TestHistogramConcurrency(t *testing.T) { go func(vals []float64) { start.Wait() for _, v := range vals { - sum.Observe(v) + if n%2 == 0 { + sum.Observe(v) + } else { + sum.ObserveWithExemplar(v, Labels{"foo": "bar"}) + } } end.Done() }(vals) @@ -201,9 +209,13 @@ func TestHistogramConcurrency(t *testing.T) { } wantCounts := getCumulativeCounts(allVars) + wantBuckets := len(testBuckets) + if !math.IsInf(m.Histogram.Bucket[len(m.Histogram.Bucket)-1].GetUpperBound(), +1) { + wantBuckets-- + } - if got, want := len(m.Histogram.Bucket), len(testBuckets)-1; got != want { - t.Errorf("got %d buckets in protobuf, want %d", got, want) + if got := len(m.Histogram.Bucket); got != wantBuckets { + t.Errorf("got %d buckets in protobuf, want %d", got, wantBuckets) } for i, wantBound := range testBuckets { if i == len(testBuckets)-1 { @@ -384,3 +396,62 @@ func TestHistogramAtomicObserve(t *testing.T) { runtime.Gosched() } } + +func TestHistogramExemplar(t *testing.T) { + now := time.Now() + + histogram := NewHistogram(HistogramOpts{ + Name: "test", + Help: "test help", + Buckets: []float64{1, 2, 3, 4}, + }).(*histogram) + histogram.now = func() time.Time { return now } + + ts, err := ptypes.TimestampProto(now) + if err != nil { + t.Fatal(err) + } + expectedExemplars := []*dto.Exemplar{ + nil, + &dto.Exemplar{ + Label: []*dto.LabelPair{ + &dto.LabelPair{Name: proto.String("id"), Value: proto.String("2")}, + }, + Value: proto.Float64(1.6), + Timestamp: ts, + }, + nil, + &dto.Exemplar{ + Label: []*dto.LabelPair{ + &dto.LabelPair{Name: proto.String("id"), Value: proto.String("3")}, + }, + Value: proto.Float64(4), + Timestamp: ts, + }, + &dto.Exemplar{ + Label: []*dto.LabelPair{ + &dto.LabelPair{Name: proto.String("id"), Value: proto.String("4")}, + }, + Value: proto.Float64(4.5), + Timestamp: ts, + }, + } + + histogram.ObserveWithExemplar(1.5, Labels{"id": "1"}) + histogram.ObserveWithExemplar(1.6, Labels{"id": "2"}) // To replace exemplar in bucket 0. + histogram.ObserveWithExemplar(4, Labels{"id": "3"}) + histogram.ObserveWithExemplar(4.5, Labels{"id": "4"}) // Should go to +Inf bucket. + + for i, ex := range histogram.exemplars { + var got, expected string + if val := ex.Load(); val != nil { + got = val.(*dto.Exemplar).String() + } + if expectedExemplars[i] != nil { + expected = expectedExemplars[i].String() + } + if got != expected { + t.Errorf("expected exemplar %s, got %s.", expected, got) + } + } +}