diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index 3fc22ec55c..b26e747782 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -2,6 +2,9 @@ # tip +* BUGFIX: properly parse timestamps in OpenMetrics format - they are exposed as floating-point number in seconds instead of integer milliseconds + unlike in Prometheus exposition format. See [the docs](https://github.com/OpenObservability/OpenMetrics/blob/master/specification/OpenMetrics.md#timestamps). + # [v1.48.0](https://github.com/VictoriaMetrics/VictoriaMetrics/releases/tag/v1.48.0) diff --git a/lib/protoparser/prometheus/parser.go b/lib/protoparser/prometheus/parser.go index 9d27538262..9938ba2292 100644 --- a/lib/protoparser/prometheus/parser.go +++ b/lib/protoparser/prometheus/parser.go @@ -166,11 +166,18 @@ func (r *Row) unmarshal(s string, tagsPool []Tag, noEscapes bool) ([]Tag, error) // There is no timestamp - just a whitespace after the value. return tagsPool, nil } - ts, err := fastfloat.ParseInt64(s) + ts, err := fastfloat.Parse(s) if err != nil { return tagsPool, fmt.Errorf("cannot parse timestamp %q: %w", s, err) } - r.Timestamp = ts + if ts >= -1<<31 && ts < 1<<31 { + // This looks like OpenMetrics timestamp in Unix seconds. + // Convert it to milliseconds. + // + // See https://github.com/OpenObservability/OpenMetrics/blob/master/specification/OpenMetrics.md#timestamps + ts *= 1000 + } + r.Timestamp = int64(ts) return tagsPool, nil } diff --git a/lib/protoparser/prometheus/parser_test.go b/lib/protoparser/prometheus/parser_test.go index 7451397332..fb5a783654 100644 --- a/lib/protoparser/prometheus/parser_test.go +++ b/lib/protoparser/prometheus/parser_test.go @@ -178,14 +178,14 @@ func TestRowsUnmarshalSuccess(t *testing.T) { Rows: []Row{{ Metric: "foobar", Value: 123.456, - Timestamp: 789, + Timestamp: 789000, }}, }) - f("foobar{} 123.456 789\n", &Rows{ + f("foobar{} 123.456 789.4354\n", &Rows{ Rows: []Row{{ Metric: "foobar", Value: 123.456, - Timestamp: 789, + Timestamp: 789435, }}, }) f(`# _ _ @@ -225,7 +225,7 @@ cassandra_token_ownership_ratio 78.9`, &Rows{ { Metric: "abc", Value: 123, - Timestamp: 456, + Timestamp: 456000, }, { Metric: "foo", @@ -274,7 +274,8 @@ cassandra_token_ownership_ratio 78.9`, &Rows{ }, }) - // Timestamp bigger than 1<<31 + // Timestamp bigger than 1<<31. + // It should be parsed in milliseconds. f("aaa 1123 429496729600", &Rows{ Rows: []Row{{ Metric: "aaa", @@ -283,6 +284,15 @@ cassandra_token_ownership_ratio 78.9`, &Rows{ }}, }) + // Floating-point timestamps in OpenMetric format. + f("aaa 1123 42949.567", &Rows{ + Rows: []Row{{ + Metric: "aaa", + Value: 1123, + Timestamp: 42949567, + }}, + }) + // Tags f(`foo{bar="baz"} 1 2`, &Rows{ Rows: []Row{{ @@ -292,7 +302,7 @@ cassandra_token_ownership_ratio 78.9`, &Rows{ Value: "baz", }}, Value: 1, - Timestamp: 2, + Timestamp: 2000, }}, }) f(`foo{bar="b\"a\\z"} -1.2`, &Rows{ @@ -324,7 +334,7 @@ cassandra_token_ownership_ratio 78.9`, &Rows{ }, }, Value: 1, - Timestamp: 2, + Timestamp: 2000, }}, }) @@ -338,7 +348,7 @@ cassandra_token_ownership_ratio 78.9`, &Rows{ Value: "baz", }}, Value: 1, - Timestamp: 2, + Timestamp: 2000, }}, }) @@ -348,7 +358,7 @@ cassandra_token_ownership_ratio 78.9`, &Rows{ { Metric: "foo", Value: 0.3, - Timestamp: 2, + Timestamp: 2000, }, { Metric: "aaa", @@ -357,7 +367,7 @@ cassandra_token_ownership_ratio 78.9`, &Rows{ { Metric: "bar.baz", Value: 0.34, - Timestamp: 43, + Timestamp: 43000, }, }, }) @@ -368,12 +378,12 @@ cassandra_token_ownership_ratio 78.9`, &Rows{ { Metric: "foo", Value: 0.3, - Timestamp: 2, + Timestamp: 2000, }, { Metric: "bar.baz", Value: 0.34, - Timestamp: 43, + Timestamp: 43000, }, }, }) diff --git a/lib/protoparser/prometheus/streamparser_test.go b/lib/protoparser/prometheus/streamparser_test.go index 6d60cb5ea6..d2b88ede6c 100644 --- a/lib/protoparser/prometheus/streamparser_test.go +++ b/lib/protoparser/prometheus/streamparser_test.go @@ -82,13 +82,13 @@ func TestParseStream(t *testing.T) { f("foo 123 456", []Row{{ Metric: "foo", Value: 123, - Timestamp: 456, + Timestamp: 456000, }}) f(`foo{bar="baz"} 1 2`+"\n"+`aaa{} 3 4`, []Row{ { Metric: "aaa", Value: 3, - Timestamp: 4, + Timestamp: 4000, }, { Metric: "foo", @@ -97,7 +97,7 @@ func TestParseStream(t *testing.T) { Value: "baz", }}, Value: 1, - Timestamp: 2, + Timestamp: 2000, }, }) f("foo 23", []Row{{