prometheus/promql/histogram_stats_iterator.go
Filip Petkovski be7a4c9b83
Ignore stale histograms for counter reset detection
The histogram stats decoder keeps track of the last seen histogram sample
in order to properly detect counter resets. We are seeing an issue where
a histogram with UnknownResetHint gets treated as a counter reset when it follows
a stale histogram sample.

I believe that this is incorrect since stale samples should be completely ignored
in PromQL. As a result, they should not be stored in the histogram stats iterator
and the counter reset detection needs to be done against the last non-stale sample.

Signed-off-by: Filip Petkovski <filip.petkovsky@gmail.com>
2024-07-26 10:08:31 +02:00

143 lines
4.2 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"
)
type histogramStatsIterator struct {
chunkenc.Iterator
currentH *histogram.Histogram
lastH *histogram.Histogram
currentFH *histogram.FloatHistogram
lastFH *histogram.FloatHistogram
}
// NewHistogramStatsIterator creates an iterator which returns histogram objects
// which 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 objects.
func NewHistogramStatsIterator(it chunkenc.Iterator) chunkenc.Iterator {
return &histogramStatsIterator{
Iterator: it,
currentH: &histogram.Histogram{},
currentFH: &histogram.FloatHistogram{},
}
}
// AtHistogram returns the next timestamp/histogram pair. The counter reset
// detection is guaranteed to be correct only when the caller does not switch
// between AtHistogram and AtFloatHistogram calls.
func (f *histogramStatsIterator) AtHistogram(h *histogram.Histogram) (int64, *histogram.Histogram) {
var t int64
t, f.currentH = f.Iterator.AtHistogram(f.currentH)
if value.IsStaleNaN(f.currentH.Sum) {
h = &histogram.Histogram{Sum: f.currentH.Sum}
return t, h
}
if h == nil {
h = &histogram.Histogram{
CounterResetHint: f.getResetHint(f.currentH),
Count: f.currentH.Count,
Sum: f.currentH.Sum,
}
f.setLastH(f.currentH)
return t, h
}
h.CounterResetHint = f.getResetHint(f.currentH)
h.Count = f.currentH.Count
h.Sum = f.currentH.Sum
f.setLastH(f.currentH)
return t, h
}
// AtFloatHistogram returns the next timestamp/float histogram pair. The counter
// reset detection is guaranteed to be correct only when the caller does not
// switch between AtHistogram and AtFloatHistogram calls.
func (f *histogramStatsIterator) AtFloatHistogram(fh *histogram.FloatHistogram) (int64, *histogram.FloatHistogram) {
var t int64
t, f.currentFH = f.Iterator.AtFloatHistogram(f.currentFH)
if value.IsStaleNaN(f.currentFH.Sum) {
return t, &histogram.FloatHistogram{Sum: f.currentFH.Sum}
}
if fh == nil {
fh = &histogram.FloatHistogram{
CounterResetHint: f.getFloatResetHint(f.currentFH.CounterResetHint),
Count: f.currentFH.Count,
Sum: f.currentFH.Sum,
}
f.setLastFH(f.currentFH)
return t, fh
}
fh.CounterResetHint = f.getFloatResetHint(f.currentFH.CounterResetHint)
fh.Count = f.currentFH.Count
fh.Sum = f.currentFH.Sum
f.setLastFH(f.currentFH)
return t, fh
}
func (f *histogramStatsIterator) setLastH(h *histogram.Histogram) {
if f.lastH == nil {
f.lastH = h.Copy()
} else {
h.CopyTo(f.lastH)
}
}
func (f *histogramStatsIterator) setLastFH(fh *histogram.FloatHistogram) {
if f.lastFH == nil {
f.lastFH = fh.Copy()
} else {
fh.CopyTo(f.lastFH)
}
}
func (f *histogramStatsIterator) getFloatResetHint(hint histogram.CounterResetHint) histogram.CounterResetHint {
if hint != histogram.UnknownCounterReset {
return hint
}
if f.lastFH == nil {
return histogram.NotCounterReset
}
if f.currentFH.DetectReset(f.lastFH) {
return histogram.CounterReset
}
return histogram.NotCounterReset
}
func (f *histogramStatsIterator) getResetHint(h *histogram.Histogram) histogram.CounterResetHint {
if h.CounterResetHint != histogram.UnknownCounterReset {
return h.CounterResetHint
}
if f.lastH == nil {
return histogram.NotCounterReset
}
fh, prevFH := h.ToFloat(nil), f.lastH.ToFloat(nil)
if fh.DetectReset(prevFH) {
return histogram.CounterReset
}
return histogram.NotCounterReset
}