mirror of
https://github.com/prometheus/prometheus.git
synced 2024-11-09 23:24:05 -08:00
Add 'ignoring' as modifier for binops.
Where 'on' uses the given labels to match, 'ignoring' uses all other labels to match. group_left/right is not supported yet.
This commit is contained in:
parent
f5084ab1c5
commit
1d08c4fef0
|
@ -231,6 +231,9 @@ type VectorMatching struct {
|
||||||
// On contains the labels which define equality of a pair
|
// On contains the labels which define equality of a pair
|
||||||
// of elements from the vectors.
|
// of elements from the vectors.
|
||||||
On model.LabelNames
|
On model.LabelNames
|
||||||
|
// Ignoring excludes the given label names from matching,
|
||||||
|
// rather than only using them.
|
||||||
|
Ignoring bool
|
||||||
// Include contains additional labels that should be included in
|
// Include contains additional labels that should be included in
|
||||||
// the result from the side with the higher cardinality.
|
// the result from the side with the higher cardinality.
|
||||||
Include model.LabelNames
|
Include model.LabelNames
|
||||||
|
|
|
@ -728,7 +728,7 @@ func (ev *evaluator) vectorAnd(lhs, rhs vector, matching *VectorMatching) vector
|
||||||
if matching.Card != CardManyToMany {
|
if matching.Card != CardManyToMany {
|
||||||
panic("set operations must only use many-to-many matching")
|
panic("set operations must only use many-to-many matching")
|
||||||
}
|
}
|
||||||
sigf := signatureFunc(matching.On...)
|
sigf := signatureFunc(matching.Ignoring, matching.On...)
|
||||||
|
|
||||||
var result vector
|
var result vector
|
||||||
// The set of signatures for the right-hand side vector.
|
// The set of signatures for the right-hand side vector.
|
||||||
|
@ -751,7 +751,7 @@ func (ev *evaluator) vectorOr(lhs, rhs vector, matching *VectorMatching) vector
|
||||||
if matching.Card != CardManyToMany {
|
if matching.Card != CardManyToMany {
|
||||||
panic("set operations must only use many-to-many matching")
|
panic("set operations must only use many-to-many matching")
|
||||||
}
|
}
|
||||||
sigf := signatureFunc(matching.On...)
|
sigf := signatureFunc(matching.Ignoring, matching.On...)
|
||||||
|
|
||||||
var result vector
|
var result vector
|
||||||
leftSigs := map[uint64]struct{}{}
|
leftSigs := map[uint64]struct{}{}
|
||||||
|
@ -773,7 +773,7 @@ func (ev *evaluator) vectorUnless(lhs, rhs vector, matching *VectorMatching) vec
|
||||||
if matching.Card != CardManyToMany {
|
if matching.Card != CardManyToMany {
|
||||||
panic("set operations must only use many-to-many matching")
|
panic("set operations must only use many-to-many matching")
|
||||||
}
|
}
|
||||||
sigf := signatureFunc(matching.On...)
|
sigf := signatureFunc(matching.Ignoring, matching.On...)
|
||||||
|
|
||||||
rightSigs := map[uint64]struct{}{}
|
rightSigs := map[uint64]struct{}{}
|
||||||
for _, rs := range rhs {
|
for _, rs := range rhs {
|
||||||
|
@ -796,7 +796,7 @@ func (ev *evaluator) vectorBinop(op itemType, lhs, rhs vector, matching *VectorM
|
||||||
}
|
}
|
||||||
var (
|
var (
|
||||||
result = vector{}
|
result = vector{}
|
||||||
sigf = signatureFunc(matching.On...)
|
sigf = signatureFunc(matching.Ignoring, matching.On...)
|
||||||
resultLabels = append(matching.On, matching.Include...)
|
resultLabels = append(matching.On, matching.Include...)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -851,7 +851,7 @@ func (ev *evaluator) vectorBinop(op itemType, lhs, rhs vector, matching *VectorM
|
||||||
} else if !keep {
|
} else if !keep {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
metric := resultMetric(ls.Metric, op, resultLabels...)
|
metric := resultMetric(ls.Metric, op, matching.Ignoring, resultLabels...)
|
||||||
|
|
||||||
insertedSigs, exists := matchedSigs[sig]
|
insertedSigs, exists := matchedSigs[sig]
|
||||||
if matching.Card == CardOneToOne {
|
if matching.Card == CardOneToOne {
|
||||||
|
@ -883,12 +883,16 @@ func (ev *evaluator) vectorBinop(op itemType, lhs, rhs vector, matching *VectorM
|
||||||
}
|
}
|
||||||
|
|
||||||
// signatureFunc returns a function that calculates the signature for a metric
|
// signatureFunc returns a function that calculates the signature for a metric
|
||||||
// based on the provided labels.
|
// based on the provided labels. If ignoring, then the given labels are ignored instead.
|
||||||
func signatureFunc(labels ...model.LabelName) func(m metric.Metric) uint64 {
|
func signatureFunc(ignoring bool, labels ...model.LabelName) func(m metric.Metric) uint64 {
|
||||||
if len(labels) == 0 {
|
if len(labels) == 0 || ignoring {
|
||||||
return func(m metric.Metric) uint64 {
|
return func(m metric.Metric) uint64 {
|
||||||
m.Del(model.MetricNameLabel)
|
tmp := m.Metric.Clone()
|
||||||
return uint64(m.Metric.Fingerprint())
|
for _, l := range labels {
|
||||||
|
delete(tmp, l)
|
||||||
|
}
|
||||||
|
delete(tmp, model.MetricNameLabel)
|
||||||
|
return uint64(tmp.Fingerprint())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return func(m metric.Metric) uint64 {
|
return func(m metric.Metric) uint64 {
|
||||||
|
@ -898,11 +902,14 @@ func signatureFunc(labels ...model.LabelName) func(m metric.Metric) uint64 {
|
||||||
|
|
||||||
// resultMetric returns the metric for the given sample(s) based on the vector
|
// resultMetric returns the metric for the given sample(s) based on the vector
|
||||||
// binary operation and the matching options.
|
// binary operation and the matching options.
|
||||||
func resultMetric(met metric.Metric, op itemType, labels ...model.LabelName) metric.Metric {
|
func resultMetric(met metric.Metric, op itemType, ignoring bool, labels ...model.LabelName) metric.Metric {
|
||||||
if len(labels) == 0 {
|
if len(labels) == 0 || ignoring {
|
||||||
if shouldDropMetricName(op) {
|
if shouldDropMetricName(op) {
|
||||||
met.Del(model.MetricNameLabel)
|
met.Del(model.MetricNameLabel)
|
||||||
}
|
}
|
||||||
|
for _, l := range labels {
|
||||||
|
met.Del(l)
|
||||||
|
}
|
||||||
return met
|
return met
|
||||||
}
|
}
|
||||||
// As we definitely write, creating a new metric is the easiest solution.
|
// As we definitely write, creating a new metric is the easiest solution.
|
||||||
|
|
|
@ -172,6 +172,7 @@ const (
|
||||||
itemBy
|
itemBy
|
||||||
itemWithout
|
itemWithout
|
||||||
itemOn
|
itemOn
|
||||||
|
itemIgnoring
|
||||||
itemGroupLeft
|
itemGroupLeft
|
||||||
itemGroupRight
|
itemGroupRight
|
||||||
itemBool
|
itemBool
|
||||||
|
@ -209,6 +210,7 @@ var key = map[string]itemType{
|
||||||
"keeping_extra": itemKeepCommon,
|
"keeping_extra": itemKeepCommon,
|
||||||
"keep_common": itemKeepCommon,
|
"keep_common": itemKeepCommon,
|
||||||
"on": itemOn,
|
"on": itemOn,
|
||||||
|
"ignoring": itemIgnoring,
|
||||||
"group_left": itemGroupLeft,
|
"group_left": itemGroupLeft,
|
||||||
"group_right": itemGroupRight,
|
"group_right": itemGroupRight,
|
||||||
"bool": itemBool,
|
"bool": itemBool,
|
||||||
|
|
|
@ -278,6 +278,9 @@ var tests = []struct {
|
||||||
}, {
|
}, {
|
||||||
input: "on",
|
input: "on",
|
||||||
expected: []item{{itemOn, 0, "on"}},
|
expected: []item{{itemOn, 0, "on"}},
|
||||||
|
}, {
|
||||||
|
input: "ignoring",
|
||||||
|
expected: []item{{itemIgnoring, 0, "ignoring"}},
|
||||||
}, {
|
}, {
|
||||||
input: "group_left",
|
input: "group_left",
|
||||||
expected: []item{{itemGroupLeft, 0, "group_left"}},
|
expected: []item{{itemGroupLeft, 0, "group_left"}},
|
||||||
|
|
|
@ -462,7 +462,10 @@ func (p *parser) expr() Expr {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Parse ON clause.
|
// Parse ON clause.
|
||||||
if p.peek().typ == itemOn {
|
if p.peek().typ == itemOn || p.peek().typ == itemIgnoring {
|
||||||
|
if p.peek().typ == itemIgnoring {
|
||||||
|
vecMatching.Ignoring = true
|
||||||
|
}
|
||||||
p.next()
|
p.next()
|
||||||
vecMatching.On = p.labels()
|
vecMatching.On = p.labels()
|
||||||
|
|
||||||
|
@ -478,6 +481,10 @@ func (p *parser) expr() Expr {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if vecMatching.Ignoring && (vecMatching.Card == CardManyToOne || vecMatching.Card == CardOneToMany) {
|
||||||
|
p.errorf("IGNORING not permitted with many to one matching")
|
||||||
|
}
|
||||||
|
|
||||||
for _, ln := range vecMatching.On {
|
for _, ln := range vecMatching.On {
|
||||||
for _, ln2 := range vecMatching.Include {
|
for _, ln2 := range vecMatching.Include {
|
||||||
if ln == ln2 {
|
if ln == ln2 {
|
||||||
|
|
|
@ -250,6 +250,14 @@ var testExpr = []struct {
|
||||||
input: "1 offset 1d",
|
input: "1 offset 1d",
|
||||||
fail: true,
|
fail: true,
|
||||||
errMsg: "offset modifier must be preceded by an instant or range selector",
|
errMsg: "offset modifier must be preceded by an instant or range selector",
|
||||||
|
}, {
|
||||||
|
input: "a - on(b) ignoring(c) d",
|
||||||
|
fail: true,
|
||||||
|
errMsg: "parse error at char 11: no valid expression found",
|
||||||
|
}, {
|
||||||
|
input: "a - ignoring(b) group_left(c) d",
|
||||||
|
fail: true,
|
||||||
|
errMsg: "parse error at char 29: IGNORING not permitted with many to one matching",
|
||||||
},
|
},
|
||||||
// Vector binary operations.
|
// Vector binary operations.
|
||||||
{
|
{
|
||||||
|
@ -517,6 +525,28 @@ var testExpr = []struct {
|
||||||
On: model.LabelNames{"test", "blub"},
|
On: model.LabelNames{"test", "blub"},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
}, {
|
||||||
|
input: "foo and ignoring(test,blub) bar",
|
||||||
|
expected: &BinaryExpr{
|
||||||
|
Op: itemLAND,
|
||||||
|
LHS: &VectorSelector{
|
||||||
|
Name: "foo",
|
||||||
|
LabelMatchers: metric.LabelMatchers{
|
||||||
|
{Type: metric.Equal, Name: model.MetricNameLabel, Value: "foo"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
RHS: &VectorSelector{
|
||||||
|
Name: "bar",
|
||||||
|
LabelMatchers: metric.LabelMatchers{
|
||||||
|
{Type: metric.Equal, Name: model.MetricNameLabel, Value: "bar"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
VectorMatching: &VectorMatching{
|
||||||
|
Card: CardManyToMany,
|
||||||
|
On: model.LabelNames{"test", "blub"},
|
||||||
|
Ignoring: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
}, {
|
}, {
|
||||||
input: "foo unless on(bar) baz",
|
input: "foo unless on(bar) baz",
|
||||||
expected: &BinaryExpr{
|
expected: &BinaryExpr{
|
||||||
|
|
|
@ -160,7 +160,11 @@ func (node *BinaryExpr) String() string {
|
||||||
matching := ""
|
matching := ""
|
||||||
vm := node.VectorMatching
|
vm := node.VectorMatching
|
||||||
if vm != nil && len(vm.On) > 0 {
|
if vm != nil && len(vm.On) > 0 {
|
||||||
matching = fmt.Sprintf(" ON(%s)", vm.On)
|
if vm.Ignoring {
|
||||||
|
matching = fmt.Sprintf(" IGNORING(%s)", vm.On)
|
||||||
|
} else {
|
||||||
|
matching = fmt.Sprintf(" ON(%s)", vm.On)
|
||||||
|
}
|
||||||
if vm.Card == CardManyToOne {
|
if vm.Card == CardManyToOne {
|
||||||
matching += fmt.Sprintf(" GROUP_LEFT(%s)", vm.Include)
|
matching += fmt.Sprintf(" GROUP_LEFT(%s)", vm.Include)
|
||||||
}
|
}
|
||||||
|
|
|
@ -36,6 +36,12 @@ func TestExprString(t *testing.T) {
|
||||||
{
|
{
|
||||||
in: `sum(task:errors:rate10s{job="s"}) WITHOUT (instance)`,
|
in: `sum(task:errors:rate10s{job="s"}) WITHOUT (instance)`,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
in: `a - ON(b) c`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
in: `a - IGNORING(b) c`,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
in: `up > BOOL 0`,
|
in: `up > BOOL 0`,
|
||||||
},
|
},
|
||||||
|
|
38
promql/testdata/operators.test
vendored
38
promql/testdata/operators.test
vendored
|
@ -79,6 +79,14 @@ eval instant at 50m (http_requests{group="canary"} + 1) and on(instance) http_re
|
||||||
{group="canary", instance="0", job="api-server"} 301
|
{group="canary", instance="0", job="api-server"} 301
|
||||||
{group="canary", instance="0", job="app-server"} 701
|
{group="canary", instance="0", job="app-server"} 701
|
||||||
|
|
||||||
|
eval instant at 50m (http_requests{group="canary"} + 1) and ignoring(group) http_requests{instance="0", group="production"}
|
||||||
|
{group="canary", instance="0", job="api-server"} 301
|
||||||
|
{group="canary", instance="0", job="app-server"} 701
|
||||||
|
|
||||||
|
eval instant at 50m (http_requests{group="canary"} + 1) and ignoring(group, job) http_requests{instance="0", group="production"}
|
||||||
|
{group="canary", instance="0", job="api-server"} 301
|
||||||
|
{group="canary", instance="0", job="app-server"} 701
|
||||||
|
|
||||||
eval instant at 50m http_requests{group="canary"} or http_requests{group="production"}
|
eval instant at 50m http_requests{group="canary"} or http_requests{group="production"}
|
||||||
http_requests{group="canary", instance="0", job="api-server"} 300
|
http_requests{group="canary", instance="0", job="api-server"} 300
|
||||||
http_requests{group="canary", instance="0", job="app-server"} 700
|
http_requests{group="canary", instance="0", job="app-server"} 700
|
||||||
|
@ -109,6 +117,14 @@ eval instant at 50m (http_requests{group="canary"} + 1) or on(instance) (http_re
|
||||||
vector_matching_a{l="x"} 10
|
vector_matching_a{l="x"} 10
|
||||||
vector_matching_a{l="y"} 20
|
vector_matching_a{l="y"} 20
|
||||||
|
|
||||||
|
eval instant at 50m (http_requests{group="canary"} + 1) or ignoring(l, group, job) (http_requests or cpu_count or vector_matching_a)
|
||||||
|
{group="canary", instance="0", job="api-server"} 301
|
||||||
|
{group="canary", instance="0", job="app-server"} 701
|
||||||
|
{group="canary", instance="1", job="api-server"} 401
|
||||||
|
{group="canary", instance="1", job="app-server"} 801
|
||||||
|
vector_matching_a{l="x"} 10
|
||||||
|
vector_matching_a{l="y"} 20
|
||||||
|
|
||||||
eval instant at 50m http_requests{group="canary"} unless http_requests{instance="0"}
|
eval instant at 50m http_requests{group="canary"} unless http_requests{instance="0"}
|
||||||
http_requests{group="canary", instance="1", job="api-server"} 400
|
http_requests{group="canary", instance="1", job="api-server"} 400
|
||||||
http_requests{group="canary", instance="1", job="app-server"} 800
|
http_requests{group="canary", instance="1", job="app-server"} 800
|
||||||
|
@ -125,6 +141,18 @@ eval instant at 50m http_requests{group="canary"} / on(instance,job) http_reques
|
||||||
{instance="1", job="api-server"} 2
|
{instance="1", job="api-server"} 2
|
||||||
{instance="1", job="app-server"} 1.3333333333333333
|
{instance="1", job="app-server"} 1.3333333333333333
|
||||||
|
|
||||||
|
eval instant at 50m http_requests{group="canary"} unless ignoring(group, instance) http_requests{instance="0"}
|
||||||
|
|
||||||
|
eval instant at 50m http_requests{group="canary"} unless ignoring(group) http_requests{instance="0"}
|
||||||
|
http_requests{group="canary", instance="1", job="api-server"} 400
|
||||||
|
http_requests{group="canary", instance="1", job="app-server"} 800
|
||||||
|
|
||||||
|
eval instant at 50m http_requests{group="canary"} / ignoring(group) http_requests{group="production"}
|
||||||
|
{instance="0", job="api-server"} 3
|
||||||
|
{instance="0", job="app-server"} 1.4
|
||||||
|
{instance="1", job="api-server"} 2
|
||||||
|
{instance="1", job="app-server"} 1.3333333333333333
|
||||||
|
|
||||||
# https://github.com/prometheus/prometheus/issues/1489
|
# https://github.com/prometheus/prometheus/issues/1489
|
||||||
eval instant at 50m http_requests AND ON (dummy) vector(1)
|
eval instant at 50m http_requests AND ON (dummy) vector(1)
|
||||||
http_requests{group="canary", instance="0", job="api-server"} 300
|
http_requests{group="canary", instance="0", job="api-server"} 300
|
||||||
|
@ -136,6 +164,16 @@ eval instant at 50m http_requests AND ON (dummy) vector(1)
|
||||||
http_requests{group="production", instance="1", job="api-server"} 200
|
http_requests{group="production", instance="1", job="api-server"} 200
|
||||||
http_requests{group="production", instance="1", job="app-server"} 600
|
http_requests{group="production", instance="1", job="app-server"} 600
|
||||||
|
|
||||||
|
eval instant at 50m http_requests AND IGNORING (group, instance, job) vector(1)
|
||||||
|
http_requests{group="canary", instance="0", job="api-server"} 300
|
||||||
|
http_requests{group="canary", instance="0", job="app-server"} 700
|
||||||
|
http_requests{group="canary", instance="1", job="api-server"} 400
|
||||||
|
http_requests{group="canary", instance="1", job="app-server"} 800
|
||||||
|
http_requests{group="production", instance="0", job="api-server"} 100
|
||||||
|
http_requests{group="production", instance="0", job="app-server"} 500
|
||||||
|
http_requests{group="production", instance="1", job="api-server"} 200
|
||||||
|
http_requests{group="production", instance="1", job="app-server"} 600
|
||||||
|
|
||||||
|
|
||||||
# Comparisons.
|
# Comparisons.
|
||||||
eval instant at 50m SUM(http_requests) BY (job) > 1000
|
eval instant at 50m SUM(http_requests) BY (job) > 1000
|
||||||
|
|
Loading…
Reference in a new issue