Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

timer: Added support for exemplars. #1233

Merged
merged 1 commit into from Mar 21, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
28 changes: 27 additions & 1 deletion prometheus/timer.go
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,19 @@ 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 {
eo.ObserveWithExemplar(d.Seconds(), exemplar)
return d
}
if t.observer != nil {
t.observer.Observe(d.Seconds())
}
return d
}
51 changes: 51 additions & 0 deletions prometheus/timer_test.go
Expand Up @@ -14,8 +14,11 @@
package prometheus

import (
"reflect"
"testing"

"google.golang.org/protobuf/proto"

dto "github.com/prometheus/client_model/go"
)

Expand Down Expand Up @@ -52,6 +55,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