timer: Added support for exemplars. (#1233)

Signed-off-by: bwplotka <bwplotka@gmail.com>
This commit is contained in:
Bartlomiej Plotka 2023-03-21 20:35:31 +01:00 committed by GitHub
parent 232b949d1f
commit e79d7e71ce
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 78 additions and 1 deletions

View File

@ -23,7 +23,9 @@ type Timer struct {
} }
// NewTimer creates a new Timer. The provided Observer is used to observe a // 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: // following way:
// //
// func TimeMe() { // func TimeMe() {
@ -31,6 +33,14 @@ type Timer struct {
// defer timer.ObserveDuration() // defer timer.ObserveDuration()
// // Do actual work. // // Do actual work.
// } // }
//
// or
//
// func TimeMeWithExemplar() {
// timer := NewTimer(myHistogram)
// defer timer.ObserveDurationWithExemplar(exemplar)
// // Do actual work.
// }
func NewTimer(o Observer) *Timer { func NewTimer(o Observer) *Timer {
return &Timer{ return &Timer{
begin: time.Now(), begin: time.Now(),
@ -53,3 +63,19 @@ func (t *Timer) ObserveDuration() time.Duration {
} }
return d 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
}

View File

@ -14,8 +14,11 @@
package prometheus package prometheus
import ( import (
"reflect"
"testing" "testing"
"google.golang.org/protobuf/proto"
dto "github.com/prometheus/client_model/go" dto "github.com/prometheus/client_model/go"
) )
@ -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) { func TestTimerEmpty(t *testing.T) {
emptyTimer := NewTimer(nil) emptyTimer := NewTimer(nil)
emptyTimer.ObserveDuration() emptyTimer.ObserveDuration()