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/services/alerting/conditions/reducer.go

174 lines
3.6 KiB

package conditions
import (
"math"
"sort"
"github.com/grafana/grafana/pkg/components/null"
"github.com/grafana/grafana/pkg/tsdb"
)
// queryReducer reduces a timeseries to a nullable float
type queryReducer struct {
// Type is how the timeseries should be reduced.
// Ex avg, sum, max, min, count
Type string
}
func (s *queryReducer) Reduce(series *tsdb.TimeSeries) null.Float {
if len(series.Points) == 0 {
return null.FloatFromPtr(nil)
}
value := float64(0)
allNull := true
switch s.Type {
case "avg":
validPointsCount := 0
for _, point := range series.Points {
if isValid(point[0]) {
value += point[0].Float64
validPointsCount++
allNull = false
}
}
if validPointsCount > 0 {
value /= float64(validPointsCount)
}
case "sum":
for _, point := range series.Points {
if isValid(point[0]) {
value += point[0].Float64
allNull = false
}
}
case "min":
value = math.MaxFloat64
for _, point := range series.Points {
if isValid(point[0]) {
allNull = false
if value > point[0].Float64 {
value = point[0].Float64
}
}
}
case "max":
value = -math.MaxFloat64
for _, point := range series.Points {
if isValid(point[0]) {
allNull = false
if value < point[0].Float64 {
value = point[0].Float64
}
}
}
case "count":
value = float64(len(series.Points))
allNull = false
case "last":
points := series.Points
for i := len(points) - 1; i >= 0; i-- {
if isValid(points[i][0]) {
value = points[i][0].Float64
allNull = false
break
}
}
case "median":
var values []float64
for _, v := range series.Points {
if isValid(v[0]) {
allNull = false
values = append(values, v[0].Float64)
}
}
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(series, allNull, value, diff)
case "diff_abs":
allNull, value = calculateDiff(series, allNull, value, diffAbs)
case "percent_diff":
allNull, value = calculateDiff(series, allNull, value, percentDiff)
case "percent_diff_abs":
allNull, value = calculateDiff(series, allNull, value, percentDiffAbs)
case "count_non_null":
for _, v := range series.Points {
if isValid(v[0]) {
value++
}
}
if value > 0 {
allNull = false
}
}
if allNull {
return null.FloatFromPtr(nil)
}
return null.FloatFrom(value)
}
func newSimpleReducer(t string) *queryReducer {
return &queryReducer{Type: t}
}
func calculateDiff(series *tsdb.TimeSeries, allNull bool, value float64, fn func(float64, float64) float64) (bool, float64) {
var (
points = series.Points
first float64
i int
)
// get the newest point
for i = len(points) - 1; i >= 0; i-- {
if isValid(points[i][0]) {
allNull = false
first = points[i][0].Float64
break
}
}
if i >= 1 {
// get the oldest point
points = points[0:i]
for i := 0; i < len(points); i++ {
if isValid(points[i][0]) {
allNull = false
value = fn(first, points[i][0].Float64)
break
}
}
}
return allNull, value
}
func isValid(f null.Float) bool {
return f.Valid && !math.IsNaN(f.Float64)
}
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)
}