mirror of https://github.com/prometheus/prometheus
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.
697 lines
13 KiB
697 lines
13 KiB
// 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/stretchr/testify/require" |
|
) |
|
|
|
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() |
|
require.Equal(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...) |
|
require.Equal(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() |
|
require.Equal(t, c.Duplicate, d, "test %d: incorrect duplicate bool", i) |
|
require.Equal(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) { |
|
require.Equal(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) |
|
require.Equal(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", |
|
}, |
|
} |
|
|
|
require.Equal(t, expected, labels, "unexpected labelset") |
|
|
|
require.Panics(t, func() { FromStrings("aaa", "111", "bbb") }) //nolint:staticcheck // Ignore SA5012, error is intentional test. |
|
} |
|
|
|
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) |
|
require.Equal(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) |
|
require.Equal(t, test.expected, got, "unexpected comparison result for test case %d", i) |
|
} |
|
} |
|
|
|
func TestLabels_Get(t *testing.T) { |
|
require.Equal(t, "", Labels{{"aaa", "111"}, {"bbb", "222"}}.Get("foo")) |
|
require.Equal(t, "111", Labels{{"aaa", "111"}, {"bbb", "222"}}.Get("aaa")) |
|
} |
|
|
|
func TestLabels_Copy(t *testing.T) { |
|
require.Equal(t, Labels{{"aaa", "111"}, {"bbb", "222"}}, Labels{{"aaa", "111"}, {"bbb", "222"}}.Copy()) |
|
} |
|
|
|
func TestLabels_Map(t *testing.T) { |
|
require.Equal(t, map[string]string{"aaa": "111", "bbb": "222"}, Labels{{"aaa", "111"}, {"bbb", "222"}}.Map()) |
|
} |
|
|
|
func TestLabels_WithLabels(t *testing.T) { |
|
require.Equal(t, Labels{{"aaa", "111"}, {"bbb", "222"}}, Labels{{"aaa", "111"}, {"bbb", "222"}, {"ccc", "333"}}.WithLabels("aaa", "bbb")) |
|
} |
|
|
|
func TestLabels_WithoutLabels(t *testing.T) { |
|
require.Equal(t, Labels{{"aaa", "111"}}, Labels{{"aaa", "111"}, {"bbb", "222"}, {"ccc", "333"}}.WithoutLabels("bbb", "ccc")) |
|
require.Equal(t, Labels{{"aaa", "111"}}, Labels{{"aaa", "111"}, {"bbb", "222"}, {MetricName, "333"}}.WithoutLabels("bbb")) |
|
} |
|
|
|
func TestBulider_NewBulider(t *testing.T) { |
|
require.Equal( |
|
t, |
|
&Builder{ |
|
base: Labels{{"aaa", "111"}}, |
|
del: []string{}, |
|
add: []Label{}, |
|
}, |
|
NewBuilder(Labels{{"aaa", "111"}}), |
|
) |
|
} |
|
|
|
func TestBuilder_Del(t *testing.T) { |
|
require.Equal( |
|
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) { |
|
require.Equal( |
|
t, |
|
&Builder{ |
|
base: Labels{{"aaa", "111"}}, |
|
del: []string{}, |
|
add: []Label{{"bbb", "222"}}, |
|
}, |
|
(&Builder{ |
|
base: Labels{{"aaa", "111"}}, |
|
del: []string{}, |
|
add: []Label{}, |
|
}).Set("bbb", "222"), |
|
) |
|
|
|
require.Equal( |
|
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) { |
|
require.Equal( |
|
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"}, |
|
} |
|
require.Equal(t, lbls.Hash(), lbls.Hash()) |
|
require.NotEqual(t, lbls.Hash(), Labels{lbls[1], lbls[0]}.Hash(), "unordered labels match.") |
|
require.NotEqual(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 |
|
}) |
|
} |
|
}
|
|
|