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

     1  package perSecond
     2  
     3  import (
     4  	"context"
     5  	"errors"
     6  	"math"
     7  	"strconv"
     8  
     9  	"github.com/go-graphite/carbonapi/expr/helper"
    10  	"github.com/go-graphite/carbonapi/expr/interfaces"
    11  	"github.com/go-graphite/carbonapi/expr/types"
    12  	"github.com/go-graphite/carbonapi/pkg/parser"
    13  )
    14  
    15  // TODO(civil): See if it's possible to merge it with NonNegativeDerivative
    16  type perSecond struct{}
    17  
    18  func GetOrder() interfaces.Order {
    19  	return interfaces.Any
    20  }
    21  
    22  func New(configFile string) []interfaces.FunctionMetadata {
    23  	res := make([]interfaces.FunctionMetadata, 0)
    24  	f := &perSecond{}
    25  	functions := []string{"perSecond"}
    26  	for _, n := range functions {
    27  		res = append(res, interfaces.FunctionMetadata{Name: n, F: f})
    28  	}
    29  	return res
    30  }
    31  
    32  // perSecond(seriesList, maxValue=None)
    33  func (f *perSecond) Do(ctx context.Context, eval interfaces.Evaluator, e parser.Expr, from, until int64, values map[parser.MetricRequest][]*types.MetricData) ([]*types.MetricData, error) {
    34  	args, err := helper.GetSeriesArg(ctx, eval, e.Arg(0), from, until, values)
    35  	if err != nil {
    36  		return nil, err
    37  	}
    38  
    39  	maxValue, err := e.GetFloatNamedOrPosArgDefault("maxValue", 1, math.NaN())
    40  	if err != nil {
    41  		return nil, err
    42  	}
    43  	minValue, err := e.GetFloatNamedOrPosArgDefault("minValue", 2, math.NaN())
    44  	if err != nil {
    45  		return nil, err
    46  	}
    47  	hasMax := !math.IsNaN(maxValue)
    48  	hasMin := !math.IsNaN(minValue)
    49  
    50  	if hasMax && hasMin && maxValue <= minValue {
    51  		return nil, errors.New("minValue must be lower than maxValue")
    52  	}
    53  	if hasMax && !hasMin {
    54  		minValue = 0
    55  	}
    56  
    57  	var minValueStr string
    58  	var maxValueStr string
    59  	if hasMax {
    60  		maxValueStr = strconv.FormatFloat(maxValue, 'g', -1, 64)
    61  	}
    62  	if hasMin {
    63  		minValueStr = strconv.FormatFloat(minValue, 'g', -1, 64)
    64  	}
    65  
    66  	argMask := 0
    67  	if _, ok := e.NamedArg("maxValue"); ok || e.ArgsLen() > 1 {
    68  		argMask |= 1
    69  	}
    70  	if _, ok := e.NamedArg("minValue"); ok || e.ArgsLen() > 2 {
    71  		argMask |= 2
    72  	}
    73  
    74  	result := make([]*types.MetricData, len(args))
    75  	for i, a := range args {
    76  		var name string
    77  		switch argMask {
    78  		case 3:
    79  			name = "perSecond(" + a.Name + "," + maxValueStr + "," + minValueStr + ")"
    80  		case 2:
    81  			name = "perSecond(" + a.Name + ",minValue=" + minValueStr + ")"
    82  		case 1:
    83  			name = "perSecond(" + a.Name + "," + maxValueStr + ")"
    84  		case 0:
    85  			name = "perSecond(" + a.Name + ")"
    86  		}
    87  
    88  		r := a.CopyLink()
    89  		r.Name = name
    90  		r.Values = make([]float64, len(a.Values))
    91  		r.Tags["perSecond"] = "1"
    92  		result[i] = r
    93  
    94  		if len(a.Values) == 0 {
    95  			continue
    96  		}
    97  
    98  		prev := a.Values[0]
    99  		for i, v := range a.Values {
   100  			if i == 0 || math.IsNaN(a.Values[i]) || math.IsNaN(a.Values[i-1]) {
   101  				r.Values[i] = math.NaN()
   102  				prev = v
   103  				continue
   104  			}
   105  			// TODO(civil): Figure out if we can optimize this now when we have NaNs
   106  			diff := v - prev
   107  			if diff >= 0 {
   108  				r.Values[i] = diff / float64(a.StepTime)
   109  			} else if hasMax && maxValue >= v {
   110  				r.Values[i] = ((maxValue - prev) + (v - minValue) + 1) / float64(a.StepTime)
   111  			} else if hasMin && minValue <= v {
   112  				r.Values[i] = (v - minValue) / float64(a.StepTime)
   113  			} else {
   114  				r.Values[i] = math.NaN()
   115  			}
   116  			prev = v
   117  		}
   118  	}
   119  	return result, nil
   120  }
   121  
   122  // Description is auto-generated description, based on output of https://github.com/graphite-project/graphite-web
   123  func (f *perSecond) Description() map[string]types.FunctionDescription {
   124  	return map[string]types.FunctionDescription{
   125  		"perSecond": {
   126  			Description: "NonNegativeDerivative adjusted for the series time interval\nThis is useful for taking a running total metric and showing how many requests\nper second were handled.\n\nExample:\n\n.. code-block:: none\n\n  &target=perSecond(company.server.application01.ifconfig.TXPackets)\n\nEach time you run ifconfig, the RX and TXPackets are higher (assuming there\nis network traffic.) By applying the perSecond function, you can get an\nidea of the packets per second sent or received, even though you're only\nrecording the total.",
   127  			Function:    "perSecond(seriesList, maxValue=None)",
   128  			Group:       "Transform",
   129  			Module:      "graphite.render.functions",
   130  			Name:        "perSecond",
   131  			Params: []types.FunctionParam{
   132  				{
   133  					Name:     "seriesList",
   134  					Required: true,
   135  					Type:     types.SeriesList,
   136  				},
   137  				{
   138  					Name: "maxValue",
   139  					Type: types.Float,
   140  				},
   141  				{
   142  					Name: "minValue",
   143  					Type: types.Float,
   144  				},
   145  			},
   146  			NameChange:   true, // name changed
   147  			ValuesChange: true, // values changed
   148  		},
   149  	}
   150  }