go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/server/cmd/statsd-to-tsmon/config_test.go (about)

     1  // Copyright 2020 The LUCI Authors.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //      http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package main
    16  
    17  import (
    18  	"strings"
    19  	"testing"
    20  
    21  	"go.chromium.org/luci/common/tsmon/field"
    22  	"go.chromium.org/luci/common/tsmon/metric"
    23  	"go.chromium.org/luci/common/tsmon/types"
    24  
    25  	"go.chromium.org/luci/server/cmd/statsd-to-tsmon/config"
    26  
    27  	. "github.com/smartystreets/goconvey/convey"
    28  	. "go.chromium.org/luci/common/testing/assertions"
    29  )
    30  
    31  func TestConfig(t *testing.T) {
    32  	t.Parallel()
    33  
    34  	Convey("Works", t, func() {
    35  		cfg, err := loadConfig(&config.Config{
    36  			Metrics: []*config.Metric{
    37  				{
    38  					Metric: "m1",
    39  					Kind:   config.Kind_COUNTER,
    40  					Fields: []string{"f1", "f2"},
    41  					Rules: []*config.Rule{
    42  						{
    43  							Pattern: "*.foo.${var}.*.bar.sfx1",
    44  							Fields: map[string]string{
    45  								"f1": "static",
    46  								"f2": "${var}",
    47  							},
    48  						},
    49  					},
    50  				},
    51  				{
    52  					Metric: "m2",
    53  					Kind:   config.Kind_COUNTER,
    54  					Rules: []*config.Rule{
    55  						{
    56  							Pattern: "foo.bar.sfx2",
    57  						},
    58  					},
    59  				},
    60  			},
    61  		})
    62  		So(err, ShouldBeNil)
    63  
    64  		rule := func(m string) string {
    65  			chunks := strings.Split(m, ".")
    66  			bytes := make([][]byte, len(chunks))
    67  			for i, c := range chunks {
    68  				bytes[i] = []byte(c)
    69  			}
    70  			if r := cfg.FindMatchingRule(bytes); r != nil {
    71  				return r.pattern.str
    72  			}
    73  			return ""
    74  		}
    75  
    76  		So(rule("xxx.foo.val.xxx.bar.sfx1"), ShouldEqual, "*.foo.${var}.*.bar.sfx1")
    77  		So(rule("yyy.foo.val.yyy.bar.sfx1"), ShouldEqual, "*.foo.${var}.*.bar.sfx1")
    78  		So(rule("foo.bar.sfx2"), ShouldEqual, "foo.bar.sfx2")
    79  
    80  		// Wrong length.
    81  		So(rule("foo.val.xxx.bar.sfx1"), ShouldEqual, "")
    82  		// Wrong static component.
    83  		So(rule("xxx.foo.val.xxx.baz.sfx1"), ShouldEqual, "")
    84  		// Unknown suffix.
    85  		So(rule("foo.bar.sfx3"), ShouldEqual, "")
    86  		// Empty.
    87  		So(rule(""), ShouldEqual, "")
    88  	})
    89  
    90  	Convey("Errors", t, func() {
    91  		call := func(cfg *config.Config) error {
    92  			_, err := loadConfig(cfg)
    93  			return err
    94  		}
    95  
    96  		Convey("Bad pattern", func() {
    97  			So(call(&config.Config{
    98  				Metrics: []*config.Metric{
    99  					{
   100  						Metric: "m3",
   101  						Kind:   config.Kind_COUNTER,
   102  						Rules: []*config.Rule{
   103  							{Pattern: "."},
   104  						},
   105  					},
   106  				},
   107  			}), ShouldErrLike, `bad pattern`)
   108  		})
   109  
   110  		Convey("Not enough fields", func() {
   111  			So(call(&config.Config{
   112  				Metrics: []*config.Metric{
   113  					{
   114  						Metric: "m4",
   115  						Kind:   config.Kind_COUNTER,
   116  						Fields: []string{"f1", "f2"},
   117  						Rules: []*config.Rule{
   118  							{Pattern: "foo", Fields: map[string]string{"f1": "value"}},
   119  						},
   120  					},
   121  				},
   122  			}), ShouldErrLike, `value of field "f2" is not provided`)
   123  		})
   124  
   125  		Convey("Extra field", func() {
   126  			So(call(&config.Config{
   127  				Metrics: []*config.Metric{
   128  					{
   129  						Metric: "m5",
   130  						Kind:   config.Kind_COUNTER,
   131  						Rules: []*config.Rule{
   132  							{Pattern: "foo", Fields: map[string]string{"f1": "value"}},
   133  						},
   134  					},
   135  				},
   136  			}), ShouldErrLike, `has too many fields`)
   137  		})
   138  
   139  		Convey("Bad field value", func() {
   140  			So(call(&config.Config{
   141  				Metrics: []*config.Metric{
   142  					{
   143  						Metric: "m6",
   144  						Kind:   config.Kind_COUNTER,
   145  						Fields: []string{"f1"},
   146  						Rules: []*config.Rule{
   147  							{Pattern: "foo", Fields: map[string]string{"f1": "foo-${bar}"}},
   148  						},
   149  					},
   150  				},
   151  			}), ShouldErrLike, `field "f1" has bad value`)
   152  		})
   153  
   154  		Convey("Unknown var ref", func() {
   155  			So(call(&config.Config{
   156  				Metrics: []*config.Metric{
   157  					{
   158  						Metric: "m7",
   159  						Kind:   config.Kind_COUNTER,
   160  						Fields: []string{"f1"},
   161  						Rules: []*config.Rule{
   162  							{Pattern: "foo", Fields: map[string]string{"f1": "${bar}"}},
   163  						},
   164  					},
   165  				},
   166  			}), ShouldErrLike, `field "f1" references undefined var "bar"`)
   167  		})
   168  
   169  		Convey("Not a static suffix", func() {
   170  			So(call(&config.Config{
   171  				Metrics: []*config.Metric{
   172  					{
   173  						Metric: "m8",
   174  						Kind:   config.Kind_COUNTER,
   175  						Rules: []*config.Rule{
   176  							{Pattern: "foo.*"},
   177  						},
   178  					},
   179  				},
   180  			}), ShouldErrLike, `must end with a static suffix`)
   181  		})
   182  
   183  		Convey("Dup suffix", func() {
   184  			So(call(&config.Config{
   185  				Metrics: []*config.Metric{
   186  					{
   187  						Metric: "m9",
   188  						Kind:   config.Kind_COUNTER,
   189  						Rules: []*config.Rule{
   190  							{Pattern: "foo1.bar"},
   191  						},
   192  					},
   193  					{
   194  						Metric: "m10",
   195  						Kind:   config.Kind_COUNTER,
   196  						Rules: []*config.Rule{
   197  							{Pattern: "foo2.bar"},
   198  						},
   199  					},
   200  				},
   201  			}), ShouldErrLike, `there's already another rule with this suffix`)
   202  		})
   203  	})
   204  }
   205  
   206  func TestLoadMetrics(t *testing.T) {
   207  	t.Parallel()
   208  
   209  	Convey("Works", t, func() {
   210  		m, err := loadMetrics([]*config.Metric{
   211  			{
   212  				Metric: "gauge",
   213  				Kind:   config.Kind_GAUGE,
   214  				Units:  config.Unit_MILLISECONDS,
   215  				Fields: []string{"f1", "f2"},
   216  			},
   217  			{
   218  				Metric: "counter",
   219  				Kind:   config.Kind_COUNTER,
   220  				Units:  config.Unit_BYTES,
   221  				Fields: []string{"f3", "f4"},
   222  			},
   223  			{
   224  				Metric: "distribution",
   225  				Kind:   config.Kind_CUMULATIVE_DISTRIBUTION,
   226  				Fields: []string{"f5", "f6"},
   227  			},
   228  		})
   229  		So(err, ShouldBeNil)
   230  		So(m, ShouldHaveLength, 3)
   231  
   232  		g, ok := m["gauge"].(metric.Int)
   233  		So(ok, ShouldBeTrue)
   234  		So(g.Metadata().Units, ShouldEqual, types.Milliseconds)
   235  		So(g.Info().Fields, ShouldResemble, []field.Field{
   236  			{Name: "f1", Type: field.StringType},
   237  			{Name: "f2", Type: field.StringType},
   238  		})
   239  
   240  		c, ok := m["counter"].(metric.Counter)
   241  		So(ok, ShouldBeTrue)
   242  		So(c.Metadata().Units, ShouldEqual, types.Bytes)
   243  		So(c.Info().Fields, ShouldResemble, []field.Field{
   244  			{Name: "f3", Type: field.StringType},
   245  			{Name: "f4", Type: field.StringType},
   246  		})
   247  
   248  		d, ok := m["distribution"].(metric.CumulativeDistribution)
   249  		So(ok, ShouldBeTrue)
   250  		So(d.Metadata().Units, ShouldEqual, types.Milliseconds)
   251  		So(d.Info().Fields, ShouldResemble, []field.Field{
   252  			{Name: "f5", Type: field.StringType},
   253  			{Name: "f6", Type: field.StringType},
   254  		})
   255  	})
   256  }
   257  
   258  func TestPattern(t *testing.T) {
   259  	t.Parallel()
   260  
   261  	Convey("Parse success", t, func() {
   262  		p, err := parsePattern("abc.${foo}.*.${bar}.zzz")
   263  		So(err, ShouldBeNil)
   264  		So(p, ShouldResemble, &pattern{
   265  			str: "abc.${foo}.*.${bar}.zzz",
   266  			len: 5,
   267  			vars: map[string]int{
   268  				"foo": 1,
   269  				"bar": 3,
   270  			},
   271  			static: []staticNameComponent{
   272  				{0, "abc"},
   273  				{4, "zzz"},
   274  			},
   275  			suffix: "zzz",
   276  		})
   277  	})
   278  
   279  	Convey("All static", t, func() {
   280  		p, err := parsePattern("abc.def")
   281  		So(err, ShouldBeNil)
   282  		So(p, ShouldResemble, &pattern{
   283  			str: "abc.def",
   284  			len: 2,
   285  			static: []staticNameComponent{
   286  				{0, "abc"},
   287  				{1, "def"},
   288  			},
   289  			suffix: "def",
   290  		})
   291  	})
   292  
   293  	Convey("Empty component", t, func() {
   294  		_, err := parsePattern("abc..zzz")
   295  		So(err, ShouldErrLike, "empty name component")
   296  	})
   297  
   298  	Convey("Bad var", t, func() {
   299  		_, err := parsePattern("${}")
   300  		So(err, ShouldErrLike, "var name is required")
   301  
   302  		_, err = parsePattern("foo-${bar}")
   303  		So(err, ShouldErrLike, "is not allowed")
   304  	})
   305  
   306  	Convey("Dup var", t, func() {
   307  		_, err := parsePattern("${abc}.${abc}")
   308  		So(err, ShouldErrLike, "duplicate var")
   309  	})
   310  
   311  	Convey("Var suffix", t, func() {
   312  		_, err := parsePattern("${abc}.${def}")
   313  		So(err, ShouldErrLike, "must end with a static suffix")
   314  	})
   315  
   316  	Convey("Star suffix", t, func() {
   317  		_, err := parsePattern("abc.*")
   318  		So(err, ShouldErrLike, "must end with a static suffix")
   319  	})
   320  }