Merge "Allow reversing vector and scalar arguments in binops."

This commit is contained in:
Björn Rabenstein 2014-04-08 17:17:42 +02:00 committed by Gerrit Code Review
commit 95bc920f5f
2 changed files with 47 additions and 14 deletions

View file

@ -195,11 +195,12 @@ type (
vector VectorNode vector VectorNode
} }
// VectorArithExpr represents an arithmetic expression of // VectorArithExpr represents an arithmetic expression of vector type. At
// vector type. // least one of the two operand Nodes must be a VectorNode. The other may be
// a VectorNode or ScalarNode. Both criteria are checked at runtime.
VectorArithExpr struct { VectorArithExpr struct {
opType BinOpType opType BinOpType
lhs VectorNode lhs Node
rhs Node rhs Node
} }
) )
@ -639,9 +640,20 @@ func labelsEqual(labels1, labels2 clientmodel.Metric) bool {
// Eval implements the VectorNode interface and returns the result of // Eval implements the VectorNode interface and returns the result of
// the expression. // the expression.
func (node *VectorArithExpr) Eval(timestamp clientmodel.Timestamp, view *viewAdapter) Vector { func (node *VectorArithExpr) Eval(timestamp clientmodel.Timestamp, view *viewAdapter) Vector {
lhs := node.lhs.Eval(timestamp, view)
result := Vector{} result := Vector{}
if node.rhs.Type() == SCALAR { if node.lhs.Type() == SCALAR && node.rhs.Type() == VECTOR {
lhs := node.lhs.(ScalarNode).Eval(timestamp, view)
rhs := node.rhs.(VectorNode).Eval(timestamp, view)
for _, rhsSample := range rhs {
value, keep := evalVectorBinop(node.opType, lhs, rhsSample.Value)
if keep {
rhsSample.Value = value
result = append(result, rhsSample)
}
}
return result
} else if node.lhs.Type() == VECTOR && node.rhs.Type() == SCALAR {
lhs := node.lhs.(VectorNode).Eval(timestamp, view)
rhs := node.rhs.(ScalarNode).Eval(timestamp, view) rhs := node.rhs.(ScalarNode).Eval(timestamp, view)
for _, lhsSample := range lhs { for _, lhsSample := range lhs {
value, keep := evalVectorBinop(node.opType, lhsSample.Value, rhs) value, keep := evalVectorBinop(node.opType, lhsSample.Value, rhs)
@ -651,7 +663,8 @@ func (node *VectorArithExpr) Eval(timestamp clientmodel.Timestamp, view *viewAda
} }
} }
return result return result
} else if node.rhs.Type() == VECTOR { } else if node.lhs.Type() == VECTOR && node.rhs.Type() == VECTOR {
lhs := node.lhs.(VectorNode).Eval(timestamp, view)
rhs := node.rhs.(VectorNode).Eval(timestamp, view) rhs := node.rhs.(VectorNode).Eval(timestamp, view)
for _, lhsSample := range lhs { for _, lhsSample := range lhs {
for _, rhsSample := range rhs { for _, rhsSample := range rhs {
@ -804,9 +817,6 @@ func NewArithExpr(opType BinOpType, lhs Node, rhs Node) (Node, error) {
if !nodesHaveTypes(Nodes{lhs, rhs}, []ExprType{SCALAR, VECTOR}) { if !nodesHaveTypes(Nodes{lhs, rhs}, []ExprType{SCALAR, VECTOR}) {
return nil, errors.New("binary operands must be of vector or scalar type") return nil, errors.New("binary operands must be of vector or scalar type")
} }
if lhs.Type() == SCALAR && rhs.Type() == VECTOR {
return nil, errors.New("left side of vector binary operation must be of vector type")
}
if opType == AND || opType == OR { if opType == AND || opType == OR {
if lhs.Type() == SCALAR || rhs.Type() == SCALAR { if lhs.Type() == SCALAR || rhs.Type() == SCALAR {
@ -817,7 +827,7 @@ func NewArithExpr(opType BinOpType, lhs Node, rhs Node) (Node, error) {
if lhs.Type() == VECTOR || rhs.Type() == VECTOR { if lhs.Type() == VECTOR || rhs.Type() == VECTOR {
return &VectorArithExpr{ return &VectorArithExpr{
opType: opType, opType: opType,
lhs: lhs.(VectorNode), lhs: lhs,
rhs: rhs, rhs: rhs,
}, nil }, nil
} }

View file

@ -209,6 +209,22 @@ func TestExpressions(t *testing.T) {
}, },
fullRanges: 0, fullRanges: 0,
intervalRanges: 8, intervalRanges: 8,
}, {
expr: `2 - SUM(http_requests) BY (job)`,
output: []string{
`http_requests{job="api-server"} => -998 @[%v]`,
`http_requests{job="app-server"} => -2598 @[%v]`,
},
fullRanges: 0,
intervalRanges: 8,
}, {
expr: `1000 / SUM(http_requests) BY (job)`,
output: []string{
`http_requests{job="api-server"} => 1 @[%v]`,
`http_requests{job="app-server"} => 0.38461538461538464 @[%v]`,
},
fullRanges: 0,
intervalRanges: 8,
}, { }, {
expr: `SUM(http_requests) BY (job) - 2`, expr: `SUM(http_requests) BY (job) - 2`,
output: []string{ output: []string{
@ -240,6 +256,13 @@ func TestExpressions(t *testing.T) {
}, },
fullRanges: 0, fullRanges: 0,
intervalRanges: 8, intervalRanges: 8,
}, {
expr: `1000 < SUM(http_requests) BY (job)`,
output: []string{
`http_requests{job="app-server"} => 1000 @[%v]`,
},
fullRanges: 0,
intervalRanges: 8,
}, { }, {
expr: `SUM(http_requests) BY (job) <= 1000`, expr: `SUM(http_requests) BY (job) <= 1000`,
output: []string{ output: []string{
@ -398,14 +421,14 @@ func TestExpressions(t *testing.T) {
// Empty expressions shouldn"t parse. // Empty expressions shouldn"t parse.
expr: ``, expr: ``,
shouldFail: true, shouldFail: true,
}, {
// Subtracting a vector from a scalar is not supported.
expr: `1 - http_requests`,
shouldFail: true,
}, { }, {
// Interval durations can"t be in quotes. // Interval durations can"t be in quotes.
expr: `http_requests["1m"]`, expr: `http_requests["1m"]`,
shouldFail: true, shouldFail: true,
}, {
// Binop arguments need to be scalar or vector.
expr: `http_requests - http_requests[1m]`,
shouldFail: true,
}, { }, {
expr: `http_requests{group!="canary"}`, expr: `http_requests{group!="canary"}`,
output: []string{ output: []string{