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  }