github.com/go-graphite/carbonapi@v0.17.0/expr/functions/exponentialMovingAverage/function.go (about) 1 package exponentialMovingAverage 2 3 import ( 4 "context" 5 "fmt" 6 "math" 7 "strconv" 8 9 "github.com/go-graphite/carbonapi/expr/consolidations" 10 "github.com/go-graphite/carbonapi/expr/helper" 11 "github.com/go-graphite/carbonapi/expr/interfaces" 12 "github.com/go-graphite/carbonapi/expr/types" 13 "github.com/go-graphite/carbonapi/pkg/parser" 14 ) 15 16 type exponentialMovingAverage struct{} 17 18 func GetOrder() interfaces.Order { 19 return interfaces.Any 20 } 21 22 func New(configFile string) []interfaces.FunctionMetadata { 23 res := make([]interfaces.FunctionMetadata, 0) 24 f := &exponentialMovingAverage{} 25 functions := []string{"exponentialMovingAverage"} 26 for _, n := range functions { 27 res = append(res, interfaces.FunctionMetadata{Name: n, F: f}) 28 } 29 return res 30 } 31 32 func (f *exponentialMovingAverage) Do(ctx context.Context, eval interfaces.Evaluator, e parser.Expr, from, until int64, values map[parser.MetricRequest][]*types.MetricData) ([]*types.MetricData, error) { 33 var ( 34 windowPoints int 35 previewSeconds int 36 argstr string 37 err error 38 constant float64 39 ) 40 41 if e.ArgsLen() < 2 { 42 return nil, parser.ErrMissingArgument 43 } 44 45 refetch := false 46 switch e.Arg(1).Type() { 47 case parser.EtConst: 48 windowPoints, err = e.GetIntArg(1) 49 argstr = strconv.Itoa(windowPoints) 50 if windowPoints < 0 { 51 // we only care about the absolute value 52 windowPoints = windowPoints * -1 53 } 54 55 // When the window is an integer, we check the fetched data to get the 56 // step, and use it to calculate the preview window, to then refetch the 57 // data. The already fetched values are discarded. 58 refetch = true 59 var maxStep int64 60 arg, err := helper.GetSeriesArg(ctx, eval, e.Arg(0), from, until, values) 61 if err != nil || len(arg) == 0 { 62 return arg, err 63 } 64 for _, a := range arg { 65 if a.StepTime > maxStep { 66 maxStep = a.StepTime 67 } 68 } 69 previewSeconds = int(maxStep) * windowPoints 70 constant = float64(2 / (float64(windowPoints) + 1)) 71 case parser.EtString: 72 // When the window is a string, we already adjusted the fetch request using the preview window. 73 // No need to refetch. 74 var n32 int32 75 n32, err = e.GetIntervalArg(1, 1) 76 if err != nil { 77 return nil, err 78 } 79 argstr = strconv.Quote(e.Arg(1).StringValue()) 80 previewSeconds = int(n32) 81 if previewSeconds < 0 { 82 // we only care about the absolute value 83 previewSeconds = previewSeconds * -1 84 } 85 constant = float64(2 / (float64(previewSeconds) + 1)) 86 87 default: 88 return nil, parser.ErrBadType 89 } 90 91 if previewSeconds < 1 { 92 return nil, fmt.Errorf("invalid window size %s", e.Arg(1).StringValue()) 93 } 94 from = from - int64(previewSeconds) 95 if refetch { 96 eval.Fetch(ctx, []parser.Expr{e.Arg(0)}, from, until, values) 97 } 98 previewList, err := helper.GetSeriesArg(ctx, eval, e.Arg(0), from, until, values) 99 if err != nil { 100 return nil, err 101 } 102 103 var results []*types.MetricData 104 for _, a := range previewList { 105 r := a.CopyLink() 106 r.Name = e.Target() + "(" + a.Name + "," + argstr + ")" 107 if e.Arg(1).Type() == parser.EtString { 108 // If the window is a string (time interval), we adjust depending on the step 109 windowPoints = previewSeconds / int(a.StepTime) 110 } 111 112 vals := make([]float64, 0, len(a.Values)/windowPoints+1) 113 114 if windowPoints > len(a.Values) { 115 mean := consolidations.AggMean(a.Values) 116 vals = append(vals, helper.SafeRound(mean, 6)) 117 } else { 118 ema := consolidations.AggMean(a.Values[:windowPoints]) 119 if math.IsNaN(ema) { 120 ema = 0 121 } 122 123 vals = append(vals, helper.SafeRound(ema, 6)) 124 for _, v := range a.Values[windowPoints:] { 125 if math.IsNaN(v) { 126 vals = append(vals, math.NaN()) 127 continue 128 } 129 ema = constant*v + (1-constant)*ema 130 vals = append(vals, helper.SafeRound(ema, 6)) 131 } 132 } 133 134 r.Tags[e.Target()] = argstr 135 r.Values = vals 136 r.StartTime = r.StartTime + int64(previewSeconds) 137 r.StopTime = r.StartTime + int64(len(r.Values))*r.StepTime 138 results = append(results, r) 139 } 140 return results, nil 141 } 142 143 func (f *exponentialMovingAverage) Description() map[string]types.FunctionDescription { 144 return map[string]types.FunctionDescription{ 145 "exponentialMovingAverage": { 146 Description: "Takes a series of values and a window size and produces an exponential moving average utilizing the following formula:\n\n ema(current) = constant * (Current Value) + (1 - constant) * ema(previous)\n The Constant is calculated as:\n constant = 2 / (windowSize + 1) \n The first period EMA uses a simple moving average for its value.\n Example:\n\n code-block:: none\n\n &target=exponentialMovingAverage(*.transactions.count, 10) \n\n &target=exponentialMovingAverage(*.transactions.count, '-10s')", 147 Function: "exponentialMovingAverage(seriesList, windowSize)", 148 Group: "Calculate", 149 Module: "graphite.render.functions", 150 Name: "exponentialMovingAverage", 151 Params: []types.FunctionParam{ 152 { 153 Name: "seriesList", 154 Required: true, 155 Type: types.SeriesList, 156 }, 157 { 158 Name: "windowSize", 159 Required: true, 160 Suggestions: types.NewSuggestions( 161 0.1, 162 0.5, 163 0.7, 164 ), 165 Type: types.Float, 166 }, 167 }, 168 }, 169 } 170 }