164 lines
		
	
	
		
			5.7 KiB
		
	
	
	
		
			Go
		
	
	
	
			
		
		
	
	
			164 lines
		
	
	
		
			5.7 KiB
		
	
	
	
		
			Go
		
	
	
	
| // Copyright 2024 The Prometheus Authors
 | |
| // Licensed under the Apache License, Version 2.0 (the "License");
 | |
| // you may not use this file except in compliance with the License.
 | |
| // You may obtain a copy of the License at
 | |
| //
 | |
| // http://www.apache.org/licenses/LICENSE-2.0
 | |
| //
 | |
| // Unless required by applicable law or agreed to in writing, software
 | |
| // distributed under the License is distributed on an "AS IS" BASIS,
 | |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | |
| // See the License for the specific language governing permissions and
 | |
| // limitations under the License.
 | |
| 
 | |
| package promql
 | |
| 
 | |
| import (
 | |
| 	"github.com/prometheus/prometheus/model/histogram"
 | |
| 	"github.com/prometheus/prometheus/model/value"
 | |
| 	"github.com/prometheus/prometheus/tsdb/chunkenc"
 | |
| )
 | |
| 
 | |
| // HistogramStatsIterator is an iterator that returns histogram objects that
 | |
| // have only their sum and count values populated. The iterator handles counter
 | |
| // reset detection internally and sets the counter reset hint accordingly in
 | |
| // each returned histogram object. The Next and Seek methods of the iterator
 | |
| // will never return ValHistogram, but ValFloatHistogram instead. Effectively,
 | |
| // the iterator enforces conversion of (integer) Histogram to FloatHistogram.
 | |
| // The AtHistogram method must not be called (and will panic).
 | |
| type HistogramStatsIterator struct {
 | |
| 	chunkenc.Iterator
 | |
| 
 | |
| 	current       *histogram.FloatHistogram
 | |
| 	last          *histogram.FloatHistogram
 | |
| 	lastIsCurrent bool
 | |
| }
 | |
| 
 | |
| // NewHistogramStatsIterator creates a new HistogramStatsIterator.
 | |
| func NewHistogramStatsIterator(it chunkenc.Iterator) *HistogramStatsIterator {
 | |
| 	return &HistogramStatsIterator{
 | |
| 		Iterator: it,
 | |
| 		current:  &histogram.FloatHistogram{},
 | |
| 	}
 | |
| }
 | |
| 
 | |
| // Reset resets this iterator for use with a new underlying iterator, reusing
 | |
| // objects already allocated where possible.
 | |
| func (hsi *HistogramStatsIterator) Reset(it chunkenc.Iterator) {
 | |
| 	hsi.Iterator = it
 | |
| 	hsi.last = nil
 | |
| 	hsi.lastIsCurrent = false
 | |
| }
 | |
| 
 | |
| // Next mostly relays to the underlying iterator, but changes a ValHistogram
 | |
| // return into a ValFloatHistogram return.
 | |
| func (hsi *HistogramStatsIterator) Next() chunkenc.ValueType {
 | |
| 	hsi.lastIsCurrent = false
 | |
| 	vt := hsi.Iterator.Next()
 | |
| 	if vt == chunkenc.ValHistogram {
 | |
| 		return chunkenc.ValFloatHistogram
 | |
| 	}
 | |
| 	return vt
 | |
| }
 | |
| 
 | |
| // Seek mostly relays to the underlying iterator, but changes a ValHistogram
 | |
| // return into a ValFloatHistogram return.
 | |
| func (hsi *HistogramStatsIterator) Seek(t int64) chunkenc.ValueType {
 | |
| 	// If the Seek is going to move the iterator, we have to forget the
 | |
| 	// lastFH and mark the currentFH as not current anymore.
 | |
| 	if t > hsi.AtT() {
 | |
| 		hsi.last = nil
 | |
| 		hsi.lastIsCurrent = false
 | |
| 	}
 | |
| 	vt := hsi.Iterator.Seek(t)
 | |
| 	if vt == chunkenc.ValHistogram {
 | |
| 		return chunkenc.ValFloatHistogram
 | |
| 	}
 | |
| 	return vt
 | |
| }
 | |
| 
 | |
| // AtHistogram must never be called.
 | |
