1
0
Fork 0

Adding upstream version 1.34.4.

Signed-off-by: Daniel Baumann <daniel@debian.org>
This commit is contained in:
Daniel Baumann 2025-05-24 07:26:29 +02:00
parent e393c3af3f
commit 4978089aab
Signed by: daniel
GPG key ID: FBB4F0E80A80222F
4963 changed files with 677545 additions and 0 deletions

View 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
```

View 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{}
})
}

View 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")
}

View 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