mirror of https://github.com/grafana/loki
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.
1095 lines
38 KiB
1095 lines
38 KiB
package syntax
|
|
|
|
import (
|
|
"fmt"
|
|
"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/log"
|
|
)
|
|
|
|
var labelBar, _ = ParseLabels("{app=\"bar\"}")
|
|
|
|
func Test_logSelectorExpr_String(t *testing.T) {
|
|
t.Parallel()
|
|
tests := []struct {
|
|
selector string
|
|
expectFilter bool
|
|
}{
|
|
{`{foo="bar"}`, false},
|
|
{`{foo="bar", bar!="baz"}`, false},
|
|
{`{foo="bar", bar!="baz"} != "bip" !~ ".+bop"`, true},
|
|
{`{foo="bar"} |= "baz" |~ "blip" |> "qux" !> "waldo" != "flip" !~ "flap"`, true},
|
|
{`{foo="bar", bar!="baz"} |= ""`, false},
|
|
{`{foo="bar", bar!="baz"} |= "" |= ip("::1")`, true},
|
|
{`{foo="bar", bar!="baz"} |= "" != ip("127.0.0.1")`, true},
|
|
{`{foo="bar", bar!="baz"} |~ ""`, false},
|
|
{`{foo="bar", bar!="baz"} |~ ".*"`, false},
|
|
{`{foo="bar", bar!="baz"} |= "" |= ""`, false},
|
|
{`{foo="bar", bar!="baz"} |~ "" |= "" |~ ".*"`, false},
|
|
{`{foo="bar", bar!="baz"} |> ""`, true},
|
|
{`{foo="bar", bar!="baz"} |> "<_>"`, true},
|
|
{`{foo="bar", bar!="baz"} |> "<_>" !> "<_> <_>"`, true},
|
|
{`{foo="bar", bar!="baz"} != "bip" !~ ".+bop" |> "<_> bop <_>" | json`, true},
|
|
{`{foo="bar"} |= "baz" |~ "blip" != "flip" !~ "flap" | logfmt`, true},
|
|
{`{foo="bar"} |= "baz" |~ "blip" != "flip" !~ "flap" | logfmt --strict`, true},
|
|
{`{foo="bar"} |= "baz" |~ "blip" != "flip" !~ "flap" | logfmt --strict --keep-empty`, true},
|
|
{`{foo="bar"} |= "baz" |~ "blip" != "flip" !~ "flap" | unpack | foo>5`, true},
|
|
{`{foo="bar"} |= "baz" |~ "blip" != "flip" !~ "flap" | pattern "<foo> bar <buzz>" | foo>5`, true},
|
|
{`{foo="bar"} |= "baz" |~ "blip" != "flip" !~ "flap" | logfmt | b>=10GB`, true},
|
|
{`{foo="bar"} |= "baz" |~ "blip" != "flip" !~ "flap" | logfmt | b=ip("127.0.0.1")`, true},
|
|
{`{foo="bar"} |= "baz" |~ "blip" != "flip" !~ "flap" | logfmt | b=ip("127.0.0.1") | level="error"`, true},
|
|
{`{foo="bar"} |= "baz" |~ "blip" != "flip" !~ "flap" | logfmt --strict b="foo" | b=ip("127.0.0.1") | level="error"`, true},
|
|
{`{foo="bar"} |= "baz" |~ "blip" != "flip" !~ "flap" | logfmt --strict --keep-empty b="foo" | b=ip("127.0.0.1") | level="error"`, true},
|
|
{`{foo="bar"} |= "baz" |~ "blip" != "flip" !~ "flap" | logfmt | b=ip("127.0.0.1") | level="error" | c=ip("::1")`, true}, // chain inside label filters.
|
|
{`{foo="bar"} |= "baz" |~ "blip" != "flip" !~ "flap" | regexp "(?P<foo>foo|bar)"`, true},
|
|
{`{foo="bar"} |= "baz" |~ "blip" != "flip" !~ "flap" | regexp "(?P<foo>foo|bar)" | ( ( foo<5.01 , bar>20ms ) or foo="bar" ) | line_format "blip{{.boop}}bap" | label_format foo=bar,bar="blip{{.blop}}"`, true},
|
|
{`{foo="bar"} | logfmt | counter>-1 | counter>=-1 | counter<-1 | counter<=-1 | counter!=-1 | counter==-1`, true},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
tt := tt
|
|
t.Run(tt.selector, func(t *testing.T) {
|
|
t.Parallel()
|
|
expr, err := ParseLogSelector(tt.selector, true)
|
|
if err != nil {
|
|
t.Fatalf("failed to parse log selector: %s", err)
|
|
}
|
|
p, err := expr.Pipeline()
|
|
if err != nil {
|
|
t.Fatalf("failed to get filter: %s", err)
|
|
}
|
|
if !tt.expectFilter {
|
|
require.Equal(t, log.NewNoopPipeline(), p)
|
|
}
|
|
if expr.String() != tt.selector {
|
|
t.Fatalf("error expected: %s got: %s", tt.selector, expr.String())
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func Test_SampleExpr_String(t *testing.T) {
|
|
t.Parallel()
|
|
for _, tc := range []string{
|
|
`rate( ( {job="mysql"} |="error" !="timeout" ) [10s] )>-1`,
|
|
`rate( ( {job="mysql"} |="error" !="timeout" ) [10s] )`,
|
|
`absent_over_time( ( {job="mysql"} |="error" !="timeout" ) [10s] )`,
|
|
`absent_over_time( ( {job="mysql"} |="error" !="timeout" ) [10s] offset 10d )`,
|
|
`vector(123)`,
|
|
`sort(sum by(a) (rate( ( {job="mysql"} |="error" !="timeout" ) [10s] ) ))`,
|
|
`sort_desc(sum by(a) (rate( ( {job="mysql"} |="error" !="timeout" ) [10s] ) ))`,
|
|
`sum without(a) ( rate ( ( {job="mysql"} |="error" !="timeout" ) [10s] ) )`,
|
|
`sum by(a) (rate( ( {job="mysql"} |="error" !="timeout" ) [10s] ) )`,
|
|
`sum(count_over_time({job="mysql"}[5m]))`,
|
|
`sum(count_over_time({job="mysql"}[5m] offset 10m))`,
|
|
`sum(count_over_time({job="mysql"} | json [5m]))`,
|
|
`sum(count_over_time({job="mysql"} | json [5m] offset 10m))`,
|
|
`sum(count_over_time({job="mysql"} | logfmt [5m]))`,
|
|
`sum(count_over_time({job="mysql"} | logfmt --strict [5m] offset 10m))`,
|
|
`sum(count_over_time({job="mysql"} | pattern "<foo> bar <buzz>" | json [5m]))`,
|
|
`sum(count_over_time({job="mysql"} | unpack | json [5m]))`,
|
|
`sum(count_over_time({job="mysql"} | regexp "(?P<foo>foo|bar)" [5m]))`,
|
|
`sum(count_over_time({job="mysql"} | regexp "(?P<foo>foo|bar)" [5m] offset 10y))`,
|
|
`topk(10,sum(rate({region="us-east1"}[5m])) by (name))`,
|
|
`topk by (name)(10,sum(rate({region="us-east1"}[5m])))`,
|
|
`avg( rate( ( {job="nginx"} |= "GET" ) [10s] ) ) by (region)`,
|
|
`avg(min_over_time({job="nginx"} |= "GET" | unwrap foo[10s])) by (region)`,
|
|
`avg(min_over_time({job="nginx"} |= "GET" | unwrap foo[10s] offset 10m)) by (region)`,
|
|
`sum by (cluster) (count_over_time({job="mysql"}[5m]))`,
|
|
`sum by (cluster) (count_over_time({job="mysql"}[5m] offset 10m))`,
|
|
`sum by (cluster) (count_over_time({job="mysql"}[5m])) / sum by (cluster) (count_over_time({job="postgres"}[5m])) `,
|
|
`sum by (cluster) (count_over_time({job="mysql"}[5m] offset 10m)) / sum by (cluster) (count_over_time({job="postgres"}[5m] offset 10m)) `,
|
|
`
|
|
sum by (cluster) (count_over_time({job="postgres"}[5m])) /
|
|
sum by (cluster) (count_over_time({job="postgres"}[5m])) /
|
|
sum by (cluster) (count_over_time({job="postgres"}[5m]))
|
|
`,
|
|
`sum by (cluster) (count_over_time({job="mysql"}[5m])) / min(count_over_time({job="mysql"}[5m])) `,
|
|
`sum by (job) (
|
|
count_over_time({namespace="tns"} |= "level=error"[5m])
|
|
/
|
|
count_over_time({namespace="tns"}[5m])
|
|
)`,
|
|
`stdvar_over_time({app="foo"} |= "bar" | json | latency >= 250ms or ( status_code < 500 and status_code > 200)
|
|
| line_format "blip{{ .foo }}blop {{.status_code}}" | label_format foo=bar,status_code="buzz{{.bar}}" | unwrap foo [5m])`,
|
|
`stdvar_over_time({app="foo"} |= "bar" | json | latency >= 250ms or ( status_code < 500 and status_code > 200)
|
|
| line_format "blip{{ .foo }}blop {{.status_code}}" | label_format foo=bar,status_code="buzz{{.bar}}" | unwrap foo [5m] offset 10m)`,
|
|
`sum_over_time({namespace="tns"} |= "level=error" | json |foo>=5,bar<25ms|unwrap latency [5m])`,
|
|
`sum by (job) (
|
|
sum_over_time({namespace="tns"} |= "level=error" | json | foo=5 and bar<25ms | unwrap latency[5m])
|
|
/
|
|
count_over_time({namespace="tns"} | logfmt | label_format foo=bar[5m])
|
|
)`,
|
|
`sum by (job) (
|
|
sum_over_time({namespace="tns"} |= "level=error" | json | foo=5 and bar<25ms | unwrap bytes(latency)[5m])
|
|
/
|
|
count_over_time({namespace="tns"} | logfmt | label_format foo=bar[5m])
|
|
)`,
|
|
`sum by (job) (
|
|
sum_over_time(
|
|
{namespace="tns"} |= "level=error" | json | avg=5 and bar<25ms | unwrap duration(latency) [5m]
|
|
)
|
|
/
|
|
count_over_time({namespace="tns"} | logfmt | label_format foo=bar[5m])
|
|
)`,
|
|
`sum_over_time({namespace="tns"} |= "level=error" | json |foo>=5,bar<25ms | unwrap latency | __error__!~".*" | foo >5[5m])`,
|
|
`last_over_time({namespace="tns"} |= "level=error" | json |foo>=5,bar<25ms | unwrap latency | __error__!~".*" | foo >5[5m])`,
|
|
`first_over_time({namespace="tns"} |= "level=error" | json |foo>=5,bar<25ms | unwrap latency | __error__!~".*" | foo >5[5m])`,
|
|
`absent_over_time({namespace="tns"} |= "level=error" | json |foo>=5,bar<25ms | unwrap latency | __error__!~".*" | foo >5[5m])`,
|
|
`sum by (job) (
|
|
sum_over_time(
|
|
{namespace="tns"} |= "level=error" | json | avg=5 and bar<25ms | unwrap duration(latency) | __error__!~".*" [5m]
|
|
)
|
|
/
|
|
count_over_time({namespace="tns"} | logfmt | label_format foo=bar[5m])
|
|
)`,
|
|
`label_replace(
|
|
sum by (job) (
|
|
sum_over_time(
|
|
{namespace="tns"} |= "level=error" | json | avg=5 and bar<25ms | unwrap duration(latency) | __error__!~".*" [5m]
|
|
)
|
|
/
|
|
count_over_time({namespace="tns"} | logfmt | label_format foo=bar[5m])
|
|
),
|
|
"foo",
|
|
"$1",
|
|
"service",
|
|
"(.*):.*"
|
|
)
|
|
`,
|
|
`10 / (5/2)`,
|
|
`(count_over_time({job="postgres"}[5m])/2) or vector(2)`,
|
|
`10 / (count_over_time({job="postgres"}[5m])/2)`,
|
|
`{app="foo"} | json response_status="response.status.code", first_param="request.params[0]"`,
|
|
`label_replace(
|
|
sum by (job) (
|
|
sum_over_time(
|
|
{namespace="tns"} |= "level=error" | json | avg=5 and bar<25ms | unwrap duration(latency) | __error__!~".*" [5m] offset 1h
|
|
)
|
|
/
|
|
count_over_time({namespace="tns"} | logfmt | label_format foo=bar[5m] offset 1h)
|
|
),
|
|
"foo",
|
|
"$1",
|
|
"service",
|
|
"(.*):.*"
|
|
)
|
|
`,
|
|
`(((
|
|
sum by(typename,pool,commandname,colo)(sum_over_time({_namespace_="appspace", _schema_="appspace-1min", pool=~"r1testlvs", colo=~"slc|lvs|rno", env!~"(pre-production|sandbox)"} | logfmt | status!="0" | ( ( type=~"(?i)^(Error|Exception|Fatal|ERRPAGE|ValidationError)$" or typename=~"(?i)^(Error|Exception|Fatal|ERRPAGE|ValidationError)$" ) or status=~"(?i)^(Error|Exception|Fatal|ERRPAGE|ValidationError)$" ) | commandname=~"(?i).*|UNSET" | unwrap sumcount[5m])) / 60)
|
|
or on ()
|
|
((sum by(typename,pool,commandname,colo)(sum_over_time({_namespace_="appspace", _schema_="appspace-15min", pool=~"r1testlvs", colo=~"slc|lvs|rno", env!~"(pre-production|sandbox)"} | logfmt | status!="0" | ( ( type=~"(?i)^(Error|Exception|Fatal|ERRPAGE|ValidationError)$" or typename=~"(?i)^(Error|Exception|Fatal|ERRPAGE|ValidationError)$" ) or status=~"(?i)^(Error|Exception|Fatal|ERRPAGE|ValidationError)$" ) | commandname=~"(?i).*|UNSET" | unwrap sumcount[5m])) / 15) / 60))
|
|
or on ()
|
|
((sum by(typename,pool,commandname,colo) (sum_over_time({_namespace_="appspace", _schema_="appspace-1h", pool=~"r1testlvs", colo=~"slc|lvs|rno", env!~"(pre-production|sandbox)"} | logfmt | status!="0" | ( ( type=~"(?i)^(Error|Exception|Fatal|ERRPAGE|ValidationError)$" or typename=~"(?i)^(Error|Exception|Fatal|ERRPAGE|ValidationError)$" ) or status=~"(?i)^(Error|Exception|Fatal|ERRPAGE|ValidationError)$" ) | commandname=~"(?i).*|UNSET" | unwrap sumcount[5m])) / 60) / 60))`,
|
|
`{app="foo"} | logfmt code="response.code", IPAddress="host"`,
|
|
} {
|
|
t.Run(tc, func(t *testing.T) {
|
|
expr, err := ParseExpr(tc)
|
|
require.Nil(t, err)
|
|
|
|
expr2, err := ParseExpr(expr.String())
|
|
require.Nil(t, err)
|
|
require.Equal(t, expr, expr2)
|
|
})
|
|
}
|
|
}
|
|
|
|
func Test_SampleExpr_String_Fail(t *testing.T) {
|
|
t.Parallel()
|
|
for _, tc := range []string{
|
|
`topk(0, sum(rate({region="us-east1"}[5m])) by (name))`,
|
|
`topk by (name)(0,sum(rate({region="us-east1"}[5m])))`,
|
|
`bottomk(0, sum(rate({region="us-east1"}[5m])) by (name))`,
|
|
`bottomk by (name)(0,sum(rate({region="us-east1"}[5m])))`,
|
|
} {
|
|
t.Run(tc, func(t *testing.T) {
|
|
_, err := ParseExpr(tc)
|
|
require.ErrorContains(t, err, "parse error : invalid parameter (must be greater than 0)")
|
|
})
|
|
}
|
|
}
|
|
|
|
func Test_SampleExpr_Sort_Fail(t *testing.T) {
|
|
t.Parallel()
|
|
for _, tc := range []string{
|
|
`sort(sum by(a) (rate( ( {job="mysql"} |="error" !="timeout" ) [10s] ) )) by (app)`,
|
|
`sort_desc(sum by(a) (rate( ( {job="mysql"} |="error" !="timeout" ) [10s] ) )) by (app)`,
|
|
} {
|
|
t.Run(tc, func(t *testing.T) {
|
|
_, err := ParseExpr(tc)
|
|
require.ErrorContains(t, err, "sort and sort_desc doesn't allow grouping by")
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestMatcherGroups(t *testing.T) {
|
|
for i, tc := range []struct {
|
|
query string
|
|
exp []MatcherRange
|
|
}{
|
|
{
|
|
query: `{job="foo"}`,
|
|
exp: []MatcherRange{
|
|
{
|
|
Matchers: []*labels.Matcher{
|
|
labels.MustNewMatcher(labels.MatchEqual, "job", "foo"),
|
|
},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
query: `count_over_time({job="foo"}[5m]) / count_over_time({job="bar"}[5m] offset 10m)`,
|
|
exp: []MatcherRange{
|
|
{
|
|
Interval: 5 * time.Minute,
|
|
Matchers: []*labels.Matcher{
|
|
labels.MustNewMatcher(labels.MatchEqual, "job", "foo"),
|
|
},
|
|
},
|
|
{
|
|
Interval: 5 * time.Minute,
|
|
Offset: 10 * time.Minute,
|
|
Matchers: []*labels.Matcher{
|
|
labels.MustNewMatcher(labels.MatchEqual, "job", "bar"),
|
|
},
|
|
},
|
|
},
|
|
},
|
|
} {
|
|
t.Run(fmt.Sprint(i), func(t *testing.T) {
|
|
expr, err := ParseExpr(tc.query)
|
|
require.Nil(t, err)
|
|
out, err := MatcherGroups(expr)
|
|
require.Nil(t, err)
|
|
require.Equal(t, tc.exp, out)
|
|
})
|
|
}
|
|
}
|
|
|
|
func Test_NilFilterDoesntPanic(t *testing.T) {
|
|
t.Parallel()
|
|
for _, tc := range []string{
|
|
`{namespace="dev", container_name="cart"} |= "" |= "bloop"`,
|
|
`{namespace="dev", container_name="cart"} |= "bleep" |= ""`,
|
|
`{namespace="dev", container_name="cart"} |= "bleep" |= "" |= "bloop"`,
|
|
`{namespace="dev", container_name="cart"} |= "bleep" |= "" |= "bloop"`,
|
|
`{namespace="dev", container_name="cart"} |= "bleep" |= "bloop" |= ""`,
|
|
`{namespace="dev", container_name="cart"} !> ""`,
|
|
} {
|
|
t.Run(tc, func(t *testing.T) {
|
|
expr, err := ParseLogSelector(tc, true)
|
|
require.Nil(t, err)
|
|
|
|
p, err := expr.Pipeline()
|
|
require.Nil(t, err)
|
|
_, _, matches := p.ForStream(labelBar).Process(0, []byte("bleepbloop"))
|
|
|
|
require.True(t, matches)
|
|
})
|
|
}
|
|
}
|
|
|
|
type linecheck struct {
|
|
l string
|
|
e bool
|
|
}
|
|
|
|
func Test_FilterMatcher(t *testing.T) {
|
|
t.Parallel()
|
|
for _, tt := range []struct {
|
|
q string
|
|
|
|
expectedMatchers []*labels.Matcher
|
|
// test line against the resulting filter, if empty filter should also be nil
|
|
lines []linecheck
|
|
}{
|
|
{
|
|
`{app="foo",cluster=~".+bar"}`,
|
|
[]*labels.Matcher{
|
|
mustNewMatcher(labels.MatchEqual, "app", "foo"),
|
|
mustNewMatcher(labels.MatchRegexp, "cluster", ".+bar"),
|
|
},
|
|
nil,
|
|
},
|
|
{
|
|
`{app!="foo",cluster=~".+bar",bar!~".?boo"}`,
|
|
[]*labels.Matcher{
|
|
mustNewMatcher(labels.MatchNotEqual, "app", "foo"),
|
|
mustNewMatcher(labels.MatchRegexp, "cluster", ".+bar"),
|
|
mustNewMatcher(labels.MatchNotRegexp, "bar", ".?boo"),
|
|
},
|
|
nil,
|
|
},
|
|
{
|
|
`{app="foo"} |= "foo"`,
|
|
[]*labels.Matcher{
|
|
mustNewMatcher(labels.MatchEqual, "app", "foo"),
|
|
},
|
|
[]linecheck{{"foobar", true}, {"bar", false}},
|
|
},
|
|
{
|
|
`{app="foo"} |= "foo" != "bar"`,
|
|
[]*labels.Matcher{
|
|
mustNewMatcher(labels.MatchEqual, "app", "foo"),
|
|
},
|
|
[]linecheck{{"foobuzz", true}, {"bar", false}},
|
|
},
|
|
{
|
|
`{app="foo"} |= "foo" !~ "f.*b"`,
|
|
[]*labels.Matcher{
|
|
mustNewMatcher(labels.MatchEqual, "app", "foo"),
|
|
},
|
|
[]linecheck{{"foo", true}, {"bar", false}, {"foobar", false}},
|
|
},
|
|
{
|
|
`{app="foo"} |= "foo" |~ "f.*b"`,
|
|
[]*labels.Matcher{
|
|
mustNewMatcher(labels.MatchEqual, "app", "foo"),
|
|
},
|
|
[]linecheck{{"foo", false}, {"bar", false}, {"foobar", true}},
|
|
},
|
|
{
|
|
`{app="foo"} |~ "foo"`,
|
|
[]*labels.Matcher{
|
|
mustNewMatcher(labels.MatchEqual, "app", "foo"),
|
|
},
|
|
[]linecheck{{"foo", true}, {"bar", false}, {"foobar", true}},
|
|
},
|
|
{
|
|
`{app="foo"} |> "foo <_>"`,
|
|
[]*labels.Matcher{
|
|
mustNewMatcher(labels.MatchEqual, "app", "foo"),
|
|
},
|
|
[]linecheck{{"foo bar", true}, {"foo", false}},
|
|
},
|
|
{
|
|
`{app="foo"} !> "foo <_>"`,
|
|
[]*labels.Matcher{
|
|
mustNewMatcher(labels.MatchEqual, "app", "foo"),
|
|
},
|
|
[]linecheck{{"foo bar", false}, {"foo", true}},
|
|
},
|
|
{
|
|
`{app="foo"} |~ "foo\\.bar\\.baz"`,
|
|
[]*labels.Matcher{
|
|
mustNewMatcher(labels.MatchEqual, "app", "foo"),
|
|
},
|
|
[]linecheck{{"foo", false}, {"bar", false}, {"foo.bar.baz", true}},
|
|
},
|
|
{
|
|
"{app=\"foo\"} | logfmt | field =~ `foo\\.bar`",
|
|
[]*labels.Matcher{
|
|
mustNewMatcher(labels.MatchEqual, "app", "foo"),
|
|
},
|
|
[]linecheck{{"field=foo", false}, {"field=bar", false}, {"field=foo.bar", true}},
|
|
},
|
|
{
|
|
`{app="foo"} | logfmt | duration > 1s and total_bytes < 1GB`,
|
|
[]*labels.Matcher{
|
|
mustNewMatcher(labels.MatchEqual, "app", "foo"),
|
|
},
|
|
[]linecheck{{"duration=5m total_bytes=5kB", true}, {"duration=1s total_bytes=256B", false}, {"duration=0s", false}},
|
|
},
|
|
{
|
|
`{app="foo"} |= "foo" or "bar"`,
|
|
[]*labels.Matcher{
|
|
mustNewMatcher(labels.MatchEqual, "app", "foo"),
|
|
},
|
|
[]linecheck{{"foo", true}, {"bar", true}, {"none", false}},
|
|
},
|
|
{
|
|
`{app="foo"} |= "test" |= "foo" or "bar"`,
|
|
[]*labels.Matcher{
|
|
mustNewMatcher(labels.MatchEqual, "app", "foo"),
|
|
},
|
|
[]linecheck{{"test foo", true}, {"test bar", true}, {"none", false}},
|
|
},
|
|
{
|
|
`{app="foo"} |= "test" |= "foo" or "bar" or "baz"`,
|
|
[]*labels.Matcher{
|
|
mustNewMatcher(labels.MatchEqual, "app", "foo"),
|
|
},
|
|
[]linecheck{{"test foo", true}, {"test bar", true}, {"test baz", true}, {"baz", false}, {"bar", false}, {"foo", false}, {"none", false}},
|
|
},
|
|
{
|
|
`{app="foo"} |= "test" |= "foo" or "bar" or "baz" |= "car"`,
|
|
[]*labels.Matcher{
|
|
mustNewMatcher(labels.MatchEqual, "app", "foo"),
|
|
},
|
|
[]linecheck{{"car test foo", true}, {"car test bar", true}, {"car test baz", true}, {"baz", false}, {"bar", false}, {"test", false}, {"foo", false}, {"car", false}, {"none", false}},
|
|
},
|
|
{
|
|
`{app="foo"} |= "test" |= "foo" or "bar" or "baz"`,
|
|
[]*labels.Matcher{
|
|
mustNewMatcher(labels.MatchEqual, "app", "foo"),
|
|
},
|
|
[]linecheck{{"test foo", true}, {"test bar", true}, {"test baz", true}, {"none", false}},
|
|
},
|
|
{
|
|
`{app="foo"} |= "foo" or "bar" |= "buzz" or "fizz"`,
|
|
[]*labels.Matcher{
|
|
mustNewMatcher(labels.MatchEqual, "app", "foo"),
|
|
},
|
|
[]linecheck{{"foo buzz", true}, {"bar fizz", true}, {"foo", false}, {"bar", false}, {"none", false}},
|
|
},
|
|
{
|
|
`{app="foo"} |~ "foo" or "bar"`,
|
|
[]*labels.Matcher{
|
|
mustNewMatcher(labels.MatchEqual, "app", "foo"),
|
|
},
|
|
[]linecheck{{"foo", true}, {"bar", true}, {"none", false}},
|
|
},
|
|
{
|
|
`{app="foo"} |~ "test" |~ "foo" or "bar"`,
|
|
[]*labels.Matcher{
|
|
mustNewMatcher(labels.MatchEqual, "app", "foo"),
|
|
},
|
|
[]linecheck{{"test foo", true}, {"test bar", true}, {"none", false}},
|
|
},
|
|
{
|
|
`{app="foo"} |~ "test" |~ "foo" or "bar" or "baz"`,
|
|
[]*labels.Matcher{
|
|
mustNewMatcher(labels.MatchEqual, "app", "foo"),
|
|
},
|
|
[]linecheck{{"test foo", true}, {"test bar", true}, {"test baz", true}, {"baz", false}, {"bar", false}, {"foo", false}, {"none", false}},
|
|
},
|
|
{
|
|
`{app="foo"} |~ "test" |~ "foo" or "bar" or "baz" |~ "car"`,
|
|
[]*labels.Matcher{
|
|
mustNewMatcher(labels.MatchEqual, "app", "foo"),
|
|
},
|
|
[]linecheck{{"car test foo", true}, {"car test bar", true}, {"car test baz", true}, {"baz", false}, {"bar", false}, {"test", false}, {"foo", false}, {"car", false}, {"none", false}},
|
|
},
|
|
{
|
|
`{app="foo"} != "foo" or "bar"`,
|
|
[]*labels.Matcher{
|
|
mustNewMatcher(labels.MatchEqual, "app", "foo"),
|
|
},
|
|
[]linecheck{{"foo", false}, {"bar", false}, {"none", true}},
|
|
},
|
|
{
|
|
`{app="foo"} != "test" != "foo" or "bar"`,
|
|
[]*labels.Matcher{
|
|
mustNewMatcher(labels.MatchEqual, "app", "foo"),
|
|
},
|
|
[]linecheck{{"test", false}, {"foo", false}, {"bar", false}, {"none", true}},
|
|
},
|
|
{
|
|
`{app="foo"} != "test" != "foo" or "bar" or "baz"`,
|
|
[]*labels.Matcher{
|
|
mustNewMatcher(labels.MatchEqual, "app", "foo"),
|
|
},
|
|
[]linecheck{{"test", false}, {"foo", false}, {"bar", false}, {"baz", false}, {"none", true}},
|
|
},
|
|
{
|
|
`{app="foo"} != "test" != "foo" or "bar" or "baz" != "car"`,
|
|
[]*labels.Matcher{
|
|
mustNewMatcher(labels.MatchEqual, "app", "foo"),
|
|
},
|
|
[]linecheck{{"test", false}, {"foo", false}, {"bar", false}, {"baz", false}, {"car", false}, {"none", true}},
|
|
},
|
|
|
|
{
|
|
`{app="foo"} |~ "foo" or "bar"`,
|
|
[]*labels.Matcher{
|
|
mustNewMatcher(labels.MatchEqual, "app", "foo"),
|
|
},
|
|
[]linecheck{{"foo", true}, {"bar", true}, {"none", false}},
|
|
},
|
|
{
|
|
`{app="foo"} !~ "foo" or "bar"`,
|
|
[]*labels.Matcher{
|
|
mustNewMatcher(labels.MatchEqual, "app", "foo"),
|
|
},
|
|
[]linecheck{{"foo", false}, {"bar", false}, {"none", true}},
|
|
},
|
|
{
|
|
`{app="foo"} !~ "test" !~ "foo" or "bar"`,
|
|
[]*labels.Matcher{
|
|
mustNewMatcher(labels.MatchEqual, "app", "foo"),
|
|
},
|
|
[]linecheck{{"test", false}, {"foo", false}, {"bar", false}, {"none", true}},
|
|
},
|
|
{
|
|
`{app="foo"} !~ "test" !~ "foo" or "bar" or "baz"`,
|
|
[]*labels.Matcher{
|
|
mustNewMatcher(labels.MatchEqual, "app", "foo"),
|
|
},
|
|
[]linecheck{{"test", false}, {"foo", false}, {"bar", false}, {"baz", false}, {"none", true}},
|
|
},
|
|
{
|
|
`{app="foo"} !~ "test" !~ "foo" or "bar" or "baz" !~ "car"`,
|
|
[]*labels.Matcher{
|
|
mustNewMatcher(labels.MatchEqual, "app", "foo"),
|
|
},
|
|
[]linecheck{{"test", false}, {"foo", false}, {"bar", false}, {"baz", false}, {"car", false}, {"none", true}},
|
|
},
|
|
{
|
|
`{app="foo"} |= ip("127.0.0.1") or "foo"`,
|
|
[]*labels.Matcher{
|
|
mustNewMatcher(labels.MatchEqual, "app", "foo"),
|
|
},
|
|
[]linecheck{{"foo", true}, {"bar", false}, {"127.0.0.2", false}, {"127.0.0.1", true}},
|
|
},
|
|
{
|
|
`{app="foo"} != ip("127.0.0.1") or "foo"`,
|
|
[]*labels.Matcher{
|
|
mustNewMatcher(labels.MatchEqual, "app", "foo"),
|
|
},
|
|
[]linecheck{{"foo", false}, {"bar", true}, {"127.0.0.2", true}, {"127.0.0.1", false}},
|
|
},
|
|
{
|
|
`{app="foo"} |> "<_>foo<_>" or "<_>bar<_>"`,
|
|
[]*labels.Matcher{
|
|
mustNewMatcher(labels.MatchEqual, "app", "foo"),
|
|
},
|
|
[]linecheck{{"test foo test", true}, {"test bar test", true}, {"none", false}},
|
|
},
|
|
{
|
|
`{app="foo"} |> "<_>foo<_>" or "<_>bar<_>" or "<_>baz<_>"`,
|
|
[]*labels.Matcher{
|
|
mustNewMatcher(labels.MatchEqual, "app", "foo"),
|
|
},
|
|
[]linecheck{{"test foo test", true}, {"test bar test", true}, {"test baz test", true}, {"none", false}},
|
|
},
|
|
{
|
|
`{app="foo"} !> "foo" or "bar"`,
|
|
[]*labels.Matcher{
|
|
mustNewMatcher(labels.MatchEqual, "app", "foo"),
|
|
},
|
|
[]linecheck{{"foo", false}, {"bar", false}, {"none", true}},
|
|
},
|
|
{
|
|
`{app="foo"} | logfmt | duration > -1s`,
|
|
[]*labels.Matcher{
|
|
mustNewMatcher(labels.MatchEqual, "app", "foo"),
|
|
},
|
|
[]linecheck{{"duration=5m", true}, {"duration=1s", true}, {"duration=0s", true}, {"duration=-5m", false}},
|
|
},
|
|
{
|
|
`{app="foo"} | logfmt | count > -1`,
|
|
[]*labels.Matcher{
|
|
mustNewMatcher(labels.MatchEqual, "app", "foo"),
|
|
},
|
|
[]linecheck{{"count=5", true}, {"count=1", true}, {"count=0", true}, {"count=-5", false}},
|
|
},
|
|
{
|
|
`{app="foo"} | logfmt | counter <= -1`,
|
|
[]*labels.Matcher{
|
|
mustNewMatcher(labels.MatchEqual, "app", "foo"),
|
|
},
|
|
[]linecheck{{"counter=1", false}, {"counter=0", false}, {"counter=-1", true}, {"counter=-2", true}},
|
|
},
|
|
} {
|
|
tt := tt
|
|
t.Run(tt.q, func(t *testing.T) {
|
|
t.Parallel()
|
|
expr, err := ParseLogSelector(tt.q, true)
|
|
assert.Nil(t, err)
|
|
assert.Equal(t, tt.expectedMatchers, expr.Matchers())
|
|
p, err := expr.Pipeline()
|
|
assert.Nil(t, err)
|
|
if tt.lines == nil {
|
|
assert.Equal(t, p, log.NewNoopPipeline())
|
|
} else {
|
|
sp := p.ForStream(labelBar)
|
|
for _, lc := range tt.lines {
|
|
_, _, matches := sp.Process(0, []byte(lc.l))
|
|
assert.Equalf(t, lc.e, matches, "query for line '%s' was %v and not %v", lc.l, matches, lc.e)
|
|
}
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestOrLineFilterTypes(t *testing.T) {
|
|
for _, tt := range []struct {
|
|
ty log.LineMatchType
|
|
}{
|
|
{log.LineMatchEqual},
|
|
{log.LineMatchNotEqual},
|
|
{log.LineMatchRegexp},
|
|
{log.LineMatchNotRegexp},
|
|
{log.LineMatchPattern},
|
|
{log.LineMatchNotPattern},
|
|
} {
|
|
t.Run("right inherits left's type", func(t *testing.T) {
|
|
left := &LineFilterExpr{LineFilter: LineFilter{Ty: tt.ty, Match: "something"}}
|
|
right := &LineFilterExpr{LineFilter: LineFilter{Ty: log.LineMatchEqual, Match: "something"}}
|
|
|
|
_ = newOrLineFilter(left, right)
|
|
require.Equal(t, tt.ty, right.Ty)
|
|
require.Equal(t, tt.ty, left.Ty)
|
|
})
|
|
|
|
t.Run("right inherits left's type with multiple or filters", func(t *testing.T) {
|
|
f1 := &LineFilterExpr{LineFilter: LineFilter{Ty: tt.ty, Match: "something"}}
|
|
f2 := &LineFilterExpr{LineFilter: LineFilter{Ty: log.LineMatchEqual, Match: "something"}}
|
|
f3 := &LineFilterExpr{LineFilter: LineFilter{Ty: log.LineMatchEqual, Match: "something"}}
|
|
|
|
_ = newOrLineFilter(f1, newOrLineFilter(f2, f3))
|
|
require.Equal(t, tt.ty, f1.Ty)
|
|
require.Equal(t, tt.ty, f2.Ty)
|
|
require.Equal(t, tt.ty, f3.Ty)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestStringer(t *testing.T) {
|
|
for _, tc := range []struct {
|
|
in string
|
|
out string
|
|
}{
|
|
{
|
|
in: `1 > 1 > 1`,
|
|
out: `0`,
|
|
},
|
|
{
|
|
in: `1.6`,
|
|
out: `1.6`,
|
|
},
|
|
{
|
|
in: `1 > 1 > bool 1`,
|
|
out: `0`,
|
|
},
|
|
{
|
|
in: `1 > bool 1 > count_over_time({foo="bar"}[1m])`,
|
|
out: `(0 > count_over_time({foo="bar"}[1m]))`,
|
|
},
|
|
{
|
|
in: `1 > bool 1 > bool count_over_time({foo="bar"}[1m])`,
|
|
out: `(0 > bool count_over_time({foo="bar"}[1m]))`,
|
|
},
|
|
{
|
|
in: `0 > count_over_time({foo="bar"}[1m])`,
|
|
out: `(0 > count_over_time({foo="bar"}[1m]))`,
|
|
},
|
|
{
|
|
in: `{app="foo"} |= "foo" or "bar"`,
|
|
out: `{app="foo"} |= "foo" or "bar"`,
|
|
},
|
|
{
|
|
in: `{app="foo"} |= "foo" or "bar" or "baz"`,
|
|
out: `{app="foo"} |= "foo" or "bar" or "baz"`,
|
|
},
|
|
{
|
|
in: `{app="foo"} |= "foo" or "bar" or "baz" |= "car"`,
|
|
out: `{app="foo"} |= "foo" or "bar" or "baz" |= "car"`,
|
|
},
|
|
{
|
|
in: `{app="foo"} |= "foo" or "bar" or "baz" |= "car" |= "a" or "b" or "c"`,
|
|
out: `{app="foo"} |= "foo" or "bar" or "baz" |= "car" |= "a" or "b" or "c"`,
|
|
},
|
|
{
|
|
in: `{app="foo"} |~ "foo" or "bar" or "baz"`,
|
|
out: `{app="foo"} |~ "foo" or "bar" or "baz"`,
|
|
},
|
|
{
|
|
in: `{app="foo"} |= "foo" or "bar" |= "buzz" or "fizz"`,
|
|
out: `{app="foo"} |= "foo" or "bar" |= "buzz" or "fizz"`,
|
|
},
|
|
{
|
|
out: `{app="foo"} |= "foo" or "bar" |~ "buzz|fizz"`,
|
|
in: `{app="foo"} |= "foo" or "bar" |~ "buzz|fizz"`,
|
|
},
|
|
{
|
|
in: `{app="foo"} |= ip("127.0.0.1") or "foo"`,
|
|
out: `{app="foo"} |= ip("127.0.0.1") or "foo"`,
|
|
},
|
|
{
|
|
in: `{app="foo"} |= "foo" or ip("127.0.0.1")`,
|
|
out: `{app="foo"} |= "foo" or ip("127.0.0.1")`,
|
|
},
|
|
{
|
|
in: `{app="foo"} |~ ip("127.0.0.1") or "foo"`,
|
|
out: `{app="foo"} |~ ip("127.0.0.1") or "foo"`,
|
|
},
|
|
{
|
|
in: `{app="foo"} |> "foo <_> baz" or "foo <_>"`,
|
|
out: `{app="foo"} |> "foo <_> baz" or "foo <_>"`,
|
|
},
|
|
{
|
|
in: `{app="foo"} |> "foo <_> baz" or "foo <_>" |> "foo <_> baz"`,
|
|
out: `{app="foo"} |> "foo <_> baz" or "foo <_>" |> "foo <_> baz"`,
|
|
},
|
|
{ // !(A || B) == !A && !B
|
|
in: `{app="foo"} != "foo" or "bar"`,
|
|
out: `{app="foo"} != "foo" != "bar"`,
|
|
},
|
|
{
|
|
in: `{app="foo"} != "test" != "foo" or "bar"`,
|
|
out: `{app="foo"} != "test" != "foo" != "bar"`,
|
|
},
|
|
{
|
|
in: `{app="foo"} != "test" != "foo" or "bar" or "baz"`,
|
|
out: `{app="foo"} != "test" != "foo" != "bar" != "baz"`,
|
|
},
|
|
{
|
|
in: `{app="foo"} != "foo" or "bar" or "baz" != "car"`,
|
|
out: `{app="foo"} != "foo" != "bar" != "baz" != "car"`,
|
|
},
|
|
{
|
|
in: `{app="foo"} != "foo" or "bar" or "baz" != "car" != "a" or "b" or "c"`,
|
|
out: `{app="foo"} != "foo" != "bar" != "baz" != "car" != "a" != "b" != "c"`,
|
|
},
|
|
{
|
|
// Mix of != and |=
|
|
in: `{app="foo"} |= "foo" or "bar" or "baz" != "car" != "a" or "b" or "c"`,
|
|
out: `{app="foo"} |= "foo" or "bar" or "baz" != "car" != "a" != "b" != "c"`,
|
|
},
|
|
{
|
|
in: `{app="foo"} !~ "foo" or "bar"`,
|
|
out: `{app="foo"} !~ "foo" !~ "bar"`,
|
|
},
|
|
{
|
|
in: `{app="foo"} !~ "test" !~ "foo" or "bar"`,
|
|
out: `{app="foo"} !~ "test" !~ "foo" !~ "bar"`,
|
|
},
|
|
{
|
|
in: `{app="foo"} !~ "test" !~ "foo" or "bar" or "baz"`,
|
|
out: `{app="foo"} !~ "test" !~ "foo" !~ "bar" !~ "baz"`,
|
|
},
|
|
{
|
|
in: `{app="foo"} !~ "foo" or "bar" or "baz" !~ "car"`,
|
|
out: `{app="foo"} !~ "foo" !~ "bar" !~ "baz" !~ "car"`,
|
|
},
|
|
{
|
|
in: `{app="foo"} !~ "foo" or "bar" or "baz" !~ "car" !~ "a" or "b" or "c"`,
|
|
out: `{app="foo"} !~ "foo" !~ "bar" !~ "baz" !~ "car" !~ "a" !~ "b" !~ "c"`,
|
|
},
|
|
{
|
|
// Mix of !~ and |~
|
|
in: `{app="foo"} |~ "foo" or "bar" or "baz" !~ "car" !~ "a" or "b" or "c"`,
|
|
out: `{app="foo"} |~ "foo" or "bar" or "baz" !~ "car" !~ "a" !~ "b" !~ "c"`,
|
|
},
|
|
{
|
|
in: `{app="foo"} != ip("127.0.0.1") or "foo"`,
|
|
out: `{app="foo"} != ip("127.0.0.1") != "foo"`,
|
|
},
|
|
{
|
|
in: `{app="foo"} !~ ip("127.0.0.1") or "foo"`,
|
|
out: `{app="foo"} !~ ip("127.0.0.1") !~ "foo"`,
|
|
},
|
|
{
|
|
in: `{app="foo"} !> "<_> foo <_>" or "foo <_>" !> "foo <_> baz"`,
|
|
out: `{app="foo"} !> "<_> foo <_>" !> "foo <_>" !> "foo <_> baz"`,
|
|
},
|
|
} {
|
|
t.Run(tc.in, func(t *testing.T) {
|
|
expr, err := ParseExpr(tc.in)
|
|
require.Nil(t, err)
|
|
require.Equal(t, tc.out, expr.String())
|
|
})
|
|
}
|
|
}
|
|
|
|
func BenchmarkContainsFilter(b *testing.B) {
|
|
lines := [][]byte{
|
|
[]byte("hello world foo bar"),
|
|
[]byte("bar hello world for"),
|
|
[]byte("hello world foobar and the bar and more bar until the end"),
|
|
[]byte("hello world foobar and the bar and more bar and more than one hundred characters for sure until the end"),
|
|
[]byte("hello world foobar and the bar and more bar and more than one hundred characters for sure until the end and yes bar"),
|
|
}
|
|
|
|
benchmarks := []struct {
|
|
name string
|
|
expr string
|
|
}{
|
|
{
|
|
"AllMatches",
|
|
`{app="foo"} |= "foo" |= "hello" |= "world" |= "bar" |> "<_> world <_>"`,
|
|
},
|
|
{
|
|
"OneMatches",
|
|
`{app="foo"} |= "foo" |= "not" |= "in" |= "there" |> "yet"`,
|
|
},
|
|
{
|
|
"MixedFiltersTrue",
|
|
`{app="foo"} |= "foo" != "not" |~ "hello.*bar" != "there" |= "world" |> "<_> more than one <_>"`,
|
|
},
|
|
{
|
|
"MixedFiltersFalse",
|
|
`{app="foo"} |= "baz" != "not" |~ "hello.*bar" != "there" |= "world" !> "<_> more than one"`,
|
|
},
|
|
{
|
|
"GreedyRegex",
|
|
`{app="foo"} |~ "hello.*bar.*"`,
|
|
},
|
|
{
|
|
"NonGreedyRegex",
|
|
`{app="foo"} |~ "hello.*?bar.*?"`,
|
|
},
|
|
{
|
|
"ReorderedRegex",
|
|
`{app="foo"} |~ "hello.*?bar.*?" |= "not"`,
|
|
},
|
|
}
|
|
|
|
for _, bm := range benchmarks {
|
|
b.Run(bm.name, func(b *testing.B) {
|
|
expr, err := ParseLogSelector(bm.expr, false)
|
|
if err != nil {
|
|
b.Fatal(err)
|
|
}
|
|
|
|
p, err := expr.Pipeline()
|
|
if err != nil {
|
|
b.Fatal(err)
|
|
}
|
|
|
|
b.ResetTimer()
|
|
sp := p.ForStream(labelBar)
|
|
for i := 0; i < b.N; i++ {
|
|
for _, line := range lines {
|
|
sp.Process(0, line)
|
|
}
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func Test_parserExpr_Parser(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
op string
|
|
param string
|
|
want log.Stage
|
|
wantErr bool
|
|
wantPanic bool
|
|
}{
|
|
{"json", OpParserTypeJSON, "", log.NewJSONParser(), false, false},
|
|
{"unpack", OpParserTypeUnpack, "", log.NewUnpackParser(), false, false},
|
|
{"pattern", OpParserTypePattern, "<foo> bar <buzz>", mustNewPatternParser("<foo> bar <buzz>"), false, false},
|
|
{"pattern err", OpParserTypePattern, "bar", nil, true, true},
|
|
{"regexp", OpParserTypeRegexp, "(?P<foo>foo)", mustNewRegexParser("(?P<foo>foo)"), false, false},
|
|
{"regexp err ", OpParserTypeRegexp, "foo", nil, true, true},
|
|
{"unknown op", "DummyOp", "", nil, true, false},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
var e *LabelParserExpr
|
|
if tt.wantPanic {
|
|
require.Panics(t, func() { e = newLabelParserExpr(tt.op, tt.param) })
|
|
return
|
|
}
|
|
e = newLabelParserExpr(tt.op, tt.param)
|
|
got, err := e.Stage()
|
|
if (err != nil) != tt.wantErr {
|
|
t.Errorf("parserExpr.Parser() error = %v, wantErr %v", err, tt.wantErr)
|
|
return
|
|
}
|
|
if tt.wantErr {
|
|
require.Nil(t, got)
|
|
} else {
|
|
require.Equal(t, tt.want, got)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func Test_parserExpr_String(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
op string
|
|
param string
|
|
want string
|
|
}{
|
|
{"valid regexp", OpParserTypeRegexp, "foo", `| regexp "foo"`},
|
|
{"empty regexp", OpParserTypeRegexp, "", `| regexp ""`},
|
|
{"valid pattern", OpParserTypePattern, "buzz", `| pattern "buzz"`},
|
|
{"empty pattern", OpParserTypePattern, "", `| pattern ""`},
|
|
{"valid json", OpParserTypeJSON, "", `| json`},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
l := LabelParserExpr{
|
|
Op: tt.op,
|
|
Param: tt.param,
|
|
}
|
|
require.Equal(t, tt.want, l.String())
|
|
})
|
|
}
|
|
}
|
|
|
|
func mustNewRegexParser(re string) log.Stage {
|
|
r, err := log.NewRegexpParser(re)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
return r
|
|
}
|
|
|
|
func mustNewPatternParser(p string) log.Stage {
|
|
r, err := log.NewPatternParser(p)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
return r
|
|
}
|
|
|
|
func Test_canInjectVectorGrouping(t *testing.T) {
|
|
tests := []struct {
|
|
vecOp string
|
|
rangeOp string
|
|
want bool
|
|
}{
|
|
{OpTypeSum, OpRangeTypeBytes, true},
|
|
{OpTypeSum, OpRangeTypeBytesRate, true},
|
|
{OpTypeSum, OpRangeTypeSum, true},
|
|
{OpTypeSum, OpRangeTypeRate, true},
|
|
{OpTypeSum, OpRangeTypeCount, true},
|
|
|
|
{OpTypeSum, OpRangeTypeAvg, false},
|
|
{OpTypeSum, OpRangeTypeMax, false},
|
|
{OpTypeSum, OpRangeTypeQuantile, false},
|
|
{OpTypeSum, OpRangeTypeStddev, false},
|
|
{OpTypeSum, OpRangeTypeStdvar, false},
|
|
{OpTypeSum, OpRangeTypeMin, false},
|
|
{OpTypeSum, OpRangeTypeMax, false},
|
|
|
|
{OpTypeAvg, OpRangeTypeBytes, false},
|
|
{OpTypeCount, OpRangeTypeBytesRate, false},
|
|
{OpTypeBottomK, OpRangeTypeSum, false},
|
|
{OpTypeMax, OpRangeTypeRate, false},
|
|
{OpTypeMin, OpRangeTypeCount, false},
|
|
{OpTypeTopK, OpRangeTypeCount, false},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.vecOp+"_"+tt.rangeOp, func(t *testing.T) {
|
|
if got := canInjectVectorGrouping(tt.vecOp, tt.rangeOp); got != tt.want {
|
|
t.Errorf("canInjectVectorGrouping() = %v, want %v", got, tt.want)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func Test_MergeBinOpVectors_Filter(t *testing.T) {
|
|
res, err := MergeBinOp(
|
|
OpTypeGT,
|
|
&promql.Sample{F: 2},
|
|
&promql.Sample{F: 0},
|
|
false,
|
|
true,
|
|
true,
|
|
)
|
|
require.NoError(t, err)
|
|
|
|
// ensure we return the left hand side's value (2) instead of the
|
|
// comparison operator's result (1: the truthy answer)
|
|
require.Equal(t, &promql.Sample{F: 2}, res)
|
|
}
|
|
|
|
func TestFilterReodering(t *testing.T) {
|
|
t.Run("it makes sure line filters are as early in the pipeline stages as possible", func(t *testing.T) {
|
|
logExpr := `{container_name="app"} |= "foo" |= "next" | logfmt |="bar" |="baz" | line_format "{{.foo}}" |="1" |="2" | logfmt |="3"`
|
|
l, err := ParseExpr(logExpr)
|
|
require.NoError(t, err)
|
|
|
|
stages := l.(*PipelineExpr).MultiStages.reorderStages()
|
|
require.Len(t, stages, 5)
|
|
require.Equal(t, `|= "foo" |= "next" |= "bar" |= "baz" | logfmt | line_format "{{.foo}}" |= "1" |= "2" |= "3" | logfmt`, MultiStageExpr(stages).String())
|
|
})
|
|
|
|
t.Run("unpack test", func(t *testing.T) {
|
|
logExpr := `{container_name="app"} |= "06497595" | unpack != "message" | json | line_format "new log: {{.foo}}"`
|
|
l, err := ParseExpr(logExpr)
|
|
require.NoError(t, err)
|
|
|
|
stages := l.(*PipelineExpr).MultiStages.reorderStages()
|
|
require.Len(t, stages, 5)
|
|
require.Equal(t, `|= "06497595" | unpack != "message" | json | line_format "new log: {{.foo}}"`, MultiStageExpr(stages).String())
|
|
})
|
|
}
|
|
|
|
var result bool
|
|
|
|
func BenchmarkReorderedPipeline(b *testing.B) {
|
|
logfmtLine := []byte(`level=info ts=2020-12-14T21:25:20.947307459Z caller=metrics.go:83 org_id=29 traceID=c80e691e8db08e2 latency=fast query="sum by (object_name) (rate(({container=\"metrictank\", cluster=\"hm-us-east2\"} |= \"PANIC\")[5m]))" query_type=metric range_type=range length=5m0s step=15s duration=322.623724ms status=200 throughput=1.2GB total_bytes=375MB`)
|
|
|
|
logExpr := `{container_name="app"} | logfmt |= "slow"`
|
|
l, err := ParseLogSelector(logExpr, true)
|
|
require.NoError(b, err)
|
|
|
|
p, err := l.Pipeline()
|
|
require.NoError(b, err)
|
|
|
|
sp := p.ForStream(labels.EmptyLabels())
|
|
|
|
b.ResetTimer()
|
|
for n := 0; n < b.N; n++ {
|
|
_, _, result = sp.Process(0, logfmtLine)
|
|
}
|
|
}
|
|
|
|
func TestParseLargeQuery(t *testing.T) {
|
|
// This query originally failed to parse because there was a function
|
|
// definition at 1024 bytes whichcaused the lexer scanner to become
|
|
// out of sync with it's underlying reader due to shared state
|
|
line := `((sum(count_over_time({foo="bar"} |~ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" [6h])) / sum(count_over_time({foo="bar"} |~ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" [6h]))) * 100)`
|
|
|
|
_, err := ParseExpr(line)
|
|
require.NoError(t, err)
|
|
}
|
|
|
|
func TestLogSelectorExprHasFilter(t *testing.T) {
|
|
for query, hasFilter := range map[string]bool{
|
|
`{foo="bar"} |= ""`: false,
|
|
`{foo="bar"} |= "" |= ""`: false,
|
|
`{foo="bar"} |~ ""`: false,
|
|
`{foo="bar"} |= "notempty"`: true,
|
|
`{foo="bar"} |= "" |= "notempty"`: true,
|
|
`{foo="bar"} != ""`: true,
|
|
`{foo="bar"} | lbl="notempty"`: true,
|
|
`{foo="bar"} |= "" | lbl="notempty"`: true,
|
|
} {
|
|
expr, err := ParseExpr(query)
|
|
require.NoError(t, err)
|
|
require.Equal(t, hasFilter, expr.(LogSelectorExpr).HasFilter())
|
|
}
|
|
}
|
|
|
|
func TestGroupingString(t *testing.T) {
|
|
g := Grouping{
|
|
Groups: []string{"a", "b"},
|
|
Without: false,
|
|
}
|
|
require.Equal(t, " by (a,b)", g.String())
|
|
|
|
g = Grouping{
|
|
Groups: []string{},
|
|
Without: false,
|
|
}
|
|
require.Equal(t, " by ()", g.String())
|
|
|
|
g = Grouping{
|
|
Groups: nil,
|
|
Without: false,
|
|
}
|
|
require.Equal(t, " by ()", g.String())
|
|
|
|
g = Grouping{
|
|
Groups: []string{"a", "b"},
|
|
Without: true,
|
|
}
|
|
require.Equal(t, " without (a,b)", g.String())
|
|
|
|
g = Grouping{
|
|
Groups: []string{},
|
|
Without: true,
|
|
}
|
|
require.Equal(t, " without ()", g.String())
|
|
|
|
g = Grouping{
|
|
Groups: nil,
|
|
Without: true,
|
|
}
|
|
require.Equal(t, " without ()", g.String())
|
|
}
|
|
|