Support tags in enum processor (#5855)

This commit is contained in:
Greg 2019-05-16 16:59:19 -06:00 committed by Daniel Nelson
parent 43c6d13c33
commit 10fd5b35f0
3 changed files with 68 additions and 15 deletions

View File

@ -1,13 +1,13 @@
# Enum Processor Plugin # Enum Processor Plugin
The Enum Processor allows the configuration of value mappings for metric fields. The Enum Processor allows the configuration of value mappings for metric tags or fields.
The main use-case for this is to rewrite status codes such as _red_, _amber_ and The main use-case for this is to rewrite status codes such as _red_, _amber_ and
_green_ by numeric values such as 0, 1, 2. The plugin supports string and bool _green_ by numeric values such as 0, 1, 2. The plugin supports string and bool
types for the field values. Multiple Fields can be configured with separate types for the field values. Multiple tags or fields can be configured with separate
value mappings for each field. Default mapping values can be configured to be value mappings for each. Default mapping values can be configured to be
used for all values, which are not contained in the value_mappings. The used for all values, which are not contained in the value_mappings. The
processor supports explicit configuration of a destination field. By default the processor supports explicit configuration of a destination tag or field. By default the
source field is overwritten. source tag or field is overwritten.
### Configuration: ### Configuration:
@ -17,8 +17,11 @@ source field is overwritten.
## Name of the field to map ## Name of the field to map
field = "status" field = "status"
## Destination field to be used for the mapped value. By default the source ## Name of the tag to map
## field is used, overwriting the original value. # tag = "status"
## Destination tag or field to be used for the mapped value. By default the
## source tag or field is used, overwriting the original value.
dest = "status_code" dest = "status_code"
## Default value to be used for all values not contained in the mapping ## Default value to be used for all values not contained in the mapping

View File

@ -1,6 +1,7 @@
package enum package enum
import ( import (
"fmt"
"strconv" "strconv"
"github.com/influxdata/telegraf" "github.com/influxdata/telegraf"
@ -12,9 +13,12 @@ var sampleConfig = `
## Name of the field to map ## Name of the field to map
field = "status" field = "status"
## Destination field to be used for the mapped value. By default the source ## Name of the tag to map
## field is used, overwriting the original value. # tag = "status"
# dest = "status_code"
## Destination tag or field to be used for the mapped value. By default the
## source tag or field is used, overwriting the original value.
dest = "status_code"
## Default value to be used for all values not contained in the mapping ## Default value to be used for all values not contained in the mapping
## table. When unset, the unmodified value for the field will be used if no ## table. When unset, the unmodified value for the field will be used if no
@ -24,7 +28,7 @@ var sampleConfig = `
## Table of mappings ## Table of mappings
[processors.enum.mapping.value_mappings] [processors.enum.mapping.value_mappings]
green = 1 green = 1
yellow = 2 amber = 2
red = 3 red = 3
` `
@ -33,6 +37,7 @@ type EnumMapper struct {
} }
type Mapping struct { type Mapping struct {
Tag string
Field string Field string
Dest string Dest string
Default interface{} Default interface{}
@ -56,10 +61,24 @@ func (mapper *EnumMapper) Apply(in ...telegraf.Metric) []telegraf.Metric {
func (mapper *EnumMapper) applyMappings(metric telegraf.Metric) telegraf.Metric { func (mapper *EnumMapper) applyMappings(metric telegraf.Metric) telegraf.Metric {
for _, mapping := range mapper.Mappings { for _, mapping := range mapper.Mappings {
if originalValue, isPresent := metric.GetField(mapping.Field); isPresent == true { if mapping.Field != "" {
if adjustedValue, isString := adjustBoolValue(originalValue).(string); isString == true { if originalValue, isPresent := metric.GetField(mapping.Field); isPresent {
if mappedValue, isMappedValuePresent := mapping.mapValue(adjustedValue); isMappedValuePresent == true { if adjustedValue, isString := adjustBoolValue(originalValue).(string); isString {
writeField(metric, mapping.getDestination(), mappedValue) if mappedValue, isMappedValuePresent := mapping.mapValue(adjustedValue); isMappedValuePresent {
writeField(metric, mapping.getDestination(), mappedValue)
}
}
}
}
if mapping.Tag != "" {
if originalValue, isPresent := metric.GetTag(mapping.Tag); isPresent {
if mappedValue, isMappedValuePresent := mapping.mapValue(originalValue); isMappedValuePresent {
switch val := mappedValue.(type) {
case string:
writeTag(metric, mapping.getDestinationTag(), val)
default:
writeTag(metric, mapping.getDestinationTag(), fmt.Sprintf("%v", val))
}
} }
} }
} }
@ -91,11 +110,23 @@ func (mapping *Mapping) getDestination() string {
return mapping.Field return mapping.Field
} }
func (mapping *Mapping) getDestinationTag() string {
if mapping.Dest != "" {
return mapping.Dest
}
return mapping.Tag
}
func writeField(metric telegraf.Metric, name string, value interface{}) { func writeField(metric telegraf.Metric, name string, value interface{}) {
metric.RemoveField(name) metric.RemoveField(name)
metric.AddField(name, value) metric.AddField(name, value)
} }
func writeTag(metric telegraf.Metric, name string, value string) {
metric.RemoveTag(name)
metric.AddTag(name, value)
}
func init() { func init() {
processors.Add("enum", func() telegraf.Processor { processors.Add("enum", func() telegraf.Processor {
return &EnumMapper{} return &EnumMapper{}

View File

@ -27,12 +27,23 @@ func calculateProcessedValues(mapper EnumMapper, metric telegraf.Metric) map[str
return processed[0].Fields() return processed[0].Fields()
} }
func calculateProcessedTags(mapper EnumMapper, metric telegraf.Metric) map[string]string {
processed := mapper.Apply(metric)
return processed[0].Tags()
}
func assertFieldValue(t *testing.T, expected interface{}, field string, fields map[string]interface{}) { func assertFieldValue(t *testing.T, expected interface{}, field string, fields map[string]interface{}) {
value, present := fields[field] value, present := fields[field]
assert.True(t, present, "value of field '"+field+"' was not present") assert.True(t, present, "value of field '"+field+"' was not present")
assert.EqualValues(t, expected, value) assert.EqualValues(t, expected, value)
} }
func assertTagValue(t *testing.T, expected interface{}, tag string, tags map[string]string) {
value, present := tags[tag]
assert.True(t, present, "value of tag '"+tag+"' was not present")
assert.EqualValues(t, expected, value)
}
func TestRetainsMetric(t *testing.T) { func TestRetainsMetric(t *testing.T) {
mapper := EnumMapper{} mapper := EnumMapper{}
source := createTestMetric() source := createTestMetric()
@ -56,6 +67,14 @@ func TestMapsSingleStringValue(t *testing.T) {
assertFieldValue(t, 1, "string_value", fields) assertFieldValue(t, 1, "string_value", fields)
} }
func TestMapsSingleStringValueTag(t *testing.T) {
mapper := EnumMapper{Mappings: []Mapping{{Tag: "tag", ValueMappings: map[string]interface{}{"tag_value": "valuable"}}}}
tags := calculateProcessedTags(mapper, createTestMetric())
assertTagValue(t, "valuable", "tag", tags)
}
func TestNoFailureOnMappingsOnNonStringValuedFields(t *testing.T) { func TestNoFailureOnMappingsOnNonStringValuedFields(t *testing.T) {
mapper := EnumMapper{Mappings: []Mapping{{Field: "int_value", ValueMappings: map[string]interface{}{"13i": int64(7)}}}} mapper := EnumMapper{Mappings: []Mapping{{Field: "int_value", ValueMappings: map[string]interface{}{"13i": int64(7)}}}}