github.com/go-graphite/carbonapi@v0.17.0/expr/functions/movingMedian/function.go (about) 1 package movingMedian 2 3 import ( 4 "context" 5 "math" 6 "strconv" 7 8 "github.com/JaderDias/movingmedian" 9 "github.com/lomik/zapwriter" 10 "github.com/spf13/viper" 11 "go.uber.org/zap" 12 13 "github.com/go-graphite/carbonapi/expr/helper" 14 "github.com/go-graphite/carbonapi/expr/interfaces" 15 "github.com/go-graphite/carbonapi/expr/types" 16 "github.com/go-graphite/carbonapi/pkg/parser" 17 ) 18 19 type movingMedian struct { 20 config movingMedianConfig 21 } 22 23 func GetOrder() interfaces.Order { 24 return interfaces.Any 25 } 26 27 type movingMedianConfig struct { 28 ReturnNaNsIfStepMismatch *bool 29 } 30 31 func New(configFile string) []interfaces.FunctionMetadata { 32 logger := zapwriter.Logger("functionInit").With(zap.String("function", "movingMedian")) 33 res := make([]interfaces.FunctionMetadata, 0) 34 f := &movingMedian{} 35 functions := []string{"movingMedian"} 36 for _, n := range functions { 37 res = append(res, interfaces.FunctionMetadata{Name: n, F: f}) 38 } 39 40 cfg := movingMedianConfig{} 41 v := viper.New() 42 v.SetConfigFile(configFile) 43 err := v.ReadInConfig() 44 if err != nil { 45 logger.Info("failed to read config file, using default", 46 zap.Error(err), 47 ) 48 } else { 49 err = v.Unmarshal(&cfg) 50 if err != nil { 51 logger.Fatal("failed to parse config", 52 zap.Error(err), 53 ) 54 return nil 55 } 56 f.config = cfg 57 } 58 59 if cfg.ReturnNaNsIfStepMismatch == nil { 60 v := true 61 f.config.ReturnNaNsIfStepMismatch = &v 62 } 63 return res 64 } 65 66 // movingMedian(seriesList, windowSize) 67 func (f *movingMedian) Do(ctx context.Context, eval interfaces.Evaluator, e parser.Expr, from, until int64, values map[parser.MetricRequest][]*types.MetricData) ([]*types.MetricData, error) { 68 if e.ArgsLen() < 2 { 69 return nil, parser.ErrMissingArgument 70 } 71 72 var n int 73 var err error 74 75 var scaleByStep bool 76 77 var argstr string 78 79 switch e.Arg(1).Type() { 80 case parser.EtConst: 81 n, err = e.GetIntArg(1) 82 argstr = strconv.Itoa(n) 83 case parser.EtString: 84 var n32 int32 85 n32, err = e.GetIntervalArg(1, 1) 86 n = int(n32) 87 argstr = "'" + e.Arg(1).StringValue() + "'" 88 scaleByStep = true 89 default: 90 err = parser.ErrBadType 91 } 92 if err != nil { 93 return nil, err 94 } 95 96 windowSize := n 97 98 start := from 99 if scaleByStep { 100 start -= int64(n) 101 } 102 103 arg, err := helper.GetSeriesArg(ctx, eval, e.Arg(0), start, until, values) 104 if err != nil { 105 return nil, err 106 } 107 108 if len(arg) == 0 { 109 return nil, nil 110 } 111 112 var offset int 113 114 if scaleByStep { 115 windowSize /= int(arg[0].StepTime) 116 offset = windowSize 117 } 118 119 result := make([]*types.MetricData, len(arg)) 120 for n, a := range arg { 121 r := a.CopyLink() 122 r.Name = "movingMedian(" + a.Name + "," + argstr + ")" 123 124 if windowSize == 0 { 125 if *f.config.ReturnNaNsIfStepMismatch { 126 r.Values = make([]float64, len(a.Values)) 127 for i := range a.Values { 128 r.Values[i] = math.NaN() 129 } 130 } 131 } else { 132 r.Values = make([]float64, len(a.Values)-offset) 133 r.StartTime = (from + r.StepTime - 1) / r.StepTime * r.StepTime // align StartTime to closest >= StepTime 134 r.StopTime = r.StartTime + int64(len(r.Values))*r.StepTime 135 136 data := movingmedian.NewMovingMedian(windowSize) 137 138 for i, v := range a.Values { 139 data.Push(v) 140 141 if ridx := i - offset; ridx >= 0 { 142 r.Values[ridx] = math.NaN() 143 if i >= (windowSize - 1) { 144 r.Values[ridx] = data.Median() 145 } 146 } 147 } 148 } 149 r.Tags["movingMedian"] = argstr 150 result[n] = r 151 } 152 return result, nil 153 } 154 155 // Description is auto-generated description, based on output of https://github.com/graphite-project/graphite-web 156 func (f *movingMedian) Description() map[string]types.FunctionDescription { 157 return map[string]types.FunctionDescription{ 158 "movingMedian": { 159 Description: "Graphs the moving median of a metric (or metrics) over a fixed number of\npast points, or a time interval.\n\nTakes one metric or a wildcard seriesList followed by a number N of datapoints\nor a quoted string with a length of time like '1hour' or '5min' (See ``from /\nuntil`` in the render\\_api_ for examples of time formats), and an xFilesFactor value to specify\nhow many points in the window must be non-null for the output to be considered valid. Graphs the\nmedian of the preceeding datapoints for each point on the graph.\n\nExample:\n\n.. code-block:: none\n\n &target=movingMedian(Server.instance01.threads.busy,10)\n &target=movingMedian(Server.instance*.threads.idle,'5min')", 160 Function: "movingMedian(seriesList, windowSize, xFilesFactor=None)", 161 Group: "Calculate", 162 Module: "graphite.render.functions", 163 Name: "movingMedian", 164 Params: []types.FunctionParam{ 165 { 166 Name: "seriesList", 167 Required: true, 168 Type: types.SeriesList, 169 }, 170 { 171 Name: "windowSize", 172 Required: true, 173 Suggestions: types.NewSuggestions( 174 5, 175 7, 176 10, 177 "1min", 178 "5min", 179 "10min", 180 "30min", 181 "1hour", 182 ), 183 Type: types.IntOrInterval, 184 }, 185 { 186 Name: "xFilesFactor", 187 Type: types.Float, 188 }, 189 }, 190 NameChange: true, // name changed 191 ValuesChange: true, // values changed 192 }, 193 } 194 }