2019-02-09 09:17:52 +00:00
// 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 (
2020-10-15 10:31:28 +00:00
"fmt"
"strings"
2019-02-09 09:17:52 +00:00
"testing"
2019-12-27 09:32:19 +00:00
2020-10-29 09:43:23 +00:00
"github.com/stretchr/testify/require"
2019-02-09 09:17:52 +00:00
)
2020-04-22 07:02:47 +00:00
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 ( )
2020-10-29 09:43:23 +00:00
require . Equal ( t , c . expected , str )
2020-04-22 07:02:47 +00:00
}
}
2019-02-09 09:17:52 +00:00
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" ,
} ,
}
2019-12-27 09:32:19 +00:00
tests := [ ] struct {
providedNames [ ] string
on bool
expected Labels
} {
// on = true, explicitly including metric name in matching.
2019-02-09 09:17:52 +00:00
{
2019-12-27 09:32:19 +00:00
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" ,
} ,
} ,
2019-02-09 09:17:52 +00:00
} ,
2019-12-27 09:32:19 +00:00
// on = false, explicitly excluding metric name from matching.
2019-02-09 09:17:52 +00:00
{
2019-12-27 09:32:19 +00:00
providedNames : [ ] string {
"__name__" ,
"alertname" ,
"alertstate" ,
"instance" ,
} ,
on : false ,
expected : Labels {
{
Name : "job" ,
Value : "app-server" ,
} ,
{
Name : "severity" ,
Value : "critical" ,
} ,
} ,
2019-02-09 09:17:52 +00:00
} ,
2019-12-27 09:32:19 +00:00
// on = true, explicitly excluding metric name from matching.
2019-02-09 09:17:52 +00:00
{
2019-12-27 09:32:19 +00:00
providedNames : [ ] string {
"alertname" ,
"alertstate" ,
"instance" ,
} ,
on : true ,
expected : Labels {
{
Name : "alertname" ,
Value : "HTTPRequestRateLow" ,
} ,
{
Name : "alertstate" ,
Value : "pending" ,
} ,
{
Name : "instance" ,
Value : "0" ,
} ,
} ,
2019-02-09 09:17:52 +00:00
} ,
2019-12-27 09:32:19 +00:00
// on = false, implicitly excluding metric name from matching.
2019-02-09 09:17:52 +00:00
{
2019-12-27 09:32:19 +00:00
providedNames : [ ] string {
"alertname" ,
"alertstate" ,
"instance" ,
} ,
on : false ,
expected : Labels {
{
Name : "job" ,
Value : "app-server" ,
} ,
{
Name : "severity" ,
Value : "critical" ,
} ,
} ,
2019-02-09 09:17:52 +00:00
} ,
}
2019-12-27 09:32:19 +00:00
for i , test := range tests {
got := labels . MatchLabels ( test . on , test . providedNames ... )
2020-10-29 09:43:23 +00:00
require . Equal ( t , test . expected , got , "unexpected labelset for test case %d" , i )
2019-02-09 09:17:52 +00:00
}
}
2020-01-20 11:05:27 +00:00
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 ( )
2020-10-29 09:43:23 +00:00
require . Equal ( t , c . Duplicate , d , "test %d: incorrect duplicate bool" , i )
require . Equal ( t , c . LabelName , l , "test %d: incorrect label name" , i )
2020-01-20 11:05:27 +00:00
}
}
2020-02-19 11:56:12 +00:00
func TestLabels_WithoutEmpty ( t * testing . T ) {
2020-10-13 07:57:53 +00:00
for _ , test := range [ ] struct {
2020-02-19 11:56:12 +00:00
input Labels
expected Labels
} {
{
input : Labels {
2020-10-13 07:57:53 +00:00
{ 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" } ,
2020-02-19 11:56:12 +00:00
} ,
expected : Labels {
2020-10-13 07:57:53 +00:00
{ Name : "__name__" , Value : "test" } ,
{ Name : "hostname" , Value : "localhost" } ,
{ Name : "job" , Value : "check" } ,
2020-02-19 11:56:12 +00:00
} ,
} ,
{
input : Labels {
2020-10-13 07:57:53 +00:00
{ Name : "__name__" , Value : "test" } ,
{ Name : "hostname" , Value : "localhost" } ,
{ Name : "bar" } ,
{ Name : "job" , Value : "check" } ,
2020-02-19 11:56:12 +00:00
} ,
expected : Labels {
2020-10-13 07:57:53 +00:00
{ Name : "__name__" , Value : "test" } ,
{ Name : "hostname" , Value : "localhost" } ,
{ Name : "job" , Value : "check" } ,
2020-02-19 11:56:12 +00:00
} ,
} ,
2020-10-13 07:57:53 +00:00
{
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 ) {
2020-10-29 09:43:23 +00:00
require . Equal ( t , test . expected , test . input . WithoutEmpty ( ) )
2020-10-13 07:57:53 +00:00
} )
2020-02-19 11:56:12 +00:00
}
}
2020-03-03 14:17:54 +00:00
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 )
2020-10-29 09:43:23 +00:00
require . Equal ( t , test . expected , got , "unexpected comparison result for test case %d" , i )
2020-03-03 14:17:54 +00:00
}
}
2020-06-24 10:54:30 +00:00
func TestLabels_FromStrings ( t * testing . T ) {
labels := FromStrings ( "aaa" , "111" , "bbb" , "222" )
expected := Labels {
{
Name : "aaa" ,
Value : "111" ,
} ,
{
Name : "bbb" ,
Value : "222" ,
} ,
}
2020-10-29 09:43:23 +00:00
require . Equal ( t , expected , labels , "unexpected labelset" )
2020-06-24 10:54:30 +00:00
2021-04-13 06:53:57 +00:00
require . Panics ( t , func ( ) { FromStrings ( "aaa" , "111" , "bbb" ) } ) //nolint:staticcheck // Ignore SA5012, error is intentional test.
2020-06-24 10:54:30 +00:00
}
2020-03-03 14:17:54 +00:00
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 )
2020-10-29 09:43:23 +00:00
require . Equal ( t , test . expected , got , "unexpected comparison result for test case %d" , i )
2020-03-03 14:17:54 +00:00
}
}
2020-04-09 15:49:09 +00:00
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 )
2020-10-29 09:43:23 +00:00
require . Equal ( t , test . expected , got , "unexpected comparison result for test case %d" , i )
2020-04-09 15:49:09 +00:00
}
}
2020-05-12 09:42:21 +00:00
func TestLabels_Get ( t * testing . T ) {
2020-10-29 09:43:23 +00:00
require . Equal ( t , "" , Labels { { "aaa" , "111" } , { "bbb" , "222" } } . Get ( "foo" ) )
require . Equal ( t , "111" , Labels { { "aaa" , "111" } , { "bbb" , "222" } } . Get ( "aaa" ) )
2020-05-12 09:42:21 +00:00
}
2021-08-24 11:05:19 +00:00
// BenchmarkLabels_Get was written to check whether a binary search can improve the performance vs the linear search implementation
// The results have shown that binary search would only be better when searching last labels in scenarios with more than 10 labels.
// In the following list, `old` is the linear search while `new` is the binary search implementaiton (without calling sort.Search, which performs even worse here)
// name old time/op new time/op delta
// Labels_Get/with_5_labels/get_first_label 5.12ns ± 0% 14.24ns ± 0% ~ (p=1.000 n=1+1)
// Labels_Get/with_5_labels/get_middle_label 13.5ns ± 0% 18.5ns ± 0% ~ (p=1.000 n=1+1)
// Labels_Get/with_5_labels/get_last_label 21.9ns ± 0% 18.9ns ± 0% ~ (p=1.000 n=1+1)
// Labels_Get/with_10_labels/get_first_label 5.11ns ± 0% 19.47ns ± 0% ~ (p=1.000 n=1+1)
// Labels_Get/with_10_labels/get_middle_label 26.2ns ± 0% 19.3ns ± 0% ~ (p=1.000 n=1+1)
// Labels_Get/with_10_labels/get_last_label 42.8ns ± 0% 23.4ns ± 0% ~ (p=1.000 n=1+1)
// Labels_Get/with_30_labels/get_first_label 5.10ns ± 0% 24.63ns ± 0% ~ (p=1.000 n=1+1)
// Labels_Get/with_30_labels/get_middle_label 75.8ns ± 0% 29.7ns ± 0% ~ (p=1.000 n=1+1)
// Labels_Get/with_30_labels/get_last_label 169ns ± 0% 29ns ± 0% ~ (p=1.000 n=1+1)
func BenchmarkLabels_Get ( b * testing . B ) {
maxLabels := 30
allLabels := make ( Labels , maxLabels )
for i := 0 ; i < maxLabels ; i ++ {
allLabels [ i ] = Label { Name : strings . Repeat ( string ( 'a' + byte ( i ) ) , 5 ) }
}
for _ , size := range [ ] int { 5 , 10 , maxLabels } {
b . Run ( fmt . Sprintf ( "with %d labels" , size ) , func ( b * testing . B ) {
labels := allLabels [ : size ]
for _ , scenario := range [ ] struct {
desc , label string
} {
{ "get first label" , labels [ 0 ] . Name } ,
{ "get middle label" , labels [ size / 2 ] . Name } ,
{ "get last label" , labels [ size - 1 ] . Name } ,
} {
b . Run ( scenario . desc , func ( b * testing . B ) {
b . ResetTimer ( )
for i := 0 ; i < b . N ; i ++ {
_ = labels . Get ( scenario . label )
}
} )
}
} )
}
}
2020-05-12 09:42:21 +00:00
func TestLabels_Copy ( t * testing . T ) {
2020-10-29 09:43:23 +00:00
require . Equal ( t , Labels { { "aaa" , "111" } , { "bbb" , "222" } } , Labels { { "aaa" , "111" } , { "bbb" , "222" } } . Copy ( ) )
2020-05-12 09:42:21 +00:00
}
func TestLabels_Map ( t * testing . T ) {
2020-10-29 09:43:23 +00:00
require . Equal ( t , map [ string ] string { "aaa" : "111" , "bbb" : "222" } , Labels { { "aaa" , "111" } , { "bbb" , "222" } } . Map ( ) )
2020-05-12 09:42:21 +00:00
}
2020-06-05 08:48:33 +00:00
func TestLabels_WithLabels ( t * testing . T ) {
2020-10-29 09:43:23 +00:00
require . Equal ( t , Labels { { "aaa" , "111" } , { "bbb" , "222" } } , Labels { { "aaa" , "111" } , { "bbb" , "222" } , { "ccc" , "333" } } . WithLabels ( "aaa" , "bbb" ) )
2020-06-05 08:48:33 +00:00
}
func TestLabels_WithoutLabels ( t * testing . T ) {
2020-10-29 09:43:23 +00:00
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" ) )
2020-06-05 08:48:33 +00:00
}
2020-06-08 07:46:21 +00:00
func TestBulider_NewBulider ( t * testing . T ) {
2020-10-29 09:43:23 +00:00
require . Equal (
2020-06-08 07:46:21 +00:00
t ,
& Builder {
base : Labels { { "aaa" , "111" } } ,
del : [ ] string { } ,
add : [ ] Label { } ,
} ,
NewBuilder ( Labels { { "aaa" , "111" } } ) ,
)
}
func TestBuilder_Del ( t * testing . T ) {
2020-10-29 09:43:23 +00:00
require . Equal (
2020-06-08 07:46:21 +00:00
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 ) {
2020-10-29 09:43:23 +00:00
require . Equal (
2020-06-08 07:46:21 +00:00
t ,
& Builder {
base : Labels { { "aaa" , "111" } } ,
del : [ ] string { } ,
add : [ ] Label { { "bbb" , "222" } } ,
} ,
( & Builder {
base : Labels { { "aaa" , "111" } } ,
del : [ ] string { } ,
add : [ ] Label { } ,
} ) . Set ( "bbb" , "222" ) ,
)
2020-10-29 09:43:23 +00:00
require . Equal (
2020-06-08 07:46:21 +00:00
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 ) {
2020-10-29 09:43:23 +00:00
require . Equal (
2020-06-08 07:46:21 +00:00
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 ( ) ,
)
}
2020-10-15 10:31:28 +00:00
func TestLabels_Hash ( t * testing . T ) {
lbls := Labels {
{ Name : "foo" , Value : "bar" } ,
{ Name : "baz" , Value : "qux" } ,
}
2020-10-29 09:43:23 +00:00
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." )
2020-10-15 10:31:28 +00:00
}
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 ++ {
2021-10-22 08:06:44 +00:00
// Label ~50B name, 50B value.
2020-10-15 10:31:28 +00:00
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
} )
}
}