promql: add power binary operation

This commit is contained in:
royels 2016-05-29 05:06:14 -04:00
parent f55a1d463d
commit 2fdc5717a3
5 changed files with 61 additions and 12 deletions

View file

@ -991,6 +991,8 @@ func scalarBinop(op itemType, lhs, rhs model.SampleValue) model.SampleValue {
return lhs * rhs return lhs * rhs
case itemDIV: case itemDIV:
return lhs / rhs return lhs / rhs
case itemPOW:
return model.SampleValue(math.Pow(float64(lhs), float64(rhs)))
case itemMOD: case itemMOD:
if rhs != 0 { if rhs != 0 {
return model.SampleValue(int(lhs) % int(rhs)) return model.SampleValue(int(lhs) % int(rhs))
@ -1023,6 +1025,8 @@ func vectorElemBinop(op itemType, lhs, rhs model.SampleValue) (model.SampleValue
return lhs * rhs, true return lhs * rhs, true
case itemDIV: case itemDIV:
return lhs / rhs, true return lhs / rhs, true
case itemPOW:
return model.SampleValue(math.Pow(float64(lhs), float64(rhs))), true
case itemMOD: case itemMOD:
if rhs != 0 { if rhs != 0 {
return model.SampleValue(int(lhs) % int(rhs)), true return model.SampleValue(int(lhs) % int(rhs)), true

View file

@ -100,11 +100,23 @@ func (i itemType) precedence() int {
return 4 return 4
case itemMUL, itemDIV, itemMOD: case itemMUL, itemDIV, itemMOD:
return 5 return 5
case itemPOW:
return 6
default: default:
return LowestPrec return LowestPrec
} }
} }
func (i itemType) isRightAssociative() bool {
switch i {
case itemPOW:
return true
default:
return false
}
}
type itemType int type itemType int
const ( const (
@ -146,6 +158,7 @@ const (
itemGTR itemGTR
itemEQLRegex itemEQLRegex
itemNEQRegex itemNEQRegex
itemPOW
operatorsEnd operatorsEnd
aggregatorsStart aggregatorsStart
@ -248,6 +261,7 @@ var itemTypeStr = map[itemType]string{
itemGTR: ">", itemGTR: ">",
itemEQLRegex: "=~", itemEQLRegex: "=~",
itemNEQRegex: "!~", itemNEQRegex: "!~",
itemPOW: "^",
} }
func init() { func init() {
@ -471,6 +485,8 @@ func lexStatements(l *lexer) stateFn {
l.emit(itemADD) l.emit(itemADD)
case r == '-': case r == '-':
l.emit(itemSUB) l.emit(itemSUB)
case r == '^':
l.emit(itemPOW)
case r == '=': case r == '=':
if t := l.peek(); t == '=' { if t := l.peek(); t == '=' {
l.next() l.next()

View file

@ -208,6 +208,9 @@ var tests = []struct {
}, { }, {
input: `/`, input: `/`,
expected: []item{{itemDIV, 0, `/`}}, expected: []item{{itemDIV, 0, `/`}},
}, {
input: `^`,
expected: []item{{itemPOW, 0, `^`}},
}, { }, {
input: `%`, input: `%`,
expected: []item{{itemMOD, 0, `%`}}, expected: []item{{itemMOD, 0, `%`}},

View file

@ -500,17 +500,20 @@ func (p *parser) expr() Expr {
} }
func (p *parser) balance(lhs Expr, op itemType, rhs Expr, vecMatching *VectorMatching, returnBool bool) *BinaryExpr { func (p *parser) balance(lhs Expr, op itemType, rhs Expr, vecMatching *VectorMatching, returnBool bool) *BinaryExpr {
if lhsBE, ok := lhs.(*BinaryExpr); ok && lhsBE.Op.precedence() < op.precedence() { if lhsBE, ok := lhs.(*BinaryExpr); ok {
balanced := p.balance(lhsBE.RHS, op, rhs, vecMatching, returnBool) precd := lhsBE.Op.precedence() - op.precedence()
if lhsBE.Op.isComparisonOperator() && !lhsBE.ReturnBool && balanced.Type() == model.ValScalar && lhsBE.LHS.Type() == model.ValScalar { if (precd < 0) || (precd == 0 && op.isRightAssociative()) {
p.errorf("comparisons between scalars must use BOOL modifier") balanced := p.balance(lhsBE.RHS, op, rhs, vecMatching, returnBool)
} if lhsBE.Op.isComparisonOperator() && !lhsBE.ReturnBool && balanced.Type() == model.ValScalar && lhsBE.LHS.Type() == model.ValScalar {
return &BinaryExpr{ p.errorf("comparisons between scalars must use BOOL modifier")
Op: lhsBE.Op, }
LHS: lhsBE.LHS, return &BinaryExpr{
RHS: balanced, Op: lhsBE.Op,
VectorMatching: lhsBE.VectorMatching, LHS: lhsBE.LHS,
ReturnBool: lhsBE.ReturnBool, RHS: balanced,
VectorMatching: lhsBE.VectorMatching,
ReturnBool: lhsBE.ReturnBool,
}
} }
} }
if op.isComparisonOperator() && !returnBool && rhs.Type() == model.ValScalar && lhs.Type() == model.ValScalar { if op.isComparisonOperator() && !returnBool && rhs.Type() == model.ValScalar && lhs.Type() == model.ValScalar {

View file

@ -8,7 +8,6 @@ load 5m
http_requests{job="app-server", instance="0", group="canary"} 0+70x10 http_requests{job="app-server", instance="0", group="canary"} 0+70x10
http_requests{job="app-server", instance="1", group="canary"} 0+80x10 http_requests{job="app-server", instance="1", group="canary"} 0+80x10
load 5m load 5m
vector_matching_a{l="x"} 0+1x100 vector_matching_a{l="x"} 0+1x100
vector_matching_a{l="y"} 0+2x50 vector_matching_a{l="y"} 0+2x50
@ -35,6 +34,30 @@ eval instant at 50m SUM(http_requests) BY (job) % 3
{job="api-server"} 1 {job="api-server"} 1
{job="app-server"} 2 {job="app-server"} 2
eval instant at 50m SUM(http_requests) BY (job) ^ 2
{job="api-server"} 1000000
{job="app-server"} 6760000
eval instant at 50m SUM(http_requests) BY (job) % 3 ^ 2
{job="api-server"} 1
{job="app-server"} 8
eval instant at 50m SUM(http_requests) BY (job) % 2 ^ (3 ^ 2)
{job="api-server"} 488
{job="app-server"} 40
eval instant at 50m SUM(http_requests) BY (job) % 2 ^ 3 ^ 2
{job="api-server"} 488
{job="app-server"} 40
eval instant at 50m SUM(http_requests) BY (job) % 2 ^ 3 ^ 2 ^ 2
{job="api-server"} 1000
{job="app-server"} 2600
eval instant at 50m COUNT(http_requests) BY (job) ^ COUNT(http_requests) BY (job)
{job="api-server"} 256
{job="app-server"} 256
eval instant at 50m SUM(http_requests) BY (job) / 0 eval instant at 50m SUM(http_requests) BY (job) / 0
{job="api-server"} +Inf {job="api-server"} +Inf
{job="app-server"} +Inf {job="app-server"} +Inf