github.com/go-graphite/carbonapi@v0.17.0/expr/functions/moving/function.go (about) 1 package moving 2 3 import ( 4 "context" 5 "math" 6 "strconv" 7 8 "github.com/lomik/zapwriter" 9 "github.com/spf13/viper" 10 "go.uber.org/zap" 11 12 "github.com/go-graphite/carbonapi/expr/helper" 13 "github.com/go-graphite/carbonapi/expr/interfaces" 14 "github.com/go-graphite/carbonapi/expr/types" 15 "github.com/go-graphite/carbonapi/pkg/parser" 16 ) 17 18 type moving struct { 19 config movingConfig 20 } 21 22 func GetOrder() interfaces.Order { 23 return interfaces.Any 24 } 25 26 type movingConfig struct { 27 ReturnNaNsIfStepMismatch *bool 28 } 29 30 func New(configFile string) []interfaces.FunctionMetadata { 31 logger := zapwriter.Logger("functionInit").With(zap.String("function", "moving")) 32 res := make([]interfaces.FunctionMetadata, 0) 33 f := &moving{} 34 functions := []string{"movingAverage", "movingMin", "movingMax", "movingSum", "movingWindow"} 35 for _, n := range functions { 36 res = append(res, interfaces.FunctionMetadata{Name: n, F: f}) 37 } 38 39 cfg := movingConfig{} 40 v := viper.New() 41 v.SetConfigFile(configFile) 42 err := v.ReadInConfig() 43 if err != nil { 44 logger.Info("failed to read config file, using default", 45 zap.Error(err), 46 ) 47 } else { 48 err = v.Unmarshal(&cfg) 49 if err != nil { 50 logger.Fatal("failed to parse config", 51 zap.Error(err), 52 ) 53 return nil 54 } 55 f.config = cfg 56 } 57 58 if cfg.ReturnNaNsIfStepMismatch == nil { 59 v := true 60 f.config.ReturnNaNsIfStepMismatch = &v 61 } 62 return res 63 } 64 65 // movingXyz(seriesList, windowSize) 66 func (f *moving) Do(ctx context.Context, eval interfaces.Evaluator, e parser.Expr, from, until int64, values map[parser.MetricRequest][]*types.MetricData) ([]*types.MetricData, error) { 67 var n int 68 var err error 69 70 var argstr string 71 var cons string 72 73 var xFilesFactor float64 74 75 if e.ArgsLen() < 2 { 76 return nil, parser.ErrMissingArgument 77 } 78 79 adjustedStart := from 80 var refetch bool 81 var windowPoints int 82 var preview int64 83 84 switch e.Arg(1).Type() { 85 case parser.EtConst: 86 n, err = e.GetIntArg(1) 87 argstr = strconv.Itoa(n) 88 89 arg, err := helper.GetSeriesArg(ctx, eval, e.Arg(0), from, until, values) 90 if err != nil { 91 return nil, err 92 } 93 if len(arg) == 0 { 94 return arg, nil 95 } 96 97 // Find the maximum step to use for determining the altered start time 98 var maxStep int64 99 for _, a := range arg { 100 if a.StepTime > maxStep { 101 maxStep = a.StepTime 102 } 103 } 104 preview = maxStep * int64(n) 105 adjustedStart -= maxStep * int64(n) 106 windowPoints = n 107 if adjustedStart != from { 108 refetch = true 109 } 110 case parser.EtString: 111 var n32 int32 112 n32, err = e.GetIntervalArg(1, 1) 113 argstr = "'" + e.Arg(1).StringValue() + "'" 114 preview = int64(math.Abs(float64(n32))) // Absolute is used in order to handle negative string intervals 115 adjustedStart -= preview 116 default: 117 err = parser.ErrBadType 118 } 119 if err != nil { 120 return nil, err 121 } 122 123 var targetValues map[parser.MetricRequest][]*types.MetricData 124 if refetch { 125 targetValues, err = eval.Fetch(ctx, []parser.Expr{e.Arg(0)}, adjustedStart, until, values) 126 if err != nil { 127 return nil, err 128 } 129 } else { 130 targetValues = values 131 } 132 133 adjustedArgs, err := helper.GetSeriesArg(ctx, eval, e.Arg(0), adjustedStart, until, targetValues) 134 if err != nil { 135 return nil, err 136 } 137 138 if len(adjustedArgs) == 0 { 139 return adjustedArgs, nil 140 } 141 142 if e.ArgsLen() >= 2 && e.Target() == "movingWindow" { 143 cons, err = e.GetStringArgDefault(2, "average") 144 if err != nil { 145 return nil, err 146 } 147 148 if e.ArgsLen() == 4 { 149 xFilesFactor, err = e.GetFloatArgDefault(3, float64(adjustedArgs[0].XFilesFactor)) 150 151 if err != nil { 152 return nil, err 153 } 154 } 155 } else if e.ArgsLen() == 3 { 156 xFilesFactor, err = e.GetFloatArgDefault(2, float64(adjustedArgs[0].XFilesFactor)) 157 158 if err != nil { 159 return nil, err 160 } 161 } 162 163 switch e.Target() { 164 case "movingAverage": 165 cons = "average" 166 case "movingSum": 167 cons = "sum" 168 case "movingMin": 169 cons = "min" 170 case "movingMax": 171 cons = "max" 172 case "movingMedian": 173 cons = "median" 174 } 175 176 result := make([]*types.MetricData, len(adjustedArgs)) 177 178 for j, a := range adjustedArgs { 179 r := a.CopyLink() 180 r.Name = e.Target() + "(" + a.Name + "," + argstr + ")" 181 r.Tags[e.Target()] = argstr 182 183 if e.Arg(1).Type() == parser.EtString { 184 windowPoints = int(preview / a.StepTime) 185 } 186 187 if windowPoints == 0 { 188 if *f.config.ReturnNaNsIfStepMismatch { 189 r.Values = make([]float64, len(a.Values)) 190 for i := range a.Values { 191 r.Values[i] = math.NaN() 192 } 193 } 194 r.StartTime += preview 195 r.StopTime += preview 196 result[j] = r 197 continue 198 } 199 200 size := len(a.Values) - windowPoints 201 if size < 0 { 202 size = 0 203 } 204 r.Values = make([]float64, size) 205 r.StartTime = a.StartTime + preview 206 r.StopTime = r.StartTime + int64(len(r.Values))*r.StepTime 207 208 w := &types.Windowed{Data: make([]float64, windowPoints)} 209 for i := 1; i < len(a.Values); i++ { // ignoring the first value in the series to avoid shifting of results one step in the future 210 w.Push(a.Values[i]) 211 212 if ridx := i - windowPoints; ridx >= 0 { 213 if w.IsNonNull() && helper.XFilesFactorValues(w.Data, xFilesFactor) { 214 switch cons { 215 case "average": 216 r.Values[ridx] = w.Mean() 217 case "avg": 218 r.Values[ridx] = w.Mean() 219 case "avg_zero": 220 r.Values[ridx] = w.MeanZero() 221 case "sum": 222 r.Values[ridx] = w.Sum() 223 case "min": 224 r.Values[ridx] = w.Min() 225 case "max": 226 r.Values[ridx] = w.Max() 227 case "multiply": 228 r.Values[ridx] = w.Multiply() 229 case "range": 230 r.Values[ridx] = w.Range() 231 case "diff": 232 r.Values[ridx] = w.Diff() 233 case "stddev": 234 r.Values[ridx] = w.Stdev() 235 case "count": 236 r.Values[ridx] = w.Count() 237 case "last": 238 r.Values[ridx] = w.Last() 239 case "median": 240 r.Values[ridx] = w.Median() 241 } 242 if i < windowPoints || math.IsNaN(r.Values[ridx]) { 243 r.Values[ridx] = math.NaN() 244 } 245 } else { 246 r.Values[ridx] = math.NaN() 247 } 248 } 249 } 250 result[j] = r 251 } 252 return result, nil 253 } 254 255 // Description is auto-generated description, based on output of https://github.com/graphite-project/graphite-web 256 func (f *moving) Description() map[string]types.FunctionDescription { 257 return map[string]types.FunctionDescription{ 258 "movingWindow": { 259 Description: "Graphs a moving window function of a metric (or metrics) over a fixed number of past 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\nsum of the preceeding datapoints for each point on the graph.\n\nExample:\n\n.. code-block:: none\n\n &target=movingWindow(Server.instance01.threads.busy,10)\n &target=movingWindow(Server.instance*.threads.idle,'5min','median',0.5)", 260 Function: "movingWindow(seriesList, windowSize, func='average', xFilesFactor=None)", 261 Group: "Calculate", 262 Module: "graphite.render.functions", 263 Name: "movingWindow", 264 Params: []types.FunctionParam{ 265 { 266 Name: "seriesList", 267 Required: true, 268 Type: types.SeriesList, 269 }, 270 { 271 Name: "windowSize", 272 Required: true, 273 Suggestions: types.NewSuggestions( 274 5, 275 7, 276 10, 277 "1min", 278 "5min", 279 "10min", 280 "30min", 281 "1hour", 282 ), 283 Type: types.IntOrInterval, 284 }, 285 { 286 Name: "func", 287 Type: types.AggFunc, 288 }, 289 { 290 Name: "xFilesFactor", 291 Type: types.Float, 292 }, 293 }, 294 }, 295 "movingAverage": { 296 Description: "Graphs the moving average 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\naverage of the preceeding datapoints for each point on the graph.\n\nExample:\n\n.. code-block:: none\n\n &target=movingAverage(Server.instance01.threads.busy,10)\n &target=movingAverage(Server.instance*.threads.idle,'5min')", 297 Function: "movingAverage(seriesList, windowSize, xFilesFactor=None)", 298 Group: "Calculate", 299 Module: "graphite.render.functions", 300 Name: "movingAverage", 301 Params: []types.FunctionParam{ 302 { 303 Name: "seriesList", 304 Required: true, 305 Type: types.SeriesList, 306 }, 307 { 308 Name: "windowSize", 309 Required: true, 310 Suggestions: types.NewSuggestions( 311 5, 312 7, 313 10, 314 "1min", 315 "5min", 316 "10min", 317 "30min", 318 "1hour", 319 ), 320 Type: types.IntOrInterval, 321 }, 322 { 323 Name: "xFilesFactor", 324 Type: types.Float, 325 }, 326 }, 327 NameChange: true, // name changed 328 ValuesChange: true, // values changed 329 }, 330 "movingMin": { 331 Description: "Graphs the moving minimum 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\nminimum of the preceeding datapoints for each point on the graph.\n\nExample:\n\n.. code-block:: none\n\n &target=movingMin(Server.instance01.requests,10)\n &target=movingMin(Server.instance*.errors,'5min')", 332 Function: "movingMin(seriesList, windowSize, xFilesFactor=None)", 333 Group: "Calculate", 334 Module: "graphite.render.functions", 335 Name: "movingMin", 336 Params: []types.FunctionParam{ 337 { 338 Name: "seriesList", 339 Required: true, 340 Type: types.SeriesList, 341 }, 342 { 343 Name: "windowSize", 344 Required: true, 345 Suggestions: types.NewSuggestions( 346 5, 347 7, 348 10, 349 "1min", 350 "5min", 351 "10min", 352 "30min", 353 "1hour", 354 ), 355 Type: types.IntOrInterval, 356 }, 357 { 358 Name: "xFilesFactor", 359 Type: types.Float, 360 }, 361 }, 362 NameChange: true, // name changed 363 ValuesChange: true, // values changed 364 }, 365 "movingMax": { 366 Description: "Graphs the moving maximum 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\nmaximum of the preceeding datapoints for each point on the graph.\n\nExample:\n\n.. code-block:: none\n\n &target=movingMax(Server.instance01.requests,10)\n &target=movingMax(Server.instance*.errors,'5min')", 367 Function: "movingMax(seriesList, windowSize, xFilesFactor=None)", 368 Group: "Calculate", 369 Module: "graphite.render.functions", 370 Name: "movingMax", 371 Params: []types.FunctionParam{ 372 { 373 Name: "seriesList", 374 Required: true, 375 Type: types.SeriesList, 376 }, 377 { 378 Name: "windowSize", 379 Required: true, 380 Suggestions: types.NewSuggestions( 381 5, 382 7, 383 10, 384 "1min", 385 "5min", 386 "10min", 387 "30min", 388 "1hour", 389 ), 390 Type: types.IntOrInterval, 391 }, 392 { 393 Name: "xFilesFactor", 394 Type: types.Float, 395 }, 396 }, 397 NameChange: true, // name changed 398 ValuesChange: true, // values changed 399 }, 400 "movingSum": { 401 Description: "Graphs the moving sum 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\nsum of the preceeding datapoints for each point on the graph.\n\nExample:\n\n.. code-block:: none\n\n &target=movingSum(Server.instance01.requests,10)\n &target=movingSum(Server.instance*.errors,'5min')", 402 Function: "movingSum(seriesList, windowSize, xFilesFactor=None)", 403 Group: "Calculate", 404 Module: "graphite.render.functions", 405 Name: "movingSum", 406 Params: []types.FunctionParam{ 407 { 408 Name: "seriesList", 409 Required: true, 410 Type: types.SeriesList, 411 }, 412 { 413 Name: "windowSize", 414 Required: true, 415 Suggestions: types.NewSuggestions( 416 5, 417 7, 418 10, 419 "1min", 420 "5min", 421 "10min", 422 "30min", 423 "1hour", 424 ), 425 Type: types.IntOrInterval, 426 }, 427 { 428 Name: "xFilesFactor", 429 Type: types.Float, 430 }, 431 }, 432 NameChange: true, // name changed 433 ValuesChange: true, // values changed 434 }, 435 } 436 }