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

Add exemplars to counter and histogram #706

Merged
merged 2 commits into from Jan 24, 2020
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
15 changes: 13 additions & 2 deletions examples/random/main.go
Expand Up @@ -18,6 +18,7 @@ package main

import (
"flag"
"fmt"
"log"
"math"
"math/rand"
Expand Down Expand Up @@ -89,7 +90,11 @@ func main() {
for {
v := (rand.NormFloat64() * *normDomain) + *normMean
rpcDurations.WithLabelValues("normal").Observe(v)
rpcDurationsHistogram.Observe(v)
rpcDurationsHistogram.ObserveWithExemplar(
// Demonstrate exemplar support with a dummy ID. This would be
// something like a trace ID in a real application.
v, prometheus.Labels{"dummyID": fmt.Sprint(rand.Intn(100000))},
)
time.Sleep(time.Duration(75*oscillationFactor()) * time.Millisecond)
}
}()
Expand All @@ -103,6 +108,12 @@ func main() {
}()

// Expose the registered metrics via HTTP.
http.Handle("/metrics", promhttp.Handler())
http.Handle("/metrics", promhttp.HandlerFor(
prometheus.DefaultGatherer,
promhttp.HandlerOpts{
// Opt into OpenMetrics to support exemplars.
EnableOpenMetrics: true,
},
))
log.Fatal(http.ListenAndServe(*addr, nil))
}
4 changes: 2 additions & 2 deletions go.mod
Expand Up @@ -5,8 +5,8 @@ require (
github.com/cespare/xxhash/v2 v2.1.1
github.com/golang/protobuf v1.3.2
github.com/json-iterator/go v1.1.8
github.com/prometheus/client_model v0.1.0
github.com/prometheus/common v0.7.0
github.com/prometheus/client_model v0.2.0
github.com/prometheus/common v0.9.0
github.com/prometheus/procfs v0.0.8
golang.org/x/sys v0.0.0-20191220142924-d4481acd189f
)
Expand Down
10 changes: 5 additions & 5 deletions go.sum
Expand Up @@ -58,12 +58,12 @@ github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910 h1:idejC8f
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90 h1:S/YWwWx/RA8rT8tKFRuGUZhuA90OyIBpPCXkcbwU8DE=
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/client_model v0.1.0 h1:ElTg5tNp4DqfV7UQjDqv2+RJlNzsDtvNAWccbItceIE=
github.com/prometheus/client_model v0.1.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/client_model v0.2.0 h1:uq5h0d+GuxiXLJLNABMgp2qUWDPiLvgCzz2dUR+/W/M=
github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/common v0.4.1 h1:K0MGApIoQvMw27RTdJkPbr3JZ7DNbtxQNyi5STVM6Kw=
github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
github.com/prometheus/common v0.7.0 h1:L+1lyG48J1zAQXA3RBX/nG/B3gjlHq0zTt2tlbJLyCY=
github.com/prometheus/common v0.7.0/go.mod h1:DjGbpBbp5NYNiECxcL/VnbXCCaQpKd3tt26CguLLsqA=
github.com/prometheus/common v0.9.0 h1:yg//x/8DqN+PxXTBFMwVCopGqDn3wSxmbF/3PCuu1bk=
github.com/prometheus/common v0.9.0/go.mod h1:yhUN8i9wzaXS3w1O07YhxHEBxD+W35wd8bs7vj7HSQ4=
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
github.com/prometheus/procfs v0.0.2 h1:6LJUbpNm42llc4HRCuvApCSWB/WfhuNo9K98Q9sNGfs=
github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
Expand Down Expand Up @@ -97,4 +97,4 @@ golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
38 changes: 36 additions & 2 deletions prometheus/counter.go
Expand Up @@ -17,6 +17,7 @@ import (
"errors"
"math"
"sync/atomic"
"time"

dto "github.com/prometheus/client_model/go"
)
Expand All @@ -40,6 +41,14 @@ type Counter interface {
// Add adds the given value to the counter. It panics if the value is <
// 0.
Add(float64)
// AddWithExemplar works like Add but also replaces the currently saved
// exemplar (if any) with a new one, created from the provided value,
// the current time as timestamp, and the provided labels. Empty Labels
// will lead to a valid (label-less) exemplar. But if Labels is nil, the
// current exemplar is left in place. This method panics if the value is
// < 0, if any of the provided labels are invalid, or if the provided
// labels contain more than 64 runes in total.
AddWithExemplar(value float64, exemplar Labels)
}

