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  }