github.com/m3db/m3@v1.5.1-0.20231129193456-75a402aa583b/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  }