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

     1  package applyByNode
     2  
     3  import (
     4  	"context"
     5  	"strings"
     6  
     7  	"github.com/ansel1/merry"
     8  	"github.com/go-graphite/carbonapi/expr/helper"
     9  	"github.com/go-graphite/carbonapi/expr/interfaces"
    10  	"github.com/go-graphite/carbonapi/expr/types"
    11  	"github.com/go-graphite/carbonapi/pkg/parser"
    12  )
    13  
    14  func GetOrder() interfaces.Order {
    15  	return interfaces.Any
    16  }
    17  
    18  type applyByNode struct {
    19  	interfaces.Function
    20  }
    21  
    22  func New(configFile string) []interfaces.RewriteFunctionMetadata {
    23  	res := make([]interfaces.RewriteFunctionMetadata, 0)
    24  	f := &applyByNode{}
    25  	for _, n := range []string{"applyByNode"} {
    26  		res = append(res, interfaces.RewriteFunctionMetadata{Name: n, F: f})
    27  	}
    28  	return res
    29  }
    30  
    31  func (f *applyByNode) Do(ctx context.Context, eval interfaces.Evaluator, e parser.Expr, from, until int64, values map[parser.MetricRequest][]*types.MetricData) (bool, []string, error) {
    32  	args, err := helper.GetSeriesArg(ctx, eval, e.Arg(0), from, until, values)
    33  	if err != nil {
    34  		return false, nil, err
    35  	}
    36  
    37  	nodeNum, err := e.GetIntArg(1)
    38  	if err != nil {
    39  		return false, nil, err
    40  	}
    41  
    42  	callback, err := e.GetStringArg(2)
    43  	if err != nil {
    44  		return false, nil, err
    45  	}
    46  
    47  	var newName string
    48  	if e.ArgsLen() == 4 {
    49  		newName, err = e.GetStringArg(3)
    50  		if err != nil {
    51  			return false, nil, err
    52  		}
    53  	}
    54  
    55  	rv := make([]string, 0, len(args))
    56  	for _, a := range args {
    57  		var node string
    58  		metric := a.Tags["name"]
    59  		nodes := strings.Split(metric, ".")
    60  		if nodeNum >= len(nodes) {
    61  			// field overflow
    62  			err := merry.WithMessagef(parser.ErrInvalidArg, "name=%s: nodeNum must be less than %d", metric, len(nodes))
    63  			return false, nil, err
    64  		} else {
    65  			node = strings.Join(nodes[0:nodeNum+1], ".")
    66  		}
    67  		newTarget := strings.ReplaceAll(callback, "%", node)
    68  
    69  		if newName != "" {
    70  			newTarget = "alias(" + newTarget + ",\"" + strings.ReplaceAll(newName, "%", node) + "\")"
    71  		}
    72  		rv = append(rv, newTarget)
    73  	}
    74  	return true, rv, nil
    75  }
    76  
    77  // Description is auto-generated description, based on output of https://github.com/graphite-project/graphite-web
    78  func (f *applyByNode) Description() map[string]types.FunctionDescription {
    79  	return map[string]types.FunctionDescription{
    80  		"applyByNode": {
    81  			Name: "applyByNode",
    82  			Params: []types.FunctionParam{
    83  				{
    84  					Name:     "seriesList",
    85  					Required: true,
    86  					Type:     types.SeriesList,
    87  				},
    88  				{
    89  					Name:     "nodeNum",
    90  					Required: true,
    91  					Type:     types.Node,
    92  				},
    93  				{
    94  					Name:     "templateFunction",
    95  					Required: true,
    96  					Type:     types.String,
    97  				},
    98  				{
    99  					Name: "newName",
   100  					Type: types.String,
   101  				},
   102  			},
   103  			Module:      "graphite.render.functions",
   104  			Description: "Takes a seriesList and applies some complicated function (described by a string), replacing templates with unique\nprefixes of keys from the seriesList (the key is all nodes up to the index given as `nodeNum`).\n\nIf the `newName` parameter is provided, the name of the resulting series will be given by that parameter, with any\n\"%\" characters replaced by the unique prefix.\n\nExample:\n\n.. code-block:: none\n\n  &target=applyByNode(servers.*.disk.bytes_free,1,\"divideSeries(%.disk.bytes_free,sumSeries(%.disk.bytes_*))\")\n\nWould find all series which match `servers.*.disk.bytes_free`, then trim them down to unique series up to the node\ngiven by nodeNum, then fill them into the template function provided (replacing % by the prefixes).\n\nAdditional Examples:\n\nGiven keys of\n\n- `stats.counts.haproxy.web.2XX`\n- `stats.counts.haproxy.web.3XX`\n- `stats.counts.haproxy.web.5XX`\n- `stats.counts.haproxy.microservice.2XX`\n- `stats.counts.haproxy.microservice.3XX`\n- `stats.counts.haproxy.microservice.5XX`\n\nThe following will return the rate of 5XX's per service:\n\n.. code-block:: none\n\n  applyByNode(stats.counts.haproxy.*.*XX, 3, \"asPercent(%.5XX, sumSeries(%.*XX))\", \"%.pct_5XX\")\n\nThe output series would have keys `stats.counts.haproxy.web.pct_5XX` and `stats.counts.haproxy.microservice.pct_5XX`.",
   105  			Function:    "applyByNode(seriesList, nodeNum, templateFunction, newName=None)",
   106  			Group:       "Combine",
   107  		},
   108  	}
   109  }