| func (*HistogramStatsIterator) AtHistogram(*histogram.Histogram) (int64, *histogram.Histogram) {
 | |
| 	panic("HistogramStatsIterator.AtHistogram must never be called")
 | |
| }
 | |
| 
 | |
| // AtFloatHistogram returns the next timestamp/float histogram pair. The method
 | |
| // performs a counter reset detection on the fly. It will return an explicit
 | |
| // hint (not UnknownCounterReset) if the previous sample has been accessed with
 | |
| // the same iterator.
 | |
| func (hsi *HistogramStatsIterator) AtFloatHistogram(fh *histogram.FloatHistogram) (int64, *histogram.FloatHistogram) {
 | |
| 	populateFH := func(src *histogram.FloatHistogram, detectReset bool) {
 | |
| 		h := histogram.FloatHistogram{
 | |
| 			CounterResetHint: src.CounterResetHint,
 | |
| 			Count:            src.Count,
 | |
| 			Sum:              src.Sum,
 | |
| 		}
 | |
| 		if detectReset {
 | |
| 			h.CounterResetHint = hsi.getResetHint(src.CounterResetHint)
 | |
| 		}
 | |
| 		if fh == nil {
 | |
| 			// Note that we cannot simply write `fh = &h` here
 | |
| 			// because that would let h escape to the heap.
 | |
| 			fh = &histogram.FloatHistogram{}
 | |
| 			*fh = h
 | |
| 		} else {
 | |
| 			h.CopyTo(fh)
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	if hsi.lastIsCurrent {
 | |
| 		// Nothing changed since last AtFloatHistogram call. Return a
 | |
| 		// copy of the stored last histogram rather than doing counter
 | |
| 		// reset detection again (which would yield a potentially wrong
 | |
| 		// result of "no counter reset").
 | |
| 		populateFH(hsi.last, false)
 | |
| 		return hsi.AtT(), fh
 | |
| 	}
 | |
| 
 | |
| 	var t int64
 | |
| 	t, hsi.current = hsi.Iterator.AtFloatHistogram(hsi.current)
 | |
| 	if value.IsStaleNaN(hsi.current.Sum) {
 | |
| 		populateFH(hsi.current, false)
 | |
| 		return t, fh
 | |
| 	}
 | |
| 	populateFH(hsi.current, true)
 | |
| 	hsi.setLastFromCurrent(fh.CounterResetHint)
 | |
| 	return t, fh
 | |
| }
 | |
| 
 | |
| // setLastFromCurrent stores a copy of hsi.current as hsi.last. The
 | |
| // CounterResetHint of hsi.last is set to the provided value, though. This is
 | |
| // meant to store the value we have calculated on the fly so that we can return
 | |
| // the same without re-calculation in case AtFloatHistogram is called multiple
 | |
| // times.
 | |
| func (hsi *HistogramStatsIterator) setLastFromCurrent(hint histogram.CounterResetHint) {
 | |
| 	if hsi.last == nil {
 | |
| 		hsi.last = hsi.current.Copy()
 | |
| 	} else {
 | |
| 		hsi.current.CopyTo(hsi.last)
 | |
| 	}
 | |
| 	hsi.last.CounterResetHint = hint
 | |
| 	hsi.lastIsCurrent = true
 | |
| }
 | |
| 
 | |
| func (hsi *HistogramStatsIterator) getResetHint(hint histogram.CounterResetHint) histogram.CounterResetHint {
 | |
| 	if hint != histogram.UnknownCounterReset {
 | |
| 		return hint
 | |
| 	}
 | |
| 	if hsi.last == nil {
 | |
| 		// We don't know if there's a counter reset. Note that this
 | |
| 		// generally will trigger an explicit counter reset detection by
 | |
| 		// the PromQL engine, which in turn isn't as reliable in this
 | |
| 		// case because the PromQL engine will not see the buckets.
 | |
| 		// However, we can assume that in cases where the counter reset
 | |
| 		// detection is relevant, an iteration through the series has
 | |
| 		// happened, and therefore we do not end up here in the first
 | |
| 		// place.
 | |
| 		return histogram.UnknownCounterReset
 | |
| 	}
 | |
| 	if hsi.current.DetectReset(hsi.last) {
 | |
| 		return histogram.CounterReset
 | |
| 	}
 | |
| 	return histogram.NotCounterReset
 | |
| }
 |