github.com/m3db/m3@v1.5.1-0.20231129193456-75a402aa583b/src/query/graphite/ts/series.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  	"errors"
    25  	"fmt"
    26  	"math"
    27  	"regexp"
    28  	"sort"
    29  	"strconv"
    30  	"strings"
    31  	"time"
    32  
    33  	"github.com/m3db/m3/src/query/block"
    34  	"github.com/m3db/m3/src/query/graphite/context"
    35  	"github.com/m3db/m3/src/query/graphite/stats"
    36  )
    37  
    38  var (
    39  	// ErrRangeIsInvalid is returned when attempting to slice Series with invalid range
    40  	// endpoints (begin is beyond end).
    41  	ErrRangeIsInvalid = errors.New("requested range is invalid")
    42  
    43  	digitsRegex = regexp.MustCompile(`\d+`)
    44  )
    45  
    46  const (
    47  	digits = "0123456789"
    48  )
    49  
    50  // An AggregationFunc combines two data values at a given point.
    51  type AggregationFunc func(a, b float64) float64
    52  
    53  // A Series is the public interface to a block of timeseries values.  Each block has a start time,
    54  // a logical number of steps, and a step size indicating the number of milliseconds represented by each point.
    55  type Series struct {
    56  	name      string
    57  	startTime time.Time
    58  	vals      Values
    59  	ctx       context.Context
    60  
    61  	// The Specification is the path that was used to generate this timeseries,
    62  	// typically either the query, or the function stack used to transform
    63  	// specific results.
    64  	Specification string
    65  
    66  	// consolidationFunc specifies how the series will be consolidated when the
    67  	// number of data points in the series is more than the maximum number allowed.
    68  	consolidationFunc ConsolidationFunc
    69  }
    70  
    71  // SeriesByName implements sort.Interface for sorting collections
    72  // of series by name.
    73  type SeriesByName []*Series
    74  
    75  // Len returns the length of the series collection
    76  func (a SeriesByName) Len() int {
    77  	return len(a)
    78  }
    79  
    80  // Swap swaps two series in the collection
    81  func (a SeriesByName) Swap(i, j int) {
    82  	a[i], a[j] = a[j], a[i]
    83  }
    84  
    85  // Less determines if a series is ordered before another series by name
    86  func (a SeriesByName) Less(i, j int) bool {
    87  	return a[i].name < a[j].name
    88  }
    89  
    90  // SeriesByNameAndNaturalNumbers implements sort.Interface for sorting
    91  // collections of series by name respecting natural sort order for numbers.
    92  type SeriesByNameAndNaturalNumbers []*Series
    93  
    94  // Len returns the length of the series collection
    95  func (a SeriesByNameAndNaturalNumbers) Len() int {
    96  	return len(a)
    97  }
    98  
    99  // Swap swaps two series in the collection
   100  func (a SeriesByNameAndNaturalNumbers) Swap(i, j int) {
   101  	a[i], a[j] = a[j], a[i]
   102  }
   103  
   104  // Less determines if a series is ordered before another series by name
   105  // nolint: ifshort
   106  func (a SeriesByNameAndNaturalNumbers) Less(i, j int) bool {
   107  	left := a[i].name
   108  	if strings.ContainsAny(left, digits) {
   109  		left = digitsRegex.ReplaceAllStringFunc(left, digitsPrefixed)
   110  	}
   111  
   112  	right := a[j].name
   113  	if strings.ContainsAny(right, digits) {
   114  		right = digitsRegex.ReplaceAllStringFunc(right, digitsPrefixed)
   115  	}
   116  
   117  	return left < right
   118  }
   119  
   120  func digitsPrefixed(digits string) string {
   121  	n, err := strconv.Atoi(digits)
   122  	if err != nil {
   123  		return digits
   124  	}
   125  	return fmt.Sprintf("%010d", n)
   126  }
   127  
   128  // NewSeries creates a new Series at a given start time, backed by the provided values
   129  func NewSeries(ctx context.Context, name string, startTime time.Time, vals Values) *Series {
   130  	return &Series{
   131  		name:          name,
   132  		startTime:     startTime,
   133  		vals:          vals,
   134  		ctx:           ctx,
   135  		Specification: name,
   136  	}
   137  }
   138  
   139  // DerivedSeries returns a series derived from the current series with different datapoints
   140  func (b *Series) DerivedSeries(startTime time.Time, vals Values) *Series {
   141  	series := NewSeries(b.ctx, b.name, startTime, vals)
   142  	series.Specification = b.Specification
   143  	series.consolidationFunc = b.consolidationFunc
   144  	return series
   145  }
   146  
   147  // Name returns the name of the timeseries block
   148  func (b *Series) Name() string { return b.name }
   149  
   150  // RenamedTo returns a new timeseries with the same values but a different name
   151  func (b *Series) RenamedTo(name string) *Series {
   152  	return &Series{
   153  		name:              name,
   154  		startTime:         b.startTime,
   155  		vals:              b.vals,
   156  		ctx:               b.ctx,
   157  		Specification:     b.Specification,
   158  		consolidationFunc: b.consolidationFunc,
   159  	}
   160  }
   161  
   162  // Shift returns a new timeseries with the same values but a different startTime
   163  func (b *Series) Shift(shift time.Duration) *Series {
   164  	return &Series{
   165  		name:              b.name,
   166  		startTime:         b.startTime.Add(shift),
   167  		vals:              b.vals,
   168  		ctx:               b.ctx,
   169  		Specification:     b.Specification,
   170  		consolidationFunc: b.consolidationFunc,
   171  	}
   172  }
   173  
   174  // StartTime returns the time the block starts
   175  func (b *Series) StartTime() time.Time { return b.startTime }
   176  
   177  // EndTime returns the time the block ends
   178  func (b *Series) EndTime() time.Time { return b.startTime.Add(b.Duration()) }
   179  
   180  // Duration returns the Duration covered by the block
   181  func (b *Series) Duration() time.Duration {
   182  	return time.Millisecond * time.Duration(b.vals.Len()*b.vals.MillisPerStep())
   183  }
   184  
   185  // MillisPerStep returns the number of milliseconds per step
   186  func (b *Series) MillisPerStep() int { return b.vals.MillisPerStep() }
   187  
   188  // Resolution returns resolution per step
   189  func (b *Series) Resolution() time.Duration {
   190  	return time.Duration(b.MillisPerStep()) * time.Millisecond
   191  }
   192  
   193  // StepAtTime returns the step within the block containing the given time
   194  func (b *Series) StepAtTime(t time.Time) int {
   195  	step := int(t.UnixNano()/1000000-b.startTime.UnixNano()/1000000) / b.vals.MillisPerStep()
   196  	if step < 0 {
   197  		return 0
   198  	}
   199  
   200  	return step
   201  }
   202  
   203  // StartTimeForStep returns the time at which the given step starts
   204  func (b *Series) StartTimeForStep(n int) time.Time {
   205  	return b.StartTime().Add(time.Millisecond * time.Duration(n*b.vals.MillisPerStep()))
   206  }
   207  
   208  // EndTimeForStep returns the time at which the given step end
   209  func (b *Series) EndTimeForStep(n int) time.Time {
   210  	return b.StartTimeForStep(n).Add(time.Millisecond * time.Duration(b.vals.MillisPerStep()))
   211  }
   212  
   213  // Slice returns a new Series composed from a subset of values in the original Series
   214  func (b *Series) Slice(begin, end int) (*Series, error) {
   215  	if begin >= end {
   216  		return nil, ErrRangeIsInvalid
   217  	}
   218  
   219  	result := NewSeries(b.ctx, b.name, b.StartTimeForStep(begin), b.vals.Slice(begin, end))
   220  	result.consolidationFunc = b.consolidationFunc
   221  
   222  	return result, nil
   223  }
   224  
   225  // ValueAtTime returns the value stored at the step representing the given time
   226  func (b *Series) ValueAtTime(t time.Time) float64 {
   227  	return b.ValueAt(b.StepAtTime(t))
   228  }
   229  
   230  // AllNaN returns true if the timeseries is all NaNs
   231  func (b *Series) AllNaN() bool { return b.vals.AllNaN() }
   232  
   233  // CalcStatistics calculates a standard aggregation across the block values
   234  func (b *Series) CalcStatistics() stats.Statistics {
   235  	if agg, ok := b.vals.(CustomStatistics); ok {
   236  		return agg.CalcStatistics()
   237  	}
   238  
   239  	return stats.Calc(b)
   240  }
   241  
   242  // Contains checks whether the given series contains the provided time
   243  func (b *Series) Contains(t time.Time) bool {
   244  	step := b.StepAtTime(t)
   245  	return step >= 0 && step < b.Len()
   246  }
   247  
   248  // Len returns the number of values in the time series.  Used for aggregation
   249  func (b *Series) Len() int { return b.vals.Len() }
   250  
   251  // ValueAt returns the value at a given step.  Used for aggregation
   252  func (b *Series) ValueAt(i int) float64 { return b.vals.ValueAt(i) }
   253  
   254  // SafeMax returns the maximum value of a series that's not an NaN.
   255  func (b *Series) SafeMax() float64 { return b.CalcStatistics().Max }
   256  
   257  // SafeMin returns the minimum value of a series that's not an NaN.
   258  func (b *Series) SafeMin() float64 { return b.CalcStatistics().Min }
   259  
   260  // SafeSum returns the sum of the values of a series, excluding NaNs.
   261  func (b *Series) SafeSum() float64 { return b.CalcStatistics().Sum }
   262  
   263  // SafeAvg returns the average of the values of a series, excluding NaNs.
   264  func (b *Series) SafeAvg() float64 { return b.CalcStatistics().Mean }
   265  
   266  // SafeStdDev returns the standard deviation of the values of a series, excluding NaNs.
   267  func (b *Series) SafeStdDev() float64 { return b.CalcStatistics().StdDev }
   268  
   269  // SafeLastValue returns the last datapoint of a series that's not an NaN.
   270  func (b *Series) SafeLastValue() float64 {
   271  	numPoints := b.Len()
   272  	for i := numPoints - 1; i >= 0; i-- {
   273  		v := b.ValueAt(i)
   274  		if !math.IsNaN(v) {
   275  			return v
   276  		}
   277  	}
   278  	return math.NaN()
   279  }
   280  
   281  // SafeValues returns all non-NaN values in the series.
   282  func (b *Series) SafeValues() []float64 {
   283  	numPoints := b.Len()
   284  	vals := make([]float64, 0, numPoints)
   285  	for i := 0; i < numPoints; i++ {
   286  		v := b.ValueAt(i)
   287  		if !math.IsNaN(v) {
   288  			vals = append(vals, v)
   289  		}
   290  	}
   291  	return vals
   292  }
   293  
   294  // ConsolidationFunc returns the consolidation function for the series,
   295  // or the averaging function is none specified.
   296  func (b *Series) ConsolidationFunc() ConsolidationFunc {
   297  	if b.consolidationFunc != nil {
   298  		return b.consolidationFunc
   299  	}
   300  	return Avg
   301  }
   302  
   303  // IsConsolidationFuncSet if the consolidationFunc is set
   304  func (b *Series) IsConsolidationFuncSet() bool {
   305  	return b.consolidationFunc != nil
   306  }
   307  
   308  // SetConsolidationFunc sets the consolidation function for the series
   309  func (b *Series) SetConsolidationFunc(cf ConsolidationFunc) {
   310  	b.consolidationFunc = cf
   311  }
   312  
   313  // PostConsolidationFunc is a function that takes a tuple of time and value after consolidation.
   314  type PostConsolidationFunc func(timestamp time.Time, value float64)
   315  
   316  // intersection returns a 3-tuple; First return parameter indicates if the intersection spans at
   317  // least one nanosecond; the next two return parameters are the start and end boundary timestamps
   318  // of the resulting overlap.
   319  func (b *Series) intersection(start, end time.Time) (bool, time.Time, time.Time) {
   320  	if b.EndTime().Before(start) || b.StartTime().After(end) {
   321  		return false, start, end
   322  	}
   323  	if start.Before(b.StartTime()) {
   324  		start = b.StartTime()
   325  	}
   326  	if end.After(b.EndTime()) {
   327  		end = b.EndTime()
   328  	}
   329  	if start.Equal(end) {
   330  		return false, start, end
   331  	}
   332  	return true, start, end
   333  }
   334  
   335  // resize takes a time series and returns a new time series of a different step size with aggregated
   336  // values; callers must provide callback method that collects the aggregated result
   337  func (b *Series) resizeStep(start, end time.Time, millisPerStep int,
   338  	stepAggregator ConsolidationFunc, callback PostConsolidationFunc) {
   339  	// panic, panic, panic for all malformed callers
   340  	if end.Before(start) || start.Before(b.StartTime()) || end.After(b.EndTime()) {
   341  		panic("invalid boundary params")
   342  	}
   343  	if b.MillisPerStep() == millisPerStep {
   344  		panic("requires different step size")
   345  	}
   346  	if b.MillisPerStep() < millisPerStep {
   347  		// Series step size is smaller than consolidation - aggregate each series step then apply
   348  		// the agggregated value to the consolidate.
   349  		seriesValuesPerStep := millisPerStep / b.MillisPerStep()
   350  		seriesStart, seriesEnd := b.StepAtTime(start), b.StepAtTime(end)
   351  		for n := seriesStart; n < seriesEnd; n += seriesValuesPerStep {
   352  			timestamp := b.StartTimeForStep(n)
   353  			aggregatedValue := math.NaN()
   354  			count := 0
   355  
   356  			for i := 0; i < seriesValuesPerStep && n+i < seriesEnd; i++ {
   357  				value := b.ValueAt(n + i)
   358  				aggregatedValue, count = consolidateValues(aggregatedValue, value, count,
   359  					stepAggregator)
   360  			}
   361  			callback(timestamp, aggregatedValue)
   362  		}
   363  		return
   364  	}
   365  }
   366  
   367  // resized implements PostConsolidationFunc.
   368  type resized struct {
   369  	values []float64
   370  }
   371  
   372  // appender adds new values to resized.values.
   373  func (v *resized) appender(timestamp time.Time, value float64) {
   374  	v.values = append(v.values, value)
   375  }
   376  
   377  // IntersectAndResize returns a new time series with a different millisPerStep that spans the
   378  // intersection of the underlying timeseries and the provided start and end time parameters
   379  func (b *Series) IntersectAndResize(
   380  	start, end time.Time,
   381  	millisPerStep int,
   382  	stepAggregator ConsolidationFunc,
   383  ) (*Series, error) {
   384  	intersects, start, end := b.intersection(start, end)
   385  	if !intersects {
   386  		ts := NewSeries(b.ctx, b.name, start, &float64Values{
   387  			millisPerStep: millisPerStep,
   388  			values:        []float64{},
   389  			numSteps:      0,
   390  		})
   391  		ts.Specification = b.Specification
   392  		return ts, nil
   393  	}
   394  	if b.MillisPerStep() == millisPerStep {
   395  		return b.Slice(b.StepAtTime(start), b.StepAtTime(end))
   396  	}
   397  	return b.resized(start, end, millisPerStep, stepAggregator), nil
   398  }
   399  
   400  func (b *Series) resized(
   401  	start, end time.Time,
   402  	millisPerStep int,
   403  	stepAggregator ConsolidationFunc,
   404  ) *Series {
   405  	// TODO: This append based model completely screws pooling; need to rewrite to allow for pooling.
   406  	v := &resized{}
   407  	b.resizeStep(start, end, millisPerStep, stepAggregator, v.appender)
   408  	ts := NewSeries(b.ctx, b.name, start, &float64Values{
   409  		millisPerStep: millisPerStep,
   410  		values:        v.values,
   411  		numSteps:      len(v.values),
   412  	})
   413  	ts.Specification = b.Specification
   414  	return ts
   415  }
   416  
   417  // NeedsResizeToMaxDataPoints returns whether the series needs resizing to max datapoints.
   418  func (b *Series) NeedsResizeToMaxDataPoints(maxDataPoints int64) bool {
   419  	if maxDataPoints <= 0 {
   420  		// No max datapoints specified.
   421  		return false
   422  	}
   423  	return int64(b.Len()) > maxDataPoints
   424  }
   425  
   426  // ResizeToMaxDataPointsMillisPerStep returns the new milliseconds per second
   427  // required if a series needs resizing and true, or if does not need resize
   428  // for max datapoints then it returns 0 and false.
   429  func (b *Series) ResizeToMaxDataPointsMillisPerStep(
   430  	maxDataPoints int64,
   431  ) (int, bool) {
   432  	if !b.NeedsResizeToMaxDataPoints(maxDataPoints) {
   433  		return 0, false
   434  	}
   435  	samplingMultiplier := math.Ceil(float64(b.Len()) / float64(maxDataPoints))
   436  	return int(samplingMultiplier * float64(b.MillisPerStep())), true
   437  }
   438  
   439  // ResizeToMaxDataPoints resizes the series to fit max datapoints and returns
   440  // true if a series was resized or false if it did not need to be resized.
   441  func (b *Series) ResizeToMaxDataPoints(
   442  	maxDataPoints int64,
   443  	stepAggregator ConsolidationFunc,
   444  ) (*Series, bool) {
   445  	resizeMillisPerStep, needsResize := b.ResizeToMaxDataPointsMillisPerStep(maxDataPoints)
   446  	if !needsResize {
   447  		return nil, false
   448  	}
   449  	return b.resized(b.StartTime(), b.EndTime(), resizeMillisPerStep, stepAggregator), true
   450  }
   451  
   452  // A MutableSeries is a Series that allows updates
   453  type MutableSeries struct {
   454  	Series
   455  }
   456  
   457  // NewMutableSeries returns a new mutable Series at the
   458  // given start time and backed by the provided storage
   459  func NewMutableSeries(
   460  	ctx context.Context,
   461  	name string,
   462  	startTime time.Time,
   463  	vals MutableValues) *MutableSeries {
   464  	return &MutableSeries{
   465  		Series{
   466  			name:          name,
   467  			startTime:     startTime,
   468  			vals:          vals,
   469  			ctx:           ctx,
   470  			Specification: name,
   471  		},
   472  	}
   473  }
   474  
   475  // SetValueAt sets the value at the given step
   476  func (b *MutableSeries) SetValueAt(i int, v float64) {
   477  	b.vals.(MutableValues).SetValueAt(i, v)
   478  }
   479  
   480  // SetValueAtTime sets the value at the step containing the given time
   481  func (b *MutableSeries) SetValueAtTime(t time.Time, v float64) {
   482  	b.SetValueAt(b.StepAtTime(t), v)
   483  }
   484  
   485  // A Consolidation produces a Series whose values are the result of applying a consolidation
   486  // function to all of the datapoints that fall within each step.  It can used to quantize raw
   487  // datapoints into a given resolution, for example, or to aggregate multiple timeseries at the
   488  // same or smaller resolutions.
   489  type Consolidation interface {
   490  	// AddDatapoint adds an individual datapoint to the consolidation.
   491  	AddDatapoint(timestamp time.Time, value float64)
   492  
   493  	// AddDatapoints adds a set of datapoints to the consolidation.
   494  	AddDatapoints(datapoints []Datapoint)
   495  
   496  	// AddSeries adds the datapoints for each series to the consolidation.  The
   497  	// stepAggregationFunc is used to combine values from the series if the series
   498  	// has a smaller step size than the consolidation.  For example, an application
   499  	// might want to produce a consolidation which is a minimum of the input timeseries,
   500  	// but where the values in smaller timeseries units are summed together to
   501  	// produce the value to which the consolidation applies.
   502  	// To put it in another way, stepAggregationFunc is used for the series to resize itself
   503  	// rather than for the consolidation
   504  	AddSeries(series *Series, stepAggregationFunc ConsolidationFunc)
   505  
   506  	// BuildSeries returns the consolidated Series and optionally finalizes
   507  	// the consolidation returning it to the pool
   508  	BuildSeries(id string, finalize FinalizeOption) *Series
   509  
   510  	// Finalize returns the consolidation to the pool
   511  	Finalize()
   512  }
   513  
   514  // FinalizeOption specifies the option to finalize or avoid finalizing
   515  type FinalizeOption int
   516  
   517  const (
   518  	// NoFinalize will avoid finalizing the subject
   519  	NoFinalize FinalizeOption = iota
   520  	// Finalize will finalize the subject
   521  	Finalize
   522  )
   523  
   524  // A ConsolidationFunc consolidates values at a given point in time.  It takes the current consolidated
   525  // value, the new value to add to the consolidation, and a count of the number of values that have
   526  // already been consolidated.
   527  type ConsolidationFunc func(existing, toAdd float64, count int) float64
   528  
   529  // NewConsolidation creates a new consolidation window.
   530  func NewConsolidation(
   531  	ctx context.Context,
   532  	start, end time.Time,
   533  	millisPerStep int,
   534  	cf ConsolidationFunc,
   535  ) Consolidation {
   536  	var (
   537  		numSteps = NumSteps(start, end, millisPerStep)
   538  		values   = NewValues(ctx, millisPerStep, numSteps)
   539  		c        *consolidation
   540  		pooled   = false
   541  	)
   542  
   543  	if consolidationPools != nil {
   544  		temp := consolidationPools.Get(numSteps)
   545  		c = temp.(*consolidation)
   546  		if cap(c.counts) >= numSteps {
   547  			c.counts = c.counts[:numSteps]
   548  			for i := range c.counts {
   549  				c.counts[i] = 0
   550  			}
   551  			pooled = true
   552  		}
   553  	}
   554  
   555  	if !pooled {
   556  		c = newConsolidation(numSteps)
   557  	}
   558  
   559  	c.ctx = ctx
   560  	c.start = start
   561  	c.end = end
   562  	c.millisPerStep = millisPerStep
   563  	c.values = values
   564  	c.f = cf
   565  
   566  	return c
   567  }
   568  
   569  func newConsolidation(numSteps int) *consolidation {
   570  	counts := make([]int, numSteps)
   571  	return &consolidation{
   572  		counts: counts,
   573  	}
   574  }
   575  
   576  type consolidation struct {
   577  	ctx           context.Context
   578  	start         time.Time
   579  	end           time.Time
   580  	millisPerStep int
   581  	values        MutableValues
   582  	counts        []int
   583  	f             ConsolidationFunc
   584  }
   585  
   586  func (c *consolidation) AddDatapoints(datapoints []Datapoint) {
   587  	for _, datapoint := range datapoints {
   588  		c.AddDatapoint(datapoint.Timestamp, datapoint.Value)
   589  	}
   590  }
   591  
   592  func (c *consolidation) AddDatapoint(timestamp time.Time, value float64) {
   593  	if timestamp.Before(c.start) || timestamp.After(c.end) {
   594  		return
   595  	}
   596  
   597  	if math.IsNaN(value) {
   598  		return
   599  	}
   600  
   601  	step := int(timestamp.UnixNano()/1000000-c.start.UnixNano()/1000000) / c.millisPerStep
   602  	if step >= c.values.Len() {
   603  		return
   604  	}
   605  
   606  	n, count := consolidateValues(c.values.ValueAt(step), value, c.counts[step], c.f)
   607  	c.counts[step] = count
   608  	c.values.SetValueAt(step, n)
   609  }
   610  
   611  func consolidateValues(current, value float64, count int, f ConsolidationFunc) (float64, int) {
   612  	if math.IsNaN(value) {
   613  		return current, count
   614  	}
   615  
   616  	if count == 0 {
   617  		return value, 1
   618  	}
   619  
   620  	return f(current, value, count), count + 1
   621  }
   622  
   623  // AddSeries adds a time series to the consolidation; stepAggregator is used to resize the
   624  // provided timeseries if it's step size is different from the consolidator's step size.
   625  func (c *consolidation) AddSeries(series *Series, stepAggregator ConsolidationFunc) {
   626  	if series.AllNaN() {
   627  		return
   628  	}
   629  
   630  	intersects, start, end := series.intersection(c.start, c.end)
   631  	if !intersects {
   632  		// Nothing to do.
   633  		return
   634  	}
   635  
   636  	if series.MillisPerStep() == c.millisPerStep {
   637  		// Series step size is identical to the consolidation: simply apply each series value to
   638  		// the consolidation.
   639  		startIndex := series.StepAtTime(start)
   640  		endIndex := int(math.Min(float64(series.StepAtTime(end)), float64(series.Len()-1)))
   641  		for n := startIndex; n <= endIndex; n++ {
   642  			c.AddDatapoint(series.StartTimeForStep(n), series.ValueAt(n))
   643  		}
   644  		return
   645  	}
   646  	series.resizeStep(start, end, c.millisPerStep, stepAggregator, c.AddDatapoint)
   647  }
   648  
   649  func (c *consolidation) BuildSeries(id string, f FinalizeOption) *Series {
   650  	series := NewSeries(c.ctx, id, c.start, c.values)
   651  	if f == Finalize {
   652  		c.Finalize()
   653  	}
   654  	return series
   655  }
   656  
   657  func (c *consolidation) Finalize() {
   658  	c.ctx = nil
   659  	c.start = time.Time{}
   660  	c.end = time.Time{}
   661  	c.millisPerStep = 0
   662  	c.values = nil
   663  	c.f = nil
   664  	if consolidationPools == nil {
   665  		return
   666  	}
   667  	consolidationPools.Put(c, cap(c.counts))
   668  }
   669  
   670  // NumSteps calculates the number of steps of a given size between two times.
   671  func NumSteps(start, end time.Time, millisPerStep int) int {
   672  	// We should round up.
   673  	numSteps := int(math.Ceil(float64(
   674  		end.Sub(start)/time.Millisecond) / float64(millisPerStep)))
   675  
   676  	if numSteps > 0 {
   677  		return numSteps
   678  	}
   679  
   680  	// Even for intervals less than millisPerStep, there should be at least one step.
   681  	return 1
   682  }
   683  
   684  // Sum sums two values.
   685  func Sum(a, b float64, count int) float64 { return a + b }
   686  
   687  // Mul multiplies two values.
   688  func Mul(a, b float64, count int) float64 { return a * b }
   689  
   690  // Avg produces a running average.
   691  func Avg(a, b float64, count int) float64 { return (a*float64(count) + b) / float64(count+1) }
   692  
   693  // Min finds the min of two values.
   694  func Min(a, b float64, count int) float64 { return math.Min(a, b) }
   695  
   696  // Max finds the max of two values.
   697  func Max(a, b float64, count int) float64 { return math.Max(a, b) }
   698  
   699  // Last finds the latter of two values.
   700  func Last(a, b float64, count int) float64 { return b }
   701  
   702  // Pow returns the first value to the power of the second value
   703  func Pow(a, b float64, count int) float64 { return math.Pow(a, b) }
   704  
   705  // Median finds the median of a slice of values.
   706  func Median(vals []float64, count int) float64 {
   707  	if count < 1 {
   708  		return math.NaN()
   709  	}
   710  	if count == 1 {
   711  		return vals[0]
   712  	}
   713  	sort.Float64s(vals)
   714  	if count%2 != 0 {
   715  		// if count is odd
   716  		return vals[(count-1)/2]
   717  	}
   718  	// if count is even
   719  	return (vals[count/2] + vals[(count/2)-1]) / 2.0
   720  }
   721  
   722  // Gcd finds the gcd of two values.
   723  func Gcd(a, b int64) int64 {
   724  	if a < 0 {
   725  		a = -a
   726  	}
   727  
   728  	if b < 0 {
   729  		b = -b
   730  	}
   731  
   732  	if b == 0 {
   733  		return a
   734  	}
   735  
   736  	return Gcd(b, a%b)
   737  }
   738  
   739  // Lcm finds the lcm of two values.
   740  func Lcm(a, b int64) int64 {
   741  	if a < 0 {
   742  		a = -a
   743  	}
   744  
   745  	if b < 0 {
   746  		b = -b
   747  	}
   748  
   749  	if a == b {
   750  		return a
   751  	}
   752  
   753  	if a < b {
   754  		a, b = b, a
   755  	}
   756  
   757  	return a / Gcd(a, b) * b
   758  }
   759  
   760  // A SeriesList is a list of series.
   761  type SeriesList struct {
   762  	// Values is the list of series.
   763  	Values []*Series
   764  	// SortApplied specifies whether a specific sort order has been applied.
   765  	SortApplied bool
   766  	// Metadata contains any additional metadata indicating information about
   767  	// series execution.
   768  	Metadata block.ResultMetadata
   769  }
   770  
   771  // NewSeriesList creates a blank series list.
   772  func NewSeriesList() SeriesList {
   773  	return SeriesList{Metadata: block.NewResultMetadata()}
   774  }
   775  
   776  // NewSeriesListWithSeries creates a series list with the given series and
   777  // default metadata.
   778  func NewSeriesListWithSeries(values ...*Series) SeriesList {
   779  	return SeriesList{
   780  		Values:   values,
   781  		Metadata: block.NewResultMetadata(),
   782  	}
   783  }
   784  
   785  // Len returns the length of the list.
   786  func (l SeriesList) Len() int {
   787  	return len(l.Values)
   788  }