Support for datadog style statsd tags

See:
http://docs.datadoghq.com/guides/dogstatsd/#datagram-format
This commit is contained in:
James Haggerty 2016-01-29 00:39:42 +00:00
parent f088dd7e00
commit edf313dc75
1 changed files with 48 additions and 17 deletions

View File

@ -39,6 +39,7 @@ type Statsd struct {
DeleteSets bool DeleteSets bool
DeleteTimings bool DeleteTimings bool
ConvertNames bool ConvertNames bool
DatadogStyle bool
// UDPPacketSize is the size of the read packets for the server listening // UDPPacketSize is the size of the read packets for the server listening
// for statsd UDP packets. This will default to 1500 bytes. // for statsd UDP packets. This will default to 1500 bytes.
@ -151,6 +152,11 @@ const sampleConfig = `
# UDP packet size for the server to listen for. This will depend on the size # UDP packet size for the server to listen for. This will depend on the size
# of the packets that the client is sending, which is usually 1500 bytes. # of the packets that the client is sending, which is usually 1500 bytes.
udp_packet_size = 1500 udp_packet_size = 1500
# Accept datadog style packets
# (i.e. tags like x:1|c|#tag:value rather than x,tag=value:1|c)
# (this means you can only send a single metric at a time)
datadog_style = false
` `
func (_ *Statsd) SampleConfig() string { func (_ *Statsd) SampleConfig() string {
@ -275,8 +281,15 @@ func (s *Statsd) parseStatsdLine(line string) error {
s.Lock() s.Lock()
defer s.Unlock() defer s.Unlock()
// Validate splitting the line on ":" var bits []string
bits := strings.Split(line, ":") if (s.DatadogStyle) {
// if it's a datadog style metric, we can't cope with more than one metric
// because datadog uses ':' to split tags.
bits = strings.SplitN(line, ":", 2)
} else {
bits = strings.Split(line, ":")
}
if len(bits) < 2 { if len(bits) < 2 {
log.Printf("Error: splitting ':', Unable to parse metric: %s\n", line) log.Printf("Error: splitting ':', Unable to parse metric: %s\n", line)
return errors.New("Error Parsing statsd line") return errors.New("Error Parsing statsd line")
@ -291,25 +304,45 @@ func (s *Statsd) parseStatsdLine(line string) error {
m.bucket = bucketName m.bucket = bucketName
// Parse the name & tags from bucket
m.name, m.tags = s.parseName(m.bucket)
// Validate splitting the bit on "|" // Validate splitting the bit on "|"
pipesplit := strings.Split(bit, "|") pipesplit := strings.Split(bit, "|")
if len(pipesplit) < 2 { if len(pipesplit) < 2 {
log.Printf("Error: splitting '|', Unable to parse metric: %s\n", line) log.Printf("Error: splitting '|', Unable to parse metric: %s\n", line)
return errors.New("Error Parsing statsd line") return errors.New("Error Parsing statsd line")
} else if len(pipesplit) > 2 { }
sr := pipesplit[2]
errmsg := "Error: parsing sample rate, %s, it must be in format like: " + for _, extra := range pipesplit[2:] {
"@0.1, @0.5, etc. Ignoring sample rate for line: %s\n" extratype, extravalue := extra[:1], extra[1:]
if strings.Contains(sr, "@") && len(sr) > 1 { if extravalue == "" {
samplerate, err := strconv.ParseFloat(sr[1:], 64) log.Printf("Error: Missing content for %s on line: %s", extratype, line)
continue
}
switch extratype {
case "#":
if (!s.DatadogStyle) {
log.Printf("Error: Datadog style tag ignored in line: %s", line)
break
}
for _, tag := range strings.Split(extravalue, ",") {
k, v := parseKeyValue(tag, ":")
if k != "" {
m.tags[k] = v
}
}
case "@":
errmsg := "Error: parsing sample rate, %s, it must be in format: " +
"like: @0.1, @0.5, etc. Ignoring sample rate for line: %s\n"
samplerate, err := strconv.ParseFloat(extravalue, 64)
if err != nil { if err != nil {
log.Printf(errmsg, err.Error(), line) log.Printf(errmsg, err.Error(), line)
} else { } else {
// sample rate successfully parsed // sample rate successfully parsed
m.samplerate = samplerate m.samplerate = samplerate
} }
} else {
log.Printf(errmsg, "", line)
} }
} }
@ -357,8 +390,6 @@ func (s *Statsd) parseStatsdLine(line string) error {
m.intvalue = v m.intvalue = v
} }
// Parse the name & tags from bucket
m.name, m.tags = s.parseName(m.bucket)
switch m.mtype { switch m.mtype {
case "c": case "c":
m.tags["metric_type"] = "counter" m.tags["metric_type"] = "counter"
@ -397,7 +428,7 @@ func (s *Statsd) parseName(bucket string) (string, map[string]string) {
// Parse out any tags in the bucket // Parse out any tags in the bucket
if len(bucketparts) > 1 { if len(bucketparts) > 1 {
for _, btag := range bucketparts[1:] { for _, btag := range bucketparts[1:] {
k, v := parseKeyValue(btag) k, v := parseKeyValue(btag, "=")
if k != "" { if k != "" {
tags[k] = v tags[k] = v
} }
@ -424,10 +455,10 @@ func (s *Statsd) parseName(bucket string) (string, map[string]string) {
} }
// Parse the key,value out of a string that looks like "key=value" // Parse the key,value out of a string that looks like "key=value"
func parseKeyValue(keyvalue string) (string, string) { func parseKeyValue(keyvalue string, separator string) (string, string) {
var key, val string var key, val string
split := strings.Split(keyvalue, "=") split := strings.Split(keyvalue, separator)
// Must be exactly 2 to get anything meaningful out of them // Must be exactly 2 to get anything meaningful out of them
if len(split) == 2 { if len(split) == 2 {
key = split[0] key = split[0]