Implement a COUNT ... BY aggregation operator.

This also removes the now obsolete scalar count() function and corrects the
expressions test naming (broken in
2202cd71c9 (L6R59))
so that the expression tests will actually run.
This commit is contained in:
Julius Volz 2013-05-08 16:35:16 +02:00
parent 6551356af4
commit 0877680761
7 changed files with 37 additions and 29 deletions

View File

@ -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{

View File

@ -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},

View File

@ -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]
}

View File

@ -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 {

View File

@ -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 }

View File

@ -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() {

View File

@ -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,