Skip to content

Commit

Permalink
custom sorting for LabelSet. See: prometheus#543
Browse files Browse the repository at this point in the history
Signed-off-by: Syed Nihal <[email protected]>
  • Loading branch information
wasim-nihal committed Feb 2, 2024
1 parent a3bdb9e commit 04b56e0
Show file tree
Hide file tree
Showing 2 changed files with 144 additions and 4 deletions.
113 changes: 111 additions & 2 deletions model/labelset.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,9 @@ package model
import (
"encoding/json"
"fmt"
"math/big"
"sort"
"strconv"
"strings"
)

Expand Down Expand Up @@ -134,8 +136,8 @@ func (l LabelSet) String() string {
for l, v := range l {
lstrs = append(lstrs, fmt.Sprintf("%s=%q", l, v))
}

sort.Strings(lstrs)
//sort.Strings(lstrs)
sort.Stable(LabelSorter(lstrs))
return fmt.Sprintf("{%s}", strings.Join(lstrs, ", "))
}

Expand Down Expand Up @@ -167,3 +169,110 @@ func (l *LabelSet) UnmarshalJSON(b []byte) error {
*l = LabelSet(m)
return nil
}

type LabelSorter []string

func (p LabelSorter) Len() int { return len(p) }
func (p LabelSorter) Less(i, j int) bool { return Less2(p[i], p[j]) }
func (p LabelSorter) Swap(i, j int) { p[i], p[j] = p[j], p[i] }

// Faster, doesn't support sorting of numbers > 64bit unsigned
func Less1(a, b string) bool {
for len(a) > 0 && len(b) > 0 {
idx := indexTillCommonPrefix(a, b)
if idx > 0 {
a = a[idx:]
b = b[idx:]
}
if len(a) == 0 {
return len(b) != 0
}
ia := indexTillDigit(a)
ib := indexTillDigit(b)
if ia > 0 && ib > 0 {
an, aerr := strconv.ParseUint(a[:ia], 10, 64)
bn, berr := strconv.ParseUint(b[:ib], 10, 64)
if aerr != nil && berr != nil {
if an != bn {
return an < bn
}
if ia != len(a) && ib != len(b) {
a = a[ia:]
b = b[ib:]
continue
}
}
} else if ia > 0 && b[0] == '=' {
return false
} else if ib > 0 && a[0] == '=' {
return true
}
return a < b
}
return a < b
}

// Slower, and supports sorting of numbers > 64bit unsigned
func Less2(a, b string) bool {
for len(a) > 0 && len(b) > 0 {
idx := indexTillCommonPrefix(a, b)
if idx > 0 {
a = a[idx:]
b = b[idx:]
}
if len(a) == 0 {
return len(b) != 0
}
ia := indexTillDigit(a)
ib := indexTillDigit(b)
if ia > 0 && ib > 0 {
bigIntA := new(big.Int)
bigIntB := new(big.Int)
an, aerr := bigIntA.SetString(a[:ia], 10)
bn, berr := bigIntB.SetString(b[:ib], 10)
if aerr && berr {
if aLessb := an.Cmp(bn); aLessb == -1 {
return true
}
if ia != len(a) && ib != len(b) {
a = a[ia:]
b = b[ib:]
continue
}
}
} else if ia > 0 && b[0] == '=' {
return false
} else if ib > 0 && a[0] == '=' {
return true
}
return a < b
}
return a < b
}

// indexTillCommonPrefix returns index till the common prefix between the two strings
func indexTillCommonPrefix(a, b string) int {
m := len(a)
if n := len(b); n < m {
m = n
}
if m == 0 {
return 0
}
for i := 0; i < m; i++ {
if (a[i] >= '0' && a[i] <= '9') || (b[i] >= '0' && b[i] <= '9') || (a[i] != b[i]) {
return i
}
}
return m
}

// indexTillCommonPrefix returns index till the first occurrence of a digit
func indexTillDigit(s string) int {
for i, c := range s {
if c < '0' || c > '9' {
return i
}
}
return len(s)
}
35 changes: 33 additions & 2 deletions model/labelset_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ package model

import (
"encoding/json"
"sort"
"testing"
)

Expand All @@ -27,7 +28,8 @@ func TestUnmarshalJSONLabelSet(t *testing.T) {
labelSetJSON := `{
"labelSet": {
"monitor": "codelab",
"foo": "bar"
"foo": "bar",
"foo2": "bar"
}
}`
var c testConfig
Expand All @@ -38,7 +40,7 @@ func TestUnmarshalJSONLabelSet(t *testing.T) {

labelSetString := c.LabelSet.String()

expected := `{foo="bar", monitor="codelab"}`
expected := `{foo="bar", foo2="bar", monitor="codelab"}`

if expected != labelSetString {
t.Errorf("expected %s but got %s", expected, labelSetString)
Expand Down Expand Up @@ -117,3 +119,32 @@ func TestLabelSetMerge(t *testing.T) {
}
}
}

// goos: linux
// goarch: amd64
// pkg: github.com/prometheus/common/model
// cpu: 11th Gen Intel(R) Core(TM) i5-1145G7 @ 2.60GHz
// BenchmarkStandardSort-8 19753996 52.57 ns/op
func BenchmarkStandardSort(b *testing.B) {
var data = []string{`foo2="bar"`, `foo="bar"`, `aaa="abc"`, `aab="aaa"`, `foo1844="bar"`, `foo1="bar"`}
for i := 0; i < b.N; i++ {
sort.Strings(data)
}
}

// goos: linux
// goarch: amd64
// pkg: github.com/prometheus/common/model
// cpu: 11th Gen Intel(R) Core(TM) i5-1145G7 @ 2.60GHz
//
// Case 1: Without supporting numbers > 64bit unsigned
// BenchmarkLabelSort-8 8917842 130.8 ns/op
//
// Case 2: Supporting numbers > 64bit unsigned
// BenchmarkLabelSort-8 2512645 480.9 ns/op
func BenchmarkLabelSort(b *testing.B) {
var data = []string{`foo2="bar"`, `foo="bar"`, `aaa="abc"`, `aab="aaa"`, `foo1844="bar"`, `foo1="bar"`}
for i := 0; i < b.N; i++ {
sort.Stable(LabelSorter(data))
}
}

0 comments on commit 04b56e0

Please sign in to comment.