// Copyright 2017 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 ( "bufio" "fmt" "math/rand" "os" "sort" "strings" "testing" "github.com/prometheus/tsdb/testutil" ) func TestCompareAndEquals(t *testing.T) { cases := []struct { a, b []Label res int }{ { a: []Label{}, b: []Label{}, res: 0, }, { a: []Label{{"a", ""}}, b: []Label{{"a", ""}, {"b", ""}}, res: -1, }, { a: []Label{{"a", ""}}, b: []Label{{"a", ""}}, res: 0, }, { a: []Label{{"aa", ""}, {"aa", ""}}, b: []Label{{"aa", ""}, {"ab", ""}}, res: -1, }, { a: []Label{{"aa", ""}, {"abb", ""}}, b: []Label{{"aa", ""}, {"ab", ""}}, res: 1, }, { a: []Label{ {"__name__", "go_gc_duration_seconds"}, {"job", "prometheus"}, {"quantile", "0.75"}, }, b: []Label{ {"__name__", "go_gc_duration_seconds"}, {"job", "prometheus"}, {"quantile", "1"}, }, res: -1, }, { a: []Label{ {"handler", "prometheus"}, {"instance", "localhost:9090"}, }, b: []Label{ {"handler", "query"}, {"instance", "localhost:9090"}, }, res: -1, }, } for _, c := range cases { // Use constructor to ensure sortedness. a, b := New(c.a...), New(c.b...) testutil.Equals(t, c.res, Compare(a, b)) testutil.Equals(t, c.res == 0, a.Equals(b)) } } func BenchmarkSliceSort(b *testing.B) { lbls, err := readPrometheusLabels("../testdata/1m.series", 900000) testutil.Ok(b, err) for len(lbls) < 20e6 { lbls = append(lbls, lbls...) } for i := range lbls { j := rand.Intn(i + 1) lbls[i], lbls[j] = lbls[j], lbls[i] } for _, k := range []int{ 100, 5000, 50000, 300000, 900000, 5e6, 20e6, } { b.Run(fmt.Sprintf("%d", k), func(b *testing.B) { b.ReportAllocs() for a := 0; a < b.N; a++ { b.StopTimer() cl := make(Slice, k) copy(cl, Slice(lbls[:k])) b.StartTimer() sort.Sort(cl) } }) } } func readPrometheusLabels(fn string, n int) ([]Labels, error) { f, err := os.Open(fn) if err != nil { return nil, err } defer f.Close() scanner := bufio.NewScanner(f) var mets []Labels hashes := map[uint64]struct{}{} i := 0 for scanner.Scan() && i < n { m := make(Labels, 0, 10) // Order of the k/v labels matters, so rather than decoding arbitrary json into an // interface{}, parse the line ourselves and remove unnecessary characters. r := strings.NewReplacer("\"", "", "{", "", "}", "") s := r.Replace(scanner.Text()) labelChunks := strings.Split(s, ",") for _, labelChunk := range labelChunks { split := strings.Split(labelChunk, ":") m = append(m, Label{Name: split[0], Value: split[1]}) } h := m.Hash() if _, ok := hashes[h]; ok { continue } mets = append(mets, m) hashes[h] = struct{}{} i++ } return mets, nil } func BenchmarkLabelSetFromMap(b *testing.B) { m := map[string]string{ "job": "node", "instance": "123.123.1.211:9090", "path": "/api/v1/namespaces//deployments/", "method": "GET", "namespace": "system", "status": "500", } var ls Labels b.ReportAllocs() for i := 0; i < b.N; i++ { ls = FromMap(m) } _ = ls } func BenchmarkMapFromLabels(b *testing.B) { m := map[string]string{ "job": "node", "instance": "123.123.1.211:9090", "path": "/api/v1/namespaces//deployments/", "method": "GET", "namespace": "system", "status": "500", } ls := FromMap(m) b.ResetTimer() b.ReportAllocs() for i := 0; i < b.N; i++ { m = ls.Map() } } func BenchmarkLabelSetEquals(b *testing.B) { // The vast majority of comparisons will be against a matching label set. m := map[string]string{ "job": "node", "instance": "123.123.1.211:9090", "path": "/api/v1/namespaces//deployments/", "method": "GET", "namespace": "system", "status": "500", } ls := FromMap(m) var res bool b.ResetTimer() b.ReportAllocs() for i := 0; i < b.N; i++ { res = ls.Equals(ls) } _ = res } func BenchmarkLabelSetHash(b *testing.B) { // The vast majority of comparisons will be against a matching label set. m := map[string]string{ "job": "node", "instance": "123.123.1.211:9090", "path": "/api/v1/namespaces//deployments/", "method": "GET", "namespace": "system", "status": "500", } ls := FromMap(m) var res uint64 b.ResetTimer() b.ReportAllocs() for i := 0; i < b.N; i++ { res += ls.Hash() } fmt.Println(res) }