blob: fd17892649099dd6d721c510e6f236b119715f97 [file] [log] [blame]
// Copyright 2020 The LUCI Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package main
import (
"strings"
"testing"
"go.chromium.org/luci/common/tsmon/field"
"go.chromium.org/luci/common/tsmon/metric"
"go.chromium.org/luci/common/tsmon/types"
"go.chromium.org/luci/server/cmd/statsd-to-tsmon/config"
. "github.com/smartystreets/goconvey/convey"
. "go.chromium.org/luci/common/testing/assertions"
)
func TestConfig(t *testing.T) {
t.Parallel()
Convey("Works", t, func() {
cfg, err := loadConfig(&config.Config{
Metrics: []*config.Metric{
{
Metric: "m1",
Kind: config.Kind_COUNTER,
Fields: []string{"f1", "f2"},
Rules: []*config.Rule{
{
Pattern: "*.foo.${var}.*.bar.sfx1",
Fields: map[string]string{
"f1": "static",
"f2": "${var}",
},
},
},
},
{
Metric: "m2",
Kind: config.Kind_COUNTER,
Rules: []*config.Rule{
{
Pattern: "foo.bar.sfx2",
},
},
},
},
})
So(err, ShouldBeNil)
rule := func(m string) string {
chunks := strings.Split(m, ".")
bytes := make([][]byte, len(chunks))
for i, c := range chunks {
bytes[i] = []byte(c)
}
if r := cfg.FindMatchingRule(bytes); r != nil {
return r.pattern.str
}
return ""
}
So(rule("xxx.foo.val.xxx.bar.sfx1"), ShouldEqual, "*.foo.${var}.*.bar.sfx1")
So(rule("yyy.foo.val.yyy.bar.sfx1"), ShouldEqual, "*.foo.${var}.*.bar.sfx1")
So(rule("foo.bar.sfx2"), ShouldEqual, "foo.bar.sfx2")
// Wrong length.
So(rule("foo.val.xxx.bar.sfx1"), ShouldEqual, "")
// Wrong static component.
So(rule("xxx.foo.val.xxx.baz.sfx1"), ShouldEqual, "")
// Unknown suffix.
So(rule("foo.bar.sfx3"), ShouldEqual, "")
// Empty.
So(rule(""), ShouldEqual, "")
})
Convey("Errors", t, func() {
call := func(cfg *config.Config) error {
_, err := loadConfig(cfg)
return err
}
Convey("Bad pattern", func() {
So(call(&config.Config{
Metrics: []*config.Metric{
{
Metric: "m3",
Kind: config.Kind_COUNTER,
Rules: []*config.Rule{
{Pattern: "."},
},
},
},
}), ShouldErrLike, `bad pattern`)
})
Convey("Not enough fields", func() {
So(call(&config.Config{
Metrics: []*config.Metric{
{
Metric: "m4",
Kind: config.Kind_COUNTER,
Fields: []string{"f1", "f2"},
Rules: []*config.Rule{
{Pattern: "foo", Fields: map[string]string{"f1": "value"}},
},
},
},
}), ShouldErrLike, `value of field "f2" is not provided`)
})
Convey("Extra field", func() {
So(call(&config.Config{
Metrics: []*config.Metric{
{
Metric: "m5",
Kind: config.Kind_COUNTER,
Rules: []*config.Rule{
{Pattern: "foo", Fields: map[string]string{"f1": "value"}},
},
},
},
}), ShouldErrLike, `has too many fields`)
})
Convey("Bad field value", func() {
So(call(&config.Config{
Metrics: []*config.Metric{
{
Metric: "m6",
Kind: config.Kind_COUNTER,
Fields: []string{"f1"},
Rules: []*config.Rule{
{Pattern: "foo", Fields: map[string]string{"f1": "foo-${bar}"}},
},
},
},
}), ShouldErrLike, `field "f1" has bad value`)
})
Convey("Unknown var ref", func() {
So(call(&config.Config{
Metrics: []*config.Metric{
{
Metric: "m7",
Kind: config.Kind_COUNTER,
Fields: []string{"f1"},
Rules: []*config.Rule{
{Pattern: "foo", Fields: map[string]string{"f1": "${bar}"}},
},
},
},
}), ShouldErrLike, `field "f1" references undefined var "bar"`)
})
Convey("Not a static suffix", func() {
So(call(&config.Config{
Metrics: []*config.Metric{
{
Metric: "m8",
Kind: config.Kind_COUNTER,
Rules: []*config.Rule{
{Pattern: "foo.*"},
},
},
},
}), ShouldErrLike, `must end with a static suffix`)
})
Convey("Dup suffix", func() {
So(call(&config.Config{
Metrics: []*config.Metric{
{
Metric: "m9",
Kind: config.Kind_COUNTER,
Rules: []*config.Rule{
{Pattern: "foo1.bar"},
},
},
{
Metric: "m10",
Kind: config.Kind_COUNTER,
Rules: []*config.Rule{
{Pattern: "foo2.bar"},
},
},
},
}), ShouldErrLike, `there's already another rule with this suffix`)
})
})
}
func TestLoadMetrics(t *testing.T) {
t.Parallel()
Convey("Works", t, func() {
m, err := loadMetrics([]*config.Metric{
{
Metric: "gauge",
Kind: config.Kind_GAUGE,
Units: config.Unit_MILLISECONDS,
Fields: []string{"f1", "f2"},
},
{
Metric: "counter",
Kind: config.Kind_COUNTER,
Units: config.Unit_BYTES,
Fields: []string{"f3", "f4"},
},
{
Metric: "distribution",
Kind: config.Kind_CUMULATIVE_DISTRIBUTION,
Fields: []string{"f5", "f6"},
},
})
So(err, ShouldBeNil)
So(m, ShouldHaveLength, 3)
g, ok := m["gauge"].(metric.Int)
So(ok, ShouldBeTrue)
So(g.Metadata().Units, ShouldEqual, types.Milliseconds)
So(g.Info().Fields, ShouldResemble, []field.Field{
{Name: "f1", Type: field.StringType},
{Name: "f2", Type: field.StringType},
})
c, ok := m["counter"].(metric.Counter)
So(ok, ShouldBeTrue)
So(c.Metadata().Units, ShouldEqual, types.Bytes)
So(c.Info().Fields, ShouldResemble, []field.Field{
{Name: "f3", Type: field.StringType},
{Name: "f4", Type: field.StringType},
})
d, ok := m["distribution"].(metric.CumulativeDistribution)
So(ok, ShouldBeTrue)
So(d.Metadata().Units, ShouldEqual, types.Milliseconds)
So(d.Info().Fields, ShouldResemble, []field.Field{
{Name: "f5", Type: field.StringType},
{Name: "f6", Type: field.StringType},
})
})
}
func TestPattern(t *testing.T) {
t.Parallel()
Convey("Parse success", t, func() {
p, err := parsePattern("abc.${foo}.*.${bar}.zzz")
So(err, ShouldBeNil)
So(p, ShouldResemble, &pattern{
str: "abc.${foo}.*.${bar}.zzz",
len: 5,
vars: map[string]int{
"foo": 1,
"bar": 3,
},
static: []staticNameComponent{
{0, "abc"},
{4, "zzz"},
},
suffix: "zzz",
})
})
Convey("All static", t, func() {
p, err := parsePattern("abc.def")
So(err, ShouldBeNil)
So(p, ShouldResemble, &pattern{
str: "abc.def",
len: 2,
static: []staticNameComponent{
{0, "abc"},
{1, "def"},
},
suffix: "def",
})
})
Convey("Empty component", t, func() {
_, err := parsePattern("abc..zzz")
So(err, ShouldErrLike, "empty name component")
})
Convey("Bad var", t, func() {
_, err := parsePattern("${}")
So(err, ShouldErrLike, "var name is required")
_, err = parsePattern("foo-${bar}")
So(err, ShouldErrLike, "is not allowed")
})
Convey("Dup var", t, func() {
_, err := parsePattern("${abc}.${abc}")
So(err, ShouldErrLike, "duplicate var")
})
Convey("Var suffix", t, func() {
_, err := parsePattern("${abc}.${def}")
So(err, ShouldErrLike, "must end with a static suffix")
})
Convey("Star suffix", t, func() {
_, err := parsePattern("abc.*")
So(err, ShouldErrLike, "must end with a static suffix")
})
}