Prometheus: Transform NaN values from matrix response to null on backend (#40757)

* avoid duplicate allocations

* set labels

* Replace NaN in matrix with null

* Refactor and add test

* Update test

* Append response only if no error

Co-authored-by: Ryan McKinley <ryantxu@gmail.com>
This commit is contained in:
Ivana Huckova 2021-10-21 15:01:14 +02:00 committed by GitHub
parent 967721068e
commit ba90b57b66
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 41 additions and 10 deletions

View File

@ -181,10 +181,10 @@ func (s *Service) QueryData(ctx context.Context, req *backend.QueryDataRequest)
if err != nil {
plog.Error("Exemplar query", query.Expr, "failed with", err)
result.Responses[query.RefId] = backend.DataResponse{Error: err}
continue
}
} else {
response[ExemplarQueryType] = exemplarResponse
}
}
frames, err := parseResponse(response, query)
if err != nil {
@ -403,19 +403,28 @@ func matrixToDataFrames(matrix model.Matrix, query *PrometheusQuery) data.Frames
for _, v := range matrix {
tags := make(map[string]string, len(v.Metric))
timeVector := make([]time.Time, 0, len(v.Values))
values := make([]float64, 0, len(v.Values))
for k, v := range v.Metric {
tags[string(k)] = string(v)
}
for _, k := range v.Values {
timeVector = append(timeVector, time.Unix(k.Timestamp.Unix(), 0).UTC())
values = append(values, float64(k.Value))
timeField := data.NewFieldFromFieldType(data.FieldTypeTime, len(v.Values))
valueField := data.NewFieldFromFieldType(data.FieldTypeNullableFloat64, len(v.Values))
for i, k := range v.Values {
timeField.Set(i, time.Unix(k.Timestamp.Unix(), 0).UTC())
value := float64(k.Value)
if !math.IsNaN(value) {
valueField.Set(i, &value)
}
}
name := formatLegend(v.Metric, query)
frame := data.NewFrame(name,
data.NewField("Time", nil, timeVector),
data.NewField("Value", tags, values).SetConfig(&data.FieldConfig{DisplayNameFromDS: name}))
timeField.Name = data.TimeSeriesTimeFieldName
valueField.Name = data.TimeSeriesValueFieldName
valueField.Config = &data.FieldConfig{DisplayNameFromDS: name}
valueField.Labels = tags
frame := data.NewFrame(name, timeField, valueField)
frame.Meta = &data.FrameMeta{
Custom: map[string]string{
"resultType": "matrix",

View File

@ -1,6 +1,7 @@
package prometheus
import (
"math"
"testing"
"time"
@ -440,6 +441,27 @@ func TestPrometheus_parseResponse(t *testing.T) {
require.Equal(t, "UTC", testValue.(time.Time).Location().String())
})
t.Run("matrix response with NaN value should be changed to null", func(t *testing.T) {
value := make(map[PrometheusQueryType]interface{})
value[RangeQueryType] = p.Matrix{
&p.SampleStream{
Metric: p.Metric{"app": "Application"},
Values: []p.SamplePair{
{Value: p.SampleValue(math.NaN()), Timestamp: 1000},
},
},
}
query := &PrometheusQuery{
LegendFormat: "",
}
res, err := parseResponse(value, query)
require.NoError(t, err)
var nilPointer *float64
require.Equal(t, res[0].Fields[1].Name, "Value")
require.Equal(t, res[0].Fields[1].At(0), nilPointer)
})
t.Run("vector response should be parsed normally", func(t *testing.T) {
value := make(map[PrometheusQueryType]interface{})
value[RangeQueryType] = p.Vector{