Implement relative complement set operator "unless"
The `unless` set operator can be used to return all vector elements from the LHS which do not match the elements on the RHS. A use case is to return all metrics for nodes which do not have a specific role: node_load1 unless on(instance) chef_role{role="app"}
This commit is contained in:
parent
4c3dc25e35
commit
8cc86f25c0
|
@ -629,6 +629,8 @@ func (ev *evaluator) eval(expr Expr) model.Value {
|
|||
return ev.vectorAnd(lhs.(vector), rhs.(vector), e.VectorMatching)
|
||||
case itemLOR:
|
||||
return ev.vectorOr(lhs.(vector), rhs.(vector), e.VectorMatching)
|
||||
case itemLUnless:
|
||||
return ev.vectorUnless(lhs.(vector), rhs.(vector), e.VectorMatching)
|
||||
default:
|
||||
return ev.vectorBinop(e.Op, lhs.(vector), rhs.(vector), e.VectorMatching, e.ReturnBool)
|
||||
}
|
||||
|
@ -724,9 +726,8 @@ func (ev *evaluator) matrixSelector(node *MatrixSelector) matrix {
|
|||
|
||||
func (ev *evaluator) vectorAnd(lhs, rhs vector, matching *VectorMatching) vector {
|
||||
if matching.Card != CardManyToMany {
|
||||
panic("logical operations must always be many-to-many matching")
|
||||
panic("set operations must only use many-to-many matching")
|
||||
}
|
||||
// If no matching labels are specified, match by all labels.
|
||||
sigf := signatureFunc(matching.On...)
|
||||
|
||||
var result vector
|
||||
|
@ -748,7 +749,7 @@ func (ev *evaluator) vectorAnd(lhs, rhs vector, matching *VectorMatching) vector
|
|||
|
||||
func (ev *evaluator) vectorOr(lhs, rhs vector, matching *VectorMatching) vector {
|
||||
if matching.Card != CardManyToMany {
|
||||
panic("logical operations must always be many-to-many matching")
|
||||
panic("set operations must only use many-to-many matching")
|
||||
}
|
||||
sigf := signatureFunc(matching.On...)
|
||||
|
||||
|
@ -768,10 +769,30 @@ func (ev *evaluator) vectorOr(lhs, rhs vector, matching *VectorMatching) vector
|
|||
return result
|
||||
}
|
||||
|
||||
// vectorBinop evaluates a binary operation between two vector, excluding AND and OR.
|
||||
func (ev *evaluator) vectorUnless(lhs, rhs vector, matching *VectorMatching) vector {
|
||||
if matching.Card != CardManyToMany {
|
||||
panic("set operations must only use many-to-many matching")
|
||||
}
|
||||
sigf := signatureFunc(matching.On...)
|
||||
|
||||
rightSigs := map[uint64]struct{}{}
|
||||
for _, rs := range rhs {
|
||||
rightSigs[sigf(rs.Metric)] = struct{}{}
|
||||
}
|
||||
|
||||
var result vector
|
||||
for _, ls := range lhs {
|
||||
if _, ok := rightSigs[sigf(ls.Metric)]; !ok {
|
||||
result = append(result, ls)
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// vectorBinop evaluates a binary operation between two vectors, excluding set operators.
|
||||
func (ev *evaluator) vectorBinop(op itemType, lhs, rhs vector, matching *VectorMatching, returnBool bool) vector {
|
||||
if matching.Card == CardManyToMany {
|
||||
panic("many-to-many only allowed for AND and OR")
|
||||
panic("many-to-many only allowed for set operators")
|
||||
}
|
||||
var (
|
||||
result = vector{}
|
||||
|
|
|
@ -48,7 +48,7 @@ func (i item) String() string {
|
|||
return fmt.Sprintf("%q", i.val)
|
||||
}
|
||||
|
||||
// isOperator returns true if the item corresponds to a logical or arithmetic operator.
|
||||
// isOperator returns true if the item corresponds to a arithmetic or set operator.
|
||||
// Returns false otherwise.
|
||||
func (i itemType) isOperator() bool { return i > operatorsStart && i < operatorsEnd }
|
||||
|
||||
|
@ -71,6 +71,15 @@ func (i itemType) isComparisonOperator() bool {
|
|||
}
|
||||
}
|
||||
|
||||
// isSetOperator returns whether the item corresponds to a set operator.
|
||||
func (i itemType) isSetOperator() bool {
|
||||
switch i {
|
||||
case itemLAND, itemLOR, itemLUnless:
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// Constants for operator precedence in expressions.
|
||||
//
|
||||
const LowestPrec = 0 // Non-operators.
|
||||
|
@ -82,7 +91,7 @@ func (i itemType) precedence() int {
|
|||
switch i {
|
||||
case itemLOR:
|
||||
return 1
|
||||
case itemLAND:
|
||||
case itemLAND, itemLUnless:
|
||||
return 2
|
||||
case itemEQL, itemNEQ, itemLTE, itemLSS, itemGTE, itemGTR:
|
||||
return 3
|
||||
|
@ -127,6 +136,7 @@ const (
|
|||
itemDIV
|
||||
itemLAND
|
||||
itemLOR
|
||||
itemLUnless
|
||||
itemEQL
|
||||
itemNEQ
|
||||
itemLTE
|
||||
|
@ -168,8 +178,9 @@ const (
|
|||
|
||||
var key = map[string]itemType{
|
||||
// Operators.
|
||||
"and": itemLAND,
|
||||
"or": itemLOR,
|
||||
"and": itemLAND,
|
||||
"or": itemLOR,
|
||||
"unless": itemLUnless,
|
||||
|
||||
// Aggregators.
|
||||
"sum": itemSum,
|
||||
|
|
|
@ -217,6 +217,9 @@ var tests = []struct {
|
|||
}, {
|
||||
input: `or`,
|
||||
expected: []item{{itemLOR, 0, `or`}},
|
||||
}, {
|
||||
input: `unless`,
|
||||
expected: []item{{itemLUnless, 0, `unless`}},
|
||||
},
|
||||
// Test aggregators.
|
||||
{
|
||||
|
|
|
@ -447,7 +447,7 @@ func (p *parser) expr() Expr {
|
|||
vecMatching := &VectorMatching{
|
||||
Card: CardOneToOne,
|
||||
}
|
||||
if op == itemLAND || op == itemLOR {
|
||||
if op.isSetOperator() {
|
||||
vecMatching.Card = CardManyToMany
|
||||
}
|
||||
|
||||
|
@ -1042,7 +1042,7 @@ func (p *parser) checkType(node Node) (typ model.ValueType) {
|
|||
rt := p.checkType(n.RHS)
|
||||
|
||||
if !n.Op.isOperator() {
|
||||
p.errorf("only logical and arithmetic operators allowed in binary expression, got %q", n.Op)
|
||||
p.errorf("binary expression does not support operator %q", n.Op)
|
||||
}
|
||||
if (lt != model.ValScalar && lt != model.ValVector) || (rt != model.ValScalar && rt != model.ValVector) {
|
||||
p.errorf("binary expression must contain only scalar and vector types")
|
||||
|
@ -1055,18 +1055,18 @@ func (p *parser) checkType(node Node) (typ model.ValueType) {
|
|||
n.VectorMatching = nil
|
||||
} else {
|
||||
// Both operands are vectors.
|
||||
if n.Op == itemLAND || n.Op == itemLOR {
|
||||
if n.Op.isSetOperator() {
|
||||
if n.VectorMatching.Card == CardOneToMany || n.VectorMatching.Card == CardManyToOne {
|
||||
p.errorf("no grouping allowed for AND and OR operations")
|
||||
p.errorf("no grouping allowed for %q operation", n.Op)
|
||||
}
|
||||
if n.VectorMatching.Card != CardManyToMany {
|
||||
p.errorf("AND and OR operations must always be many-to-many")
|
||||
p.errorf("set operations must always be many-to-many")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (lt == model.ValScalar || rt == model.ValScalar) && (n.Op == itemLAND || n.Op == itemLOR) {
|
||||
p.errorf("AND and OR not allowed in binary scalar expression")
|
||||
if (lt == model.ValScalar || rt == model.ValScalar) && n.Op.isSetOperator() {
|
||||
p.errorf("set operator %q not allowed in binary scalar expression", n.Op)
|
||||
}
|
||||
|
||||
case *Call:
|
||||
|
|
|
@ -213,7 +213,7 @@ var testExpr = []struct {
|
|||
}, {
|
||||
input: "1 and 1",
|
||||
fail: true,
|
||||
errMsg: "AND and OR not allowed in binary scalar expression",
|
||||
errMsg: "set operator \"and\" not allowed in binary scalar expression",
|
||||
}, {
|
||||
input: "1 == 1",
|
||||
fail: true,
|
||||
|
@ -221,7 +221,11 @@ var testExpr = []struct {
|
|||
}, {
|
||||
input: "1 or 1",
|
||||
fail: true,
|
||||
errMsg: "AND and OR not allowed in binary scalar expression",
|
||||
errMsg: "set operator \"or\" not allowed in binary scalar expression",
|
||||
}, {
|
||||
input: "1 unless 1",
|
||||
fail: true,
|
||||
errMsg: "set operator \"unless\" not allowed in binary scalar expression",
|
||||
}, {
|
||||
input: "1 !~ 1",
|
||||
fail: true,
|
||||
|
@ -339,6 +343,24 @@ var testExpr = []struct {
|
|||
},
|
||||
VectorMatching: &VectorMatching{Card: CardManyToMany},
|
||||
},
|
||||
}, {
|
||||
input: "foo unless bar",
|
||||
expected: &BinaryExpr{
|
||||
Op: itemLUnless,
|
||||
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},
|
||||
},
|
||||
}, {
|
||||
// Test and/or precedence and reassigning of operands.
|
||||
input: "foo + bar or bla and blub",
|
||||
|
@ -378,6 +400,45 @@ var testExpr = []struct {
|
|||
},
|
||||
VectorMatching: &VectorMatching{Card: CardManyToMany},
|
||||
},
|
||||
}, {
|
||||
// Test and/or/unless precedence.
|
||||
input: "foo and bar unless baz or qux",
|
||||
expected: &BinaryExpr{
|
||||
Op: itemLOR,
|
||||
LHS: &BinaryExpr{
|
||||
Op: itemLUnless,
|
||||
LHS: &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},
|
||||
},
|
||||
RHS: &VectorSelector{
|
||||
Name: "baz",
|
||||
LabelMatchers: metric.LabelMatchers{
|
||||
{Type: metric.Equal, Name: model.MetricNameLabel, Value: "baz"},
|
||||
},
|
||||
},
|
||||
VectorMatching: &VectorMatching{Card: CardManyToMany},
|
||||
},
|
||||
RHS: &VectorSelector{
|
||||
Name: "qux",
|
||||
LabelMatchers: metric.LabelMatchers{
|
||||
{Type: metric.Equal, Name: model.MetricNameLabel, Value: "qux"},
|
||||
},
|
||||
},
|
||||
VectorMatching: &VectorMatching{Card: CardManyToMany},
|
||||
},
|
||||
}, {
|
||||
// Test precedence and reassigning of operands.
|
||||
input: "bar + on(foo) bla / on(baz, buz) group_right(test) blub",
|
||||
|
@ -456,6 +517,27 @@ var testExpr = []struct {
|
|||
On: model.LabelNames{"test", "blub"},
|
||||
},
|
||||
},
|
||||
}, {
|
||||
input: "foo unless on(bar) baz",
|
||||
expected: &BinaryExpr{
|
||||
Op: itemLUnless,
|
||||
LHS: &VectorSelector{
|
||||
Name: "foo",
|
||||
LabelMatchers: metric.LabelMatchers{
|
||||
{Type: metric.Equal, Name: model.MetricNameLabel, Value: "foo"},
|
||||
},
|
||||
},
|
||||
RHS: &VectorSelector{
|
||||
Name: "baz",
|
||||
LabelMatchers: metric.LabelMatchers{
|
||||
{Type: metric.Equal, Name: model.MetricNameLabel, Value: "baz"},
|
||||
},
|
||||
},
|
||||
VectorMatching: &VectorMatching{
|
||||
Card: CardManyToMany,
|
||||
On: model.LabelNames{"bar"},
|
||||
},
|
||||
},
|
||||
}, {
|
||||
input: "foo / on(test,blub) group_left(bar) bar",
|
||||
expected: &BinaryExpr{
|
||||
|
@ -503,19 +585,27 @@ var testExpr = []struct {
|
|||
}, {
|
||||
input: "foo and 1",
|
||||
fail: true,
|
||||
errMsg: "AND and OR not allowed in binary scalar expression",
|
||||
errMsg: "set operator \"and\" not allowed in binary scalar expression",
|
||||
}, {
|
||||
input: "1 and foo",
|
||||
fail: true,
|
||||
errMsg: "AND and OR not allowed in binary scalar expression",
|
||||
errMsg: "set operator \"and\" not allowed in binary scalar expression",
|
||||
}, {
|
||||
input: "foo or 1",
|
||||
fail: true,
|
||||
errMsg: "AND and OR not allowed in binary scalar expression",
|
||||
errMsg: "set operator \"or\" not allowed in binary scalar expression",
|
||||
}, {
|
||||
input: "1 or foo",
|
||||
fail: true,
|
||||
errMsg: "AND and OR not allowed in binary scalar expression",
|
||||
errMsg: "set operator \"or\" not allowed in binary scalar expression",
|
||||
}, {
|
||||
input: "foo unless 1",
|
||||
fail: true,
|
||||
errMsg: "set operator \"unless\" not allowed in binary scalar expression",
|
||||
}, {
|
||||
input: "1 unless foo",
|
||||
fail: true,
|
||||
errMsg: "set operator \"unless\" not allowed in binary scalar expression",
|
||||
}, {
|
||||
input: "1 or on(bar) foo",
|
||||
fail: true,
|
||||
|
@ -527,19 +617,27 @@ var testExpr = []struct {
|
|||
}, {
|
||||
input: "foo and on(bar) group_left(baz) bar",
|
||||
fail: true,
|
||||
errMsg: "no grouping allowed for AND and OR operations",
|
||||
errMsg: "no grouping allowed for \"and\" operation",
|
||||
}, {
|
||||
input: "foo and on(bar) group_right(baz) bar",
|
||||
fail: true,
|
||||
errMsg: "no grouping allowed for AND and OR operations",
|
||||
errMsg: "no grouping allowed for \"and\" operation",
|
||||
}, {
|
||||
input: "foo or on(bar) group_left(baz) bar",
|
||||
fail: true,
|
||||
errMsg: "no grouping allowed for AND and OR operations",
|
||||
errMsg: "no grouping allowed for \"or\" operation",
|
||||
}, {
|
||||
input: "foo or on(bar) group_right(baz) bar",
|
||||
fail: true,
|
||||
errMsg: "no grouping allowed for AND and OR operations",
|
||||
errMsg: "no grouping allowed for \"or\" operation",
|
||||
}, {
|
||||
input: "foo unless on(bar) group_left(baz) bar",
|
||||
fail: true,
|
||||
errMsg: "no grouping allowed for \"unless\" operation",
|
||||
}, {
|
||||
input: "foo unless on(bar) group_right(baz) bar",
|
||||
fail: true,
|
||||
errMsg: "no grouping allowed for \"unless\" operation",
|
||||
}, {
|
||||
input: `http_requests{group="production"} / on(instance) group_left cpu_count{type="smp"}`,
|
||||
fail: true,
|
||||
|
|
|
@ -109,6 +109,16 @@ eval instant at 50m (http_requests{group="canary"} + 1) or on(instance) (http_re
|
|||
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"}
|
||||
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"} unless on(job) http_requests{instance="0"}
|
||||
|
||||
eval instant at 50m http_requests{group="canary"} unless on(job, instance) 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"} / on(instance,job) http_requests{group="production"}
|
||||
{instance="0", job="api-server"} 3
|
||||
{instance="0", job="app-server"} 1.4
|
||||
|
|
Loading…
Reference in New Issue