DogStatsD: review changes

This commit is contained in:
Ilya Margolin 2016-04-23 23:50:41 +02:00 committed by Ilya Margolin
parent 8f36baf045
commit 0b792e0be6
3 changed files with 80 additions and 30 deletions

View file

@ -26,6 +26,16 @@ We recommend this only as an intermediate solution and recommend switching to
[native Prometheus instrumentation](http://prometheus.io/docs/instrumenting/clientlibs/) [native Prometheus instrumentation](http://prometheus.io/docs/instrumenting/clientlibs/)
in the long term. in the long term.
### DogStatsD extensions
The exporter will convert DogStatsD-style tags to prometheus labels. See
[Tags](http://docs.datadoghq.com/guides/dogstatsd/#tags) in the DogStatsD
documentation for the concept description and
[Datagram Format](http://docs.datadoghq.com/guides/dogstatsd/#datagram-format)
for specifics. It boils down to appending
`|#tag:value,another_tag:another_value` to the normal StatsD format. Tags
without values (`#some_tag`) are not supported.
## Building and Running ## Building and Running
$ go build $ go build

View file

@ -58,42 +58,82 @@ func TestHandlePacket(t *testing.T) {
}, },
}, { }, {
name: "datadog tag extension", name: "datadog tag extension",
in: "foo:100|c|#tag1:bar,tag2:baz,tag3,tag4", in: "foo:100|c|#tag1:bar,tag2:baz",
out: Events{ out: Events{
&CounterEvent{ &CounterEvent{
metricName: "foo", metricName: "foo",
value: 100, value: 100,
labels: map[string]string{"tag1": "bar", "tag2": "baz", "tag3": ".", "tag4": "."}, labels: map[string]string{"tag1": "bar", "tag2": "baz"},
}, },
}, },
}, { }, {
name: "datadog tag extension with # in all keys (as sent by datadog php client)", name: "datadog tag extension with # in all keys (as sent by datadog php client)",
in: "foo:100|c|#tag1:bar,#tag2:baz,#tag3,#tag4", in: "foo:100|c|#tag1:bar,#tag2:baz",
out: Events{ out: Events{
&CounterEvent{ &CounterEvent{
metricName: "foo", metricName: "foo",
value: 100, value: 100,
labels: map[string]string{"tag1": "bar", "tag2": "baz", "tag3": ".", "tag4": "."}, labels: map[string]string{"tag1": "bar", "tag2": "baz"},
}, },
}, },
}, { }, {
name: "datadog tag extension with tags unsupported by prometheus", name: "datadog tag extension with tag keys unsupported by prometheus",
in: "foo:100|c|#09digits:0,tag.with.dots,tag_with_empty_value:", in: "foo:100|c|#09digits:0,tag.with.dots:1",
out: Events{ out: Events{
&CounterEvent{ &CounterEvent{
metricName: "foo", metricName: "foo",
value: 100, value: 100,
labels: map[string]string{"_09digits": "0", "tag_with_dots": ".", "tag_with_empty_value": "."}, labels: map[string]string{"_09digits": "0", "tag_with_dots": "1"},
},
},
}, {
name: "datadog tag extension with valueless tags: ignored",
in: "foo:100|c|#tag_without_a_value",
out: Events{
&CounterEvent{
metricName: "foo",
value: 100,
labels: map[string]string{},
},
},
}, {
name: "datadog tag extension with valueless tags (edge case)",
in: "foo:100|c|#tag_without_a_value,tag:value",
out: Events{
&CounterEvent{
metricName: "foo",
value: 100,
labels: map[string]string{"tag": "value"},
},
},
}, {
name: "datadog tag extension with empty tags (edge case)",
in: "foo:100|c|#tag:value,,",
out: Events{
&CounterEvent{
metricName: "foo",
value: 100,
labels: map[string]string{"tag": "value"},
}, },
}, },
}, { }, {
name: "datadog tag extension with sampling", name: "datadog tag extension with sampling",
in: "foo:100|c|@0.1|#tag1:bar,tag2,tag3:baz", in: "foo:100|c|@0.1|#tag1:bar,#tag2:baz",
out: Events{ out: Events{
&CounterEvent{ &CounterEvent{
metricName: "foo", metricName: "foo",
value: 1000, value: 1000,
labels: map[string]string{"tag1": "bar", "tag2": ".", "tag3": "baz"}, labels: map[string]string{"tag1": "bar", "tag2": "baz"},
},
},
}, {
name: "datadog tag extension with multiple colons",
in: "foo:100|c|@0.1|#tag1:foo:bar",
out: Events{
&CounterEvent{
metricName: "foo",
value: 1000,
labels: map[string]string{"tag1": "foo:bar"},
}, },
}, },
}, { }, {

View file

@ -303,6 +303,25 @@ func (l *StatsDListener) Listen(e chan<- Events) {
} }
} }
func parseDogStatsDTagsToLabels(component string) map[string]string {
labels := map[string]string{}
networkStats.WithLabelValues("dogstatsd_tags").Inc()
tags := strings.Split(component, ",")
for _, t := range tags {
t = strings.TrimPrefix(t, "#")
kv := strings.SplitN(t, ":", 2)
if len(kv) < 2 || len(kv[1]) == 0 {
networkStats.WithLabelValues("malformed_dogstatsd_tag").Inc()
log.Printf("Malformed or empty DogStatsD tag %s in component %s", t, component)
continue
}
labels[escapeMetricName(kv[0])] = kv[1]
}
return labels
}
func (l *StatsDListener) handlePacket(packet []byte, e chan<- Events) { func (l *StatsDListener) handlePacket(packet []byte, e chan<- Events) {
lines := strings.Split(string(packet), "\n") lines := strings.Split(string(packet), "\n")
events := Events{} events := Events{}
@ -334,7 +353,6 @@ func (l *StatsDListener) handlePacket(packet []byte, e chan<- Events) {
continue continue
} }
valueStr, statType := components[0], components[1] valueStr, statType := components[0], components[1]
labels := map[string]string{}
value, err := strconv.ParseFloat(valueStr, 64) value, err := strconv.ParseFloat(valueStr, 64)
if err != nil { if err != nil {
log.Printf("Bad value %s on line: %s", valueStr, line) log.Printf("Bad value %s on line: %s", valueStr, line)
@ -342,6 +360,7 @@ func (l *StatsDListener) handlePacket(packet []byte, e chan<- Events) {
continue continue
} }
labels := map[string]string{}
if len(components) >= 3 { if len(components) >= 3 {
for _, component := range components[2:] { for _, component := range components[2:] {
switch component[0] { switch component[0] {
@ -360,26 +379,7 @@ func (l *StatsDListener) handlePacket(packet []byte, e chan<- Events) {
} }
value /= samplingFactor value /= samplingFactor
case '#': case '#':
networkStats.WithLabelValues("dogstatsd_tags").Inc() labels = parseDogStatsDTagsToLabels(component)
tags := strings.Split(component, ",")
for _, t := range tags {
t = strings.TrimPrefix(t, "#")
kv := strings.Split(t, ":")
tag_key := kv[0]
tag_key = escapeMetricName(tag_key)
var tag_value string
if len(kv) == 2 {
if len(kv[1]) > 0 {
tag_value = kv[1]
} else {
tag_value = "."
}
} else if len(kv) == 1 {
tag_value = "."
}
labels[tag_key] = tag_value
}
default: default:
log.Printf("Invalid sampling factor or tag section %s on line %s", components[2], line) log.Printf("Invalid sampling factor or tag section %s on line %s", components[2], line)
networkStats.WithLabelValues("invalid_sample_factor").Inc() networkStats.WithLabelValues("invalid_sample_factor").Inc()