Add defaults processor to set default field values (#7370)

This commit is contained in:
Jeff Registre 2020-05-22 14:44:13 -04:00 committed by GitHub
parent e6f06457ce
commit a7674b707b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 246 additions and 0 deletions

View File

@ -5,6 +5,7 @@ import (
_ "github.com/influxdata/telegraf/plugins/processors/converter"
_ "github.com/influxdata/telegraf/plugins/processors/date"
_ "github.com/influxdata/telegraf/plugins/processors/dedup"
_ "github.com/influxdata/telegraf/plugins/processors/defaults"
_ "github.com/influxdata/telegraf/plugins/processors/enum"
_ "github.com/influxdata/telegraf/plugins/processors/filepath"
_ "github.com/influxdata/telegraf/plugins/processors/override"

View File

@ -0,0 +1,42 @@
# Defaults Processor
The *Defaults* processor allows you to ensure certain fields will always exist with a specified default value on your metric(s).
There are three cases where this processor will insert a configured default field.
1. The field is nil on the incoming metric
1. The field is not nil, but its value is an empty string.
1. The field is not nil, but its value is a string of one or more empty spaces.
### Configuration
```toml
## Set default fields on your metric(s) when they are nil or empty
[[processors.defaults]]
## This table determines what fields will be inserted in your metric(s)
[processors.defaults.fields]
field_1 = "bar"
time_idle = 0
is_error = true
```
### Example
Ensure a _status\_code_ field with _N/A_ is inserted in the metric when one it's not set in the metric be default:
```toml
[[processors.defaults]]
[processors.defaults.fields]
status_code = "N/A"
```
```diff
- lb,http_method=GET cache_status=HIT,latency=230
+ lb,http_method=GET cache_status=HIT,latency=230,status_code="N/A"
```
Ensure an empty string gets replaced by a default:
```diff
- lb,http_method=GET cache_status=HIT,latency=230,status_code=""
+ lb,http_method=GET cache_status=HIT,latency=230,status_code="N/A"
```

View File

@ -0,0 +1,72 @@
package defaults
import (
"github.com/influxdata/telegraf"
"github.com/influxdata/telegraf/plugins/processors"
"strings"
)
const sampleConfig = `
## Ensures a set of fields always exists on your metric(s) with their
## respective default value.
## For any given field pair (key = default), if it's not set, a field
## is set on the metric with the specified default.
##
## A field is considered not set if it is nil on the incoming metric;
## or it is not nil but its value is an empty string or is a string
## of one or more spaces.
## <target-field> = <value>
# [processors.defaults.fields]
# field_1 = "bar"
# time_idle = 0
# is_error = true
`
// Defaults is a processor for ensuring certain fields always exist
// on your Metrics with at least a default value.
type Defaults struct {
DefaultFieldsSets map[string]interface{} `toml:"fields"`
}
// SampleConfig represents a sample toml config for this plugin.
func (def *Defaults) SampleConfig() string {
return sampleConfig
}
// Description is a brief description of this processor plugin's behaviour.
func (def *Defaults) Description() string {
return "Defaults sets default value(s) for specified fields that are not set on incoming metrics."
}
// Apply contains the main implementation of this processor.
// For each metric in 'inputMetrics', it goes over each default pair.
// If the field in the pair does not exist on the metric, the associated default is added.
// If the field was found, then, if its value is the empty string or one or more spaces, it is replaced
// by the associated default.
func (def *Defaults) Apply(inputMetrics ...telegraf.Metric) []telegraf.Metric {
for _, metric := range inputMetrics {
for defField, defValue := range def.DefaultFieldsSets {
if maybeCurrent, isSet := metric.GetField(defField); !isSet {
metric.AddField(defField, defValue)
} else if trimmed, isStr := maybeTrimmedString(maybeCurrent); isStr && trimmed == "" {
metric.RemoveField(defField)
metric.AddField(defField, defValue)
}
}
}
return inputMetrics
}
func maybeTrimmedString(v interface{}) (string, bool) {
switch value := v.(type) {
case string:
return strings.TrimSpace(value), true
}
return "", false
}
func init() {
processors.Add("defaults", func() telegraf.Processor {
return &Defaults{}
})
}

View File

@ -0,0 +1,131 @@
package defaults
import (
"testing"
"time"
"github.com/influxdata/telegraf"
"github.com/influxdata/telegraf/testutil"
"github.com/stretchr/testify/assert"
)
func TestDefaults(t *testing.T) {
scenarios := []struct {
name string
defaults *Defaults
input telegraf.Metric
expected []telegraf.Metric
}{
{
name: "Test that no values are changed since they are not nil or empty",
defaults: &Defaults{
DefaultFieldsSets: map[string]interface{}{
"usage": 30,
"wind_feel": "very chill",
"is_dead": true,
},
},
input: testutil.MustMetric(
"CPU metrics",
map[string]string{},
map[string]interface{}{
"usage": 45,
"wind_feel": "a dragon's breath",
"is_dead": false,
},
time.Unix(0, 0),
),
expected: []telegraf.Metric{
testutil.MustMetric(
"CPU metrics",
map[string]string{},
map[string]interface{}{
"usage": 45,
"wind_feel": "a dragon's breath",
"is_dead": false,
},
time.Unix(0, 0),
),
},
},
{
name: "Tests that the missing fields are set on the metric",
defaults: &Defaults{
DefaultFieldsSets: map[string]interface{}{
"max_clock_gz": 6,
"wind_feel": "Unknown",
"boost_enabled": false,
"variance": 1.2,
},
},
input: testutil.MustMetric(
"CPU metrics",
map[string]string{},
map[string]interface{}{
"usage": 45,
"temperature": 64,
},
time.Unix(0, 0),
),
expected: []telegraf.Metric{
testutil.MustMetric(
"CPU metrics",
map[string]string{},
map[string]interface{}{
"usage": 45,
"temperature": 64,
"max_clock_gz": 6,
"wind_feel": "Unknown",
"boost_enabled": false,
"variance": 1.2,
},
time.Unix(0, 0),
),
},
},
{
name: "Tests that set but empty fields are replaced by specified defaults",
defaults: &Defaults{
DefaultFieldsSets: map[string]interface{}{
"max_clock_gz": 6,
"wind_feel": "Unknown",
"fan_loudness": "Inaudible",
"boost_enabled": false,
},
},
input: testutil.MustMetric(
"CPU metrics",
map[string]string{},
map[string]interface{}{
"max_clock_gz": "",
"wind_feel": " ",
"fan_loudness": " ",
},
time.Unix(0, 0),
),
expected: []telegraf.Metric{
testutil.MustMetric(
"CPU metrics",
map[string]string{},
map[string]interface{}{
"max_clock_gz": 6,
"wind_feel": "Unknown",
"fan_loudness": "Inaudible",
"boost_enabled": false,
},
time.Unix(0, 0),
),
},
},
}
for _, scenario := range scenarios {
t.Run(scenario.name, func(t *testing.T) {
defaults := scenario.defaults
resultMetrics := defaults.Apply(scenario.input)
assert.Len(t, resultMetrics, 1)
testutil.RequireMetricsEqual(t, scenario.expected, resultMetrics)
})
}
}