The open and composable observability and data visualization platform. Visualize metrics, logs, and traces from multiple sources like Prometheus, Loki, Elasticsearch, InfluxDB, Postgres and many more.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 
grafana/pkg/expr/classic/reduce.go

201 lines
3.7 KiB

package classic
import (
"math"
"sort"
"github.com/grafana/grafana/pkg/expr/mathexp"
)
func nilOrNaN(f *float64) bool {
return f == nil || math.IsNaN(*f)
}
func (cr classicReducer) ValidReduceFunc() bool {
switch cr {
case "avg", "sum", "min", "max", "count", "last", "median":
return true
case "diff", "diff_abs", "percent_diff", "percent_diff_abs", "count_not_null":
return true
}
return false
}
//nolint: gocyclo
func (cr classicReducer) 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[series.ValueIdx]
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
}
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)
}