github.com/m3db/m3@v1.5.0/src/query/graphite/ts/values.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 ts 22 23 import ( 24 "math" 25 26 "github.com/m3db/m3/src/query/graphite/context" 27 "github.com/m3db/m3/src/query/graphite/stats" 28 "github.com/m3db/m3/src/query/util" 29 xpool "github.com/m3db/m3/src/x/pool" 30 ) 31 32 // Values holds the values for a timeseries. It provides a minimal interface 33 // for storing and retrieving values in the series, with Series providing a 34 // more convenient interface for applications to build on top of. Values 35 // objects are not specific to a given time, allowing them to be 36 // pre-allocated, pooled, and re-used across multiple Series. There are 37 // multiple implementations of Values so that we can optimize storage based on 38 // the density of the series. 39 type Values interface { 40 stats.Values 41 42 // The number of millisseconds represented by each index 43 MillisPerStep() int 44 45 // Slice of data values in a range 46 Slice(begin, end int) Values 47 48 // AllNaN returns true if the values are all NaN 49 AllNaN() bool 50 } 51 52 // MutableValues is the interface for values that can be updated 53 type MutableValues interface { 54 Values 55 56 // Resets the values 57 Reset() 58 59 // Sets the value at the given entry 60 SetValueAt(n int, v float64) 61 } 62 63 // CustomStatistics are for values that do custom statistics calculations 64 type CustomStatistics interface { 65 CalcStatistics() stats.Statistics 66 } 67 68 // NewConstantValues returns a block of timeseries values all of which have the 69 // same value 70 func NewConstantValues(ctx context.Context, value float64, numSteps, millisPerStep int) Values { 71 return constantValues{ 72 numSteps: numSteps, 73 millisPerStep: millisPerStep, 74 value: value, 75 } 76 } 77 78 type constantValues struct { 79 numSteps int 80 millisPerStep int 81 value float64 82 } 83 84 func (values constantValues) AllNaN() bool { return math.IsNaN(values.value) } 85 func (values constantValues) MillisPerStep() int { return values.millisPerStep } 86 func (values constantValues) Len() int { return values.numSteps } 87 func (values constantValues) ValueAt(point int) float64 { return values.value } 88 func (values constantValues) Slice(begin, end int) Values { 89 return &constantValues{ 90 end - begin, 91 values.millisPerStep, 92 values.value, 93 } 94 } 95 96 func (values constantValues) CalcStatistics() stats.Statistics { 97 if math.IsNaN(values.value) { 98 return stats.Statistics{ 99 Count: 0, 100 StdDev: 0, 101 Min: math.NaN(), 102 Max: math.NaN(), 103 Mean: math.NaN(), 104 } 105 } 106 107 return stats.Statistics{ 108 Count: uint(values.numSteps), 109 Min: values.value, 110 Max: values.value, 111 Mean: values.value, 112 StdDev: 0, 113 } 114 } 115 116 // NewZeroValues returns a MutableValues supporting the given number of values 117 // at the requested granularity. The values start off initialized at 0 118 func NewZeroValues(ctx context.Context, millisPerStep, numSteps int) MutableValues { 119 return newValues(ctx, millisPerStep, numSteps, 0) 120 } 121 122 // NewValues returns MutableValues supporting the given number of values at the 123 // requested granularity. The values start off as NaN 124 func NewValues(ctx context.Context, millisPerStep, numSteps int) MutableValues { 125 return newValues(ctx, millisPerStep, numSteps, math.NaN()) 126 } 127 128 var ( 129 pooledValuesLength = []int{} 130 pooledConsolidationsLength = []int{} 131 ) 132 133 var ( 134 timeSeriesValuesPools xpool.BucketizedObjectPool 135 consolidationPools xpool.BucketizedObjectPool 136 ) 137 138 func newValues( 139 ctx context.Context, 140 millisPerStep, 141 numSteps int, 142 initialValue float64, 143 ) MutableValues { 144 var values []float64 145 var pooled bool 146 147 if timeSeriesValuesPools != nil { 148 temp := timeSeriesValuesPools.Get(numSteps) 149 values = temp.([]float64) 150 if cap(values) >= numSteps { 151 values = values[:numSteps] 152 pooled = true 153 } 154 } 155 156 if !pooled { 157 values = make([]float64, numSteps) 158 } 159 160 // Faster way to initialize an array instead of a loop. 161 util.Memset(values, initialValue) 162 vals := &float64Values{ 163 ctx: ctx, 164 millisPerStep: millisPerStep, 165 numSteps: numSteps, 166 allNaN: math.IsNaN(initialValue), 167 values: values, 168 } 169 ctx.RegisterCloser(vals) 170 return vals 171 } 172 173 type float64Values struct { 174 ctx context.Context 175 millisPerStep int 176 numSteps int 177 values []float64 178 allNaN bool 179 } 180 181 func (b *float64Values) Reset() { 182 for i := range b.values { 183 b.values[i] = math.NaN() 184 } 185 b.allNaN = true 186 } 187 188 func (b *float64Values) Close() error { 189 if timeSeriesValuesPools != nil { 190 timeSeriesValuesPools.Put(b.values, cap(b.values)) 191 } 192 b.numSteps = 0 193 b.values = nil 194 return nil 195 } 196 197 func (b *float64Values) AllNaN() bool { return b.allNaN } 198 func (b *float64Values) MillisPerStep() int { return b.millisPerStep } 199 func (b *float64Values) Len() int { return b.numSteps } 200 func (b *float64Values) ValueAt(point int) float64 { return b.values[point] } 201 func (b *float64Values) SetValueAt(point int, v float64) { 202 b.allNaN = b.allNaN && math.IsNaN(v) 203 b.values[point] = v 204 } 205 206 func (b *float64Values) Slice(begin, end int) Values { 207 return &float64Values{ 208 ctx: b.ctx, 209 millisPerStep: b.millisPerStep, 210 values: b.values[begin:end], 211 numSteps: end - begin, 212 // NB(mmihic): Someone might modify the parent and we won't be able to tell 213 allNaN: false, 214 } 215 } 216 217 func initPools(valueBuckets, consolidationBuckets []xpool.Bucket) error { 218 pooledValuesLength = pooledValuesLength[:0] 219 pooledConsolidationsLength = pooledConsolidationsLength[:0] 220 221 for _, b := range valueBuckets { 222 pooledValuesLength = append(pooledValuesLength, b.Capacity) 223 } 224 for _, b := range consolidationBuckets { 225 pooledConsolidationsLength = append(pooledConsolidationsLength, b.Capacity) 226 } 227 228 poolOpts := xpool.NewObjectPoolOptions() 229 valuesOpts := poolOpts.SetInstrumentOptions( 230 poolOpts.InstrumentOptions()) 231 consolidationOpts := poolOpts.SetInstrumentOptions( 232 poolOpts.InstrumentOptions()) 233 timeSeriesValuesPools = xpool.NewBucketizedObjectPool(valueBuckets, valuesOpts) 234 timeSeriesValuesPools.Init(func(capacity int) interface{} { 235 return make([]float64, capacity) 236 }) 237 consolidationPools = xpool.NewBucketizedObjectPool(consolidationBuckets, consolidationOpts) 238 consolidationPools.Init(func(capacity int) interface{} { 239 return newConsolidation(capacity) 240 }) 241 return nil 242 } 243 244 // EnablePooling enables pooling. 245 func EnablePooling(valueBuckets, consolidationBuckets []xpool.Bucket) { 246 initPools(valueBuckets, consolidationBuckets) 247 }