Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 6 additions & 17 deletions internal/tags/hash.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,32 +21,21 @@ var (
// overwhelming majority in practice) it performs zero heap allocations.
// Longer serialized forms fall back to a single exact-size heap allocation.
func HashTags(name string, tags map[string]string) (hi, lo uint64) {
numValid := len(tags)
for k, v := range tags {
if k == "" || v == "" {
numValid--
}
}
numValid := numValidTags(tags)
if numValid == 0 {
return hashSerialize(name, nil)
}

// Gather into a small stack array for the common case; only tag sets
// larger than this need a heap allocation for the gather itself.
var arr [16]Tag
var pairs TagSet
var arr [serializeStackTags]Tag
var dst TagSet
if numValid <= len(arr) {
pairs = arr[:0]
dst = arr[:0]
} else {
pairs = make(TagSet, 0, numValid)
}
for k, v := range tags {
if k != "" && v != "" {
pairs = append(pairs, NewTag(k, v))
}
dst = make(TagSet, 0, numValid)
}
pairs.Sort()
return hashSerialize(name, pairs)
return hashSerialize(name, gatherValidTags(dst, tags))
}

// Hash returns a 128-bit hash that uniquely identifies the canonical
Expand Down
153 changes: 37 additions & 116 deletions internal/tags/tags.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,14 +24,33 @@ type TagSet []Tag

// NewTagSet returns a new TagSet from the tags map.
func NewTagSet(tags map[string]string) TagSet {
a := make(TagSet, 0, len(tags))
return gatherValidTags(make(TagSet, 0, len(tags)), tags)
}

// gatherValidTags appends any pair from tags with a non-empty key and
// value onto dst and returns the sorted result. The caller owns dst's
// backing array (which may be a stack array pre-sized via numValidTags),
// so this function never allocates on its own.
func gatherValidTags(dst TagSet, tags map[string]string) TagSet {
for k, v := range tags {
if k != "" && v != "" {
a = append(a, NewTag(k, v))
dst = append(dst, NewTag(k, v))
}
}
a.Sort()
return a
dst.Sort()
return dst
}

// numValidTags returns the number of entries in tags with a non-empty key
// and value.
func numValidTags(tags map[string]string) int {
n := len(tags)
for k, v := range tags {
if k == "" || v == "" {
n--
}
}
return n
}

func (t TagSet) Len() int { return len(t) }
Expand Down Expand Up @@ -345,123 +364,25 @@ func (t TagSet) Serialize(name string) string {
return *(*string)(unsafe.Pointer(&b))
}

// serializeStackTags is the largest tag count SerializeTags/HashTags will
// gather into a stack array before falling back to a heap allocation.
const serializeStackTags = 16

// SerializeTags serializes name and tags into a statsd stat.
func SerializeTags(name string, tags map[string]string) string {
const prefix = ".__"
const sep = "="

// discard pairs where the tag or value is an empty string
numValid := len(tags)
for k, v := range tags {
if k == "" || v == "" {
numValid--
}
}

switch numValid {
case 0:
numValid := numValidTags(tags)
if numValid == 0 {
return name
case 1:
var t0 Tag
for k, v := range tags {
if k != "" && v != "" {
t0 = NewTag(k, v)
break
}
}
return name + prefix + t0.Key + sep + t0.Value
case 2:
var t0, t1 Tag
for k, v := range tags {
if k == "" || v == "" {
continue
}
t1 = t0
t0 = NewTag(k, v)
}
if t0.Key > t1.Key {
t0, t1 = t1, t0
}
return name + prefix + t0.Key + sep + t0.Value +
prefix + t1.Key + sep + t1.Value
case 3:
var t0, t1, t2 Tag
for k, v := range tags {
if k == "" || v == "" {
continue
}
t2 = t1
t1 = t0
t0 = NewTag(k, v)
}
if t1.Key > t2.Key {
t1, t2 = t2, t1
}
if t0.Key > t2.Key {
t0, t2 = t2, t0
}
if t0.Key > t1.Key {
t0, t1 = t1, t0
}
return name + prefix + t0.Key + sep + t0.Value +
prefix + t1.Key + sep + t1.Value +
prefix + t2.Key + sep + t2.Value
case 4:
var t0, t1, t2, t3 Tag
for k, v := range tags {
if k == "" || v == "" {
continue
}
t3 = t2
t2 = t1
t1 = t0
t0 = NewTag(k, v)
}
if t0.Key > t1.Key {
t0, t1 = t1, t0
}
if t2.Key > t3.Key {
t2, t3 = t3, t2
}
if t0.Key > t2.Key {
t0, t2 = t2, t0
}
if t1.Key > t3.Key {
t1, t3 = t3, t1
}
if t1.Key > t2.Key {
t1, t2 = t2, t1
}
return name + prefix + t0.Key + sep + t0.Value +
prefix + t1.Key + sep + t1.Value +
prefix + t2.Key + sep + t2.Value +
prefix + t3.Key + sep + t3.Value
default:
// n stores the length of the serialized name + tags
n := (len(prefix) + len(sep)) * numValid
n += len(name)
}

pairs := make(TagSet, 0, numValid)
for k, v := range tags {
if k == "" || v == "" {
continue
}
n += len(k) + len(v)
pairs = append(pairs, NewTag(k, v))
}
pairs.Sort()

// CEV: this is same as strings.Builder, but works with go1.9 and earlier
b := make([]byte, 0, n)
b = append(b, name...)
for _, tag := range pairs {
b = append(b, prefix...)
b = append(b, tag.Key...)
b = append(b, sep...)
b = append(b, tag.Value...)
}
return *(*string)(unsafe.Pointer(&b))
var arr [serializeStackTags]Tag
var dst TagSet
if numValid <= len(arr) {
dst = arr[:0]
} else {
dst = make(TagSet, 0, numValid)
}
return gatherValidTags(dst, tags).Serialize(name)
}

// ReplaceChars replaces any invalid chars ([.:|]) in value s with '_'.
Expand Down
Loading