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
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
_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
value mappings for each field. Default mapping values can be configured to be
types for the field values. Multiple tags or fields can be configured with separate
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
processor supports explicit configuration of a destination field. By default the
source field is overwritten.
processor supports explicit configuration of a destination tag or field. By default the
source tag or field is overwritten.
### Configuration:
@ -17,8 +17,11 @@ source field is overwritten.
## Name of the field to map
field = "status"
## Destination field to be used for the mapped value. By default the source
## field is used, overwriting the original value.
## Name of the tag to map
# 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"
## Default value to be used for all values not contained in the mapping

View File

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

View File

@ -27,12 +27,23 @@ func calculateProcessedValues(mapper EnumMapper, metric telegraf.Metric) map[str
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{}) {
value, present := fields[field]
assert.True(t, present, "value of field '"+field+"' was not present")
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) {
mapper := EnumMapper{}
source := createTestMetric()
@ -56,6 +67,14 @@ func TestMapsSingleStringValue(t *testing.T) {
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) {
mapper := EnumMapper{Mappings: []Mapping{{Field: "int_value", ValueMappings: map[string]interface{}{"13i": int64(7)}}}}