diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index 66d91bd55..fd8ea4f26 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 4972c7bd6..c7381f2bc 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 dafa3f447..f864dd9a1 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{{