github.com/go-graphite/carbonapi@v0.17.0/expr/functions/moving/function.go (about)

     1  package moving
     2  
     3  import (
     4  	"context"
     5  	"math"
     6  	"strconv"
     7  
     8  	"github.com/lomik/zapwriter"
     9  	"github.com/spf13/viper"
    10  	"go.uber.org/zap"
    11  
    12  	"github.com/go-graphite/carbonapi/expr/helper"
    13  	"github.com/go-graphite/carbonapi/expr/interfaces"
    14  	"github.com/go-graphite/carbonapi/expr/types"
    15  	"github.com/go-graphite/carbonapi/pkg/parser"
    16  )
    17  
    18  type moving struct {
    19  	config movingConfig
    20  }
    21  
    22  func GetOrder() interfaces.Order {
    23  	return interfaces.Any
    24  }
    25  
    26  type movingConfig struct {
    27  	ReturnNaNsIfStepMismatch *bool
    28  }
    29  
    30  func New(configFile string) []interfaces.FunctionMetadata {
    31  	logger := zapwriter.Logger("functionInit").With(zap.String("function", "moving"))
    32  	res := make([]interfaces.FunctionMetadata, 0)
    33  	f := &moving{}
    34  	functions := []string{"movingAverage", "movingMin", "movingMax", "movingSum", "movingWindow"}
    35  	for _, n := range functions {
    36  		res = append(res, interfaces.FunctionMetadata{Name: n, F: f})
    37  	}
    38  
    39  	cfg := movingConfig{}
    40  	v := viper.New()
    41  	v.SetConfigFile(configFile)
    42  	err := v.ReadInConfig()
    43  	if err != nil {
    44  		logger.Info("failed to read config file, using default",
    45  			zap.Error(err),
    46  		)
    47  	} else {
    48  		err = v.Unmarshal(&cfg)
    49  		if err != nil {
    50  			logger.Fatal("failed to parse config",
    51  				zap.Error(err),
    52  			)
    53  			return nil
    54  		}
    55  		f.config = cfg
    56  	}
    57  
    58  	if cfg.ReturnNaNsIfStepMismatch == nil {
    59  		v := true
    60  		f.config.ReturnNaNsIfStepMismatch = &v
    61  	}
    62  	return res
    63  }
    64  
    65  // movingXyz(seriesList, windowSize)
    66  func (f *moving) Do(ctx context.Context, eval interfaces.Evaluator, e parser.Expr, from, until int64, values map[parser.MetricRequest][]*types.MetricData) ([]*types.MetricData, error) {
    67  	var n int
    68  	var err error
    69  
    70  	var argstr string
    71  	var cons string
    72  
    73  	var xFilesFactor float64
    74  
    75  	if e.ArgsLen() < 2 {
    76  		return nil, parser.ErrMissingArgument
    77  	}
    78  
    79  	adjustedStart := from
    80  	var refetch bool
    81  	var windowPoints int
    82  	var preview int64
    83  
    84  	switch e.Arg(1).Type() {
    85  	case parser.EtConst:
    86  		n, err = e.GetIntArg(1)
    87  		argstr = strconv.Itoa(n)
    88  
    89  		arg, err := helper.GetSeriesArg(ctx, eval, e.Arg(0), from, until, values)
    90  		if err != nil {
    91  			return nil, err
    92  		}
    93  		if len(arg) == 0 {
    94  			return arg, nil
    95  		}
    96  
    97  		// Find the maximum step to use for determining the altered start time
    98  		var maxStep int64
    99  		for _, a := range arg {
   100  			if a.StepTime > maxStep {
   101  				maxStep = a.StepTime
   102  			}
   103  		}
   104  		preview = maxStep * int64(n)
   105  		adjustedStart -= maxStep * int64(n)
   106  		windowPoints = n
   107  		if adjustedStart != from {
   108  			refetch = true
   109  		}
   110  	case parser.EtString:
   111  		var n32 int32
   112  		n32, err = e.GetIntervalArg(1, 1)
   113  		argstr = "'" + e.Arg(1).StringValue() + "'"
   114  		preview = int64(math.Abs(float64(n32))) // Absolute is used in order to handle negative string intervals
   115  		adjustedStart -= preview
   116  	default:
   117  		err = parser.ErrBadType
   118  	}
   119  	if err != nil {
   120  		return nil, err
   121  	}
   122  
   123  	var targetValues map[parser.MetricRequest][]*types.MetricData
   124  	if refetch {
   125  		targetValues, err = eval.Fetch(ctx, []parser.Expr{e.Arg(0)}, adjustedStart, until, values)
   126  		if err != nil {
   127  			return nil, err
   128  		}
   129  	} else {
   130  		targetValues = values
   131  	}
   132  
   133  	adjustedArgs, err := helper.GetSeriesArg(ctx, eval, e.Arg(0), adjustedStart, until, targetValues)
   134  	if err != nil {
   135  		return nil, err
   136  	}
   137  
   138  	if len(adjustedArgs) == 0 {
   139  		return adjustedArgs, nil
   140  	}
   141  
   142  	if e.ArgsLen() >= 2 && e.Target() == "movingWindow" {
   143  		cons, err = e.GetStringArgDefault(2, "average")
   144  		if err != nil {
   145  			return nil, err
   146  		}
   147  
   148  		if e.ArgsLen() == 4 {
   149  			xFilesFactor, err = e.GetFloatArgDefault(3, float64(adjustedArgs[0].XFilesFactor))
   150  
   151  			if err != nil {
   152  				return nil, err
   153  			}
   154  		}
   155  	} else if e.ArgsLen() == 3 {
   156  		xFilesFactor, err = e.GetFloatArgDefault(2, float64(adjustedArgs[0].XFilesFactor))
   157  
   158  		if err != nil {
   159  			return nil, err
   160  		}
   161  	}
   162  
   163  	switch e.Target() {
   164  	case "movingAverage":
   165  		cons = "average"
   166  	case "movingSum":
   167  		cons = "sum"
   168  	case "movingMin":
   169  		cons = "min"
   170  	case "movingMax":
   171  		cons = "max"
   172  	case "movingMedian":
   173  		cons = "median"
   174  	}
   175  
   176  	result := make([]*types.MetricData, len(adjustedArgs))
   177  
   178  	for j, a := range adjustedArgs {
   179  		r := a.CopyLink()
   180  		r.Name = e.Target() + "(" + a.Name + "," + argstr + ")"
   181  		r.Tags[e.Target()] = argstr
   182  
   183  		if e.Arg(1).Type() == parser.EtString {
   184  			windowPoints = int(preview / a.StepTime)
   185  		}
   186  
   187  		if windowPoints == 0 {
   188  			if *f.config.ReturnNaNsIfStepMismatch {
   189  				r.Values = make([]float64, len(a.Values))
   190  				for i := range a.Values {
   191  					r.Values[i] = math.NaN()
   192  				}
   193  			}
   194  			r.StartTime += preview
   195  			r.StopTime += preview
   196  			result[j] = r
   197  			continue
   198  		}
   199  
   200  		size := len(a.Values) - windowPoints
   201  		if size < 0 {
   202  			size = 0
   203  		}
   204  		r.Values = make([]float64, size)
   205  		r.StartTime = a.StartTime + preview
   206  		r.StopTime = r.StartTime + int64(len(r.Values))*r.StepTime
   207  
   208  		w := &types.Windowed{Data: make([]float64, windowPoints)}
   209  		for i := 1; i < len(a.Values); i++ { // ignoring the first value in the series to avoid shifting of results one step in the future
   210  			w.Push(a.Values[i])
   211  
   212  			if ridx := i - windowPoints; ridx >= 0 {
   213  				if w.IsNonNull() && helper.XFilesFactorValues(w.Data, xFilesFactor) {
   214  					switch cons {
   215  					case "average":
   216  						r.Values[ridx] = w.Mean()
   217  					case "avg":
   218  						r.Values[ridx] = w.Mean()
   219  					case "avg_zero":
   220  						r.Values[ridx] = w.MeanZero()
   221  					case "sum":
   222  						r.Values[ridx] = w.Sum()
   223  					case "min":
   224  						r.Values[ridx] = w.Min()
   225  					case "max":
   226  						r.Values[ridx] = w.Max()
   227  					case "multiply":
   228  						r.Values[ridx] = w.Multiply()
   229  					case "range":
   230  						r.Values[ridx] = w.Range()
   231  					case "diff":
   232  						r.Values[ridx] = w.Diff()
   233  					case "stddev":
   234  						r.Values[ridx] = w.Stdev()
   235  					case "count":
   236  						r.Values[ridx] = w.Count()
   237  					case "last":
   238  						r.Values[ridx] = w.Last()
   239  					case "median":
   240  						r.Values[ridx] = w.Median()
   241  					}
   242  					if i < windowPoints || math.IsNaN(r.Values[ridx]) {
   243  						r.Values[ridx] = math.NaN()
   244  					}
   245  				} else {
   246  					r.Values[ridx] = math.NaN()
   247  				}
   248  			}
   249  		}
   250  		result[j] = r
   251  	}
   252  	return result, nil
   253  }
   254  
   255  // Description is auto-generated description, based on output of https://github.com/graphite-project/graphite-web
   256  func (f *moving) Description() map[string]types.FunctionDescription {
   257  	return map[string]types.FunctionDescription{
   258  		"movingWindow": {
   259  			Description: "Graphs a moving window function of a metric (or metrics) over a fixed number of past points, or a time interval.\n\nTakes one metric or a wildcard seriesList followed by a number N of datapoints\nor a quoted string with a length of time like '1hour' or '5min' (See ``from /\nuntil`` in the render\\_api_ for examples of time formats), and an xFilesFactor value to specify\nhow many points in the window must be non-null for the output to be considered valid. Graphs the\nsum of the preceeding datapoints for each point on the graph.\n\nExample:\n\n.. code-block:: none\n\n  &target=movingWindow(Server.instance01.threads.busy,10)\n  &target=movingWindow(Server.instance*.threads.idle,'5min','median',0.5)",
   260  			Function:    "movingWindow(seriesList, windowSize, func='average', xFilesFactor=None)",
   261  			Group:       "Calculate",
   262  			Module:      "graphite.render.functions",
   263  			Name:        "movingWindow",
   264  			Params: []types.FunctionParam{
   265  				{
   266  					Name:     "seriesList",
   267  					Required: true,
   268  					Type:     types.SeriesList,
   269  				},
   270  				{
   271  					Name:     "windowSize",
   272  					Required: true,
   273  					Suggestions: types.NewSuggestions(
   274  						5,
   275  						7,
   276  						10,
   277  						"1min",
   278  						"5min",
   279  						"10min",
   280  						"30min",
   281  						"1hour",
   282  					),
   283  					Type: types.IntOrInterval,
   284  				},
   285  				{
   286  					Name: "func",
   287  					Type: types.AggFunc,
   288  				},
   289  				{
   290  					Name: "xFilesFactor",
   291  					Type: types.Float,
   292  				},
   293  			},
   294  		},
   295  		"movingAverage": {
   296  			Description: "Graphs the moving average of a metric (or metrics) over a fixed number of\npast points, or a time interval.\n\nTakes one metric or a wildcard seriesList followed by a number N of datapoints\nor a quoted string with a length of time like '1hour' or '5min' (See ``from /\nuntil`` in the render\\_api_ for examples of time formats), and an xFilesFactor value to specify\nhow many points in the window must be non-null for the output to be considered valid. Graphs the\naverage of the preceeding datapoints for each point on the graph.\n\nExample:\n\n.. code-block:: none\n\n  &target=movingAverage(Server.instance01.threads.busy,10)\n  &target=movingAverage(Server.instance*.threads.idle,'5min')",
   297  			Function:    "movingAverage(seriesList, windowSize, xFilesFactor=None)",
   298  			Group:       "Calculate",
   299  			Module:      "graphite.render.functions",
   300  			Name:        "movingAverage",
   301  			Params: []types.FunctionParam{
   302  				{
   303  					Name:     "seriesList",
   304  					Required: true,
   305  					Type:     types.SeriesList,
   306  				},
   307  				{
   308  					Name:     "windowSize",
   309  					Required: true,
   310  					Suggestions: types.NewSuggestions(
   311  						5,
   312  						7,
   313  						10,
   314  						"1min",
   315  						"5min",
   316  						"10min",
   317  						"30min",
   318  						"1hour",
   319  					),
   320  					Type: types.IntOrInterval,
   321  				},
   322  				{
   323  					Name: "xFilesFactor",
   324  					Type: types.Float,
   325  				},
   326  			},
   327  			NameChange:   true, // name changed
   328  			ValuesChange: true, // values changed
   329  		},
   330  		"movingMin": {
   331  			Description: "Graphs the moving minimum of a metric (or metrics) over a fixed number of\npast points, or a time interval.\n\nTakes one metric or a wildcard seriesList followed by a number N of datapoints\nor a quoted string with a length of time like '1hour' or '5min' (See ``from /\nuntil`` in the render\\_api_ for examples of time formats), and an xFilesFactor value to specify\nhow many points in the window must be non-null for the output to be considered valid. Graphs the\nminimum of the preceeding datapoints for each point on the graph.\n\nExample:\n\n.. code-block:: none\n\n  &target=movingMin(Server.instance01.requests,10)\n  &target=movingMin(Server.instance*.errors,'5min')",
   332  			Function:    "movingMin(seriesList, windowSize, xFilesFactor=None)",
   333  			Group:       "Calculate",
   334  			Module:      "graphite.render.functions",
   335  			Name:        "movingMin",
   336  			Params: []types.FunctionParam{
   337  				{
   338  					Name:     "seriesList",
   339  					Required: true,
   340  					Type:     types.SeriesList,
   341  				},
   342  				{
   343  					Name:     "windowSize",
   344  					Required: true,
   345  					Suggestions: types.NewSuggestions(
   346  						5,
   347  						7,
   348  						10,
   349  						"1min",
   350  						"5min",
   351  						"10min",
   352  						"30min",
   353  						"1hour",
   354  					),
   355  					Type: types.IntOrInterval,
   356  				},
   357  				{
   358  					Name: "xFilesFactor",
   359  					Type: types.Float,
   360  				},
   361  			},
   362  			NameChange:   true, // name changed
   363  			ValuesChange: true, // values changed
   364  		},
   365  		"movingMax": {
   366  			Description: "Graphs the moving maximum of a metric (or metrics) over a fixed number of\npast points, or a time interval.\n\nTakes one metric or a wildcard seriesList followed by a number N of datapoints\nor a quoted string with a length of time like '1hour' or '5min' (See ``from /\nuntil`` in the render\\_api_ for examples of time formats), and an xFilesFactor value to specify\nhow many points in the window must be non-null for the output to be considered valid. Graphs the\nmaximum of the preceeding datapoints for each point on the graph.\n\nExample:\n\n.. code-block:: none\n\n  &target=movingMax(Server.instance01.requests,10)\n  &target=movingMax(Server.instance*.errors,'5min')",
   367  			Function:    "movingMax(seriesList, windowSize, xFilesFactor=None)",
   368  			Group:       "Calculate",
   369  			Module:      "graphite.render.functions",
   370  			Name:        "movingMax",
   371  			Params: []types.FunctionParam{
   372  				{
   373  					Name:     "seriesList",
   374  					Required: true,
   375  					Type:     types.SeriesList,
   376  				},
   377  				{
   378  					Name:     "windowSize",
   379  					Required: true,
   380  					Suggestions: types.NewSuggestions(
   381  						5,
   382  						7,
   383  						10,
   384  						"1min",
   385  						"5min",
   386  						"10min",
   387  						"30min",
   388  						"1hour",
   389  					),
   390  					Type: types.IntOrInterval,
   391  				},
   392  				{
   393  					Name: "xFilesFactor",
   394  					Type: types.Float,
   395  				},
   396  			},
   397  			NameChange:   true, // name changed
   398  			ValuesChange: true, // values changed
   399  		},
   400  		"movingSum": {
   401  			Description: "Graphs the moving sum of a metric (or metrics) over a fixed number of\npast points, or a time interval.\n\nTakes one metric or a wildcard seriesList followed by a number N of datapoints\nor a quoted string with a length of time like '1hour' or '5min' (See ``from /\nuntil`` in the render\\_api_ for examples of time formats), and an xFilesFactor value to specify\nhow many points in the window must be non-null for the output to be considered valid. Graphs the\nsum of the preceeding datapoints for each point on the graph.\n\nExample:\n\n.. code-block:: none\n\n  &target=movingSum(Server.instance01.requests,10)\n  &target=movingSum(Server.instance*.errors,'5min')",
   402  			Function:    "movingSum(seriesList, windowSize, xFilesFactor=None)",
   403  			Group:       "Calculate",
   404  			Module:      "graphite.render.functions",
   405  			Name:        "movingSum",
   406  			Params: []types.FunctionParam{
   407  				{
   408  					Name:     "seriesList",
   409  					Required: true,
   410  					Type:     types.SeriesList,
   411  				},
   412  				{
   413  					Name:     "windowSize",
   414  					Required: true,
   415  					Suggestions: types.NewSuggestions(
   416  						5,
   417  						7,
   418  						10,
   419  						"1min",
   420  						"5min",
   421  						"10min",
   422  						"30min",
   423  						"1hour",
   424  					),
   425  					Type: types.IntOrInterval,
   426  				},
   427  				{
   428  					Name: "xFilesFactor",
   429  					Type: types.Float,
   430  				},
   431  			},
   432  			NameChange:   true, // name changed
   433  			ValuesChange: true, // values changed
   434  		},
   435  	}
   436  }