Skip to content

Commit

Permalink
Support tags in enum processor (influxdata#5855)
Browse files Browse the repository at this point in the history
  • Loading branch information
glinton authored and idohalevi committed Sep 23, 2020
1 parent e5a41f5 commit 034e20a
Show file tree
Hide file tree
Showing 3 changed files with 68 additions and 15 deletions.
17 changes: 10 additions & 7 deletions plugins/processors/enum/README.md
Original file line number Diff line number Diff line change
@@ -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:

Expand All @@ -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
Expand Down
47 changes: 39 additions & 8 deletions plugins/processors/enum/enum.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package enum

import (
"fmt"
"strconv"

"github.com/influxdata/telegraf"
Expand All @@ -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
Expand All @@ -24,7 +28,7 @@ var sampleConfig = `
## Table of mappings
[processors.enum.mapping.value_mappings]
green = 1
yellow = 2
amber = 2
red = 3
`

Expand All @@ -33,6 +37,7 @@ type EnumMapper struct {
}

type Mapping struct {
Tag string
Field string
Dest string
Default interface{}
Expand All @@ -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))
}
}
}
}
Expand Down Expand Up @@ -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{}
Expand Down
19 changes: 19 additions & 0 deletions plugins/processors/enum/enum_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand All @@ -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)}}}}

Expand Down

0 comments on commit 034e20a

Please sign in to comment.