Add OR operation and vector matching options.

This commits implements the OR operation between two vectors.
Vector matching using the ON clause is added to limit the set of
labels that define a match between two elements. Group modifiers
(GROUP_LEFT/GROUP_RIGHT) to request many-to-one matching are added.
This commit is contained in:
Fabian Reinartz 2015-03-03 10:58:28 +01:00
parent cce2f30a8b
commit 6f754073d5
8 changed files with 2070 additions and 1164 deletions

View file

@ -244,9 +244,21 @@ type (
opType BinOpType opType BinOpType
lhs Node lhs Node
rhs Node rhs Node
matchCardinality VectorMatchCardinality
matchOn clientmodel.LabelNames
includeLabels clientmodel.LabelNames
} }
) )
type VectorMatchCardinality int
const (
MatchOneToOne VectorMatchCardinality = iota
MatchManyToOne
MatchOneToMany
MatchManyToMany
)
// ---------------------------------------------------------------------------- // ----------------------------------------------------------------------------
// MatrixNode types. // MatrixNode types.
@ -371,14 +383,21 @@ func (node *ScalarFunctionCall) Eval(timestamp clientmodel.Timestamp) clientmode
return node.function.callFn(timestamp, node.args).(clientmodel.SampleValue) return node.function.callFn(timestamp, node.args).(clientmodel.SampleValue)
} }
func (node *VectorAggregation) labelsToGroupingKey(labels clientmodel.Metric) uint64 { // hashForLabels returns a hash value taken from the label/value pairs of the
summer := fnv.New64a() // specified labels in the metric.
for _, label := range node.groupBy { func hashForLabels(metric clientmodel.Metric, labels clientmodel.LabelNames) uint64 {
summer.Write([]byte(labels[label])) var result uint64
summer.Write([]byte{clientmodel.SeparatorByte}) s := fnv.New64a()
for _, label := range labels {
s.Write([]byte(label))
s.Write([]byte{clientmodel.SeparatorByte})
s.Write([]byte(metric[label]))
result ^= s.Sum64()
s.Reset()
} }
return summer.Sum64() return result
} }
// EvalVectorInstant evaluates a VectorNode with an instant query. // EvalVectorInstant evaluates a VectorNode with an instant query.
@ -484,7 +503,7 @@ func (node *VectorAggregation) Eval(timestamp clientmodel.Timestamp) Vector {
vector := node.vector.Eval(timestamp) vector := node.vector.Eval(timestamp)
result := map[uint64]*groupedAggregation{} result := map[uint64]*groupedAggregation{}
for _, sample := range vector { for _, sample := range vector {
groupingKey := node.labelsToGroupingKey(sample.Metric.Metric) groupingKey := hashForLabels(sample.Metric.Metric, node.groupBy)
if groupedResult, ok := result[groupingKey]; ok { if groupedResult, ok := result[groupingKey]; ok {
if node.keepExtraLabels { if node.keepExtraLabels {
groupedResult.labels = labelIntersection(groupedResult.labels, sample.Metric) groupedResult.labels = labelIntersection(groupedResult.labels, sample.Metric)
@ -729,10 +748,6 @@ func evalVectorBinop(opType BinOpType,
return lhs, true return lhs, true
} }
return 0, false return 0, false
case And:
return lhs, true
case Or:
return lhs, true // TODO: implement OR
} }
panic("Not all enum values enumerated in switch") panic("Not all enum values enumerated in switch")
} }
@ -749,26 +764,38 @@ 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) Vector { func (node *VectorArithExpr) Eval(timestamp clientmodel.Timestamp) Vector {
result := Vector{} // Calculate vector-to-vector operation.
if node.lhs.Type() == VectorType && node.rhs.Type() == VectorType {
lhs := node.lhs.(VectorNode).Eval(timestamp)
rhs := node.rhs.(VectorNode).Eval(timestamp)
return node.evalVectors(timestamp, lhs, rhs)
}
// Calculate vector-to-scalar operation.
var lhs Vector
var rhs clientmodel.SampleValue
swap := false
if node.lhs.Type() == ScalarType && node.rhs.Type() == VectorType { if node.lhs.Type() == ScalarType && node.rhs.Type() == VectorType {
lhs := node.lhs.(ScalarNode).Eval(timestamp) lhs = node.rhs.(VectorNode).Eval(timestamp)
rhs := node.rhs.(VectorNode).Eval(timestamp) rhs = node.lhs.(ScalarNode).Eval(timestamp)
for _, rhsSample := range rhs { swap = true
value, keep := evalVectorBinop(node.opType, lhs, rhsSample.Value) } else {
if keep { lhs = node.lhs.(VectorNode).Eval(timestamp)
rhsSample.Value = value rhs = node.rhs.(ScalarNode).Eval(timestamp)
if node.opType.shouldDropMetric() {
rhsSample.Metric.Delete(clientmodel.MetricNameLabel)
} }
result = append(result, rhsSample)
} result := make(Vector, 0, len(lhs))
}
return result
} else if node.lhs.Type() == VectorType && node.rhs.Type() == ScalarType {
lhs := node.lhs.(VectorNode).Eval(timestamp)
rhs := node.rhs.(ScalarNode).Eval(timestamp)
for _, lhsSample := range lhs { for _, lhsSample := range lhs {
value, keep := evalVectorBinop(node.opType, lhsSample.Value, rhs) lv, rv := lhsSample.Value, rhs
// lhs always contains the vector. If the original position was different
// swap for calculating the value.
if swap {
lv, rv = rv, lv
}
value, keep := evalVectorBinop(node.opType, lv, rv)
if keep { if keep {
lhsSample.Value = value lhsSample.Value = value
if node.opType.shouldDropMetric() { if node.opType.shouldDropMetric() {
@ -778,26 +805,173 @@ func (node *VectorArithExpr) Eval(timestamp clientmodel.Timestamp) Vector {
} }
} }
return result return result
} else if node.lhs.Type() == VectorType && node.rhs.Type() == VectorType { }
lhs := node.lhs.(VectorNode).Eval(timestamp)
rhs := node.rhs.(VectorNode).Eval(timestamp) // evalVectors evaluates the binary operation for the given vectors.
for _, lhsSample := range lhs { func (node *VectorArithExpr) evalVectors(timestamp clientmodel.Timestamp, lhs, rhs Vector) Vector {
for _, rhsSample := range rhs { result := make(Vector, 0, len(rhs))
if labelsEqual(lhsSample.Metric.Metric, rhsSample.Metric.Metric) { // The control flow below handles one-to-one or many-to-one matching.
value, keep := evalVectorBinop(node.opType, lhsSample.Value, rhsSample.Value) // For one-to-many, swap sidedness and account for the swap when calculating
// values.
if node.matchCardinality == MatchOneToMany {
lhs, rhs = rhs, lhs
}
// All samples from the rhs hashed by the matching label/values.
rm := make(map[uint64]*Sample)
// Maps the hash of the label values used for matching to the hashes of the label
// values of the include labels (if any). It is used to keep track of already
// inserted samples.
added := make(map[uint64][]uint64)
// Add all rhs samples to a map so we can easily find matches later.
for _, rs := range rhs {
hash := node.hashForMetric(rs.Metric.Metric)
// The rhs is guaranteed to be the 'one' side. Having multiple samples
// with the same hash means that the matching is many-to-many,
// which is not supported.
if _, found := rm[hash]; node.matchCardinality != MatchManyToMany && found {
// Many-to-many matching not allowed.
// TODO(fabxc): Return a query error here once AST nodes support that.
return Vector{}
}
// In many-to-many matching the entry is simply overwritten. It can thus only
// be used to check whether any matching rhs entry exists but not retrieve them all.
rm[hash] = rs
}
// For all lhs samples find a respective rhs sample and perform
// the binary operation.
for _, ls := range lhs {
hash := node.hashForMetric(ls.Metric.Metric)
// Any lhs sample we encounter in an OR operation belongs to the result.
if node.opType == Or {
ls.Metric = node.resultMetric(ls, nil)
result = append(result, ls)
added[hash] = nil // Ensure matching rhs sample is not added later.
continue
}
rs, found := rm[hash] // Look for a match in the rhs vector.
if !found {
continue
}
var value clientmodel.SampleValue
var keep bool
if node.opType == And {
value = ls.Value
keep = true
} else {
if _, exists := added[hash]; node.matchCardinality == MatchOneToOne && exists {
// Many-to-one matching must be explicit.
// TODO(fabxc): Return a query error here once AST nodes support that.
return Vector{}
}
// Account for potentially swapped sidedness.
vl, vr := ls.Value, rs.Value
if node.matchCardinality == MatchOneToMany {
vl, vr = vr, vl
}
value, keep = evalVectorBinop(node.opType, vl, vr)
}
if keep { if keep {
lhsSample.Value = value metric := node.resultMetric(ls, rs)
if node.opType.shouldDropMetric() { // Check if the same label set has been added for a many-to-one matching before.
lhsSample.Metric.Delete(clientmodel.MetricNameLabel) if node.matchCardinality == MatchManyToOne || node.matchCardinality == MatchOneToMany {
insHash := hashForLabels(metric.Metric, node.includeLabels)
if ihs, exists := added[hash]; exists {
for _, ih := range ihs {
if ih == insHash {
// TODO(fabxc): Return a query error here once AST nodes support that.
return Vector{}
} }
result = append(result, lhsSample)
} }
added[hash] = append(ihs, insHash)
} else {
added[hash] = []uint64{insHash}
}
}
ns := &Sample{
Metric: metric,
Value: value,
Timestamp: timestamp,
}
result = append(result, ns)
added[hash] = added[hash] // Set existance to true.
}
}
// Add all remaining samples in the rhs in an OR operation if they
// have not been matched up with a lhs sample.
if node.opType == Or {
for hash, rs := range rm {
if _, exists := added[hash]; !exists {
rs.Metric = node.resultMetric(rs, nil)
result = append(result, rs)
} }
} }
} }
return result return result
}
// resultMetric returns the metric for the given sample(s) based on the vector
// binary operation and the matching options. If a label that has to be included is set on
// both sides an error is returned.
func (node *VectorArithExpr) resultMetric(ls, rs *Sample) clientmodel.COWMetric {
if len(node.matchOn) == 0 || node.opType == Or || node.opType == And {
if node.opType.shouldDropMetric() {
ls.Metric.Delete(clientmodel.MetricNameLabel)
} }
panic("Invalid vector arithmetic expression operands") return ls.Metric
}
m := clientmodel.Metric{}
for _, ln := range node.matchOn {
m[ln] = ls.Metric.Metric[ln]
}
for _, ln := range node.includeLabels {
// Included labels from the `group_x` modifier are taken from the "many"-side.
v, ok := ls.Metric.Metric[ln]
if ok {
m[ln] = v
}
}
return clientmodel.COWMetric{false, m}
}
// hashForMetric calculates a hash value for the given metric based on the matching
// options for the binary operation.
func (node *VectorArithExpr) hashForMetric(metric clientmodel.Metric) uint64 {
var labels clientmodel.LabelNames
if len(node.matchOn) > 0 {
var match bool
for _, ln := range node.matchOn {
if _, match = metric[ln]; !match {
break
}
}
// If the metric does not contain the labels to match on, build the hash
// over the whole metric to give it a unique hash.
if !match {
labels = make(clientmodel.LabelNames, 0, len(metric))
for ln := range metric {
labels = append(labels, ln)
}
} else {
labels = node.matchOn
}
} else {
labels = make(clientmodel.LabelNames, 0, len(metric))
for ln := range metric {
if ln != clientmodel.MetricNameLabel {
labels = append(labels, ln)
}
}
}
return hashForLabels(metric, labels)
} }
// Eval implements the MatrixNode interface and returns the value of // Eval implements the MatrixNode interface and returns the value of
@ -962,7 +1136,7 @@ func nodesHaveTypes(nodes Nodes, exprTypes []ExprType) bool {
// NewArithExpr returns a (not yet evaluated) expression node (of type // NewArithExpr returns a (not yet evaluated) expression node (of type
// VectorArithExpr or ScalarArithExpr). // VectorArithExpr or ScalarArithExpr).
func NewArithExpr(opType BinOpType, lhs Node, rhs Node) (Node, error) { func NewArithExpr(opType BinOpType, lhs Node, rhs Node, matchCard VectorMatchCardinality, matchOn, include clientmodel.LabelNames) (Node, error) {
if !nodesHaveTypes(Nodes{lhs, rhs}, []ExprType{ScalarType, VectorType}) { if !nodesHaveTypes(Nodes{lhs, rhs}, []ExprType{ScalarType, VectorType}) {
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")
} }
@ -971,6 +1145,15 @@ func NewArithExpr(opType BinOpType, lhs Node, rhs Node) (Node, error) {
if lhs.Type() == ScalarType || rhs.Type() == ScalarType { if lhs.Type() == ScalarType || rhs.Type() == ScalarType {
return nil, errors.New("AND and OR operators may only be used between vectors") return nil, errors.New("AND and OR operators may only be used between vectors")
} }
// Logical operations must never be used with group modifiers.
if len(include) > 0 {
return nil, errors.New("AND and OR operators must not have a group modifier")
}
}
if lhs.Type() != VectorType || rhs.Type() != VectorType {
if matchCard != MatchOneToOne || matchOn != nil || include != nil {
return nil, errors.New("binary scalar expressions cannot have vector matching options")
}
} }
if lhs.Type() == VectorType || rhs.Type() == VectorType { if lhs.Type() == VectorType || rhs.Type() == VectorType {
@ -978,6 +1161,9 @@ func NewArithExpr(opType BinOpType, lhs Node, rhs Node) (Node, error) {
opType: opType, opType: opType,
lhs: lhs, lhs: lhs,
rhs: rhs, rhs: rhs,
matchCardinality: matchCard,
matchOn: matchOn,
includeLabels: include,
}, nil }, nil
} }

View file

@ -82,8 +82,40 @@ func NewVectorAggregation(aggrTypeStr string, vector ast.Node, groupBy clientmod
return ast.NewVectorAggregation(aggrType, vector.(ast.VectorNode), groupBy, keepExtraLabels), nil return ast.NewVectorAggregation(aggrType, vector.(ast.VectorNode), groupBy, keepExtraLabels), nil
} }
// vectorMatching combines data used to match samples between vectors.
type vectorMatching struct {
matchCardinality ast.VectorMatchCardinality
matchOn clientmodel.LabelNames
includeLabels clientmodel.LabelNames
}
// newVectorMatching is a convenience function to create a new vectorMatching.
func newVectorMatching(card string, matchOn, include clientmodel.LabelNames) (*vectorMatching, error) {
var matchCardinalities = map[string]ast.VectorMatchCardinality{
"": ast.MatchOneToOne,
"GROUP_LEFT": ast.MatchManyToOne,
"GROUP_RIGHT": ast.MatchOneToMany,
}
matchCard, ok := matchCardinalities[card]
if !ok {
return nil, fmt.Errorf("invalid vector match cardinality %q", card)
}
if matchCard != ast.MatchOneToOne && len(include) == 0 {
return nil, fmt.Errorf("grouped vector matching must provide labels")
}
// There must be no overlap between both labelname lists.
for _, matchLabel := range matchOn {
for _, incLabel := range include {
if matchLabel == incLabel {
return nil, fmt.Errorf("use of label %s in ON and %s clauses not allowed", incLabel, card)
}
}
}
return &vectorMatching{matchCard, matchOn, include}, nil
}
// NewArithExpr is a convenience function to create a new AST arithmetic expression. // NewArithExpr is a convenience function to create a new AST arithmetic expression.
func NewArithExpr(opTypeStr string, lhs ast.Node, rhs ast.Node) (ast.Node, error) { func NewArithExpr(opTypeStr string, lhs ast.Node, rhs ast.Node, vecMatching *vectorMatching) (ast.Node, error) {
var opTypes = map[string]ast.BinOpType{ var opTypes = map[string]ast.BinOpType{
"+": ast.Add, "+": ast.Add,
"-": ast.Sub, "-": ast.Sub,
@ -103,7 +135,15 @@ func NewArithExpr(opTypeStr string, lhs ast.Node, rhs ast.Node) (ast.Node, error
if !ok { if !ok {
return nil, fmt.Errorf("invalid binary operator %q", opTypeStr) return nil, fmt.Errorf("invalid binary operator %q", opTypeStr)
} }
expr, err := ast.NewArithExpr(opType, lhs, rhs) var vm vectorMatching
if vecMatching != nil {
vm = *vecMatching
// And/or always do many-to-many matching.
if opType == ast.And || opType == ast.Or {
vm.matchCardinality = ast.MatchManyToMany
}
}
expr, err := ast.NewArithExpr(opType, lhs, rhs, vm.matchCardinality, vm.matchOn, vm.includeLabels)
if err != nil { if err != nil {
return nil, fmt.Errorf(err.Error()) return nil, fmt.Errorf(err.Error())
} }

View file

@ -423,6 +423,63 @@ var testMatrix = ast.Matrix{
}, },
Values: getTestValueStream(0, 90, 9, testStartTime), Values: getTestValueStream(0, 90, 9, testStartTime),
}, },
{
Metric: clientmodel.COWMetric{
Metric: clientmodel.Metric{
clientmodel.MetricNameLabel: "vector_matching_a",
"l": "x",
},
},
Values: getTestValueStream(0, 100, 1, testStartTime),
},
{
Metric: clientmodel.COWMetric{
Metric: clientmodel.Metric{
clientmodel.MetricNameLabel: "vector_matching_a",
"l": "y",
},
},
Values: getTestValueStream(0, 100, 2, testStartTime),
},
{
Metric: clientmodel.COWMetric{
Metric: clientmodel.Metric{
clientmodel.MetricNameLabel: "vector_matching_b",
"l": "x",
},
},
Values: getTestValueStream(0, 100, 4, testStartTime),
},
{
Metric: clientmodel.COWMetric{
Metric: clientmodel.Metric{
clientmodel.MetricNameLabel: "cpu_count",
"instance": "0",
"type": "numa",
},
},
Values: getTestValueStream(0, 500, 30, testStartTime),
},
{
Metric: clientmodel.COWMetric{
Metric: clientmodel.Metric{
clientmodel.MetricNameLabel: "cpu_count",
"instance": "0",
"type": "smp",
},
},
Values: getTestValueStream(0, 200, 10, testStartTime),
},
{
Metric: clientmodel.COWMetric{
Metric: clientmodel.Metric{
clientmodel.MetricNameLabel: "cpu_count",
"instance": "1",
"type": "smp",
},
},
Values: getTestValueStream(0, 200, 20, testStartTime),
},
} }
var testVector = getTestVectorFromTestMatrix(testMatrix) var testVector = getTestVectorFromTestMatrix(testMatrix)

View file

@ -84,6 +84,9 @@ DESCRIPTION|description return DESCRIPTION
PERMANENT|permanent return PERMANENT PERMANENT|permanent return PERMANENT
BY|by return GROUP_OP BY|by return GROUP_OP
ON|on return MATCH_OP
GROUP_LEFT|GROUP_RIGHT lval.str = lexer.token(); return MATCH_MOD
group_left|group_right lval.str = strings.ToUpper(lexer.token()); return MATCH_MOD
KEEPING_EXTRA|keeping_extra return KEEPING_EXTRA KEEPING_EXTRA|keeping_extra return KEEPING_EXTRA
OFFSET|offset return OFFSET OFFSET|offset return OFFSET
AVG|SUM|MAX|MIN|COUNT lval.str = lexer.token(); return AGGR_OP AVG|SUM|MAX|MIN|COUNT lval.str = lexer.token(); return AGGR_OP

File diff suppressed because it is too large Load diff

View file

@ -32,6 +32,7 @@
labelSet clientmodel.LabelSet labelSet clientmodel.LabelSet
labelMatcher *metric.LabelMatcher labelMatcher *metric.LabelMatcher
labelMatchers metric.LabelMatchers labelMatchers metric.LabelMatchers
vectorMatching *vectorMatching
} }
/* We simulate multiple start symbols for closely-related grammars via dummy tokens. See /* We simulate multiple start symbols for closely-related grammars via dummy tokens. See
@ -42,8 +43,8 @@
%token <str> IDENTIFIER STRING DURATION METRICNAME %token <str> IDENTIFIER STRING DURATION METRICNAME
%token <num> NUMBER %token <num> NUMBER
%token PERMANENT GROUP_OP KEEPING_EXTRA OFFSET %token PERMANENT GROUP_OP KEEPING_EXTRA OFFSET MATCH_OP
%token <str> AGGR_OP CMP_OP ADDITIVE_OP MULT_OP %token <str> AGGR_OP CMP_OP ADDITIVE_OP MULT_OP MATCH_MOD
%token ALERT IF FOR WITH SUMMARY DESCRIPTION %token ALERT IF FOR WITH SUMMARY DESCRIPTION
%type <ruleNodeSlice> func_arg_list %type <ruleNodeSlice> func_arg_list
@ -51,6 +52,7 @@
%type <labelSet> label_assign label_assign_list rule_labels %type <labelSet> label_assign label_assign_list rule_labels
%type <labelMatcher> label_match %type <labelMatcher> label_match
%type <labelMatchers> label_match_list label_matches %type <labelMatchers> label_match_list label_matches
%type <vectorMatching> vector_matching
%type <ruleNode> rule_expr func_arg %type <ruleNode> rule_expr func_arg
%type <boolean> qualifier extra_labels_opts %type <boolean> qualifier extra_labels_opts
%type <str> for_duration metric_name label_match_type offset_opts %type <str> for_duration metric_name label_match_type offset_opts
@ -207,34 +209,50 @@ rule_expr : '(' rule_expr ')'
} }
/* Yacc can only attach associativity to terminals, so we /* Yacc can only attach associativity to terminals, so we
* have to list all operators here. */ * have to list all operators here. */
| rule_expr ADDITIVE_OP rule_expr | rule_expr ADDITIVE_OP vector_matching rule_expr
{ {
var err error var err error
$$, err = NewArithExpr($2, $1, $3) $$, err = NewArithExpr($2, $1, $4, $3)
if err != nil { yylex.Error(err.Error()); return 1 } if err != nil { yylex.Error(err.Error()); return 1 }
} }
| rule_expr MULT_OP rule_expr | rule_expr MULT_OP vector_matching rule_expr
{ {
var err error var err error
$$, err = NewArithExpr($2, $1, $3) $$, err = NewArithExpr($2, $1, $4, $3)
if err != nil { yylex.Error(err.Error()); return 1 } if err != nil { yylex.Error(err.Error()); return 1 }
} }
| rule_expr CMP_OP rule_expr | rule_expr CMP_OP vector_matching rule_expr
{ {
var err error var err error
$$, err = NewArithExpr($2, $1, $3) $$, err = NewArithExpr($2, $1, $4, $3)
if err != nil { yylex.Error(err.Error()); return 1 } if err != nil { yylex.Error(err.Error()); return 1 }
} }
| NUMBER | NUMBER
{ $$ = ast.NewScalarLiteral($1)} { $$ = ast.NewScalarLiteral($1)}
; ;
extra_labels_opts : extra_labels_opts : /* empty */
{ $$ = false } { $$ = false }
| KEEPING_EXTRA | KEEPING_EXTRA
{ $$ = true } { $$ = true }
; ;
vector_matching : /* empty */
{ $$ = nil }
| MATCH_OP '(' label_list ')'
{
var err error
$$, err = newVectorMatching("", $3, nil)
if err != nil { yylex.Error(err.Error()); return 1 }
}
| MATCH_OP '(' label_list ')' MATCH_MOD '(' label_list ')'
{
var err error
$$, err = newVectorMatching($5, $3, $7)
if err != nil { yylex.Error(err.Error()); return 1 }
}
;
grouping_opts : grouping_opts :
{ $$ = clientmodel.LabelNames{} } { $$ = clientmodel.LabelNames{} }
| GROUP_OP '(' label_list ')' | GROUP_OP '(' label_list ')'

View file

@ -23,6 +23,7 @@ type yySymType struct {
labelSet clientmodel.LabelSet labelSet clientmodel.LabelSet
labelMatcher *metric.LabelMatcher labelMatcher *metric.LabelMatcher
labelMatchers metric.LabelMatchers labelMatchers metric.LabelMatchers
vectorMatching *vectorMatching
} }
const START_RULES = 57346 const START_RULES = 57346
@ -36,16 +37,18 @@ const PERMANENT = 57353
const GROUP_OP = 57354 const GROUP_OP = 57354
const KEEPING_EXTRA = 57355 const KEEPING_EXTRA = 57355
const OFFSET = 57356 const OFFSET = 57356
const AGGR_OP = 57357 const MATCH_OP = 57357
const CMP_OP = 57358 const AGGR_OP = 57358
const ADDITIVE_OP = 57359 const CMP_OP = 57359
const MULT_OP = 57360 const ADDITIVE_OP = 57360
const ALERT = 57361 const MULT_OP = 57361
const IF = 57362 const MATCH_MOD = 57362
const FOR = 57363 const ALERT = 57363
const WITH = 57364 const IF = 57364
const SUMMARY = 57365 const FOR = 57365
const DESCRIPTION = 57366 const WITH = 57366
const SUMMARY = 57367
const DESCRIPTION = 57368
var yyToknames = []string{ var yyToknames = []string{
"START_RULES", "START_RULES",
@ -59,10 +62,12 @@ var yyToknames = []string{
"GROUP_OP", "GROUP_OP",
"KEEPING_EXTRA", "KEEPING_EXTRA",
"OFFSET", "OFFSET",
"MATCH_OP",
"AGGR_OP", "AGGR_OP",
"CMP_OP", "CMP_OP",
"ADDITIVE_OP", "ADDITIVE_OP",
"MULT_OP", "MULT_OP",
"MATCH_MOD",
"ALERT", "ALERT",
"IF", "IF",
"FOR", "FOR",
@ -77,7 +82,7 @@ const yyEofCode = 1
const yyErrCode = 2 const yyErrCode = 2
const yyMaxDepth = 200 const yyMaxDepth = 200
//line parser.y:261 //line parser.y:279
//line yacctab:1 //line yacctab:1
var yyExca = []int{ var yyExca = []int{
@ -89,97 +94,101 @@ var yyExca = []int{
-2, 10, -2, 10,
} }
const yyNprod = 52 const yyNprod = 55
const yyPrivate = 57344 const yyPrivate = 57344
var yyTokenNames []string var yyTokenNames []string
var yyStates []string var yyStates []string
const yyLast = 142 const yyLast = 155
var yyAct = []int{ var yyAct = []int{
58, 76, 55, 52, 51, 30, 45, 6, 24, 20, 76, 59, 81, 56, 53, 52, 30, 46, 6, 24,
61, 22, 10, 53, 18, 13, 12, 84, 68, 83, 10, 54, 22, 13, 12, 21, 19, 20, 19, 20,
67, 11, 18, 36, 37, 38, 21, 19, 20, 21, 11, 62, 21, 19, 20, 20, 18, 90, 96, 111,
19, 20, 8, 54, 90, 7, 50, 21, 19, 20, 99, 18, 8, 18, 55, 7, 51, 60, 18, 18,
92, 18, 10, 53, 18, 13, 12, 70, 63, 62, 90, 63, 97, 65, 66, 21, 19, 20, 107, 75,
88, 11, 18, 57, 31, 21, 19, 20, 86, 87, 68, 67, 10, 54, 64, 13, 12, 21, 19, 20,
66, 40, 8, 10, 78, 7, 13, 12, 79, 69, 74, 18, 11, 31, 58, 85, 83, 90, 94, 89,
18, 29, 11, 80, 82, 81, 28, 85, 21, 19, 84, 27, 40, 18, 8, 28, 23, 7, 112, 86,
20, 19, 20, 8, 91, 77, 7, 41, 40, 94, 88, 87, 29, 91, 21, 19, 20, 82, 73, 10,
25, 23, 39, 18, 59, 18, 44, 98, 27, 73, 72, 98, 13, 12, 92, 93, 101, 71, 41, 11,
101, 99, 60, 96, 17, 43, 75, 9, 46, 56, 18, 42, 41, 25, 49, 106, 45, 78, 109, 108,
31, 47, 16, 33, 97, 102, 13, 65, 35, 48, 80, 8, 103, 36, 7, 61, 44, 17, 105, 37,
100, 95, 64, 32, 77, 93, 72, 25, 34, 2, 9, 47, 57, 31, 104, 33, 48, 16, 13, 70,
3, 14, 5, 4, 1, 42, 89, 15, 26, 74, 35, 113, 110, 102, 38, 39, 32, 69, 77, 82,
71, 49, 100, 25, 34, 2, 3, 14, 5, 4, 1, 43,
95, 15, 26, 79, 50,
} }
var yyPact = []int{ var yyPact = []int{
125, -1000, -1000, 57, 93, -1000, 21, 57, 121, 72, 139, -1000, -1000, 83, 106, -1000, 67, 83, 135, 43,
47, 42, -1000, -1000, -1000, 107, 122, -1000, 110, 57, 44, 51, -1000, -1000, -1000, 119, 136, -1000, 122, 104,
57, 57, 62, 60, -1000, 80, 94, 84, 6, 57, 104, 104, 40, 72, -1000, 89, 107, 97, 4, 83,
96, 24, 68, -1000, 82, -22, -9, -17, 64, -1000, 109, 33, 9, -1000, 93, -13, 83, 23, 83, 83,
121, 94, 115, -1000, -1000, -1000, 109, -1000, 33, -10, -1000, 135, 107, 130, -1000, -1000, -1000, 121, -1000, 68,
-1000, -1000, 21, -1000, 39, 18, -1000, 120, 74, 79, 58, -1000, -1000, 67, -1000, 28, 18, -1000, 132, 80,
57, 94, -1000, -1000, -1000, -1000, -1000, -1000, 36, 98, 81, 83, 107, 6, 132, -7, 0, -1000, -1000, -1000,
57, -11, -1000, 57, 31, -1000, -1000, 25, 13, -1000, -1000, -1000, -1000, 46, 111, 83, 37, -1000, 83, 65,
-1000, 96, 10, -1000, 119, 21, -1000, 118, 114, 81, -1000, -1000, 41, 5, -1000, 10, -1000, 109, -2, -1000,
106, -1000, -1000, -1000, -1000, -1000, 68, -1000, 78, 113, 134, 67, -1000, 133, 126, 88, 116, 98, -1000, -1000,
76, 108, -1000, -1000, -1000, -1000, 9, -1000, 17, 84, 132, 125, -3,
52, -1000, 124, -1000,
} }
var yyPgo = []int{ var yyPgo = []int{
0, 141, 140, 5, 1, 139, 0, 8, 91, 138, 0, 154, 0, 6, 2, 153, 1, 9, 76, 152,
3, 4, 137, 2, 136, 107, 135, 6, 134, 133, 113, 4, 5, 151, 3, 150, 120, 149, 7, 148,
132, 131, 147, 146, 145,
} }
var yyR1 = []int{ var yyR1 = []int{
0, 18, 18, 19, 19, 20, 21, 21, 14, 14, 0, 19, 19, 20, 20, 21, 22, 22, 15, 15,
12, 12, 15, 15, 6, 6, 6, 5, 5, 4, 13, 13, 16, 16, 6, 6, 6, 5, 5, 4,
9, 9, 9, 8, 8, 7, 16, 16, 17, 17, 9, 9, 9, 8, 8, 7, 17, 17, 18, 18,
10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11,
10, 10, 13, 13, 3, 3, 2, 2, 1, 1, 11, 11, 14, 14, 10, 10, 10, 3, 3, 2,
11, 11, 2, 1, 1, 12, 12,
} }
var yyR2 = []int{ var yyR2 = []int{
0, 2, 2, 0, 2, 1, 5, 11, 0, 2, 0, 2, 2, 0, 2, 1, 5, 11, 0, 2,
0, 1, 1, 1, 0, 3, 2, 1, 3, 3, 0, 1, 1, 1, 0, 3, 2, 1, 3, 3,
0, 2, 3, 1, 3, 3, 1, 1, 0, 2, 0, 2, 3, 1, 3, 3, 1, 1, 0, 2,
3, 4, 3, 4, 3, 5, 6, 6, 3, 3, 3, 4, 3, 4, 3, 5, 6, 6, 4, 4,
3, 1, 0, 1, 0, 4, 1, 3, 1, 3, 4, 1, 0, 1, 0, 4, 8, 0, 4, 1,
1, 1, 3, 1, 3, 1, 1,
} }
var yyChk = []int{ var yyChk = []int{
-1000, -18, 4, 5, -19, -20, -10, 29, 26, -15, -1000, -19, 4, 5, -20, -21, -11, 31, 28, -16,
6, 15, 10, 9, -21, -12, 19, 11, 31, 17, 6, 16, 10, 9, -22, -13, 21, 11, 33, 18,
18, 16, -10, -8, -7, 6, -9, 26, 29, 29, 19, 17, -11, -8, -7, 6, -9, 28, 31, 31,
-3, 12, -15, 6, 6, 8, -10, -10, -10, 30, -3, 12, -16, 6, 6, 8, -10, 15, -10, -10,
28, 27, -16, 25, 16, -17, 14, 27, -8, -1, 32, 30, 29, -17, 27, 17, -18, 14, 29, -8,
30, -11, -10, 7, -10, -13, 13, 29, -6, 26, -1, 32, -12, -11, 7, -11, -14, 13, 31, -6,
20, 32, -7, -17, 7, 8, 27, 30, 28, 30, 28, 22, 34, -11, 31, -11, -11, -7, -18, 7,
29, -2, 6, 25, -5, 27, -4, 6, -10, -17, 8, 29, 32, 30, 32, 31, -2, 6, 27, -5,
-11, -3, -10, 30, 28, -10, 27, 28, 25, -14, 29, -4, 6, -11, -18, -2, -12, -3, -11, 32,
21, -13, 30, 6, -4, 7, 22, 8, -6, 23, 30, -11, 29, 30, 27, -15, 23, 32, -14, 32,
7, 24, 7, 6, -4, 7, 24, 8, 20, -6, 31, 25, -2,
7, 32, 26, 7,
} }
var yyDef = []int{ var yyDef = []int{
0, -2, 3, 0, -2, 2, 5, 0, 0, 20, 0, -2, 3, 0, -2, 2, 5, 0, 0, 20,
13, 44, 41, 12, 4, 0, 0, 11, 0, 0, 13, 47, 41, 12, 4, 0, 0, 11, 0, 44,
0, 0, 0, 0, 23, 0, 28, 0, 0, 0, 44, 44, 0, 0, 23, 0, 28, 0, 0, 0,
42, 0, 14, 13, 0, 0, 38, 39, 40, 30, 42, 0, 14, 13, 0, 0, 0, 0, 0, 0,
0, 28, 0, 26, 27, 32, 0, 21, 0, 0, 30, 0, 28, 0, 26, 27, 32, 0, 21, 0,
34, 48, 50, 51, 0, 0, 43, 0, 0, 0, 0, 34, 51, 53, 54, 0, 0, 43, 0, 0,
0, 28, 24, 31, 25, 29, 22, 33, 0, 44, 0, 0, 28, 38, 0, 39, 40, 24, 31, 25,
0, 0, 46, 0, 0, 16, 17, 0, 8, 35, 29, 22, 33, 0, 47, 0, 0, 49, 0, 0,
49, 42, 0, 45, 0, 6, 15, 0, 0, 0, 16, 17, 0, 8, 35, 0, 52, 42, 0, 48,
0, 36, 37, 47, 18, 19, 14, 9, 0, 0, 0, 6, 15, 0, 0, 0, 0, 45, 36, 37,
0, 0, 7, 50, 18, 19, 14, 9, 0, 0, 0, 0, 0,
0, 46, 0, 7,
} }
var yyTok1 = []int{ var yyTok1 = []int{
@ -187,21 +196,21 @@ var yyTok1 = []int{
3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
29, 30, 3, 3, 28, 3, 3, 3, 3, 3, 31, 32, 3, 3, 30, 3, 3, 3, 3, 3,
3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
3, 25, 3, 3, 3, 3, 3, 3, 3, 3, 3, 27, 3, 3, 3, 3, 3, 3, 3, 3,
3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
3, 31, 3, 32, 3, 3, 3, 3, 3, 3, 3, 33, 3, 34, 3, 3, 3, 3, 3, 3,
3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
3, 3, 3, 26, 3, 27, 3, 3, 3, 28, 3, 29,
} }
var yyTok2 = []int{ var yyTok2 = []int{
2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11,
12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21,
22, 23, 24, 22, 23, 24, 25, 26,
} }
var yyTok3 = []int{ var yyTok3 = []int{
0, 0,
@ -433,12 +442,12 @@ yydefault:
switch yynt { switch yynt {
case 5: case 5:
//line parser.y:74 //line parser.y:76
{ {
yylex.(*RulesLexer).parsedExpr = yyS[yypt-0].ruleNode yylex.(*RulesLexer).parsedExpr = yyS[yypt-0].ruleNode
} }
case 6: case 6:
//line parser.y:79 //line parser.y:81
{ {
rule, err := CreateRecordingRule(yyS[yypt-3].str, yyS[yypt-2].labelSet, yyS[yypt-0].ruleNode, yyS[yypt-4].boolean) rule, err := CreateRecordingRule(yyS[yypt-3].str, yyS[yypt-2].labelSet, yyS[yypt-0].ruleNode, yyS[yypt-4].boolean)
if err != nil { if err != nil {
@ -448,7 +457,7 @@ yydefault:
yylex.(*RulesLexer).parsedRules = append(yylex.(*RulesLexer).parsedRules, rule) yylex.(*RulesLexer).parsedRules = append(yylex.(*RulesLexer).parsedRules, rule)
} }
case 7: case 7:
//line parser.y:85 //line parser.y:87
{ {
rule, err := CreateAlertingRule(yyS[yypt-9].str, yyS[yypt-7].ruleNode, yyS[yypt-6].str, yyS[yypt-4].labelSet, yyS[yypt-2].str, yyS[yypt-0].str) rule, err := CreateAlertingRule(yyS[yypt-9].str, yyS[yypt-7].ruleNode, yyS[yypt-6].str, yyS[yypt-4].labelSet, yyS[yypt-2].str, yyS[yypt-0].str)
if err != nil { if err != nil {
@ -458,94 +467,94 @@ yydefault:
yylex.(*RulesLexer).parsedRules = append(yylex.(*RulesLexer).parsedRules, rule) yylex.(*RulesLexer).parsedRules = append(yylex.(*RulesLexer).parsedRules, rule)
} }
case 8: case 8:
//line parser.y:93 //line parser.y:95
{ {
yyVAL.str = "0s" yyVAL.str = "0s"
} }
case 9: case 9:
//line parser.y:95 //line parser.y:97
{ {
yyVAL.str = yyS[yypt-0].str yyVAL.str = yyS[yypt-0].str
} }
case 10: case 10:
//line parser.y:99 //line parser.y:101
{ {
yyVAL.boolean = false yyVAL.boolean = false
} }
case 11: case 11:
//line parser.y:101 //line parser.y:103
{ {
yyVAL.boolean = true yyVAL.boolean = true
} }
case 12: case 12:
//line parser.y:105
{
yyVAL.str = yyS[yypt-0].str
}
case 13:
//line parser.y:107 //line parser.y:107
{ {
yyVAL.str = yyS[yypt-0].str yyVAL.str = yyS[yypt-0].str
} }
case 13:
//line parser.y:109
{
yyVAL.str = yyS[yypt-0].str
}
case 14: case 14:
//line parser.y:111 //line parser.y:113
{ {
yyVAL.labelSet = clientmodel.LabelSet{} yyVAL.labelSet = clientmodel.LabelSet{}
} }
case 15: case 15:
//line parser.y:113 //line parser.y:115
{ {
yyVAL.labelSet = yyS[yypt-1].labelSet yyVAL.labelSet = yyS[yypt-1].labelSet
} }
case 16: case 16:
//line parser.y:115 //line parser.y:117
{ {
yyVAL.labelSet = clientmodel.LabelSet{} yyVAL.labelSet = clientmodel.LabelSet{}
} }
case 17: case 17:
//line parser.y:118 //line parser.y:120
{ {
yyVAL.labelSet = yyS[yypt-0].labelSet yyVAL.labelSet = yyS[yypt-0].labelSet
} }
case 18: case 18:
//line parser.y:120 //line parser.y:122
{ {
for k, v := range yyS[yypt-0].labelSet { for k, v := range yyS[yypt-0].labelSet {
yyVAL.labelSet[k] = v yyVAL.labelSet[k] = v
} }
} }
case 19: case 19:
//line parser.y:124 //line parser.y:126
{ {
yyVAL.labelSet = clientmodel.LabelSet{clientmodel.LabelName(yyS[yypt-2].str): clientmodel.LabelValue(yyS[yypt-0].str)} yyVAL.labelSet = clientmodel.LabelSet{clientmodel.LabelName(yyS[yypt-2].str): clientmodel.LabelValue(yyS[yypt-0].str)}
} }
case 20: case 20:
//line parser.y:128
{
yyVAL.labelMatchers = metric.LabelMatchers{}
}
case 21:
//line parser.y:130 //line parser.y:130
{ {
yyVAL.labelMatchers = metric.LabelMatchers{} yyVAL.labelMatchers = metric.LabelMatchers{}
} }
case 22: case 21:
//line parser.y:132 //line parser.y:132
{
yyVAL.labelMatchers = metric.LabelMatchers{}
}
case 22:
//line parser.y:134
{ {
yyVAL.labelMatchers = yyS[yypt-1].labelMatchers yyVAL.labelMatchers = yyS[yypt-1].labelMatchers
} }
case 23: case 23:
//line parser.y:136 //line parser.y:138
{ {
yyVAL.labelMatchers = metric.LabelMatchers{yyS[yypt-0].labelMatcher} yyVAL.labelMatchers = metric.LabelMatchers{yyS[yypt-0].labelMatcher}
} }
case 24: case 24:
//line parser.y:138 //line parser.y:140
{ {
yyVAL.labelMatchers = append(yyVAL.labelMatchers, yyS[yypt-0].labelMatcher) yyVAL.labelMatchers = append(yyVAL.labelMatchers, yyS[yypt-0].labelMatcher)
} }
case 25: case 25:
//line parser.y:142 //line parser.y:144
{ {
var err error var err error
yyVAL.labelMatcher, err = newLabelMatcher(yyS[yypt-1].str, clientmodel.LabelName(yyS[yypt-2].str), clientmodel.LabelValue(yyS[yypt-0].str)) yyVAL.labelMatcher, err = newLabelMatcher(yyS[yypt-1].str, clientmodel.LabelName(yyS[yypt-2].str), clientmodel.LabelValue(yyS[yypt-0].str))
@ -555,32 +564,32 @@ yydefault:
} }
} }
case 26: case 26:
//line parser.y:150 //line parser.y:152
{ {
yyVAL.str = "=" yyVAL.str = "="
} }
case 27: case 27:
//line parser.y:152 //line parser.y:154
{ {
yyVAL.str = yyS[yypt-0].str yyVAL.str = yyS[yypt-0].str
} }
case 28: case 28:
//line parser.y:156 //line parser.y:158
{ {
yyVAL.str = "0s" yyVAL.str = "0s"
} }
case 29: case 29:
//line parser.y:158 //line parser.y:160
{ {
yyVAL.str = yyS[yypt-0].str yyVAL.str = yyS[yypt-0].str
} }
case 30: case 30:
//line parser.y:162 //line parser.y:164
{ {
yyVAL.ruleNode = yyS[yypt-1].ruleNode yyVAL.ruleNode = yyS[yypt-1].ruleNode
} }
case 31: case 31:
//line parser.y:164 //line parser.y:166
{ {
var err error var err error
yyVAL.ruleNode, err = NewVectorSelector(yyS[yypt-2].labelMatchers, yyS[yypt-0].str) yyVAL.ruleNode, err = NewVectorSelector(yyS[yypt-2].labelMatchers, yyS[yypt-0].str)
@ -590,7 +599,7 @@ yydefault:
} }
} }
case 32: case 32:
//line parser.y:170 //line parser.y:172
{ {
var err error var err error
m, err := metric.NewLabelMatcher(metric.Equal, clientmodel.MetricNameLabel, clientmodel.LabelValue(yyS[yypt-2].str)) m, err := metric.NewLabelMatcher(metric.Equal, clientmodel.MetricNameLabel, clientmodel.LabelValue(yyS[yypt-2].str))
@ -606,7 +615,7 @@ yydefault:
} }
} }
case 33: case 33:
//line parser.y:179 //line parser.y:181
{ {
var err error var err error
yyVAL.ruleNode, err = NewFunctionCall(yyS[yypt-3].str, yyS[yypt-1].ruleNodeSlice) yyVAL.ruleNode, err = NewFunctionCall(yyS[yypt-3].str, yyS[yypt-1].ruleNodeSlice)
@ -616,7 +625,7 @@ yydefault:
} }
} }
case 34: case 34:
//line parser.y:185 //line parser.y:187
{ {
var err error var err error
yyVAL.ruleNode, err = NewFunctionCall(yyS[yypt-2].str, []ast.Node{}) yyVAL.ruleNode, err = NewFunctionCall(yyS[yypt-2].str, []ast.Node{})
@ -626,7 +635,7 @@ yydefault:
} }
} }
case 35: case 35:
//line parser.y:191 //line parser.y:193
{ {
var err error var err error
yyVAL.ruleNode, err = NewMatrixSelector(yyS[yypt-4].ruleNode, yyS[yypt-2].str, yyS[yypt-0].str) yyVAL.ruleNode, err = NewMatrixSelector(yyS[yypt-4].ruleNode, yyS[yypt-2].str, yyS[yypt-0].str)
@ -636,7 +645,7 @@ yydefault:
} }
} }
case 36: case 36:
//line parser.y:197 //line parser.y:199
{ {
var err error var err error
yyVAL.ruleNode, err = NewVectorAggregation(yyS[yypt-5].str, yyS[yypt-3].ruleNode, yyS[yypt-1].labelNameSlice, yyS[yypt-0].boolean) yyVAL.ruleNode, err = NewVectorAggregation(yyS[yypt-5].str, yyS[yypt-3].ruleNode, yyS[yypt-1].labelNameSlice, yyS[yypt-0].boolean)
@ -646,7 +655,7 @@ yydefault:
} }
} }
case 37: case 37:
//line parser.y:203 //line parser.y:205
{ {
var err error var err error
yyVAL.ruleNode, err = NewVectorAggregation(yyS[yypt-5].str, yyS[yypt-1].ruleNode, yyS[yypt-4].labelNameSlice, yyS[yypt-3].boolean) yyVAL.ruleNode, err = NewVectorAggregation(yyS[yypt-5].str, yyS[yypt-1].ruleNode, yyS[yypt-4].labelNameSlice, yyS[yypt-3].boolean)
@ -656,87 +665,112 @@ yydefault:
} }
} }
case 38: case 38:
//line parser.y:211 //line parser.y:213
{ {
var err error var err error
yyVAL.ruleNode, err = NewArithExpr(yyS[yypt-1].str, yyS[yypt-2].ruleNode, yyS[yypt-0].ruleNode) yyVAL.ruleNode, err = NewArithExpr(yyS[yypt-2].str, yyS[yypt-3].ruleNode, yyS[yypt-0].ruleNode, yyS[yypt-1].vectorMatching)
if err != nil { if err != nil {
yylex.Error(err.Error()) yylex.Error(err.Error())
return 1 return 1
} }
} }
case 39: case 39:
//line parser.y:217 //line parser.y:219
{ {
var err error var err error
yyVAL.ruleNode, err = NewArithExpr(yyS[yypt-1].str, yyS[yypt-2].ruleNode, yyS[yypt-0].ruleNode) yyVAL.ruleNode, err = NewArithExpr(yyS[yypt-2].str, yyS[yypt-3].ruleNode, yyS[yypt-0].ruleNode, yyS[yypt-1].vectorMatching)
if err != nil { if err != nil {
yylex.Error(err.Error()) yylex.Error(err.Error())
return 1 return 1
} }
} }
case 40: case 40:
//line parser.y:223 //line parser.y:225
{ {
var err error var err error
yyVAL.ruleNode, err = NewArithExpr(yyS[yypt-1].str, yyS[yypt-2].ruleNode, yyS[yypt-0].ruleNode) yyVAL.ruleNode, err = NewArithExpr(yyS[yypt-2].str, yyS[yypt-3].ruleNode, yyS[yypt-0].ruleNode, yyS[yypt-1].vectorMatching)
if err != nil { if err != nil {
yylex.Error(err.Error()) yylex.Error(err.Error())
return 1 return 1
} }
} }
case 41: case 41:
//line parser.y:229 //line parser.y:231
{ {
yyVAL.ruleNode = ast.NewScalarLiteral(yyS[yypt-0].num) yyVAL.ruleNode = ast.NewScalarLiteral(yyS[yypt-0].num)
} }
case 42: case 42:
//line parser.y:233 //line parser.y:235
{ {
yyVAL.boolean = false yyVAL.boolean = false
} }
case 43: case 43:
//line parser.y:235 //line parser.y:237
{ {
yyVAL.boolean = true yyVAL.boolean = true
} }
case 44: case 44:
//line parser.y:239 //line parser.y:241
{
yyVAL.vectorMatching = nil
}
case 45:
//line parser.y:243
{
var err error
yyVAL.vectorMatching, err = newVectorMatching("", yyS[yypt-1].labelNameSlice, nil)
if err != nil {
yylex.Error(err.Error())
return 1
}
}
case 46:
//line parser.y:249
{
var err error
yyVAL.vectorMatching, err = newVectorMatching(yyS[yypt-3].str, yyS[yypt-5].labelNameSlice, yyS[yypt-1].labelNameSlice)
if err != nil {
yylex.Error(err.Error())
return 1
}
}
case 47:
//line parser.y:257
{ {
yyVAL.labelNameSlice = clientmodel.LabelNames{} yyVAL.labelNameSlice = clientmodel.LabelNames{}
} }
case 45: case 48:
//line parser.y:241 //line parser.y:259
{ {
yyVAL.labelNameSlice = yyS[yypt-1].labelNameSlice yyVAL.labelNameSlice = yyS[yypt-1].labelNameSlice
} }
case 46: case 49:
//line parser.y:245 //line parser.y:263
{ {
yyVAL.labelNameSlice = clientmodel.LabelNames{clientmodel.LabelName(yyS[yypt-0].str)} yyVAL.labelNameSlice = clientmodel.LabelNames{clientmodel.LabelName(yyS[yypt-0].str)}
} }
case 47: case 50:
//line parser.y:247 //line parser.y:265
{ {
yyVAL.labelNameSlice = append(yyVAL.labelNameSlice, clientmodel.LabelName(yyS[yypt-0].str)) yyVAL.labelNameSlice = append(yyVAL.labelNameSlice, clientmodel.LabelName(yyS[yypt-0].str))
} }
case 48: case 51:
//line parser.y:251 //line parser.y:269
{ {
yyVAL.ruleNodeSlice = []ast.Node{yyS[yypt-0].ruleNode} yyVAL.ruleNodeSlice = []ast.Node{yyS[yypt-0].ruleNode}
} }
case 49: case 52:
//line parser.y:253 //line parser.y:271
{ {
yyVAL.ruleNodeSlice = append(yyVAL.ruleNodeSlice, yyS[yypt-0].ruleNode) yyVAL.ruleNodeSlice = append(yyVAL.ruleNodeSlice, yyS[yypt-0].ruleNode)
} }
case 50: case 53:
//line parser.y:257 //line parser.y:275
{ {
yyVAL.ruleNode = yyS[yypt-0].ruleNode yyVAL.ruleNode = yyS[yypt-0].ruleNode
} }
case 51: case 54:
//line parser.y:259 //line parser.y:277
{ {
yyVAL.ruleNode = ast.NewStringLiteral(yyS[yypt-0].str) yyVAL.ruleNode = ast.NewStringLiteral(yyS[yypt-0].str)
} }

