lib/protoparser/prometheus: properly parse OpenMetrics timestamps

OpenMetrics timestamps are floating-point numbers, that represent Unix timestamp in seconds.
This differs from Prometheus exposition format, where timestamps are integer numbers representing Unix timestamp in milliseconds.
This commit is contained in:
Aliaksandr Valialkin 2020-11-27 14:53:27 +02:00
parent eedb79ead8
commit a906b3862f
4 changed files with 37 additions and 17 deletions

View file

@ -2,6 +2,9 @@
# tip # 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) # [v1.48.0](https://github.com/VictoriaMetrics/VictoriaMetrics/releases/tag/v1.48.0)

View file

@ -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. // There is no timestamp - just a whitespace after the value.
return tagsPool, nil return tagsPool, nil
} }
ts, err := fastfloat.ParseInt64(s) ts, err := fastfloat.Parse(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.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 return tagsPool, nil
} }

View file

@ -178,14 +178,14 @@ func TestRowsUnmarshalSuccess(t *testing.T) {
Rows: []Row{{ Rows: []Row{{
Metric: "foobar", Metric: "foobar",
Value: 123.456, Value: 123.456,
Timestamp: 789, Timestamp: 789000,
}}, }},
}) })
f("foobar{} 123.456 789\n", &Rows{ f("foobar{} 123.456 789.4354\n", &Rows{
Rows: []Row{{ Rows: []Row{{
Metric: "foobar", Metric: "foobar",
Value: 123.456, Value: 123.456,
Timestamp: 789, Timestamp: 789435,
}}, }},
}) })
f(`# _ _ f(`# _ _
@ -225,7 +225,7 @@ cassandra_token_ownership_ratio 78.9`, &Rows{
{ {
Metric: "abc", Metric: "abc",
Value: 123, Value: 123,
Timestamp: 456, Timestamp: 456000,
}, },
{ {
Metric: "foo", 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{ f("aaa 1123 429496729600", &Rows{
Rows: []Row{{ Rows: []Row{{
Metric: "aaa", 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 // Tags
f(`foo{bar="baz"} 1 2`, &Rows{ f(`foo{bar="baz"} 1 2`, &Rows{
Rows: []Row{{ Rows: []Row{{
@ -292,7 +302,7 @@ cassandra_token_ownership_ratio 78.9`, &Rows{
Value: "baz", Value: "baz",
}}, }},
Value: 1, Value: 1,
Timestamp: 2, Timestamp: 2000,
}}, }},
}) })
f(`foo{bar="b\"a\\z"} -1.2`, &Rows{ f(`foo{bar="b\"a\\z"} -1.2`, &Rows{
@ -324,7 +334,7 @@ cassandra_token_ownership_ratio 78.9`, &Rows{
}, },
}, },
Value: 1, Value: 1,
Timestamp: 2, Timestamp: 2000,
}}, }},
}) })
@ -338,7 +348,7 @@ cassandra_token_ownership_ratio 78.9`, &Rows{
Value: "baz", Value: "baz",
}}, }},
Value: 1, Value: 1,
Timestamp: 2, Timestamp: 2000,
}}, }},
}) })
@ -348,7 +358,7 @@ cassandra_token_ownership_ratio 78.9`, &Rows{
{ {
Metric: "foo", Metric: "foo",
Value: 0.3, Value: 0.3,
Timestamp: 2, Timestamp: 2000,
}, },
{ {
Metric: "aaa", Metric: "aaa",
@ -357,7 +367,7 @@ cassandra_token_ownership_ratio 78.9`, &Rows{
{ {
Metric: "bar.baz", Metric: "bar.baz",
Value: 0.34, Value: 0.34,
Timestamp: 43, Timestamp: 43000,
}, },
}, },
}) })
@ -368,12 +378,12 @@ cassandra_token_ownership_ratio 78.9`, &Rows{
{ {
Metric: "foo", Metric: "foo",
Value: 0.3, Value: 0.3,
Timestamp: 2, Timestamp: 2000,
}, },
{ {
Metric: "bar.baz", Metric: "bar.baz",
Value: 0.34, Value: 0.34,
Timestamp: 43, Timestamp: 43000,
}, },
}, },
}) })

View file

@ -82,13 +82,13 @@ func TestParseStream(t *testing.T) {
f("foo 123 456", []Row{{ f("foo 123 456", []Row{{
Metric: "foo", Metric: "foo",
Value: 123, Value: 123,
Timestamp: 456, Timestamp: 456000,
}}) }})
f(`foo{bar="baz"} 1 2`+"\n"+`aaa{} 3 4`, []Row{ f(`foo{bar="baz"} 1 2`+"\n"+`aaa{} 3 4`, []Row{
{ {
Metric: "aaa", Metric: "aaa",
Value: 3, Value: 3,
Timestamp: 4, Timestamp: 4000,
}, },
{ {
Metric: "foo", Metric: "foo",
@ -97,7 +97,7 @@ func TestParseStream(t *testing.T) {
Value: "baz", Value: "baz",
}}, }},
Value: 1, Value: 1,
Timestamp: 2, Timestamp: 2000,
}, },
}) })
f("foo 23", []Row{{ f("foo 23", []Row{{