From 1a5f005c914eee4a9f0adf5b0fb19007683a6d99 Mon Sep 17 00:00:00 2001 From: Steven Swartz Date: Mon, 1 Jul 2024 16:35:47 -0400 Subject: [PATCH] Allow creating constant histogram and summary metrics with a created timestamp (#1537) --- prometheus/examples_test.go | 57 ++++++++++++++++++++++++++++++++++++ prometheus/histogram.go | 42 ++++++++++++++++++++++++++ prometheus/histogram_test.go | 25 ++++++++++++++++ prometheus/summary.go | 42 ++++++++++++++++++++++++++ prometheus/summary_test.go | 25 ++++++++++++++++ 5 files changed, 191 insertions(+) diff --git a/prometheus/examples_test.go b/prometheus/examples_test.go index e4fed3e..fdbb6f7 100644 --- a/prometheus/examples_test.go +++ b/prometheus/examples_test.go @@ -405,6 +405,35 @@ func ExampleNewConstSummary() { // {"label":[{"name":"code","value":"200"},{"name":"method","value":"get"},{"name":"owner","value":"example"}],"summary":{"sampleCount":"4711","sampleSum":403.34,"quantile":[{"quantile":0.5,"value":42.3},{"quantile":0.9,"value":323.3}]}} } +func ExampleNewConstSummaryWithCreatedTimestamp() { + desc := prometheus.NewDesc( + "http_request_duration_seconds", + "A summary of the HTTP request durations.", + []string{"code", "method"}, + prometheus.Labels{"owner": "example"}, + ) + + // Create a constant summary with created timestamp set + createdTs := time.Unix(1719670764, 123) + s := prometheus.MustNewConstSummaryWithCreatedTimestamp( + desc, + 4711, 403.34, + map[float64]float64{0.5: 42.3, 0.9: 323.3}, + createdTs, + "200", "get", + ) + + // Just for demonstration, let's check the state of the summary by + // (ab)using its Write method (which is usually only used by Prometheus + // internally). + metric := &dto.Metric{} + s.Write(metric) + fmt.Println(toNormalizedJSON(metric)) + + // Output: + // {"label":[{"name":"code","value":"200"},{"name":"method","value":"get"},{"name":"owner","value":"example"}],"summary":{"sampleCount":"4711","sampleSum":403.34,"quantile":[{"quantile":0.5,"value":42.3},{"quantile":0.9,"value":323.3}],"createdTimestamp":"2024-06-29T14:19:24.000000123Z"}} +} + func ExampleHistogram() { temps := prometheus.NewHistogram(prometheus.HistogramOpts{ Name: "pond_temperature_celsius", @@ -456,6 +485,34 @@ func ExampleNewConstHistogram() { // {"label":[{"name":"code","value":"200"},{"name":"method","value":"get"},{"name":"owner","value":"example"}],"histogram":{"sampleCount":"4711","sampleSum":403.34,"bucket":[{"cumulativeCount":"121","upperBound":25},{"cumulativeCount":"2403","upperBound":50},{"cumulativeCount":"3221","upperBound":100},{"cumulativeCount":"4233","upperBound":200}]}} } +func ExampleNewConstHistogramWithCreatedTimestamp() { + desc := prometheus.NewDesc( + "http_request_duration_seconds", + "A histogram of the HTTP request durations.", + []string{"code", "method"}, + prometheus.Labels{"owner": "example"}, + ) + + createdTs := time.Unix(1719670764, 123) + h := prometheus.MustNewConstHistogramWithCreatedTimestamp( + desc, + 4711, 403.34, + map[float64]uint64{25: 121, 50: 2403, 100: 3221, 200: 4233}, + createdTs, + "200", "get", + ) + + // Just for demonstration, let's check the state of the histogram by + // (ab)using its Write method (which is usually only used by Prometheus + // internally). + metric := &dto.Metric{} + h.Write(metric) + fmt.Println(toNormalizedJSON(metric)) + + // Output: + // {"label":[{"name":"code","value":"200"},{"name":"method","value":"get"},{"name":"owner","value":"example"}],"histogram":{"sampleCount":"4711","sampleSum":403.34,"bucket":[{"cumulativeCount":"121","upperBound":25},{"cumulativeCount":"2403","upperBound":50},{"cumulativeCount":"3221","upperBound":100},{"cumulativeCount":"4233","upperBound":200}],"createdTimestamp":"2024-06-29T14:19:24.000000123Z"}} +} + func ExampleNewConstHistogram_WithExemplar() { desc := prometheus.NewDesc( "http_request_duration_seconds", diff --git a/prometheus/histogram.go b/prometheus/histogram.go index bf5f68a..8d35f2d 100644 --- a/prometheus/histogram.go +++ b/prometheus/histogram.go @@ -1373,6 +1373,48 @@ func MustNewConstHistogram( return m } +// NewConstHistogramWithCreatedTimestamp does the same thing as NewConstHistogram but sets the created timestamp. +func NewConstHistogramWithCreatedTimestamp( + desc *Desc, + count uint64, + sum float64, + buckets map[float64]uint64, + 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 + } + return &constHistogram{ + desc: desc, + count: count, + sum: sum, + buckets: buckets, + labelPairs: MakeLabelPairs(desc, labelValues), + createdTs: timestamppb.New(ct), + }, nil +} + +// MustNewConstHistogramWithCreatedTimestamp is a version of NewConstHistogramWithCreatedTimestamp that panics where +// NewConstHistogramWithCreatedTimestamp would have returned an error. +func MustNewConstHistogramWithCreatedTimestamp( + desc *Desc, + count uint64, + sum float64, + buckets map[float64]uint64, + ct time.Time, + labelValues ...string, +) Metric { + m, err := NewConstHistogramWithCreatedTimestamp(desc, count, sum, buckets, ct, labelValues...) + if err != nil { + panic(err) + } + return m +} + type buckSort []*dto.Bucket func (s buckSort) Len() int { diff --git a/prometheus/histogram_test.go b/prometheus/histogram_test.go index 57fafd3..f2fb5bb 100644 --- a/prometheus/histogram_test.go +++ b/prometheus/histogram_test.go @@ -1272,6 +1272,31 @@ func TestHistogramVecCreatedTimestampWithDeletes(t *testing.T) { expectCTsForMetricVecValues(t, histogramVec.MetricVec, dto.MetricType_HISTOGRAM, expected) } +func TestNewConstHistogramWithCreatedTimestamp(t *testing.T) { + metricDesc := NewDesc( + "sample_value", + "sample value", + nil, + nil, + ) + buckets := map[float64]uint64{25: 100, 50: 200} + createdTs := time.Unix(1719670764, 123) + + h, err := NewConstHistogramWithCreatedTimestamp(metricDesc, 100, 200, buckets, createdTs) + if err != nil { + t.Fatal(err) + } + + var metric dto.Metric + if err := h.Write(&metric); err != nil { + t.Fatal(err) + } + + if metric.Histogram.CreatedTimestamp.AsTime().UnixMicro() != createdTs.UnixMicro() { + t.Errorf("Expected created timestamp %v, got %v", createdTs, &metric.Histogram.CreatedTimestamp) + } +} + func TestNativeHistogramExemplar(t *testing.T) { // Test the histogram with positive NativeHistogramExemplarTTL and NativeHistogramMaxExemplars h := NewHistogram(HistogramOpts{ diff --git a/prometheus/summary.go b/prometheus/summary.go index 1462704..1ab0e47 100644 --- a/prometheus/summary.go +++ b/prometheus/summary.go @@ -783,3 +783,45 @@ func MustNewConstSummary( } return m } + +// NewConstSummaryWithCreatedTimestamp does the same thing as NewConstSummary but sets the created timestamp. +func NewConstSummaryWithCreatedTimestamp( + desc *Desc, + count uint64, + sum float64, + quantiles map[float64]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 + } + return &constSummary{ + desc: desc, + count: count, + sum: sum, + quantiles: quantiles, + labelPairs: MakeLabelPairs(desc, labelValues), + createdTs: timestamppb.New(ct), + }, nil +} + +// MustNewConstSummaryWithCreatedTimestamp is a version of NewConstSummaryWithCreatedTimestamp that panics where +// NewConstSummaryWithCreatedTimestamp would have returned an error. +func MustNewConstSummaryWithCreatedTimestamp( + desc *Desc, + count uint64, + sum float64, + quantiles map[float64]float64, + ct time.Time, + labelValues ...string, +) Metric { + m, err := NewConstSummaryWithCreatedTimestamp(desc, count, sum, quantiles, ct, labelValues...) + if err != nil { + panic(err) + } + return m +} diff --git a/prometheus/summary_test.go b/prometheus/summary_test.go index d1ea072..9c961e9 100644 --- a/prometheus/summary_test.go +++ b/prometheus/summary_test.go @@ -474,3 +474,28 @@ func TestSummaryVecCreatedTimestampWithDeletes(t *testing.T) { }) } } + +func TestNewConstSummaryWithCreatedTimestamp(t *testing.T) { + metricDesc := NewDesc( + "sample_value", + "sample value", + nil, + nil, + ) + quantiles := map[float64]float64{50: 200.12, 99: 500.342} + createdTs := time.Unix(1719670764, 123) + + s, err := NewConstSummaryWithCreatedTimestamp(metricDesc, 100, 200, quantiles, createdTs) + if err != nil { + t.Fatal(err) + } + + var metric dto.Metric + if err := s.Write(&metric); err != nil { + t.Fatal(err) + } + + if metric.Summary.CreatedTimestamp.AsTime().UnixMicro() != createdTs.UnixMicro() { + t.Errorf("Expected created timestamp %v, got %v", createdTs, &metric.Summary.CreatedTimestamp) + } +}