grafana/pkg/expr/classic/reduce.go
2022-10-12 10:55:48 +01:00

204 lines
3.7 KiB
Go

package classic
import (
"math"
"sort"
"github.com/grafana/grafana/pkg/expr/mathexp"
)
type reducer string
func (cr reducer) ValidReduceFunc() bool {
switch cr {
case "avg", "sum", "min", "max", "count", "last", "median":
return true
case "diff", "diff_abs", "percent_diff", "percent_diff_abs", "count_non_null":
return true
}
return false
}
//nolint:gocyclo
func (cr reducer) Reduce(series mathexp.Series) mathexp.Number {
num := mathexp.NewNumber("", nil)
if series.GetLabels() != nil {
num.SetLabels(series.GetLabels().Copy())
}
num.SetValue(nil)
if series.Len() == 0 {
return num
}
value := float64(0)
allNull := true
vF := series.Frame.Fields[1]
ff := mathexp.Float64Field(*vF)
switch cr {
case "avg":
validPointsCount := 0
for i := 0; i < ff.Len(); i++ {
f := ff.GetValue(i)
if nilOrNaN(f) {
continue
}
value += *f
validPointsCount++
allNull = false
}
if validPointsCount > 0 {
value /= float64(validPointsCount)
}
case "sum":
for i := 0; i < ff.Len(); i++ {
f := ff.GetValue(i)
if nilOrNaN(f) {
continue
}
value += *f
allNull = false
}
case "min":
value = math.MaxFloat64
for i := 0; i < ff.Len(); i++ {
f := ff.GetValue(i)
if nilOrNaN(f) {
continue
}
allNull = false
if value > *f {
value = *f
}
}
if allNull {
value = 0
}
case "max":
value = -math.MaxFloat64
for i := 0; i < ff.Len(); i++ {
f := ff.GetValue(i)
if nilOrNaN(f) {
continue
}
allNull = false
if value < *f {
value = *f
}
}
if allNull {
value = 0
}
case "count":
value = float64(ff.Len())
allNull = false
case "last":
for i := ff.Len() - 1; i >= 0; i-- {
f := ff.GetValue(i)
if !nilOrNaN(f) {
value = *f
allNull = false
break
}
}
case "median":
var values []float64
for i := 0; i < ff.Len(); i++ {
f := ff.GetValue(i)
if nilOrNaN(f) {
continue
}
allNull = false
values = append(values, *f)
}
if len(values) >= 1 {
sort.Float64s(values)
length := len(values)
if length%2 == 1 {
value = values[(length-1)/2]
} else {
value = (values[(length/2)-1] + values[length/2]) / 2
}
}
case "diff":
allNull, value = calculateDiff(ff, allNull, value, diff)
case "diff_abs":
allNull, value = calculateDiff(ff, allNull, value, diffAbs)
case "percent_diff":
allNull, value = calculateDiff(ff, allNull, value, percentDiff)
case "percent_diff_abs":
allNull, value = calculateDiff(ff, allNull, value, percentDiffAbs)
case "count_non_null":
for i := 0; i < ff.Len(); i++ {
f := ff.GetValue(i)
if nilOrNaN(f) {
continue
}
value++
}
if value > 0 {
allNull = false
}
}
if allNull {
return num
}
num.SetValue(&value)
return num
}
func calculateDiff(ff mathexp.Float64Field, allNull bool, value float64, fn func(float64, float64) float64) (bool, float64) {
var (
first float64
i int
)
// get the newest point
for i = ff.Len() - 1; i >= 0; i-- {
f := ff.GetValue(i)
if !nilOrNaN(f) {
first = *f
allNull = false
break
}
}
if i >= 1 {
// get the oldest point
for i := 0; i < ff.Len(); i++ {
f := ff.GetValue(i)
if !nilOrNaN(f) {
value = fn(first, *f)
allNull = false
break
}
}
}
return allNull, value
}
func nilOrNaN(f *float64) bool {
return f == nil || math.IsNaN(*f)
}
var diff = func(newest, oldest float64) float64 {
return newest - oldest
}
var diffAbs = func(newest, oldest float64) float64 {
return math.Abs(newest - oldest)
}
var percentDiff = func(newest, oldest float64) float64 {
return (newest - oldest) / math.Abs(oldest) * 100
}
var percentDiffAbs = func(newest, oldest float64) float64 {
return math.Abs((newest - oldest) / oldest * 100)
}