Skip to content

Commit

Permalink
timer: Added support for exemplars.
Browse files Browse the repository at this point in the history
Signed-off-by: bwplotka <bwplotka@gmail.com>
  • Loading branch information
bwplotka committed Mar 17, 2023
1 parent 3d2cf0b commit b1a1576
Show file tree
Hide file tree
Showing 2 changed files with 78 additions and 1 deletion.
29 changes: 28 additions & 1 deletion prometheus/timer.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,14 +23,24 @@ type Timer struct {
}

// NewTimer creates a new Timer. The provided Observer is used to observe a
// duration in seconds. Timer is usually used to time a function call in the
// duration in seconds. If the Observer implements ExemplarObserver, passing exemplar
// later on will be also supported.
// Timer is usually used to time a function call in the
// following way:
//
// func TimeMe() {
// timer := NewTimer(myHistogram)
// defer timer.ObserveDuration()
// // Do actual work.
// }
//
// or
//
// func TimeMeWithExemplar() {
// timer := NewTimer(myHistogram)
// defer timer.ObserveDurationWithExemplar(exemplar)
// // Do actual work.
// }
func NewTimer(o Observer) *Timer {
return &Timer{
begin: time.Now(),
Expand All @@ -53,3 +63,20 @@ func (t *Timer) ObserveDuration() time.Duration {
}
return d
}

// ObserveDurationWithExemplar is like ObserveDuration, but it will also
// observe exemplar with the duration unless exemplar is nil or provided Observer can't
// be casted to ExemplarObserver.
func (t *Timer) ObserveDurationWithExemplar(exemplar Labels) time.Duration {
d := time.Since(t.begin)
eo, ok := t.observer.(ExemplarObserver)
if !ok || exemplar == nil {
if t.observer != nil {
t.observer.Observe(d.Seconds())
}
return d
}

eo.ObserveWithExemplar(d.Seconds(), exemplar)
return d
}
50 changes: 50 additions & 0 deletions prometheus/timer_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@
package prometheus

import (
"google.golang.org/protobuf/proto"
"reflect"
"testing"

dto "github.com/prometheus/client_model/go"
Expand Down Expand Up @@ -52,6 +54,54 @@ func TestTimerObserve(t *testing.T) {
}
}

func TestTimerObserveWithExemplar(t *testing.T) {
var (
exemplar = Labels{"foo": "bar"}
his = NewHistogram(HistogramOpts{Name: "test_histogram"})
sum = NewSummary(SummaryOpts{Name: "test_summary"})
gauge = NewGauge(GaugeOpts{Name: "test_gauge"})
)

func() {
hisTimer := NewTimer(his)
sumTimer := NewTimer(sum)
gaugeTimer := NewTimer(ObserverFunc(gauge.Set))
defer hisTimer.ObserveDurationWithExemplar(exemplar)
// Gauges and summaries does not implement ExemplarObserver, so we expect them to ignore exemplar.
defer sumTimer.ObserveDurationWithExemplar(exemplar)
defer gaugeTimer.ObserveDurationWithExemplar(exemplar)
}()

m := &dto.Metric{}
his.Write(m)
if want, got := uint64(1), m.GetHistogram().GetSampleCount(); want != got {
t.Errorf("want %d observations for histogram, got %d", want, got)
}
var got []*dto.LabelPair
for _, b := range m.GetHistogram().GetBucket() {
if b.Exemplar != nil {
got = b.Exemplar.GetLabel()
break
}
}

want := []*dto.LabelPair{{Name: proto.String("foo"), Value: proto.String("bar")}}
if !reflect.DeepEqual(got, want) {
t.Errorf("expected %v exemplar labels, got %v", want, got)
}

m.Reset()
sum.Write(m)
if want, got := uint64(1), m.GetSummary().GetSampleCount(); want != got {
t.Errorf("want %d observations for summary, got %d", want, got)
}
m.Reset()
gauge.Write(m)
if got := m.GetGauge().GetValue(); got <= 0 {
t.Errorf("want value > 0 for gauge, got %f", got)
}
}

func TestTimerEmpty(t *testing.T) {
emptyTimer := NewTimer(nil)
emptyTimer.ObserveDuration()
Expand Down

0 comments on commit b1a1576

Please sign in to comment.