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 (
2022-07-18 15:42:09 +00:00
"encoding/json"
2020-10-15 10:31:28 +00:00
"fmt"
2024-04-08 19:26:23 +00:00
"net/http"
2024-05-13 15:36:19 +00:00
"strconv"
2020-10-15 10:31:28 +00:00
"strings"
2019-02-09 09:17:52 +00:00
"testing"
2019-12-27 09:32:19 +00:00
2024-08-28 15:15:42 +00:00
"github.com/prometheus/common/model"
2020-10-29 09:43:23 +00:00
"github.com/stretchr/testify/require"
2022-08-31 13:50:38 +00:00
"gopkg.in/yaml.v2"
2019-02-09 09:17:52 +00:00
)
2020-04-22 07:02:47 +00:00
func TestLabels_String ( t * testing . T ) {
cases := [ ] struct {
2024-04-01 16:06:05 +00:00
labels Labels
2020-04-22 07:02:47 +00:00
expected string
} {
{
2024-04-01 16:06:05 +00:00
labels : FromStrings ( "t1" , "t1" , "t2" , "t2" ) ,
2020-04-22 07:02:47 +00:00
expected : "{t1=\"t1\", t2=\"t2\"}" ,
} ,
{
2024-04-01 16:06:05 +00:00
labels : Labels { } ,
2020-04-22 07:02:47 +00:00
expected : "{}" ,
} ,
}
for _ , c := range cases {
2024-04-01 16:06:05 +00:00
str := c . labels . String ( )
2020-10-29 09:43:23 +00:00
require . Equal ( t , c . expected , str )
2020-04-22 07:02:47 +00:00
}
}
2024-03-12 11:34:03 +00:00
func BenchmarkString ( b * testing . B ) {
ls := New ( benchmarkLabels ... )
for i := 0 ; i < b . N ; i ++ {
_ = ls . String ( )
}
}
2019-02-09 09:17:52 +00:00
func TestLabels_MatchLabels ( t * testing . T ) {
2022-05-26 14:58:06 +00:00
labels := FromStrings (
"__name__" , "ALERTS" ,
"alertname" , "HTTPRequestRateLow" ,
"alertstate" , "pending" ,
"instance" , "0" ,
"job" , "app-server" ,
"severity" , "critical" )
2019-02-09 09:17:52 +00:00
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 ,
2022-05-26 14:58:06 +00:00
expected : FromStrings (
"__name__" , "ALERTS" ,
"alertname" , "HTTPRequestRateLow" ,
"alertstate" , "pending" ,
"instance" , "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 ,
2022-05-26 14:58:06 +00:00
expected : FromStrings (
"job" , "app-server" ,
"severity" , "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 ,
2022-05-26 14:58:06 +00:00
expected : FromStrings (
"alertname" , "HTTPRequestRateLow" ,
"alertstate" , "pending" ,
"instance" , "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 ,
2022-05-26 14:58:06 +00:00
expected : FromStrings (
"job" , "app-server" ,
"severity" , "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 ... )
2023-03-24 17:58:25 +00:00
require . True ( t , Equal ( 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 ,
} , {
2022-05-26 14:58:06 +00:00
Input : FromStrings ( "__name__" , "up" , "hostname" , "localhost" , "hostname" , "127.0.0.1" ) ,
2020-01-20 11:05:27 +00:00
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
} {
{
2022-05-26 14:58:06 +00:00
input : FromStrings (
"foo" , "" ,
"bar" , "" ) ,
expected : EmptyLabels ( ) ,
} ,
{
input : FromStrings (
"foo" , "" ,
"bar" , "" ,
"baz" , "" ) ,
expected : EmptyLabels ( ) ,
} ,
{
input : FromStrings (
"__name__" , "test" ,
"hostname" , "localhost" ,
"job" , "check" ) ,
expected : FromStrings (
"__name__" , "test" ,
"hostname" , "localhost" ,
"job" , "check" ) ,
} ,
{
input : FromStrings (
"__name__" , "test" ,
"hostname" , "localhost" ,
"bar" , "" ,
"job" , "check" ) ,
expected : FromStrings (
"__name__" , "test" ,
"hostname" , "localhost" ,
"job" , "check" ) ,
} ,
{
input : FromStrings (
"__name__" , "test" ,
"foo" , "" ,
"hostname" , "localhost" ,
"bar" , "" ,
"job" , "check" ) ,
expected : FromStrings (
"__name__" , "test" ,
"hostname" , "localhost" ,
"job" , "check" ) ,
} ,
{
input : FromStrings (
"__name__" , "test" ,
"foo" , "" ,
"baz" , "" ,
"hostname" , "localhost" ,
"bar" , "" ,
"job" , "check" ) ,
expected : FromStrings (
"__name__" , "test" ,
"hostname" , "localhost" ,
"job" , "check" ) ,
2020-10-13 07:57:53 +00:00
} ,
} {
t . Run ( "" , func ( t * testing . T ) {
2023-03-24 17:58:25 +00:00
require . True ( t , Equal ( 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
2022-12-08 03:09:43 +00:00
func TestLabels_IsValid ( t * testing . T ) {
for _ , test := range [ ] struct {
input Labels
expected bool
} {
{
input : FromStrings (
"__name__" , "test" ,
"hostname" , "localhost" ,
"job" , "check" ,
) ,
expected : true ,
} ,
{
input : FromStrings (
"__name__" , "test:ms" ,
"hostname_123" , "localhost" ,
"_job" , "check" ,
) ,
expected : true ,
} ,
{
input : FromStrings ( "__name__" , "test-ms" ) ,
expected : false ,
} ,
{
input : FromStrings ( "__name__" , "0zz" ) ,
expected : false ,
} ,
{
input : FromStrings ( "abc:xyz" , "invalid" ) ,
expected : false ,
} ,
{
input : FromStrings ( "123abc" , "invalid" ) ,
expected : false ,
} ,
{
input : FromStrings ( "中文abc" , "invalid" ) ,
expected : false ,
} ,
{
input : FromStrings ( "invalid" , "aa\xe2" ) ,
expected : false ,
} ,
{
input : FromStrings ( "invalid" , "\xF7\xBF\xBF\xBF" ) ,
expected : false ,
} ,
} {
t . Run ( "" , func ( t * testing . T ) {
2024-08-28 15:15:42 +00:00
require . Equal ( t , test . expected , test . input . IsValid ( model . LegacyValidation ) )
2022-12-08 03:09:43 +00:00
} )
}
}
2024-08-28 15:15:42 +00:00
func TestLabels_ValidationModes ( t * testing . T ) {
for _ , test := range [ ] struct {
input Labels
globalMode model . ValidationScheme
callMode model . ValidationScheme
expected bool
} {
{
input : FromStrings (
"__name__" , "test.metric" ,
"hostname" , "localhost" ,
"job" , "check" ,
) ,
globalMode : model . UTF8Validation ,
callMode : model . UTF8Validation ,
expected : true ,
} ,
{
input : FromStrings (
"__name__" , "test" ,
"\xc5 bad utf8" , "localhost" ,
"job" , "check" ,
) ,
globalMode : model . UTF8Validation ,
callMode : model . UTF8Validation ,
expected : false ,
} ,
{
// Setting the common model to legacy validation and then trying to check for UTF-8 on a
// per-call basis is not supported.
input : FromStrings (
"__name__" , "test.utf8.metric" ,
"hostname" , "localhost" ,
"job" , "check" ,
) ,
globalMode : model . LegacyValidation ,
callMode : model . UTF8Validation ,
expected : false ,
} ,
{
input : FromStrings (
"__name__" , "test" ,
"hostname" , "localhost" ,
"job" , "check" ,
) ,
globalMode : model . LegacyValidation ,
callMode : model . LegacyValidation ,
expected : true ,
} ,
{
input : FromStrings (
"__name__" , "test.utf8.metric" ,
"hostname" , "localhost" ,
"job" , "check" ,
) ,
globalMode : model . UTF8Validation ,
callMode : model . LegacyValidation ,
expected : false ,
} ,
{
input : FromStrings (
"__name__" , "test" ,
"host.name" , "localhost" ,
"job" , "check" ,
) ,
globalMode : model . UTF8Validation ,
callMode : model . LegacyValidation ,
expected : false ,
} ,
} {
model . NameValidationScheme = test . globalMode
require . Equal ( t , test . expected , test . input . IsValid ( test . callMode ) )
}
}
2020-03-03 14:17:54 +00:00
func TestLabels_Equal ( t * testing . T ) {
2022-05-26 14:58:06 +00:00
labels := FromStrings (
"aaa" , "111" ,
"bbb" , "222" )
2020-03-03 14:17:54 +00:00
tests := [ ] struct {
compared Labels
expected bool
} {
{
2022-05-26 14:58:06 +00:00
compared : FromStrings (
"aaa" , "111" ,
"bbb" , "222" ,
"ccc" , "333" ) ,
2020-03-03 14:17:54 +00:00
expected : false ,
} ,
{
2022-05-26 14:58:06 +00:00
compared : FromStrings (
"aaa" , "111" ,
"bar" , "222" ) ,
2020-03-03 14:17:54 +00:00
expected : false ,
} ,
{
2022-05-26 14:58:06 +00:00
compared : FromStrings (
"aaa" , "111" ,
"bbb" , "233" ) ,
2020-03-03 14:17:54 +00:00
expected : false ,
} ,
{
2022-05-26 14:58:06 +00:00
compared : FromStrings (
"aaa" , "111" ,
"bbb" , "222" ) ,
2020-03-03 14:17:54 +00:00
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" )
2022-05-26 14:58:06 +00:00
x := 0
labels . Range ( func ( l Label ) {
switch x {
case 0 :
require . Equal ( t , Label { Name : "aaa" , Value : "111" } , l , "unexpected value" )
case 1 :
require . Equal ( t , Label { Name : "bbb" , Value : "222" } , l , "unexpected value" )
default :
t . Fatalf ( "unexpected labelset value %d: %v" , x , l )
}
x ++
} )
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 ) {
2022-05-26 14:58:06 +00:00
labels := FromStrings (
"aaa" , "111" ,
"bbb" , "222" )
2020-03-03 14:17:54 +00:00
tests := [ ] struct {
compared Labels
expected int
} {
{
2022-05-26 14:58:06 +00:00
compared : FromStrings (
"aaa" , "110" ,
"bbb" , "222" ) ,
2020-03-03 14:17:54 +00:00
expected : 1 ,
} ,
{
2022-05-26 14:58:06 +00:00
compared : FromStrings (
"aaa" , "111" ,
"bbb" , "233" ) ,
2020-03-03 14:17:54 +00:00
expected : - 1 ,
} ,
{
2022-05-26 14:58:06 +00:00
compared : FromStrings (
"aaa" , "111" ,
"bar" , "222" ) ,
2020-03-03 14:17:54 +00:00
expected : 1 ,
} ,
{
2022-05-26 14:58:06 +00:00
compared : FromStrings (
"aaa" , "111" ,
"bbc" , "222" ) ,
2020-03-03 14:17:54 +00:00
expected : - 1 ,
} ,
2023-06-20 19:58:47 +00:00
{
compared : FromStrings (
"aaa" , "111" ,
"bb" , "222" ) ,
expected : 1 ,
} ,
{
compared : FromStrings (
"aaa" , "111" ,
"bbbb" , "222" ) ,
expected : - 1 ,
} ,
2020-03-03 14:17:54 +00:00
{
2022-05-26 14:58:06 +00:00
compared : FromStrings (
"aaa" , "111" ) ,
2020-03-03 14:17:54 +00:00
expected : 1 ,
} ,
{
2022-05-26 14:58:06 +00:00
compared : FromStrings (
"aaa" , "111" ,
"bbb" , "222" ,
"ccc" , "333" ,
"ddd" , "444" ) ,
2020-03-03 14:17:54 +00:00
expected : - 2 ,
} ,
{
2022-05-26 14:58:06 +00:00
compared : FromStrings (
"aaa" , "111" ,
"bbb" , "222" ) ,
2020-03-03 14:17:54 +00:00
expected : 0 ,
} ,
2023-06-20 19:58:47 +00:00
{
compared : EmptyLabels ( ) ,
expected : 1 ,
} ,
2020-03-03 14:17:54 +00:00
}
2022-06-26 17:11:40 +00:00
sign := func ( a int ) int {
switch {
case a < 0 :
return - 1
case a > 0 :
return 1
}
return 0
}
2020-03-03 14:17:54 +00:00
for i , test := range tests {
got := Compare ( labels , test . compared )
2022-06-26 17:11:40 +00:00
require . Equal ( t , sign ( test . expected ) , sign ( got ) , "unexpected comparison result for test case %d" , i )
2023-06-20 19:58:47 +00:00
got = Compare ( test . compared , labels )
require . Equal ( t , - sign ( test . expected ) , sign ( got ) , "unexpected comparison result for reverse 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 ,
} ,
}
2022-05-26 14:58:06 +00:00
labelsSet := FromStrings (
"aaa" , "111" ,
"bbb" , "222" )
2020-04-09 15:49:09 +00:00
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 ) {
2022-05-26 14:58:06 +00:00
require . Equal ( t , "" , FromStrings ( "aaa" , "111" , "bbb" , "222" ) . Get ( "foo" ) )
2023-06-26 17:35:22 +00:00
require . Equal ( t , "111" , FromStrings ( "aaaa" , "111" , "bbb" , "222" ) . Get ( "aaaa" ) )
require . Equal ( t , "222" , FromStrings ( "aaaa" , "111" , "bbb" , "222" ) . Get ( "bbb" ) )
2020-05-12 09:42:21 +00:00
}
2024-01-25 10:48:49 +00:00
func TestLabels_DropMetricName ( t * testing . T ) {
require . True ( t , Equal ( FromStrings ( "aaa" , "111" , "bbb" , "222" ) , FromStrings ( "aaa" , "111" , "bbb" , "222" ) . DropMetricName ( ) ) )
require . True ( t , Equal ( FromStrings ( "aaa" , "111" ) , FromStrings ( MetricName , "myname" , "aaa" , "111" ) . DropMetricName ( ) ) )
2024-03-27 10:35:17 +00:00
original := FromStrings ( "__aaa__" , "111" , MetricName , "myname" , "bbb" , "222" )
check := FromStrings ( "__aaa__" , "111" , MetricName , "myname" , "bbb" , "222" )
require . True ( t , Equal ( FromStrings ( "__aaa__" , "111" , "bbb" , "222" ) , check . DropMetricName ( ) ) )
require . True ( t , Equal ( original , check ) )
2024-01-25 10:48:49 +00:00
}
2024-06-21 15:49:07 +00:00
func ScratchBuilderForBenchmark ( ) ScratchBuilder {
// (Only relevant to -tags dedupelabels: stuff the symbol table before adding the real labels, to avoid having everything fitting into 1 byte.)
b := NewScratchBuilder ( 256 )
for i := 0 ; i < 256 ; i ++ {
b . Add ( fmt . Sprintf ( "name%d" , i ) , fmt . Sprintf ( "value%d" , i ) )
}
b . Labels ( )
b . Reset ( )
return b
}
func NewForBenchmark ( ls ... Label ) Labels {
b := ScratchBuilderForBenchmark ( )
for _ , l := range ls {
b . Add ( l . Name , l . Value )
}
b . Sort ( )
return b . Labels ( )
}
func FromStringsForBenchmark ( ss ... string ) Labels {
if len ( ss ) % 2 != 0 {
panic ( "invalid number of strings" )
}
b := ScratchBuilderForBenchmark ( )
for i := 0 ; i < len ( ss ) ; i += 2 {
b . Add ( ss [ i ] , ss [ i + 1 ] )
}
b . Sort ( )
return b . Labels ( )
}
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.
2022-03-03 17:11:19 +00:00
// In the following list, `old` is the linear search while `new` is the binary search implementation (without calling sort.Search, which performs even worse here)
2023-10-03 20:09:25 +00:00
//
// 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)
2021-08-24 11:05:19 +00:00
func BenchmarkLabels_Get ( b * testing . B ) {
maxLabels := 30
2022-06-26 17:15:46 +00:00
allLabels := make ( [ ] Label , maxLabels )
2021-08-24 11:05:19 +00:00
for i := 0 ; i < maxLabels ; i ++ {
2023-06-26 17:35:22 +00:00
allLabels [ i ] = Label { Name : strings . Repeat ( string ( 'a' + byte ( i ) ) , 5 + ( i % 5 ) ) }
2021-08-24 11:05:19 +00:00
}
for _ , size := range [ ] int { 5 , 10 , maxLabels } {
b . Run ( fmt . Sprintf ( "with %d labels" , size ) , func ( b * testing . B ) {
2024-06-21 15:49:07 +00:00
labels := NewForBenchmark ( allLabels [ : size ] ... )
2021-08-24 11:05:19 +00:00
for _ , scenario := range [ ] struct {
desc , label string
} {
2023-08-13 13:55:50 +00:00
{ "first label" , allLabels [ 0 ] . Name } ,
{ "middle label" , allLabels [ size / 2 ] . Name } ,
{ "last label" , allLabels [ size - 1 ] . Name } ,
{ "not-found label" , "benchmark" } ,
2021-08-24 11:05:19 +00:00
} {
b . Run ( scenario . desc , func ( b * testing . B ) {
2023-08-13 13:55:50 +00:00
b . Run ( "get" , func ( b * testing . B ) {
for i := 0 ; i < b . N ; i ++ {
_ = labels . Get ( scenario . label )
}
} )
b . Run ( "has" , func ( b * testing . B ) {
for i := 0 ; i < b . N ; i ++ {
_ = labels . Has ( scenario . label )
}
} )
2021-08-24 11:05:19 +00:00
} )
}
} )
}
}
2023-06-20 19:58:47 +00:00
var comparisonBenchmarkScenarios = [ ] struct {
desc string
base , other Labels
} {
{
"equal" ,
2024-06-21 15:49:07 +00:00
FromStringsForBenchmark ( "a_label_name" , "a_label_value" , "another_label_name" , "another_label_value" ) ,
FromStringsForBenchmark ( "a_label_name" , "a_label_value" , "another_label_name" , "another_label_value" ) ,
2023-06-20 19:58:47 +00:00
} ,
{
"not equal" ,
2024-06-21 15:49:07 +00:00
FromStringsForBenchmark ( "a_label_name" , "a_label_value" , "another_label_name" , "another_label_value" ) ,
FromStringsForBenchmark ( "a_label_name" , "a_label_value" , "another_label_name" , "a_different_label_value" ) ,
2023-06-20 19:58:47 +00:00
} ,
{
"different sizes" ,
2024-06-21 15:49:07 +00:00
FromStringsForBenchmark ( "a_label_name" , "a_label_value" , "another_label_name" , "another_label_value" ) ,
FromStringsForBenchmark ( "a_label_name" , "a_label_value" ) ,
2023-06-20 19:58:47 +00:00
} ,
{
"lots" ,
2024-06-21 15:49:07 +00:00
FromStringsForBenchmark ( "aaa" , "bbb" , "ccc" , "ddd" , "eee" , "fff" , "ggg" , "hhh" , "iii" , "jjj" , "kkk" , "lll" , "mmm" , "nnn" , "ooo" , "ppp" , "qqq" , "rrz" ) ,
FromStringsForBenchmark ( "aaa" , "bbb" , "ccc" , "ddd" , "eee" , "fff" , "ggg" , "hhh" , "iii" , "jjj" , "kkk" , "lll" , "mmm" , "nnn" , "ooo" , "ppp" , "qqq" , "rrr" ) ,
2023-06-20 19:58:47 +00:00
} ,
2024-03-17 15:56:12 +00:00
{
"real long equal" ,
2024-06-21 15:49:07 +00:00
FromStringsForBenchmark ( "__name__" , "kube_pod_container_status_last_terminated_exitcode" , "cluster" , "prod-af-north-0" , " container" , "prometheus" , "instance" , "kube-state-metrics-0:kube-state-metrics:ksm" , "job" , "kube-state-metrics/kube-state-metrics" , " namespace" , "observability-prometheus" , "pod" , "observability-prometheus-0" , "uid" , "d3ec90b2-4975-4607-b45d-b9ad64bb417e" ) ,
FromStringsForBenchmark ( "__name__" , "kube_pod_container_status_last_terminated_exitcode" , "cluster" , "prod-af-north-0" , " container" , "prometheus" , "instance" , "kube-state-metrics-0:kube-state-metrics:ksm" , "job" , "kube-state-metrics/kube-state-metrics" , " namespace" , "observability-prometheus" , "pod" , "observability-prometheus-0" , "uid" , "d3ec90b2-4975-4607-b45d-b9ad64bb417e" ) ,
2024-03-17 15:56:12 +00:00
} ,
{
"real long different end" ,
2024-06-21 15:49:07 +00:00
FromStringsForBenchmark ( "__name__" , "kube_pod_container_status_last_terminated_exitcode" , "cluster" , "prod-af-north-0" , " container" , "prometheus" , "instance" , "kube-state-metrics-0:kube-state-metrics:ksm" , "job" , "kube-state-metrics/kube-state-metrics" , " namespace" , "observability-prometheus" , "pod" , "observability-prometheus-0" , "uid" , "d3ec90b2-4975-4607-b45d-b9ad64bb417e" ) ,
FromStringsForBenchmark ( "__name__" , "kube_pod_container_status_last_terminated_exitcode" , "cluster" , "prod-af-north-0" , " container" , "prometheus" , "instance" , "kube-state-metrics-0:kube-state-metrics:ksm" , "job" , "kube-state-metrics/kube-state-metrics" , " namespace" , "observability-prometheus" , "pod" , "observability-prometheus-0" , "uid" , "deadbeef-0000-1111-2222-b9ad64bb417e" ) ,
2024-03-17 15:56:12 +00:00
} ,
2023-06-20 19:58:47 +00:00
}
2022-03-14 23:30:04 +00:00
func BenchmarkLabels_Equals ( b * testing . B ) {
2023-06-20 19:58:47 +00:00
for _ , scenario := range comparisonBenchmarkScenarios {
2022-03-14 23:30:04 +00:00
b . Run ( scenario . desc , func ( b * testing . B ) {
b . ResetTimer ( )
for i := 0 ; i < b . N ; i ++ {
_ = Equal ( scenario . base , scenario . other )
}
} )
}
}
2023-06-20 19:58:47 +00:00
func BenchmarkLabels_Compare ( b * testing . B ) {
for _ , scenario := range comparisonBenchmarkScenarios {
b . Run ( scenario . desc , func ( b * testing . B ) {
b . ResetTimer ( )
for i := 0 ; i < b . N ; i ++ {
_ = Compare ( scenario . base , scenario . other )
}
} )
}
}
2020-05-12 09:42:21 +00:00
func TestLabels_Copy ( t * testing . T ) {
2022-05-26 14:58:06 +00:00
require . Equal ( t , FromStrings ( "aaa" , "111" , "bbb" , "222" ) , FromStrings ( "aaa" , "111" , "bbb" , "222" ) . Copy ( ) )
2020-05-12 09:42:21 +00:00
}
func TestLabels_Map ( t * testing . T ) {
2022-05-26 14:58:06 +00:00
require . Equal ( t , map [ string ] string { "aaa" : "111" , "bbb" : "222" } , FromStrings ( "aaa" , "111" , "bbb" , "222" ) . Map ( ) )
2020-05-12 09:42:21 +00:00
}
2020-06-05 08:48:33 +00:00
2022-06-07 04:38:27 +00:00
func TestLabels_BytesWithLabels ( t * testing . T ) {
2022-05-26 14:58:06 +00:00
require . Equal ( t , FromStrings ( "aaa" , "111" , "bbb" , "222" ) . Bytes ( nil ) , FromStrings ( "aaa" , "111" , "bbb" , "222" , "ccc" , "333" ) . BytesWithLabels ( nil , "aaa" , "bbb" ) )
require . Equal ( t , FromStrings ( ) . Bytes ( nil ) , FromStrings ( "aaa" , "111" , "bbb" , "222" , "ccc" , "333" ) . BytesWithLabels ( nil ) )
2020-06-05 08:48:33 +00:00
}
2022-06-07 04:38:27 +00:00
func TestLabels_BytesWithoutLabels ( t * testing . T ) {
2022-05-26 14:58:06 +00:00
require . Equal ( t , FromStrings ( "aaa" , "111" ) . Bytes ( nil ) , FromStrings ( "aaa" , "111" , "bbb" , "222" , "ccc" , "333" ) . BytesWithoutLabels ( nil , "bbb" , "ccc" ) )
require . Equal ( t , FromStrings ( MetricName , "333" , "aaa" , "111" ) . Bytes ( nil ) , FromStrings ( MetricName , "333" , "aaa" , "111" , "bbb" , "222" ) . BytesWithoutLabels ( nil , "bbb" ) )
require . Equal ( t , FromStrings ( "aaa" , "111" ) . Bytes ( nil ) , FromStrings ( MetricName , "333" , "aaa" , "111" , "bbb" , "222" ) . BytesWithoutLabels ( nil , MetricName , "bbb" ) )
2020-06-05 08:48:33 +00:00
}
2022-06-07 04:38:27 +00:00
func TestBuilder ( t * testing . T ) {
2023-12-05 18:31:19 +00:00
reuseBuilder := NewBuilderWithSymbolTable ( NewSymbolTable ( ) )
2022-06-07 04:38:27 +00:00
for i , tcase := range [ ] struct {
base Labels
del [ ] string
keep [ ] string
set [ ] Label
want Labels
} {
{
base : FromStrings ( "aaa" , "111" ) ,
want : FromStrings ( "aaa" , "111" ) ,
2020-06-08 07:46:21 +00:00
} ,
2023-12-05 18:31:19 +00:00
{
base : EmptyLabels ( ) ,
set : [ ] Label { { "aaa" , "444" } , { "bbb" , "555" } , { "ccc" , "666" } } ,
want : FromStrings ( "aaa" , "444" , "bbb" , "555" , "ccc" , "666" ) ,
} ,
2023-03-16 13:25:55 +00:00
{
base : FromStrings ( "aaa" , "111" , "bbb" , "222" , "ccc" , "333" ) ,
set : [ ] Label { { "aaa" , "444" } , { "bbb" , "555" } , { "ccc" , "666" } } ,
want : FromStrings ( "aaa" , "444" , "bbb" , "555" , "ccc" , "666" ) ,
} ,
2022-06-07 04:38:27 +00:00
{
base : FromStrings ( "aaa" , "111" , "bbb" , "222" , "ccc" , "333" ) ,
2020-06-08 07:46:21 +00:00
del : [ ] string { "bbb" } ,
2022-06-07 04:38:27 +00:00
want : FromStrings ( "aaa" , "111" , "ccc" , "333" ) ,
} ,
{
set : [ ] Label { { "aaa" , "111" } , { "bbb" , "222" } , { "ccc" , "333" } } ,
del : [ ] string { "bbb" } ,
want : FromStrings ( "aaa" , "111" , "ccc" , "333" ) ,
} ,
{
base : FromStrings ( "aaa" , "111" ) ,
set : [ ] Label { { "bbb" , "222" } } ,
want : FromStrings ( "aaa" , "111" , "bbb" , "222" ) ,
} ,
{
base : FromStrings ( "aaa" , "111" ) ,
set : [ ] Label { { "bbb" , "222" } , { "bbb" , "333" } } ,
want : FromStrings ( "aaa" , "111" , "bbb" , "333" ) ,
} ,
{
base : FromStrings ( "aaa" , "111" , "bbb" , "222" , "ccc" , "333" ) ,
del : [ ] string { "bbb" } ,
set : [ ] Label { { "ddd" , "444" } } ,
want : FromStrings ( "aaa" , "111" , "ccc" , "333" , "ddd" , "444" ) ,
} ,
{ // Blank value is interpreted as delete.
base : FromStrings ( "aaa" , "111" , "bbb" , "" , "ccc" , "333" ) ,
want : FromStrings ( "aaa" , "111" , "ccc" , "333" ) ,
} ,
{
base : FromStrings ( "aaa" , "111" , "bbb" , "222" , "ccc" , "333" ) ,
set : [ ] Label { { "bbb" , "" } } ,
want : FromStrings ( "aaa" , "111" , "ccc" , "333" ) ,
} ,
{
base : FromStrings ( "aaa" , "111" , "bbb" , "222" , "ccc" , "333" ) ,
keep : [ ] string { "bbb" } ,
want : FromStrings ( "bbb" , "222" ) ,
} ,
{
base : FromStrings ( "aaa" , "111" , "bbb" , "222" , "ccc" , "333" ) ,
keep : [ ] string { "aaa" , "ccc" } ,
want : FromStrings ( "aaa" , "111" , "ccc" , "333" ) ,
} ,
{
base : FromStrings ( "aaa" , "111" , "bbb" , "222" , "ccc" , "333" ) ,
del : [ ] string { "bbb" } ,
set : [ ] Label { { "ddd" , "444" } } ,
keep : [ ] string { "aaa" , "ddd" } ,
want : FromStrings ( "aaa" , "111" , "ddd" , "444" ) ,
} ,
} {
2023-12-05 18:31:19 +00:00
test := func ( t * testing . T , b * Builder ) {
2022-06-07 04:38:27 +00:00
for _ , lbl := range tcase . set {
b . Set ( lbl . Name , lbl . Value )
}
if len ( tcase . keep ) > 0 {
b . Keep ( tcase . keep ... )
}
b . Del ( tcase . del ... )
2023-03-24 17:58:25 +00:00
require . True ( t , Equal ( tcase . want , b . Labels ( ) ) )
2023-03-16 13:25:55 +00:00
// Check what happens when we call Range and mutate the builder.
b . Range ( func ( l Label ) {
if l . Name == "aaa" || l . Name == "bbb" {
b . Del ( l . Name )
}
} )
2023-03-22 15:46:02 +00:00
require . Equal ( t , tcase . want . BytesWithoutLabels ( nil , "aaa" , "bbb" ) , b . Labels ( ) . Bytes ( nil ) )
2023-12-05 18:31:19 +00:00
}
t . Run ( fmt . Sprintf ( "NewBuilder %d" , i ) , func ( t * testing . T ) {
test ( t , NewBuilder ( tcase . base ) )
} )
t . Run ( fmt . Sprintf ( "NewSymbolTable %d" , i ) , func ( t * testing . T ) {
b := NewBuilderWithSymbolTable ( NewSymbolTable ( ) )
b . Reset ( tcase . base )
test ( t , b )
} )
t . Run ( fmt . Sprintf ( "reuseBuilder %d" , i ) , func ( t * testing . T ) {
reuseBuilder . Reset ( tcase . base )
test ( t , reuseBuilder )
2022-06-07 04:38:27 +00:00
} )
}
2023-05-03 10:59:27 +00:00
t . Run ( "set_after_del" , func ( t * testing . T ) {
b := NewBuilder ( FromStrings ( "aaa" , "111" ) )
b . Del ( "bbb" )
b . Set ( "bbb" , "222" )
require . Equal ( t , FromStrings ( "aaa" , "111" , "bbb" , "222" ) , b . Labels ( ) )
require . Equal ( t , "222" , b . Get ( "bbb" ) )
} )
2020-06-08 07:46:21 +00:00
}
2020-10-15 10:31:28 +00:00
2022-07-10 14:22:49 +00:00
func TestScratchBuilder ( t * testing . T ) {
for i , tcase := range [ ] struct {
add [ ] Label
want Labels
} {
{
add : [ ] Label { } ,
want : EmptyLabels ( ) ,
} ,
{
add : [ ] Label { { "aaa" , "111" } } ,
want : FromStrings ( "aaa" , "111" ) ,
} ,
{
add : [ ] Label { { "aaa" , "111" } , { "bbb" , "222" } , { "ccc" , "333" } } ,
want : FromStrings ( "aaa" , "111" , "bbb" , "222" , "ccc" , "333" ) ,
} ,
{
add : [ ] Label { { "bbb" , "222" } , { "aaa" , "111" } , { "ccc" , "333" } } ,
want : FromStrings ( "aaa" , "111" , "bbb" , "222" , "ccc" , "333" ) ,
} ,
{
add : [ ] Label { { "ddd" , "444" } } ,
want : FromStrings ( "ddd" , "444" ) ,
} ,
} {
2024-05-13 15:36:19 +00:00
t . Run ( strconv . Itoa ( i ) , func ( t * testing . T ) {
2023-03-24 17:58:25 +00:00
b := NewScratchBuilder ( len ( tcase . add ) )
2022-07-10 14:22:49 +00:00
for _ , lbl := range tcase . add {
b . Add ( lbl . Name , lbl . Value )
}
b . Sort ( )
2023-03-24 17:58:25 +00:00
require . True ( t , Equal ( tcase . want , b . Labels ( ) ) )
2022-12-15 18:19:15 +00:00
b . Assign ( tcase . want )
2023-03-24 17:58:25 +00:00
require . True ( t , Equal ( tcase . want , b . Labels ( ) ) )
2022-07-10 14:22:49 +00:00
} )
}
}
2020-10-15 10:31:28 +00:00
func TestLabels_Hash ( t * testing . T ) {
2022-05-26 14:58:06 +00:00
lbls := FromStrings ( "foo" , "bar" , "baz" , "qux" )
2024-02-07 17:12:26 +00:00
hash1 , hash2 := lbls . Hash ( ) , lbls . Hash ( )
require . Equal ( t , hash1 , hash2 )
2022-05-26 14:58:06 +00:00
require . NotEqual ( t , lbls . Hash ( ) , FromStrings ( "foo" , "bar" ) . 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 {
2022-05-26 14:58:06 +00:00
b := NewBuilder ( EmptyLabels ( ) )
for i := 0 ; i < 10 ; i ++ {
2020-10-15 10:31:28 +00:00
// Label ~20B name, 50B value.
2022-05-26 14:58:06 +00:00
b . Set ( fmt . Sprintf ( "abcdefghijabcdefghijabcdefghij%d" , i ) , fmt . Sprintf ( "abcdefghijabcdefghijabcdefghijabcdefghijabcdefghij%d" , i ) )
2020-10-15 10:31:28 +00:00
}
2023-03-22 15:46:02 +00:00
return b . Labels ( )
2020-10-15 10:31:28 +00:00
} ( ) ,
} ,
{
name : "bigger labels over 1KB" ,
lbls : func ( ) Labels {
2022-05-26 14:58:06 +00:00
b := NewBuilder ( EmptyLabels ( ) )
for i := 0 ; i < 10 ; i ++ {
2021-10-22 08:06:44 +00:00
// Label ~50B name, 50B value.
2022-05-26 14:58:06 +00:00
b . Set ( fmt . Sprintf ( "abcdefghijabcdefghijabcdefghijabcdefghijabcdefghij%d" , i ) , fmt . Sprintf ( "abcdefghijabcdefghijabcdefghijabcdefghijabcdefghij%d" , i ) )
2020-10-15 10:31:28 +00:00
}
2023-03-22 15:46:02 +00:00
return b . Labels ( )
2020-10-15 10:31:28 +00:00
} ( ) ,
} ,
{
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 )
}
2022-05-26 14:58:06 +00:00
return FromStrings ( "__name__" , lbl . String ( ) )
2020-10-15 10:31:28 +00:00
} ( ) ,
} ,
} {
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
} )
}
}
2022-07-18 15:42:09 +00:00
2024-03-12 11:34:03 +00:00
var benchmarkLabels = [ ] Label {
{ "job" , "node" } ,
{ "instance" , "123.123.1.211:9090" } ,
{ "path" , "/api/v1/namespaces/<namespace>/deployments/<name>" } ,
2024-04-08 19:26:23 +00:00
{ "method" , http . MethodGet } ,
2024-03-12 11:34:03 +00:00
{ "namespace" , "system" } ,
{ "status" , "500" } ,
{ "prometheus" , "prometheus-core-1" } ,
{ "datacenter" , "eu-west-1" } ,
{ "pod_name" , "abcdef-99999-defee" } ,
}
2023-03-06 18:17:31 +00:00
2024-03-12 11:34:03 +00:00
func BenchmarkBuilder ( b * testing . B ) {
2023-03-06 18:17:31 +00:00
var l Labels
builder := NewBuilder ( EmptyLabels ( ) )
for i := 0 ; i < b . N ; i ++ {
builder . Reset ( EmptyLabels ( ) )
2024-03-12 11:34:03 +00:00
for _ , l := range benchmarkLabels {
2023-03-06 18:17:31 +00:00
builder . Set ( l . Name , l . Value )
}
2023-03-22 15:46:02 +00:00
l = builder . Labels ( )
2023-03-06 18:17:31 +00:00
}
require . Equal ( b , 9 , l . Len ( ) )
}
2023-01-10 15:41:39 +00:00
func BenchmarkLabels_Copy ( b * testing . B ) {
2024-06-21 15:49:07 +00:00
l := NewForBenchmark ( benchmarkLabels ... )
2023-01-10 15:41:39 +00:00
for i := 0 ; i < b . N ; i ++ {
l = l . Copy ( )
}
}
2022-07-18 15:42:09 +00:00
func TestMarshaling ( t * testing . T ) {
lbls := FromStrings ( "aaa" , "111" , "bbb" , "2222" , "ccc" , "33333" )
expectedJSON := "{\"aaa\":\"111\",\"bbb\":\"2222\",\"ccc\":\"33333\"}"
b , err := json . Marshal ( lbls )
require . NoError ( t , err )
2024-11-20 16:22:20 +00:00
require . JSONEq ( t , expectedJSON , string ( b ) )
2022-07-18 15:42:09 +00:00
var gotJ Labels
err = json . Unmarshal ( b , & gotJ )
require . NoError ( t , err )
require . Equal ( t , lbls , gotJ )
expectedYAML := "aaa: \"111\"\nbbb: \"2222\"\nccc: \"33333\"\n"
b , err = yaml . Marshal ( lbls )
require . NoError ( t , err )
2024-11-20 16:22:20 +00:00
require . YAMLEq ( t , expectedYAML , string ( b ) )
2022-07-18 15:42:09 +00:00
var gotY Labels
err = yaml . Unmarshal ( b , & gotY )
require . NoError ( t , err )
require . Equal ( t , lbls , gotY )
// Now in a struct with a tag
type foo struct {
ALabels Labels ` json:"a_labels,omitempty" yaml:"a_labels,omitempty" `
}
f := foo { ALabels : lbls }
b , err = json . Marshal ( f )
require . NoError ( t , err )
expectedJSONFromStruct := "{\"a_labels\":" + expectedJSON + "}"
2024-11-20 16:22:20 +00:00
require . JSONEq ( t , expectedJSONFromStruct , string ( b ) )
2022-07-18 15:42:09 +00:00
var gotFJ foo
err = json . Unmarshal ( b , & gotFJ )
require . NoError ( t , err )
require . Equal ( t , f , gotFJ )
b , err = yaml . Marshal ( f )
require . NoError ( t , err )
expectedYAMLFromStruct := "a_labels:\n aaa: \"111\"\n bbb: \"2222\"\n ccc: \"33333\"\n"
2024-11-20 16:22:20 +00:00
require . YAMLEq ( t , expectedYAMLFromStruct , string ( b ) )
2022-07-18 15:42:09 +00:00
var gotFY foo
err = yaml . Unmarshal ( b , & gotFY )
require . NoError ( t , err )
require . Equal ( t , f , gotFY )
}