github.com/go-graphite/carbonapi@v0.17.0/expr/types/windowed.go (about)

     1  package types
     2  
     3  import (
     4  	"github.com/go-graphite/carbonapi/expr/consolidations"
     5  	"math"
     6  )
     7  
     8  // Based on github.com/dgryski/go-onlinestats
     9  // Copied here because we don't need the rest of the package, and we only need
    10  // a small part of this type which we need to modify anyway.
    11  
    12  // Note that this uses a slightly unstable but faster implementation of
    13  // standard deviation.  This is also required to be compatible with graphite.
    14  
    15  // Windowed is a struct to compute simple windowed stats
    16  type Windowed struct {
    17  	Data   []float64
    18  	head   int
    19  	length int
    20  	sum    float64
    21  	sumsq  float64
    22  	nans   int
    23  }
    24  
    25  func (w *Windowed) Reset() {
    26  	w.length = 0
    27  	w.head = 0
    28  	w.sum = 0
    29  	w.sumsq = 0
    30  	w.nans = 0
    31  	for i := range w.Data {
    32  		w.Data[i] = 0
    33  	}
    34  }
    35  
    36  // Push pushes data
    37  func (w *Windowed) Push(n float64) {
    38  	if len(w.Data) == 0 {
    39  		return
    40  	}
    41  
    42  	old := w.Data[w.head]
    43  
    44  	w.length++
    45  
    46  	w.Data[w.head] = n
    47  	w.head++
    48  	if w.head >= len(w.Data) {
    49  		w.head = 0
    50  	}
    51  
    52  	if !math.IsNaN(old) {
    53  		w.sum -= old
    54  		w.sumsq -= (old * old)
    55  	} else {
    56  		w.nans--
    57  	}
    58  
    59  	if !math.IsNaN(n) {
    60  		w.sum += n
    61  		w.sumsq += (n * n)
    62  	} else {
    63  		w.nans++
    64  	}
    65  }
    66  
    67  // Len returns current len of data
    68  func (w *Windowed) Len() int {
    69  	if w.length < len(w.Data) {
    70  		return w.length - w.nans
    71  	}
    72  
    73  	return len(w.Data) - w.nans
    74  }
    75  
    76  // Stdev computes standard deviation of data
    77  func (w *Windowed) Stdev() float64 {
    78  	l := w.Len()
    79  
    80  	if l == 0 {
    81  		return 0
    82  	}
    83  
    84  	n := float64(l)
    85  	return math.Sqrt(n*w.sumsq-(w.sum*w.sum)) / n
    86  }
    87  
    88  // SumSQ returns sum of squares
    89  func (w *Windowed) SumSQ() float64 {
    90  	return w.sumsq
    91  }
    92  
    93  // Sum returns sum of data
    94  func (w *Windowed) Sum() float64 {
    95  	return w.sum
    96  }
    97  
    98  func (w *Windowed) Multiply() float64 {
    99  	var rv = 1.0
   100  	for _, f := range w.Data {
   101  		if !math.IsNaN(rv) {
   102  			rv *= f
   103  		}
   104  	}
   105  	return rv
   106  }
   107  
   108  // Mean returns mean value of data
   109  func (w *Windowed) Mean() float64 { return w.sum / float64(w.Len()) }
   110  
   111  // MeanZero returns mean value of data, with NaN values replaced with 0
   112  func (w *Windowed) MeanZero() float64 { return w.sum / float64(len(w.Data)) }
   113  
   114  func (w *Windowed) Median() float64 {
   115  	return consolidations.Percentile(w.Data, 50, true)
   116  }
   117  
   118  // Max returns max(values)
   119  func (w *Windowed) Max() float64 {
   120  	rv := math.NaN()
   121  	for _, f := range w.Data {
   122  		if math.IsNaN(rv) || f > rv {
   123  			rv = f
   124  		}
   125  	}
   126  	return rv
   127  }
   128  
   129  // Min returns min(values)
   130  func (w *Windowed) Min() float64 {
   131  	rv := math.NaN()
   132  	for _, f := range w.Data {
   133  		if math.IsNaN(rv) || f < rv {
   134  			rv = f
   135  		}
   136  	}
   137  	return rv
   138  }
   139  
   140  // Count returns number of non-NaN points
   141  func (w *Windowed) Count() float64 {
   142  	return float64(w.Len())
   143  }
   144  
   145  // Diff subtracts series 2 through n from series 1
   146  func (w *Windowed) Diff() float64 {
   147  	rv := w.Data[w.head]
   148  	for i, f := range w.Data {
   149  		if !math.IsNaN(f) && i != w.head {
   150  			rv -= f
   151  		}
   152  	}
   153  	return rv
   154  }
   155  
   156  func (w *Windowed) Range() float64 {
   157  	vMax := math.Inf(-1)
   158  	vMin := math.Inf(1)
   159  	for _, f := range w.Data {
   160  		if f > vMax {
   161  			vMax = f
   162  		}
   163  		if f < vMin {
   164  			vMin = f
   165  		}
   166  	}
   167  	return vMax - vMin
   168  }
   169  
   170  // Last returns the last data point
   171  func (w *Windowed) Last() float64 {
   172  	if w.head == 0 {
   173  		return w.Data[len(w.Data)-1]
   174  	}
   175  
   176  	return w.Data[w.head-1]
   177  }
   178  
   179  // IsNonNull checks if the window's data contains only NaN values
   180  // This is to prevent returning -Inf when the window's data contains only NaN values
   181  func (w *Windowed) IsNonNull() bool {
   182  	if len(w.Data) == w.nans {
   183  		return false
   184  	}
   185  	return true
   186  }