lib/protoparser/prometheus: properly parse metrics with exemplars

Examplars have been introduced in OpenMetrics - see https://github.com/OpenObservability/OpenMetrics/blob/master/OpenMetrics.md#exemplars-1
Previously VictoriaMetrics couldn't parse the following metric

    foo{bar="baz"} 123 # exemplar here

This commit fixes this. Note that VictoriaMetrics ignores the exemplar as for now.
This commit is contained in:
Aliaksandr Valialkin 2020-11-24 12:26:17 +02:00
parent 768fd8c3d9
commit dad8b76a0e
3 changed files with 47 additions and 1 deletions

View File

@ -19,6 +19,8 @@ See https://github.com/VictoriaMetrics/VictoriaMetrics/issues/851
* FEATURE: add remoteAddr to slow query log in order to simplify identifying the client that sends slow queries to VictoriaMetrics. * FEATURE: add remoteAddr to slow query log in order to simplify identifying the client that sends slow queries to VictoriaMetrics.
Slow query logging is controlled with `-search.logSlowQueryDuration` command-line flag. Slow query logging is controlled with `-search.logSlowQueryDuration` command-line flag.
* BUGFIX: properly parse Prometheus metrics with [exemplars](https://github.com/OpenObservability/OpenMetrics/blob/master/OpenMetrics.md#exemplars-1) such as `foo 123 # {bar="baz"} 1`.
# [v1.47.0](https://github.com/VictoriaMetrics/VictoriaMetrics/releases/tag/v1.47.0) # [v1.47.0](https://github.com/VictoriaMetrics/VictoriaMetrics/releases/tag/v1.47.0)

View File

@ -70,6 +70,14 @@ func (r *Row) reset() {
r.Timestamp = 0 r.Timestamp = 0
} }
func skipTrailingComment(s string) string {
n := strings.IndexByte(s, '#')
if n < 0 {
return s
}
return s[:n]
}
func skipLeadingWhitespace(s string) string { func skipLeadingWhitespace(s string) string {
// Prometheus treats ' ' and '\t' as whitespace // Prometheus treats ' ' and '\t' as whitespace
// according to https://github.com/prometheus/docs/blob/master/content/docs/instrumenting/exposition_formats.md#text-format-details // according to https://github.com/prometheus/docs/blob/master/content/docs/instrumenting/exposition_formats.md#text-format-details
@ -133,6 +141,7 @@ func (r *Row) unmarshal(s string, tagsPool []Tag, noEscapes bool) ([]Tag, error)
return tagsPool, fmt.Errorf("metric cannot be empty") return tagsPool, fmt.Errorf("metric cannot be empty")
} }
s = skipLeadingWhitespace(s) s = skipLeadingWhitespace(s)
s = skipTrailingComment(s)
if len(s) == 0 { if len(s) == 0 {
return tagsPool, fmt.Errorf("value cannot be empty") return tagsPool, fmt.Errorf("value cannot be empty")
} }
@ -151,12 +160,16 @@ func (r *Row) unmarshal(s string, tagsPool []Tag, noEscapes bool) ([]Tag, error)
if err != nil { if err != nil {
return tagsPool, fmt.Errorf("cannot parse value %q: %w", s[:n], err) return tagsPool, fmt.Errorf("cannot parse value %q: %w", s[:n], err)
} }
r.Value = v
s = skipLeadingWhitespace(s[n+1:]) s = skipLeadingWhitespace(s[n+1:])
if len(s) == 0 {
// There is no timestamp - just a whitespace after the value.
return tagsPool, nil
}
ts, err := fastfloat.ParseInt64(s) ts, err := fastfloat.ParseInt64(s)
if err != nil { if err != nil {
return tagsPool, fmt.Errorf("cannot parse timestamp %q: %w", s, err) return tagsPool, fmt.Errorf("cannot parse timestamp %q: %w", s, err)
} }
r.Value = v
r.Timestamp = ts r.Timestamp = ts
return tagsPool, nil return tagsPool, nil
} }

View File

@ -202,6 +202,37 @@ cassandra_token_ownership_ratio 78.9`, &Rows{
}}, }},
}) })
// Exemplars - see https://github.com/OpenObservability/OpenMetrics/blob/master/OpenMetrics.md#exemplars-1
f(`foo_bucket{le="10",a="#b"} 17 # {trace_id="oHg5SJ#YRHA0"} 9.8 1520879607.789
abc 123 456#foobar
foo 344#bar`, &Rows{
Rows: []Row{
{
Metric: "foo_bucket",
Tags: []Tag{
{
Key: "le",
Value: "10",
},
{
Key: "a",
Value: "#b",
},
},
Value: 17,
},
{
Metric: "abc",
Value: 123,
Timestamp: 456,
},
{
Metric: "foo",
Value: 344,
},
},
})
// Timestamp bigger than 1<<31 // Timestamp bigger than 1<<31
f("aaa 1123 429496729600", &Rows{ f("aaa 1123 429496729600", &Rows{
Rows: []Row{{ Rows: []Row{{