categraf/inputs/mtail/internal/runtime/runtime_integration_test.go

1106 lines
20 KiB
Go

// Copyright 2019 Google Inc. All Rights Reserved.
// This file is available under the Apache license.
package runtime
import (
"bufio"
"context"
"math"
"strings"
"sync"
"testing"
"flashcat.cloud/categraf/inputs/mtail/internal/logline"
"flashcat.cloud/categraf/inputs/mtail/internal/metrics"
"flashcat.cloud/categraf/inputs/mtail/internal/metrics/datum"
"flashcat.cloud/categraf/inputs/mtail/internal/testutil"
)
var vmTests = []struct {
name string
prog string
log string
errs int64
metrics metrics.MetricSlice
}{
{
"single-dash-parseint",
`counter c
/(?P<x>-)/ {
$x == "-" {
c++
}
}
`, `123 a
- b
`,
0,
metrics.MetricSlice{
{
Name: "c",
Program: "single-dash-parseint",
Kind: metrics.Counter,
Type: metrics.Int,
Hidden: false,
Keys: []string{},
LabelValues: []*metrics.LabelValue{
{
Value: &datum.Int{Value: 1},
},
},
},
},
},
{
"histogram",
`histogram hist1 buckets 1, 2, 4, 8
histogram hist2 by code buckets 0, 1, 2, 4, 8
histogram hist3 by f buckets -1, 0, 1
/^(.) (\d+)/ {
hist1 = $2
hist2[$1] = $2
}
/^(?P<foo>[a-z]+) (?P<time>\d+)$/ {
hist3[$foo] = $time
}
`,
`b 3
b 3
b 3
`,
0,
metrics.MetricSlice{
{
Name: "hist1",
Program: "histogram",
Kind: metrics.Histogram,
Type: metrics.Buckets,
Keys: []string{},
LabelValues: []*metrics.LabelValue{
{
Value: &datum.Buckets{
Buckets: []datum.BucketCount{
{Range: datum.Range{Min: 0, Max: 1}},
{Range: datum.Range{Min: 1, Max: 2}},
{
Range: datum.Range{Min: 2, Max: 4},
Count: 3,
},
{Range: datum.Range{Min: 4, Max: 8}},
{Range: datum.Range{Min: 8, Max: math.Inf(+1)}},
},
Count: 3,
Sum: 9,
},
},
},
Buckets: []datum.Range{{Min: 0, Max: 1}, {Min: 1, Max: 2}, {Min: 2, Max: 4}, {Min: 4, Max: 8}, {Min: 8, Max: math.Inf(+1)}},
},
{
Name: "hist2",
Program: "histogram",
Kind: metrics.Histogram,
Type: metrics.Buckets,
Keys: []string{"code"},
LabelValues: []*metrics.LabelValue{
{
Labels: []string{"b"},
Value: &datum.Buckets{
Buckets: []datum.BucketCount{
{Range: datum.Range{Min: 0, Max: 1}},
{Range: datum.Range{Min: 1, Max: 2}},
{
Range: datum.Range{Min: 2, Max: 4},
Count: 3,
},
{Range: datum.Range{Min: 4, Max: 8}},
{Range: datum.Range{Min: 8, Max: math.Inf(+1)}},
},
Count: 3,
Sum: 9,
},
},
},
Buckets: []datum.Range{{Min: 0, Max: 1}, {Min: 1, Max: 2}, {Min: 2, Max: 4}, {Min: 4, Max: 8}, {Min: 8, Max: math.Inf(+1)}},
},
{
Name: "hist3",
Program: "histogram",
Kind: metrics.Histogram,
Type: metrics.Buckets,
Keys: []string{"f"},
LabelValues: []*metrics.LabelValue{
{
Labels: []string{"b"},
Value: &datum.Buckets{
Buckets: []datum.BucketCount{
{Range: datum.Range{Min: -1, Max: 0}},
{Range: datum.Range{Min: 0, Max: 1}},
{
Range: datum.Range{Min: 1, Max: math.Inf(+1)},
Count: 3,
},
},
Count: 3,
Sum: 9,
},
},
},
Buckets: []datum.Range{{Min: -1, Max: 0}, {Min: 0, Max: 1}, {Min: 1, Max: math.Inf(+1)}},
},
},
},
{
"numbers",
`counter error_log_count
/^/ +
/(?P<date>\d{4}\/\d{2}\/\d{2} \d{2}:\d{2}:\d{2}) / +
/.*/ +
/$/ {
strptime($date, "2006/01/02 15:04:05")
error_log_count++
}
`,
`2019/05/14 11:10:05 [warn] ...
2019/05/14 11:11:06 [warn] ...
`,
0,
metrics.MetricSlice{
{
Name: "error_log_count",
Program: "numbers",
Kind: metrics.Counter,
Type: metrics.Int,
Keys: []string{},
LabelValues: []*metrics.LabelValue{
{
Value: &datum.Int{
Value: 2,
},
},
},
},
},
},
{
"parse a hyphen",
`counter total
/^[a-z]+ ((?P<response_size>\d+)|-)$/ {
$response_size > 0 {
total = $response_size
}
}`,
`test 99
test -
`,
1,
metrics.MetricSlice{
{
Name: "total",
Program: "parse a hyphen",
Kind: metrics.Counter,
Type: metrics.Int,
Keys: []string{},
LabelValues: []*metrics.LabelValue{
{
Value: &datum.Int{
Value: 99,
},
},
},
},
},
},
{
"parse around a hyphen",
`counter total
/^[a-z]+ ((?P<response_size>\d+)|-)$/ {
$1 != "-" {
total = $response_size
}
}`,
`test 99
test -
`,
0,
metrics.MetricSlice{
{
Name: "total",
Program: "parse around a hyphen",
Kind: metrics.Counter,
Type: metrics.Int,
Keys: []string{},
LabelValues: []*metrics.LabelValue{
{
Value: &datum.Int{
Value: 99,
},
},
},
},
},
},
{
"add_assign_float",
`gauge metric
/(\d+\.\d+)/ {
metric += $1
}
`, `1.1
`,
0,
metrics.MetricSlice{
{
Name: "metric",
Program: "add_assign_float",
Kind: metrics.Gauge,
Type: metrics.Float,
Hidden: false,
Keys: []string{},
LabelValues: []*metrics.LabelValue{
{
Labels: []string{},
Value: &datum.Float{Valuebits: math.Float64bits(1.1)},
},
},
},
},
},
{
"decorator",
`counter a
counter b
counter c
def decoratora {
/(...).*/ {
next
}
}
def decoratorb {
/(?P<x>...).*/ {
next
}
}
# This tests that the variables in the decorator are visible to the decoratedo block.
@decoratora {
$1 == "Dec" {
a++
}
}
@decoratorb {
$x == "Dec" {
b++
}
}
/(...).*/ {
$1 == "Dec" {
c++
}
}
`, `Dec
Jan
Dec
Apr
Dec
Oct
`,
0,
metrics.MetricSlice{
{
Name: "a",
Program: "decorator",
Kind: metrics.Counter,
Type: metrics.Int,
Hidden: false,
Keys: []string{},
LabelValues: []*metrics.LabelValue{
{
Value: &datum.Int{Value: 3},
},
},
},
{
Name: "b",
Program: "decorator",
Kind: metrics.Counter,
Type: metrics.Int,
Hidden: false,
Keys: []string{},
LabelValues: []*metrics.LabelValue{
{
Value: &datum.Int{Value: 3},
},
},
},
{
Name: "c",
Program: "decorator",
Kind: metrics.Counter,
Type: metrics.Int,
Hidden: false,
Keys: []string{},
LabelValues: []*metrics.LabelValue{
{
Value: &datum.Int{Value: 3},
},
},
},
},
},
{
"else",
`counter yes
counter maybe
counter no
/1/ {
/^1$/ {
yes++
} else {
maybe++
}
} else {
no++
}
`, `1
2
12
3
4
991
`, 0,
metrics.MetricSlice{
{
Name: "yes",
Program: "else",
Kind: metrics.Counter,
Type: metrics.Int,
Keys: []string{},
LabelValues: []*metrics.LabelValue{
{
Value: &datum.Int{Value: 1},
},
},
},
{
Name: "maybe",
Program: "else",
Kind: metrics.Counter,
Type: metrics.Int,
Keys: []string{},
LabelValues: []*metrics.LabelValue{
{
Value: &datum.Int{Value: 2},
},
},
},
{
Name: "no",
Program: "else",
Kind: metrics.Counter,
Type: metrics.Int,
Keys: []string{},
LabelValues: []*metrics.LabelValue{
{
Value: &datum.Int{Value: 3},
},
},
},
},
},
{
"otherwise",
`counter yes
counter maybe
counter no
/1/ {
/^1$/ {
yes++
}
otherwise {
maybe++
}
}
otherwise {
no++
}
`, `1
2
12
3
4
991
`, 0,
metrics.MetricSlice{
{
Name: "yes",
Program: "otherwise",
Kind: metrics.Counter,
Type: metrics.Int,
Keys: []string{},
LabelValues: []*metrics.LabelValue{
{
Value: &datum.Int{Value: 1},
},
},
},
{
Name: "maybe",
Program: "otherwise",
Kind: metrics.Counter,
Type: metrics.Int,
Keys: []string{},
LabelValues: []*metrics.LabelValue{
{
Value: &datum.Int{Value: 2},
},
},
},
{
Name: "no",
Program: "otherwise",
Kind: metrics.Counter,
Type: metrics.Int,
Keys: []string{},
LabelValues: []*metrics.LabelValue{
{
Value: &datum.Int{Value: 3},
},
},
},
},
},
{
"types",
`gauge should_be_int
gauge should_be_float
counter neg
gauge should_be_float_map by label
gauge should_be_int_map by label
counter i
/^(\d+)$/ {
should_be_int = $1
should_be_int_map[$1] = $1
}
/^(\d+\.\d+)$/ {
should_be_float = $1
should_be_float_map[$1] = $1
}
/(?P<bar>[+-]?[\d.]+)/ {
$bar < -1 {
neg++
}
}
/^(\d+)$/ {
# Sneaky float promotion
i += 1.0 * $1
}
`, `37
12.8
`,
0,
metrics.MetricSlice{
{
Name: "should_be_int",
Program: "types",
Kind: metrics.Gauge,
Type: metrics.Int,
Keys: []string{},
LabelValues: []*metrics.LabelValue{
{
Labels: []string{},
Value: &datum.Int{Value: 37},
},
},
},
{
Name: "should_be_float",
Program: "types",
Kind: metrics.Gauge,
Type: metrics.Float,
Keys: []string{},
LabelValues: []*metrics.LabelValue{
{
Labels: []string{},
Value: &datum.Float{Valuebits: math.Float64bits(12.8)},
},
},
},
{
Name: "should_be_int_map",
Program: "types",
Kind: metrics.Gauge,
Type: metrics.Int,
Keys: []string{"label"},
LabelValues: []*metrics.LabelValue{
{
Labels: []string{"37"},
Value: &datum.Int{Value: 37},
},
},
},
{
Name: "should_be_float_map",
Program: "types",
Kind: metrics.Gauge,
Type: metrics.Float,
Keys: []string{"label"},
LabelValues: []*metrics.LabelValue{
{
Labels: []string{"12.8"},
Value: &datum.Float{Valuebits: math.Float64bits(12.8)},
},
},
},
{
Name: "neg",
Program: "types",
Kind: metrics.Counter,
Type: metrics.Int,
Keys: []string{},
LabelValues: []*metrics.LabelValue{
{
Value: &datum.Int{},
},
},
},
{
Name: "i",
Program: "types",
Kind: metrics.Counter,
Type: metrics.Float,
Keys: []string{},
LabelValues: []*metrics.LabelValue{
{
Value: &datum.Float{Valuebits: math.Float64bits(37.0)},
},
},
},
},
},
{
"filename",
`counter filename_lines by filename
// {
filename_lines[getfilename()] ++
}
`, `1
2
12
3
4
991
`,
0,
metrics.MetricSlice{
{
Name: "filename_lines",
Program: "filename",
Kind: metrics.Counter,
Type: metrics.Int,
Keys: []string{"filename"},
LabelValues: []*metrics.LabelValue{
{
Labels: []string{"filename"},
Value: &datum.Int{Value: 6},
},
},
},
},
},
{
"logical operators",
`counter foo
counter bar
# To make ex_test.go happy
strptime("2017-10-03T20:14:42Z", "2006-01-02T15:04:05Z07:00")
/(?P<var>.*)/ {
$var == "foo" || $var == "bar" {
foo++
}
$var == "bar" && 1 == 1 {
bar++
}
}
`, `foo
foo
bar
bar
quux
12.8
`,
0,
metrics.MetricSlice{
{
Name: "foo",
Program: "logical operators",
Kind: metrics.Counter,
Type: metrics.Int,
Keys: []string{},
LabelValues: []*metrics.LabelValue{
{
Value: &datum.Int{Value: 4},
},
},
},
{
Name: "bar",
Program: "logical operators",
Kind: metrics.Counter,
Type: metrics.Int,
Keys: []string{},
LabelValues: []*metrics.LabelValue{
{
Value: &datum.Int{Value: 2},
},
},
},
},
},
{
"strcat",
`counter f by s
/(.*), (.*)/ {
f[$1 + $2]++
}
`, `a, b
c, d
`,
0,
metrics.MetricSlice{
{
Name: "f",
Program: "strcat",
Kind: metrics.Counter,
Type: metrics.Int,
Keys: []string{"s"},
LabelValues: []*metrics.LabelValue{
{
Labels: []string{"ab"},
Value: &datum.Int{Value: 1},
},
{
Labels: []string{"cd"},
Value: &datum.Int{Value: 1},
},
},
},
},
},
{
"typed-comparison",
`counter t by le
counter t_sum
/^(?P<v>\d+(\.\d+)?)/ {
$v < 0.5 {
t["0.5"]++
}
$v < 1 {
t["1"]++
}
t["inf"]++
t_sum += $v
}
`, `0.1
1
1.765
`,
0,
metrics.MetricSlice{
{
Name: "t",
Program: "typed-comparison",
Kind: metrics.Counter,
Type: metrics.Int,
Keys: []string{"le"},
LabelValues: []*metrics.LabelValue{
{
Labels: []string{"0.5"},
Value: &datum.Int{Value: 1},
},
{
Labels: []string{"1"},
Value: &datum.Int{Value: 1},
},
{
Labels: []string{"inf"},
Value: &datum.Int{Value: 3},
},
},
},
{
Name: "t_sum",
Program: "typed-comparison",
Kind: metrics.Counter,
Type: metrics.Float,
Keys: []string{},
LabelValues: []*metrics.LabelValue{
{
Value: &datum.Float{Valuebits: math.Float64bits(2.865)},
},
},
},
},
},
{
"match-expression",
`counter someas
counter notas
counter total
/(.*)/ {
$1 =~ /a/ {
someas++
}
$1 !~ /a/ {
notas++
}
total++
}
`, `a
b
abba
baba
cdf
`,
0,
metrics.MetricSlice{
{
Name: "someas",
Program: "match-expression",
Kind: metrics.Counter,
Type: metrics.Int,
Keys: []string{},
LabelValues: []*metrics.LabelValue{
{
Value: &datum.Int{Value: 3},
},
},
},
{
Name: "notas",
Program: "match-expression",
Kind: metrics.Counter,
Type: metrics.Int,
Keys: []string{},
LabelValues: []*metrics.LabelValue{
{
Value: &datum.Int{Value: 2},
},
},
},
{
Name: "total",
Program: "match-expression",
Kind: metrics.Counter,
Type: metrics.Int,
Keys: []string{},
LabelValues: []*metrics.LabelValue{
{
Value: &datum.Int{Value: 5},
},
},
},
},
},
{
"metric-as-rvalue",
`gauge response_time
counter hit
counter miss
/seconds = (?P<response_seconds>\d+)/ {
response_time = $response_seconds * 1000
response_time < 100000 {
hit++
} else {
miss++
}
}
`, `seconds = 100
seconds = 200
seconds = 50
`,
0,
metrics.MetricSlice{
{
Name: "hit",
Program: "metric-as-rvalue",
Kind: metrics.Counter,
Type: metrics.Int,
Keys: []string{},
LabelValues: []*metrics.LabelValue{
{
Value: &datum.Int{Value: 1},
},
},
},
{
Name: "miss",
Program: "metric-as-rvalue",
Kind: metrics.Counter,
Type: metrics.Int,
Keys: []string{},
LabelValues: []*metrics.LabelValue{
{
Value: &datum.Int{Value: 2},
},
},
},
{
Name: "response_time",
Program: "metric-as-rvalue",
Kind: metrics.Gauge,
Type: metrics.Int,
Keys: []string{},
LabelValues: []*metrics.LabelValue{
{
Labels: []string{},
Value: &datum.Int{Value: 50000},
},
},
},
},
},
{
"stringy",
`text str
counter b by foo
/(.*)/ {
str = $1
}
/b/ {
b[str]++
}
`, `1.1
c
b
a
`,
0,
metrics.MetricSlice{
{
Name: "str",
Program: "stringy",
Kind: metrics.Text,
Type: metrics.String,
Keys: []string{},
LabelValues: []*metrics.LabelValue{
{
Labels: []string{},
Value: &datum.String{Value: "a"},
},
},
},
{
Name: "b",
Program: "stringy",
Kind: metrics.Counter,
Type: metrics.Int,
Keys: []string{"foo"},
LabelValues: []*metrics.LabelValue{
{
Labels: []string{"b"},
Value: &datum.Int{Value: 1},
},
},
},
},
},
{
"ip-addr",
`text ipaddr
/ip address (\d+\.\d+\.\d+\.\d+)/ {
ipaddr = $1
}
`, `ip address 1.1.1.1
`, 0,
metrics.MetricSlice{
{
Name: "ipaddr",
Program: "ip-addr",
Kind: metrics.Text,
Type: metrics.String,
Keys: []string{},
LabelValues: []*metrics.LabelValue{
{
Labels: []string{},
Value: &datum.String{Value: "1.1.1.1"},
},
},
},
},
},
{
name: "subst timestamp",
prog: `gauge val
/(\d{4}-\d{2}-\d{2}_\d{2}:\d{2}:\d{2}) .*: (\d+)/ {
strptime(subst("_", " ", $1), "2006-01-02 15:04:05")
val = $2
}`,
log: `2021-01-03_17:34:23 CUL_MAXCUBE_FRONT credit10ms: 3494
`,
errs: 0,
metrics: metrics.MetricSlice{
{
Name: "val",
Program: "subst timestamp",
Kind: metrics.Gauge,
Type: metrics.Int,
Keys: []string{},
LabelValues: []*metrics.LabelValue{
{
Labels: []string{},
Value: &datum.Int{Value: 3494},
},
},
},
},
},
{
name: "subst integer",
prog: `counter bytes_total by dir
/sent (?P<sent>[\d,]+) bytes received (?P<received>[\d,]+) bytes/ {
# Sum total bytes across all sessions for this process
bytes_total["sent"] += int(subst(",", "", $sent))
bytes_total["received"] += int(subst(",", "", $received))
}`,
log: `Jun 28 20:44:32 backup rsync[3996]: sent 642,410,725 bytes received 14,998,522,122 bytes 497,002.00 bytes/sec
`,
errs: 0,
metrics: metrics.MetricSlice{
{
Name: "bytes_total",
Program: "subst integer",
Kind: metrics.Counter,
Type: metrics.Int,
Keys: []string{"dir"},
LabelValues: []*metrics.LabelValue{
{
Labels: []string{"sent"},
Value: &datum.Int{Value: 642410725},
},
{
Labels: []string{"received"},
Value: &datum.Int{Value: 14998522122},
},
},
},
},
},
{
name: "regexp replace",
prog: `hidden text route
counter http_requests_total by method, route
/(?P<method>\S+) (?P<url>\S+)/ {
route = subst(/\/\d+/, "/:num", $url)
http_requests_total[$method, route]++
}
`,
log: `GET /v1/read/10001
GET /v1/users/1001/orders/2001
`,
errs: 0,
metrics: metrics.MetricSlice{
{
Name: "http_requests_total",
Program: "regexp replace",
Kind: metrics.Counter,
Type: metrics.Int,
Keys: []string{"method", "route"},
LabelValues: []*metrics.LabelValue{
{
Labels: []string{"GET", "/v1/read/:num"},
Value: &datum.Int{Value: 1},
},
{
Labels: []string{"GET", "/v1/users/:num/orders/:num"},
Value: &datum.Int{Value: 1},
},
},
},
},
},
{
name: "match a pattern in a binary expr",
prog: `const N /n/
N {
}
N && 1 {
}
`,
log: `
`,
errs: 0,
metrics: nil,
},
}
func TestRuntimeEndToEnd(t *testing.T) {
testutil.SkipIfShort(t)
if testing.Verbose() {
testutil.SetFlag(t, "vmodule", "vm=2,loader=2,checker=2")
}
for _, tc := range vmTests {
tc := tc
t.Run(tc.name, func(t *testing.T) {
progRuntimeErrorsCheck := testutil.ExpectMapExpvarDeltaWithDeadline(t, "prog_runtime_errors_total", tc.name, tc.errs)
store := metrics.NewStore()
lines := make(chan *logline.LogLine, 1)
var wg sync.WaitGroup
r, err := New(lines, &wg, "", store, ErrorsAbort(), DumpAst(), DumpAstTypes(), DumpBytecode(), OmitMetricSource(), TraceExecution())
testutil.FatalIfErr(t, err)
compileErrors := r.CompileAndRun(tc.name, strings.NewReader(tc.prog))
testutil.FatalIfErr(t, compileErrors)
scanner := bufio.NewScanner(strings.NewReader(tc.log))
lineCount := 0
for scanner.Scan() {
lineCount++
lines <- logline.New(context.Background(), tc.name, scanner.Text())
}
close(lines)
wg.Wait()
progRuntimeErrorsCheck()
var ms metrics.MetricSlice
store.Range(func(m *metrics.Metric) error {
ms = append(ms, m)
return nil
})
// Ignore the datum.Time field as well, as the results will be unstable otherwise.
testutil.ExpectNoDiff(t, tc.metrics, ms, testutil.SortSlices(metrics.Less), testutil.IgnoreUnexported(metrics.Metric{}, sync.RWMutex{}, datum.String{}), testutil.IgnoreFields(datum.BaseDatum{}, "Time"))
})
}
}