Skip to content

Commit

Permalink
LabelSet.String(): use different implementation for go 1.20
Browse files Browse the repository at this point in the history
Signed-off-by: Arthur Silva Sens <arthur.sens@coralogix.com>
  • Loading branch information
ArthurSens committed Apr 9, 2024
1 parent fc703e6 commit e18058b
Show file tree
Hide file tree
Showing 6 changed files with 231 additions and 27 deletions.
2 changes: 1 addition & 1 deletion go.mod
@@ -1,6 +1,6 @@
module github.com/prometheus/common

go 1.21
go 1.20

require (
github.com/alecthomas/kingpin/v2 v2.4.0
Expand Down
26 changes: 0 additions & 26 deletions model/labelset.go
Expand Up @@ -14,12 +14,9 @@
package model

import (
"bytes"
"encoding/json"
"fmt"
"slices"
"sort"
"strconv"
)

// A LabelSet is a collection of LabelName and LabelValue pairs. The LabelSet
Expand Down Expand Up @@ -131,29 +128,6 @@ func (l LabelSet) Merge(other LabelSet) LabelSet {
return result
}

// String will look like `{foo="bar", more="less"}`. Names are sorted alphabetically.
func (l LabelSet) String() string {
var lna [32]LabelName // On stack to avoid memory allocation for sorting names.
labelNames := lna[:0]
for name := range l {
labelNames = append(labelNames, name)
}
slices.Sort(labelNames)
var bytea [1024]byte // On stack to avoid memory allocation while building the output.
b := bytes.NewBuffer(bytea[:0])
b.WriteByte('{')
for i, name := range labelNames {
if i > 0 {
b.WriteString(", ")
}
b.WriteString(string(name))
b.WriteByte('=')
b.Write(strconv.AppendQuote(b.AvailableBuffer(), string(l[name])))
}
b.WriteByte('}')
return b.String()
}

// Fingerprint returns the LabelSet's fingerprint.
func (ls LabelSet) Fingerprint() Fingerprint {
return labelSetToFingerprint(ls)
Expand Down
144 changes: 144 additions & 0 deletions model/labelset_go120_test.go
@@ -0,0 +1,144 @@
// 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.

//go:build go1.20 && !go1.21

package model

import (
"encoding/json"
"testing"
)

func TestUnmarshalJSONLabelSet(t *testing.T) {
type testConfig struct {
LabelSet LabelSet `yaml:"labelSet,omitempty"`
}

// valid LabelSet JSON
labelSetJSON := `{
"labelSet": {
"monitor": "codelab",
"foo": "bar",
"foo2": "bar",
"abc": "prometheus",
"foo11": "bar11"
}
}`
var c testConfig
err := json.Unmarshal([]byte(labelSetJSON), &c)
if err != nil {
t.Errorf("unexpected error while marshalling JSON : %s", err.Error())
}

labelSetString := c.LabelSet.String()

expected := `{abc="prometheus", foo="bar", foo11="bar11", foo2="bar", monitor="codelab"}`

if expected != labelSetString {
t.Errorf("expected %s but got %s", expected, labelSetString)
}

// invalid LabelSet JSON
invalidlabelSetJSON := `{
"labelSet": {
"1nvalid_23name": "codelab",
"foo": "bar"
}
}`

NameValidationScheme = LegacyValidation
err = json.Unmarshal([]byte(invalidlabelSetJSON), &c)
expectedErr := `"1nvalid_23name" is not a valid label name`
if err == nil || err.Error() != expectedErr {
t.Errorf("expected an error with message '%s' to be thrown", expectedErr)
}
}

func TestLabelSetClone(t *testing.T) {
labelSet := LabelSet{
"monitor": "codelab",
"foo": "bar",
"bar": "baz",
}

cloneSet := labelSet.Clone()

if len(labelSet) != len(cloneSet) {
t.Errorf("expected the length of the cloned Label set to be %d, but got %d",
len(labelSet), len(cloneSet))
}

for ln, lv := range labelSet {
expected := cloneSet[ln]
if expected != lv {
t.Errorf("expected to get LabelValue %s, but got %s for LabelName %s", expected, lv, ln)
}
}
}

func TestLabelSetMerge(t *testing.T) {
labelSet := LabelSet{
"monitor": "codelab",
"foo": "bar",
"bar": "baz",
}

labelSet2 := LabelSet{
"monitor": "codelab",
"dolor": "mi",
"lorem": "ipsum",
}

expectedSet := LabelSet{
"monitor": "codelab",
"foo": "bar",
"bar": "baz",
"dolor": "mi",
"lorem": "ipsum",
}

mergedSet := labelSet.Merge(labelSet2)

if len(mergedSet) != len(expectedSet) {
t.Errorf("expected the length of the cloned Label set to be %d, but got %d",
len(expectedSet), len(mergedSet))
}

for ln, lv := range mergedSet {
expected := expectedSet[ln]
if expected != lv {
t.Errorf("expected to get LabelValue %s, but got %s for LabelName %s", expected, lv, ln)
}
}
}

// Benchmark Results for LabelSet's String() method
// ---------------------------------------------------------------------------------------------------------
// goos: linux
// goarch: amd64
// pkg: github.com/prometheus/common/model
// cpu: 11th Gen Intel(R) Core(TM) i5-1145G7 @ 2.60GHz
// BenchmarkLabelSetStringMethod-8 732376 1532 ns/op

func BenchmarkLabelSetStringMethod(b *testing.B) {
ls := make(LabelSet)
ls["monitor"] = "codelab"
ls["foo2"] = "bar"
ls["foo"] = "bar"
ls["abc"] = "prometheus"
ls["foo11"] = "bar11"
for i := 0; i < b.N; i++ {
_ = ls.String()
}
}
45 changes: 45 additions & 0 deletions model/labelset_string.go
@@ -0,0 +1,45 @@
// Copyright 2013 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.

//go:build !go1.20

package model

import (
"bytes"
"sort"
"strconv"
)

// String will look like `{foo="bar", more="less"}`. Names are sorted alphabetically.
func (l LabelSet) String() string {
var lna [32]string // On stack to avoid memory allocation for sorting names.
labelNames := lna[:0]
for name := range l {
labelNames = append(labelNames, string(name))
}
sort.Strings(labelNames)
var bytea [1024]byte // On stack to avoid memory allocation while building the output.
b := bytes.NewBuffer(bytea[:0])
b.WriteByte('{')
for i, name := range labelNames {
if i > 0 {
b.WriteString(", ")
}
b.WriteString(name)
b.WriteByte('=')
b.Write(strconv.AppendQuote(b.AvailableBuffer(), string(l[LabelName(name)])))
}
b.WriteByte('}')
return b.String()
}
39 changes: 39 additions & 0 deletions model/labelset_string_go120.go
@@ -0,0 +1,39 @@
// Copyright 2013 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.

//go:build go1.20 && !go1.21

package model

import (
"fmt"
"sort"
"strings"
)

// String was optimized using functions not available for go 1.20
// or lower. We keep the old implementation for compatibility with client_golang.
// Once client golang drops support for go 1.20 (scheduled for August 2024), this
// file can be removed.
func (l LabelSet) String() string {
labelNames := make([]string, 0, len(l))
for name := range l {
labelNames = append(labelNames, string(name))
}
sort.Strings(labelNames)
lstrs := make([]string, 0, len(l))
for _, name := range labelNames {
lstrs = append(lstrs, fmt.Sprintf("%s=%q", name, l[LabelName(name)]))
}
return fmt.Sprintf("{%s}", strings.Join(lstrs, ", "))
}
2 changes: 2 additions & 0 deletions model/labelset_test.go
Expand Up @@ -11,6 +11,8 @@
// See the License for the specific language governing permissions and
// limitations under the License.

//go:build !go1.20

package model

import (
Expand Down

0 comments on commit e18058b

Please sign in to comment.