mirror of
https://github.com/prometheus/prometheus
synced 2024-12-23 15:04:13 +00:00
be7a4c9b83
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>
159 lines
5.2 KiB
Go
159 lines
5.2 KiB
Go
// Copyright 2015 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 (
|
|
"fmt"
|
|
"math"
|
|
"testing"
|
|
|
|
"github.com/stretchr/testify/require"
|
|
|
|
"github.com/prometheus/prometheus/model/histogram"
|
|
"github.com/prometheus/prometheus/model/labels"
|
|
"github.com/prometheus/prometheus/model/value"
|
|
"github.com/prometheus/prometheus/tsdb/chunkenc"
|
|
"github.com/prometheus/prometheus/tsdb/tsdbutil"
|
|
)
|
|
|
|
func TestHistogramStatsDecoding(t *testing.T) {
|
|
cases := []struct {
|
|
name string
|
|
histograms []*histogram.Histogram
|
|
expectedHints []histogram.CounterResetHint
|
|
}{
|
|
{
|
|
name: "unknown counter reset triggers detection",
|
|
histograms: []*histogram.Histogram{
|
|
tsdbutil.GenerateTestHistogramWithHint(0, histogram.NotCounterReset),
|
|
tsdbutil.GenerateTestHistogramWithHint(1, histogram.UnknownCounterReset),
|
|
tsdbutil.GenerateTestHistogramWithHint(2, histogram.CounterReset),
|
|
tsdbutil.GenerateTestHistogramWithHint(2, histogram.UnknownCounterReset),
|
|
},
|
|
expectedHints: []histogram.CounterResetHint{
|
|
histogram.NotCounterReset,
|
|
histogram.NotCounterReset,
|
|
histogram.CounterReset,
|
|
histogram.NotCounterReset,
|
|
},
|
|
},
|
|
{
|
|
name: "stale sample before unknown reset hint",
|
|
histograms: []*histogram.Histogram{
|
|
tsdbutil.GenerateTestHistogramWithHint(0, histogram.NotCounterReset),
|
|
tsdbutil.GenerateTestHistogramWithHint(1, histogram.UnknownCounterReset),
|
|
{Sum: math.Float64frombits(value.StaleNaN)},
|
|
tsdbutil.GenerateTestHistogramWithHint(1, histogram.UnknownCounterReset),
|
|
},
|
|
expectedHints: []histogram.CounterResetHint{
|
|
histogram.NotCounterReset,
|
|
histogram.NotCounterReset,
|
|
histogram.UnknownCounterReset,
|
|
histogram.NotCounterReset,
|
|
},
|
|
},
|
|
}
|
|
|
|
for _, tc := range cases {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
t.Run("histogram_stats", func(t *testing.T) {
|
|
decodedStats := make([]*histogram.Histogram, 0)
|
|
statsIterator := NewHistogramStatsIterator(newHistogramSeries(tc.histograms).Iterator(nil))
|
|
for statsIterator.Next() != chunkenc.ValNone {
|
|
_, h := statsIterator.AtHistogram(nil)
|
|
decodedStats = append(decodedStats, h)
|
|
}
|
|
for i := 0; i < len(tc.histograms); i++ {
|
|
require.Equal(t, tc.expectedHints[i], decodedStats[i].CounterResetHint, fmt.Sprintf("mismatch in counter reset hint for histogram %d", i))
|
|
h := tc.histograms[i]
|
|
if value.IsStaleNaN(h.Sum) {
|
|
require.True(t, value.IsStaleNaN(decodedStats[i].Sum))
|
|
require.Equal(t, uint64(0), decodedStats[i].Count)
|
|
} else {
|
|
require.Equal(t, tc.histograms[i].Count, decodedStats[i].Count)
|
|
require.Equal(t, tc.histograms[i].Sum, decodedStats[i].Sum)
|
|
}
|
|
}
|
|
})
|
|
t.Run("float_histogram_stats", func(t *testing.T) {
|
|
decodedStats := make([]*histogram.FloatHistogram, 0)
|
|
statsIterator := NewHistogramStatsIterator(newHistogramSeries(tc.histograms).Iterator(nil))
|
|
for statsIterator.Next() != chunkenc.ValNone {
|
|
_, h := statsIterator.AtFloatHistogram(nil)
|
|
decodedStats = append(decodedStats, h)
|
|
}
|
|
for i := 0; i < len(tc.histograms); i++ {
|
|
require.Equal(t, tc.expectedHints[i], decodedStats[i].CounterResetHint)
|
|
fh := tc.histograms[i].ToFloat(nil)
|
|
if value.IsStaleNaN(fh.Sum) {
|
|
require.True(t, value.IsStaleNaN(decodedStats[i].Sum))
|
|
require.Equal(t, float64(0), decodedStats[i].Count)
|
|
} else {
|
|
require.Equal(t, fh.Count, decodedStats[i].Count)
|
|
require.Equal(t, fh.Sum, decodedStats[i].Sum)
|
|
}
|
|
}
|
|
})
|
|
})
|
|
}
|
|
}
|
|
|
|
type histogramSeries struct {
|
|
histograms []*histogram.Histogram
|
|
}
|
|
|
|
func newHistogramSeries(histograms []*histogram.Histogram) *histogramSeries {
|
|
return &histogramSeries{
|
|
histograms: histograms,
|
|
}
|
|
}
|
|
|
|
func (m histogramSeries) Labels() labels.Labels { return labels.EmptyLabels() }
|
|
|
|
func (m histogramSeries) Iterator(_ chunkenc.Iterator) chunkenc.Iterator {
|
|
return &histogramIterator{
|
|
i: -1,
|
|
histograms: m.histograms,
|
|
}
|
|
}
|
|
|
|
type histogramIterator struct {
|
|
i int
|
|
histograms []*histogram.Histogram
|
|
}
|
|
|
|
func (h *histogramIterator) Next() chunkenc.ValueType {
|
|
h.i++
|
|
if h.i < len(h.histograms) {
|
|
return chunkenc.ValHistogram
|
|
}
|
|
return chunkenc.ValNone
|
|
}
|
|
|
|
func (h *histogramIterator) Seek(t int64) chunkenc.ValueType { panic("not implemented") }
|
|
|
|
func (h *histogramIterator) At() (int64, float64) { panic("not implemented") }
|
|
|
|
func (h *histogramIterator) AtHistogram(_ *histogram.Histogram) (int64, *histogram.Histogram) {
|
|
return 0, h.histograms[h.i]
|
|
}
|
|
|
|
func (h *histogramIterator) AtFloatHistogram(_ *histogram.FloatHistogram) (int64, *histogram.FloatHistogram) {
|
|
return 0, h.histograms[h.i].ToFloat(nil)
|
|
}
|
|
|
|
func (h *histogramIterator) AtT() int64 { return 0 }
|
|
|
|
func (h *histogramIterator) Err() error { return nil }
|