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 }