-
Notifications
You must be signed in to change notification settings - Fork 100
/
Copy pathtags.go
108 lines (95 loc) · 3.12 KB
/
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
package gostatsd
import (
"sort"
"strings"
)
// Tags represents a list of tags. Tags can be of two forms:
// 1. "key:value". "value" may contain column(s) as well.
// 2. "tag". No column.
// Each tag's key and/or value may contain characters invalid for a particular backend.
// Backends are expected to handle them appropriately. Different backends may have different sets of valid
// characters so it is undesirable to have restrictions on the input side.
type Tags []string
const (
// StatsdSourceID stores the key used to tag metrics with the origin IP address.
// Should be short to avoid extra hashing and memory overhead for map operations.
StatsdSourceID = "s"
unset = "unknown"
)
// String returns a comma-separated string representation of the tags.
func (tags Tags) String() string {
return strings.Join(tags, ",")
}
// SortedString sorts the tags alphabetically and returns
// a comma-separated string representation of the tags.
// Note that this method may mutate the original object.
func (tags Tags) SortedString() string {
sort.Strings(tags)
return tags.String()
}
// NormalizeTagKey cleans up the key of a tag.
func NormalizeTagKey(key string) string {
return strings.Replace(key, ":", "_", -1)
}
// Concat returns a new Tags with the additional ones added
func (tags Tags) Concat(additional Tags) Tags {
t := make(Tags, 0, len(tags)+len(additional))
t = append(t, tags...)
t = append(t, additional...)
return t
}
// Copy returns a copy of the Tags
func (tags Tags) Copy() Tags {
if tags == nil {
return nil
}
tagCopy := make(Tags, len(tags))
copy(tagCopy, tags)
return tagCopy
}
// Exists returns true if the tag exists in the tags
func (tags Tags) Exists(tag string) bool {
for _, t := range tags {
key, _ := parseTag(t)
if key == tag {
return true
}
}
return false
}
// ToMap converts all the tags into a format that can be translated
// to send to a different vendor if required.
// - If the tag exists without a value it is converted to: "unknown:<tag>"
// - If the tag has several values it is converted to: "tag:Join(Sort(values), "__")"
// - []{ "tag:pineapple","tag:pear" } ==> "tag:pear__pineapple"
// - []{ "tag:newt" } ==> "tag:newt"
// - []{ "tag:newt", "tag:newt" } ==> "tag:newt__newt"
//
// - If the tag key contains a . it is re-mapped to _
func (tags Tags) ToMap() map[string]string {
flatpack := make(map[string][]string, len(tags))
for i := 0; i < len(tags); i++ {
key, value := parseTag(tags[i])
key = strings.ReplaceAll(key, `.`, `_`)
flatpack[key] = append(flatpack[key], value)
}
tagsMap := make(map[string]string, len(flatpack))
for key, values := range flatpack {
// Cheap operation, due to the fact that no sorting or additional allocation is required.
if len(values) == 1 {
tagsMap[key] = values[0]
continue
}
// Expensive operation due an values being sorted and additioanl string being created with the Join
sort.Strings(values)
tagsMap[key] = strings.Join(values, `__`)
}
return tagsMap
}
func parseTag(tag string) (string, string) {
tokens := strings.SplitN(tag, ":", 2)
if len(tokens) == 2 {
return tokens[0], tokens[1]
}
return unset, tokens[0]
}