Skip to content

Commit e79d7e7

Browse files
authored
timer: Added support for exemplars. (#1233)
Signed-off-by: bwplotka <[email protected]>
1 parent 232b949 commit e79d7e7

File tree

2 files changed

+78
-1
lines changed

2 files changed

+78
-1
lines changed

prometheus/timer.go

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,14 +23,24 @@ type Timer struct {
2323
}
2424

2525
// NewTimer creates a new Timer. The provided Observer is used to observe a
26-
// duration in seconds. Timer is usually used to time a function call in the
26+
// duration in seconds. If the Observer implements ExemplarObserver, passing exemplar
27+
// later on will be also supported.
28+
// Timer is usually used to time a function call in the
2729
// following way:
2830
//
2931
// func TimeMe() {
3032
// timer := NewTimer(myHistogram)
3133
// defer timer.ObserveDuration()
3234
// // Do actual work.
3335
// }
36+
//
37+
// or
38+
//
39+
// func TimeMeWithExemplar() {
40+
// timer := NewTimer(myHistogram)
41+
// defer timer.ObserveDurationWithExemplar(exemplar)
42+
// // Do actual work.
43+
// }
3444
func NewTimer(o Observer) *Timer {
3545
return &Timer{
3646
begin: time.Now(),
@@ -53,3 +63,19 @@ func (t *Timer) ObserveDuration() time.Duration {
5363
}
5464
return d
5565
}
66+
67+
// ObserveDurationWithExemplar is like ObserveDuration, but it will also
68+
// observe exemplar with the duration unless exemplar is nil or provided Observer can't
69+
// be casted to ExemplarObserver.
70+
func (t *Timer) ObserveDurationWithExemplar(exemplar Labels) time.Duration {
71+
d := time.Since(t.begin)
72+
eo, ok := t.observer.(ExemplarObserver)
73+
if ok && exemplar != nil {
74+
eo.ObserveWithExemplar(d.Seconds(), exemplar)
75+
return d
76+
}
77+
if t.observer != nil {
78+
t.observer.Observe(d.Seconds())
79+
}
80+
return d
81+
}

prometheus/timer_test.go

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,11 @@
1414
package prometheus
1515

1616
import (
17+
"reflect"
1718
"testing"
1819

20+
"google.golang.org/protobuf/proto"
21+
1922
dto "github.com/prometheus/client_model/go"
2023
)
2124

@@ -52,6 +55,54 @@ func TestTimerObserve(t *testing.T) {
5255
}
5356
}
5457

58+
func TestTimerObserveWithExemplar(t *testing.T) {
59+
var (
60+
exemplar = Labels{"foo": "bar"}
61+
his = NewHistogram(HistogramOpts{Name: "test_histogram"})
62+
sum = NewSummary(SummaryOpts{Name: "test_summary"})
63+
gauge = NewGauge(GaugeOpts{Name: "test_gauge"})
64+
)
65+
66+
func() {
67+
hisTimer := NewTimer(his)
68+
sumTimer := NewTimer(sum)
69+
gaugeTimer := NewTimer(ObserverFunc(gauge.Set))
70+
defer hisTimer.ObserveDurationWithExemplar(exemplar)
71+
// Gauges and summaries does not implement ExemplarObserver, so we expect them to ignore exemplar.
72+
defer sumTimer.ObserveDurationWithExemplar(exemplar)
73+
defer gaugeTimer.ObserveDurationWithExemplar(exemplar)
74+
}()
75+
76+
m := &dto.Metric{}
77+
his.Write(m)
78+
if want, got := uint64(1), m.GetHistogram().GetSampleCount(); want != got {
79+
t.Errorf("want %d observations for histogram, got %d", want, got)
80+
}
81+
var got []*dto.LabelPair
82+
for _, b := range m.GetHistogram().GetBucket() {
83+
if b.Exemplar != nil {
84+
got = b.Exemplar.GetLabel()
85+
break
86+
}
87+
}
88+
89+
want := []*dto.LabelPair{{Name: proto.String("foo"), Value: proto.String("bar")}}
90+
if !reflect.DeepEqual(got, want) {
91+
t.Errorf("expected %v exemplar labels, got %v", want, got)
92+
}
93+
94+
m.Reset()
95+
sum.Write(m)
96+
if want, got := uint64(1), m.GetSummary().GetSampleCount(); want != got {
97+
t.Errorf("want %d observations for summary, got %d", want, got)
98+
}
99+
m.Reset()
100+
gauge.Write(m)
101+
if got := m.GetGauge().GetValue(); got <= 0 {
102+
t.Errorf("want value > 0 for gauge, got %f", got)
103+
}
104+
}
105+
55106
func TestTimerEmpty(t *testing.T) {
56107
emptyTimer := NewTimer(nil)
57108
emptyTimer.ObserveDuration()

0 commit comments

Comments
 (0)