1package statsd
2
3import (
4	"strconv"
5	"strings"
6)
7
8var (
9	gaugeSymbol        = []byte("g")
10	countSymbol        = []byte("c")
11	histogramSymbol    = []byte("h")
12	distributionSymbol = []byte("d")
13	setSymbol          = []byte("s")
14	timingSymbol       = []byte("ms")
15	tagSeparatorSymbol = ","
16)
17
18func appendHeader(buffer []byte, namespace string, name string) []byte {
19	if namespace != "" {
20		buffer = append(buffer, namespace...)
21	}
22	buffer = append(buffer, name...)
23	buffer = append(buffer, ':')
24	return buffer
25}
26
27func appendRate(buffer []byte, rate float64) []byte {
28	if rate < 1 {
29		buffer = append(buffer, "|@"...)
30		buffer = strconv.AppendFloat(buffer, rate, 'f', -1, 64)
31	}
32	return buffer
33}
34
35func appendWithoutNewlines(buffer []byte, s string) []byte {
36	// fastpath for strings without newlines
37	if strings.IndexByte(s, '\n') == -1 {
38		return append(buffer, s...)
39	}
40
41	for _, b := range []byte(s) {
42		if b != '\n' {
43			buffer = append(buffer, b)
44		}
45	}
46	return buffer
47}
48
49func appendTags(buffer []byte, globalTags []string, tags []string) []byte {
50	if len(globalTags) == 0 && len(tags) == 0 {
51		return buffer
52	}
53	buffer = append(buffer, "|#"...)
54	firstTag := true
55
56	for _, tag := range globalTags {
57		if !firstTag {
58			buffer = append(buffer, tagSeparatorSymbol...)
59		}
60		buffer = appendWithoutNewlines(buffer, tag)
61		firstTag = false
62	}
63	for _, tag := range tags {
64		if !firstTag {
65			buffer = append(buffer, tagSeparatorSymbol...)
66		}
67		buffer = appendWithoutNewlines(buffer, tag)
68		firstTag = false
69	}
70	return buffer
71}
72
73func appendTagsAggregated(buffer []byte, globalTags []string, tags string) []byte {
74	if len(globalTags) == 0 && tags == "" {
75		return buffer
76	}
77
78	buffer = append(buffer, "|#"...)
79	firstTag := true
80
81	for _, tag := range globalTags {
82		if !firstTag {
83			buffer = append(buffer, tagSeparatorSymbol...)
84		}
85		buffer = appendWithoutNewlines(buffer, tag)
86		firstTag = false
87	}
88	if tags != "" {
89		if !firstTag {
90			buffer = append(buffer, tagSeparatorSymbol...)
91		}
92		buffer = appendWithoutNewlines(buffer, tags)
93	}
94	return buffer
95}
96
97func appendFloatMetric(buffer []byte, typeSymbol []byte, namespace string, globalTags []string, name string, value float64, tags []string, rate float64, precision int) []byte {
98	buffer = appendHeader(buffer, namespace, name)
99	buffer = strconv.AppendFloat(buffer, value, 'f', precision, 64)
100	buffer = append(buffer, '|')
101	buffer = append(buffer, typeSymbol...)
102	buffer = appendRate(buffer, rate)
103	buffer = appendTags(buffer, globalTags, tags)
104	return buffer
105}
106
107func appendIntegerMetric(buffer []byte, typeSymbol []byte, namespace string, globalTags []string, name string, value int64, tags []string, rate float64) []byte {
108	buffer = appendHeader(buffer, namespace, name)
109	buffer = strconv.AppendInt(buffer, value, 10)
110	buffer = append(buffer, '|')
111	buffer = append(buffer, typeSymbol...)
112	buffer = appendRate(buffer, rate)
113	buffer = appendTags(buffer, globalTags, tags)
114	return buffer
115}
116
117func appendStringMetric(buffer []byte, typeSymbol []byte, namespace string, globalTags []string, name string, value string, tags []string, rate float64) []byte {
118	buffer = appendHeader(buffer, namespace, name)
119	buffer = append(buffer, value...)
120	buffer = append(buffer, '|')
121	buffer = append(buffer, typeSymbol...)
122	buffer = appendRate(buffer, rate)
123	buffer = appendTags(buffer, globalTags, tags)
124	return buffer
125}
126
127func appendGauge(buffer []byte, namespace string, globalTags []string, name string, value float64, tags []string, rate float64) []byte {
128	return appendFloatMetric(buffer, gaugeSymbol, namespace, globalTags, name, value, tags, rate, -1)
129}
130
131func appendCount(buffer []byte, namespace string, globalTags []string, name string, value int64, tags []string, rate float64) []byte {
132	return appendIntegerMetric(buffer, countSymbol, namespace, globalTags, name, value, tags, rate)
133}
134
135func appendHistogram(buffer []byte, namespace string, globalTags []string, name string, value float64, tags []string, rate float64) []byte {
136	return appendFloatMetric(buffer, histogramSymbol, namespace, globalTags, name, value, tags, rate, -1)
137}
138
139func appendDistribution(buffer []byte, namespace string, globalTags []string, name string, value float64, tags []string, rate float64) []byte {
140	return appendFloatMetric(buffer, distributionSymbol, namespace, globalTags, name, value, tags, rate, -1)
141}
142
143func appendSet(buffer []byte, namespace string, globalTags []string, name string, value string, tags []string, rate float64) []byte {
144	return appendStringMetric(buffer, setSymbol, namespace, globalTags, name, value, tags, rate)
145}
146
147func appendTiming(buffer []byte, namespace string, globalTags []string, name string, value float64, tags []string, rate float64) []byte {
148	return appendFloatMetric(buffer, timingSymbol, namespace, globalTags, name, value, tags, rate, 6)
149}
150
151func escapedEventTextLen(text string) int {
152	return len(text) + strings.Count(text, "\n")
153}
154
155func appendEscapedEventText(buffer []byte, text string) []byte {
156	for _, b := range []byte(text) {
157		if b != '\n' {
158			buffer = append(buffer, b)
159		} else {
160			buffer = append(buffer, "\\n"...)
161		}
162	}
163	return buffer
164}
165
166func appendEvent(buffer []byte, event Event, globalTags []string) []byte {
167	escapedTextLen := escapedEventTextLen(event.Text)
168
169	buffer = append(buffer, "_e{"...)
170	buffer = strconv.AppendInt(buffer, int64(len(event.Title)), 10)
171	buffer = append(buffer, tagSeparatorSymbol...)
172	buffer = strconv.AppendInt(buffer, int64(escapedTextLen), 10)
173	buffer = append(buffer, "}:"...)
174	buffer = append(buffer, event.Title...)
175	buffer = append(buffer, '|')
176	if escapedTextLen != len(event.Text) {
177		buffer = appendEscapedEventText(buffer, event.Text)
178	} else {
179		buffer = append(buffer, event.Text...)
180	}
181
182	if !event.Timestamp.IsZero() {
183		buffer = append(buffer, "|d:"...)
184		buffer = strconv.AppendInt(buffer, int64(event.Timestamp.Unix()), 10)
185	}
186
187	if len(event.Hostname) != 0 {
188		buffer = append(buffer, "|h:"...)
189		buffer = append(buffer, event.Hostname...)
190	}
191
192	if len(event.AggregationKey) != 0 {
193		buffer = append(buffer, "|k:"...)
194		buffer = append(buffer, event.AggregationKey...)
195	}
196
197	if len(event.Priority) != 0 {
198		buffer = append(buffer, "|p:"...)
199		buffer = append(buffer, event.Priority...)
200	}
201
202	if len(event.SourceTypeName) != 0 {
203		buffer = append(buffer, "|s:"...)
204		buffer = append(buffer, event.SourceTypeName...)
205	}
206
207	if len(event.AlertType) != 0 {
208		buffer = append(buffer, "|t:"...)
209		buffer = append(buffer, string(event.AlertType)...)
210	}
211
212	buffer = appendTags(buffer, globalTags, event.Tags)
213	return buffer
214}
215
216func appendEscapedServiceCheckText(buffer []byte, text string) []byte {
217	for i := 0; i < len(text); i++ {
218		if text[i] == '\n' {
219			buffer = append(buffer, "\\n"...)
220		} else if text[i] == 'm' && i+1 < len(text) && text[i+1] == ':' {
221			buffer = append(buffer, "m\\:"...)
222			i++
223		} else {
224			buffer = append(buffer, text[i])
225		}
226	}
227	return buffer
228}
229
230func appendServiceCheck(buffer []byte, serviceCheck ServiceCheck, globalTags []string) []byte {
231	buffer = append(buffer, "_sc|"...)
232	buffer = append(buffer, serviceCheck.Name...)
233	buffer = append(buffer, '|')
234	buffer = strconv.AppendInt(buffer, int64(serviceCheck.Status), 10)
235
236	if !serviceCheck.Timestamp.IsZero() {
237		buffer = append(buffer, "|d:"...)
238		buffer = strconv.AppendInt(buffer, int64(serviceCheck.Timestamp.Unix()), 10)
239	}
240
241	if len(serviceCheck.Hostname) != 0 {
242		buffer = append(buffer, "|h:"...)
243		buffer = append(buffer, serviceCheck.Hostname...)
244	}
245
246	buffer = appendTags(buffer, globalTags, serviceCheck.Tags)
247
248	if len(serviceCheck.Message) != 0 {
249		buffer = append(buffer, "|m:"...)
250		buffer = appendEscapedServiceCheckText(buffer, serviceCheck.Message)
251	}
252	return buffer
253}
254
255func appendSeparator(buffer []byte) []byte {
256	return append(buffer, '\n')
257}
258