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
DeleteTimings bool
ConvertNames bool
DatadogStyle bool
// UDPPacketSize is the size of the read packets for the server listening
// 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
# of the packets that the client is sending, which is usually 1500 bytes.
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 {
@ -275,15 +281,22 @@ func (s *Statsd) parseStatsdLine(line string) error {
s.Lock()
defer s.Unlock()
// Validate splitting the line on ":"
bits := strings.Split(line, ":")
var bits []string
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 {
log.Printf("Error: splitting ':', Unable to parse metric: %s\n", line)
return errors.New("Error Parsing statsd line")
}
// Extract bucket name from individual metric bits
bucketName, bits := bits[0], bits[1:]
// Extract bucket name from individual metric bits
bucketName, bits := bits[0], bits[1:]
// Add a metric for each bit available
for _, bit := range bits {
@ -291,25 +304,45 @@ func (s *Statsd) parseStatsdLine(line string) error {
m.bucket = bucketName
// Parse the name & tags from bucket
m.name, m.tags = s.parseName(m.bucket)
// Validate splitting the bit on "|"
pipesplit := strings.Split(bit, "|")
if len(pipesplit) < 2 {
log.Printf("Error: splitting '|', Unable to parse metric: %s\n", 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: " +
"@0.1, @0.5, etc. Ignoring sample rate for line: %s\n"
if strings.Contains(sr, "@") && len(sr) > 1 {
samplerate, err := strconv.ParseFloat(sr[1:], 64)
}
for _, extra := range pipesplit[2:] {
extratype, extravalue := extra[:1], extra[1:]
if extravalue == "" {
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 {
log.Printf(errmsg, err.Error(), line)
} else {
// sample rate successfully parsed
m.samplerate = samplerate
}
} else {
log.Printf(errmsg, "", line)
}
}
@ -357,8 +390,6 @@ func (s *Statsd) parseStatsdLine(line string) error {
m.intvalue = v
}
// Parse the name & tags from bucket
m.name, m.tags = s.parseName(m.bucket)
switch m.mtype {
case "c":
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
if len(bucketparts) > 1 {
for _, btag := range bucketparts[1:] {
k, v := parseKeyValue(btag)
k, v := parseKeyValue(btag, "=")
if k != "" {
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"
func parseKeyValue(keyvalue string) (string, string) {
func parseKeyValue(keyvalue string, separator string) (string, 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
if len(split) == 2 {
key = split[0]