Skip to content
This repository has been archived by the owner on Oct 17, 2018. It is now read-only.

Commit

Permalink
Make tags a variadic argument
Browse files Browse the repository at this point in the history
  • Loading branch information
Jerome Froelich committed Feb 12, 2017
1 parent 4b3cda5 commit 09df881
Show file tree
Hide file tree
Showing 3 changed files with 63 additions and 39 deletions.
27 changes: 15 additions & 12 deletions metric/serialize.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import (
)

const (
defaultTagsMapLen = 4
defaultTagsMapLen = 8

// ComponentSplitter is the separator for different compoenents of the path
ComponentSplitter = '+'
Expand All @@ -23,23 +23,26 @@ var (
)

// Serialize serializes the name and tags of a metric into a buffer of bytes
func Serialize(name string, tags map[string]string, buf *bytes.Buffer) {
func Serialize(buf *bytes.Buffer, name string, tags ...map[string]string) {
buf.WriteString(name)
if len(tags) > 0 {
buf.WriteRune(ComponentSplitter)
}

// NB(jeromefroe): we do not check for duplicate tags here to avoid the performance penalty
first := true
for key, val := range tags {
if !first {
buf.WriteRune(TagPairSplitter)
} else {
first = false
}
for _, tagMap := range tags {
for key, val := range tagMap {
if !first {
buf.WriteRune(TagPairSplitter)
} else {
first = false
}

buf.WriteString(key)
buf.WriteRune(TagNameSplitter)
buf.WriteString(val)
buf.WriteString(key)
buf.WriteRune(TagNameSplitter)
buf.WriteString(val)
}
}
}

Expand All @@ -52,7 +55,7 @@ func Deserialize(buf []byte) (string, map[string]string, error) {

name := string(buf[:n])
buf = buf[(n + 1):]
tags := make(map[string]string)
tags := make(map[string]string, defaultTagsMapLen)

for {
n = bytes.IndexByte(buf, TagNameSplitter)
Expand Down
2 changes: 1 addition & 1 deletion metric/serialize_benchmark_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import (
func BenchmarkSerialize(b *testing.B) {
buf := new(bytes.Buffer)
for n := 0; n < b.N; n++ {
Serialize(name, tags, buf)
Serialize(buf, name, commonTags, serviceTags)
}
}

Expand Down
73 changes: 47 additions & 26 deletions metric/serialize_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,84 +8,105 @@ import (
)

var (
name = "foobar"
tags = map[string]string{
name = "foobar"
commonTags = map[string]string{
"service": "fizzbuzz",
"dc": "ewr1",
"env": "production",
}
expected = "foobar+service=fizzbuzz,dc=ewr1,env=production"
serviceTags = map[string]string{
"type": "requests",
"city": "nyc",
}
expected = "foobar+service=fizzbuzz,dc=ewr1,env=production,type=requests,city=nyc"
)

func TestSerializeName(t *testing.T) {
testSerialize(t, name, nil, name)
testSerialize(t, name, name)
}

func TestSerializeNameAndTags(t *testing.T) {
// NB(jeromefroe): Since iterating over a map is randomized we can't rely on tags to be serialized
// in the same order across calls to Serialize. Consequently we test with only one tag here.
tags := map[string]string{
commonTags := map[string]string{
"service": "fizzbuzz",
}
expected := "foobar+service=fizzbuzz"
testSerialize(t, name, tags, expected)
serviceTags := map[string]string{
"type": "requests",
}
expected := "foobar+service=fizzbuzz,type=requests"
testSerialize(t, expected, name, commonTags, serviceTags)
}

func testSerialize(t *testing.T, name string, tags map[string]string, expected string) {
func testSerialize(t *testing.T, expected string, name string, tags ...map[string]string) {
buf := new(bytes.Buffer)
Serialize(name, tags, buf)
Serialize(buf, name, tags...)
assert.Equal(t, expected, string(buf.Bytes()))
}

func TestDeserializeName(t *testing.T) {
testDeserialize(t, []byte(name), name, nil, nil)
testDeserialize(t, []byte(name), nil, name)
}

func TestDeserializeNameAndTags(t *testing.T) {
buf := []byte(expected)
testDeserialize(t, buf, name, tags, nil)
testDeserialize(t, buf, nil, name, commonTags, serviceTags)
}

func TestDeserializeErrInvalidTags(t *testing.T) {
buf := []byte("foobar+")
testDeserialize(t, buf, "", nil, errParseTagFailure)
testDeserialize(t, buf, errParseTagFailure, "")
}

func TestDeserializeErrEmptyTagKey(t *testing.T) {
buf := []byte("foobar+service=fizzbuzz,=ewr1,env=production")
testDeserialize(t, buf, "", nil, errEmptyTagKey)
testDeserialize(t, buf, errEmptyTagKey, "")
}

func TestDeserializeErrEmptyTagValue(t *testing.T) {
buf := []byte("foobar+service=,dc=ewr1,env=production")
testDeserialize(t, buf, "", nil, errEmptyTagValue)
testDeserialize(t, buf, errEmptyTagValue, "")
}

func testDeserialize(t *testing.T, buf []byte, name string, tags map[string]string, err error) {
func testDeserialize(t *testing.T, buf []byte, err error, name string, tags ...map[string]string) {
allTags := combineTags(tags...)
actualName, actualTags, actualErr := Deserialize(buf)

assert.Equal(t, name, actualName)
assert.Equal(t, tags, actualTags)
assert.Equal(t, allTags, actualTags)
assert.Equal(t, err, actualErr)
}

func TestRoundtripName(t *testing.T) {
testRoundtrip(t, "foobar", nil)
testRoundtrip(t, "foobar")
}

func TestRoundtripNameAndTags(t *testing.T) {
tags := map[string]string{
"service": "fizzbuzz",
"dc": "ewr1",
"env": "production",
}
testRoundtrip(t, "foobar", tags)
testRoundtrip(t, "foobar", commonTags, serviceTags)
}

func testRoundtrip(t *testing.T, name string, tags map[string]string) {
func testRoundtrip(t *testing.T, name string, tags ...map[string]string) {
buf := new(bytes.Buffer)
Serialize(name, tags, buf)
Serialize(buf, name, tags...)

actualName, actualTags, err := Deserialize(buf.Bytes())

allTags := combineTags(tags...)
assert.Nil(t, err)
assert.Equal(t, name, actualName)
assert.Equal(t, tags, actualTags)
assert.Equal(t, allTags, actualTags)
}

func combineTags(tags ...map[string]string) map[string]string {
if len(tags) == 0 {
return nil
}

allTags := make(map[string]string)
for _, tagMap := range tags {
for key, val := range tagMap {
allTags[key] = val
}
}
return allTags
}

0 comments on commit 09df881

Please sign in to comment.