From 6790767948416cab3376af4e5c0c6334e2ef0c09 Mon Sep 17 00:00:00 2001 From: Arthur Silva Sens Date: Fri, 28 Jul 2023 16:07:49 -0300 Subject: [PATCH] Implement new function NewConstMetricWithCreatedTimestamp NewConstMetricWithCreatedTimestamp generates constant counters with created timestamp set. It returns errors if it receives other metric types. Signed-off-by: Arthur Silva Sens --- prometheus/counter.go | 4 +-- prometheus/value.go | 49 ++++++++++++++++++++++++++++++--- prometheus/value_test.go | 58 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 106 insertions(+), 5 deletions(-) diff --git a/prometheus/counter.go b/prometheus/counter.go index 07ee441c1..6460cdd86 100644 --- a/prometheus/counter.go +++ b/prometheus/counter.go @@ -165,8 +165,8 @@ func (c *counter) Write(out *dto.Metric) error { exemplar = e.(*dto.Exemplar) } val := c.get() - - return populateMetric(CounterValue, val, c.labelPairs, exemplar, out, c.createdTs) + ct := c.createdTs.AsTime() + return populateMetric(CounterValue, val, c.labelPairs, exemplar, out, &ct) } func (c *counter) updateExemplar(v float64, l Labels) { diff --git a/prometheus/value.go b/prometheus/value.go index a0d59e096..f5665b794 100644 --- a/prometheus/value.go +++ b/prometheus/value.go @@ -14,6 +14,7 @@ package prometheus import ( + "errors" "fmt" "sort" "time" @@ -110,7 +111,7 @@ func NewConstMetric(desc *Desc, valueType ValueType, value float64, labelValues } metric := &dto.Metric{} - if err := populateMetric(valueType, value, MakeLabelPairs(desc, labelValues), nil, metric, timestamppb.Now()); err != nil { + if err := populateMetric(valueType, value, MakeLabelPairs(desc, labelValues), nil, metric, nil); err != nil { return nil, err } @@ -130,6 +131,43 @@ func MustNewConstMetric(desc *Desc, valueType ValueType, value float64, labelVal 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 { desc *Desc metric *dto.Metric @@ -153,12 +191,17 @@ func populateMetric( labelPairs []*dto.LabelPair, e *dto.Exemplar, m *dto.Metric, - cTs *timestamppb.Timestamp, + createdTimestamp *time.Time, ) error { + var ct *timestamppb.Timestamp + if createdTimestamp != nil { + ct = timestamppb.New(*createdTimestamp) + } + m.Label = labelPairs switch t { case CounterValue: - m.Counter = &dto.Counter{Value: proto.Float64(v), Exemplar: e, CreatedTimestamp: cTs} + m.Counter = &dto.Counter{Value: proto.Float64(v), Exemplar: e, CreatedTimestamp: ct} case GaugeValue: m.Gauge = &dto.Gauge{Value: proto.Float64(v)} case UntypedValue: diff --git a/prometheus/value_test.go b/prometheus/value_test.go index 51867b5e4..abdf79669 100644 --- a/prometheus/value_test.go +++ b/prometheus/value_test.go @@ -16,6 +16,10 @@ package prometheus import ( "fmt" "testing" + "time" + + dto "github.com/prometheus/client_model/go" + "google.golang.org/protobuf/types/known/timestamppb" ) func TestNewConstMetricInvalidLabelValues(t *testing.T) { @@ -54,3 +58,57 @@ 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) + } + } + }) + } +}