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

     1  package reduce
     2  
     3  import (
     4  	"context"
     5  
     6  	"github.com/go-graphite/carbonapi/expr/helper"
     7  	"github.com/go-graphite/carbonapi/expr/interfaces"
     8  	"github.com/go-graphite/carbonapi/expr/types"
     9  	"github.com/go-graphite/carbonapi/pkg/parser"
    10  
    11  	"strings"
    12  )
    13  
    14  type reduce struct{}
    15  
    16  func GetOrder() interfaces.Order {
    17  	return interfaces.Any
    18  }
    19  
    20  func New(configFile string) []interfaces.FunctionMetadata {
    21  	res := make([]interfaces.FunctionMetadata, 0)
    22  	f := &reduce{}
    23  	functions := []string{"reduceSeries", "reduce"}
    24  	for _, n := range functions {
    25  		res = append(res, interfaces.FunctionMetadata{Name: n, F: f})
    26  	}
    27  	return res
    28  }
    29  
    30  func (f *reduce) Do(ctx context.Context, eval interfaces.Evaluator, e parser.Expr, from, until int64, values map[parser.MetricRequest][]*types.MetricData) ([]*types.MetricData, error) {
    31  	const matchersStartIndex = 3
    32  
    33  	if e.ArgsLen() < matchersStartIndex+1 {
    34  		return nil, parser.ErrMissingArgument
    35  	}
    36  
    37  	seriesList, err := helper.GetSeriesArg(ctx, eval, e.Arg(0), from, until, values)
    38  	if err != nil {
    39  		return nil, err
    40  	}
    41  
    42  	reduceFunction, err := e.GetStringArg(1)
    43  	if err != nil {
    44  		return nil, err
    45  	}
    46  
    47  	reduceNode, err := e.GetIntArg(2)
    48  	if err != nil {
    49  		return nil, err
    50  	}
    51  
    52  	argsCount := e.ArgsLen()
    53  	matchersCount := argsCount - matchersStartIndex
    54  	reduceMatchers := make([]string, matchersCount)
    55  	for i := matchersStartIndex; i < argsCount; i++ {
    56  		reduceMatcher, err := e.GetStringArg(i)
    57  		if err != nil {
    58  			return nil, err
    59  		}
    60  
    61  		reduceMatchers[i-matchersStartIndex] = reduceMatcher
    62  	}
    63  
    64  	results := make([]*types.MetricData, 0, len(seriesList))
    65  
    66  	reduceGroups := make(map[string]map[string]*types.MetricData)
    67  	reducedValues := values
    68  	var aliasNames []string
    69  
    70  	for _, series := range seriesList {
    71  		metric := series.Tags["name"]
    72  		nodes := strings.Split(metric, ".")
    73  		reduceNodeKey := nodes[reduceNode]
    74  		nodes[reduceNode] = "reduce." + reduceFunction
    75  		aliasName := strings.Join(nodes, ".")
    76  		_, exist := reduceGroups[aliasName]
    77  		if !exist {
    78  			reduceGroups[aliasName] = make(map[string]*types.MetricData)
    79  			aliasNames = append(aliasNames, aliasName)
    80  		}
    81  
    82  		reduceGroups[aliasName][reduceNodeKey] = series
    83  		valueKey := parser.MetricRequest{Metric: series.Name, From: from, Until: until}
    84  		reducedValues[valueKey] = append(reducedValues[valueKey], series)
    85  	}
    86  AliasLoop:
    87  	for _, aliasName := range aliasNames {
    88  
    89  		reducedNodes := make([]parser.Expr, len(reduceMatchers))
    90  		for i, reduceMatcher := range reduceMatchers {
    91  			matched, ok := reduceGroups[aliasName][reduceMatcher]
    92  			if !ok {
    93  				continue AliasLoop
    94  			}
    95  			reducedNodes[i] = parser.NewTargetExpr(matched.Name)
    96  		}
    97  
    98  		result, err := eval.Eval(ctx, parser.NewExprTyped("alias", []parser.Expr{
    99  			parser.NewExprTyped(reduceFunction, reducedNodes),
   100  			parser.NewValueExpr(aliasName),
   101  		}), from, until, reducedValues)
   102  
   103  		if err != nil {
   104  			return nil, err
   105  		}
   106  
   107  		results = append(results, result...)
   108  	}
   109  
   110  	return results, nil
   111  }
   112  
   113  // Description is auto-generated description, based on output of https://github.com/graphite-project/graphite-web
   114  func (f *reduce) Description() map[string]types.FunctionDescription {
   115  	return map[string]types.FunctionDescription{
   116  		"reduceSeries": {
   117  			Description: "Short form: ``reduce()``\n\nTakes a list of seriesLists and reduces it to a list of series by means of the reduceFunction.\n\nReduction is performed by matching the reduceNode in each series against the list of\nreduceMatchers. Then each series is passed to the reduceFunction as arguments in the order\ngiven by reduceMatchers. The reduceFunction should yield a single series.\n\nThe resulting list of series are aliased so that they can easily be nested in other functions.\n\n**Example**: Map/Reduce asPercent(bytes_used,total_bytes) for each server\n\nAssume that metrics in the form below exist:\n\n.. code-block:: none\n\n     servers.server1.disk.bytes_used\n     servers.server1.disk.total_bytes\n     servers.server2.disk.bytes_used\n     servers.server2.disk.total_bytes\n     servers.server3.disk.bytes_used\n     servers.server3.disk.total_bytes\n     ...\n     servers.serverN.disk.bytes_used\n     servers.serverN.disk.total_bytes\n\nTo get the percentage of disk used for each server:\n\n.. code-block:: none\n\n    reduceSeries(mapSeries(servers.*.disk.*,1),\"asPercent\",3,\"bytes_used\",\"total_bytes\") =>\n\n      alias(asPercent(servers.server1.disk.bytes_used,servers.server1.disk.total_bytes),\"servers.server1.disk.reduce.asPercent\"),\n      alias(asPercent(servers.server2.disk.bytes_used,servers.server2.disk.total_bytes),\"servers.server2.disk.reduce.asPercent\"),\n      alias(asPercent(servers.server3.disk.bytes_used,servers.server3.disk.total_bytes),\"servers.server3.disk.reduce.asPercent\"),\n      ...\n      alias(asPercent(servers.serverN.disk.bytes_used,servers.serverN.disk.total_bytes),\"servers.serverN.disk.reduce.asPercent\")\n\nIn other words, we will get back the following metrics::\n\n    servers.server1.disk.reduce.asPercent\n    servers.server2.disk.reduce.asPercent\n    servers.server3.disk.reduce.asPercent\n    ...\n    servers.serverN.disk.reduce.asPercent\n\n.. seealso:: :py:func:`mapSeries`",
   118  			Function:    "reduceSeries(seriesLists, reduceFunction, reduceNode, *reduceMatchers)",
   119  			Group:       "Combine",
   120  			Module:      "graphite.render.functions",
   121  			Name:        "reduceSeries",
   122  			Params: []types.FunctionParam{
   123  				{
   124  					Name:     "seriesLists",
   125  					Required: true,
   126  					Type:     types.SeriesLists,
   127  				},
   128  				{
   129  					Name:     "reduceFunction",
   130  					Required: true,
   131  					Type:     types.String,
   132  				},
   133  				{
   134  					Name:     "reduceNode",
   135  					Required: true,
   136  					Type:     types.Node,
   137  				},
   138  				{
   139  					Multiple: true,
   140  					Name:     "reduceMatchers",
   141  					Required: true,
   142  					Type:     types.String,
   143  				},
   144  			},
   145  			SeriesChange: true, // function aggregate metrics or change series items count
   146  			NameChange:   true, // name changed
   147  			TagsChange:   true, // name tag changed
   148  			ValuesChange: true, // values changed
   149  		},
   150  		"reduce": {
   151  			Description: "Short form: ``reduce()``\n\nTakes a list of seriesLists and reduces it to a list of series by means of the reduceFunction.\n\nReduction is performed by matching the reduceNode in each series against the list of\nreduceMatchers. Then each series is passed to the reduceFunction as arguments in the order\ngiven by reduceMatchers. The reduceFunction should yield a single series.\n\nThe resulting list of series are aliased so that they can easily be nested in other functions.\n\n**Example**: Map/Reduce asPercent(bytes_used,total_bytes) for each server\n\nAssume that metrics in the form below exist:\n\n.. code-block:: none\n\n     servers.server1.disk.bytes_used\n     servers.server1.disk.total_bytes\n     servers.server2.disk.bytes_used\n     servers.server2.disk.total_bytes\n     servers.server3.disk.bytes_used\n     servers.server3.disk.total_bytes\n     ...\n     servers.serverN.disk.bytes_used\n     servers.serverN.disk.total_bytes\n\nTo get the percentage of disk used for each server:\n\n.. code-block:: none\n\n    reduceSeries(mapSeries(servers.*.disk.*,1),\"asPercent\",3,\"bytes_used\",\"total_bytes\") =>\n\n      alias(asPercent(servers.server1.disk.bytes_used,servers.server1.disk.total_bytes),\"servers.server1.disk.reduce.asPercent\"),\n      alias(asPercent(servers.server2.disk.bytes_used,servers.server2.disk.total_bytes),\"servers.server2.disk.reduce.asPercent\"),\n      alias(asPercent(servers.server3.disk.bytes_used,servers.server3.disk.total_bytes),\"servers.server3.disk.reduce.asPercent\"),\n      ...\n      alias(asPercent(servers.serverN.disk.bytes_used,servers.serverN.disk.total_bytes),\"servers.serverN.disk.reduce.asPercent\")\n\nIn other words, we will get back the following metrics::\n\n    servers.server1.disk.reduce.asPercent\n    servers.server2.disk.reduce.asPercent\n    servers.server3.disk.reduce.asPercent\n    ...\n    servers.serverN.disk.reduce.asPercent\n\n.. seealso:: :py:func:`mapSeries`",
   152  			Function:    "reduce(seriesLists, reduceFunction, reduceNode, *reduceMatchers)",
   153  			Group:       "Combine",
   154  			Module:      "graphite.render.functions",
   155  			Name:        "reduce",
   156  			Params: []types.FunctionParam{
   157  				{
   158  					Name:     "seriesLists",
   159  					Required: true,
   160  					Type:     types.SeriesLists,
   161  				},
   162  				{
   163  					Name:     "reduceFunction",
   164  					Required: true,
   165  					Type:     types.String,
   166  				},
   167  				{
   168  					Name:     "reduceNode",
   169  					Required: true,
   170  					Type:     types.Node,
   171  				},
   172  				{
   173  					Multiple: true,
   174  					Name:     "reduceMatchers",
   175  					Required: true,
   176  					Type:     types.String,
   177  				},
   178  			},
   179  			SeriesChange: true, // function aggregate metrics or change series items count
   180  			NameChange:   true, // name changed
   181  			TagsChange:   true, // name tag changed
   182  			ValuesChange: true, // values changed
   183  		},
   184  	}
   185  }