diff --git a/storage/errors.go b/storage/errors.go new file mode 100644 index 000000000..eff70f678 --- /dev/null +++ b/storage/errors.go @@ -0,0 +1,48 @@ +// Copyright 2014 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 storage + +import "fmt" + +type errDuplicateSampleForTimestamp struct { + timestamp int64 + existing float64 + newValue float64 +} + +func NewDuplicateFloatErr(t int64, existing, newValue float64) error { + return errDuplicateSampleForTimestamp{ + timestamp: t, + existing: existing, + newValue: newValue, + } +} + +func (e errDuplicateSampleForTimestamp) Error() string { + if e.timestamp == 0 { + return "duplicate sample for timestamp" + } + return fmt.Sprintf("duplicate sample for timestamp %d; overrides not allowed: existing %g, new value %g", e.timestamp, e.existing, e.newValue) +} + +// Every errDuplicateSampleForTimestamp compares equal to the global ErrDuplicateSampleForTimestamp. +func (e errDuplicateSampleForTimestamp) Is(t error) bool { + if t == ErrDuplicateSampleForTimestamp { + return true + } + if v, ok := t.(errDuplicateSampleForTimestamp); ok { + return e == v + } + return false +} diff --git a/storage/interface.go b/storage/interface.go index 347e779b5..493c2d689 100644 --- a/storage/interface.go +++ b/storage/interface.go @@ -37,7 +37,7 @@ var ( // ErrTooOldSample is when out of order support is enabled but the sample is outside the time window allowed. ErrTooOldSample = errors.New("too old sample") // ErrDuplicateSampleForTimestamp is when the sample has same timestamp but different value. - ErrDuplicateSampleForTimestamp = errors.New("duplicate sample for timestamp") + ErrDuplicateSampleForTimestamp = errDuplicateSampleForTimestamp{} ErrOutOfOrderExemplar = errors.New("out of order exemplar") ErrDuplicateExemplar = errors.New("duplicate exemplar") ErrExemplarLabelLength = fmt.Errorf("label length for exemplar exceeds maximum of %d UTF-8 characters", exemplar.ExemplarMaxLabelSetLength) diff --git a/tsdb/db_test.go b/tsdb/db_test.go index 5965e5317..f0d672fad 100644 --- a/tsdb/db_test.go +++ b/tsdb/db_test.go @@ -503,7 +503,7 @@ func TestAmendHistogramDatapointCausesError(t *testing.T) { _, err = app.Append(0, labels.FromStrings("a", "b"), 0, 0) require.NoError(t, err) _, err = app.Append(0, labels.FromStrings("a", "b"), 0, 1) - require.Equal(t, storage.ErrDuplicateSampleForTimestamp, err) + require.ErrorIs(t, err, storage.ErrDuplicateSampleForTimestamp) require.NoError(t, app.Rollback()) h := histogram.Histogram{ @@ -579,7 +579,7 @@ func TestNonDuplicateNaNDatapointsCausesAmendError(t *testing.T) { app = db.Appender(ctx) _, err = app.Append(0, labels.FromStrings("a", "b"), 0, math.Float64frombits(0x7ff0000000000002)) - require.Equal(t, storage.ErrDuplicateSampleForTimestamp, err) + require.ErrorIs(t, err, storage.ErrDuplicateSampleForTimestamp) } func TestEmptyLabelsetCausesError(t *testing.T) { diff --git a/tsdb/head_append.go b/tsdb/head_append.go index 224f65314..62c3727e2 100644 --- a/tsdb/head_append.go +++ b/tsdb/head_append.go @@ -467,7 +467,7 @@ func (s *memSeries) appendable(t int64, v float64, headMaxt, minValidTime, oooTi // This only checks against the latest in-order sample. // The OOO headchunk has its own method to detect these duplicates. if math.Float64bits(s.lastValue) != math.Float64bits(v) { - return false, 0, storage.ErrDuplicateSampleForTimestamp + return false, 0, storage.NewDuplicateFloatErr(t, s.lastValue, v) } // Sample is identical (ts + value) with most current (highest ts) sample in sampleBuf. return false, 0, nil