// Copyright 2019 The Prometheus 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 labels import ( "fmt" "strings" "testing" "github.com/prometheus/prometheus/util/testutil" ) func TestLabels_String(t *testing.T) { cases := []struct { lables Labels expected string }{ { lables: Labels{ { Name: "t1", Value: "t1", }, { Name: "t2", Value: "t2", }, }, expected: "{t1=\"t1\", t2=\"t2\"}", }, { lables: Labels{}, expected: "{}", }, { lables: nil, expected: "{}", }, } for _, c := range cases { str := c.lables.String() testutil.Equals(t, c.expected, str) } } func TestLabels_MatchLabels(t *testing.T) { labels := Labels{ { Name: "__name__", Value: "ALERTS", }, { Name: "alertname", Value: "HTTPRequestRateLow", }, { Name: "alertstate", Value: "pending", }, { Name: "instance", Value: "0", }, { Name: "job", Value: "app-server", }, { Name: "severity", Value: "critical", }, } tests := []struct { providedNames []string on bool expected Labels }{ // on = true, explicitly including metric name in matching. { providedNames: []string{ "__name__", "alertname", "alertstate", "instance", }, on: true, expected: Labels{ { Name: "__name__", Value: "ALERTS", }, { Name: "alertname", Value: "HTTPRequestRateLow", }, { Name: "alertstate", Value: "pending", }, { Name: "instance", Value: "0", }, }, }, // on = false, explicitly excluding metric name from matching. { providedNames: []string{ "__name__", "alertname", "alertstate", "instance", }, on: false, expected: Labels{ { Name: "job", Value: "app-server", }, { Name: "severity", Value: "critical", }, }, }, // on = true, explicitly excluding metric name from matching. { providedNames: []string{ "alertname", "alertstate", "instance", }, on: true, expected: Labels{ { Name: "alertname", Value: "HTTPRequestRateLow", }, { Name: "alertstate", Value: "pending", }, { Name: "instance", Value: "0", }, }, }, // on = false, implicitly excluding metric name from matching. { providedNames: []string{ "alertname", "alertstate", "instance", }, on: false, expected: Labels{ { Name: "job", Value: "app-server", }, { Name: "severity", Value: "critical", }, }, }, } for i, test := range tests { got := labels.MatchLabels(test.on, test.providedNames...) testutil.Equals(t, test.expected, got, "unexpected labelset for test case %d", i) } } func TestLabels_HasDuplicateLabelNames(t *testing.T) { cases := []struct { Input Labels Duplicate bool LabelName string }{ { Input: FromMap(map[string]string{"__name__": "up", "hostname": "localhost"}), Duplicate: false, }, { Input: append( FromMap(map[string]string{"__name__": "up", "hostname": "localhost"}), FromMap(map[string]string{"hostname": "127.0.0.1"})..., ), Duplicate: true, LabelName: "hostname", }, } for i, c := range cases { l, d := c.Input.HasDuplicateLabelNames() testutil.Equals(t, c.Duplicate, d, "test %d: incorrect duplicate bool", i) testutil.Equals(t, c.LabelName, l, "test %d: incorrect label name", i) } } func TestLabels_WithoutEmpty(t *testing.T) { for _, test := range []struct { input Labels expected Labels }{ { input: Labels{ {Name: "foo"}, {Name: "bar"}, }, expected: Labels{}, }, { input: Labels{ {Name: "foo"}, {Name: "bar"}, {Name: "baz"}, }, expected: Labels{}, }, { input: Labels{ {Name: "__name__", Value: "test"}, {Name: "hostname", Value: "localhost"}, {Name: "job", Value: "check"}, }, expected: Labels{ {Name: "__name__", Value: "test"}, {Name: "hostname", Value: "localhost"}, {Name: "job", Value: "check"}, }, }, { input: Labels{ {Name: "__name__", Value: "test"}, {Name: "hostname", Value: "localhost"}, {Name: "bar"}, {Name: "job", Value: "check"}, }, expected: Labels{ {Name: "__name__", Value: "test"}, {Name: "hostname", Value: "localhost"}, {Name: "job", Value: "check"}, }, }, { input: Labels{ {Name: "__name__", Value: "test"}, {Name: "foo"}, {Name: "hostname", Value: "localhost"}, {Name: "bar"}, {Name: "job", Value: "check"}, }, expected: Labels{ {Name: "__name__", Value: "test"}, {Name: "hostname", Value: "localhost"}, {Name: "job", Value: "check"}, }, }, { input: Labels{ {Name: "__name__", Value: "test"}, {Name: "foo"}, {Name: "baz"}, {Name: "hostname", Value: "localhost"}, {Name: "bar"}, {Name: "job", Value: "check"}, }, expected: Labels{ {Name: "__name__", Value: "test"}, {Name: "hostname", Value: "localhost"}, {Name: "job", Value: "check"}, }, }, } { t.Run("", func(t *testing.T) { testutil.Equals(t, test.expected, test.input.WithoutEmpty()) }) } } func TestLabels_Equal(t *testing.T) { labels := Labels{ { Name: "aaa", Value: "111", }, { Name: "bbb", Value: "222", }, } tests := []struct { compared Labels expected bool }{ { compared: Labels{ { Name: "aaa", Value: "111", }, { Name: "bbb", Value: "222", }, { Name: "ccc", Value: "333", }, }, expected: false, }, { compared: Labels{ { Name: "aaa", Value: "111", }, { Name: "bar", Value: "222", }, }, expected: false, }, { compared: Labels{ { Name: "aaa", Value: "111", }, { Name: "bbb", Value: "233", }, }, expected: false, }, { compared: Labels{ { Name: "aaa", Value: "111", }, { Name: "bbb", Value: "222", }, }, expected: true, }, } for i, test := range tests { got := Equal(labels, test.compared) testutil.Equals(t, test.expected, got, "unexpected comparison result for test case %d", i) } } func TestLabels_FromStrings(t *testing.T) { labels := FromStrings("aaa", "111", "bbb", "222") expected := Labels{ { Name: "aaa", Value: "111", }, { Name: "bbb", Value: "222", }, } testutil.Equals(t, expected, labels, "unexpected labelset") defer func() { recover() }() FromStrings("aaa", "111", "bbb") testutil.Assert(t, false, "did not panic as expected") } func TestLabels_Compare(t *testing.T) { labels := Labels{ { Name: "aaa", Value: "111", }, { Name: "bbb", Value: "222", }, } tests := []struct { compared Labels expected int }{ { compared: Labels{ { Name: "aaa", Value: "110", }, { Name: "bbb", Value: "222", }, }, expected: 1, }, { compared: Labels{ { Name: "aaa", Value: "111", }, { Name: "bbb", Value: "233", }, }, expected: -1, }, { compared: Labels{ { Name: "aaa", Value: "111", }, { Name: "bar", Value: "222", }, }, expected: 1, }, { compared: Labels{ { Name: "aaa", Value: "111", }, { Name: "bbc", Value: "222", }, }, expected: -1, }, { compared: Labels{ { Name: "aaa", Value: "111", }, }, expected: 1, }, { compared: Labels{ { Name: "aaa", Value: "111", }, { Name: "bbb", Value: "222", }, { Name: "ccc", Value: "333", }, { Name: "ddd", Value: "444", }, }, expected: -2, }, { compared: Labels{ { Name: "aaa", Value: "111", }, { Name: "bbb", Value: "222", }, }, expected: 0, }, } for i, test := range tests { got := Compare(labels, test.compared) testutil.Equals(t, test.expected, got, "unexpected comparison result for test case %d", i) } } func TestLabels_Has(t *testing.T) { tests := []struct { input string expected bool }{ { input: "foo", expected: false, }, { input: "aaa", expected: true, }, } labelsSet := Labels{ { Name: "aaa", Value: "111", }, { Name: "bbb", Value: "222", }, } for i, test := range tests { got := labelsSet.Has(test.input) testutil.Equals(t, test.expected, got, "unexpected comparison result for test case %d", i) } } func TestLabels_Get(t *testing.T) { testutil.Equals(t, "", Labels{{"aaa", "111"}, {"bbb", "222"}}.Get("foo")) testutil.Equals(t, "111", Labels{{"aaa", "111"}, {"bbb", "222"}}.Get("aaa")) } func TestLabels_Copy(t *testing.T) { testutil.Equals(t, Labels{{"aaa", "111"}, {"bbb", "222"}}, Labels{{"aaa", "111"}, {"bbb", "222"}}.Copy()) } func TestLabels_Map(t *testing.T) { testutil.Equals(t, map[string]string{"aaa": "111", "bbb": "222"}, Labels{{"aaa", "111"}, {"bbb", "222"}}.Map()) } func TestLabels_WithLabels(t *testing.T) { testutil.Equals(t, Labels{{"aaa", "111"}, {"bbb", "222"}}, Labels{{"aaa", "111"}, {"bbb", "222"}, {"ccc", "333"}}.WithLabels("aaa", "bbb")) } func TestLabels_WithoutLabels(t *testing.T) { testutil.Equals(t, Labels{{"aaa", "111"}}, Labels{{"aaa", "111"}, {"bbb", "222"}, {"ccc", "333"}}.WithoutLabels("bbb", "ccc")) testutil.Equals(t, Labels{{"aaa", "111"}}, Labels{{"aaa", "111"}, {"bbb", "222"}, {MetricName, "333"}}.WithoutLabels("bbb")) } func TestBulider_NewBulider(t *testing.T) { testutil.Equals( t, &Builder{ base: Labels{{"aaa", "111"}}, del: []string{}, add: []Label{}, }, NewBuilder(Labels{{"aaa", "111"}}), ) } func TestBuilder_Del(t *testing.T) { testutil.Equals( t, &Builder{ del: []string{"bbb"}, add: []Label{{"aaa", "111"}, {"ccc", "333"}}, }, (&Builder{ del: []string{}, add: []Label{{"aaa", "111"}, {"bbb", "222"}, {"ccc", "333"}}, }).Del("bbb"), ) } func TestBuilder_Set(t *testing.T) { testutil.Equals( t, &Builder{ base: Labels{{"aaa", "111"}}, del: []string{}, add: []Label{{"bbb", "222"}}, }, (&Builder{ base: Labels{{"aaa", "111"}}, del: []string{}, add: []Label{}, }).Set("bbb", "222"), ) testutil.Equals( t, &Builder{ base: Labels{{"aaa", "111"}}, del: []string{}, add: []Label{{"bbb", "333"}}, }, (&Builder{ base: Labels{{"aaa", "111"}}, del: []string{}, add: []Label{{"bbb", "222"}}, }).Set("bbb", "333"), ) } func TestBuilder_Labels(t *testing.T) { testutil.Equals( t, Labels{{"aaa", "111"}, {"ccc", "333"}, {"ddd", "444"}}, (&Builder{ base: Labels{{"aaa", "111"}, {"bbb", "222"}, {"ccc", "333"}}, del: []string{"bbb"}, add: []Label{{"ddd", "444"}}, }).Labels(), ) } func TestLabels_Hash(t *testing.T) { lbls := Labels{ {Name: "foo", Value: "bar"}, {Name: "baz", Value: "qux"}, } testutil.Equals(t, lbls.Hash(), lbls.Hash()) testutil.Assert(t, lbls.Hash() != Labels{lbls[1], lbls[0]}.Hash(), "unordered labels match.") testutil.Assert(t, lbls.Hash() != Labels{lbls[0]}.Hash(), "different labels match.") } var benchmarkLabelsResult uint64 func BenchmarkLabels_Hash(b *testing.B) { for _, tcase := range []struct { name string lbls Labels }{ { name: "typical labels under 1KB", lbls: func() Labels { lbls := make(Labels, 10) for i := 0; i < len(lbls); i++ { // Label ~20B name, 50B value. lbls[i] = Label{Name: fmt.Sprintf("abcdefghijabcdefghijabcdefghij%d", i), Value: fmt.Sprintf("abcdefghijabcdefghijabcdefghijabcdefghijabcdefghij%d", i)} } return lbls }(), }, { name: "bigger labels over 1KB", lbls: func() Labels { lbls := make(Labels, 10) for i := 0; i < len(lbls); i++ { //Label ~50B name, 50B value. lbls[i] = Label{Name: fmt.Sprintf("abcdefghijabcdefghijabcdefghijabcdefghijabcdefghij%d", i), Value: fmt.Sprintf("abcdefghijabcdefghijabcdefghijabcdefghijabcdefghij%d", i)} } return lbls }(), }, { name: "extremely large label value 10MB", lbls: func() Labels { lbl := &strings.Builder{} lbl.Grow(1024 * 1024 * 10) // 10MB. word := "abcdefghij" for i := 0; i < lbl.Cap()/len(word); i++ { _, _ = lbl.WriteString(word) } return Labels{{Name: "__name__", Value: lbl.String()}} }(), }, } { b.Run(tcase.name, func(b *testing.B) { var h uint64 b.ReportAllocs() b.ResetTimer() for i := 0; i < b.N; i++ { h = tcase.lbls.Hash() } benchmarkLabelsResult = h }) } }