// CounterOpts is an alias for Opts. See there for doc comments.
Expand All @@ -61,7 +70,7 @@ func NewCounter(opts CounterOpts) Counter {
nil,
opts.ConstLabels,
)
result := &counter{desc: desc, labelPairs: desc.constLabelPairs}
result := &counter{desc: desc, labelPairs: desc.constLabelPairs, now: time.Now}
result.init(result) // Init self-collection.
return result
}
Expand All @@ -78,6 +87,9 @@ type counter struct {
desc *Desc

labelPairs []*dto.LabelPair
exemplar atomic.Value // Containing nil or a *dto.Exemplar.

now func() time.Time // To mock out time.Now() for testing.
}

func (c *counter) Desc() *Desc {
Expand All @@ -88,6 +100,7 @@ func (c *counter) Add(v float64) {
if v < 0 {
panic(errors.New("counter cannot decrease in value"))
}

ival := uint64(v)
if float64(ival) == v {
atomic.AddUint64(&c.valInt, ival)
Expand All @@ -103,6 +116,11 @@ func (c *counter) Add(v float64) {
}
}

func (c *counter) AddWithExemplar(v float64, e Labels) {
c.Add(v)
c.updateExemplar(v, e)
}

func (c *counter) Inc() {
atomic.AddUint64(&c.valInt, 1)
}
Expand All @@ -112,7 +130,23 @@ func (c *counter) Write(out *dto.Metric) error {
ival := atomic.LoadUint64(&c.valInt)
val := fval + float64(ival)

return populateMetric(CounterValue, val, c.labelPairs, out)
var exemplar *dto.Exemplar
if e := c.exemplar.Load(); e != nil {
exemplar = e.(*dto.Exemplar)
}

return populateMetric(CounterValue, val, c.labelPairs, exemplar, out)
}

func (c *counter) updateExemplar(v float64, l Labels) {
if l == nil {
return
}
e, err := newExemplar(v, c.now(), l)
if err != nil {
panic(err)
}
c.exemplar.Store(e)
}

// CounterVec is a Collector that bundles a set of Counters that all share the
Expand Down
62 changes: 62 additions & 0 deletions prometheus/counter_test.go
Expand Up @@ -17,6 +17,10 @@ import (
"fmt"
"math"
"testing"
"time"

"github.com/golang/protobuf/proto"
"github.com/golang/protobuf/ptypes"

dto "github.com/prometheus/client_model/go"
)
Expand Down Expand Up @@ -210,3 +214,61 @@ func TestCounterAddSmall(t *testing.T) {
t.Errorf("expected %q, got %q", expected, got)
}
}

func TestCounterExemplar(t *testing.T) {
now := time.Now()

counter := NewCounter(CounterOpts{
Name: "test",
Help: "test help",
}).(*counter)
counter.now = func() time.Time { return now }

ts, err := ptypes.TimestampProto(now)
if err != nil {
t.Fatal(err)
}
expectedExemplar := &dto.Exemplar{
Label: []*dto.LabelPair{
&dto.LabelPair{Name: proto.String("foo"), Value: proto.String("bar")},
},
Value: proto.Float64(42),
Timestamp: ts,
}

counter.AddWithExemplar(42, Labels{"foo": "bar"})
if expected, got := expectedExemplar.String(), counter.exemplar.Load().(*dto.Exemplar).String(); expected != got {
t.Errorf("expected exemplar %s, got %s.", expected, got)
}

addExemplarWithInvalidLabel := func() (err error) {
defer func() {
if e := recover(); e != nil {
err = e.(error)
}
}()
// Should panic because of invalid label name.
counter.AddWithExemplar(42, Labels{":o)": "smile"})
return nil
}
if addExemplarWithInvalidLabel() == nil {
t.Error("adding exemplar with invalid label succeeded")
}

addExemplarWithOversizedLabels := func() (err error) {
defer func() {
if e := recover(); e != nil {
err = e.(error)
}
}()
// Should panic because of 65 runes.
counter.AddWithExemplar(42, Labels{
"abcdefghijklmnopqrstuvwxyz": "26+16 characters",
"x1234567": "8+15 characters",
})
return nil
}
if addExemplarWithOversizedLabels() == nil {
t.Error("adding exemplar with oversized labels succeeded")
}
}
2 changes: 1 addition & 1 deletion prometheus/gauge.go
Expand Up @@ -123,7 +123,7 @@ func (g *gauge) Sub(val float64) {

func (g *gauge) Write(out *dto.Metric) error {
val := math.Float64frombits(atomic.LoadUint64(&g.valBits))
return populateMetric(GaugeValue, val, g.labelPairs, out)
return populateMetric(GaugeValue, val, g.labelPairs, nil, out)
}

// GaugeVec is a Collector that bundles a set of Gauges that all share the same
Expand Down