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  }