feat(processors.unpivot): Add mode to create new metrics (#12659)

This commit is contained in:
Joshua Powers 2023-02-15 03:34:56 -07:00 committed by GitHub
parent 426b7eca29
commit ecfd367a46
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 195 additions and 9 deletions

View File

@ -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

View File

@ -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"

View File

@ -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()

View File

@ -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())
})
}
}