diff --git a/CHANGELOG.md b/CHANGELOG.md index 8a054d392021e15fa9ee968c85d277deeb89146b..b65c68c3bca971f635bff028718dc4122ea2d354 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -58,6 +58,7 @@ continue sending logs to /var/log/telegraf/telegraf.log. - [#1854](https://github.com/influxdata/telegraf/pull/1853): SQL Server waitstats truncation bug. - [#1810](https://github.com/influxdata/telegraf/issues/1810): Fix logparser common log format: numbers in ident. - [#1793](https://github.com/influxdata/telegraf/pull/1793): Fix JSON Serialization in OpenTSDB output. +- [#1731](https://github.com/influxdata/telegraf/issues/1731): Fix Graphite template ordering, use most specific. ## v1.0.1 [2016-09-26] diff --git a/docs/DATA_FORMATS_INPUT.md b/docs/DATA_FORMATS_INPUT.md index 2e3a479ac7e9bcca87828f3eea2c96dbf4d248c6..c14752d9cdf81657829c50ba11b7eeb6e82f347a 100644 --- a/docs/DATA_FORMATS_INPUT.md +++ b/docs/DATA_FORMATS_INPUT.md @@ -232,6 +232,16 @@ us.west.cpu.load 100 => cpu.load,region=us.west value=100 ``` +Multiple templates can also be specified, but these should be differentiated +using _filters_ (see below for more details) + +```toml +templates = [ + "*.*.* region.region.measurement", # <- all 3-part measurements will match this one. + "*.*.*.* region.region.host.measurement", # <- all 4-part measurements will match this one. +] +``` + #### Field Templates: The field keyword tells Telegraf to give the metric that field name. diff --git a/plugins/parsers/graphite/parser.go b/plugins/parsers/graphite/parser.go index d371274df974ece147381f8f6db8d445cd9a7128..4a3c21df984952b022e9ebc1546f01a9c767bf86 100644 --- a/plugins/parsers/graphite/parser.go +++ b/plugins/parsers/graphite/parser.go @@ -57,38 +57,34 @@ func NewGraphiteParser( defaultTemplate, _ := NewTemplate("measurement*", nil, p.Separator) matcher.AddDefaultTemplate(defaultTemplate) + tmplts := parsedTemplates{} for _, pattern := range p.Templates { - template := pattern - filter := "" + tmplt := parsedTemplate{} + tmplt.template = pattern // Format is [filter] <template> [tag1=value1,tag2=value2] parts := strings.Fields(pattern) if len(parts) < 1 { continue } else if len(parts) >= 2 { if strings.Contains(parts[1], "=") { - template = parts[0] + tmplt.template = parts[0] + tmplt.tagstring = parts[1] } else { - filter = parts[0] - template = parts[1] - } - } - - // Parse out the default tags specific to this template - tags := map[string]string{} - if strings.Contains(parts[len(parts)-1], "=") { - tagStrs := strings.Split(parts[len(parts)-1], ",") - for _, kv := range tagStrs { - parts := strings.Split(kv, "=") - tags[parts[0]] = parts[1] + tmplt.filter = parts[0] + tmplt.template = parts[1] + if len(parts) > 2 { + tmplt.tagstring = parts[2] + } } } + tmplts = append(tmplts, tmplt) + } - tmpl, err1 := NewTemplate(template, tags, p.Separator) - if err1 != nil { - err = err1 - break + sort.Sort(tmplts) + for _, tmplt := range tmplts { + if err := p.addToMatcher(tmplt); err != nil { + return nil, err } - matcher.Add(filter, tmpl) } if err != nil { @@ -98,6 +94,24 @@ func NewGraphiteParser( } } +func (p *GraphiteParser) addToMatcher(tmplt parsedTemplate) error { + // Parse out the default tags specific to this template + tags := map[string]string{} + if tmplt.tagstring != "" { + for _, kv := range strings.Split(tmplt.tagstring, ",") { + parts := strings.Split(kv, "=") + tags[parts[0]] = parts[1] + } + } + + tmpl, err := NewTemplate(tmplt.template, tags, p.Separator) + if err != nil { + return err + } + p.matcher.Add(tmplt.filter, tmpl) + return nil +} + func (p *GraphiteParser) Parse(buf []byte) ([]telegraf.Metric, error) { // parse even if the buffer begins with a newline buf = bytes.TrimPrefix(buf, []byte("\n")) @@ -465,3 +479,30 @@ func (n *nodes) Less(j, k int) bool { func (n *nodes) Swap(i, j int) { (*n)[i], (*n)[j] = (*n)[j], (*n)[i] } func (n *nodes) Len() int { return len(*n) } + +type parsedTemplate struct { + template string + filter string + tagstring string +} +type parsedTemplates []parsedTemplate + +func (e parsedTemplates) Less(j, k int) bool { + if len(e[j].filter) == 0 && len(e[k].filter) == 0 { + nj := len(strings.Split(e[j].template, ".")) + nk := len(strings.Split(e[k].template, ".")) + return nj < nk + } + if len(e[j].filter) == 0 { + return true + } + if len(e[k].filter) == 0 { + return false + } + + nj := len(strings.Split(e[j].template, ".")) + nk := len(strings.Split(e[k].template, ".")) + return nj < nk +} +func (e parsedTemplates) Swap(i, j int) { e[i], e[j] = e[j], e[i] } +func (e parsedTemplates) Len() int { return len(e) } diff --git a/plugins/parsers/graphite/parser_test.go b/plugins/parsers/graphite/parser_test.go index 55f1a9e2b81291d72fb9041d6299d2c4ef557e2f..9665c2c4615996f5fc98672df914518586c38d56 100644 --- a/plugins/parsers/graphite/parser_test.go +++ b/plugins/parsers/graphite/parser_test.go @@ -747,6 +747,48 @@ func TestApplyTemplateGreedyField(t *testing.T) { } } +func TestApplyTemplateOverSpecific(t *testing.T) { + p, err := NewGraphiteParser( + ".", + []string{ + "measurement.host.metric.metric.metric", + }, + nil, + ) + assert.NoError(t, err) + + measurement, tags, _, err := p.ApplyTemplate("net.server001.a.b 2") + assert.Equal(t, "net", measurement) + assert.Equal(t, + map[string]string{"host": "server001", "metric": "a.b"}, + tags) +} + +func TestApplyTemplateMostSpecificTemplate(t *testing.T) { + p, err := NewGraphiteParser( + ".", + []string{ + "measurement.host.metric", + "measurement.host.metric.metric.metric", + "measurement.host.metric.metric", + }, + nil, + ) + assert.NoError(t, err) + + measurement, tags, _, err := p.ApplyTemplate("net.server001.a.b.c 2") + assert.Equal(t, "net", measurement) + assert.Equal(t, + map[string]string{"host": "server001", "metric": "a.b.c"}, + tags) + + measurement, tags, _, err = p.ApplyTemplate("net.server001.a.b 2") + assert.Equal(t, "net", measurement) + assert.Equal(t, + map[string]string{"host": "server001", "metric": "a.b"}, + tags) +} + // Test Helpers func errstr(err error) string { if err != nil {