package mathexp import ( "math" "github.com/grafana/grafana/pkg/expr/mathexp/parse" ) var builtins = map[string]parse.Func{ "abs": { Args: []parse.ReturnType{parse.TypeVariantSet}, VariantReturn: true, F: abs, }, "log": { Args: []parse.ReturnType{parse.TypeVariantSet}, VariantReturn: true, F: log, }, "nan": { Return: parse.TypeScalar, F: nan, }, "is_nan": { Args: []parse.ReturnType{parse.TypeVariantSet}, VariantReturn: true, F: isNaN, }, "inf": { Return: parse.TypeScalar, F: inf, }, "infn": { Return: parse.TypeScalar, F: infn, }, "is_inf": { Args: []parse.ReturnType{parse.TypeVariantSet}, VariantReturn: true, F: isInf, }, "null": { Return: parse.TypeScalar, F: null, }, "is_null": { Args: []parse.ReturnType{parse.TypeVariantSet}, VariantReturn: true, F: isNull, }, "is_number": { Args: []parse.ReturnType{parse.TypeVariantSet}, VariantReturn: true, F: isNumber, }, "round": { Args: []parse.ReturnType{parse.TypeVariantSet}, VariantReturn: true, F: round, }, "ceil": { Args: []parse.ReturnType{parse.TypeVariantSet}, VariantReturn: true, F: ceil, }, "floor": { Args: []parse.ReturnType{parse.TypeVariantSet}, VariantReturn: true, F: floor, }, } // abs returns the absolute value for each result in NumberSet, SeriesSet, or Scalar func abs(e *State, varSet Results) (Results, error) { newRes := Results{} for _, res := range varSet.Values { newVal, err := perFloat(e, res, math.Abs) if err != nil { return newRes, err } newRes.Values = append(newRes.Values, newVal) } return newRes, nil } // log returns the natural logarithm value for each result in NumberSet, SeriesSet, or Scalar func log(e *State, varSet Results) (Results, error) { newRes := Results{} for _, res := range varSet.Values { newVal, err := perFloat(e, res, math.Log) if err != nil { return newRes, err } newRes.Values = append(newRes.Values, newVal) } return newRes, nil } // isNaN returns 1 if the value for each result in NumberSet, SeriesSet, or Scalar is NaN, else 0. func isNaN(e *State, varSet Results) (Results, error) { newRes := Results{} for _, res := range varSet.Values { newVal, err := perFloat(e, res, func(f float64) float64 { if math.IsNaN(f) { return 1 } return 0 }) if err != nil { return newRes, err } newRes.Values = append(newRes.Values, newVal) } return newRes, nil } // isInf returns 1 if the value for each result in NumberSet, SeriesSet, or Scalar is a // positive or negative Inf, else 0. func isInf(e *State, varSet Results) (Results, error) { newRes := Results{} for _, res := range varSet.Values { newVal, err := perFloat(e, res, func(f float64) float64 { if math.IsInf(f, 0) { return 1 } return 0 }) if err != nil { return newRes, err } newRes.Values = append(newRes.Values, newVal) } return newRes, nil } // nan returns a scalar nan value func nan(e *State) Results { aNaN := math.NaN() return NewScalarResults(e.RefID, &aNaN) } // inf returns a scalar positive infinity value func inf(e *State) Results { aInf := math.Inf(0) return NewScalarResults(e.RefID, &aInf) } // infn returns a scalar negative infinity value func infn(e *State) Results { aInf := math.Inf(-1) return NewScalarResults(e.RefID, &aInf) } // null returns a null scalar value func null(e *State) Results { return NewScalarResults(e.RefID, nil) } // isNull returns 1 if the value for each result in NumberSet, SeriesSet, or Scalar is null, else 0. func isNull(e *State, varSet Results) (Results, error) { newRes := Results{} for _, res := range varSet.Values { newVal, err := perNullableFloat(e, res, func(f *float64) *float64 { nF := float64(0) if f == nil { nF = 1 } return &nF }) if err != nil { return newRes, err } newRes.Values = append(newRes.Values, newVal) } return newRes, nil } // isNumber returns 1 if the value for each result in NumberSet, SeriesSet, or Scalar is a real number, else 0. // Therefore 0 is returned if the value Inf+, Inf-, NaN, or Null. func isNumber(e *State, varSet Results) (Results, error) { newRes := Results{} for _, res := range varSet.Values { newVal, err := perNullableFloat(e, res, func(f *float64) *float64 { nF := float64(1) if f == nil || math.IsInf(*f, 0) || math.IsNaN(*f) { nF = 0 } return &nF }) if err != nil { return newRes, err } newRes.Values = append(newRes.Values, newVal) } return newRes, nil } // perFloat passes the non-null value of a Scalar/Number or each value point of a Series to floatF. // The return Value type will be the same type provided to function, (e.g. a Series input returns a series). // If input values are null the function is not called and NaN is returned for each value. func perFloat(e *State, val Value, floatF func(x float64) float64) (Value, error) { var newVal Value switch val.Type() { case parse.TypeNumberSet: n := NewNumber(e.RefID, val.GetLabels()) f := val.(Number).GetFloat64Value() nF := math.NaN() if f != nil { nF = floatF(*f) } n.SetValue(&nF) newVal = n case parse.TypeScalar: f := val.(Scalar).GetFloat64Value() nF := math.NaN() if f != nil { nF = floatF(*f) } newVal = NewScalar(e.RefID, &nF) case parse.TypeSeriesSet: resSeries := val.(Series) newSeries := NewSeries(e.RefID, resSeries.GetLabels(), resSeries.Len()) for i := 0; i < resSeries.Len(); i++ { t, f := resSeries.GetPoint(i) nF := math.NaN() if f != nil { nF = floatF(*f) } newSeries.SetPoint(i, t, &nF) } newVal = newSeries default: // TODO: Should we deal with TypeString, TypeVariantSet? } return newVal, nil } // perNullableFloat is like perFloat, but takes and returns float pointers instead of floats. // This is for instead for functions that need specific null handling. // The input float pointer should not be modified in the floatF func. func perNullableFloat(e *State, val Value, floatF func(x *float64) *float64) (Value, error) { var newVal Value switch val.Type() { case parse.TypeNumberSet: n := NewNumber(e.RefID, val.GetLabels()) f := val.(Number).GetFloat64Value() n.SetValue(floatF(f)) newVal = n case parse.TypeScalar: f := val.(Scalar).GetFloat64Value() newVal = NewScalar(e.RefID, floatF(f)) case parse.TypeSeriesSet: resSeries := val.(Series) newSeries := NewSeries(e.RefID, resSeries.GetLabels(), resSeries.Len()) for i := 0; i < resSeries.Len(); i++ { t, f := resSeries.GetPoint(i) newSeries.SetPoint(i, t, floatF(f)) } newVal = newSeries default: // TODO: Should we deal with TypeString, TypeVariantSet? } return newVal, nil } // round returns the rounded value for each result in NumberSet, SeriesSet, or Scalar func round(e *State, varSet Results) (Results, error) { newRes := Results{} for _, res := range varSet.Values { newVal, err := perFloat(e, res, math.Round) if err != nil { return newRes, err } newRes.Values = append(newRes.Values, newVal) } return newRes, nil } // ceil returns the rounded up value for each result in NumberSet, SeriesSet, or Scalar func ceil(e *State, varSet Results) (Results, error) { newRes := Results{} for _, res := range varSet.Values { newVal, err := perFloat(e, res, math.Ceil) if err != nil { return newRes, err } newRes.Values = append(newRes.Values, newVal) } return newRes, nil } // floor returns the rounded down value for each result in NumberSet, SeriesSet, or Scalar func floor(e *State, varSet Results) (Results, error) { newRes := Results{} for _, res := range varSet.Values { newVal, err := perFloat(e, res, math.Floor) if err != nil { return newRes, err } newRes.Values = append(newRes.Values, newVal) } return newRes, nil }