Parse created timestamps from Prometheus Protobuf (#12973)

Signed-off-by: Arthur Silva Sens <arthur.sens@coralogix.com>
This commit is contained in:
Arthur Silva Sens 2023-10-18 13:04:02 -05:00 committed by GitHub
parent 4d50e5d122
commit ef8e6ae780
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 276 additions and 3 deletions

View file

@ -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)

View file

@ -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()

View file

@ -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 {

View file

@ -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.

View file

@ -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()