From ef8e6ae78040613fdc1ceb25f22b76f87dd2c80a Mon Sep 17 00:00:00 2001 From: Arthur Silva Sens Date: Wed, 18 Oct 2023 13:04:02 -0500 Subject: [PATCH] Parse created timestamps from Prometheus Protobuf (#12973) Signed-off-by: Arthur Silva Sens --- model/textparse/interface.go | 7 + model/textparse/openmetricsparse.go | 7 + model/textparse/promparse.go | 7 + model/textparse/protobufparse.go | 19 ++ model/textparse/protobufparse_test.go | 239 +++++++++++++++++++++++++- 5 files changed, 276 insertions(+), 3 deletions(-) diff --git a/model/textparse/interface.go b/model/textparse/interface.go index 38903afc9..2f5fdbc3b 100644 --- a/model/textparse/interface.go +++ b/model/textparse/interface.go @@ -16,6 +16,8 @@ package textparse import ( "mime" + "github.com/gogo/protobuf/types" + "github.com/prometheus/prometheus/model/exemplar" "github.com/prometheus/prometheus/model/histogram" "github.com/prometheus/prometheus/model/labels" @@ -64,6 +66,11 @@ type Parser interface { // retrieved (including the case where no exemplars exist at all). Exemplar(l *exemplar.Exemplar) bool + // CreatedTimestamp writes the created timestamp of the current sample + // into the passed timestamp. It returns false if no created timestamp + // exists or if the metric type does not support created timestamps. + CreatedTimestamp(ct *types.Timestamp) bool + // Next advances the parser to the next sample. It returns false if no // more samples were read or an error occurred. Next() (Entry, error) diff --git a/model/textparse/openmetricsparse.go b/model/textparse/openmetricsparse.go index 5623e6833..bb5075544 100644 --- a/model/textparse/openmetricsparse.go +++ b/model/textparse/openmetricsparse.go @@ -24,6 +24,8 @@ import ( "strings" "unicode/utf8" + "github.com/gogo/protobuf/types" + "github.com/prometheus/prometheus/model/exemplar" "github.com/prometheus/prometheus/model/histogram" "github.com/prometheus/prometheus/model/labels" @@ -211,6 +213,11 @@ func (p *OpenMetricsParser) Exemplar(e *exemplar.Exemplar) bool { return true } +// CreatedTimestamp returns false because OpenMetricsParser does not support created timestamps (yet). +func (p *OpenMetricsParser) CreatedTimestamp(_ *types.Timestamp) bool { + return false +} + // nextToken returns the next token from the openMetricsLexer. func (p *OpenMetricsParser) nextToken() token { tok := p.l.Lex() diff --git a/model/textparse/promparse.go b/model/textparse/promparse.go index 04c295dd0..b3fa2d8a6 100644 --- a/model/textparse/promparse.go +++ b/model/textparse/promparse.go @@ -26,6 +26,8 @@ import ( "unicode/utf8" "unsafe" + "github.com/gogo/protobuf/types" + "github.com/prometheus/prometheus/model/exemplar" "github.com/prometheus/prometheus/model/histogram" "github.com/prometheus/prometheus/model/labels" @@ -245,6 +247,11 @@ func (p *PromParser) Exemplar(*exemplar.Exemplar) bool { return false } +// CreatedTimestamp returns false because PromParser does not support created timestamps. +func (p *PromParser) CreatedTimestamp(_ *types.Timestamp) bool { + return false +} + // nextToken returns the next token from the promlexer. It skips over tabs // and spaces. func (p *PromParser) nextToken() token { diff --git a/model/textparse/protobufparse.go b/model/textparse/protobufparse.go index fbb84a2bd..94ea5e4a3 100644 --- a/model/textparse/protobufparse.go +++ b/model/textparse/protobufparse.go @@ -23,6 +23,7 @@ import ( "unicode/utf8" "github.com/gogo/protobuf/proto" + "github.com/gogo/protobuf/types" "github.com/pkg/errors" "github.com/prometheus/common/model" @@ -347,6 +348,24 @@ func (p *ProtobufParser) Exemplar(ex *exemplar.Exemplar) bool { return true } +func (p *ProtobufParser) CreatedTimestamp(ct *types.Timestamp) bool { + var foundCT *types.Timestamp + switch p.mf.GetType() { + case dto.MetricType_COUNTER: + foundCT = p.mf.GetMetric()[p.metricPos].GetCounter().GetCreatedTimestamp() + case dto.MetricType_SUMMARY: + foundCT = p.mf.GetMetric()[p.metricPos].GetSummary().GetCreatedTimestamp() + case dto.MetricType_HISTOGRAM, dto.MetricType_GAUGE_HISTOGRAM: + foundCT = p.mf.GetMetric()[p.metricPos].GetHistogram().GetCreatedTimestamp() + default: + } + if foundCT == nil { + return false + } + *ct = *foundCT + return true +} + // Next advances the parser to the next "sample" (emulating the behavior of a // text format parser). It returns (EntryInvalid, io.EOF) if no samples were // read. diff --git a/model/textparse/protobufparse_test.go b/model/textparse/protobufparse_test.go index 5436d7f3e..10ec5f440 100644 --- a/model/textparse/protobufparse_test.go +++ b/model/textparse/protobufparse_test.go @@ -21,6 +21,7 @@ import ( "testing" "github.com/gogo/protobuf/proto" + "github.com/gogo/protobuf/types" "github.com/stretchr/testify/require" "github.com/prometheus/prometheus/model/exemplar" @@ -530,6 +531,69 @@ metric: < > > +`, + `name: "test_counter_with_createdtimestamp" +help: "A counter with a created timestamp." +type: COUNTER +metric: < + counter: < + value: 42 + created_timestamp: < + seconds: 1 + nanos: 1 + > + > +> + +`, + `name: "test_summary_with_createdtimestamp" +help: "A summary with a created timestamp." +type: SUMMARY +metric: < + summary: < + sample_count: 42 + sample_sum: 1.234 + created_timestamp: < + seconds: 1 + nanos: 1 + > + > +> + +`, + `name: "test_histogram_with_createdtimestamp" +help: "A histogram with a created timestamp." +type: HISTOGRAM +metric: < + histogram: < + created_timestamp: < + seconds: 1 + nanos: 1 + > + positive_span: < + offset: 0 + length: 0 + > + > +> + +`, + `name: "test_gaugehistogram_with_createdtimestamp" +help: "A gauge histogram with a created timestamp." +type: GAUGE_HISTOGRAM +metric: < + histogram: < + created_timestamp: < + seconds: 1 + nanos: 1 + > + positive_span: < + offset: 0 + length: 0 + > + > +> + `, } @@ -566,6 +630,7 @@ func TestProtobufParse(t *testing.T) { shs *histogram.Histogram fhs *histogram.FloatHistogram e []exemplar.Exemplar + ct *types.Timestamp } inputBuf := createTestProtoBuf(t) @@ -997,6 +1062,86 @@ func TestProtobufParse(t *testing.T) { "__name__", "empty_histogram", ), }, + { + m: "test_counter_with_createdtimestamp", + help: "A counter with a created timestamp.", + }, + { + m: "test_counter_with_createdtimestamp", + typ: MetricTypeCounter, + }, + { + m: "test_counter_with_createdtimestamp", + v: 42, + ct: &types.Timestamp{Seconds: 1, Nanos: 1}, + lset: labels.FromStrings( + "__name__", "test_counter_with_createdtimestamp", + ), + }, + { + m: "test_summary_with_createdtimestamp", + help: "A summary with a created timestamp.", + }, + { + m: "test_summary_with_createdtimestamp", + typ: MetricTypeSummary, + }, + { + m: "test_summary_with_createdtimestamp_count", + v: 42, + ct: &types.Timestamp{Seconds: 1, Nanos: 1}, + lset: labels.FromStrings( + "__name__", "test_summary_with_createdtimestamp_count", + ), + }, + { + m: "test_summary_with_createdtimestamp_sum", + v: 1.234, + ct: &types.Timestamp{Seconds: 1, Nanos: 1}, + lset: labels.FromStrings( + "__name__", "test_summary_with_createdtimestamp_sum", + ), + }, + { + m: "test_histogram_with_createdtimestamp", + help: "A histogram with a created timestamp.", + }, + { + m: "test_histogram_with_createdtimestamp", + typ: MetricTypeHistogram, + }, + { + m: "test_histogram_with_createdtimestamp", + ct: &types.Timestamp{Seconds: 1, Nanos: 1}, + shs: &histogram.Histogram{ + CounterResetHint: histogram.UnknownCounterReset, + PositiveSpans: []histogram.Span{}, + NegativeSpans: []histogram.Span{}, + }, + lset: labels.FromStrings( + "__name__", "test_histogram_with_createdtimestamp", + ), + }, + { + m: "test_gaugehistogram_with_createdtimestamp", + help: "A gauge histogram with a created timestamp.", + }, + { + m: "test_gaugehistogram_with_createdtimestamp", + typ: MetricTypeGaugeHistogram, + }, + { + m: "test_gaugehistogram_with_createdtimestamp", + ct: &types.Timestamp{Seconds: 1, Nanos: 1}, + shs: &histogram.Histogram{ + CounterResetHint: histogram.GaugeType, + PositiveSpans: []histogram.Span{}, + NegativeSpans: []histogram.Span{}, + }, + lset: labels.FromStrings( + "__name__", "test_gaugehistogram_with_createdtimestamp", + ), + }, }, }, { @@ -1739,6 +1884,86 @@ func TestProtobufParse(t *testing.T) { "__name__", "empty_histogram", ), }, + { // 81 + m: "test_counter_with_createdtimestamp", + help: "A counter with a created timestamp.", + }, + { // 82 + m: "test_counter_with_createdtimestamp", + typ: MetricTypeCounter, + }, + { // 83 + m: "test_counter_with_createdtimestamp", + v: 42, + ct: &types.Timestamp{Seconds: 1, Nanos: 1}, + lset: labels.FromStrings( + "__name__", "test_counter_with_createdtimestamp", + ), + }, + { // 84 + m: "test_summary_with_createdtimestamp", + help: "A summary with a created timestamp.", + }, + { // 85 + m: "test_summary_with_createdtimestamp", + typ: MetricTypeSummary, + }, + { // 86 + m: "test_summary_with_createdtimestamp_count", + v: 42, + ct: &types.Timestamp{Seconds: 1, Nanos: 1}, + lset: labels.FromStrings( + "__name__", "test_summary_with_createdtimestamp_count", + ), + }, + { // 87 + m: "test_summary_with_createdtimestamp_sum", + v: 1.234, + ct: &types.Timestamp{Seconds: 1, Nanos: 1}, + lset: labels.FromStrings( + "__name__", "test_summary_with_createdtimestamp_sum", + ), + }, + { // 88 + m: "test_histogram_with_createdtimestamp", + help: "A histogram with a created timestamp.", + }, + { // 89 + m: "test_histogram_with_createdtimestamp", + typ: MetricTypeHistogram, + }, + { // 90 + m: "test_histogram_with_createdtimestamp", + ct: &types.Timestamp{Seconds: 1, Nanos: 1}, + shs: &histogram.Histogram{ + CounterResetHint: histogram.UnknownCounterReset, + PositiveSpans: []histogram.Span{}, + NegativeSpans: []histogram.Span{}, + }, + lset: labels.FromStrings( + "__name__", "test_histogram_with_createdtimestamp", + ), + }, + { // 91 + m: "test_gaugehistogram_with_createdtimestamp", + help: "A gauge histogram with a created timestamp.", + }, + { // 92 + m: "test_gaugehistogram_with_createdtimestamp", + typ: MetricTypeGaugeHistogram, + }, + { // 93 + m: "test_gaugehistogram_with_createdtimestamp", + ct: &types.Timestamp{Seconds: 1, Nanos: 1}, + shs: &histogram.Histogram{ + CounterResetHint: histogram.GaugeType, + PositiveSpans: []histogram.Span{}, + NegativeSpans: []histogram.Span{}, + }, + lset: labels.FromStrings( + "__name__", "test_gaugehistogram_with_createdtimestamp", + ), + }, }, }, } @@ -1764,8 +1989,10 @@ func TestProtobufParse(t *testing.T) { m, ts, v := p.Series() var e exemplar.Exemplar + var ct types.Timestamp p.Metric(&res) - found := p.Exemplar(&e) + eFound := p.Exemplar(&e) + ctFound := p.CreatedTimestamp(&ct) require.Equal(t, exp[i].m, string(m), "i: %d", i) if ts != nil { require.Equal(t, exp[i].t, *ts, "i: %d", i) @@ -1775,12 +2002,18 @@ func TestProtobufParse(t *testing.T) { require.Equal(t, exp[i].v, v, "i: %d", i) require.Equal(t, exp[i].lset, res, "i: %d", i) if len(exp[i].e) == 0 { - require.Equal(t, false, found, "i: %d", i) + require.Equal(t, false, eFound, "i: %d", i) } else { - require.Equal(t, true, found, "i: %d", i) + require.Equal(t, true, eFound, "i: %d", i) require.Equal(t, exp[i].e[0], e, "i: %d", i) require.False(t, p.Exemplar(&e), "too many exemplars returned, i: %d", i) } + if exp[i].ct != nil { + require.Equal(t, true, ctFound, "i: %d", i) + require.Equal(t, exp[i].ct.String(), ct.String(), "i: %d", i) + } else { + require.Equal(t, false, ctFound, "i: %d", i) + } case EntryHistogram: m, ts, shs, fhs := p.Histogram()