Adding upstream version 1.34.4.
Signed-off-by: Daniel Baumann <daniel@debian.org>
This commit is contained in:
parent
e393c3af3f
commit
4978089aab
4963 changed files with 677545 additions and 0 deletions
61
plugins/processors/enum/README.md
Normal file
61
plugins/processors/enum/README.md
Normal file
|
@ -0,0 +1,61 @@
|
|||
# Enum Processor Plugin
|
||||
|
||||
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, int, float64 and bool 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 tag or field. By default the source tag or field is overwritten.
|
||||
|
||||
## Global configuration options <!-- @/docs/includes/plugin_config.md -->
|
||||
|
||||
In addition to the plugin-specific configuration settings, plugins support
|
||||
additional global and plugin configuration settings. These settings are used to
|
||||
modify metrics, tags, and field or create aliases and configure ordering, etc.
|
||||
See the [CONFIGURATION.md][CONFIGURATION.md] for more details.
|
||||
|
||||
[CONFIGURATION.md]: ../../../docs/CONFIGURATION.md#plugins
|
||||
|
||||
## Configuration
|
||||
|
||||
```toml @sample.conf
|
||||
# Map enum values according to given table.
|
||||
[[processors.enum]]
|
||||
[[processors.enum.mapping]]
|
||||
## Name of the field to map. Globs accepted.
|
||||
field = "status"
|
||||
|
||||
## Name of the tag to map. Globs accepted.
|
||||
# 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 and no match is found, the original field will remain
|
||||
## unmodified and the destination tag or field will not be created.
|
||||
# default = 0
|
||||
|
||||
## Table of mappings
|
||||
[processors.enum.mapping.value_mappings]
|
||||
green = 1
|
||||
amber = 2
|
||||
red = 3
|
||||
```
|
||||
|
||||
## Example
|
||||
|
||||
```diff
|
||||
- xyzzy status="green" 1502489900000000000
|
||||
+ xyzzy status="green",status_code=1i 1502489900000000000
|
||||
```
|
||||
|
||||
With unknown value and no default set:
|
||||
|
||||
```diff
|
||||
- xyzzy status="black" 1502489900000000000
|
||||
+ xyzzy status="black" 1502489900000000000
|
||||
```
|
165
plugins/processors/enum/enum.go
Normal file
165
plugins/processors/enum/enum.go
Normal file
|
@ -0,0 +1,165 @@
|
|||
//go:generate ../../../tools/readme_config_includer/generator
|
||||
package enum
|
||||
|
||||
import (
|
||||
_ "embed"
|
||||
"fmt"
|
||||
"strconv"
|
||||
|
||||
"github.com/influxdata/telegraf"
|
||||
"github.com/influxdata/telegraf/filter"
|
||||
"github.com/influxdata/telegraf/plugins/processors"
|
||||
)
|
||||
|
||||
//go:embed sample.conf
|
||||
var sampleConfig string
|
||||
|
||||
type EnumMapper struct {
|
||||
Mappings []Mapping `toml:"mapping"`
|
||||
|
||||
FieldFilters map[string]filter.Filter
|
||||
TagFilters map[string]filter.Filter
|
||||
}
|
||||
|
||||
type Mapping struct {
|
||||
Tag string
|
||||
Field string
|
||||
Dest string
|
||||
Default interface{}
|
||||
ValueMappings map[string]interface{}
|
||||
}
|
||||
|
||||
func (*EnumMapper) SampleConfig() string {
|
||||
return sampleConfig
|
||||
}
|
||||
|
||||
func (mapper *EnumMapper) Init() error {
|
||||
mapper.FieldFilters = make(map[string]filter.Filter)
|
||||
mapper.TagFilters = make(map[string]filter.Filter)
|
||||
for _, mapping := range mapper.Mappings {
|
||||
if mapping.Field != "" {
|
||||
fieldFilter, err := filter.NewIncludeExcludeFilter([]string{mapping.Field}, nil)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create new field filter: %w", err)
|
||||
}
|
||||
mapper.FieldFilters[mapping.Field] = fieldFilter
|
||||
}
|
||||
if mapping.Tag != "" {
|
||||
tagFilter, err := filter.NewIncludeExcludeFilter([]string{mapping.Tag}, nil)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create new tag filter: %w", err)
|
||||
}
|
||||
mapper.TagFilters[mapping.Tag] = tagFilter
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (mapper *EnumMapper) Apply(in ...telegraf.Metric) []telegraf.Metric {
|
||||
for i := 0; i < len(in); i++ {
|
||||
in[i] = mapper.applyMappings(in[i])
|
||||
}
|
||||
return in
|
||||
}
|
||||
|
||||
func (mapper *EnumMapper) applyMappings(metric telegraf.Metric) telegraf.Metric {
|
||||
newFields := make(map[string]interface{})
|
||||
newTags := make(map[string]string)
|
||||
|
||||
for _, mapping := range mapper.Mappings {
|
||||
if mapping.Field != "" {
|
||||
mapper.fieldMapping(metric, mapping, newFields)
|
||||
}
|
||||
if mapping.Tag != "" {
|
||||
mapper.tagMapping(metric, mapping, newTags)
|
||||
}
|
||||
}
|
||||
|
||||
for k, v := range newFields {
|
||||
writeField(metric, k, v)
|
||||
}
|
||||
|
||||
for k, v := range newTags {
|
||||
writeTag(metric, k, v)
|
||||
}
|
||||
|
||||
return metric
|
||||
}
|
||||
|
||||
func (mapper *EnumMapper) fieldMapping(metric telegraf.Metric, mapping Mapping, newFields map[string]interface{}) {
|
||||
fields := metric.FieldList()
|
||||
for _, f := range fields {
|
||||
if mapper.FieldFilters[mapping.Field].Match(f.Key) {
|
||||
if adjustedValue, isString := adjustValue(f.Value).(string); isString {
|
||||
if mappedValue, isMappedValuePresent := mapping.mapValue(adjustedValue); isMappedValuePresent {
|
||||
newFields[mapping.getDestination(f.Key)] = mappedValue
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (mapper *EnumMapper) tagMapping(metric telegraf.Metric, mapping Mapping, newTags map[string]string) {
|
||||
tags := metric.TagList()
|
||||
for _, t := range tags {
|
||||
if mapper.TagFilters[mapping.Tag].Match(t.Key) {
|
||||
if mappedValue, isMappedValuePresent := mapping.mapValue(t.Value); isMappedValuePresent {
|
||||
switch val := mappedValue.(type) {
|
||||
case string:
|
||||
newTags[mapping.getDestination(t.Key)] = val
|
||||
default:
|
||||
newTags[mapping.getDestination(t.Key)] = fmt.Sprintf("%v", val)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func adjustValue(in interface{}) interface{} {
|
||||
switch val := in.(type) {
|
||||
case bool:
|
||||
return strconv.FormatBool(val)
|
||||
case int64:
|
||||
return strconv.FormatInt(val, 10)
|
||||
case float64:
|
||||
return strconv.FormatFloat(val, 'f', -1, 64)
|
||||
case uint64:
|
||||
return strconv.FormatUint(val, 10)
|
||||
default:
|
||||
return in
|
||||
}
|
||||
}
|
||||
|
||||
func (mapping *Mapping) mapValue(original string) (interface{}, bool) {
|
||||
if mapped, found := mapping.ValueMappings[original]; found {
|
||||
return mapped, true
|
||||
}
|
||||
if mapping.Default != nil {
|
||||
return mapping.Default, true
|
||||
}
|
||||
return original, false
|
||||
}
|
||||
|
||||
func (mapping *Mapping) getDestination(defaultDest string) string {
|
||||
if mapping.Dest != "" {
|
||||
return mapping.Dest
|
||||
}
|
||||
return defaultDest
|
||||
}
|
||||
|
||||
func writeField(metric telegraf.Metric, name string, value interface{}) {
|
||||
metric.RemoveField(name)
|
||||
metric.AddField(name, value)
|
||||
}
|
||||
|
||||
func writeTag(metric telegraf.Metric, name, value string) {
|
||||
metric.RemoveTag(name)
|
||||
metric.AddTag(name, value)
|
||||
}
|
||||
|
||||
func init() {
|
||||
processors.Add("enum", func() telegraf.Processor {
|
||||
return &EnumMapper{}
|
||||
})
|
||||
}
|
219
plugins/processors/enum/enum_test.go
Normal file
219
plugins/processors/enum/enum_test.go
Normal file
|
@ -0,0 +1,219 @@
|
|||
package enum
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/influxdata/telegraf"
|
||||
"github.com/influxdata/telegraf/metric"
|
||||
)
|
||||
|
||||
func createTestMetric() telegraf.Metric {
|
||||
m := metric.New("m1",
|
||||
map[string]string{
|
||||
"tag": "tag_value",
|
||||
"duplicate_tag": "tag_value",
|
||||
},
|
||||
map[string]interface{}{
|
||||
"string_value": "test",
|
||||
"duplicate_string_value": "test",
|
||||
"int_value": 200,
|
||||
"uint_value": uint(500),
|
||||
"float_value": float64(3.14),
|
||||
"true_value": true,
|
||||
},
|
||||
time.Now(),
|
||||
)
|
||||
return m
|
||||
}
|
||||
|
||||
func calculateProcessedValues(mapper EnumMapper, m telegraf.Metric) map[string]interface{} {
|
||||
processed := mapper.Apply(m)
|
||||
return processed[0].Fields()
|
||||
}
|
||||
|
||||
func calculateProcessedTags(mapper EnumMapper, m telegraf.Metric) map[string]string {
|
||||
processed := mapper.Apply(m)
|
||||
return processed[0].Tags()
|
||||
}
|
||||
|
||||
func assertFieldValue(t *testing.T, expected interface{}, field string, fields map[string]interface{}) {
|
||||
value, present := fields[field]
|
||||
require.True(t, present, "value of field '"+field+"' was not present")
|
||||
require.EqualValues(t, expected, value)
|
||||
}
|
||||
|
||||
func assertTagValue(t *testing.T, expected interface{}, tag string, tags map[string]string) {
|
||||
value, present := tags[tag]
|
||||
require.True(t, present, "value of tag '"+tag+"' was not present")
|
||||
require.EqualValues(t, expected, value)
|
||||
}
|
||||
|
||||
func TestRetainsMetric(t *testing.T) {
|
||||
mapper := EnumMapper{}
|
||||
err := mapper.Init()
|
||||
require.NoError(t, err)
|
||||
source := createTestMetric()
|
||||
|
||||
target := mapper.Apply(source)[0]
|
||||
fields := target.Fields()
|
||||
|
||||
assertFieldValue(t, "test", "string_value", fields)
|
||||
assertFieldValue(t, 200, "int_value", fields)
|
||||
assertFieldValue(t, 500, "uint_value", fields)
|
||||
assertFieldValue(t, float64(3.14), "float_value", fields)
|
||||
assertFieldValue(t, true, "true_value", fields)
|
||||
require.Equal(t, "m1", target.Name())
|
||||
require.Equal(t, source.Tags(), target.Tags())
|
||||
require.Equal(t, source.Time(), target.Time())
|
||||
}
|
||||
|
||||
func TestMapsSingleStringValueTag(t *testing.T) {
|
||||
mapper := EnumMapper{Mappings: []Mapping{{Tag: "tag", ValueMappings: map[string]interface{}{"tag_value": "valuable"}}}}
|
||||
err := mapper.Init()
|
||||
require.NoError(t, err)
|
||||
tags := calculateProcessedTags(mapper, createTestMetric())
|
||||
|
||||
assertTagValue(t, "valuable", "tag", tags)
|
||||
}
|
||||
|
||||
func TestMappings(t *testing.T) {
|
||||
mappings := []map[string][]interface{}{
|
||||
{
|
||||
"field_name": []interface{}{"string_value"},
|
||||
"target_values": []interface{}{"test", "test", "test", "not_test", "50", "true"},
|
||||
"mapped_values": []interface{}{"test_1", 5, true, "test_1", 10, false},
|
||||
"expected_values": []interface{}{"test_1", 5, true, "test", "test", "test"},
|
||||
},
|
||||
{
|
||||
"field_name": []interface{}{"true_value"},
|
||||
"target_value": []interface{}{"true", "true", "true", "false", "test", "5"},
|
||||
"mapped_value": []interface{}{false, 1, "false", false, false, false},
|
||||
"expected_value": []interface{}{false, 1, "false", true, true, true},
|
||||
},
|
||||
{
|
||||
"field_name": []interface{}{"int_value"},
|
||||
"target_value": []interface{}{"200", "200", "200", "200", "test", "5"},
|
||||
"mapped_value": []interface{}{"http_ok", true, 1, float64(200.001), false, false},
|
||||
"expected_value": []interface{}{"http_ok", true, 1, float64(200.001), 200, 200},
|
||||
},
|
||||
{
|
||||
"field_name": []interface{}{"uint_value"},
|
||||
"target_value": []interface{}{"500", "500", "500", "test", "false", "5"},
|
||||
"mapped_value": []interface{}{"internal_error", 1, false, false, false, false},
|
||||
"expected_value": []interface{}{"internal_error", 1, false, 500, 500, 500},
|
||||
},
|
||||
{
|
||||
"field_name": []interface{}{"float_value"},
|
||||
"target_value": []interface{}{"3.14", "3.14", "3.14", "3.14", "not_float", "5"},
|
||||
"mapped_value": []interface{}{"pi", 1, false, float64(100.2), float64(3.14), "pi"},
|
||||
"expected_value": []interface{}{"pi", 1, false, float64(100.2), float64(3.14), float64(3.14)},
|
||||
},
|
||||
}
|
||||
|
||||
for _, mapping := range mappings {
|
||||
fieldName := mapping["field_name"][0].(string)
|
||||
for index := range mapping["target_value"] {
|
||||
mapper := EnumMapper{
|
||||
Mappings: []Mapping{
|
||||
{Field: fieldName, ValueMappings: map[string]interface{}{mapping["target_value"][index].(string): mapping["mapped_value"][index]}},
|
||||
},
|
||||
}
|
||||
err := mapper.Init()
|
||||
require.NoError(t, err)
|
||||
fields := calculateProcessedValues(mapper, createTestMetric())
|
||||
assertFieldValue(t, mapping["expected_value"][index], fieldName, fields)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestMapsToDefaultValueOnUnknownSourceValue(t *testing.T) {
|
||||
mapper := EnumMapper{Mappings: []Mapping{{Field: "string_value", Default: int64(42), ValueMappings: map[string]interface{}{"other": int64(1)}}}}
|
||||
err := mapper.Init()
|
||||
require.NoError(t, err)
|
||||
fields := calculateProcessedValues(mapper, createTestMetric())
|
||||
|
||||
assertFieldValue(t, 42, "string_value", fields)
|
||||
}
|
||||
|
||||
func TestDoNotMapToDefaultValueKnownSourceValue(t *testing.T) {
|
||||
mapper := EnumMapper{Mappings: []Mapping{{Field: "string_value", Default: int64(42), ValueMappings: map[string]interface{}{"test": int64(1)}}}}
|
||||
err := mapper.Init()
|
||||
require.NoError(t, err)
|
||||
fields := calculateProcessedValues(mapper, createTestMetric())
|
||||
|
||||
assertFieldValue(t, 1, "string_value", fields)
|
||||
}
|
||||
|
||||
func TestNoMappingWithoutDefaultOrDefinedMappingValue(t *testing.T) {
|
||||
mapper := EnumMapper{Mappings: []Mapping{{Field: "string_value", ValueMappings: map[string]interface{}{"other": int64(1)}}}}
|
||||
err := mapper.Init()
|
||||
require.NoError(t, err)
|
||||
fields := calculateProcessedValues(mapper, createTestMetric())
|
||||
|
||||
assertFieldValue(t, "test", "string_value", fields)
|
||||
}
|
||||
|
||||
func TestWritesToDestination(t *testing.T) {
|
||||
mapper := EnumMapper{Mappings: []Mapping{{Field: "string_value", Dest: "string_code", ValueMappings: map[string]interface{}{"test": int64(1)}}}}
|
||||
err := mapper.Init()
|
||||
require.NoError(t, err)
|
||||
fields := calculateProcessedValues(mapper, createTestMetric())
|
||||
|
||||
assertFieldValue(t, "test", "string_value", fields)
|
||||
assertFieldValue(t, 1, "string_code", fields)
|
||||
}
|
||||
|
||||
func TestDoNotWriteToDestinationWithoutDefaultOrDefinedMapping(t *testing.T) {
|
||||
field := "string_code"
|
||||
mapper := EnumMapper{Mappings: []Mapping{{Field: "string_value", Dest: field, ValueMappings: map[string]interface{}{"other": int64(1)}}}}
|
||||
err := mapper.Init()
|
||||
require.NoError(t, err)
|
||||
fields := calculateProcessedValues(mapper, createTestMetric())
|
||||
|
||||
assertFieldValue(t, "test", "string_value", fields)
|
||||
_, present := fields[field]
|
||||
require.False(t, present, "value of field '"+field+"' was present")
|
||||
}
|
||||
|
||||
func TestFieldGlobMatching(t *testing.T) {
|
||||
mapper := EnumMapper{Mappings: []Mapping{{Field: "*", ValueMappings: map[string]interface{}{"test": "glob"}}}}
|
||||
err := mapper.Init()
|
||||
require.NoError(t, err)
|
||||
fields := calculateProcessedValues(mapper, createTestMetric())
|
||||
|
||||
assertFieldValue(t, "glob", "string_value", fields)
|
||||
assertFieldValue(t, "glob", "duplicate_string_value", fields)
|
||||
}
|
||||
|
||||
func TestTagGlobMatching(t *testing.T) {
|
||||
mapper := EnumMapper{Mappings: []Mapping{{Tag: "*", ValueMappings: map[string]interface{}{"tag_value": "glob"}}}}
|
||||
err := mapper.Init()
|
||||
require.NoError(t, err)
|
||||
tags := calculateProcessedTags(mapper, createTestMetric())
|
||||
|
||||
assertTagValue(t, "glob", "tag", tags)
|
||||
}
|
||||
|
||||
func TestTracking(t *testing.T) {
|
||||
m := createTestMetric()
|
||||
var delivered bool
|
||||
notify := func(telegraf.DeliveryInfo) {
|
||||
delivered = true
|
||||
}
|
||||
m, _ = metric.WithTracking(m, notify)
|
||||
|
||||
mapper := EnumMapper{Mappings: []Mapping{{Tag: "*", ValueMappings: map[string]interface{}{"tag_value": "glob"}}}}
|
||||
err := mapper.Init()
|
||||
require.NoError(t, err)
|
||||
|
||||
actual := mapper.Apply(m)[0]
|
||||
assertTagValue(t, "glob", "tag", actual.Tags())
|
||||
|
||||
actual.Accept()
|
||||
require.Eventually(t, func() bool {
|
||||
return delivered
|
||||
}, time.Second, 100*time.Millisecond, "no metrics delivered")
|
||||
}
|
23
plugins/processors/enum/sample.conf
Normal file
23
plugins/processors/enum/sample.conf
Normal file
|
@ -0,0 +1,23 @@
|
|||
# Map enum values according to given table.
|
||||
[[processors.enum]]
|
||||
[[processors.enum.mapping]]
|
||||
## Name of the field to map. Globs accepted.
|
||||
field = "status"
|
||||
|
||||
## Name of the tag to map. Globs accepted.
|
||||
# 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 and no match is found, the original field will remain
|
||||
## unmodified and the destination tag or field will not be created.
|
||||
# default = 0
|
||||
|
||||
## Table of mappings
|
||||
[processors.enum.mapping.value_mappings]
|
||||
green = 1
|
||||
amber = 2
|
||||
red = 3
|
Loading…
Add table
Add a link
Reference in a new issue