From dad8b76a0e9031aa0e4bf4570e4e469ab9b485bf Mon Sep 17 00:00:00 2001 From: Aliaksandr Valialkin Date: Tue, 24 Nov 2020 12:26:17 +0200 Subject: [PATCH] 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. --- docs/CHANGELOG.md | 2 ++ lib/protoparser/prometheus/parser.go | 15 ++++++++++- lib/protoparser/prometheus/parser_test.go | 31 +++++++++++++++++++++++ 3 files changed, 47 insertions(+), 1 deletion(-) diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index 66d91bd554..fd8ea4f262 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -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. 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) diff --git a/lib/protoparser/prometheus/parser.go b/lib/protoparser/prometheus/parser.go index 4972c7bd6e..c7381f2bcc 100644 --- a/lib/protoparser/prometheus/parser.go +++ b/lib/protoparser/prometheus/parser.go @@ -70,6 +70,14 @@ func (r *Row) reset() { 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 { // Prometheus treats ' ' and '\t' as whitespace // 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") } s = skipLeadingWhitespace(s) + s = skipTrailingComment(s) if len(s) == 0 { 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 { return tagsPool, fmt.Errorf("cannot parse value %q: %w", s[:n], err) } + r.Value = v 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) if err != nil { return tagsPool, fmt.Errorf("cannot parse timestamp %q: %w", s, err) } - r.Value = v r.Timestamp = ts return tagsPool, nil } diff --git a/lib/protoparser/prometheus/parser_test.go b/lib/protoparser/prometheus/parser_test.go index dafa3f447a..f864dd9a11 100644 --- a/lib/protoparser/prometheus/parser_test.go +++ b/lib/protoparser/prometheus/parser_test.go @@ -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 f("aaa 1123 429496729600", &Rows{ Rows: []Row{{