Skip to content
This repository was archived by the owner on Aug 23, 2023. It is now read-only.

Commit 36065b4

Browse files
authored
Merge pull request #1158 from bloomberg/improveSetTags
Improve performance of SetTags
2 parents e5cdcf0 + b6a848d commit 36065b4

File tree

2 files changed

+139
-7
lines changed

2 files changed

+139
-7
lines changed

api/models/series.go

+34-7
Original file line numberDiff line numberDiff line change
@@ -26,19 +26,46 @@ type Series struct {
2626
}
2727

2828
func (s *Series) SetTags() {
29-
tagSplits := strings.Split(s.Target, ";")
29+
numTags := strings.Count(s.Target, ";")
30+
31+
if s.Tags == nil {
32+
// +1 for the name tag
33+
s.Tags = make(map[string]string, numTags+1)
34+
} else {
35+
for k := range s.Tags {
36+
delete(s.Tags, k)
37+
}
38+
}
39+
40+
if numTags == 0 {
41+
s.Tags["name"] = s.Target
42+
return
43+
}
44+
45+
index := strings.IndexByte(s.Target, ';')
46+
name := s.Target[:index]
3047

31-
s.Tags = make(map[string]string, len(tagSplits))
48+
remainder := s.Target
49+
for index > 0 {
50+
remainder = remainder[index+1:]
51+
index = strings.IndexByte(remainder, ';')
3252

33-
for _, tagPair := range tagSplits[1:] {
34-
parts := strings.SplitN(tagPair, "=", 2)
35-
if len(parts) != 2 {
53+
tagPair := remainder
54+
if index > 0 {
55+
tagPair = remainder[:index]
56+
}
57+
58+
equalsPos := strings.IndexByte(tagPair, '=')
59+
if equalsPos < 1 {
3660
// Shouldn't happen
3761
continue
3862
}
39-
s.Tags[parts[0]] = parts[1]
63+
64+
s.Tags[tagPair[:equalsPos]] = tagPair[equalsPos+1:]
4065
}
41-
s.Tags["name"] = tagSplits[0]
66+
67+
// Do this last to overwrite any "name" tag that might have been specified in the series tags.
68+
s.Tags["name"] = name
4269
}
4370

4471
func (s Series) Copy(emptyDatapoints []schema.Point) Series {

api/models/series_test.go

+105
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@ package models
22

33
import (
44
"encoding/json"
5+
"math/rand"
6+
"reflect"
57
"testing"
68

79
"github.com/raintank/schema"
@@ -90,3 +92,106 @@ func TestJsonMarshal(t *testing.T) {
9092
}
9193
}
9294
}
95+
96+
func TestSetTags(t *testing.T) {
97+
cases := []struct {
98+
in Series
99+
out map[string]string
100+
}{
101+
{
102+
in: Series{},
103+
out: map[string]string{
104+
"name": "",
105+
},
106+
},
107+
{
108+
in: Series{
109+
Target: "a",
110+
},
111+
out: map[string]string{
112+
"name": "a",
113+
},
114+
},
115+
{
116+
in: Series{
117+
Target: `a\b`,
118+
},
119+
out: map[string]string{
120+
"name": `a\b`,
121+
},
122+
},
123+
{
124+
in: Series{
125+
Target: "a;b=c;c=d",
126+
},
127+
out: map[string]string{
128+
"name": "a",
129+
"b": "c",
130+
"c": "d",
131+
},
132+
},
133+
{
134+
in: Series{
135+
Target: "a;biglongtagkeyhere=andithasabiglongtagvaluetoo;c=d",
136+
},
137+
out: map[string]string{
138+
"name": "a",
139+
"biglongtagkeyhere": "andithasabiglongtagvaluetoo",
140+
"c": "d",
141+
},
142+
},
143+
}
144+
145+
for _, c := range cases {
146+
c.in.SetTags()
147+
if !reflect.DeepEqual(c.out, c.in.Tags) {
148+
t.Fatalf("SetTags incorrect\nexpected:%v\ngot: %v\n", c.out, c.in.Tags)
149+
}
150+
}
151+
}
152+
153+
func BenchmarkSetTags_00tags_00chars(b *testing.B) {
154+
benchmarkSetTags(b, 0, 0, 0, true)
155+
}
156+
157+
func BenchmarkSetTags_20tags_32chars(b *testing.B) {
158+
benchmarkSetTags(b, 20, 32, 32, true)
159+
}
160+
161+
func BenchmarkSetTags_20tags_32chars_reused(b *testing.B) {
162+
benchmarkSetTags(b, 20, 32, 32, false)
163+
}
164+
165+
func benchmarkSetTags(b *testing.B, numTags, tagKeyLength, tagValueLength int, resetTags bool) {
166+
in := Series{
167+
Target: "my.metric.name",
168+
}
169+
170+
for i := 0; i < numTags; i++ {
171+
in.Target += ";" + randString(tagKeyLength) + "=" + randString(tagValueLength)
172+
}
173+
174+
b.ResetTimer()
175+
176+
for i := 0; i < b.N; i++ {
177+
in.SetTags()
178+
if len(in.Tags) != numTags+1 {
179+
b.Fatalf("Expected %d tags, got %d, target = %s, tags = %v", numTags+1, len(in.Tags), in.Target, in.Tags)
180+
}
181+
if resetTags {
182+
// Reset so as to not game the allocations
183+
in.Tags = nil
184+
}
185+
}
186+
b.SetBytes(int64(len(in.Target)))
187+
}
188+
189+
func randString(n int) string {
190+
const letterBytes = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
191+
192+
b := make([]byte, n)
193+
for i := range b {
194+
b[i] = letterBytes[rand.Int63()%int64(len(letterBytes))]
195+
}
196+
return string(b)
197+
}

0 commit comments

Comments
 (0)