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 }