SSE: Mode to drop NaN/Inf/Null in Reduction operations (#43583)

Co-authored-by: Yuriy Tseretyan <yuriy.tseretyan@grafana.com>
Co-authored-by: gillesdemey <gilles.de.mey@gmail.com>
This commit is contained in:
Kyle Brandt
2022-02-02 14:50:44 +01:00
committed by GitHub
co-authored by Yuriy Tseretyan gillesdemey
parent 0cb3037b55
commit 040ce40113
14 changed files with 627 additions and 100 deletions
+264 -1
View File
@@ -2,6 +2,7 @@ package mathexp
import (
"math"
"math/rand"
"testing"
"time"
@@ -227,6 +228,19 @@ func TestSeriesReduce(t *testing.T) {
},
},
},
{
name: "last null series",
red: "last",
varToReduce: "A",
vars: seriesWithNil,
errIs: require.NoError,
resultsIs: require.Equal,
results: Results{
[]Value{
makeNumber("", nil, nil),
},
},
},
}
for _, tt := range tests {
@@ -234,7 +248,7 @@ func TestSeriesReduce(t *testing.T) {
results := Results{}
seriesSet := tt.vars[tt.varToReduce]
for _, series := range seriesSet.Values {
ns, err := series.Value().(*Series).Reduce("", tt.red)
ns, err := series.Value().(*Series).Reduce("", tt.red, nil)
tt.errIs(t, err)
if err != nil {
return
@@ -251,3 +265,252 @@ func TestSeriesReduce(t *testing.T) {
})
}
}
var seriesNonNumbers = Vars{
"A": Results{
[]Value{
makeSeries("temp", nil,
tp{time.Unix(5, 0), NaN},
tp{time.Unix(10, 0), float64Pointer(math.Inf(-1))},
tp{time.Unix(15, 0), float64Pointer(math.Inf(1))},
tp{time.Unix(15, 0), nil}),
},
},
}
func TestSeriesReduceDropNN(t *testing.T) {
var tests = []struct {
name string
red string
vars Vars
varToReduce string
results Results
}{
{
name: "dropNN: sum series",
red: "sum",
varToReduce: "A",
vars: aSeries,
results: Results{
[]Value{
makeNumber("", nil, float64Pointer(3)),
},
},
},
{
name: "dropNN: sum series with a nil value",
red: "sum",
varToReduce: "A",
vars: seriesWithNil,
results: Results{
[]Value{
makeNumber("", nil, float64Pointer(2)),
},
},
},
{
name: "dropNN: sum empty series",
red: "sum",
varToReduce: "A",
vars: seriesEmpty,
results: Results{
[]Value{
makeNumber("", nil, float64Pointer(0)),
},
},
},
{
name: "dropNN: mean series with a nil value and real value",
red: "mean",
varToReduce: "A",
vars: seriesWithNil,
results: Results{
[]Value{
makeNumber("", nil, float64Pointer(2)),
},
},
},
{
name: "DropNN: mean empty series",
red: "mean",
varToReduce: "A",
vars: seriesEmpty,
results: Results{
[]Value{
makeNumber("", nil, nil),
},
},
},
{
name: "DropNN: mean series that becomes empty after filtering non-number",
red: "mean",
varToReduce: "A",
vars: seriesNonNumbers,
results: Results{
[]Value{
makeNumber("", nil, nil),
},
},
},
{
name: "DropNN: count empty series",
red: "count",
varToReduce: "A",
vars: seriesEmpty,
results: Results{
[]Value{
makeNumber("", nil, float64Pointer(0)),
},
},
},
{
name: "DropNN: count series with nil and value should only count real numbers",
red: "count",
varToReduce: "A",
vars: seriesWithNil,
results: Results{
[]Value{
makeNumber("", nil, float64Pointer(1)),
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
results := Results{}
seriesSet := tt.vars[tt.varToReduce]
for _, series := range seriesSet.Values {
ns, err := series.Value().(*Series).Reduce("", tt.red, DropNonNumber{})
require.NoError(t, err)
results.Values = append(results.Values, ns)
}
opt := cmp.Comparer(func(x, y float64) bool {
return (math.IsNaN(x) && math.IsNaN(y)) || x == y
})
options := append([]cmp.Option{opt}, data.FrameTestCompareOptions()...)
if diff := cmp.Diff(tt.results, results, options...); diff != "" {
t.Errorf("Result mismatch (-want +got):\n%s", diff)
}
})
}
}
func TestSeriesReduceReplaceNN(t *testing.T) {
replaceWith := rand.Float64()
var tests = []struct {
name string
red string
vars Vars
varToReduce string
results Results
}{
{
name: "replaceNN: sum series",
red: "sum",
varToReduce: "A",
vars: aSeries,
results: Results{
[]Value{
makeNumber("", nil, float64Pointer(3)),
},
},
},
{
name: "replaceNN: sum series with a nil value",
red: "sum",
varToReduce: "A",
vars: seriesWithNil,
results: Results{
[]Value{
makeNumber("", nil, float64Pointer(replaceWith+2)),
},
},
},
{
name: "replaceNN: sum empty series",
red: "sum",
varToReduce: "A",
vars: seriesEmpty,
results: Results{
[]Value{
makeNumber("", nil, float64Pointer(0)),
},
},
},
{
name: "replaceNN: mean series with a nil value and real value",
red: "mean",
varToReduce: "A",
vars: seriesWithNil,
results: Results{
[]Value{
makeNumber("", nil, float64Pointer((2+replaceWith)/2e0)),
},
},
},
{
name: "replaceNN: mean empty series",
red: "mean",
varToReduce: "A",
vars: seriesEmpty,
results: Results{
[]Value{
makeNumber("", nil, float64Pointer(replaceWith)),
},
},
},
{
name: "replaceNN: mean series that becomes empty after filtering non-number",
red: "mean",
varToReduce: "A",
vars: seriesNonNumbers,
results: Results{
[]Value{
makeNumber("", nil, float64Pointer(replaceWith)),
},
},
},
{
name: "replaceNN: count empty series",
red: "count",
varToReduce: "A",
vars: seriesEmpty,
results: Results{
[]Value{
makeNumber("", nil, float64Pointer(0)),
},
},
},
{
name: "replaceNN: count series with nil and value should only count real numbers",
red: "count",
varToReduce: "A",
vars: seriesWithNil,
results: Results{
[]Value{
makeNumber("", nil, float64Pointer(2)),
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
results := Results{}
seriesSet := tt.vars[tt.varToReduce]
for _, series := range seriesSet.Values {
ns, err := series.Value().(*Series).Reduce("", tt.red, ReplaceNonNumberWithValue{Value: replaceWith})
require.NoError(t, err)
results.Values = append(results.Values, ns)
}
opt := cmp.Comparer(func(x, y float64) bool {
return (math.IsNaN(x) && math.IsNaN(y)) || x == y
})
options := append([]cmp.Option{opt}, data.FrameTestCompareOptions()...)
if diff := cmp.Diff(tt.results, results, options...); diff != "" {
t.Errorf("Result mismatch (-want +got):\n%s", diff)
}
})
}
}