github.com/go-graphite/carbonapi@v0.17.0/expr/consolidations/consolidations.go (about) 1 package consolidations 2 3 import ( 4 "math" 5 "regexp" 6 "strconv" 7 "strings" 8 9 "github.com/ansel1/merry" 10 11 "github.com/wangjohn/quickselect" 12 "gonum.org/v1/gonum/mat" 13 ) 14 15 var ErrInvalidConsolidationFunc = merry.New("Invalid Consolidation Function") 16 17 // ConsolidationToFunc contains a map of graphite-compatible consolidation functions definitions to actual functions that can do aggregation 18 // TODO(civil): take into account xFilesFactor 19 var ConsolidationToFunc = map[string]func([]float64) float64{ 20 "average": AggMean, 21 "avg_zero": AggMeanZero, 22 "avg": AggMean, 23 "count": AggCount, 24 "diff": AggDiff, 25 "max": AggMax, 26 "maximum": AggMax, 27 "median": summarizeToAggregate("median"), 28 "min": AggMin, 29 "minimum": AggMin, 30 "multiply": summarizeToAggregate("multiply"), 31 "range": summarizeToAggregate("range"), 32 "rangeOf": summarizeToAggregate("rangeOf"), 33 "sum": AggSum, 34 "total": AggSum, 35 "stddev": summarizeToAggregate("stddev"), 36 "first": AggFirst, 37 "last": AggLast, 38 "current": AggLast, 39 } 40 41 var AvailableSummarizers = []string{"sum", "total", "avg", "average", "avg_zero", "max", "min", "last", "current", "first", "range", "rangeOf", "median", "multiply", "diff", "count", "stddev"} 42 43 func CheckValidConsolidationFunc(functionName string) error { 44 if _, ok := ConsolidationToFunc[functionName]; ok { 45 return nil 46 } else { 47 // Check if this is a p50 - p99.9 consolidation 48 if match, _ := regexp.MatchString("p([0-9]*[.])?[0-9]+", functionName); match { 49 return nil 50 } 51 } 52 return ErrInvalidConsolidationFunc.WithMessage("invalid consolidation " + functionName) 53 } 54 55 // AvgValue returns average of list of values 56 func AvgValue(f64s []float64) float64 { 57 var t float64 58 var elts int 59 for _, v := range f64s { 60 if math.IsNaN(v) { 61 continue 62 } 63 elts++ 64 t += v 65 } 66 return t / float64(elts) 67 } 68 69 // VarianceValue gets variances of list of values 70 func VarianceValue(f64s []float64) float64 { 71 var squareSum float64 72 var elts int 73 74 mean := AvgValue(f64s) 75 if math.IsNaN(mean) { 76 return mean 77 } 78 79 for _, v := range f64s { 80 if math.IsNaN(v) { 81 continue 82 } 83 elts++ 84 squareSum += (mean - v) * (mean - v) 85 } 86 return squareSum / float64(elts) 87 } 88 89 // Percentile returns percent-th percentile. Can interpolate if needed 90 func Percentile(data []float64, percent float64, interpolate bool) float64 { 91 dataFiltered := make([]float64, 0) 92 for _, v := range data { 93 if !math.IsNaN(v) { 94 dataFiltered = append(dataFiltered, v) 95 } 96 } 97 98 if len(dataFiltered) == 0 || percent < 0 || percent > 100 { 99 return math.NaN() 100 } 101 if len(dataFiltered) == 1 { 102 return dataFiltered[0] 103 } 104 105 k := (float64(len(dataFiltered)-1) * percent) / 100 106 length := int(math.Ceil(k)) + 1 107 108 _ = quickselect.Float64QuickSelect(dataFiltered, length) 109 top, secondTop := math.Inf(-1), math.Inf(-1) 110 for _, val := range dataFiltered[0:length] { 111 if val > top { 112 secondTop = top 113 top = val 114 } else if val > secondTop { 115 secondTop = val 116 } 117 } 118 remainder := k - float64(int(k)) 119 if remainder == 0 || !interpolate { 120 return top 121 } 122 return (top * remainder) + (secondTop * (1 - remainder)) 123 } 124 125 func summarizeToAggregate(f string) func([]float64) float64 { 126 return func(v []float64) float64 { 127 return SummarizeValues(f, v, 0) 128 } 129 } 130 131 // SummarizeValues summarizes values 132 func SummarizeValues(f string, values []float64, XFilesFactor float32) float64 { 133 rv := 0.0 134 total := 0 135 136 notNans := func(values []float64) int { 137 t := 0 138 for _, av := range values { 139 if !math.IsNaN(av) { 140 t++ 141 } 142 } 143 return t 144 } 145 146 if len(values) == 0 { 147 return math.NaN() 148 } 149 150 switch f { 151 case "sum", "total": 152 for _, av := range values { 153 if !math.IsNaN(av) { 154 rv += av 155 total++ 156 } 157 } 158 159 case "avg", "average", "avg_zero": 160 for _, av := range values { 161 if !math.IsNaN(av) { 162 rv += av 163 total++ 164 } 165 } 166 if total == 0 { 167 return math.NaN() 168 } 169 rv /= float64(total) 170 case "max": 171 rv = math.Inf(-1) 172 for _, av := range values { 173 if !math.IsNaN(av) { 174 total++ 175 if av > rv { 176 rv = av 177 } 178 } 179 } 180 case "min": 181 rv = math.Inf(1) 182 for _, av := range values { 183 if !math.IsNaN(av) { 184 total++ 185 if av < rv { 186 rv = av 187 } 188 } 189 } 190 case "last", "current": 191 rv = values[len(values)-1] 192 total = notNans(values) 193 case "range", "rangeOf": 194 vMax := math.Inf(-1) 195 vMin := math.Inf(1) 196 isNaN := true 197 for _, av := range values { 198 if !math.IsNaN(av) { 199 total++ 200 isNaN = false 201 } 202 if av > vMax { 203 vMax = av 204 } 205 if av < vMin { 206 vMin = av 207 } 208 } 209 if isNaN { 210 rv = math.NaN() 211 } else { 212 rv = vMax - vMin 213 } 214 case "median": 215 rv = Percentile(values, 50, true) 216 total = notNans(values) 217 case "multiply": 218 rv = 1.0 219 for _, v := range values { 220 if math.IsNaN(v) { 221 rv = math.NaN() 222 break 223 } else { 224 total++ 225 rv *= v 226 } 227 } 228 case "diff": 229 rv = values[0] 230 for _, av := range values[1:] { 231 if !math.IsNaN(av) { 232 total++ 233 rv -= av 234 } 235 } 236 case "count": 237 total = notNans(values) 238 rv = float64(total) 239 case "stddev": 240 rv = math.Sqrt(VarianceValue(values)) 241 total = notNans(values) 242 case "first": 243 if len(values) > 0 { 244 rv = values[0] 245 } else { 246 rv = math.NaN() 247 } 248 total = notNans(values) 249 default: 250 // This processes function percentile functions such as p50 or p99.9. 251 // If a function name is passed in that does not match that format, 252 // it should be ignored 253 fn := strings.Split(f, "p") 254 if len(fn) > 1 { 255 f = fn[1] 256 percent, err := strconv.ParseFloat(f, 64) 257 if err == nil { 258 total = notNans(values) 259 rv = Percentile(values, percent, true) 260 } 261 } 262 } 263 264 if total == 0 { 265 return math.NaN() 266 } 267 268 if float32(total)/float32(len(values)) < XFilesFactor { 269 return math.NaN() 270 } 271 272 return rv 273 } 274 275 var consolidateFuncs []string 276 277 // AvailableConsolidationFuncs lists all available consolidation functions 278 func AvailableConsolidationFuncs() []string { 279 if len(consolidateFuncs) == 0 { 280 for name := range ConsolidationToFunc { 281 consolidateFuncs = append(consolidateFuncs, name) 282 } 283 } 284 return consolidateFuncs 285 } 286 287 // AggMean computes mean (sum(v)/len(v), excluding NaN points) of values 288 func AggMean(v []float64) float64 { 289 var sum float64 290 var n int 291 for _, vv := range v { 292 if !math.IsNaN(vv) { 293 sum += vv 294 n++ 295 } 296 } 297 if n == 0 { 298 return math.NaN() 299 } 300 return sum / float64(n) 301 } 302 303 // AggMeanZero computes mean (sum(v)/len(v), replacing NaN points with 0 304 func AggMeanZero(v []float64) float64 { 305 var sum float64 306 n := len(v) 307 n2 := 0 308 for _, vv := range v { 309 if !math.IsNaN(vv) { 310 sum += vv 311 n2++ 312 } 313 } 314 315 if n2 == 0 { 316 return math.NaN() 317 } 318 319 return sum / float64(n) 320 } 321 322 // AggMax computes max of values 323 func AggMax(v []float64) float64 { 324 var m = math.Inf(-1) 325 var abs = true 326 for _, vv := range v { 327 if !math.IsNaN(vv) { 328 abs = false 329 if m < vv { 330 m = vv 331 } 332 } 333 } 334 if abs { 335 return math.NaN() 336 } 337 return m 338 } 339 340 // AggMin computes min of values 341 func AggMin(v []float64) float64 { 342 var m = math.Inf(1) 343 var abs = true 344 for _, vv := range v { 345 if !math.IsNaN(vv) { 346 abs = false 347 if m > vv { 348 m = vv 349 } 350 } 351 } 352 if abs { 353 return math.NaN() 354 } 355 return m 356 } 357 358 // AggSum computes sum of values 359 func AggSum(v []float64) float64 { 360 var sum float64 361 var abs = true 362 for _, vv := range v { 363 if !math.IsNaN(vv) { 364 sum += vv 365 abs = false 366 } 367 } 368 if abs { 369 return math.NaN() 370 } 371 return sum 372 } 373 374 // AggFirst returns first point 375 func AggFirst(v []float64) float64 { 376 var m = math.Inf(-1) 377 var abs = true 378 if len(v) > 0 { 379 return v[0] 380 } 381 if abs { 382 return math.NaN() 383 } 384 return m 385 } 386 387 // AggLast returns last point 388 func AggLast(v []float64) float64 { 389 if len(v) > 0 { 390 i := len(v) 391 for i != 0 { 392 i-- 393 if !math.IsNaN(v[i]) { 394 return v[i] 395 } 396 } 397 } 398 return math.NaN() 399 } 400 401 // AggCount counts non-NaN points 402 func AggCount(v []float64) float64 { 403 n := 0 404 405 for _, vv := range v { 406 if !math.IsNaN(vv) { 407 n++ 408 } 409 } 410 411 if n == 0 { 412 return math.NaN() 413 } 414 415 return float64(n) 416 } 417 418 func AggDiff(v []float64) float64 { 419 safeValues := make([]float64, 0, len(v)) 420 for _, vv := range v { 421 if !math.IsNaN(vv) { 422 safeValues = append(safeValues, vv) 423 } 424 } 425 426 if len(safeValues) > 0 { 427 res := safeValues[0] 428 if len(safeValues) == 1 { 429 return res 430 } 431 432 for _, vv := range safeValues[1:] { 433 res -= vv 434 } 435 436 return res 437 } else { 438 return math.NaN() 439 } 440 } 441 442 // MaxValue returns maximum from the list 443 func MaxValue(f64s []float64) float64 { 444 m := math.Inf(-1) 445 for _, v := range f64s { 446 if math.IsNaN(v) { 447 continue 448 } 449 if v > m { 450 m = v 451 } 452 } 453 return m 454 } 455 456 // MinValue returns minimal from the list 457 func MinValue(f64s []float64) float64 { 458 m := math.Inf(1) 459 for _, v := range f64s { 460 if math.IsNaN(v) { 461 continue 462 } 463 if v < m { 464 m = v 465 } 466 } 467 return m 468 } 469 470 // CurrentValue returns last non-absent value (if any), otherwise returns NaN 471 func CurrentValue(f64s []float64) float64 { 472 for i := len(f64s) - 1; i >= 0; i-- { 473 if !math.IsNaN(f64s[i]) { 474 return f64s[i] 475 } 476 } 477 478 return math.NaN() 479 } 480 481 // Vandermonde creates a Vandermonde matrix 482 func Vandermonde(absent []float64, deg int) *mat.Dense { 483 e := []float64{} 484 for i := range absent { 485 if math.IsNaN(absent[i]) { 486 continue 487 } 488 v := 1 489 for j := 0; j < deg+1; j++ { 490 e = append(e, float64(v)) 491 v *= i 492 } 493 } 494 return mat.NewDense(len(e)/(deg+1), deg+1, e) 495 } 496 497 // Poly computes polynom with specified coefficients 498 func Poly(x float64, coeffs ...float64) float64 { 499 y := coeffs[0] 500 v := 1.0 501 for _, c := range coeffs[1:] { 502 v *= x 503 y += c * v 504 } 505 return y 506 }