Add tests for examplars

Signed-off-by: beorn7 <beorn@grafana.com>
This commit is contained in:
beorn7 2020-01-24 13:34:44 +01:00
parent 57d41259e1
commit c32ffd121f
4 changed files with 144 additions and 6 deletions

View File

@ -70,7 +70,7 @@ func NewCounter(opts CounterOpts) Counter {
nil, nil,
opts.ConstLabels, 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. result.init(result) // Init self-collection.
return result return result
} }
@ -88,6 +88,8 @@ type counter struct {
labelPairs []*dto.LabelPair labelPairs []*dto.LabelPair
exemplar atomic.Value // Containing nil or a *dto.Exemplar. 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 { func (c *counter) Desc() *Desc {
@ -140,7 +142,7 @@ func (c *counter) updateExemplar(v float64, l Labels) {
if l == nil { if l == nil {
return return
} }
e, err := newExemplar(v, time.Now(), l) e, err := newExemplar(v, c.now(), l)
if err != nil { if err != nil {
panic(err) panic(err)
} }

View File

@ -17,6 +17,10 @@ import (
"fmt" "fmt"
"math" "math"
"testing" "testing"
"time"
"github.com/golang/protobuf/proto"
"github.com/golang/protobuf/ptypes"
dto "github.com/prometheus/client_model/go" dto "github.com/prometheus/client_model/go"
) )
@ -210,3 +214,61 @@ func TestCounterAddSmall(t *testing.T) {
t.Errorf("expected %q, got %q", expected, got) 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")
}
}

View File

@ -198,6 +198,7 @@ func newHistogram(desc *Desc, opts HistogramOpts, labelValues ...string) Histogr
upperBounds: opts.Buckets, upperBounds: opts.Buckets,
labelPairs: makeLabelPairs(desc, labelValues), labelPairs: makeLabelPairs(desc, labelValues),
counts: [2]*histogramCounts{{}, {}}, counts: [2]*histogramCounts{{}, {}},
now: time.Now,
} }
for i, upperBound := range h.upperBounds { for i, upperBound := range h.upperBounds {
if i < len(h.upperBounds)-1 { if i < len(h.upperBounds)-1 {
@ -266,6 +267,8 @@ type histogram struct {
upperBounds []float64 upperBounds []float64
labelPairs []*dto.LabelPair labelPairs []*dto.LabelPair
exemplars []atomic.Value // One more than buckets (to include +Inf), each a *dto.Exemplar. 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 { func (h *histogram) Desc() *Desc {
@ -397,7 +400,7 @@ func (h *histogram) updateExemplar(v float64, bucket int, l Labels) {
if l == nil { if l == nil {
return return
} }
e, err := newExemplar(v, time.Now(), l) e, err := newExemplar(v, h.now(), l)
if err != nil { if err != nil {
panic(err) panic(err)
} }

View File

@ -22,6 +22,10 @@ import (
"sync" "sync"
"testing" "testing"
"testing/quick" "testing/quick"
"time"
"github.com/golang/protobuf/proto"
"github.com/golang/protobuf/ptypes"
dto "github.com/prometheus/client_model/go" dto "github.com/prometheus/client_model/go"
) )
@ -182,7 +186,11 @@ func TestHistogramConcurrency(t *testing.T) {
go func(vals []float64) { go func(vals []float64) {
start.Wait() start.Wait()
for _, v := range vals { for _, v := range vals {
if n%2 == 0 {
sum.Observe(v) sum.Observe(v)
} else {
sum.ObserveWithExemplar(v, Labels{"foo": "bar"})
}
} }
end.Done() end.Done()
}(vals) }(vals)
@ -201,9 +209,13 @@ func TestHistogramConcurrency(t *testing.T) {
} }
wantCounts := getCumulativeCounts(allVars) 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 { if got := len(m.Histogram.Bucket); got != wantBuckets {
t.Errorf("got %d buckets in protobuf, want %d", got, want) t.Errorf("got %d buckets in protobuf, want %d", got, wantBuckets)
} }
for i, wantBound := range testBuckets { for i, wantBound := range testBuckets {
if i == len(testBuckets)-1 { if i == len(testBuckets)-1 {
@ -384,3 +396,62 @@ func TestHistogramAtomicObserve(t *testing.T) {
runtime.Gosched() 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)
}
}
}