influxdb/query/promql/query_test.go

597 lines
13 KiB
Go

package promql
import (
"testing"
"time"
"github.com/google/go-cmp/cmp"
"github.com/google/go-cmp/cmp/cmpopts"
"github.com/influxdata/flux"
"github.com/influxdata/flux/ast"
"github.com/influxdata/flux/semantic"
"github.com/influxdata/flux/semantic/semantictest"
"github.com/influxdata/flux/stdlib/influxdata/influxdb"
"github.com/influxdata/flux/stdlib/universe"
)
func TestParsePromQL(t *testing.T) {
tests := []struct {
name string
promql string
opts []Option
want interface{}
wantErr bool
}{
{
name: "testing comments",
promql: "# http_requests_total",
want: &Comment{
Source: "# http_requests_total",
},
},
{
name: "vector",
promql: "http_requests_total",
want: &Selector{
Name: "http_requests_total",
},
},
{
name: "vector with label matching",
promql: `http_requests_total{a="b"}`,
want: &Selector{
Name: "http_requests_total",
LabelMatchers: []*LabelMatcher{
&LabelMatcher{
Name: "a",
Kind: Equal,
Value: &StringLiteral{
String: "b",
},
},
},
},
},
{
name: "vector with two labels matching",
promql: `http_requests_total{a="b", c!="d"}`,
want: &Selector{
Name: "http_requests_total",
LabelMatchers: []*LabelMatcher{
&LabelMatcher{
Name: "a",
Kind: Equal,
Value: &StringLiteral{
String: "b",
},
},
&LabelMatcher{
Name: "c",
Kind: NotEqual,
Value: &StringLiteral{
String: "d",
},
},
},
},
},
{
name: "vector with numeric label matcher",
promql: `http_requests_total{a=500}`,
want: &Selector{
Name: "http_requests_total",
LabelMatchers: []*LabelMatcher{
&LabelMatcher{
Name: "a",
Kind: Equal,
Value: &Number{
Val: 500,
},
},
},
},
},
{
name: "invalid operator in label matcher",
promql: `http_requests_total{a > 500}`,
wantErr: true,
want: "",
},
{
name: "no metric name",
promql: `{}`,
wantErr: true,
want: "",
},
{
name: "vector with multiple regular expressions",
promql: `foo{a="b", foo!="bar", test=~"test", bar!~"baz"}`,
want: &Selector{
Name: "foo",
LabelMatchers: []*LabelMatcher{
&LabelMatcher{
Name: "a",
Kind: Equal,
Value: &StringLiteral{
String: "b",
},
},
&LabelMatcher{
Name: "foo",
Kind: NotEqual,
Value: &StringLiteral{
String: "bar",
},
},
&LabelMatcher{
Name: "test",
Kind: RegexMatch,
Value: &StringLiteral{
String: "test",
},
},
&LabelMatcher{
Name: "bar",
Kind: RegexNoMatch,
Value: &StringLiteral{
String: "baz",
},
},
},
},
},
{
name: "vector with offset",
promql: "http_requests_total OFFSET 5m",
want: &Selector{
Name: "http_requests_total",
Offset: time.Minute * 5,
},
},
{
name: "vector with range",
promql: "http_requests_total[5y]",
want: &Selector{
Name: "http_requests_total",
Range: time.Hour * 24 * 365 * 5,
},
},
{
name: "vector with label matches, range, and offset",
promql: `test{a="b"}[5y] OFFSET 3d`,
want: &Selector{
Name: "test",
Offset: time.Hour * 24 * 3,
Range: time.Hour * 24 * 365 * 5,
LabelMatchers: []*LabelMatcher{
&LabelMatcher{
Name: "a",
Kind: Equal,
Value: &StringLiteral{
String: "b",
},
},
},
},
},
{
name: "Min function with group by and keep common",
promql: `MIN (some_metric) by (foo) keep_common`,
want: &AggregateExpr{
Op: &Operator{
Kind: MinKind,
},
Selector: &Selector{
Name: "some_metric",
},
Aggregate: &Aggregate{
By: true,
Labels: []*Identifier{
&Identifier{
Name: "foo",
},
},
},
},
},
{
name: "count function with group by and keep common reversed with label",
promql: `COUNT by (foo) keep_common (some_metric)`,
want: &AggregateExpr{
Op: &Operator{
Kind: CountKind,
},
Selector: &Selector{
Name: "some_metric",
},
Aggregate: &Aggregate{
By: true,
Labels: []*Identifier{
&Identifier{
Name: "foo",
},
},
},
},
},
{
name: "avg function with group by and no keep common reversed with label",
promql: `avg by (foo)(some_metric)`,
want: &AggregateExpr{
Op: &Operator{
Kind: AvgKind,
},
Selector: &Selector{
Name: "some_metric",
},
Aggregate: &Aggregate{
By: true,
Labels: []*Identifier{
&Identifier{
Name: "foo",
},
},
},
},
},
{
name: "sum function with multiple group by and keep common",
promql: `sum (some_metric) by (foo,bar) keep_common`,
want: &AggregateExpr{
Op: &Operator{
Kind: SumKind,
},
Selector: &Selector{
Name: "some_metric",
},
Aggregate: &Aggregate{
By: true,
Labels: []*Identifier{
&Identifier{
Name: "foo",
},
&Identifier{
Name: "bar",
},
},
},
},
},
{
name: "sum function with group by",
promql: `sum by (foo)(some_metric)`,
want: &AggregateExpr{
Op: &Operator{
Kind: SumKind,
},
Selector: &Selector{
Name: "some_metric",
},
Aggregate: &Aggregate{
By: true,
Labels: []*Identifier{
&Identifier{
Name: "foo",
},
},
},
},
},
{
name: "sum function without reversed label",
promql: `sum without (foo) (some_metric)`,
want: &AggregateExpr{
Op: &Operator{
Kind: SumKind,
},
Selector: &Selector{
Name: "some_metric",
},
Aggregate: &Aggregate{
Without: true,
Labels: []*Identifier{
&Identifier{
Name: "foo",
},
},
},
},
},
{
name: "count_values with argument",
promql: `count_values("version", build_version)`,
want: &AggregateExpr{
Op: &Operator{
Kind: CountValuesKind,
Arg: &StringLiteral{
String: "version",
},
},
Selector: &Selector{
Name: "build_version",
},
},
},
{
name: "sum over a range",
promql: `sum(node_cpu{_measurement="m0"}[170h])`,
want: &AggregateExpr{
Op: &Operator{
Kind: SumKind,
},
Selector: &Selector{
Name: "node_cpu",
Range: 170 * time.Hour,
LabelMatchers: []*LabelMatcher{
{
Name: "_measurement",
Kind: Equal,
Value: &StringLiteral{
String: "m0",
},
},
},
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := ParsePromQL(tt.promql, tt.opts...)
if (err != nil) != tt.wantErr {
t.Errorf("ParsePromQL() %s error = %v, wantErr %v", tt.promql, err, tt.wantErr)
return
}
if !cmp.Equal(tt.want, got) {
t.Errorf("ParsePromQL() = %s -got/+want %s", tt.promql, cmp.Diff(tt.want, got))
}
})
}
}
func TestBuild(t *testing.T) {
tests := []struct {
name string
promql string
opts []Option
want *flux.Spec
wantErr bool
}{
{
name: "aggregate with count without a group by",
promql: `count(node_cpu{mode="user",cpu="cpu2"})`,
want: &flux.Spec{
Operations: []*flux.Operation{
{
ID: flux.OperationID("from"),
Spec: &influxdb.FromOpSpec{Bucket: "prometheus"},
},
{
ID: "where",
Spec: &universe.FilterOpSpec{
Fn: &semantic.FunctionExpression{
Block: &semantic.FunctionBlock{
Parameters: &semantic.FunctionParameters{
List: []*semantic.FunctionParameter{{Key: &semantic.Identifier{Name: "r"}}},
},
Body: &semantic.LogicalExpression{
Operator: ast.AndOperator,
Left: &semantic.LogicalExpression{
Operator: ast.AndOperator,
Left: &semantic.BinaryExpression{
Operator: ast.EqualOperator,
Left: &semantic.MemberExpression{
Object: &semantic.IdentifierExpression{
Name: "r",
},
Property: "_metric",
},
Right: &semantic.StringLiteral{
Value: "node_cpu",
},
},
Right: &semantic.BinaryExpression{
Operator: ast.EqualOperator,
Left: &semantic.MemberExpression{
Object: &semantic.IdentifierExpression{
Name: "r",
},
Property: "mode",
},
Right: &semantic.StringLiteral{
Value: "user",
},
},
},
Right: &semantic.BinaryExpression{
Operator: ast.EqualOperator,
Left: &semantic.MemberExpression{
Object: &semantic.IdentifierExpression{
Name: "r",
},
Property: "cpu",
},
Right: &semantic.StringLiteral{
Value: "cpu2",
},
},
},
},
},
},
},
{
ID: flux.OperationID("count"), Spec: &universe.CountOpSpec{},
},
},
Edges: []flux.Edge{
{
Parent: flux.OperationID("from"),
Child: flux.OperationID("where"),
},
{
Parent: flux.OperationID("where"),
Child: flux.OperationID("count"),
},
},
},
},
{
name: "range of time but no aggregates",
promql: `node_cpu{mode="user"}[2m] offset 5m`,
want: &flux.Spec{
Operations: []*flux.Operation{
{
ID: flux.OperationID("from"),
Spec: &influxdb.FromOpSpec{Bucket: "prometheus"},
},
{
ID: flux.OperationID("range"),
Spec: &universe.RangeOpSpec{
Start: flux.Time{Relative: -time.Minute * 7},
},
},
{
ID: "where",
Spec: &universe.FilterOpSpec{
Fn: &semantic.FunctionExpression{
Block: &semantic.FunctionBlock{
Parameters: &semantic.FunctionParameters{
List: []*semantic.FunctionParameter{{Key: &semantic.Identifier{Name: "r"}}},
},
Body: &semantic.LogicalExpression{
Operator: ast.AndOperator,
Left: &semantic.BinaryExpression{
Operator: ast.EqualOperator,
Left: &semantic.MemberExpression{
Object: &semantic.IdentifierExpression{
Name: "r",
},
Property: "_metric",
},
Right: &semantic.StringLiteral{
Value: "node_cpu",
},
},
Right: &semantic.BinaryExpression{
Operator: ast.EqualOperator,
Left: &semantic.MemberExpression{
Object: &semantic.IdentifierExpression{
Name: "r",
},
Property: "mode",
},
Right: &semantic.StringLiteral{
Value: "user",
},
},
},
},
},
},
},
},
Edges: []flux.Edge{
{
Parent: flux.OperationID("from"),
Child: flux.OperationID("range"),
},
{
Parent: flux.OperationID("range"),
Child: flux.OperationID("where"),
},
},
},
},
{
name: "sum over a range",
promql: `sum(node_cpu{_measurement="m0"}[170h])`,
want: &flux.Spec{
Operations: []*flux.Operation{
{
ID: flux.OperationID("from"),
Spec: &influxdb.FromOpSpec{Bucket: "prometheus"},
},
{
ID: flux.OperationID("range"),
Spec: &universe.RangeOpSpec{
Start: flux.Time{Relative: -170 * time.Hour},
},
},
{
ID: "where",
Spec: &universe.FilterOpSpec{
Fn: &semantic.FunctionExpression{
Block: &semantic.FunctionBlock{
Parameters: &semantic.FunctionParameters{
List: []*semantic.FunctionParameter{{Key: &semantic.Identifier{Name: "r"}}},
},
Body: &semantic.LogicalExpression{
Operator: ast.AndOperator,
Left: &semantic.BinaryExpression{
Operator: ast.EqualOperator,
Left: &semantic.MemberExpression{
Object: &semantic.IdentifierExpression{
Name: "r",
},
Property: "_metric",
},
Right: &semantic.StringLiteral{
Value: "node_cpu",
},
},
Right: &semantic.BinaryExpression{
Operator: ast.EqualOperator,
Left: &semantic.MemberExpression{
Object: &semantic.IdentifierExpression{
Name: "r",
},
Property: "_measurement",
},
Right: &semantic.StringLiteral{
Value: "m0",
},
},
},
},
},
},
},
{
ID: flux.OperationID("sum"), Spec: &universe.SumOpSpec{},
},
},
Edges: []flux.Edge{
{
Parent: flux.OperationID("from"),
Child: flux.OperationID("range"),
},
{
Parent: flux.OperationID("range"),
Child: flux.OperationID("where"),
},
{
Parent: flux.OperationID("where"),
Child: flux.OperationID("sum"),
},
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := Build(tt.promql, tt.opts...)
if (err != nil) != tt.wantErr {
t.Errorf("Build() %s error = %v, wantErr %v", tt.promql, err, tt.wantErr)
return
}
opts := append(semantictest.CmpOptions, []cmp.Option{cmp.AllowUnexported(flux.Spec{}), cmpopts.IgnoreUnexported(flux.Spec{})}...)
if !cmp.Equal(tt.want, got, opts...) {
t.Errorf("Build() = %s -want/+got\n%s", tt.promql, cmp.Diff(tt.want, got, opts...))
}
})
}
}