-
Notifications
You must be signed in to change notification settings - Fork 32
/
tag.go
143 lines (118 loc) · 3.01 KB
/
tag.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
package stats
import (
"sync"
"golang.org/x/exp/slices"
)
// A Tag is a pair of a string key and value set on measures to define the
// dimensions of the metrics.
type Tag struct {
Name string
Value string
}
// T is shorthand for `stats.Tag{Name: "blah", Value: "foo"}` It returns
// the tag for Name k and Value v.
func T(k, v string) Tag {
return Tag{Name: k, Value: v}
}
func (t Tag) String() string {
return t.Name + "=" + t.Value
}
// M allows for creating a tag list from a map.
func M(m map[string]string) []Tag {
tags := make([]Tag, 0, len(m))
for k, v := range m {
tags = append(tags, T(k, v))
}
return tags
}
// TagsAreSorted returns true if the given list of tags is sorted by tag name,
// false otherwise.
func TagsAreSorted(tags []Tag) bool {
return slices.IsSortedFunc(tags, tagCompare)
}
// SortTags sorts and deduplicates tags in-place, favoring later elements
// whenever a tag name duplicate occurs. The returned slice may be shorter than
// the input due to the elimination of duplicates.
func SortTags(tags []Tag) []Tag {
// Stable sort ensures that we have deterministic
// "latest wins" deduplication.
// For 20 or fewer tags, this is as fast as an unstable sort.
slices.SortStableFunc(tags, tagCompare)
return deduplicateTags(tags)
}
// tagCompare reports whether a is less than b.
func tagCompare(a, b Tag) int {
if a.Name < b.Name {
return -1
} else if b.Name < a.Name {
return 1
}
return 0
}
func deduplicateTags(tags []Tag) []Tag {
var prev string
out := tags[:0]
for _, tag := range tags {
switch {
case tag.Name == "":
// Ignore unnamed tags.
continue
case tag.Name != prev:
// Non-duplicate tag: keep.
prev = tag.Name
out = append(out, tag)
default:
// Duplicate tag: replace previous, same-named tag.
i := len(out) - 1
out[i] = tag
}
}
if len(out) == 0 {
// No input tags had non-empty names:
// return nil to be consistent for ease of testing.
return nil
}
return out
}
// mergeTags returns the sorted, deduplicated-by-name union of t1 and t2.
// When duplicate tag names are encountered,
// the latest Tag with that name is the name-value pair that is retained:
// for each tag name in t2, the same tag names in t1 will be ignored,
// though this will also have the effect of deduplicating tag
// that may have even existed within a single tag slice.
func mergeTags(t1, t2 []Tag) []Tag {
n := len(t1) + len(t2)
if n == 0 {
return nil
}
out := make([]Tag, 0, n)
out = append(out, t1...)
out = append(out, t2...)
return SortTags(out)
}
func copyTags(tags []Tag) []Tag {
if len(tags) == 0 {
return nil
}
ctags := make([]Tag, len(tags))
copy(ctags, tags)
return ctags
}
type tagsBuffer struct {
tags []Tag
}
func (b *tagsBuffer) reset() {
for i := range b.tags {
b.tags[i] = Tag{}
}
b.tags = b.tags[:0]
}
func (b *tagsBuffer) sort() {
SortTags(b.tags)
}
func (b *tagsBuffer) append(tags ...Tag) {
b.tags = append(b.tags, tags...)
}
var tagsPool = sync.Pool{
New: func() any { return &tagsBuffer{tags: make([]Tag, 0, 8)} },
}