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

     1  package aggregate
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"strings"
     7  
     8  	"github.com/go-graphite/carbonapi/expr/consolidations"
     9  	fconfig "github.com/go-graphite/carbonapi/expr/functions/config"
    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 aggregate struct{}
    17  
    18  func GetOrder() interfaces.Order {
    19  	return interfaces.Any
    20  }
    21  
    22  func New(configFile string) []interfaces.FunctionMetadata {
    23  	f := &aggregate{}
    24  	res := make([]interfaces.FunctionMetadata, 0)
    25  	for _, n := range []string{"aggregate"} {
    26  		res = append(res, interfaces.FunctionMetadata{Name: n, F: f})
    27  	}
    28  
    29  	// Also register aliases for each and every summarizer
    30  	for _, n := range consolidations.AvailableSummarizers {
    31  		res = append(res,
    32  			interfaces.FunctionMetadata{Name: n, F: f},
    33  			interfaces.FunctionMetadata{Name: n + "Series", F: f},
    34  		)
    35  	}
    36  	return res
    37  }
    38  
    39  // aggregate(*seriesLists)
    40  func (f *aggregate) Do(ctx context.Context, eval interfaces.Evaluator, e parser.Expr, from, until int64, values map[parser.MetricRequest][]*types.MetricData) ([]*types.MetricData, error) {
    41  	var args []*types.MetricData
    42  	var xFilesFactor float64
    43  	isAggregateFunc := true
    44  
    45  	callback, err := e.GetStringArg(1)
    46  	if err != nil {
    47  		if e.Target() == "aggregate" {
    48  			return nil, err
    49  		} else {
    50  			args, err = helper.GetSeriesArgsAndRemoveNonExisting(ctx, eval, e, from, until, values)
    51  			if err != nil {
    52  				return nil, err
    53  			}
    54  			if len(args) == 0 {
    55  				return []*types.MetricData{}, nil
    56  			}
    57  			callback = strings.Replace(e.Target(), "Series", "", 1)
    58  			isAggregateFunc = false
    59  			xFilesFactor = -1 // xFilesFactor is not used by the ...Series functions
    60  		}
    61  	} else {
    62  		args, err = helper.GetSeriesArg(ctx, eval, e.Arg(0), from, until, values)
    63  		if err != nil {
    64  			return nil, err
    65  		}
    66  		if len(args) == 0 {
    67  			return []*types.MetricData{}, nil
    68  		}
    69  
    70  		xFilesFactor, err = e.GetFloatArgDefault(2, float64(args[0].XFilesFactor)) // If set by setXFilesFactor, all series in a list will have the same value
    71  		if err != nil {
    72  			return nil, err
    73  		}
    74  	}
    75  
    76  	aggFunc, ok := consolidations.ConsolidationToFunc[callback]
    77  	if !ok {
    78  		return nil, fmt.Errorf("unsupported consolidation function %s", callback)
    79  	}
    80  	target := callback + "Series"
    81  
    82  	e.SetTarget(target)
    83  	if isAggregateFunc {
    84  		e.SetRawArgs(e.Arg(0).Target())
    85  	}
    86  
    87  	results, err := helper.AggregateSeries(e, args, aggFunc, xFilesFactor, fconfig.Config.ExtractTagsFromArgs)
    88  	if err != nil {
    89  		return nil, err
    90  	}
    91  
    92  	for _, result := range results {
    93  		result.Tags["aggregatedBy"] = callback
    94  	}
    95  
    96  	return results, nil
    97  }
    98  
    99  // Description is auto-generated description, based on output of https://github.com/graphite-project/graphite-web
   100  func (f *aggregate) Description() map[string]types.FunctionDescription {
   101  	// TODO(Civil): this should be reworked. Graphite do not provide consistent mappings for some of the consolidation
   102  	// functions. Also it's very easy to miss something obvious here.
   103  	return map[string]types.FunctionDescription{
   104  		"aggregate": {
   105  			Name:        "aggregate",
   106  			Function:    "aggregate(seriesList, func, xFilesFactor=None)",
   107  			Description: "Aggregate series using the specified function.\n\nExample:\n\n.. code-block:: none\n\n  &target=aggregate(host.cpu-[0-7}.cpu-{user,system}.value, \"sum\")\n\nThis would be the equivalent of\n\n.. code-block:: none\n\n  &target=sumSeries(host.cpu-[0-7}.cpu-{user,system}.value)\n\nThis function can be used with aggregation functions ``average``, ``median``, ``sum``, ``min``,\n``max``, ``diff``, ``stddev``, ``count``, ``range``, ``multiply`` & ``last``.",
   108  			Module:      "graphite.render.functions",
   109  			Group:       "Combine",
   110  			Params: []types.FunctionParam{
   111  				{
   112  					Name:     "seriesList",
   113  					Type:     types.SeriesList,
   114  					Required: true,
   115  				},
   116  				{
   117  					Name:     "func",
   118  					Type:     types.AggFunc,
   119  					Required: true,
   120  					Options:  types.StringsToSuggestionList(consolidations.AvailableConsolidationFuncs()),
   121  				},
   122  				{
   123  
   124  					Name:     "xFilesFactor",
   125  					Type:     types.Float,
   126  					Required: false,
   127  				},
   128  			},
   129  			SeriesChange: true, // function aggregate metrics or change series items count
   130  			NameChange:   true, // name changed
   131  			TagsChange:   true, // name tag changed
   132  			ValuesChange: true, // values changed
   133  		},
   134  		"averageSeries": {
   135  			Description: "Short Alias: avg()\n\nTakes one metric or a wildcard seriesList.\nDraws the average value of all metrics passed at each time.\n\nExample:\n\n.. code-block:: none\n\n  &target=averageSeries(company.server.*.threads.busy)\n\nThis is an alias for :py:func:`aggregate <aggregate>` with aggregation ``average``.",
   136  			Function:    "averageSeries(*seriesLists)",
   137  			Group:       "Combine",
   138  			Module:      "graphite.render.functions",
   139  			Name:        "averageSeries",
   140  			Params: []types.FunctionParam{
   141  				{
   142  					Multiple: true,
   143  					Name:     "seriesLists",
   144  					Required: true,
   145  					Type:     types.SeriesList,
   146  				},
   147  			},
   148  			SeriesChange: true, // function aggregate metrics or change series items count
   149  			NameChange:   true, // name changed
   150  			TagsChange:   true, // name tag changed
   151  			ValuesChange: true, // values changed
   152  		},
   153  		"avg": {
   154  			Description: "Short Alias: avg()\n\nTakes one metric or a wildcard seriesList.\nDraws the average value of all metrics passed at each time.\n\nExample:\n\n.. code-block:: none\n\n  &target=averageSeries(company.server.*.threads.busy)\n\nThis is an alias for :py:func:`aggregate <aggregate>` with aggregation ``average``.",
   155  			Function:    "avg(*seriesLists)",
   156  			Group:       "Combine",
   157  			Module:      "graphite.render.functions",
   158  			Name:        "avg",
   159  			Params: []types.FunctionParam{
   160  				{
   161  					Multiple: true,
   162  					Name:     "seriesLists",
   163  					Required: true,
   164  					Type:     types.SeriesList,
   165  				},
   166  			},
   167  			SeriesChange: true, // function aggregate metrics or change series items count
   168  			NameChange:   true, // name changed
   169  			TagsChange:   true, // name tag changed
   170  			ValuesChange: true, // values changed
   171  		},
   172  		"max": {
   173  			Description: "Takes one metric or a wildcard seriesList.\nFor each datapoint from each metric passed in, pick the maximum value and graph it.\n\nExample:\n\n.. code-block:: none\n\n  &target=maxSeries(Server*.connections.total)\n\nThis is an alias for :py:func:`aggregate <aggregate>` with aggregation ``max``.",
   174  			Function:    "maxSeries(*seriesLists)",
   175  			Group:       "Combine",
   176  			Module:      "graphite.render.functions",
   177  			Name:        "maxSeries",
   178  			Params: []types.FunctionParam{
   179  				{
   180  					Multiple: true,
   181  					Name:     "seriesLists",
   182  					Required: true,
   183  					Type:     types.SeriesList,
   184  				},
   185  			},
   186  			SeriesChange: true, // function aggregate metrics or change series items count
   187  			NameChange:   true, // name changed
   188  			TagsChange:   true, // name tag changed
   189  			ValuesChange: true, // values changed
   190  		},
   191  		"maxSeries": {
   192  			Description: "Takes one metric or a wildcard seriesList.\nFor each datapoint from each metric passed in, pick the maximum value and graph it.\n\nExample:\n\n.. code-block:: none\n\n  &target=maxSeries(Server*.connections.total)\n\nThis is an alias for :py:func:`aggregate <aggregate>` with aggregation ``max``.",
   193  			Function:    "maxSeries(*seriesLists)",
   194  			Group:       "Combine",
   195  			Module:      "graphite.render.functions",
   196  			Name:        "maxSeries",
   197  			Params: []types.FunctionParam{
   198  				{
   199  					Multiple: true,
   200  					Name:     "seriesLists",
   201  					Required: true,
   202  					Type:     types.SeriesList,
   203  				},
   204  			},
   205  			SeriesChange: true, // function aggregate metrics or change series items count
   206  			NameChange:   true, // name changed
   207  			TagsChange:   true, // name tag changed
   208  			ValuesChange: true, // values changed
   209  		},
   210  		"min": {
   211  			Description: "Takes one metric or a wildcard seriesList.\nFor each datapoint from each metric passed in, pick the minimum value and graph it.\n\nExample:\n\n.. code-block:: none\n\n  &target=minSeries(Server*.connections.total)\n\nThis is an alias for :py:func:`aggregate <aggregate>` with aggregation ``min``.",
   212  			Function:    "minSeries(*seriesLists)",
   213  			Group:       "Combine",
   214  			Module:      "graphite.render.functions",
   215  			Name:        "minSeries",
   216  			Params: []types.FunctionParam{
   217  				{
   218  					Multiple: true,
   219  					Name:     "seriesLists",
   220  					Required: true,
   221  					Type:     types.SeriesList,
   222  				},
   223  			},
   224  			SeriesChange: true, // function aggregate metrics or change series items count
   225  			NameChange:   true, // name changed
   226  			TagsChange:   true, // name tag changed
   227  			ValuesChange: true, // values changed
   228  		},
   229  		"minSeries": {
   230  			Description: "Takes one metric or a wildcard seriesList.\nFor each datapoint from each metric passed in, pick the minimum value and graph it.\n\nExample:\n\n.. code-block:: none\n\n  &target=minSeries(Server*.connections.total)\n\nThis is an alias for :py:func:`aggregate <aggregate>` with aggregation ``min``.",
   231  			Function:    "minSeries(*seriesLists)",
   232  			Group:       "Combine",
   233  			Module:      "graphite.render.functions",
   234  			Name:        "minSeries",
   235  			Params: []types.FunctionParam{
   236  				{
   237  					Multiple: true,
   238  					Name:     "seriesLists",
   239  					Required: true,
   240  					Type:     types.SeriesList,
   241  				},
   242  			},
   243  			SeriesChange: true, // function aggregate metrics or change series items count
   244  			NameChange:   true, // name changed
   245  			TagsChange:   true, // name tag changed
   246  			ValuesChange: true, // values changed
   247  		},
   248  		"sum": {
   249  			Description: "Short form: sum()\n\nThis will add metrics together and return the sum at each datapoint. (See\nintegral for a sum over time)\n\nExample:\n\n.. code-block:: none\n\n  &target=sum(company.server.application*.requestsHandled)\n\nThis would show the sum of all requests handled per minute (provided\nrequestsHandled are collected once a minute).   If metrics with different\nretention rates are combined, the coarsest metric is graphed, and the sum\nof the other metrics is averaged for the metrics with finer retention rates.\n\nThis is an alias for :py:func:`aggregate <aggregate>` with aggregation ``sum``.",
   250  			Function:    "sum(*seriesLists)",
   251  			Group:       "Combine",
   252  			Module:      "graphite.render.functions",
   253  			Name:        "sum",
   254  			Params: []types.FunctionParam{
   255  				{
   256  					Multiple: true,
   257  					Name:     "seriesLists",
   258  					Required: true,
   259  					Type:     types.SeriesList,
   260  				},
   261  			},
   262  			SeriesChange: true, // function aggregate metrics or change series items count
   263  			NameChange:   true, // name changed
   264  			TagsChange:   true, // name tag changed
   265  			ValuesChange: true, // values changed
   266  		},
   267  		"sumSeries": {
   268  			Description: "Short form: sum()\n\nThis will add metrics together and return the sum at each datapoint. (See\nintegral for a sum over time)\n\nExample:\n\n.. code-block:: none\n\n  &target=sum(company.server.application*.requestsHandled)\n\nThis would show the sum of all requests handled per minute (provided\nrequestsHandled are collected once a minute).   If metrics with different\nretention rates are combined, the coarsest metric is graphed, and the sum\nof the other metrics is averaged for the metrics with finer retention rates.\n\nThis is an alias for :py:func:`aggregate <aggregate>` with aggregation ``sum``.",
   269  			Function:    "sumSeries(*seriesLists)",
   270  			Group:       "Combine",
   271  			Module:      "graphite.render.functions",
   272  			Name:        "sumSeries",
   273  			Params: []types.FunctionParam{
   274  				{
   275  					Multiple: true,
   276  					Name:     "seriesLists",
   277  					Required: true,
   278  					Type:     types.SeriesList,
   279  				},
   280  			},
   281  			SeriesChange: true, // function aggregate metrics or change series items count
   282  			NameChange:   true, // name changed
   283  			TagsChange:   true, // name tag changed
   284  			ValuesChange: true, // values changed
   285  		},
   286  		"stddev": {
   287  			Description: "Short form: stddev()\n\nTakes one metric or a wildcard seriesList.\nDraws the standard deviation of all metrics passed at each time.\n\nExample:\n\n.. code-block:: none\n\n  &target=stddevSeries(company.server.*.threads.busy)\n\nThis is an alias for :py:func:`aggregate <aggregate>` with aggregation ``stddev``.",
   288  			Function:    "stddev(*seriesLists)",
   289  			Group:       "Combine",
   290  			Module:      "graphite.render.functions",
   291  			Name:        "stddev",
   292  			Params: []types.FunctionParam{
   293  				{
   294  					Multiple: true,
   295  					Name:     "seriesLists",
   296  					Required: true,
   297  					Type:     types.SeriesList,
   298  				},
   299  			},
   300  			SeriesChange: true, // function aggregate metrics or change series items count
   301  			NameChange:   true, // name changed
   302  			TagsChange:   true, // name tag changed
   303  			ValuesChange: true, // values changed
   304  		},
   305  		"stddevSeries": {
   306  			Description: "Short form: stddev()\n\nTakes one metric or a wildcard seriesList.\nDraws the standard deviation of all metrics passed at each time.\n\nExample:\n\n.. code-block:: none\n\n  &target=stddevSeries(company.server.*.threads.busy)\n\nThis is an alias for :py:func:`aggregate <aggregate>` with aggregation ``stddev``.",
   307  			Function:    "stddevSeries(*seriesLists)",
   308  			Group:       "Combine",
   309  			Module:      "graphite.render.functions",
   310  			Name:        "stddevSeries",
   311  			Params: []types.FunctionParam{
   312  				{
   313  					Multiple: true,
   314  					Name:     "seriesLists",
   315  					Required: true,
   316  					Type:     types.SeriesList,
   317  				},
   318  			},
   319  			SeriesChange: true, // function aggregate metrics or change series items count
   320  			NameChange:   true, // name changed
   321  			TagsChange:   true, // name tag changed
   322  			ValuesChange: true, // values changed
   323  		},
   324  		"count": {
   325  			Description: "Draws a horizontal line representing the number of nodes found in the seriesList.\n\n.. code-block:: none\n\n  &target=count(carbon.agents.*.*)",
   326  			Function:    "count(*seriesLists)",
   327  			Group:       "Combine",
   328  			Module:      "graphite.render.functions",
   329  			Name:        "count",
   330  			Params: []types.FunctionParam{
   331  				{
   332  					Multiple: true,
   333  					Name:     "seriesLists",
   334  					Required: true,
   335  					Type:     types.SeriesList,
   336  				},
   337  			},
   338  			SeriesChange: true, // function aggregate metrics or change series items count
   339  			NameChange:   true, // name changed
   340  			TagsChange:   true, // name tag changed
   341  			ValuesChange: true, // values changed
   342  		},
   343  		"countSeries": {
   344  			Description: "Draws a horizontal line representing the number of nodes found in the seriesList.\n\n.. code-block:: none\n\n  &target=countSeries(carbon.agents.*.*)",
   345  			Function:    "countSeries(*seriesLists)",
   346  			Group:       "Combine",
   347  			Module:      "graphite.render.functions",
   348  			Name:        "countSeries",
   349  			Params: []types.FunctionParam{
   350  				{
   351  					Multiple: true,
   352  					Name:     "seriesLists",
   353  					Required: true,
   354  					Type:     types.SeriesList,
   355  				},
   356  			},
   357  			SeriesChange: true, // function aggregate metrics or change series items count
   358  			NameChange:   true, // name changed
   359  			TagsChange:   true, // name tag changed
   360  			ValuesChange: true, // values changed
   361  		},
   362  		"diff": {
   363  			Description: "Subtracts series 2 through n from series 1.\n\nExample:\n\n.. code-block:: none\n\n  &target=diff(service.connections.total,service.connections.failed)\n\nTo diff a series and a constant, one should use offset instead of (or in\naddition to) diffSeries\n\nExample:\n\n.. code-block:: none\n\n  &target=offset(service.connections.total,-5)\n\n  &target=offset(diffSeries(service.connections.total,service.connections.failed),-4)\n\nThis is an alias for :py:func:`aggregate <aggregate>` with aggregation ``diff``.",
   364  			Function:    "diff(*seriesLists)",
   365  			Group:       "Combine",
   366  			Module:      "graphite.render.functions",
   367  			Name:        "diff",
   368  			Params: []types.FunctionParam{
   369  				{
   370  					Multiple: true,
   371  					Name:     "seriesLists",
   372  					Required: true,
   373  					Type:     types.SeriesList,
   374  				},
   375  			},
   376  			SeriesChange: true, // function aggregate metrics or change series items count
   377  			NameChange:   true, // name changed
   378  			TagsChange:   true, // name tag changed
   379  			ValuesChange: true, // values changed
   380  		},
   381  		"diffSeries": {
   382  			Description: "Subtracts series 2 through n from series 1.\n\nExample:\n\n.. code-block:: none\n\n  &target=diffSeries(service.connections.total,service.connections.failed)\n\nTo diff a series and a constant, one should use offset instead of (or in\naddition to) diffSeries\n\nExample:\n\n.. code-block:: none\n\n  &target=offset(service.connections.total,-5)\n\n  &target=offset(diffSeries(service.connections.total,service.connections.failed),-4)\n\nThis is an alias for :py:func:`aggregate <aggregate>` with aggregation ``diff``.",
   383  			Function:    "diffSeries(*seriesLists)",
   384  			Group:       "Combine",
   385  			Module:      "graphite.render.functions",
   386  			Name:        "diffSeries",
   387  			Params: []types.FunctionParam{
   388  				{
   389  					Multiple: true,
   390  					Name:     "seriesLists",
   391  					Required: true,
   392  					Type:     types.SeriesList,
   393  				},
   394  			},
   395  			SeriesChange: true, // function aggregate metrics or change series items count
   396  			NameChange:   true, // name changed
   397  			TagsChange:   true, // name tag changed
   398  			ValuesChange: true, // values changed
   399  		},
   400  		"multiply": {
   401  			Description: "Takes two or more series and multiplies their points. A constant may not be\nused. To multiply by a constant, use the scale() function.\n\nExample:\n\n.. code-block:: none\n\n  &target=multiplySeries(Series.dividends,Series.divisors)\n\nThis is an alias for :py:func:`aggregate <aggregate>` with aggregation ``multiply``.",
   402  			Function:    "multiply(*seriesLists)",
   403  			Group:       "Combine",
   404  			Module:      "graphite.render.functions",
   405  			Name:        "multiply",
   406  			Params: []types.FunctionParam{
   407  				{
   408  					Multiple: true,
   409  					Name:     "seriesLists",
   410  					Required: true,
   411  					Type:     types.SeriesList,
   412  				},
   413  			},
   414  			SeriesChange: true, // function aggregate metrics or change series items count
   415  			NameChange:   true, // name changed
   416  			TagsChange:   true, // name tag changed
   417  			ValuesChange: true, // values changed
   418  		},
   419  		"multiplySeries": {
   420  			Description: "Takes two or more series and multiplies their points. A constant may not be\nused. To multiply by a constant, use the scale() function.\n\nExample:\n\n.. code-block:: none\n\n  &target=multiplySeries(Series.dividends,Series.divisors)\n\nThis is an alias for :py:func:`aggregate <aggregate>` with aggregation ``multiply``.",
   421  			Function:    "multiplySeries(*seriesLists)",
   422  			Group:       "Combine",
   423  			Module:      "graphite.render.functions",
   424  			Name:        "multiplySeries",
   425  			Params: []types.FunctionParam{
   426  				{
   427  					Multiple: true,
   428  					Name:     "seriesLists",
   429  					Required: true,
   430  					Type:     types.SeriesList,
   431  				},
   432  			},
   433  			SeriesChange: true, // function aggregate metrics or change series items count
   434  			NameChange:   true, // name changed
   435  			TagsChange:   true, // name tag changed
   436  			ValuesChange: true, // values changed
   437  		},
   438  	}
   439  }