package mathexp import ( "fmt" "math" "strings" "github.com/grafana/grafana-plugin-sdk-go/data" ) type ReducerFunc = func(fv *Float64Field) *float64 func Sum(fv *Float64Field) *float64 { var sum float64 for i := 0; i < fv.Len(); i++ { f := fv.GetValue(i) if f == nil || math.IsNaN(*f) { nan := math.NaN() return &nan } sum += *f } return &sum } func Avg(fv *Float64Field) *float64 { sum := Sum(fv) f := *sum / float64(fv.Len()) return &f } func Min(fv *Float64Field) *float64 { var f float64 if fv.Len() == 0 { nan := math.NaN() return &nan } for i := 0; i < fv.Len(); i++ { v := fv.GetValue(i) if v == nil || math.IsNaN(*v) { nan := math.NaN() return &nan } if i == 0 || *v < f { f = *v } } return &f } func Max(fv *Float64Field) *float64 { var f float64 if fv.Len() == 0 { nan := math.NaN() return &nan } for i := 0; i < fv.Len(); i++ { v := fv.GetValue(i) if v == nil || math.IsNaN(*v) { nan := math.NaN() return &nan } if i == 0 || *v > f { f = *v } } return &f } func Count(fv *Float64Field) *float64 { f := float64(fv.Len()) return &f } func Last(fv *Float64Field) *float64 { var f float64 if fv.Len() == 0 { f = math.NaN() return &f } return fv.GetValue(fv.Len() - 1) } func GetReduceFunc(rFunc string) (ReducerFunc, error) { switch strings.ToLower(rFunc) { case "sum": return Sum, nil case "mean": return Avg, nil case "min": return Min, nil case "max": return Max, nil case "count": return Count, nil case "last": return Last, nil default: return nil, fmt.Errorf("reduction %v not implemented", rFunc) } } // Reduce turns the Series into a Number based on the given reduction function // if ReduceMapper is defined it applies it to the provided series and performs reduction of the resulting series. // Otherwise, the reduction operation is done against the original series. func (s Series) Reduce(refID, rFunc string, mapper ReduceMapper) (Number, error) { var l data.Labels if s.GetLabels() != nil { l = s.GetLabels().Copy() } number := NewNumber(refID, l) var f *float64 series := s if mapper != nil { series = mapSeries(s, mapper) } fVec := series.Frame.Fields[seriesTypeValIdx] floatField := Float64Field(*fVec) reduceFunc, err := GetReduceFunc(rFunc) if err != nil { return number, err } f = reduceFunc(&floatField) if f != nil && mapper != nil { f = mapper.MapOutput(f) } number.SetValue(f) return number, nil } type ReduceMapper interface { MapInput(s *float64) *float64 MapOutput(v *float64) *float64 } // mapSeries creates a series where all points are mapped using the provided map function ReduceMapper.MapInput func mapSeries(s Series, mapper ReduceMapper) Series { newSeries := NewSeries(s.Frame.RefID, s.GetLabels(), 0) for i := 0; i < s.Len(); i++ { f := s.GetValue(i) f = mapper.MapInput(f) if f == nil { continue } newFloat := *f newSeries.AppendPoint(s.GetTime(i), &newFloat) } return newSeries } type DropNonNumber struct { } // MapInput returns nil if the input parameter is nil or point to either a NaN or a Inf func (d DropNonNumber) MapInput(s *float64) *float64 { if s == nil || math.IsNaN(*s) || math.IsInf(*s, 0) { return nil } return s } // MapOutput returns nil if the input parameter is nil or point to either a NaN or a Inf func (d DropNonNumber) MapOutput(s *float64) *float64 { if s != nil && math.IsNaN(*s) { return nil } return s } type ReplaceNonNumberWithValue struct { Value float64 } // MapInput returns a pointer to ReplaceNonNumberWithValue.Value if input parameter is nil or points to either a NaN or an Inf. // Otherwise, returns the input pointer as is. func (r ReplaceNonNumberWithValue) MapInput(v *float64) *float64 { if v == nil || math.IsNaN(*v) || math.IsInf(*v, 0) { return &r.Value } else { return v } } // MapOutput returns a pointer to ReplaceNonNumberWithValue.Value if input parameter is nil or points to either a NaN or an Inf. // Otherwise, returns the input pointer as is. func (r ReplaceNonNumberWithValue) MapOutput(s *float64) *float64 { if s != nil && math.IsNaN(*s) { return &r.Value } return s }