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 }