Add duplicate-labelset check for range/instant vectors (#4589)

Signed-off-by: Harsh Agarwal <cs15btech11019@iith.ac.in>
This commit is contained in:
Harsh Agarwal 2018-09-18 15:16:13 +05:30 committed by Brian Brazil
parent cfbd72b4f1
commit 18a9a390b5
6 changed files with 76 additions and 23 deletions

View file

@ -720,6 +720,9 @@ func (ev *evaluator) rangeEval(f func([]Value, *EvalNodeHelper) Vector, exprs ..
// Make the function call.
enh.ts = ts
result := f(args, enh)
if result.ContainsSameLabelset() {
ev.errorf("vector cannot contain metrics with the same labelset")
}
enh.out = result[:0] // Reuse result vector.
// If this could be an instant query, shortcut so as not to change sort order.
if ev.endTimestamp == ev.startTimestamp {
@ -883,6 +886,10 @@ func (ev *evaluator) eval(expr Expr) Value {
mat = append(mat, ss)
}
}
if mat.ContainsSameLabelset() {
ev.errorf("vector cannot contain metrics with the same labelset")
}
putPointSlice(points)
return mat
@ -898,6 +905,9 @@ func (ev *evaluator) eval(expr Expr) Value {
mat[i].Points[j].V = -mat[i].Points[j].V
}
}
if mat.ContainsSameLabelset() {
ev.errorf("vector cannot contain metrics with the same labelset")
}
}
return mat

View file

@ -734,7 +734,6 @@ func funcLabelReplace(vals []Value, args Expressions, enh *EvalNodeHelper) Vecto
enh.dmn = make(map[uint64]labels.Labels, len(enh.out))
}
outSet := make(map[uint64]struct{}, len(vector))
for _, el := range vector {
h := el.Metric.Hash()
var outMetric labels.Labels
@ -759,17 +758,10 @@ func funcLabelReplace(vals []Value, args Expressions, enh *EvalNodeHelper) Vecto
}
}
outHash := outMetric.Hash()
if _, ok := outSet[outHash]; ok {
panic(fmt.Errorf("duplicated label set in output of label_replace(): %s", el.Metric))
} else {
enh.out = append(enh.out,
Sample{
Metric: outMetric,
Point: Point{V: el.Point.V},
})
outSet[outHash] = struct{}{}
}
enh.out = append(enh.out, Sample{
Metric: outMetric,
Point: Point{V: el.Point.V},
})
}
return enh.out
}
@ -808,7 +800,6 @@ func funcLabelJoin(vals []Value, args Expressions, enh *EvalNodeHelper) Vector {
panic(fmt.Errorf("invalid destination label name in label_join(): %s", dst))
}
outSet := make(map[uint64]struct{}, len(vector))
srcVals := make([]string, len(srcLabels))
for _, el := range vector {
h := el.Metric.Hash()
@ -833,17 +824,11 @@ func funcLabelJoin(vals []Value, args Expressions, enh *EvalNodeHelper) Vector {
outMetric = lb.Labels()
enh.dmn[h] = outMetric
}
outHash := outMetric.Hash()
if _, exists := outSet[outHash]; exists {
panic(fmt.Errorf("duplicated label set in output of label_join(): %s", el.Metric))
} else {
enh.out = append(enh.out, Sample{
Metric: outMetric,
Point: Point{V: el.Point.V},
})
outSet[outHash] = struct{}{}
}
enh.out = append(enh.out, Sample{
Metric: outMetric,
Point: Point{V: el.Point.V},
})
}
return enh.out
}

View file

@ -514,3 +514,11 @@ eval instant at 0m days_in_month(vector(1454284800))
eval instant at 0m days_in_month(vector(1485907200))
{} 28
clear
# Test duplicate labelset in promql output.
load 5m
testmetric1{src="a",dst="b"} 0
testmetric2{src="a",dst="b"} 1
eval_fail instant at 0m changes({__name__=~'testmetric1|testmetric2'}[5m])

View file

@ -357,3 +357,12 @@ load 1h
eval instant at 0h testmetric
testmetric{aa="bb"} 1
testmetric{a="abb"} 2
clear
# Test duplicate labelset in promql output.
load 5m
testmetric1{src="a",dst="b"} 0
testmetric2{src="a",dst="b"} 1
eval_fail instant at 0m ceil({__name__=~'testmetric1|testmetric2'})

View file

@ -367,3 +367,12 @@ eval instant at 5m metricA + ignoring() metricB
eval instant at 5m metricA + metricB
{baz="meh"} 7
clear
# Test duplicate labelset in promql output.
load 5m
testmetric1{src="a",dst="b"} 0
testmetric2{src="a",dst="b"} 1
eval_fail instant at 0m -{__name__=~'testmetric1|testmetric2'}

View file

@ -140,6 +140,22 @@ func (vec Vector) String() string {
return strings.Join(entries, "\n")
}
// ContainsSameLabelset checks if a vector has samples with the same labelset
// Such a behaviour is semantically undefined
// https://github.com/prometheus/prometheus/issues/4562
func (vec Vector) ContainsSameLabelset() bool {
l := make(map[uint64]struct{}, len(vec))
for _, s := range vec {
hash := s.Metric.Hash()
if _, ok := l[hash]; ok {
return true
} else {
l[hash] = struct{}{}
}
}
return false
}
// Matrix is a slice of Seriess that implements sort.Interface and
// has a String method.
type Matrix []Series
@ -159,6 +175,22 @@ func (m Matrix) Len() int { return len(m) }
func (m Matrix) Less(i, j int) bool { return labels.Compare(m[i].Metric, m[j].Metric) < 0 }
func (m Matrix) Swap(i, j int) { m[i], m[j] = m[j], m[i] }
// ContainsSameLabelset checks if a matrix has samples with the same labelset
// Such a behaviour is semantically undefined
// https://github.com/prometheus/prometheus/issues/4562
func (m Matrix) ContainsSameLabelset() bool {
l := make(map[uint64]struct{}, len(m))
for _, ss := range m {
hash := ss.Metric.Hash()
if _, ok := l[hash]; ok {
return true
} else {
l[hash] = struct{}{}
}
}
return false
}
// Result holds the resulting value of an execution or an error
// if any occurred.
type Result struct {