github.com/m3db/m3@v1.5.1-0.20231129193456-75a402aa583b/src/query/storage/m3/consolidators/step_consolidator.go (about)

     1  // Copyright (c) 2018 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 consolidators
    22  
    23  import (
    24  	"time"
    25  
    26  	"github.com/m3db/m3/src/dbnode/ts"
    27  	xtime "github.com/m3db/m3/src/x/time"
    28  )
    29  
    30  func removeStale(
    31  	earliestLookback xtime.UnixNano,
    32  	dps []ts.Datapoint,
    33  ) []ts.Datapoint {
    34  	for i, dp := range dps {
    35  		if !dp.TimestampNanos.Before(earliestLookback) {
    36  			return dps[i:]
    37  		}
    38  	}
    39  
    40  	return dps[:0]
    41  }
    42  
    43  // StepLookbackConsolidator is a helper for consolidating series in a step-wise
    44  // fashion. It takes a 'step' of values, which represents a vertical
    45  // slice of time across a list of series, and consolidates when a
    46  // valid step has been reached.
    47  type StepLookbackConsolidator struct {
    48  	stepSize         time.Duration
    49  	earliestLookback xtime.UnixNano
    50  	datapoints       []ts.Datapoint
    51  	buffer           []float64
    52  	unconsumed       []float64
    53  	fn               ConsolidationFunc
    54  }
    55  
    56  // Ensure StepLookbackConsolidator satisfies StepCollector.
    57  var _ StepCollector = (*StepLookbackConsolidator)(nil)
    58  
    59  // NewStepLookbackConsolidator creates a multivalue consolidator used for
    60  // step iteration across a series list with a given lookback.
    61  func NewStepLookbackConsolidator(
    62  	lookbackDuration, stepSize time.Duration,
    63  	startTime xtime.UnixNano,
    64  	fn ConsolidationFunc,
    65  ) *StepLookbackConsolidator {
    66  	datapoints := make([]ts.Datapoint, 0, initLength)
    67  	buffer := make([]float64, BufferSteps)
    68  	return &StepLookbackConsolidator{
    69  		stepSize:         stepSize,
    70  		earliestLookback: startTime.Add(-1 * lookbackDuration),
    71  		datapoints:       datapoints,
    72  		buffer:           buffer,
    73  		unconsumed:       buffer[:0],
    74  		fn:               fn,
    75  	}
    76  }
    77  
    78  // AddPoint adds a datapoint to a given step if it's within the valid
    79  // time period; otherwise drops it silently, which is fine for consolidation.
    80  func (c *StepLookbackConsolidator) AddPoint(dp ts.Datapoint) {
    81  	if dp.TimestampNanos.Before(c.earliestLookback) {
    82  		// this datapoint is too far in the past, it can be dropped.
    83  		return
    84  	}
    85  
    86  	c.datapoints = append(c.datapoints, dp)
    87  }
    88  
    89  // BufferStep adds viable points to the next unconsumed buffer step.
    90  func (c *StepLookbackConsolidator) BufferStep() {
    91  	c.earliestLookback = c.earliestLookback.Add(c.stepSize)
    92  	val := c.fn(c.datapoints)
    93  
    94  	// Remove any datapoints not relevant to the next step now.
    95  	datapointsRelevant := removeStale(c.earliestLookback, c.datapoints)
    96  	if len(datapointsRelevant) > 0 {
    97  		// Move them back to the start of the slice to reuse the slice
    98  		// as best as possible.
    99  		c.datapoints = c.datapoints[:len(datapointsRelevant)]
   100  		copy(c.datapoints, datapointsRelevant)
   101  	} else {
   102  		// No relevant datapoints, repoint to the start of the buffer.
   103  		c.datapoints = c.datapoints[:0]
   104  	}
   105  
   106  	// Blindly append to unconsumed.
   107  	c.unconsumed = append(c.unconsumed, val)
   108  }
   109  
   110  // BufferStepCount indicates how many accumulated points are still unconsumed.
   111  func (c *StepLookbackConsolidator) BufferStepCount() int {
   112  	return len(c.unconsumed)
   113  }
   114  
   115  // ConsolidateAndMoveToNext consolidates the current values and moves the
   116  // consolidator to the next given value, purging stale values.
   117  func (c *StepLookbackConsolidator) ConsolidateAndMoveToNext() float64 {
   118  	if len(c.unconsumed) == 0 {
   119  		return c.fn(nil)
   120  	}
   121  
   122  	// Consume value.
   123  	val := c.unconsumed[0]
   124  	remaining := c.unconsumed[1:]
   125  
   126  	if len(remaining) > 0 {
   127  		// Move any unconsumed values to the front of unconsumed.
   128  		c.unconsumed = c.buffer[:len(remaining)]
   129  		copy(c.unconsumed, remaining)
   130  	} else {
   131  		// Otherwise just repoint to the start of the buffer.
   132  		c.unconsumed = c.buffer[:0]
   133  	}
   134  
   135  	return val
   136  }