diff --git a/rules/ast/ast.go b/rules/ast/ast.go index ad148b97a..94d25b7f7 100644 --- a/rules/ast/ast.go +++ b/rules/ast/ast.go @@ -82,6 +82,7 @@ const ( AVG MIN MAX + COUNT ) // ---------------------------------------------------------------------------- @@ -317,8 +318,13 @@ func labelIntersection(metric1, metric2 model.Metric) model.Metric { func (node *VectorAggregation) groupedAggregationsToVector(aggregations map[string]*groupedAggregation, timestamp time.Time) Vector { vector := Vector{} for _, aggregation := range aggregations { - if node.aggrType == AVG { + switch node.aggrType { + case AVG: aggregation.value = aggregation.value / model.SampleValue(aggregation.groupCount) + case COUNT: + aggregation.value = model.SampleValue(aggregation.groupCount) + default: + // For other aggregations, we already have the right value. } sample := model.Sample{ Metric: aggregation.labels, @@ -351,6 +357,10 @@ func (node *VectorAggregation) Eval(timestamp time.Time, view *viewAdapter) Vect if groupedResult.value > sample.Value { groupedResult.value = sample.Value } + case COUNT: + groupedResult.groupCount++ + default: + panic("Unknown aggregation type") } } else { result[groupingKey] = &groupedAggregation{ diff --git a/rules/ast/functions.go b/rules/ast/functions.go index a77ab9819..2c833c500 100644 --- a/rules/ast/functions.go +++ b/rules/ast/functions.go @@ -69,11 +69,6 @@ func timeImpl(timestamp time.Time, view *viewAdapter, args []Node) interface{} { return model.SampleValue(time.Now().Unix()) } -// === count(vector VectorNode) model.SampleValue === -func countImpl(timestamp time.Time, view *viewAdapter, args []Node) interface{} { - return model.SampleValue(len(args[0].(VectorNode).Eval(timestamp, view))) -} - // === delta(matrix MatrixNode, isCounter ScalarNode) Vector === func deltaImpl(timestamp time.Time, view *viewAdapter, args []Node) interface{} { matrixNode := args[0].(MatrixNode) @@ -253,12 +248,6 @@ func sampleVectorImpl(timestamp time.Time, view *viewAdapter, args []Node) inter } var functions = map[string]*Function{ - "count": { - name: "count", - argTypes: []ExprType{VECTOR}, - returnType: SCALAR, - callFn: countImpl, - }, "delta": { name: "delta", argTypes: []ExprType{MATRIX, SCALAR}, diff --git a/rules/ast/printer.go b/rules/ast/printer.go index 7f322e2d2..51bf81b67 100644 --- a/rules/ast/printer.go +++ b/rules/ast/printer.go @@ -50,10 +50,11 @@ func (opType BinOpType) String() string { func (aggrType AggrType) String() string { aggrTypeMap := map[AggrType]string{ - SUM: "SUM", - AVG: "AVG", - MIN: "MIN", - MAX: "MAX", + SUM: "SUM", + AVG: "AVG", + MIN: "MIN", + MAX: "MAX", + COUNT: "COUNT", } return aggrTypeMap[aggrType] } diff --git a/rules/helpers.go b/rules/helpers.go index 6633c1132..251867a6d 100644 --- a/rules/helpers.go +++ b/rules/helpers.go @@ -55,10 +55,11 @@ func NewVectorAggregation(aggrTypeStr string, vector ast.Node, groupBy []model.L return nil, fmt.Errorf("Operand of %v aggregation must be of vector type", aggrTypeStr) } var aggrTypes = map[string]ast.AggrType{ - "SUM": ast.SUM, - "MAX": ast.MAX, - "MIN": ast.MIN, - "AVG": ast.AVG, + "SUM": ast.SUM, + "MAX": ast.MAX, + "MIN": ast.MIN, + "AVG": ast.AVG, + "COUNT": ast.COUNT, } aggrType, ok := aggrTypes[aggrTypeStr] if !ok { diff --git a/rules/lexer.l b/rules/lexer.l index 766120e6e..5eaa47bd9 100644 --- a/rules/lexer.l +++ b/rules/lexer.l @@ -44,8 +44,8 @@ WITH|with { return WITH } PERMANENT|permanent { return PERMANENT } BY|by { return GROUP_OP } -AVG|SUM|MAX|MIN { yylval.str = yytext; return AGGR_OP } -avg|sum|max|min { yylval.str = strings.ToUpper(yytext); return AGGR_OP } +AVG|SUM|MAX|MIN|COUNT { yylval.str = yytext; return AGGR_OP } +avg|sum|max|min|count { yylval.str = strings.ToUpper(yytext); return AGGR_OP } \<|>|AND|OR|and|or { yylval.str = strings.ToUpper(yytext); return CMP_OP } ==|!=|>=|<= { yylval.str = yytext; return CMP_OP } [+\-] { yylval.str = yytext; return ADDITIVE_OP } diff --git a/rules/lexer.l.go b/rules/lexer.l.go index da632e1fc..fc5f8d73f 100644 --- a/rules/lexer.l.go +++ b/rules/lexer.l.go @@ -380,7 +380,7 @@ var yyrules []yyrule = []yyrule{{regexp.MustCompile("[^\\n]"), nil, []yystartcon return yyactionreturn{GROUP_OP, yyRT_USER_RETURN} } return yyactionreturn{0, yyRT_FALLTHROUGH} -}}, {regexp.MustCompile("AVG|SUM|MAX|MIN"), nil, []yystartcondition{}, false, func() (yyar yyactionreturn) { +}}, {regexp.MustCompile("AVG|SUM|MAX|MIN|COUNT"), nil, []yystartcondition{}, false, func() (yyar yyactionreturn) { defer func() { if r := recover(); r != nil { if r != "yyREJECT" { @@ -394,7 +394,7 @@ var yyrules []yyrule = []yyrule{{regexp.MustCompile("[^\\n]"), nil, []yystartcon return yyactionreturn{AGGR_OP, yyRT_USER_RETURN} } return yyactionreturn{0, yyRT_FALLTHROUGH} -}}, {regexp.MustCompile("avg|sum|max|min"), nil, []yystartcondition{}, false, func() (yyar yyactionreturn) { +}}, {regexp.MustCompile("avg|sum|max|min|count"), nil, []yystartcondition{}, false, func() (yyar yyactionreturn) { defer func() { if r := recover(); r != nil { if r != "yyREJECT" { @@ -509,7 +509,6 @@ var yyrules []yyrule = []yyrule{{regexp.MustCompile("[^\\n]"), nil, []yystartcon yylval.num = model.SampleValue(num) return yyactionreturn{NUMBER, yyRT_USER_RETURN} } - return yyactionreturn{0, yyRT_FALLTHROUGH} }}, {regexp.MustCompile("\\\"(\\\\[^\\n]|[^\\\\\"])*\\\""), nil, []yystartcondition{}, false, func() (yyar yyactionreturn) { defer func() { diff --git a/rules/rules_test.go b/rules/rules_test.go index 331a4c5ec..6ade851af 100644 --- a/rules/rules_test.go +++ b/rules/rules_test.go @@ -58,7 +58,7 @@ func newTestStorage(t test.Tester) (storage *metric.TieredStorage, closer test.C return } -func ExpressionTests(t *testing.T) { +func TestExpressions(t *testing.T) { // Labels in expected output need to be alphabetically sorted. var expressionTests = []struct { expr string @@ -81,6 +81,14 @@ func ExpressionTests(t *testing.T) { }, fullRanges: 0, intervalRanges: 8, + }, { + expr: "COUNT(http_requests) BY (job)", + output: []string{ + "http_requests{job='api-server'} => 4 @[%v]", + "http_requests{job='app-server'} => 4 @[%v]", + }, + fullRanges: 0, + intervalRanges: 8, }, { expr: "SUM(http_requests) BY (job, group)", output: []string{ @@ -116,10 +124,10 @@ func ExpressionTests(t *testing.T) { fullRanges: 0, intervalRanges: 8, }, { - expr: "SUM(http_requests) BY (job) - count(http_requests)", + expr: "SUM(http_requests) BY (job) - COUNT(http_requests) BY (job)", output: []string{ - "http_requests{job='api-server'} => 992 @[%v]", - "http_requests{job='app-server'} => 2592 @[%v]", + "http_requests{job='api-server'} => 996 @[%v]", + "http_requests{job='app-server'} => 2596 @[%v]", }, fullRanges: 0, intervalRanges: 8,