-
Notifications
You must be signed in to change notification settings - Fork 3
/
Copy pathfast_tags.go
348 lines (304 loc) · 8.74 KB
/
fast_tags.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
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
package metrics
import (
"sort"
"sync"
)
var (
disableFastTags = false
)
// FastTag is an element of FastTags (see "FastTags")
type FastTag struct {
Key string
StringValue string
// The main value is the StringValue. "intValue" exists only for optimizations
intValue int64
intValueIsSet bool
// This check was temporary added only for debugging (to locate and fix one bug in this module)
// It negatively affects the performance and should be removed in future (like in >= 2021 year)
// A marker if the tag is already in-use and cannot be returned from a pool with Get()
isInUse bool
// EndOf the debugging check
}
var (
fastTagPool = sync.Pool{
New: func() interface{} {
return &FastTag{}
},
}
)
// SetDisableFastTags forces to use Tags instead of FastTags. So if SetDisableFastTags(true) is set then
// NewFastTags() will return "Tags" instead of "FastTags".
//
// This is supposed to be used only for debugging (like to check if there's a bug caused by FastTags).
func SetDisableFastTags(newDisableFastTags bool) {
disableFastTags = newDisableFastTags
}
func newFastTag() *FastTag {
tag := fastTagPool.Get().(*FastTag)
// This check was temporary added only for debugging (to locate and fix one bug in this module)
// It negatively affects the performance and should be removed in future (like in >= 2021 year)
if tag.isInUse {
panic(`A attempt to acquire a busy FastTag`)
}
tag.isInUse = true
// EndOf the debugging check
return tag
}
// Release puts the FastTag back into the pool. The pool is use for memory reuse (to do not GC and reallocate
// memory).
//
// This method is supposed to be used to internal needs, only.
func (tag *FastTag) Release() {
if !MemoryReuseEnabled() {
return
}
// This check was temporary added only for debugging (to locate and fix one bug in this module)
// It negatively affects the performance and should be removed in future (like in >= 2021 year)
if !tag.isInUse {
panic(`An attempt to release a (already) released FastTag`)
}
tag.isInUse = false
// EndOf the debugging check
tag.intValueIsSet = false
fastTagPool.Put(tag)
}
/*func TagValueToBytes(value Tag) []byte {
switch v := value.(type) {
case []byte:
return v
default:
return []byte(TagValueToString(value))
}
}*/
// GetValue returns the value of the tag. It returns it as an int64 if the value could be represented as an integer, or
// as a string if it cannot be represented as an integer.
func (tag *FastTag) GetValue() interface{} {
if tag.intValueIsSet {
return tag.intValue
}
return tag.StringValue
}
// Set sets the key and the value.
//
// The value will be stored as a string and, if possible, as an int64.
func (tag *FastTag) Set(key string, value interface{}) {
tag.Key = key
tag.StringValue = TagValueToString(value)
if intV, ok := toInt64(value); ok {
tag.intValue = intV
tag.intValueIsSet = true
}
}
type FastTags struct {
Slice []*FastTag
isInUse bool
}
var (
fastTagsPool = sync.Pool{
New: func() interface{} {
return &FastTags{}
},
}
)
func newFastTags() *FastTags {
tags := fastTagsPool.Get().(*FastTags)
if tags.isInUse {
panic(`An attempt to acquire a busy FastTags`)
}
tags.isInUse = true
return tags
}
// NewFastTags returns an implementation of AnyTags with a full memory reuse support (if SetDisableFastTags(true) is
// not set).
//
// This implementation is supposed to be used if it's required to reduce a pressure on GC (see "GCCPUFraction",
// https://golang.org/pkg/runtime/#MemStats).
//
// It could be required if there's a metric that is retrieved very often and it's required to reduce CPU utilization.
//
// If SetDisableFastTags(true) is set then it returns the same as "NewTags" (without full memory reuse).
//
// See "Tags" in README.md
func NewFastTags() AnyTags {
if disableFastTags {
return NewTags()
}
return newFastTags()
}
// Release clears the tags and puts the them back into the pool. It's required for memory reusing.
//
// See "Tags" in README.md
func (tags *FastTags) Release() {
if !MemoryReuseEnabled() {
return
}
if tags == nil {
return
}
if !tags.isInUse {
panic(`An attempt to release a released FastTags`)
}
tags.isInUse = false
for _, tag := range tags.Slice {
tag.Release()
}
tags.Slice = tags.Slice[:0]
fastTagsPool.Put(tags)
}
// Len returns the amount/count of tags
func (tags *FastTags) Len() int {
if tags == nil {
return 0
}
return len(tags.Slice)
}
// Less returns if the Key of the tag by index "i" is less (strings comparison) than the Key of the tag by index "j".
func (tags *FastTags) Less(i, j int) bool {
return tags.Slice[i].Key < tags.Slice[j].Key
}
// Swap just swaps tags by indexes "i" and "j"
func (tags *FastTags) Swap(i, j int) {
tags.Slice[i], tags.Slice[j] = tags.Slice[j], tags.Slice[i]
}
// Sort sorts tags by keys (using Swap, Less and Len)
func (tags *FastTags) Sort() {
// We use our-own implementation of sorts without interfaces which doesn't require a memory allocation
if len(tags.Slice) <= 8 {
// On a small slice "Bubble" is really not that bad (k*O(n*n) with a small "k").
tags.sortBubble()
} else {
// TODO: May be it's not required to reimplement QuickSort if some of magic comments
// (https://github.com/xaionaro-go/hackery) may be used to safely prevent memory allocation.
tags.sortQuick()
}
}
// findStupid finds the tag with key "key" using a full scan
//
// It returns the index of the found tag. If the tag wasn't found then -1 will be returned.
func (tags *FastTags) findStupid(key string) int {
for idx, tag := range tags.Slice {
if tag.Key == key {
return idx
}
}
return -1
}
// findFast finds the tag with key "key" using a binary search.
//
// It returns the index of the found tag. If the tag wasn't found then -1 will be returned.
//
// Tags should be sorted before use this method.
func (tags *FastTags) findFast(key string) int {
l := len(tags.Slice)
idx := sort.Search(l, func(i int) bool {
return tags.Slice[i].Key >= key
})
if idx < 0 || idx >= l {
return -1
}
if tags.Slice[idx].Key != key {
return -1
}
return idx
}
// IsSet returns true if there's a tag with key "key", otherwise -- false.
func (tags *FastTags) IsSet(key string) bool {
return tags.findStupid(key) != -1
}
// Get returns the value of the tag with key "key".
//
// If there's no such tag then nil will be returned.
func (tags *FastTags) Get(key string) interface{} {
idx := tags.findStupid(key)
if idx == -1 {
return nil
}
return tags.Slice[idx].GetValue()
}
// Set sets the value of the tag with key "key" to "value". If there's no such tag then creates it and sets the value.
func (tags *FastTags) Set(key string, value interface{}) AnyTags {
idx := tags.findStupid(key)
if idx != -1 {
tags.Slice[idx].Set(key, value)
return tags
}
newTag := newFastTag()
newTag.Set(key, value)
tags.Slice = append(tags.Slice, newTag)
return tags
}
// Each is a function to call function "fn" for each tag. A key and a value of a tag will be passed as "k" and "v"
// arguments, accordingly.
func (tags *FastTags) Each(fn func(k string, v interface{}) bool) {
if tags == nil {
return
}
if !tags.isInUse {
panic(`An attempt to use a released FastTags`)
}
for _, tag := range tags.Slice {
if !fn(tag.Key, tag.GetValue()) {
break
}
}
}
// ToFastTags does nothing and returns the same tags.
//
// This method is required to implement interface "AnyTags".
func (tags *FastTags) ToFastTags() *FastTags {
return tags
}
// ToMap returns tags as an map of tag keys to tag values ("map[string]interface{}").
//
// Any maps passed as an argument will overwrite values of the resulting map.
func (tags *FastTags) ToMap(fieldMaps ...map[string]interface{}) map[string]interface{} {
if tags == nil {
return nil
}
if !tags.isInUse {
panic(`An attempt to use a released FastTags`)
}
fields := map[string]interface{}{}
if tags != nil {
for _, tag := range tags.Slice {
fields[tag.Key] = tag.GetValue()
}
}
for _, fieldMap := range fieldMaps {
for k, v := range fieldMap {
fields[k] = v
}
}
return fields
}
// String returns tags as a string compatible with StatsD format of tags.
func (tags *FastTags) String() string {
buf := newBytesBuffer()
tags.WriteAsString(buf)
result := buf.String()
buf.Release()
return result
}
// WriteAsString writes tags in StatsD format through the WriteStringer (passed as the argument)
func (tags *FastTags) WriteAsString(writeStringer interface{ WriteString(string) (int, error) }) {
if tags == nil {
return
}
if !tags.isInUse {
panic(`An attempt to use a released FastTags`)
}
tags.Sort()
tagsCount := 0
for _, tag := range tags.Slice {
if defaultTags.IsSet(tag.Key) {
continue
}
if tagsCount != 0 {
writeStringer.WriteString(`,`)
}
writeStringer.WriteString(tag.Key)
writeStringer.WriteString(`=`)
writeStringer.WriteString(tag.StringValue)
tagsCount++
}
}