mirror of
https://github.com/prometheus/prometheus.git
synced 2025-03-05 20:59:13 -08:00
tsdb: error on series with duplicate labels (#6664)
Signed-off-by: Julien Pivotto <roidelapluie@inuits.eu>
This commit is contained in:
parent
08d6ddc7f2
commit
46d18112a3
|
@ -200,6 +200,20 @@ func (ls Labels) Has(name string) bool {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// HasDuplicateLabels returns whether ls has duplicate label names.
|
||||||
|
// It assumes that the labelset is sorted.
|
||||||
|
func (ls Labels) HasDuplicateLabelNames() (string, bool) {
|
||||||
|
for i, l := range ls {
|
||||||
|
if i == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if l.Name == ls[i-1].Name {
|
||||||
|
return l.Name, true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return "", false
|
||||||
|
}
|
||||||
|
|
||||||
// WithoutEmpty returns the labelset without empty labels.
|
// WithoutEmpty returns the labelset without empty labels.
|
||||||
// May return the same labelset.
|
// May return the same labelset.
|
||||||
func (ls Labels) WithoutEmpty() Labels {
|
func (ls Labels) WithoutEmpty() Labels {
|
||||||
|
|
|
@ -149,3 +149,29 @@ func TestLabels_MatchLabels(t *testing.T) {
|
||||||
testutil.Equals(t, test.expected, got, "unexpected labelset for test case %d", i)
|
testutil.Equals(t, test.expected, got, "unexpected labelset for test case %d", i)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestLabels_HasDuplicateLabelNames(t *testing.T) {
|
||||||
|
cases := []struct {
|
||||||
|
Input Labels
|
||||||
|
Duplicate bool
|
||||||
|
LabelName string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
Input: FromMap(map[string]string{"__name__": "up", "hostname": "localhost"}),
|
||||||
|
Duplicate: false,
|
||||||
|
}, {
|
||||||
|
Input: append(
|
||||||
|
FromMap(map[string]string{"__name__": "up", "hostname": "localhost"}),
|
||||||
|
FromMap(map[string]string{"hostname": "127.0.0.1"})...,
|
||||||
|
),
|
||||||
|
Duplicate: true,
|
||||||
|
LabelName: "hostname",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, c := range cases {
|
||||||
|
l, d := c.Input.HasDuplicateLabelNames()
|
||||||
|
testutil.Equals(t, c.Duplicate, d, "test %d: incorrect duplicate bool", i)
|
||||||
|
testutil.Equals(t, c.LabelName, l, "test %d: incorrect label name", i)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -917,7 +917,6 @@ func TestScrapeLoopCacheMemoryExhaustionProtection(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestScrapeLoopAppend(t *testing.T) {
|
func TestScrapeLoopAppend(t *testing.T) {
|
||||||
|
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
title string
|
title string
|
||||||
honorLabels bool
|
honorLabels bool
|
||||||
|
@ -1535,3 +1534,45 @@ func TestScrapeLoop_DiscardTimestamps(t *testing.T) {
|
||||||
}
|
}
|
||||||
testutil.Equals(t, want, capp.result, "Appended samples not as expected")
|
testutil.Equals(t, want, capp.result, "Appended samples not as expected")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestScrapeLoopDiscardDuplicateLabels(t *testing.T) {
|
||||||
|
s := teststorage.New(t)
|
||||||
|
defer s.Close()
|
||||||
|
|
||||||
|
app, err := s.Appender()
|
||||||
|
testutil.Ok(t, err)
|
||||||
|
|
||||||
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
|
sl := newScrapeLoop(ctx,
|
||||||
|
&testScraper{},
|
||||||
|
nil, nil,
|
||||||
|
nopMutator,
|
||||||
|
nopMutator,
|
||||||
|
func() storage.Appender { return app },
|
||||||
|
nil,
|
||||||
|
0,
|
||||||
|
true,
|
||||||
|
)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
// We add a good and a bad metric to check that both are discarded.
|
||||||
|
_, _, _, err = sl.append([]byte("test_metric{le=\"500\"} 1\ntest_metric{le=\"600\",le=\"700\"} 1\n"), "", time.Time{})
|
||||||
|
testutil.NotOk(t, err)
|
||||||
|
|
||||||
|
q, err := s.Querier(ctx, time.Time{}.UnixNano(), 0)
|
||||||
|
testutil.Ok(t, err)
|
||||||
|
series, _, err := q.Select(&storage.SelectParams{}, labels.MustNewMatcher(labels.MatchRegexp, "__name__", ".*"))
|
||||||
|
testutil.Ok(t, err)
|
||||||
|
testutil.Equals(t, false, series.Next(), "series found in tsdb")
|
||||||
|
|
||||||
|
// We add a good metric to check that it is recorded.
|
||||||
|
_, _, _, err = sl.append([]byte("test_metric{le=\"500\"} 1\n"), "", time.Time{})
|
||||||
|
testutil.Ok(t, err)
|
||||||
|
|
||||||
|
q, err = s.Querier(ctx, time.Time{}.UnixNano(), 0)
|
||||||
|
testutil.Ok(t, err)
|
||||||
|
series, _, err = q.Select(&storage.SelectParams{}, labels.MustNewMatcher(labels.MatchEqual, "le", "500"))
|
||||||
|
testutil.Ok(t, err)
|
||||||
|
testutil.Equals(t, true, series.Next(), "series not found in tsdb")
|
||||||
|
testutil.Equals(t, false, series.Next(), "more than one series found in tsdb")
|
||||||
|
}
|
||||||
|
|
|
@ -53,6 +53,10 @@ var (
|
||||||
// writable time range.
|
// writable time range.
|
||||||
ErrOutOfBounds = errors.New("out of bounds")
|
ErrOutOfBounds = errors.New("out of bounds")
|
||||||
|
|
||||||
|
// ErrInvalidSample is returned if an appended sample is not valid and can't
|
||||||
|
// be ingested.
|
||||||
|
ErrInvalidSample = errors.New("invalid sample")
|
||||||
|
|
||||||
// emptyTombstoneReader is a no-op Tombstone Reader.
|
// emptyTombstoneReader is a no-op Tombstone Reader.
|
||||||
// This is used by head to satisfy the Tombstones() function call.
|
// This is used by head to satisfy the Tombstones() function call.
|
||||||
emptyTombstoneReader = tombstones.NewMemTombstones()
|
emptyTombstoneReader = tombstones.NewMemTombstones()
|
||||||
|
@ -921,6 +925,10 @@ func (a *headAppender) Add(lset labels.Labels, t int64, v float64) (uint64, erro
|
||||||
// Ensure no empty labels have gotten through.
|
// Ensure no empty labels have gotten through.
|
||||||
lset = lset.WithoutEmpty()
|
lset = lset.WithoutEmpty()
|
||||||
|
|
||||||
|
if l, dup := lset.HasDuplicateLabelNames(); dup {
|
||||||
|
return 0, errors.Wrap(ErrInvalidSample, fmt.Sprintf(`label name "%s" is not unique`, l))
|
||||||
|
}
|
||||||
|
|
||||||
s, created := a.head.getOrCreate(lset.Hash(), lset)
|
s, created := a.head.getOrCreate(lset.Hash(), lset)
|
||||||
if created {
|
if created {
|
||||||
a.series = append(a.series, record.RefSeries{
|
a.series = append(a.series, record.RefSeries{
|
||||||
|
|
|
@ -1255,7 +1255,7 @@ func TestWalRepair_DecodingError(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestNewWalSegmentOnTruncate(t *testing.T) {
|
func TestNewWalSegmentOnTruncate(t *testing.T) {
|
||||||
dir, err := ioutil.TempDir("", "test_wal_segements")
|
dir, err := ioutil.TempDir("", "test_wal_segments")
|
||||||
testutil.Ok(t, err)
|
testutil.Ok(t, err)
|
||||||
defer func() {
|
defer func() {
|
||||||
testutil.Ok(t, os.RemoveAll(dir))
|
testutil.Ok(t, os.RemoveAll(dir))
|
||||||
|
@ -1290,3 +1290,28 @@ func TestNewWalSegmentOnTruncate(t *testing.T) {
|
||||||
testutil.Ok(t, err)
|
testutil.Ok(t, err)
|
||||||
testutil.Equals(t, 2, last)
|
testutil.Equals(t, 2, last)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestAddDuplicateLabelName(t *testing.T) {
|
||||||
|
dir, err := ioutil.TempDir("", "test_duplicate_label_name")
|
||||||
|
testutil.Ok(t, err)
|
||||||
|
defer func() {
|
||||||
|
testutil.Ok(t, os.RemoveAll(dir))
|
||||||
|
}()
|
||||||
|
wlog, err := wal.NewSize(nil, nil, dir, 32768, false)
|
||||||
|
testutil.Ok(t, err)
|
||||||
|
|
||||||
|
h, err := NewHead(nil, nil, wlog, 1000)
|
||||||
|
testutil.Ok(t, err)
|
||||||
|
defer h.Close()
|
||||||
|
|
||||||
|
add := func(labels labels.Labels, labelName string) {
|
||||||
|
app := h.Appender()
|
||||||
|
_, err = app.Add(labels, 0, 0)
|
||||||
|
testutil.NotOk(t, err)
|
||||||
|
testutil.Equals(t, fmt.Sprintf(`label name "%s" is not unique: invalid sample`, labelName), err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
add(labels.Labels{{Name: "a", Value: "c"}, {Name: "a", Value: "b"}}, "a")
|
||||||
|
add(labels.Labels{{Name: "a", Value: "c"}, {Name: "a", Value: "c"}}, "a")
|
||||||
|
add(labels.Labels{{Name: "__name__", Value: "up"}, {Name: "job", Value: "prometheus"}, {Name: "le", Value: "500"}, {Name: "le", Value: "400"}, {Name: "unit", Value: "s"}}, "le")
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue