Skip to content

Commit 333f01c

Browse files
authored
Merge pull request #668 from kamaev/master
Send Graphite metrics with tags
2 parents 49d8fa7 + 06342cf commit 333f01c

File tree

2 files changed

+183
-27
lines changed

2 files changed

+183
-27
lines changed

prometheus/graphite/bridge.go

Lines changed: 46 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,9 @@ const (
5454

5555
// Config defines the Graphite bridge config.
5656
type Config struct {
57+
// Whether to use Graphite tags or not. Defaults to false.
58+
UseTags bool
59+
5760
// The url to push data to. Required.
5861
URL string
5962

@@ -80,6 +83,7 @@ type Config struct {
8083

8184
// Bridge pushes metrics to the configured Graphite server.
8285
type Bridge struct {
86+
useTags bool
8387
url string
8488
prefix string
8589
interval time.Duration
@@ -102,6 +106,8 @@ type Logger interface {
102106
func NewBridge(c *Config) (*Bridge, error) {
103107
b := &Bridge{}
104108

109+
b.useTags = c.UseTags
110+
105111
if c.URL == "" {
106112
return nil, errors.New("missing URL")
107113
}
@@ -178,10 +184,10 @@ func (b *Bridge) Push() error {
178184
}
179185
defer conn.Close()
180186

181-
return writeMetrics(conn, mfs, b.prefix, model.Now())
187+
return writeMetrics(conn, mfs, b.useTags, b.prefix, model.Now())
182188
}
183189

184-
func writeMetrics(w io.Writer, mfs []*dto.MetricFamily, prefix string, now model.Time) error {
190+
func writeMetrics(w io.Writer, mfs []*dto.MetricFamily, useTags bool, prefix string, now model.Time) error {
185191
vec, err := expfmt.ExtractSamples(&expfmt.DecodeOptions{
186192
Timestamp: now,
187193
}, mfs...)
@@ -199,7 +205,7 @@ func writeMetrics(w io.Writer, mfs []*dto.MetricFamily, prefix string, now model
199205
if err := buf.WriteByte('.'); err != nil {
200206
return err
201207
}
202-
if err := writeMetric(buf, s.Metric); err != nil {
208+
if err := writeMetric(buf, s.Metric, useTags); err != nil {
203209
return err
204210
}
205211
if _, err := fmt.Fprintf(buf, " %g %d\n", s.Value, int64(s.Timestamp)/millisecondsPerSecond); err != nil {
@@ -213,43 +219,68 @@ func writeMetrics(w io.Writer, mfs []*dto.MetricFamily, prefix string, now model
213219
return nil
214220
}
215221

216-
func writeMetric(buf *bufio.Writer, m model.Metric) error {
222+
func writeMetric(buf *bufio.Writer, m model.Metric, useTags bool) error {
217223
metricName, hasName := m[model.MetricNameLabel]
218224
numLabels := len(m) - 1
219225
if !hasName {
220226
numLabels = len(m)
221227
}
222228

223-
labelStrings := make([]string, 0, numLabels)
224-
for label, value := range m {
225-
if label != model.MetricNameLabel {
226-
labelStrings = append(labelStrings, fmt.Sprintf("%s %s", string(label), string(value)))
227-
}
228-
}
229-
230229
var err error
231230
switch numLabels {
232231
case 0:
233232
if hasName {
234233
return writeSanitized(buf, string(metricName))
235234
}
236235
default:
237-
sort.Strings(labelStrings)
238236
if err = writeSanitized(buf, string(metricName)); err != nil {
239237
return err
240238
}
241-
for _, s := range labelStrings {
242-
if err = buf.WriteByte('.'); err != nil {
239+
if useTags {
240+
return writeTags(buf, m)
241+
} else {
242+
return writeLabels(buf, m, numLabels)
243+
}
244+
}
245+
return nil
246+
}
247+
248+
func writeTags(buf *bufio.Writer, m model.Metric) error {
249+
for label, value := range m {
250+
if label != model.MetricNameLabel {
251+
buf.WriteRune(';')
252+
if _, err := buf.WriteString(string(label)); err != nil {
243253
return err
244254
}
245-
if err = writeSanitized(buf, s); err != nil {
255+
buf.WriteRune('=')
256+
if _, err := buf.WriteString(string(value)); err != nil {
246257
return err
247258
}
248259
}
249260
}
250261
return nil
251262
}
252263

264+
func writeLabels(buf *bufio.Writer, m model.Metric, numLabels int) error {
265+
labelStrings := make([]string, 0, numLabels)
266+
for label, value := range m {
267+
if label != model.MetricNameLabel {
268+
labelString := string(label) + " " + string(value)
269+
labelStrings = append(labelStrings, labelString)
270+
}
271+
}
272+
sort.Strings(labelStrings)
273+
for _, s := range labelStrings {
274+
if err := buf.WriteByte('.'); err != nil {
275+
return err
276+
}
277+
if err := writeSanitized(buf, s); err != nil {
278+
return err
279+
}
280+
}
281+
return nil
282+
}
283+
253284
func writeSanitized(buf *bufio.Writer, s string) error {
254285
prevUnderscore := false
255286

prometheus/graphite/bridge_test.go

Lines changed: 137 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,10 @@ import (
2222
"log"
2323
"net"
2424
"os"
25+
"reflect"
2526
"regexp"
27+
"sort"
28+
"strings"
2629
"testing"
2730
"time"
2831

@@ -62,6 +65,11 @@ func TestSanitize(t *testing.T) {
6265
}
6366

6467
func TestWriteSummary(t *testing.T) {
68+
testWriteSummary(t, false)
69+
testWriteSummary(t, true)
70+
}
71+
72+
func testWriteSummary(t *testing.T, useTags bool) {
6573
sumVec := prometheus.NewSummaryVec(
6674
prometheus.SummaryOpts{
6775
Name: "name",
@@ -95,7 +103,8 @@ func TestWriteSummary(t *testing.T) {
95103
{prefix: "pre.fix"},
96104
}
97105

98-
const want = `%s.name.constname.constvalue.labelname.val1.quantile.0_5 20 1477043
106+
var (
107+
want = `%s.name.constname.constvalue.labelname.val1.quantile.0_5 20 1477043
99108
%s.name.constname.constvalue.labelname.val1.quantile.0_9 30 1477043
100109
%s.name.constname.constvalue.labelname.val1.quantile.0_99 30 1477043
101110
%s.name_sum.constname.constvalue.labelname.val1 60 1477043
@@ -106,11 +115,28 @@ func TestWriteSummary(t *testing.T) {
106115
%s.name_sum.constname.constvalue.labelname.val2 90 1477043
107116
%s.name_count.constname.constvalue.labelname.val2 3 1477043
108117
`
118+
wantTagged = `%s.name;constname=constvalue;labelname=val1;quantile=0.5 20 1477043
119+
%s.name;constname=constvalue;labelname=val1;quantile=0.9 30 1477043
120+
%s.name;constname=constvalue;labelname=val1;quantile=0.99 30 1477043
121+
%s.name_sum;constname=constvalue;labelname=val1 60 1477043
122+
%s.name_count;constname=constvalue;labelname=val1 3 1477043
123+
%s.name;constname=constvalue;labelname=val2;quantile=0.5 30 1477043
124+
%s.name;constname=constvalue;labelname=val2;quantile=0.9 40 1477043
125+
%s.name;constname=constvalue;labelname=val2;quantile=0.99 40 1477043
126+
%s.name_sum;constname=constvalue;labelname=val2 90 1477043
127+
%s.name_count;constname=constvalue;labelname=val2 3 1477043
128+
`
129+
)
130+
131+
if useTags {
132+
want = wantTagged
133+
}
134+
109135
for i, tc := range testCases {
110136

111137
now := model.Time(1477043083)
112138
var buf bytes.Buffer
113-
err = writeMetrics(&buf, mfs, tc.prefix, now)
139+
err = writeMetrics(&buf, mfs, useTags, tc.prefix, now)
114140
if err != nil {
115141
t.Fatalf("error: %v", err)
116142
}
@@ -119,13 +145,21 @@ func TestWriteSummary(t *testing.T) {
119145
tc.prefix, tc.prefix, tc.prefix, tc.prefix, tc.prefix,
120146
tc.prefix, tc.prefix, tc.prefix, tc.prefix, tc.prefix,
121147
)
122-
if got := buf.String(); wantWithPrefix != got {
123-
t.Fatalf("test case index %d: wanted \n%s\n, got \n%s\n", i, wantWithPrefix, got)
148+
149+
got := buf.String()
150+
151+
if err := checkLinesAreEqual(wantWithPrefix, got, useTags); err != nil {
152+
t.Fatalf("test case index %d:\n%s", i, err.Error())
124153
}
125154
}
126155
}
127156

128157
func TestWriteHistogram(t *testing.T) {
158+
testWriteHistogram(t, false)
159+
testWriteHistogram(t, true)
160+
}
161+
162+
func testWriteHistogram(t *testing.T, useTags bool) {
129163
histVec := prometheus.NewHistogramVec(
130164
prometheus.HistogramOpts{
131165
Name: "name",
@@ -153,12 +187,13 @@ func TestWriteHistogram(t *testing.T) {
153187

154188
now := model.Time(1477043083)
155189
var buf bytes.Buffer
156-
err = writeMetrics(&buf, mfs, "prefix", now)
190+
err = writeMetrics(&buf, mfs, useTags, "prefix", now)
157191
if err != nil {
158192
t.Fatalf("error: %v", err)
159193
}
160194

161-
want := `prefix.name_bucket.constname.constvalue.labelname.val1.le.0_01 0 1477043
195+
var (
196+
want = `prefix.name_bucket.constname.constvalue.labelname.val1.le.0_01 0 1477043
162197
prefix.name_bucket.constname.constvalue.labelname.val1.le.0_02 0 1477043
163198
prefix.name_bucket.constname.constvalue.labelname.val1.le.0_05 0 1477043
164199
prefix.name_bucket.constname.constvalue.labelname.val1.le.0_1 0 1477043
@@ -173,12 +208,40 @@ prefix.name_sum.constname.constvalue.labelname.val2 90 1477043
173208
prefix.name_count.constname.constvalue.labelname.val2 3 1477043
174209
prefix.name_bucket.constname.constvalue.labelname.val2.le._Inf 3 1477043
175210
`
176-
if got := buf.String(); want != got {
177-
t.Fatalf("wanted \n%s\n, got \n%s\n", want, got)
211+
wantTagged = `prefix.name_bucket;constname=constvalue;labelname=val1;le=0.01 0 1477043
212+
prefix.name_bucket;constname=constvalue;labelname=val1;le=0.02 0 1477043
213+
prefix.name_bucket;constname=constvalue;labelname=val1;le=0.05 0 1477043
214+
prefix.name_bucket;constname=constvalue;labelname=val1;le=0.1 0 1477043
215+
prefix.name_sum;constname=constvalue;labelname=val1 60 1477043
216+
prefix.name_count;constname=constvalue;labelname=val1 3 1477043
217+
prefix.name_bucket;constname=constvalue;labelname=val1;le=+Inf 3 1477043
218+
prefix.name_bucket;constname=constvalue;labelname=val2;le=0.01 0 1477043
219+
prefix.name_bucket;constname=constvalue;labelname=val2;le=0.02 0 1477043
220+
prefix.name_bucket;constname=constvalue;labelname=val2;le=0.05 0 1477043
221+
prefix.name_bucket;constname=constvalue;labelname=val2;le=0.1 0 1477043
222+
prefix.name_sum;constname=constvalue;labelname=val2 90 1477043
223+
prefix.name_count;constname=constvalue;labelname=val2 3 1477043
224+
prefix.name_bucket;constname=constvalue;labelname=val2;le=+Inf 3 1477043
225+
`
226+
)
227+
228+
if useTags {
229+
want = wantTagged
230+
}
231+
232+
got := buf.String()
233+
234+
if err := checkLinesAreEqual(want, got, useTags); err != nil {
235+
t.Fatalf(err.Error())
178236
}
179237
}
180238

181239
func TestToReader(t *testing.T) {
240+
testToReader(t, false)
241+
testToReader(t, true)
242+
}
243+
244+
func testToReader(t *testing.T, useTags bool) {
182245
cntVec := prometheus.NewCounterVec(
183246
prometheus.CounterOpts{
184247
Name: "name",
@@ -193,24 +256,86 @@ func TestToReader(t *testing.T) {
193256
reg := prometheus.NewRegistry()
194257
reg.MustRegister(cntVec)
195258

196-
want := `prefix.name.constname.constvalue.labelname.val1 1 1477043
259+
var (
260+
want = `prefix.name.constname.constvalue.labelname.val1 1 1477043
197261
prefix.name.constname.constvalue.labelname.val2 1 1477043
198262
`
263+
wantTagged = `prefix.name;constname=constvalue;labelname=val1 1 1477043
264+
prefix.name;constname=constvalue;labelname=val2 1 1477043
265+
`
266+
)
267+
268+
if useTags {
269+
want = wantTagged
270+
}
271+
199272
mfs, err := reg.Gather()
200273
if err != nil {
201274
t.Fatalf("error: %v", err)
202275
}
203276

204277
now := model.Time(1477043083)
205278
var buf bytes.Buffer
206-
err = writeMetrics(&buf, mfs, "prefix", now)
279+
err = writeMetrics(&buf, mfs, useTags, "prefix", now)
207280
if err != nil {
208281
t.Fatalf("error: %v", err)
209282
}
210283

211-
if got := buf.String(); want != got {
212-
t.Fatalf("wanted \n%s\n, got \n%s\n", want, got)
284+
got := buf.String()
285+
286+
if err := checkLinesAreEqual(want, got, useTags); err != nil {
287+
t.Fatalf(err.Error())
288+
}
289+
}
290+
291+
func checkLinesAreEqual(w, g string, useTags bool) error {
292+
if useTags {
293+
taggedLineRegexp := regexp.MustCompile(`;| `)
294+
295+
wantLines, err := stringToLines(w)
296+
if err != nil {
297+
return err
298+
}
299+
300+
gotLines, err := stringToLines(g)
301+
if err != nil {
302+
return err
303+
}
304+
305+
for lineInd := range gotLines {
306+
var log string
307+
// Tagged metric, order of tags doesn't matter
308+
// m1 := "prefix.name;tag1=val1;tag2=val2 3 1477043"
309+
// m2 := "prefix.name;tag2=val2;tag1=val1 3 1477043"
310+
// m1 should be equal to m2
311+
wantSplit := taggedLineRegexp.Split(wantLines[lineInd], -1)
312+
gotSplit := taggedLineRegexp.Split(gotLines[lineInd], -1)
313+
sort.Strings(wantSplit)
314+
sort.Strings(gotSplit)
315+
316+
log += fmt.Sprintf("want: %v\ngot: %v\n\n", wantSplit, gotSplit)
317+
318+
if !reflect.DeepEqual(wantSplit, gotSplit) {
319+
return fmt.Errorf(log)
320+
}
321+
}
322+
return nil
323+
}
324+
325+
if w != g {
326+
return fmt.Errorf("wanted:\n\n%s\ngot:\n\n%s", w, g)
327+
}
328+
329+
return nil
330+
}
331+
332+
func stringToLines(s string) (lines []string, err error) {
333+
scanner := bufio.NewScanner(strings.NewReader(s))
334+
for scanner.Scan() {
335+
lines = append(lines, scanner.Text())
213336
}
337+
err = scanner.Err()
338+
return
214339
}
215340

216341
func TestPush(t *testing.T) {

0 commit comments

Comments
 (0)