Skip to content

Commit c32ffd1

Browse files
committed
Add tests for examplars
Signed-off-by: beorn7 <[email protected]>
1 parent 57d4125 commit c32ffd1

File tree

4 files changed

+144
-6
lines changed

4 files changed

+144
-6
lines changed

prometheus/counter.go

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ func NewCounter(opts CounterOpts) Counter {
7070
nil,
7171
opts.ConstLabels,
7272
)
73-
result := &counter{desc: desc, labelPairs: desc.constLabelPairs}
73+
result := &counter{desc: desc, labelPairs: desc.constLabelPairs, now: time.Now}
7474
result.init(result) // Init self-collection.
7575
return result
7676
}
@@ -88,6 +88,8 @@ type counter struct {
8888

8989
labelPairs []*dto.LabelPair
9090
exemplar atomic.Value // Containing nil or a *dto.Exemplar.
91+
92+
now func() time.Time // To mock out time.Now() for testing.
9193
}
9294

9395
func (c *counter) Desc() *Desc {
@@ -140,7 +142,7 @@ func (c *counter) updateExemplar(v float64, l Labels) {
140142
if l == nil {
141143
return
142144
}
143-
e, err := newExemplar(v, time.Now(), l)
145+
e, err := newExemplar(v, c.now(), l)
144146
if err != nil {
145147
panic(err)
146148
}

prometheus/counter_test.go

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,10 @@ import (
1717
"fmt"
1818
"math"
1919
"testing"
20+
"time"
21+
22+
"github.com/golang/protobuf/proto"
23+
"github.com/golang/protobuf/ptypes"
2024

2125
dto "github.com/prometheus/client_model/go"
2226
)
@@ -210,3 +214,61 @@ func TestCounterAddSmall(t *testing.T) {
210214
t.Errorf("expected %q, got %q", expected, got)
211215
}
212216
}
217+
218+
func TestCounterExemplar(t *testing.T) {
219+
now := time.Now()
220+
221+
counter := NewCounter(CounterOpts{
222+
Name: "test",
223+
Help: "test help",
224+
}).(*counter)
225+
counter.now = func() time.Time { return now }
226+
227+
ts, err := ptypes.TimestampProto(now)
228+
if err != nil {
229+
t.Fatal(err)
230+
}
231+
expectedExemplar := &dto.Exemplar{
232+
Label: []*dto.LabelPair{
233+
&dto.LabelPair{Name: proto.String("foo"), Value: proto.String("bar")},
234+
},
235+
Value: proto.Float64(42),
236+
Timestamp: ts,
237+
}
238+
239+
counter.AddWithExemplar(42, Labels{"foo": "bar"})
240+
if expected, got := expectedExemplar.String(), counter.exemplar.Load().(*dto.Exemplar).String(); expected != got {
241+
t.Errorf("expected exemplar %s, got %s.", expected, got)
242+
}
243+
244+
addExemplarWithInvalidLabel := func() (err error) {
245+
defer func() {
246+
if e := recover(); e != nil {
247+
err = e.(error)
248+
}
249+
}()
250+
// Should panic because of invalid label name.
251+
counter.AddWithExemplar(42, Labels{":o)": "smile"})
252+
return nil
253+
}
254+
if addExemplarWithInvalidLabel() == nil {
255+
t.Error("adding exemplar with invalid label succeeded")
256+
}
257+
258+
addExemplarWithOversizedLabels := func() (err error) {
259+
defer func() {
260+
if e := recover(); e != nil {
261+
err = e.(error)
262+
}
263+
}()
264+
// Should panic because of 65 runes.
265+
counter.AddWithExemplar(42, Labels{
266+
"abcdefghijklmnopqrstuvwxyz": "26+16 characters",
267+
"x1234567": "8+15 characters",
268+
})
269+
return nil
270+
}
271+
if addExemplarWithOversizedLabels() == nil {
272+
t.Error("adding exemplar with oversized labels succeeded")
273+
}
274+
}

prometheus/histogram.go

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -198,6 +198,7 @@ func newHistogram(desc *Desc, opts HistogramOpts, labelValues ...string) Histogr
198198
upperBounds: opts.Buckets,
199199
labelPairs: makeLabelPairs(desc, labelValues),
200200
counts: [2]*histogramCounts{{}, {}},
201+
now: time.Now,
201202
}
202203
for i, upperBound := range h.upperBounds {
203204
if i < len(h.upperBounds)-1 {
@@ -266,6 +267,8 @@ type histogram struct {
266267
upperBounds []float64
267268
labelPairs []*dto.LabelPair
268269
exemplars []atomic.Value // One more than buckets (to include +Inf), each a *dto.Exemplar.
270+
271+
now func() time.Time // To mock out time.Now() for testing.
269272
}
270273

271274
func (h *histogram) Desc() *Desc {
@@ -397,7 +400,7 @@ func (h *histogram) updateExemplar(v float64, bucket int, l Labels) {
397400
if l == nil {
398401
return
399402
}
400-
e, err := newExemplar(v, time.Now(), l)
403+
e, err := newExemplar(v, h.now(), l)
401404
if err != nil {
402405
panic(err)
403406
}

prometheus/histogram_test.go

Lines changed: 74 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,10 @@ import (
2222
"sync"
2323
"testing"
2424
"testing/quick"
25+
"time"
26+
27+
"github.com/golang/protobuf/proto"
28+
"github.com/golang/protobuf/ptypes"
2529

2630
dto "github.com/prometheus/client_model/go"
2731
)
@@ -182,7 +186,11 @@ func TestHistogramConcurrency(t *testing.T) {
182186
go func(vals []float64) {
183187
start.Wait()
184188
for _, v := range vals {
185-
sum.Observe(v)
189+
if n%2 == 0 {
190+
sum.Observe(v)
191+
} else {
192+
sum.ObserveWithExemplar(v, Labels{"foo": "bar"})
193+
}
186194
}
187195
end.Done()
188196
}(vals)
@@ -201,9 +209,13 @@ func TestHistogramConcurrency(t *testing.T) {
201209
}
202210

203211
wantCounts := getCumulativeCounts(allVars)
212+
wantBuckets := len(testBuckets)
213+
if !math.IsInf(m.Histogram.Bucket[len(m.Histogram.Bucket)-1].GetUpperBound(), +1) {
214+
wantBuckets--
215+
}
204216

205-
if got, want := len(m.Histogram.Bucket), len(testBuckets)-1; got != want {
206-
t.Errorf("got %d buckets in protobuf, want %d", got, want)
217+
if got := len(m.Histogram.Bucket); got != wantBuckets {
218+
t.Errorf("got %d buckets in protobuf, want %d", got, wantBuckets)
207219
}
208220
for i, wantBound := range testBuckets {
209221
if i == len(testBuckets)-1 {
@@ -384,3 +396,62 @@ func TestHistogramAtomicObserve(t *testing.T) {
384396
runtime.Gosched()
385397
}
386398
}
399+
400+
func TestHistogramExemplar(t *testing.T) {
401+
now := time.Now()
402+
403+
histogram := NewHistogram(HistogramOpts{
404+
Name: "test",
405+
Help: "test help",
406+
Buckets: []float64{1, 2, 3, 4},
407+
}).(*histogram)
408+
histogram.now = func() time.Time { return now }
409+
410+
ts, err := ptypes.TimestampProto(now)
411+
if err != nil {
412+
t.Fatal(err)
413+
}
414+
expectedExemplars := []*dto.Exemplar{
415+
nil,
416+
&dto.Exemplar{
417+
Label: []*dto.LabelPair{
418+
&dto.LabelPair{Name: proto.String("id"), Value: proto.String("2")},
419+
},
420+
Value: proto.Float64(1.6),
421+
Timestamp: ts,
422+
},
423+
nil,
424+
&dto.Exemplar{
425+
Label: []*dto.LabelPair{
426+
&dto.LabelPair{Name: proto.String("id"), Value: proto.String("3")},
427+
},
428+
Value: proto.Float64(4),
429+
Timestamp: ts,
430+
},
431+
&dto.Exemplar{
432+
Label: []*dto.LabelPair{
433+
&dto.LabelPair{Name: proto.String("id"), Value: proto.String("4")},
434+
},
435+
Value: proto.Float64(4.5),
436+
Timestamp: ts,
437+
},
438+
}
439+
440+
histogram.ObserveWithExemplar(1.5, Labels{"id": "1"})
441+
histogram.ObserveWithExemplar(1.6, Labels{"id": "2"}) // To replace exemplar in bucket 0.
442+
histogram.ObserveWithExemplar(4, Labels{"id": "3"})
443+
histogram.ObserveWithExemplar(4.5, Labels{"id": "4"}) // Should go to +Inf bucket.
444+
445+
for i, ex := range histogram.exemplars {
446+
var got, expected string
447+
if val := ex.Load(); val != nil {
448+
got = val.(*dto.Exemplar).String()
449+
}
450+
if expectedExemplars[i] != nil {
451+
expected = expectedExemplars[i].String()
452+
}
453+
if got != expected {
454+
t.Errorf("expected exemplar %s, got %s.", expected, got)
455+
}
456+
}
457+
}

0 commit comments

Comments
 (0)