diff --git a/plugins/outputs/graphite/graphite.go b/plugins/outputs/graphite/graphite.go index 5e8842629..fc0f97545 100644 --- a/plugins/outputs/graphite/graphite.go +++ b/plugins/outputs/graphite/graphite.go @@ -1,6 +1,7 @@ package graphite import ( + "bytes" "errors" "fmt" "github.com/influxdata/telegraf" @@ -15,10 +16,11 @@ import ( type Graphite struct { // URL is only for backwards compatability - Servers []string - Prefix string - Timeout int - conns []net.Conn + Servers []string + Prefix string + Timeout int + MetricsNameBuilder map[string][]string + conns []net.Conn } var sampleConfig = ` @@ -28,6 +30,11 @@ var sampleConfig = ` prefix = "" # timeout in seconds for the write connection to graphite timeout = 2 + # # Build custom metric name from tags for each plugins + # [graphite.metricsnamebuilder] + # # Igore unlisted tags and put metric name after disk name + # diskio = ["host","{{metric}}","name","{{field}}"] + # disk = ["host","{{metric}}","name","{{field}}"] ` func (g *Graphite) Connect() error { @@ -72,34 +79,16 @@ func (g *Graphite) Write(metrics []telegraf.Metric) error { // Prepare data var bp []string for _, metric := range metrics { - // Get name - name := metric.Name() // Convert UnixNano to Unix timestamps timestamp := metric.UnixNano() / 1000000000 - tag_str := buildTags(metric) - for field_name, value := range metric.Fields() { // Convert value value_str := fmt.Sprintf("%#v", value) // Write graphite metric - var graphitePoint string - if name == field_name { - graphitePoint = fmt.Sprintf("%s.%s %s %d\n", - tag_str, - strings.Replace(name, ".", "_", -1), - value_str, - timestamp) - } else { - graphitePoint = fmt.Sprintf("%s.%s.%s %s %d\n", - tag_str, - strings.Replace(name, ".", "_", -1), - strings.Replace(field_name, ".", "_", -1), - value_str, - timestamp) - } - if g.Prefix != "" { - graphitePoint = fmt.Sprintf("%s.%s", g.Prefix, graphitePoint) - } + graphitePoint := fmt.Sprintf("%s %s %d\n", + g.buildMetricName(metric, field_name), + value_str, + timestamp) bp = append(bp, graphitePoint) } } @@ -107,7 +96,6 @@ func (g *Graphite) Write(metrics []telegraf.Metric) error { // This will get set to nil if a successful write occurs err := errors.New("Could not write to any Graphite server in cluster\n") - // Send data to a random server p := rand.Perm(len(g.conns)) for _, n := range p { @@ -127,36 +115,35 @@ func (g *Graphite) Write(metrics []telegraf.Metric) error { } return err } - -func buildTags(metric telegraf.Metric) string { - var keys []string +func (g *Graphite) buildMetricName(metric telegraf.Metric, fieldName string) string { + metricName := bytes.NewBufferString(g.Prefix) + metricName.WriteString(".") tags := metric.Tags() - for k := range tags { - if k == "host" { + metricsTemplate, ok := g.MetricsNameBuilder[metric.Name()] + if !ok { + metricsTemplate = []string{"host"} + for k := range tags { + if k != "host" { + metricsTemplate = append(metricsTemplate, k) + } + } + sort.Strings(metricsTemplate[1:]) + if metric.Name() != fieldName { + metricsTemplate = append(metricsTemplate, "{{metric}}") + } + metricsTemplate = append(metricsTemplate, "{{field}}") + } + tags["{{metric}}"] = metric.Name() + tags["{{field}}"] = fieldName + for _, tagName := range metricsTemplate { + tagValue, ok := tags[tagName] + if !ok || tagValue == "" { continue } - keys = append(keys, k) + metricName.WriteString(strings.Replace(tagValue, ".", "_", -1)) + metricName.WriteString(".") } - sort.Strings(keys) - - var tag_str string - if host, ok := tags["host"]; ok { - if len(keys) > 0 { - tag_str = strings.Replace(host, ".", "_", -1) + "." - } else { - tag_str = strings.Replace(host, ".", "_", -1) - } - } - - for i, k := range keys { - tag_value := strings.Replace(tags[k], ".", "_", -1) - if i == 0 { - tag_str += tag_value - } else { - tag_str += "." + tag_value - } - } - return tag_str + return strings.Trim(metricName.String(), ".") } func init() { diff --git a/plugins/outputs/graphite/graphite_test.go b/plugins/outputs/graphite/graphite_test.go index 4d8c9f353..6b9570f3f 100644 --- a/plugins/outputs/graphite/graphite_test.go +++ b/plugins/outputs/graphite/graphite_test.go @@ -103,7 +103,7 @@ func TCPServer(t *testing.T, wg *sync.WaitGroup) { wg.Done() } -func TestGraphiteTags(t *testing.T) { +func TestGraphiteMetricName(t *testing.T) { m1, _ := telegraf.NewMetric( "mymeasurement", map[string]string{"host": "192.168.0.1"}, @@ -122,12 +122,34 @@ func TestGraphiteTags(t *testing.T) { map[string]interface{}{"value": float64(3.14)}, time.Date(2010, time.November, 10, 23, 0, 0, 0, time.UTC), ) + m4, _ := telegraf.NewMetric( + "custom1", + map[string]string{"host": "192.168.0.1", "afoo": "first", "bfoo": "second"}, + map[string]interface{}{"value": float64(3.14)}, + time.Date(2010, time.November, 10, 23, 0, 0, 0, time.UTC), + ) + m5, _ := telegraf.NewMetric( + "custom.2", + map[string]string{"host": "192.168.0.1", "afoo": "first", "bfoo": "second"}, + map[string]interface{}{"value": float64(3.14)}, + time.Date(2010, time.November, 10, 23, 0, 0, 0, time.UTC), + ) + g := Graphite{ + Prefix: "my.prefix", + MetricsNameBuilder: map[string][]string{ + "custom1": []string{"host", "afoo", "bfoo"}, + "custom.2": []string{"{{field}}", "bfoo", "{{metric}}"}}, + } + metricName1 := g.buildMetricName(m1, "value") + metricName2 := g.buildMetricName(m2, "value") + metricName3 := g.buildMetricName(m3, "value") + metricName4 := g.buildMetricName(m4, "value") + metricName5 := g.buildMetricName(m5, "value") - tags1 := buildTags(m1) - tags2 := buildTags(m2) - tags3 := buildTags(m3) + assert.Equal(t, "my.prefix.192_168_0_1.mymeasurement.value", metricName1) + assert.Equal(t, "my.prefix.192_168_0_1.first.second.mymeasurement.value", metricName2) + assert.Equal(t, "my.prefix.first.second.mymeasurement.value", metricName3) + assert.Equal(t, "my.prefix.192_168_0_1.first.second", metricName4) + assert.Equal(t, "my.prefix.value.second.custom_2", metricName5) - assert.Equal(t, "192_168_0_1", tags1) - assert.Equal(t, "192_168_0_1.first.second", tags2) - assert.Equal(t, "first.second", tags3) }