Extend Counters, Summaries and Histograms with creation timestamp
Signed-off-by: Arthur Silva Sens <arthur.sens@coralogix.com>
This commit is contained in:
parent
51d24f8680
commit
3c7e78cf3e
2
go.mod
2
go.mod
|
@ -7,7 +7,7 @@ require (
|
||||||
github.com/cespare/xxhash/v2 v2.2.0
|
github.com/cespare/xxhash/v2 v2.2.0
|
||||||
github.com/davecgh/go-spew v1.1.1
|
github.com/davecgh/go-spew v1.1.1
|
||||||
github.com/json-iterator/go v1.1.12
|
github.com/json-iterator/go v1.1.12
|
||||||
github.com/prometheus/client_model v0.4.0
|
github.com/prometheus/client_model v0.4.1-0.20230718164431-9a2bf3000d16
|
||||||
github.com/prometheus/common v0.44.0
|
github.com/prometheus/common v0.44.0
|
||||||
github.com/prometheus/procfs v0.11.1
|
github.com/prometheus/procfs v0.11.1
|
||||||
golang.org/x/sys v0.11.0
|
golang.org/x/sys v0.11.0
|
||||||
|
|
4
go.sum
4
go.sum
|
@ -34,8 +34,8 @@ github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRW
|
||||||
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
|
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
|
||||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
github.com/prometheus/client_model v0.4.0 h1:5lQXD3cAg1OXBf4Wq03gTrXHeaV0TQvGfUooCfx1yqY=
|
github.com/prometheus/client_model v0.4.1-0.20230718164431-9a2bf3000d16 h1:v7DLqVdK4VrYkVD5diGdl4sxJurKJEMnODWRJlxV9oM=
|
||||||
github.com/prometheus/client_model v0.4.0/go.mod h1:oMQmHW1/JoDwqLtg57MGgP/Fb1CJEYF2imWWhWtMkYU=
|
github.com/prometheus/client_model v0.4.1-0.20230718164431-9a2bf3000d16/go.mod h1:oMQmHW1/JoDwqLtg57MGgP/Fb1CJEYF2imWWhWtMkYU=
|
||||||
github.com/prometheus/common v0.44.0 h1:+5BrQJwiBB9xsMygAB3TNvpQKOwlkc25LbISbrdOOfY=
|
github.com/prometheus/common v0.44.0 h1:+5BrQJwiBB9xsMygAB3TNvpQKOwlkc25LbISbrdOOfY=
|
||||||
github.com/prometheus/common v0.44.0/go.mod h1:ofAIvZbQ1e/nugmZGz4/qCb9Ap1VoSTIO7x0VV9VvuY=
|
github.com/prometheus/common v0.44.0/go.mod h1:ofAIvZbQ1e/nugmZGz4/qCb9Ap1VoSTIO7x0VV9VvuY=
|
||||||
github.com/prometheus/procfs v0.11.1 h1:xRC8Iq1yyca5ypa9n1EZnWZkt7dwcoRPQwX/5gwaUuI=
|
github.com/prometheus/procfs v0.11.1 h1:xRC8Iq1yyca5ypa9n1EZnWZkt7dwcoRPQwX/5gwaUuI=
|
||||||
|
|
|
@ -20,6 +20,7 @@ import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
dto "github.com/prometheus/client_model/go"
|
dto "github.com/prometheus/client_model/go"
|
||||||
|
"google.golang.org/protobuf/types/known/timestamppb"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Counter is a Metric that represents a single numerical value that only ever
|
// Counter is a Metric that represents a single numerical value that only ever
|
||||||
|
@ -90,8 +91,12 @@ func NewCounter(opts CounterOpts) Counter {
|
||||||
nil,
|
nil,
|
||||||
opts.ConstLabels,
|
opts.ConstLabels,
|
||||||
)
|
)
|
||||||
result := &counter{desc: desc, labelPairs: desc.constLabelPairs, now: time.Now}
|
if opts.now == nil {
|
||||||
|
opts.now = time.Now
|
||||||
|
}
|
||||||
|
result := &counter{desc: desc, labelPairs: desc.constLabelPairs, now: opts.now}
|
||||||
result.init(result) // Init self-collection.
|
result.init(result) // Init self-collection.
|
||||||
|
result.createdTs = timestamppb.New(opts.now())
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -106,10 +111,11 @@ type counter struct {
|
||||||
selfCollector
|
selfCollector
|
||||||
desc *Desc
|
desc *Desc
|
||||||
|
|
||||||
|
createdTs *timestamppb.Timestamp
|
||||||
labelPairs []*dto.LabelPair
|
labelPairs []*dto.LabelPair
|
||||||
exemplar atomic.Value // Containing nil or a *dto.Exemplar.
|
exemplar atomic.Value // Containing nil or a *dto.Exemplar.
|
||||||
|
|
||||||
now func() time.Time // To mock out time.Now() for testing.
|
now func() time.Time // For testing, all constructors put time.Now() here.
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *counter) Desc() *Desc {
|
func (c *counter) Desc() *Desc {
|
||||||
|
@ -160,7 +166,8 @@ func (c *counter) Write(out *dto.Metric) error {
|
||||||
}
|
}
|
||||||
val := c.get()
|
val := c.get()
|
||||||
|
|
||||||
return populateMetric(CounterValue, val, c.labelPairs, exemplar, out)
|
ct := c.createdTs.AsTime()
|
||||||
|
return populateMetric(CounterValue, val, c.labelPairs, exemplar, out, &ct)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *counter) updateExemplar(v float64, l Labels) {
|
func (c *counter) updateExemplar(v float64, l Labels) {
|
||||||
|
@ -200,6 +207,9 @@ func (v2) NewCounterVec(opts CounterVecOpts) *CounterVec {
|
||||||
opts.VariableLabels,
|
opts.VariableLabels,
|
||||||
opts.ConstLabels,
|
opts.ConstLabels,
|
||||||
)
|
)
|
||||||
|
if opts.now == nil {
|
||||||
|
opts.now = time.Now
|
||||||
|
}
|
||||||
return &CounterVec{
|
return &CounterVec{
|
||||||
MetricVec: NewMetricVec(desc, func(lvs ...string) Metric {
|
MetricVec: NewMetricVec(desc, func(lvs ...string) Metric {
|
||||||
if len(lvs) != len(desc.variableLabels.names) {
|
if len(lvs) != len(desc.variableLabels.names) {
|
||||||
|
@ -207,6 +217,7 @@ func (v2) NewCounterVec(opts CounterVecOpts) *CounterVec {
|
||||||
}
|
}
|
||||||
result := &counter{desc: desc, labelPairs: MakeLabelPairs(desc, lvs), now: time.Now}
|
result := &counter{desc: desc, labelPairs: MakeLabelPairs(desc, lvs), now: time.Now}
|
||||||
result.init(result) // Init self-collection.
|
result.init(result) // Init self-collection.
|
||||||
|
result.createdTs = timestamppb.New(opts.now())
|
||||||
return result
|
return result
|
||||||
}),
|
}),
|
||||||
}
|
}
|
||||||
|
|
|
@ -298,3 +298,40 @@ func TestCounterExemplar(t *testing.T) {
|
||||||
t.Error("adding exemplar with oversized labels succeeded")
|
t.Error("adding exemplar with oversized labels succeeded")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestCounterCreatedTimestamp(t *testing.T) {
|
||||||
|
now := time.Now()
|
||||||
|
counter := NewCounter(CounterOpts{
|
||||||
|
Name: "test",
|
||||||
|
Help: "test help",
|
||||||
|
now: func() time.Time { return now },
|
||||||
|
})
|
||||||
|
|
||||||
|
var metric dto.Metric
|
||||||
|
if err := counter.Write(&metric); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if metric.Counter.CreatedTimestamp.AsTime().Unix() != now.Unix() {
|
||||||
|
t.Errorf("expected created timestamp %d, got %d", now.Unix(), metric.Counter.CreatedTimestamp.AsTime().Unix())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCounterVecCreatedTimestamp(t *testing.T) {
|
||||||
|
now := time.Now()
|
||||||
|
counterVec := NewCounterVec(CounterOpts{
|
||||||
|
Name: "test",
|
||||||
|
Help: "test help",
|
||||||
|
now: func() time.Time { return now },
|
||||||
|
}, []string{"label"})
|
||||||
|
counter := counterVec.WithLabelValues("value")
|
||||||
|
|
||||||
|
var metric dto.Metric
|
||||||
|
if err := counter.Write(&metric); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if metric.Counter.CreatedTimestamp.AsTime().Unix() != now.Unix() {
|
||||||
|
t.Errorf("expected created timestamp %d, got %d", now.Unix(), metric.Counter.CreatedTimestamp.AsTime().Unix())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -135,7 +135,7 @@ func (g *gauge) Sub(val float64) {
|
||||||
|
|
||||||
func (g *gauge) Write(out *dto.Metric) error {
|
func (g *gauge) Write(out *dto.Metric) error {
|
||||||
val := math.Float64frombits(atomic.LoadUint64(&g.valBits))
|
val := math.Float64frombits(atomic.LoadUint64(&g.valBits))
|
||||||
return populateMetric(GaugeValue, val, g.labelPairs, nil, out)
|
return populateMetric(GaugeValue, val, g.labelPairs, nil, out, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
// GaugeVec is a Collector that bundles a set of Gauges that all share the same
|
// GaugeVec is a Collector that bundles a set of Gauges that all share the same
|
||||||
|
|
|
@ -25,6 +25,7 @@ import (
|
||||||
dto "github.com/prometheus/client_model/go"
|
dto "github.com/prometheus/client_model/go"
|
||||||
|
|
||||||
"google.golang.org/protobuf/proto"
|
"google.golang.org/protobuf/proto"
|
||||||
|
"google.golang.org/protobuf/types/known/timestamppb"
|
||||||
)
|
)
|
||||||
|
|
||||||
// nativeHistogramBounds for the frac of observed values. Only relevant for
|
// nativeHistogramBounds for the frac of observed values. Only relevant for
|
||||||
|
@ -471,6 +472,8 @@ type HistogramOpts struct {
|
||||||
NativeHistogramMaxBucketNumber uint32
|
NativeHistogramMaxBucketNumber uint32
|
||||||
NativeHistogramMinResetDuration time.Duration
|
NativeHistogramMinResetDuration time.Duration
|
||||||
NativeHistogramMaxZeroThreshold float64
|
NativeHistogramMaxZeroThreshold float64
|
||||||
|
|
||||||
|
now func() time.Time // For testing, all constructors put time.Now() here.
|
||||||
}
|
}
|
||||||
|
|
||||||
// HistogramVecOpts bundles the options to create a HistogramVec metric.
|
// HistogramVecOpts bundles the options to create a HistogramVec metric.
|
||||||
|
@ -568,7 +571,11 @@ func newHistogram(desc *Desc, opts HistogramOpts, labelValues ...string) Histogr
|
||||||
atomic.StoreInt32(&h.counts[1].nativeHistogramSchema, h.nativeHistogramSchema)
|
atomic.StoreInt32(&h.counts[1].nativeHistogramSchema, h.nativeHistogramSchema)
|
||||||
h.exemplars = make([]atomic.Value, len(h.upperBounds)+1)
|
h.exemplars = make([]atomic.Value, len(h.upperBounds)+1)
|
||||||
|
|
||||||
|
if opts.now == nil {
|
||||||
|
opts.now = time.Now
|
||||||
|
}
|
||||||
h.init(h) // Init self-collection.
|
h.init(h) // Init self-collection.
|
||||||
|
h.createdTs = timestamppb.New(opts.now())
|
||||||
return h
|
return h
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -707,8 +714,9 @@ type histogram struct {
|
||||||
nativeHistogramMaxBuckets uint32
|
nativeHistogramMaxBuckets uint32
|
||||||
nativeHistogramMinResetDuration time.Duration
|
nativeHistogramMinResetDuration time.Duration
|
||||||
lastResetTime time.Time // Protected by mtx.
|
lastResetTime time.Time // Protected by mtx.
|
||||||
|
createdTs *timestamppb.Timestamp
|
||||||
|
|
||||||
now func() time.Time // To mock out time.Now() for testing.
|
now func() time.Time // For testing, all constructors put time.Now() here.
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *histogram) Desc() *Desc {
|
func (h *histogram) Desc() *Desc {
|
||||||
|
@ -747,9 +755,10 @@ func (h *histogram) Write(out *dto.Metric) error {
|
||||||
waitForCooldown(count, coldCounts)
|
waitForCooldown(count, coldCounts)
|
||||||
|
|
||||||
his := &dto.Histogram{
|
his := &dto.Histogram{
|
||||||
Bucket: make([]*dto.Bucket, len(h.upperBounds)),
|
Bucket: make([]*dto.Bucket, len(h.upperBounds)),
|
||||||
SampleCount: proto.Uint64(count),
|
SampleCount: proto.Uint64(count),
|
||||||
SampleSum: proto.Float64(math.Float64frombits(atomic.LoadUint64(&coldCounts.sumBits))),
|
SampleSum: proto.Float64(math.Float64frombits(atomic.LoadUint64(&coldCounts.sumBits))),
|
||||||
|
CreatedTimestamp: h.createdTs,
|
||||||
}
|
}
|
||||||
out.Histogram = his
|
out.Histogram = his
|
||||||
out.Label = h.labelPairs
|
out.Label = h.labelPairs
|
||||||
|
@ -1194,6 +1203,7 @@ type constHistogram struct {
|
||||||
sum float64
|
sum float64
|
||||||
buckets map[float64]uint64
|
buckets map[float64]uint64
|
||||||
labelPairs []*dto.LabelPair
|
labelPairs []*dto.LabelPair
|
||||||
|
createdTs *timestamppb.Timestamp
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *constHistogram) Desc() *Desc {
|
func (h *constHistogram) Desc() *Desc {
|
||||||
|
@ -1201,7 +1211,9 @@ func (h *constHistogram) Desc() *Desc {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *constHistogram) Write(out *dto.Metric) error {
|
func (h *constHistogram) Write(out *dto.Metric) error {
|
||||||
his := &dto.Histogram{}
|
his := &dto.Histogram{
|
||||||
|
CreatedTimestamp: h.createdTs,
|
||||||
|
}
|
||||||
|
|
||||||
buckets := make([]*dto.Bucket, 0, len(h.buckets))
|
buckets := make([]*dto.Bucket, 0, len(h.buckets))
|
||||||
|
|
||||||
|
|
|
@ -1152,3 +1152,44 @@ func TestGetLe(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestHistogramCreatedTimestamp(t *testing.T) {
|
||||||
|
now := time.Now()
|
||||||
|
|
||||||
|
histogram := NewHistogram(HistogramOpts{
|
||||||
|
Name: "test",
|
||||||
|
Help: "test help",
|
||||||
|
Buckets: []float64{1, 2, 3, 4},
|
||||||
|
now: func() time.Time { return now },
|
||||||
|
})
|
||||||
|
|
||||||
|
var metric dto.Metric
|
||||||
|
if err := histogram.Write(&metric); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if metric.Histogram.CreatedTimestamp.AsTime().Unix() != now.Unix() {
|
||||||
|
t.Errorf("expected created timestamp %d, got %d", now.Unix(), metric.Histogram.CreatedTimestamp.AsTime().Unix())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestHistogramVecCreatedTimestamp(t *testing.T) {
|
||||||
|
now := time.Now()
|
||||||
|
|
||||||
|
histogramVec := NewHistogramVec(HistogramOpts{
|
||||||
|
Name: "test",
|
||||||
|
Help: "test help",
|
||||||
|
Buckets: []float64{1, 2, 3, 4},
|
||||||
|
now: func() time.Time { return now },
|
||||||
|
}, []string{"label"})
|
||||||
|
histogram := histogramVec.WithLabelValues("value").(Histogram)
|
||||||
|
|
||||||
|
var metric dto.Metric
|
||||||
|
if err := histogram.Write(&metric); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if metric.Histogram.CreatedTimestamp.AsTime().Unix() != now.Unix() {
|
||||||
|
t.Errorf("expected created timestamp %d, got %d", now.Unix(), metric.Histogram.CreatedTimestamp.AsTime().Unix())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -92,6 +92,8 @@ type Opts struct {
|
||||||
// machine_role metric). See also
|
// machine_role metric). See also
|
||||||
// https://prometheus.io/docs/instrumenting/writing_exporters/#target-labels-not-static-scraped-labels
|
// https://prometheus.io/docs/instrumenting/writing_exporters/#target-labels-not-static-scraped-labels
|
||||||
ConstLabels Labels
|
ConstLabels Labels
|
||||||
|
|
||||||
|
now func() time.Time // For testing, all constructors put time.Now() here.
|
||||||
}
|
}
|
||||||
|
|
||||||
// BuildFQName joins the given three name components by "_". Empty name
|
// BuildFQName joins the given three name components by "_". Empty name
|
||||||
|
|
|
@ -26,6 +26,7 @@ import (
|
||||||
|
|
||||||
"github.com/beorn7/perks/quantile"
|
"github.com/beorn7/perks/quantile"
|
||||||
"google.golang.org/protobuf/proto"
|
"google.golang.org/protobuf/proto"
|
||||||
|
"google.golang.org/protobuf/types/known/timestamppb"
|
||||||
)
|
)
|
||||||
|
|
||||||
// quantileLabel is used for the label that defines the quantile in a
|
// quantileLabel is used for the label that defines the quantile in a
|
||||||
|
@ -145,6 +146,8 @@ type SummaryOpts struct {
|
||||||
// is the internal buffer size of the underlying package
|
// is the internal buffer size of the underlying package
|
||||||
// "github.com/bmizerany/perks/quantile").
|
// "github.com/bmizerany/perks/quantile").
|
||||||
BufCap uint32
|
BufCap uint32
|
||||||
|
|
||||||
|
now func() time.Time // For testing, all constructors put time.Now() here.
|
||||||
}
|
}
|
||||||
|
|
||||||
// SummaryVecOpts bundles the options to create a SummaryVec metric.
|
// SummaryVecOpts bundles the options to create a SummaryVec metric.
|
||||||
|
@ -222,6 +225,9 @@ func newSummary(desc *Desc, opts SummaryOpts, labelValues ...string) Summary {
|
||||||
opts.BufCap = DefBufCap
|
opts.BufCap = DefBufCap
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if opts.now == nil {
|
||||||
|
opts.now = time.Now
|
||||||
|
}
|
||||||
if len(opts.Objectives) == 0 {
|
if len(opts.Objectives) == 0 {
|
||||||
// Use the lock-free implementation of a Summary without objectives.
|
// Use the lock-free implementation of a Summary without objectives.
|
||||||
s := &noObjectivesSummary{
|
s := &noObjectivesSummary{
|
||||||
|
@ -230,6 +236,7 @@ func newSummary(desc *Desc, opts SummaryOpts, labelValues ...string) Summary {
|
||||||
counts: [2]*summaryCounts{{}, {}},
|
counts: [2]*summaryCounts{{}, {}},
|
||||||
}
|
}
|
||||||
s.init(s) // Init self-collection.
|
s.init(s) // Init self-collection.
|
||||||
|
s.createdTs = timestamppb.New(opts.now())
|
||||||
return s
|
return s
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -259,6 +266,7 @@ func newSummary(desc *Desc, opts SummaryOpts, labelValues ...string) Summary {
|
||||||
sort.Float64s(s.sortedObjectives)
|
sort.Float64s(s.sortedObjectives)
|
||||||
|
|
||||||
s.init(s) // Init self-collection.
|
s.init(s) // Init self-collection.
|
||||||
|
s.createdTs = timestamppb.New(opts.now())
|
||||||
return s
|
return s
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -286,6 +294,8 @@ type summary struct {
|
||||||
headStream *quantile.Stream
|
headStream *quantile.Stream
|
||||||
headStreamIdx int
|
headStreamIdx int
|
||||||
headStreamExpTime, hotBufExpTime time.Time
|
headStreamExpTime, hotBufExpTime time.Time
|
||||||
|
|
||||||
|
createdTs *timestamppb.Timestamp
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *summary) Desc() *Desc {
|
func (s *summary) Desc() *Desc {
|
||||||
|
@ -307,7 +317,9 @@ func (s *summary) Observe(v float64) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *summary) Write(out *dto.Metric) error {
|
func (s *summary) Write(out *dto.Metric) error {
|
||||||
sum := &dto.Summary{}
|
sum := &dto.Summary{
|
||||||
|
CreatedTimestamp: s.createdTs,
|
||||||
|
}
|
||||||
qs := make([]*dto.Quantile, 0, len(s.objectives))
|
qs := make([]*dto.Quantile, 0, len(s.objectives))
|
||||||
|
|
||||||
s.bufMtx.Lock()
|
s.bufMtx.Lock()
|
||||||
|
@ -440,6 +452,8 @@ type noObjectivesSummary struct {
|
||||||
counts [2]*summaryCounts
|
counts [2]*summaryCounts
|
||||||
|
|
||||||
labelPairs []*dto.LabelPair
|
labelPairs []*dto.LabelPair
|
||||||
|
|
||||||
|
createdTs *timestamppb.Timestamp
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *noObjectivesSummary) Desc() *Desc {
|
func (s *noObjectivesSummary) Desc() *Desc {
|
||||||
|
@ -490,8 +504,9 @@ func (s *noObjectivesSummary) Write(out *dto.Metric) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
sum := &dto.Summary{
|
sum := &dto.Summary{
|
||||||
SampleCount: proto.Uint64(count),
|
SampleCount: proto.Uint64(count),
|
||||||
SampleSum: proto.Float64(math.Float64frombits(atomic.LoadUint64(&coldCounts.sumBits))),
|
SampleSum: proto.Float64(math.Float64frombits(atomic.LoadUint64(&coldCounts.sumBits))),
|
||||||
|
CreatedTimestamp: s.createdTs,
|
||||||
}
|
}
|
||||||
|
|
||||||
out.Summary = sum
|
out.Summary = sum
|
||||||
|
@ -681,6 +696,7 @@ type constSummary struct {
|
||||||
sum float64
|
sum float64
|
||||||
quantiles map[float64]float64
|
quantiles map[float64]float64
|
||||||
labelPairs []*dto.LabelPair
|
labelPairs []*dto.LabelPair
|
||||||
|
createdTs *timestamppb.Timestamp
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *constSummary) Desc() *Desc {
|
func (s *constSummary) Desc() *Desc {
|
||||||
|
@ -688,7 +704,9 @@ func (s *constSummary) Desc() *Desc {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *constSummary) Write(out *dto.Metric) error {
|
func (s *constSummary) Write(out *dto.Metric) error {
|
||||||
sum := &dto.Summary{}
|
sum := &dto.Summary{
|
||||||
|
CreatedTimestamp: s.createdTs,
|
||||||
|
}
|
||||||
qs := make([]*dto.Quantile, 0, len(s.quantiles))
|
qs := make([]*dto.Quantile, 0, len(s.quantiles))
|
||||||
|
|
||||||
sum.SampleCount = proto.Uint64(s.count)
|
sum.SampleCount = proto.Uint64(s.count)
|
||||||
|
|
|
@ -420,3 +420,78 @@ func getBounds(vars []float64, q, ε float64) (min, max float64) {
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestSummaryCreatedTimestamp(t *testing.T) {
|
||||||
|
testCases := []struct {
|
||||||
|
desc string
|
||||||
|
objectives map[float64]float64
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
desc: "summary with objectives",
|
||||||
|
objectives: map[float64]float64{
|
||||||
|
1.0: 1.0,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "no objectives summary",
|
||||||
|
objectives: nil,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
now := time.Now()
|
||||||
|
for _, test := range testCases {
|
||||||
|
test := test
|
||||||
|
t.Run(test.desc, func(t *testing.T) {
|
||||||
|
summary := NewSummary(SummaryOpts{
|
||||||
|
Name: "test",
|
||||||
|
Help: "test help",
|
||||||
|
Objectives: test.objectives,
|
||||||
|
now: func() time.Time { return now },
|
||||||
|
})
|
||||||
|
|
||||||
|
var metric dto.Metric
|
||||||
|
summary.Write(&metric)
|
||||||
|
|
||||||
|
if metric.Summary.CreatedTimestamp.AsTime().Unix() != now.Unix() {
|
||||||
|
t.Errorf("expected created timestamp %d, got %d", now.Unix(), metric.Summary.CreatedTimestamp.AsTime().Unix())
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSummaryVecCreatedTimestamp(t *testing.T) {
|
||||||
|
testCases := []struct {
|
||||||
|
desc string
|
||||||
|
objectives map[float64]float64
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
desc: "summary with objectives",
|
||||||
|
objectives: map[float64]float64{
|
||||||
|
1.0: 1.0,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "no objectives summary",
|
||||||
|
objectives: nil,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
now := time.Now()
|
||||||
|
for _, test := range testCases {
|
||||||
|
test := test
|
||||||
|
t.Run(test.desc, func(t *testing.T) {
|
||||||
|
summaryVec := NewSummaryVec(SummaryOpts{
|
||||||
|
Name: "test",
|
||||||
|
Help: "test help",
|
||||||
|
Objectives: test.objectives,
|
||||||
|
now: func() time.Time { return now },
|
||||||
|
},
|
||||||
|
[]string{"label"})
|
||||||
|
summary := summaryVec.WithLabelValues("value").(Summary)
|
||||||
|
var metric dto.Metric
|
||||||
|
summary.Write(&metric)
|
||||||
|
|
||||||
|
if metric.Summary.CreatedTimestamp.AsTime().Unix() != now.Unix() {
|
||||||
|
t.Errorf("expected created timestamp %d, got %d", now.Unix(), metric.Summary.CreatedTimestamp.AsTime().Unix())
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -14,6 +14,7 @@
|
||||||
package prometheus
|
package prometheus
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"sort"
|
"sort"
|
||||||
"time"
|
"time"
|
||||||
|
@ -91,7 +92,7 @@ func (v *valueFunc) Desc() *Desc {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (v *valueFunc) Write(out *dto.Metric) error {
|
func (v *valueFunc) Write(out *dto.Metric) error {
|
||||||
return populateMetric(v.valType, v.function(), v.labelPairs, nil, out)
|
return populateMetric(v.valType, v.function(), v.labelPairs, nil, out, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewConstMetric returns a metric with one fixed value that cannot be
|
// NewConstMetric returns a metric with one fixed value that cannot be
|
||||||
|
@ -110,7 +111,7 @@ func NewConstMetric(desc *Desc, valueType ValueType, value float64, labelValues
|
||||||
}
|
}
|
||||||
|
|
||||||
metric := &dto.Metric{}
|
metric := &dto.Metric{}
|
||||||
if err := populateMetric(valueType, value, MakeLabelPairs(desc, labelValues), nil, metric); err != nil {
|
if err := populateMetric(valueType, value, MakeLabelPairs(desc, labelValues), nil, metric, nil); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -130,6 +131,43 @@ func MustNewConstMetric(desc *Desc, valueType ValueType, value float64, labelVal
|
||||||
return m
|
return m
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NewConstMetricWithCreatedTimestamp does the same thing as NewConstMetric, but generates Counters
|
||||||
|
// with created timestamp set and returns an error for other metric types.
|
||||||
|
func NewConstMetricWithCreatedTimestamp(desc *Desc, valueType ValueType, value float64, ct time.Time, labelValues ...string) (Metric, error) {
|
||||||
|
if desc.err != nil {
|
||||||
|
return nil, desc.err
|
||||||
|
}
|
||||||
|
if err := validateLabelValues(labelValues, len(desc.variableLabels.names)); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
switch valueType {
|
||||||
|
case CounterValue:
|
||||||
|
break
|
||||||
|
default:
|
||||||
|
return nil, errors.New("Created timestamps are only supported for counters")
|
||||||
|
}
|
||||||
|
|
||||||
|
metric := &dto.Metric{}
|
||||||
|
if err := populateMetric(valueType, value, MakeLabelPairs(desc, labelValues), nil, metric, &ct); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &constMetric{
|
||||||
|
desc: desc,
|
||||||
|
metric: metric,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// MustNewConstMetricWithCreatedTimestamp is a version of NewConstMetricWithCreatedTimestamp that panics where
|
||||||
|
// NewConstMetricWithCreatedTimestamp would have returned an error.
|
||||||
|
func MustNewConstMetricWithCreatedTimestamp(desc *Desc, valueType ValueType, value float64, ct time.Time, labelValues ...string) Metric {
|
||||||
|
m, err := NewConstMetricWithCreatedTimestamp(desc, valueType, value, ct, labelValues...)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
return m
|
||||||
|
}
|
||||||
|
|
||||||
type constMetric struct {
|
type constMetric struct {
|
||||||
desc *Desc
|
desc *Desc
|
||||||
metric *dto.Metric
|
metric *dto.Metric
|
||||||
|
@ -153,11 +191,16 @@ func populateMetric(
|
||||||
labelPairs []*dto.LabelPair,
|
labelPairs []*dto.LabelPair,
|
||||||
e *dto.Exemplar,
|
e *dto.Exemplar,
|
||||||
m *dto.Metric,
|
m *dto.Metric,
|
||||||
|
createdTimestamp *time.Time,
|
||||||
) error {
|
) error {
|
||||||
m.Label = labelPairs
|
m.Label = labelPairs
|
||||||
switch t {
|
switch t {
|
||||||
case CounterValue:
|
case CounterValue:
|
||||||
m.Counter = &dto.Counter{Value: proto.Float64(v), Exemplar: e}
|
var ct *timestamppb.Timestamp
|
||||||
|
if createdTimestamp != nil {
|
||||||
|
ct = timestamppb.New(*createdTimestamp)
|
||||||
|
}
|
||||||
|
m.Counter = &dto.Counter{Value: proto.Float64(v), Exemplar: e, CreatedTimestamp: ct}
|
||||||
case GaugeValue:
|
case GaugeValue:
|
||||||
m.Gauge = &dto.Gauge{Value: proto.Float64(v)}
|
m.Gauge = &dto.Gauge{Value: proto.Float64(v)}
|
||||||
case UntypedValue:
|
case UntypedValue:
|
||||||
|
|
|
@ -16,6 +16,10 @@ package prometheus
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"testing"
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
dto "github.com/prometheus/client_model/go"
|
||||||
|
"google.golang.org/protobuf/types/known/timestamppb"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestNewConstMetricInvalidLabelValues(t *testing.T) {
|
func TestNewConstMetricInvalidLabelValues(t *testing.T) {
|
||||||
|
@ -54,3 +58,56 @@ func TestNewConstMetricInvalidLabelValues(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestNewConstMetricWithCreatedTimestamp(t *testing.T) {
|
||||||
|
now := time.Now()
|
||||||
|
testCases := []struct {
|
||||||
|
desc string
|
||||||
|
metricType ValueType
|
||||||
|
createdTimestamp time.Time
|
||||||
|
expecErr bool
|
||||||
|
expectedCt *timestamppb.Timestamp
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
desc: "gauge with CT",
|
||||||
|
metricType: GaugeValue,
|
||||||
|
createdTimestamp: now,
|
||||||
|
expecErr: true,
|
||||||
|
expectedCt: nil,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "counter with CT",
|
||||||
|
metricType: CounterValue,
|
||||||
|
createdTimestamp: now,
|
||||||
|
expecErr: false,
|
||||||
|
expectedCt: timestamppb.New(now),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range testCases {
|
||||||
|
test := test
|
||||||
|
t.Run(test.desc, func(t *testing.T) {
|
||||||
|
metricDesc := NewDesc(
|
||||||
|
"sample_value",
|
||||||
|
"sample value",
|
||||||
|
nil,
|
||||||
|
nil,
|
||||||
|
)
|
||||||
|
m, err := NewConstMetricWithCreatedTimestamp(metricDesc, test.metricType, float64(1), test.createdTimestamp)
|
||||||
|
if test.expecErr && err == nil {
|
||||||
|
t.Errorf("Expected error is test %s, got no err", test.desc)
|
||||||
|
}
|
||||||
|
if !test.expecErr && err != nil {
|
||||||
|
t.Errorf("Didn't expect error in test %s, got %s", test.desc, err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
if test.expectedCt != nil {
|
||||||
|
var metric dto.Metric
|
||||||
|
m.Write(&metric)
|
||||||
|
if metric.Counter.CreatedTimestamp.AsTime() != test.expectedCt.AsTime() {
|
||||||
|
t.Errorf("Expected timestamp %v, got %v", test.expectedCt, &metric.Counter.CreatedTimestamp)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue