Like Prometheus, but for logs.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 
loki/pkg/logql/evaluator_test.go

388 lines
7.0 KiB

package logql
import (
"math"
"testing"
"time"
"github.com/prometheus/prometheus/model/labels"
"github.com/prometheus/prometheus/promql"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/grafana/loki/v3/pkg/logql/syntax"
)
func TestDefaultEvaluator_DivideByZero(t *testing.T) {
op, err := syntax.MergeBinOp(syntax.OpTypeDiv,
&promql.Sample{
T: 1, F: 1,
},
&promql.Sample{
T: 1, F: 0,
},
false,
false,
false,
)
require.NoError(t, err)
require.Equal(t, true, math.IsNaN(op.F))
binOp, err := syntax.MergeBinOp(syntax.OpTypeMod,
&promql.Sample{
T: 1, F: 1,
},
&promql.Sample{
T: 1, F: 0,
},
false,
false,
false,
)
require.NoError(t, err)
require.Equal(t, true, math.IsNaN(binOp.F))
}
func TestDefaultEvaluator_Sortable(t *testing.T) {
logqlSort := `sort(rate(({app=~"foo|bar"} |~".+bar")[1m])) `
sortable, err := Sortable(LiteralParams{queryString: logqlSort, queryExpr: syntax.MustParseExpr(logqlSort)})
if err != nil {
t.Fatal(err)
}
require.Equal(t, true, sortable)
logqlSum := `sum(rate(({app=~"foo|bar"} |~".+bar")[1m])) `
sortableSum, err := Sortable(LiteralParams{queryString: logqlSum, queryExpr: syntax.MustParseExpr(logqlSum)})
if err != nil {
t.Fatal(err)
}
require.Equal(t, false, sortableSum)
}
func TestEvaluator_mergeBinOpComparisons(t *testing.T) {
for _, tc := range []struct {
desc string
op string
lhs, rhs *promql.Sample
expected *promql.Sample
}{
{
`eq_0`,
syntax.OpTypeCmpEQ,
&promql.Sample{
F: 1,
},
&promql.Sample{
F: 1,
},
&promql.Sample{
F: 1,
},
},
{
`eq_1`,
syntax.OpTypeCmpEQ,
&promql.Sample{
F: 1,
},
&promql.Sample{
F: 0,
},
&promql.Sample{
F: 0,
},
},
{
`neq_0`,
syntax.OpTypeNEQ,
&promql.Sample{
F: 0,
},
&promql.Sample{
F: 1,
},
&promql.Sample{
F: 1,
},
},
{
`neq_1`,
syntax.OpTypeNEQ,
&promql.Sample{
F: 1,
},
&promql.Sample{
F: 1,
},
&promql.Sample{
F: 0,
},
},
{
`gt_0`,
syntax.OpTypeGT,
&promql.Sample{
F: 1,
},
&promql.Sample{
F: 1,
},
&promql.Sample{
F: 0,
},
},
{
`gt_1`,
syntax.OpTypeGT,
&promql.Sample{
F: 1,
},
&promql.Sample{
F: 0,
},
&promql.Sample{
F: 1,
},
},
{
`lt_0`,
syntax.OpTypeLT,
&promql.Sample{
F: 1,
},
&promql.Sample{
F: 1,
},
&promql.Sample{
F: 0,
},
},
{
`lt_1`,
syntax.OpTypeLT,
&promql.Sample{
F: 0,
},
&promql.Sample{
F: 1,
},
&promql.Sample{
F: 1,
},
},
{
`gte_0`,
syntax.OpTypeGTE,
&promql.Sample{
F: 1,
},
&promql.Sample{
F: 1,
},
&promql.Sample{
F: 1,
},
},
{
`gt_1`,
syntax.OpTypeGTE,
&promql.Sample{
F: 0,
},
&promql.Sample{
F: 1,
},
&promql.Sample{
F: 0,
},
},
{
`lte_0`,
syntax.OpTypeLTE,
&promql.Sample{
F: 0,
},
&promql.Sample{
F: 0,
},
&promql.Sample{
F: 1,
},
},
{
`lte_1`,
syntax.OpTypeLTE,
&promql.Sample{
F: 1,
},
&promql.Sample{
F: 0,
},
&promql.Sample{
F: 0,
},
},
} {
t.Run(tc.desc, func(t *testing.T) {
// comparing a binop should yield the unfiltered (non-nil variant) regardless
// of whether this is a vector-vector comparison or not.
op, err := syntax.MergeBinOp(tc.op, tc.lhs, tc.rhs, false, false, false)
require.NoError(t, err)
require.Equal(t, tc.expected, op)
op2, err := syntax.MergeBinOp(tc.op, tc.lhs, tc.rhs, false, false, true)
require.NoError(t, err)
require.Equal(t, tc.expected, op2)
op3, err := syntax.MergeBinOp(tc.op, tc.lhs, nil, false, false, true)
require.NoError(t, err)
require.Nil(t, op3)
// test filtered variants
if tc.expected.F == 0 {
// ensure zeroed predicates are filtered out
op, err := syntax.MergeBinOp(tc.op, tc.lhs, tc.rhs, false, true, false)
require.NoError(t, err)
require.Nil(t, op)
op2, err := syntax.MergeBinOp(tc.op, tc.lhs, tc.rhs, false, true, true)
require.NoError(t, err)
require.Nil(t, op2)
// for vector-vector comparisons, ensure that nil right hand sides
// translate into nil results
op3, err := syntax.MergeBinOp(tc.op, tc.lhs, nil, false, true, true)
require.NoError(t, err)
require.Nil(t, op3)
}
})
}
}
func TestEmptyNestedEvaluator(t *testing.T) {
for _, tc := range []struct {
desc string
ev StepEvaluator
}{
{
desc: "LiteralStepEvaluator",
ev: &LiteralStepEvaluator{nextEv: &emptyEvaluator{}},
},
{
desc: "LabelReplaceEvaluator",
ev: &LabelReplaceEvaluator{nextEvaluator: &emptyEvaluator{}},
},
{
desc: "BinOpStepEvaluator",
ev: &BinOpStepEvaluator{rse: &emptyEvaluator{}},
},
} {
t.Run(tc.desc, func(t *testing.T) {
ok, _, _ := tc.ev.Next()
require.False(t, ok)
})
}
}
func TestLiteralStepEvaluator(t *testing.T) {
cases := []struct {
name string
expr *LiteralStepEvaluator
expected []float64
}{
{
name: "vector op scalar",
// e.g: sum(count_over_time({app="foo"}[1m])) > 20
expr: &LiteralStepEvaluator{
nextEv: newReturnVectorEvaluator([]float64{20, 21, 22, 23}),
val: 20,
inverted: true, // set to true, because literal expression is not on left.
op: ">",
},
expected: []float64{21, 22, 23},
},
{
name: "scalar op vector",
// e.g: 20 < sum(count_over_time({app="foo"}[1m]))
expr: &LiteralStepEvaluator{
nextEv: newReturnVectorEvaluator([]float64{20, 21, 22, 23}),
val: 20,
inverted: false,
op: "<",
},
expected: []float64{21, 22, 23},
},
}
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
ok, _, got := tc.expr.Next()
require.True(t, ok)
vecs := got.SampleVector()
gotSamples := make([]float64, 0, len(vecs))
for _, v := range vecs {
gotSamples = append(gotSamples, v.F)
}
assert.Equal(t, tc.expected, gotSamples)
})
}
}
type emptyEvaluator struct{}
func (*emptyEvaluator) Next() (ok bool, ts int64, r StepResult) {
return false, 0, nil
}
func (*emptyEvaluator) Close() error {
return nil
}
func (*emptyEvaluator) Error() error {
return nil
}
func (*emptyEvaluator) Explain(Node) {}
// returnVectorEvaluator returns elements of vector
// passed in, everytime it's `Next()` is called. Used for testing.
type returnVectorEvaluator struct {
vec promql.Vector
}
func (e *returnVectorEvaluator) Next() (ok bool, ts int64, r StepResult) {
return true, 0, SampleVector(e.vec)
}
func (*returnVectorEvaluator) Close() error {
return nil
}
func (*returnVectorEvaluator) Error() error {
return nil
}
func (*returnVectorEvaluator) Explain(Node) {
}
func newReturnVectorEvaluator(vec []float64) *returnVectorEvaluator {
testTime := time.Now().Unix()
pvec := make([]promql.Sample, 0, len(vec))
for _, v := range vec {
pvec = append(pvec, promql.Sample{
T: testTime,
F: v,
Metric: labels.FromStrings("foo", "bar"),
})
}
return &returnVectorEvaluator{
vec: pvec,
}
}