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