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:
Brian Brazil 2016-04-21 11:45:06 +01:00
parent f5084ab1c5
commit 1d08c4fef0
9 changed files with 114 additions and 14 deletions

View file

@ -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

View file

@ -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.

View file

@ -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,

View file

@ -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"}},

View file

@ -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 {

View file

@ -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{

View file

@ -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)
} }

View file

@ -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`,
}, },

View file

@ -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