github.com/muhammadn/cortex@v1.9.1-0.20220510110439-46bb7000d03d/pkg/ingester/series.go (about)

     1  package ingester
     2  
     3  import (
     4  	"fmt"
     5  	"sort"
     6  
     7  	"github.com/prometheus/client_golang/prometheus"
     8  	"github.com/prometheus/common/model"
     9  	"github.com/prometheus/prometheus/pkg/labels"
    10  	"github.com/prometheus/prometheus/pkg/value"
    11  
    12  	"github.com/cortexproject/cortex/pkg/chunk/encoding"
    13  	"github.com/cortexproject/cortex/pkg/prom1/storage/metric"
    14  )
    15  
    16  const (
    17  	sampleOutOfOrder     = "sample-out-of-order"
    18  	newValueForTimestamp = "new-value-for-timestamp"
    19  	sampleOutOfBounds    = "sample-out-of-bounds"
    20  	duplicateSample      = "duplicate-sample"
    21  	duplicateTimestamp   = "duplicate-timestamp"
    22  )
    23  
    24  type memorySeries struct {
    25  	metric labels.Labels
    26  
    27  	// Sorted by start time, overlapping chunk ranges are forbidden.
    28  	chunkDescs []*desc
    29  
    30  	// Whether the current head chunk has already been finished.  If true,
    31  	// the current head chunk must not be modified anymore.
    32  	headChunkClosed bool
    33  
    34  	// The timestamp & value of the last sample in this series. Needed to
    35  	// ensure timestamp monotonicity during ingestion.
    36  	lastSampleValueSet bool
    37  	lastTime           model.Time
    38  	lastSampleValue    model.SampleValue
    39  
    40  	// Prometheus metrics.
    41  	createdChunks prometheus.Counter
    42  }
    43  
    44  // newMemorySeries returns a pointer to a newly allocated memorySeries for the
    45  // given metric.
    46  func newMemorySeries(m labels.Labels, createdChunks prometheus.Counter) *memorySeries {
    47  	return &memorySeries{
    48  		metric:        m,
    49  		lastTime:      model.Earliest,
    50  		createdChunks: createdChunks,
    51  	}
    52  }
    53  
    54  // add adds a sample pair to the series, possibly creating a new chunk.
    55  // The caller must have locked the fingerprint of the series.
    56  func (s *memorySeries) add(v model.SamplePair) error {
    57  	// If sender has repeated the same timestamp, check more closely and perhaps return error.
    58  	if v.Timestamp == s.lastTime {
    59  		// If we don't know what the last sample value is, silently discard.
    60  		// This will mask some errors but better than complaining when we don't really know.
    61  		if !s.lastSampleValueSet {
    62  			return makeNoReportError(duplicateTimestamp)
    63  		}
    64  		// If both timestamp and sample value are the same as for the last append,
    65  		// ignore as they are a common occurrence when using client-side timestamps
    66  		// (e.g. Pushgateway or federation).
    67  		if v.Value.Equal(s.lastSampleValue) {
    68  			return makeNoReportError(duplicateSample)
    69  		}
    70  		return makeMetricValidationError(newValueForTimestamp, s.metric,
    71  			fmt.Errorf("sample with repeated timestamp but different value; last value: %v, incoming value: %v", s.lastSampleValue, v.Value))
    72  	}
    73  	if v.Timestamp < s.lastTime {
    74  		return makeMetricValidationError(sampleOutOfOrder, s.metric,
    75  			fmt.Errorf("sample timestamp out of order; last timestamp: %v, incoming timestamp: %v", s.lastTime, v.Timestamp))
    76  	}
    77  
    78  	if len(s.chunkDescs) == 0 || s.headChunkClosed {
    79  		newHead := newDesc(encoding.New(), v.Timestamp, v.Timestamp)
    80  		s.chunkDescs = append(s.chunkDescs, newHead)
    81  		s.headChunkClosed = false
    82  		s.createdChunks.Inc()
    83  	}
    84  
    85  	newChunk, err := s.head().add(v)
    86  	if err != nil {
    87  		return err
    88  	}
    89  
    90  	// If we get a single chunk result, then just replace the head chunk with it
    91  	// (no need to update first/last time).  Otherwise, we'll need to update first
    92  	// and last time.
    93  	if newChunk != nil {
    94  		first, last, err := firstAndLastTimes(newChunk)
    95  		if err != nil {
    96  			return err
    97  		}
    98  		s.chunkDescs = append(s.chunkDescs, newDesc(newChunk, first, last))
    99  		s.createdChunks.Inc()
   100  	}
   101  
   102  	s.lastTime = v.Timestamp
   103  	s.lastSampleValue = v.Value
   104  	s.lastSampleValueSet = true
   105  
   106  	return nil
   107  }
   108  
   109  func firstAndLastTimes(c encoding.Chunk) (model.Time, model.Time, error) {
   110  	var (
   111  		first    model.Time
   112  		last     model.Time
   113  		firstSet bool
   114  		iter     = c.NewIterator(nil)
   115  	)
   116  	for iter.Scan() {
   117  		sample := iter.Value()
   118  		if !firstSet {
   119  			first = sample.Timestamp
   120  			firstSet = true
   121  		}
   122  		last = sample.Timestamp
   123  	}
   124  	return first, last, iter.Err()
   125  }
   126  
   127  // closeHead marks the head chunk closed. The caller must have locked
   128  // the fingerprint of the memorySeries. This method will panic if this
   129  // series has no chunk descriptors.
   130  func (s *memorySeries) closeHead(reason flushReason) {
   131  	s.chunkDescs[0].flushReason = reason
   132  	s.headChunkClosed = true
   133  }
   134  
   135  // firstTime returns the earliest known time for the series. The caller must have
   136  // locked the fingerprint of the memorySeries. This method will panic if this
   137  // series has no chunk descriptors.
   138  func (s *memorySeries) firstTime() model.Time {
   139  	return s.chunkDescs[0].FirstTime
   140  }
   141  
   142  // Returns time of oldest chunk in the series, that isn't flushed. If there are
   143  // no chunks, or all chunks are flushed, returns 0.
   144  // The caller must have locked the fingerprint of the memorySeries.
   145  func (s *memorySeries) firstUnflushedChunkTime() model.Time {
   146  	for _, c := range s.chunkDescs {
   147  		if !c.flushed {
   148  			return c.FirstTime
   149  		}
   150  	}
   151  
   152  	return 0
   153  }
   154  
   155  // head returns a pointer to the head chunk descriptor. The caller must have
   156  // locked the fingerprint of the memorySeries. This method will panic if this
   157  // series has no chunk descriptors.
   158  func (s *memorySeries) head() *desc {
   159  	return s.chunkDescs[len(s.chunkDescs)-1]
   160  }
   161  
   162  func (s *memorySeries) samplesForRange(from, through model.Time) ([]model.SamplePair, error) {
   163  	// Find first chunk with start time after "from".
   164  	fromIdx := sort.Search(len(s.chunkDescs), func(i int) bool {
   165  		return s.chunkDescs[i].FirstTime.After(from)
   166  	})
   167  	// Find first chunk with start time after "through".
   168  	throughIdx := sort.Search(len(s.chunkDescs), func(i int) bool {
   169  		return s.chunkDescs[i].FirstTime.After(through)
   170  	})
   171  	if fromIdx == len(s.chunkDescs) {
   172  		// Even the last chunk starts before "from". Find out if the
   173  		// series ends before "from" and we don't need to do anything.
   174  		lt := s.chunkDescs[len(s.chunkDescs)-1].LastTime
   175  		if lt.Before(from) {
   176  			return nil, nil
   177  		}
   178  	}
   179  	if fromIdx > 0 {
   180  		fromIdx--
   181  	}
   182  	if throughIdx == len(s.chunkDescs) {
   183  		throughIdx--
   184  	}
   185  	var values []model.SamplePair
   186  	in := metric.Interval{
   187  		OldestInclusive: from,
   188  		NewestInclusive: through,
   189  	}
   190  	var reuseIter encoding.Iterator
   191  	for idx := fromIdx; idx <= throughIdx; idx++ {
   192  		cd := s.chunkDescs[idx]
   193  		reuseIter = cd.C.NewIterator(reuseIter)
   194  		chValues, err := encoding.RangeValues(reuseIter, in)
   195  		if err != nil {
   196  			return nil, err
   197  		}
   198  		values = append(values, chValues...)
   199  	}
   200  	return values, nil
   201  }
   202  
   203  func (s *memorySeries) setChunks(descs []*desc) error {
   204  	if len(s.chunkDescs) != 0 {
   205  		return fmt.Errorf("series already has chunks")
   206  	}
   207  
   208  	s.chunkDescs = descs
   209  	if len(descs) > 0 {
   210  		s.lastTime = descs[len(descs)-1].LastTime
   211  	}
   212  	return nil
   213  }
   214  
   215  func (s *memorySeries) isStale() bool {
   216  	return s.lastSampleValueSet && value.IsStaleNaN(float64(s.lastSampleValue))
   217  }
   218  
   219  type desc struct {
   220  	C           encoding.Chunk // nil if chunk is evicted.
   221  	FirstTime   model.Time     // Timestamp of first sample. Populated at creation. Immutable.
   222  	LastTime    model.Time     // Timestamp of last sample. Populated at creation & on append.
   223  	LastUpdate  model.Time     // This server's local time on last change
   224  	flushReason flushReason    // If chunk is closed, holds the reason why.
   225  	flushed     bool           // set to true when flush succeeds
   226  }
   227  
   228  func newDesc(c encoding.Chunk, firstTime model.Time, lastTime model.Time) *desc {
   229  	return &desc{
   230  		C:          c,
   231  		FirstTime:  firstTime,
   232  		LastTime:   lastTime,
   233  		LastUpdate: model.Now(),
   234  	}
   235  }
   236  
   237  // Add adds a sample pair to the underlying chunk. For safe concurrent access,
   238  // The chunk must be pinned, and the caller must have locked the fingerprint of
   239  // the series.
   240  func (d *desc) add(s model.SamplePair) (encoding.Chunk, error) {
   241  	cs, err := d.C.Add(s)
   242  	if err != nil {
   243  		return nil, err
   244  	}
   245  
   246  	if cs == nil {
   247  		d.LastTime = s.Timestamp // sample was added to this chunk
   248  		d.LastUpdate = model.Now()
   249  	}
   250  
   251  	return cs, nil
   252  }
   253  
   254  func (d *desc) slice(start, end model.Time) *desc {
   255  	return &desc{
   256  		C:         d.C.Slice(start, end),
   257  		FirstTime: start,
   258  		LastTime:  end,
   259  	}
   260  }