mirror of
https://github.com/prometheus/prometheus
synced 2026-04-21 23:21:44 +08:00
fix(teststorage/appender.go): TODO and Sample staleness check (#17905)
Some checks failed
buf.build / lint and publish (push) Has been cancelled
CI / Go tests (push) Has been cancelled
CI / More Go tests (push) Has been cancelled
CI / Go tests with previous Go version (push) Has been cancelled
CI / UI tests (push) Has been cancelled
CI / Go tests on Windows (push) Has been cancelled
CI / Mixins tests (push) Has been cancelled
CI / Build Prometheus for common architectures (0) (push) Has been cancelled
CI / Build Prometheus for common architectures (1) (push) Has been cancelled
CI / Build Prometheus for common architectures (2) (push) Has been cancelled
CI / Build Prometheus for all architectures (0) (push) Has been cancelled
CI / Build Prometheus for all architectures (1) (push) Has been cancelled
CI / Build Prometheus for all architectures (10) (push) Has been cancelled
CI / Build Prometheus for all architectures (11) (push) Has been cancelled
CI / Build Prometheus for all architectures (2) (push) Has been cancelled
CI / Build Prometheus for all architectures (3) (push) Has been cancelled
CI / Build Prometheus for all architectures (4) (push) Has been cancelled
CI / Build Prometheus for all architectures (5) (push) Has been cancelled
CI / Build Prometheus for all architectures (6) (push) Has been cancelled
CI / Build Prometheus for all architectures (7) (push) Has been cancelled
CI / Build Prometheus for all architectures (8) (push) Has been cancelled
CI / Build Prometheus for all architectures (9) (push) Has been cancelled
CI / Report status of build Prometheus for all architectures (push) Has been cancelled
CI / Check generated parser (push) Has been cancelled
CI / golangci-lint (push) Has been cancelled
CI / fuzzing (push) Has been cancelled
CI / codeql (push) Has been cancelled
CI / Publish main branch artifacts (push) Has been cancelled
CI / Publish release artefacts (push) Has been cancelled
CI / Publish UI on npm Registry (push) Has been cancelled
Scorecards supply-chain security / Scorecards analysis (push) Has been cancelled
Stale Check / stale (push) Has been cancelled
Lock Threads / action (push) Has been cancelled
Some checks failed
buf.build / lint and publish (push) Has been cancelled
CI / Go tests (push) Has been cancelled
CI / More Go tests (push) Has been cancelled
CI / Go tests with previous Go version (push) Has been cancelled
CI / UI tests (push) Has been cancelled
CI / Go tests on Windows (push) Has been cancelled
CI / Mixins tests (push) Has been cancelled
CI / Build Prometheus for common architectures (0) (push) Has been cancelled
CI / Build Prometheus for common architectures (1) (push) Has been cancelled
CI / Build Prometheus for common architectures (2) (push) Has been cancelled
CI / Build Prometheus for all architectures (0) (push) Has been cancelled
CI / Build Prometheus for all architectures (1) (push) Has been cancelled
CI / Build Prometheus for all architectures (10) (push) Has been cancelled
CI / Build Prometheus for all architectures (11) (push) Has been cancelled
CI / Build Prometheus for all architectures (2) (push) Has been cancelled
CI / Build Prometheus for all architectures (3) (push) Has been cancelled
CI / Build Prometheus for all architectures (4) (push) Has been cancelled
CI / Build Prometheus for all architectures (5) (push) Has been cancelled
CI / Build Prometheus for all architectures (6) (push) Has been cancelled
CI / Build Prometheus for all architectures (7) (push) Has been cancelled
CI / Build Prometheus for all architectures (8) (push) Has been cancelled
CI / Build Prometheus for all architectures (9) (push) Has been cancelled
CI / Report status of build Prometheus for all architectures (push) Has been cancelled
CI / Check generated parser (push) Has been cancelled
CI / golangci-lint (push) Has been cancelled
CI / fuzzing (push) Has been cancelled
CI / codeql (push) Has been cancelled
CI / Publish main branch artifacts (push) Has been cancelled
CI / Publish release artefacts (push) Has been cancelled
CI / Publish UI on npm Registry (push) Has been cancelled
Scorecards supply-chain security / Scorecards analysis (push) Has been cancelled
Stale Check / stale (push) Has been cancelled
Lock Threads / action (push) Has been cancelled
* fix(teststorage/appender.go): TODO and Sample staleness check Allow different order of consecutive stale samples between the expected and actual array for RequireEqual and RequireNotEqual by trying to swap the expected side until it matches. Also fix the definition of stale sample in the test, it's not only float, but defined for native histograms as well. Signed-off-by: György Krajcsovits <gyorgy.krajcsovits@grafana.com> * add unit tests Signed-off-by: György Krajcsovits <gyorgy.krajcsovits@grafana.com> --------- Signed-off-by: György Krajcsovits <gyorgy.krajcsovits@grafana.com>
This commit is contained in:
committed by
GitHub
parent
04a3ef75f2
commit
21fb899c32
@@ -24,7 +24,6 @@ import (
|
||||
"testing"
|
||||
|
||||
"github.com/google/go-cmp/cmp"
|
||||
"github.com/google/go-cmp/cmp/cmpopts"
|
||||
"github.com/prometheus/common/model"
|
||||
"github.com/stretchr/testify/require"
|
||||
"go.uber.org/atomic"
|
||||
@@ -97,37 +96,32 @@ func (s Sample) Equals(other Sample) bool {
|
||||
slices.EqualFunc(s.ES, other.ES, exemplar.Exemplar.Equals)
|
||||
}
|
||||
|
||||
var (
|
||||
sampleComparer = cmp.Comparer(func(a, b Sample) bool {
|
||||
return a.Equals(b)
|
||||
})
|
||||
byLabelSort = cmpopts.SortSlices(func(a, b Sample) int {
|
||||
return labels.Compare(a.L, b.L)
|
||||
})
|
||||
)
|
||||
|
||||
func includeStaleNaNs(s []Sample) bool {
|
||||
for _, e := range s {
|
||||
if value.IsStaleNaN(e.V) {
|
||||
return true
|
||||
}
|
||||
// IsStale returns whether the sample represents a stale sample, according to
|
||||
// https://prometheus.io/docs/specs/native_histograms/#staleness-markers.
|
||||
func (s Sample) IsStale() bool {
|
||||
switch {
|
||||
case s.FH != nil:
|
||||
return value.IsStaleNaN(s.FH.Sum)
|
||||
case s.H != nil:
|
||||
return value.IsStaleNaN(s.H.Sum)
|
||||
default:
|
||||
return value.IsStaleNaN(s.V)
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
var sampleComparer = cmp.Comparer(func(a, b Sample) bool {
|
||||
return a.Equals(b)
|
||||
})
|
||||
|
||||
// RequireEqual is a special require equal that correctly compare Prometheus structures.
|
||||
//
|
||||
// In comparison to testutil.RequireEqual, this function adds special logic for comparing []Samples.
|
||||
//
|
||||
// It also ignores ordering when expected slice contains at least one StaleNaN. This is because the
|
||||
// scrape StaleNan samples are generated by iterating over a map, thus expectedly different.
|
||||
//
|
||||
// TODO(bwplotka): We should likely reorder only within a group of sequential NaNs or only in scrape package.
|
||||
// It also ignores ordering between consecutive stale samples to avoid false
|
||||
// negatives due to map iteration order in staleness tracking.
|
||||
func RequireEqual(t testing.TB, expected, got []Sample, msgAndArgs ...any) {
|
||||
opts := []cmp.Option{sampleComparer}
|
||||
if includeStaleNaNs(expected) {
|
||||
opts = append(opts, byLabelSort)
|
||||
}
|
||||
expected = reorderExpectedForStaleness(expected, got)
|
||||
testutil.RequireEqualWithOptions(t, expected, got, opts, msgAndArgs...)
|
||||
}
|
||||
|
||||
@@ -136,9 +130,7 @@ func RequireNotEqual(t testing.TB, expected, got []Sample, msgAndArgs ...any) {
|
||||
t.Helper()
|
||||
|
||||
opts := []cmp.Option{cmp.Comparer(labels.Equal), sampleComparer}
|
||||
if includeStaleNaNs(expected) {
|
||||
opts = append(opts, byLabelSort)
|
||||
}
|
||||
expected = reorderExpectedForStaleness(expected, got)
|
||||
if !cmp.Equal(expected, got, opts...) {
|
||||
return
|
||||
}
|
||||
@@ -147,6 +139,45 @@ func RequireNotEqual(t testing.TB, expected, got []Sample, msgAndArgs ...any) {
|
||||
"b: %s", expected, got), msgAndArgs...)
|
||||
}
|
||||
|
||||
func reorderExpectedForStaleness(expected, got []Sample) []Sample {
|
||||
if len(expected) != len(got) || !includeStaleNaNs(expected) {
|
||||
return expected
|
||||
}
|
||||
result := make([]Sample, len(expected))
|
||||
copy(result, expected)
|
||||
|
||||
// Try to reorder only consecutive stale samples to avoid false negatives
|
||||
// due to map iteration order in staleness tracking.
|
||||
for i := range result {
|
||||
if !result[i].IsStale() {
|
||||
continue
|
||||
}
|
||||
if result[i].Equals(got[i]) {
|
||||
continue
|
||||
}
|
||||
for j := i + 1; j < len(result); j++ {
|
||||
if !result[j].IsStale() {
|
||||
break
|
||||
}
|
||||
if result[j].Equals(got[i]) {
|
||||
// Swap.
|
||||
result[i], result[j] = result[j], result[i]
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func includeStaleNaNs(s []Sample) bool {
|
||||
for _, e := range s {
|
||||
if e.IsStale() {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// Appendable is a storage.Appendable mock.
|
||||
// It allows recording all samples that were added through the appender and injecting errors.
|
||||
// Appendable will panic if more than one Appender is open.
|
||||
|
||||
@@ -306,3 +306,108 @@ func TestConcurrentAppenderV2_ReturnsErrAppender(t *testing.T) {
|
||||
require.Error(t, app.Commit())
|
||||
require.Error(t, app.Rollback())
|
||||
}
|
||||
|
||||
func TestReorderExpectedForStaleness(t *testing.T) {
|
||||
testcases := []struct {
|
||||
name string
|
||||
inExpected []Sample
|
||||
inGot []Sample
|
||||
expected []Sample
|
||||
}{
|
||||
{
|
||||
name: "no staleness markers",
|
||||
inExpected: []Sample{
|
||||
{L: labels.FromStrings("a", "1"), T: 1, V: 1},
|
||||
{L: labels.FromStrings("a", "2"), T: 1, V: 2},
|
||||
},
|
||||
inGot: []Sample{
|
||||
{L: labels.FromStrings("a", "2"), T: 1, V: 2},
|
||||
{L: labels.FromStrings("a", "1"), T: 1, V: 1},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "with staleness markers",
|
||||
inExpected: []Sample{
|
||||
{L: labels.FromStrings("a", "1"), T: 1, V: 1},
|
||||
{L: labels.FromStrings("a", "2"), T: 2, V: 2},
|
||||
{L: labels.FromStrings("a", "3"), T: 3, V: math.Float64frombits(value.StaleNaN)},
|
||||
{L: labels.FromStrings("a", "4"), T: 4, V: math.Float64frombits(value.StaleNaN)},
|
||||
},
|
||||
inGot: []Sample{
|
||||
{L: labels.FromStrings("a", "1"), T: 1, V: 1},
|
||||
{L: labels.FromStrings("a", "2"), T: 2, V: 2},
|
||||
{L: labels.FromStrings("a", "3"), T: 3, V: math.Float64frombits(value.StaleNaN)},
|
||||
{L: labels.FromStrings("a", "4"), T: 4, V: math.Float64frombits(value.StaleNaN)},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "with staleness markers wrong order",
|
||||
inExpected: []Sample{
|
||||
{L: labels.FromStrings("a", "1"), T: 1, V: 1},
|
||||
{L: labels.FromStrings("a", "2"), T: 2, V: 2},
|
||||
{L: labels.FromStrings("a", "3"), T: 3, V: math.Float64frombits(value.StaleNaN)},
|
||||
{L: labels.FromStrings("a", "4"), T: 4, V: math.Float64frombits(value.StaleNaN)},
|
||||
},
|
||||
inGot: []Sample{
|
||||
{L: labels.FromStrings("a", "2"), T: 2, V: 2},
|
||||
{L: labels.FromStrings("a", "1"), T: 1, V: 1},
|
||||
{L: labels.FromStrings("a", "4"), T: 4, V: math.Float64frombits(value.StaleNaN)},
|
||||
{L: labels.FromStrings("a", "3"), T: 3, V: math.Float64frombits(value.StaleNaN)},
|
||||
},
|
||||
expected: []Sample{
|
||||
{L: labels.FromStrings("a", "1"), T: 1, V: 1},
|
||||
{L: labels.FromStrings("a", "2"), T: 2, V: 2},
|
||||
{L: labels.FromStrings("a", "4"), T: 4, V: math.Float64frombits(value.StaleNaN)},
|
||||
{L: labels.FromStrings("a", "3"), T: 3, V: math.Float64frombits(value.StaleNaN)},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "with staleness markers wrong order but not consecutive",
|
||||
inExpected: []Sample{
|
||||
{L: labels.FromStrings("a", "1"), T: 1, V: 1},
|
||||
{L: labels.FromStrings("a", "3"), T: 3, V: math.Float64frombits(value.StaleNaN)},
|
||||
{L: labels.FromStrings("a", "2"), T: 2, V: 2},
|
||||
{L: labels.FromStrings("a", "4"), T: 4, V: math.Float64frombits(value.StaleNaN)},
|
||||
},
|
||||
inGot: []Sample{
|
||||
{L: labels.FromStrings("a", "2"), T: 2, V: 2},
|
||||
{L: labels.FromStrings("a", "1"), T: 1, V: 1},
|
||||
{L: labels.FromStrings("a", "4"), T: 4, V: math.Float64frombits(value.StaleNaN)},
|
||||
{L: labels.FromStrings("a", "3"), T: 3, V: math.Float64frombits(value.StaleNaN)},
|
||||
},
|
||||
expected: []Sample{
|
||||
{L: labels.FromStrings("a", "1"), T: 1, V: 1},
|
||||
{L: labels.FromStrings("a", "3"), T: 3, V: math.Float64frombits(value.StaleNaN)},
|
||||
{L: labels.FromStrings("a", "2"), T: 2, V: 2},
|
||||
{L: labels.FromStrings("a", "4"), T: 4, V: math.Float64frombits(value.StaleNaN)},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tc := range testcases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
if tc.expected == nil {
|
||||
tc.expected = tc.inExpected
|
||||
}
|
||||
RequireEqual(t, tc.expected, reorderExpectedForStaleness(tc.inExpected, tc.inGot))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestSampleIsStale(t *testing.T) {
|
||||
s1 := Sample{V: 1}
|
||||
require.False(t, s1.IsStale())
|
||||
s2 := Sample{V: math.Float64frombits(value.StaleNaN)}
|
||||
require.True(t, s2.IsStale())
|
||||
h := tsdbutil.GenerateTestHistogram(0)
|
||||
h1 := Sample{V: math.Float64frombits(value.StaleNaN), H: h}
|
||||
require.False(t, h1.IsStale()) // Histogram takes precedence over V.
|
||||
h.Sum = math.Float64frombits(value.StaleNaN)
|
||||
h2 := Sample{V: 1, H: h}
|
||||
require.True(t, h2.IsStale())
|
||||
fh := tsdbutil.GenerateTestFloatHistogram(0)
|
||||
fh1 := Sample{V: math.Float64frombits(value.StaleNaN), H: h, FH: fh}
|
||||
require.False(t, fh1.IsStale()) // FloatHistogram takes precedence over all.
|
||||
fh.Sum = math.Float64frombits(value.StaleNaN)
|
||||
fh2 := Sample{V: 1, H: tsdbutil.GenerateTestHistogram(1), FH: fh}
|
||||
require.True(t, fh2.IsStale())
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user