View file

@ -630,6 +630,12 @@ func TestExpressions(t *testing.T) {
`request_duration_seconds_bucket{instance="ins2", job="job2", le="0.1"} => 40 @[%v]`, `request_duration_seconds_bucket{instance="ins2", job="job2", le="0.1"} => 40 @[%v]`,
`request_duration_seconds_bucket{instance="ins2", job="job2", le="0.2"} => 70 @[%v]`, `request_duration_seconds_bucket{instance="ins2", job="job2", le="0.2"} => 70 @[%v]`,
`request_duration_seconds_bucket{instance="ins2", job="job2", le="+Inf"} => 90 @[%v]`, `request_duration_seconds_bucket{instance="ins2", job="job2", le="+Inf"} => 90 @[%v]`,
`vector_matching_a{l="x"} => 10 @[%v]`,
`vector_matching_a{l="y"} => 20 @[%v]`,
`vector_matching_b{l="x"} => 40 @[%v]`,
`cpu_count{instance="1", type="smp"} => 200 @[%v]`,
`cpu_count{instance="0", type="smp"} => 100 @[%v]`,
`cpu_count{instance="0", type="numa"} => 300 @[%v]`,
}, },
}, },
{ {
@ -666,6 +672,191 @@ func TestExpressions(t *testing.T) {
`{job="api-server"} => 1000 @[%v]`, `{job="api-server"} => 1000 @[%v]`,
}, },
}, },
{
expr: `http_requests{group="canary"} and http_requests{instance="0"}`,
output: []string{
`http_requests{group="canary", instance="0", job="api-server"} => 300 @[%v]`,
`http_requests{group="canary", instance="0", job="app-server"} => 700 @[%v]`,
},
},
{
expr: `(http_requests{group="canary"} + 1) and http_requests{instance="0"}`,
output: []string{
`{group="canary", instance="0", job="api-server"} => 301 @[%v]`,
`{group="canary", instance="0", job="app-server"} => 701 @[%v]`,
},
},
{
expr: `(http_requests{group="canary"} + 1) and on(instance, job) http_requests{instance="0", group="production"}`,
output: []string{
`{group="canary", instance="0", job="api-server"} => 301 @[%v]`,
`{group="canary", instance="0", job="app-server"} => 701 @[%v]`,
},
},
{
expr: `(http_requests{group="canary"} + 1) and on(instance) http_requests{instance="0", group="production"}`,
output: []string{
`{group="canary", instance="0", job="api-server"} => 301 @[%v]`,
`{group="canary", instance="0", job="app-server"} => 701 @[%v]`,
},
},
{
expr: `http_requests{group="canary"} or http_requests{group="production"}`,
output: []string{
`http_requests{group="canary", instance="0", job="api-server"} => 300 @[%v]`,
`http_requests{group="canary", instance="0", job="app-server"} => 700 @[%v]`,
`http_requests{group="canary", instance="1", job="api-server"} => 400 @[%v]`,
`http_requests{group="canary", instance="1", job="app-server"} => 800 @[%v]`,
`http_requests{group="production", instance="0", job="api-server"} => 100 @[%v]`,
`http_requests{group="production", instance="0", job="app-server"} => 500 @[%v]`,
`http_requests{group="production", instance="1", job="api-server"} => 200 @[%v]`,
`http_requests{group="production", instance="1", job="app-server"} => 600 @[%v]`,
},
},
{
// On overlap the rhs samples must be dropped.
expr: `(http_requests{group="canary"} + 1) or http_requests{instance="1"}`,
output: []string{
`{group="canary", instance="0", job="api-server"} => 301 @[%v]`,
`{group="canary", instance="0", job="app-server"} => 701 @[%v]`,
`{group="canary", instance="1", job="api-server"} => 401 @[%v]`,
`{group="canary", instance="1", job="app-server"} => 801 @[%v]`,
`http_requests{group="production", instance="1", job="api-server"} => 200 @[%v]`,
`http_requests{group="production", instance="1", job="app-server"} => 600 @[%v]`,
},
},
{
// Matching only on instance excludes everything that has instance=0/1 but includes
// entries without the instance label.
expr: `(http_requests{group="canary"} + 1) or on(instance) (http_requests or cpu_count or vector_matching_a)`,
output: []string{
`{group="canary", instance="0", job="api-server"} => 301 @[%v]`,
`{group="canary", instance="0", job="app-server"} => 701 @[%v]`,
`{group="canary", instance="1", job="api-server"} => 401 @[%v]`,
`{group="canary", instance="1", job="app-server"} => 801 @[%v]`,
`vector_matching_a{l="x"} => 10 @[%v]`,
`vector_matching_a{l="y"} => 20 @[%v]`,
},
},
{
expr: `http_requests{group="canary"} / on(instance,job) http_requests{group="production"}`,
output: []string{
`{instance="0", job="api-server"} => 3 @[%v]`,
`{instance="0", job="app-server"} => 1.4 @[%v]`,
`{instance="1", job="api-server"} => 2 @[%v]`,
`{instance="1", job="app-server"} => 1.3333333333333333 @[%v]`,
},
},
{
// Include labels must guarantee uniquely identifiable time series.
expr: `http_requests{group="production"} / on(instance) group_left(group) cpu_count{type="smp"}`,
output: []string{}, // Empty result returned on error (see TODOs).
},
{
// Many-to-many matching is not allowed.
expr: `http_requests{group="production"} / on(instance) group_left(job,type) cpu_count`,
output: []string{}, // Empty result returned on error (see TODOs).
},
{
// Many-to-one matching must be explicit.
expr: `http_requests{group="production"} / on(instance) cpu_count{type="smp"}`,
output: []string{}, // Empty result returned on error (see TODOs).
},
{
expr: `http_requests{group="production"} / on(instance) group_left(job) cpu_count{type="smp"}`,
output: []string{
`{instance="1", job="api-server"} => 1 @[%v]`,
`{instance="0", job="app-server"} => 5 @[%v]`,
`{instance="1", job="app-server"} => 3 @[%v]`,
`{instance="0", job="api-server"} => 1 @[%v]`,
},
},
{
// Ensure sidedness of grouping preserves operand sides.
expr: `cpu_count{type="smp"} / on(instance) group_right(job) http_requests{group="production"}`,
output: []string{
`{instance="1", job="app-server"} => 0.3333333333333333 @[%v]`,
`{instance="0", job="app-server"} => 0.2 @[%v]`,
`{instance="1", job="api-server"} => 1 @[%v]`,
`{instance="0", job="api-server"} => 1 @[%v]`,
},
},
{
// Include labels from both sides.
expr: `http_requests{group="production"} / on(instance) group_left(job) cpu_count{type="smp"}`,
output: []string{
`{instance="1", job="api-server"} => 1 @[%v]`,
`{instance="0", job="app-server"} => 5 @[%v]`,
`{instance="1", job="app-server"} => 3 @[%v]`,
`{instance="0", job="api-server"} => 1 @[%v]`,
},
},
{
expr: `http_requests{group="production"} < on(instance,job) http_requests{group="canary"}`,
output: []string{
`{instance="1", job="app-server"} => 600 @[%v]`,
`{instance="0", job="app-server"} => 500 @[%v]`,
`{instance="1", job="api-server"} => 200 @[%v]`,
`{instance="0", job="api-server"} => 100 @[%v]`,
},
},
{
expr: `http_requests{group="production"} > on(instance,job) http_requests{group="canary"}`,
output: []string{},
},
{
expr: `http_requests{group="production"} == on(instance,job) http_requests{group="canary"}`,
output: []string{},
},
{
expr: `http_requests > on(instance) group_left(group,job) cpu_count{type="smp"}`,
output: []string{
`{group="canary", instance="0", job="app-server"} => 700 @[%v]`,
`{group="canary", instance="1", job="app-server"} => 800 @[%v]`,
`{group="canary", instance="0", job="api-server"} => 300 @[%v]`,
`{group="canary", instance="1", job="api-server"} => 400 @[%v]`,
`{group="production", instance="0", job="app-server"} => 500 @[%v]`,
`{group="production", instance="1", job="app-server"} => 600 @[%v]`,
},
},
{
expr: `http_requests / on(instance) 3`,
shouldFail: true,
},
{
expr: `3 / on(instance) http_requests_total`,
shouldFail: true,
},
{
expr: `3 / on(instance) 3`,
shouldFail: true,
},
{
// Missing label list for grouping mod.
expr: `http_requests{group="production"} / on(instance) group_left cpu_count{type="smp"}`,
shouldFail: true,
},
{
// No group mod allowed for logical operations.
expr: `http_requests{group="production"} or on(instance) group_left(type) cpu_count{type="smp"}`,
shouldFail: true,
},
{
// No group mod allowed for logical operations.
expr: `http_requests{group="production"} and on(instance) group_left(type) cpu_count{type="smp"}`,
shouldFail: true,
},
{
// No duplicate use of label.
expr: `http_requests{group="production"} + on(instance) group_left(job,instance) cpu_count{type="smp"}`,
shouldFail: true,
},
{
expr: `{l="x"} + on(__name__) {l="y"}`,
output: []string{
`vector_matching_a => 30 @[%v]`,
},
},
{ {
expr: `absent(nonexistent)`, expr: `absent(nonexistent)`,
output: []string{ output: []string{
@ -975,10 +1166,14 @@ func TestExpressions(t *testing.T) {
t.Errorf("%d. Test should fail, but didn't", i) t.Errorf("%d. Test should fail, but didn't", i)
} }
failed := false failed := false
resultStr := ast.EvalToString(testExpr, testEvalTime, ast.Text, storage, stats.NewTimerGroup()) resultStr := ast.EvalToString(testExpr, testEvalTime, ast.Text, storage, stats.NewTimerGroup())
resultLines := strings.Split(resultStr, "\n") resultLines := strings.Split(resultStr, "\n")
if len(exprTest.output) != len(resultLines) { if len(exprTest.output) == 0 && strings.Trim(resultStr, "\n") == "" {
// expected and received empty vector, everything is fine
continue
} else if len(exprTest.output) != len(resultLines) {
t.Errorf("%d. Number of samples in expected and actual output don't match", i) t.Errorf("%d. Number of samples in expected and actual output don't match", i)
failed = true failed = true
} }