Skip to content

Commit

Permalink
Merge pull request #706 from prometheus/beorn7/exemplars
Browse files Browse the repository at this point in the history
Add exemplars to counter and histogram
  • Loading branch information
beorn7 committed Jan 24, 2020
2 parents 803ef2a + c32ffd1 commit ba79017
Show file tree
Hide file tree
Showing 10 changed files with 369 additions and 64 deletions.
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

0 comments on commit ba79017

Please sign in to comment.