diff --git a/README.md b/README.md index 6307b5356f191..a4bdcc1c08dab 100644 --- a/README.md +++ b/README.md @@ -278,6 +278,7 @@ formats may be used with input plugins supporting the `data_format` option: * [override](./plugins/processors/override) * [printer](./plugins/processors/printer) * [regex](./plugins/processors/regex) +* [rename](./plugins/processors/rename) * [topk](./plugins/processors/topk) ## Aggregator Plugins diff --git a/plugins/processors/all/all.go b/plugins/processors/all/all.go index c06bbd426fc90..f581ea6026c74 100644 --- a/plugins/processors/all/all.go +++ b/plugins/processors/all/all.go @@ -6,5 +6,6 @@ import ( _ "github.com/influxdata/telegraf/plugins/processors/override" _ "github.com/influxdata/telegraf/plugins/processors/printer" _ "github.com/influxdata/telegraf/plugins/processors/regex" + _ "github.com/influxdata/telegraf/plugins/processors/rename" _ "github.com/influxdata/telegraf/plugins/processors/topk" ) diff --git a/plugins/processors/rename/README.md b/plugins/processors/rename/README.md new file mode 100644 index 0000000000000..dbd31490e41c9 --- /dev/null +++ b/plugins/processors/rename/README.md @@ -0,0 +1,41 @@ +# Rename Processor Plugin + +The `rename` processor renames measurements, fields, and tags. + +### Configuration: + +```toml +## Measurement, tag, and field renamings are stored in separate sub-tables. +## Specify one sub-table per rename operation. +[[processors.rename]] +[[processors.rename.measurement]] + ## measurement to change + from = "network_interface_throughput" + to = "throughput" + +[[processors.rename.tag]] + ## tag to change + from = "hostname" + to = "host" + +[[processors.rename.field]] + ## field to change + from = "lower" + to = "min" + +[[processors.rename.field]] + ## field to change + from = "upper" + to = "max" +``` + +### Tags: + +No tags are applied by this processor, though it can alter them by renaming. + +### Example processing: + +```diff +- network_interface_throughput,hostname=backend.example.com,units=kbps lower=10i,upper=1000i,mean=500i 1502489900000000000 ++ throughput,host=backend.example.com,units=kbps min=10i,max=1000i,mean=500i 1502489900000000000 +``` diff --git a/plugins/processors/rename/rename.go b/plugins/processors/rename/rename.go new file mode 100644 index 0000000000000..a86f7eea523cc --- /dev/null +++ b/plugins/processors/rename/rename.go @@ -0,0 +1,101 @@ +package rename + +import ( + "github.com/influxdata/telegraf" + "github.com/influxdata/telegraf/plugins/processors" + "sync" +) + +const sampleConfig = ` + ## Measurement, tag, and field renamings are stored in separate sub-tables. + ## Specify one sub-table per rename operation. + # [[processors.rename.measurement]] + # ## measurement to change + # from = "kilobytes_per_second" + # to = "kbps" + + # [[processors.rename.tag]] + # ## tag to change + # from = "host" + # to = "hostname" + + # [[processors.rename.field]] + # ## field to change + # from = "lower" + # to = "min" + + # [[processors.rename.field]] + # ## field to change + # from = "upper" + # to = "max" +` + +type renamer struct { + From string + To string +} + +type Rename struct { + Measurement []renamer + Tag []renamer + Field []renamer + measurements map[string]string + tags map[string]string + fields map[string]string + once sync.Once +} + +func (r *Rename) SampleConfig() string { + return sampleConfig +} + +func (r *Rename) Description() string { + return "Rename measurements, tags, and fields that pass through this filter." +} + +func (r *Rename) Apply(in ...telegraf.Metric) []telegraf.Metric { + r.once.Do(r.init) + + for _, point := range in { + if newMeasurementName, ok := r.measurements[point.Name()]; ok { + point.SetName(newMeasurementName) + } + for oldTagName, tagValue := range point.Tags() { + if newTagName, ok := r.tags[oldTagName]; ok { + point.RemoveTag(oldTagName) + point.AddTag(newTagName, tagValue) + } + } + for oldFieldName, fieldValue := range point.Fields() { + if newFieldName, ok := r.fields[oldFieldName]; ok { + point.RemoveField(oldFieldName) + point.AddField(newFieldName, fieldValue) + } + } + } + + return in +} + +func (r *Rename) init() { + if r.measurements == nil || r.tags == nil || r.fields == nil { + r.measurements = make(map[string]string, len(r.Measurement)) + for _, o := range r.Measurement { + r.measurements[o.From] = o.To + } + r.tags = make(map[string]string, len(r.Tag)) + for _, o := range r.Tag { + r.tags[o.From] = o.To + } + r.fields = make(map[string]string, len(r.Field)) + for _, o := range r.Field { + r.fields[o.From] = o.To + } + } +} + +func init() { + processors.Add("rename", func() telegraf.Processor { + return &Rename{} + }) +} diff --git a/plugins/processors/rename/rename_test.go b/plugins/processors/rename/rename_test.go new file mode 100644 index 0000000000000..43f7fcc30a502 --- /dev/null +++ b/plugins/processors/rename/rename_test.go @@ -0,0 +1,58 @@ +package rename + +import ( + "testing" + "time" + + "github.com/influxdata/telegraf" + "github.com/influxdata/telegraf/metric" + "github.com/stretchr/testify/assert" +) + +func newMetric(name string, tags map[string]string, fields map[string]interface{}) telegraf.Metric { + if tags == nil { + tags = map[string]string{} + } + if fields == nil { + fields = map[string]interface{}{} + } + m, _ := metric.New(name, tags, fields, time.Now()) + return m +} + +func TestMeasurementRename(t *testing.T) { + r := Rename{} + r.Measurement = []renamer{ + {From: "foo", To: "bar"}, + {From: "baz", To: "quux"}, + } + m1 := newMetric("foo", nil, nil) + m2 := newMetric("bar", nil, nil) + m3 := newMetric("baz", nil, nil) + results := r.Apply(m1, m2, m3) + assert.Equal(t, "bar", results[0].Name(), "Should change name from 'foo' to 'bar'") + assert.Equal(t, "bar", results[1].Name(), "Should not name from 'bar'") + assert.Equal(t, "quux", results[2].Name(), "Should change name from 'baz' to 'quux'") +} + +func TestTagRename(t *testing.T) { + r := Rename{} + r.Tag = []renamer{ + {From: "hostname", To: "host"}, + } + m := newMetric("foo", map[string]string{"hostname": "localhost", "region": "east-1"}, nil) + results := r.Apply(m) + + assert.Equal(t, map[string]string{"host": "localhost", "region": "east-1"}, results[0].Tags(), "should change tag 'hostname' to 'host'") +} + +func TestFieldRename(t *testing.T) { + r := Rename{} + r.Field = []renamer{ + {From: "time_msec", To: "time"}, + } + m := newMetric("foo", nil, map[string]interface{}{"time_msec": int64(1250), "snakes": true}) + results := r.Apply(m) + + assert.Equal(t, map[string]interface{}{"time": int64(1250), "snakes": true}, results[0].Fields(), "should change field 'time_msec' to 'time'") +}