diff --git a/plugins/processors/unpivot/README.md b/plugins/processors/unpivot/README.md index 060c83e45..37be94115 100644 --- a/plugins/processors/unpivot/README.md +++ b/plugins/processors/unpivot/README.md @@ -20,18 +20,37 @@ See the [CONFIGURATION.md][CONFIGURATION.md] for more details. ```toml @sample.conf # Rotate multi field metric into several single field metrics [[processors.unpivot]] + ## Metric mode to pivot to + ## Set to "tag", metrics are pivoted as a tag and the metric is kept as + ## the original measurement name. Tag key name is set by tag_key value. + ## Set to "metric" creates a new metric named the field name. With this + ## option the tag_key is ignored. Be aware that this could lead to metric + ## name conflicts! + # use_fieldname_as = "tag" + ## Tag to use for the name. - tag_key = "name" + # tag_key = "name" + ## Field to use for the name of the value. - value_key = "value" + # value_key = "value" ``` ## Example +Metric mode `tag`: + ```diff - cpu,cpu=cpu0 time_idle=42i,time_user=43i + cpu,cpu=cpu0,name=time_idle value=42i + cpu,cpu=cpu0,name=time_user value=43i ``` +Metric mode `metric`: + +```diff +- cpu,cpu=cpu0 time_idle=42i,time_user=43i ++ time_idle,cpu=cpu0 value=42i ++ time_user,cpu=cpu0 value=43i +``` + [pivot]: /plugins/processors/pivot/README.md diff --git a/plugins/processors/unpivot/sample.conf b/plugins/processors/unpivot/sample.conf index 222900552..50f5f741a 100644 --- a/plugins/processors/unpivot/sample.conf +++ b/plugins/processors/unpivot/sample.conf @@ -1,6 +1,15 @@ # Rotate multi field metric into several single field metrics [[processors.unpivot]] + ## Metric mode to pivot to + ## Set to "tag", metrics are pivoted as a tag and the metric is kept as + ## the original measurement name. Tag key name is set by tag_key value. + ## Set to "metric" creates a new metric named the field name. With this + ## option the tag_key is ignored. Be aware that this could lead to metric + ## name conflicts! + # use_fieldname_as = "tag" + ## Tag to use for the name. - tag_key = "name" + # tag_key = "name" + ## Field to use for the name of the value. - value_key = "value" + # value_key = "value" diff --git a/plugins/processors/unpivot/unpivot.go b/plugins/processors/unpivot/unpivot.go index af97017fa..7163d1b28 100644 --- a/plugins/processors/unpivot/unpivot.go +++ b/plugins/processors/unpivot/unpivot.go @@ -3,6 +3,7 @@ package unpivot import ( _ "embed" + "fmt" "github.com/influxdata/telegraf" "github.com/influxdata/telegraf/plugins/processors" @@ -12,8 +13,9 @@ import ( var sampleConfig string type Unpivot struct { - TagKey string `toml:"tag_key"` - ValueKey string `toml:"value_key"` + FieldNameAs string `toml:"use_fieldname_as"` + TagKey string `toml:"tag_key"` + ValueKey string `toml:"value_key"` } func copyWithoutFields(metric telegraf.Metric) telegraf.Metric { @@ -35,12 +37,30 @@ func (*Unpivot) SampleConfig() string { return sampleConfig } +func (p *Unpivot) Init() error { + switch p.FieldNameAs { + case "metric": + case "", "tag": + p.FieldNameAs = "tag" + default: + return fmt.Errorf("unrecognized metric mode: %q", p.FieldNameAs) + } + + if p.TagKey == "" { + p.TagKey = "name" + } + if p.ValueKey == "" { + p.ValueKey = "value" + } + + return nil +} + func (p *Unpivot) Apply(metrics ...telegraf.Metric) []telegraf.Metric { fieldCount := 0 for _, m := range metrics { fieldCount += len(m.FieldList()) } - results := make([]telegraf.Metric, 0, fieldCount) for _, m := range metrics { @@ -48,7 +68,14 @@ func (p *Unpivot) Apply(metrics ...telegraf.Metric) []telegraf.Metric { for _, field := range m.FieldList() { newMetric := base.Copy() newMetric.AddField(p.ValueKey, field.Value) - newMetric.AddTag(p.TagKey, field.Key) + + switch p.FieldNameAs { + case "metric": + newMetric.SetName(field.Key) + case "", "tag": + newMetric.AddTag(p.TagKey, field.Key) + } + results = append(results, newMetric) } m.Accept() diff --git a/plugins/processors/unpivot/unpivot_test.go b/plugins/processors/unpivot/unpivot_test.go index a3a538503..6d3c8795b 100644 --- a/plugins/processors/unpivot/unpivot_test.go +++ b/plugins/processors/unpivot/unpivot_test.go @@ -6,9 +6,23 @@ import ( "github.com/influxdata/telegraf" "github.com/influxdata/telegraf/testutil" + "github.com/stretchr/testify/require" ) -func TestUnpivot(t *testing.T) { +func TestUnpivot_defaults(t *testing.T) { + unpivot := &Unpivot{} + require.NoError(t, unpivot.Init()) + require.Equal(t, "tag", unpivot.FieldNameAs) + require.Equal(t, "name", unpivot.TagKey) + require.Equal(t, "value", unpivot.ValueKey) +} + +func TestUnpivot_invalidMetricMode(t *testing.T) { + unpivot := &Unpivot{FieldNameAs: "unknown"} + require.Error(t, unpivot.Init()) +} + +func TestUnpivot_originalMode(t *testing.T) { now := time.Now() tests := []struct { name string @@ -88,3 +102,120 @@ func TestUnpivot(t *testing.T) { }) } } + +func TestUnpivot_fieldMode(t *testing.T) { + now := time.Now() + tests := []struct { + name string + unpivot *Unpivot + metrics []telegraf.Metric + expected []telegraf.Metric + }{ + { + name: "simple", + unpivot: &Unpivot{ + FieldNameAs: "metric", + TagKey: "name", + ValueKey: "value", + }, + metrics: []telegraf.Metric{ + testutil.MustMetric("cpu", + map[string]string{}, + map[string]interface{}{ + "idle_time": int64(42), + }, + now, + ), + }, + expected: []telegraf.Metric{ + testutil.MustMetric("idle_time", + map[string]string{}, + map[string]interface{}{ + "value": int64(42), + }, + now, + ), + }, + }, + { + name: "multi fields", + unpivot: &Unpivot{ + FieldNameAs: "metric", + TagKey: "name", + ValueKey: "value", + }, + metrics: []telegraf.Metric{ + testutil.MustMetric("cpu", + map[string]string{}, + map[string]interface{}{ + "idle_time": int64(42), + "idle_user": int64(43), + }, + now, + ), + }, + expected: []telegraf.Metric{ + testutil.MustMetric("idle_time", + map[string]string{}, + map[string]interface{}{ + "value": int64(42), + }, + now, + ), + testutil.MustMetric("idle_user", + map[string]string{}, + map[string]interface{}{ + "value": int64(43), + }, + now, + ), + }, + }, + { + name: "multi fields and tags", + unpivot: &Unpivot{ + FieldNameAs: "metric", + TagKey: "name", + ValueKey: "value", + }, + metrics: []telegraf.Metric{ + testutil.MustMetric("cpu", + map[string]string{ + "building": "5a", + }, + map[string]interface{}{ + "idle_time": int64(42), + "idle_user": int64(43), + }, + now, + ), + }, + expected: []telegraf.Metric{ + testutil.MustMetric("idle_time", + map[string]string{ + "building": "5a", + }, + map[string]interface{}{ + "value": int64(42), + }, + now, + ), + testutil.MustMetric("idle_user", + map[string]string{ + "building": "5a", + }, + map[string]interface{}{ + "value": int64(43), + }, + now, + ), + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + actual := tt.unpivot.Apply(tt.metrics...) + testutil.RequireMetricsEqual(t, tt.expected, actual, testutil.SortMetrics()) + }) + } +}