github.com/m3db/m3@v1.5.0/src/query/graphite/common/aggregation.go (about) 1 // Copyright (c) 2019 Uber Technologies, Inc. 2 // 3 // Permission is hereby granted, free of charge, to any person obtaining a copy 4 // of this software and associated documentation files (the "Software"), to deal 5 // in the Software without restriction, including without limitation the rights 6 // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 // copies of the Software, and to permit persons to whom the Software is 8 // furnished to do so, subject to the following conditions: 9 // 10 // The above copyright notice and this permission notice shall be included in 11 // all copies or substantial portions of the Software. 12 // 13 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 // THE SOFTWARE. 20 21 package common 22 23 import ( 24 "math" 25 "sort" 26 27 "github.com/m3db/m3/src/query/graphite/ts" 28 ) 29 30 // Range distills down a set of inputs into the range of the series. 31 func Range(ctx *Context, series ts.SeriesList, renamer SeriesListRenamer) (*ts.Series, error) { 32 numSeries := series.Len() 33 if numSeries == 0 { 34 return nil, ErrEmptySeriesList 35 } 36 normalized, start, end, millisPerStep, err := Normalize(ctx, series) 37 if err != nil { 38 return nil, err 39 } 40 numSteps := ts.NumSteps(start, end, millisPerStep) 41 vals := ts.NewValues(ctx, millisPerStep, numSteps) 42 nan := math.NaN() 43 44 for i := 0; i < numSteps; i++ { 45 minVal, maxVal := nan, nan 46 for j := 0; j < numSeries; j++ { 47 v := normalized.Values[j].ValueAt(i) 48 if math.IsNaN(v) { 49 continue 50 } 51 if math.IsNaN(minVal) || minVal > v { 52 minVal = v 53 } 54 if math.IsNaN(maxVal) || maxVal < v { 55 maxVal = v 56 } 57 } 58 if !math.IsNaN(minVal) && !math.IsNaN(maxVal) { 59 vals.SetValueAt(i, maxVal-minVal) 60 } 61 } 62 name := renamer(normalized) 63 return ts.NewSeries(ctx, name, start, vals), nil 64 } 65 66 // SafeAggregationFn is a safe aggregation function. 67 type SafeAggregationFn func(input []float64) (float64, int, bool) 68 69 // SafeAggregationFns is the collection of safe aggregation functions. 70 var SafeAggregationFns = map[string]SafeAggregationFn{ 71 "sum": SafeSum, 72 "avg": SafeAverage, 73 "average": SafeAverage, 74 "max": SafeMax, 75 "min": SafeMin, 76 "median": SafeMedian, 77 "diff": SafeDiff, 78 "stddev": SafeStddev, 79 "range": SafeRange, 80 "multiply": SafeMul, 81 "last": SafeLast, 82 "count": SafeCount, 83 } 84 85 // SafeSort sorts the input slice and returns the number of NaNs in the input. 86 func SafeSort(input []float64) int { 87 nans := 0 88 for i := 0; i < len(input); i++ { 89 if math.IsNaN(input[i]) { 90 nans++ 91 } 92 } 93 sort.Float64s(input) 94 return nans 95 } 96 97 // SafeSum returns the sum of the input slice and the number of NaNs in the input. 98 func SafeSum(input []float64) (float64, int, bool) { 99 nans := 0 100 sum := 0.0 101 for _, v := range input { 102 if !math.IsNaN(v) { 103 sum += v 104 } else { 105 nans++ 106 } 107 } 108 if len(input) == nans { 109 return 0, 0, false // Either no elements or all nans. 110 } 111 return sum, nans, true 112 } 113 114 // SafeAverage returns the average of the input slice and the number of NaNs in the input. 115 func SafeAverage(input []float64) (float64, int, bool) { 116 sum, nans, ok := SafeSum(input) 117 if !ok { 118 return 0, 0, false 119 } 120 if len(input) == nans { 121 return 0, 0, false // Either no elements or all nans. 122 } 123 count := len(input) - nans 124 return sum / float64(count), nans, true 125 } 126 127 // SafeMax returns the maximum value of the input slice and the number of NaNs in the input. 128 func SafeMax(input []float64) (float64, int, bool) { 129 nans := 0 130 max := -math.MaxFloat64 131 for _, v := range input { 132 if math.IsNaN(v) { 133 nans++ 134 continue 135 } 136 if v > max { 137 max = v 138 } 139 } 140 if len(input) == nans { 141 return 0, 0, false // Either no elements or all nans. 142 } 143 return max, nans, true 144 } 145 146 // SafeMin returns the minimum value of the input slice and the number of NaNs in the input. 147 func SafeMin(input []float64) (float64, int, bool) { 148 nans := 0 149 min := math.MaxFloat64 150 for _, v := range input { 151 if math.IsNaN(v) { 152 nans++ 153 continue 154 } 155 if v < min { 156 min = v 157 } 158 } 159 if len(input) == nans { 160 return 0, 0, false // Either no elements or all nans. 161 } 162 return min, nans, true 163 } 164 165 // SafeMedian returns the median value of the input slice and the number of NaNs in the input. 166 func SafeMedian(input []float64) (float64, int, bool) { 167 safeValues, nans, ok := safeValues(input) 168 if !ok { 169 return 0, 0, false 170 } 171 return ts.Median(safeValues, len(safeValues)), nans, true 172 } 173 174 // SafeDiff returns the subtracted value of all the subsequent numbers from the 1st one and 175 // the number of NaNs in the input. 176 func SafeDiff(input []float64) (float64, int, bool) { 177 safeValues, nans, ok := safeValues(input) 178 if !ok { 179 return 0, 0, false 180 } 181 182 diff := safeValues[0] 183 for i := 1; i < len(safeValues); i++ { 184 diff -= safeValues[i] 185 } 186 187 return diff, nans, true 188 } 189 190 // SafeStddev returns the standard deviation value of the input slice and the number of NaNs in the input. 191 func SafeStddev(input []float64) (float64, int, bool) { 192 safeAvg, nans, ok := SafeAverage(input) 193 if !ok { 194 return 0, 0, false 195 } 196 197 safeValues, _, ok := safeValues(input) 198 if !ok { 199 return 0, 0, false 200 } 201 202 sum := 0.0 203 for _, v := range safeValues { 204 sum += (v - safeAvg) * (v - safeAvg) 205 } 206 207 return math.Sqrt(sum / float64(len(safeValues))), nans, true 208 } 209 210 // SafeRange returns the range value of the input slice and the number of NaNs in the input. 211 func SafeRange(input []float64) (float64, int, bool) { 212 safeMax, nans, ok := SafeMax(input) 213 if !ok { 214 return 0, 0, false 215 } 216 217 safeMin, _, ok := SafeMin(input) 218 if !ok { 219 return 0, 0, false 220 } 221 222 return safeMax - safeMin, nans, true 223 } 224 225 // SafeMul returns the product value of the input slice and the number of NaNs in the input. 226 func SafeMul(input []float64) (float64, int, bool) { 227 safeValues, nans, ok := safeValues(input) 228 if !ok { 229 return 0, 0, false 230 } 231 232 product := 1.0 233 for _, v := range safeValues { 234 product *= v 235 } 236 237 return product, nans, true 238 } 239 240 // SafeLast returns the last value of the input slice and the number of NaNs in the input. 241 func SafeLast(input []float64) (float64, int, bool) { 242 safeValues, nans, ok := safeValues(input) 243 if !ok { 244 return 0, 0, false 245 } 246 247 return safeValues[len(safeValues)-1], nans, true 248 } 249 250 // SafeCount returns the number of valid values in the input slice and the number of NaNs in the input. 251 func SafeCount(input []float64) (float64, int, bool) { 252 safeValues, nans, ok := safeValues(input) 253 if !ok { 254 return 0, 0, false 255 } 256 257 return float64(len(safeValues)), nans, true 258 } 259 260 func safeValues(input []float64) ([]float64, int, bool) { 261 nans := 0 262 safeValues := make([]float64, 0, len(input)) 263 for _, v := range input { 264 if !math.IsNaN(v) { 265 safeValues = append(safeValues, v) 266 } else { 267 nans++ 268 } 269 } 270 if len(input) == nans { 271 return nil, 0, false // Either no elements or all nans. 272 } 273 return safeValues, nans, true 274 }