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,59 @@
# Value Parser Plugin
The "value" data format translates single values into Telegraf metrics. This
is done by assigning a measurement name and setting a single field ("value")
as the parsed metric.
## Configuration
```toml
[[inputs.exec]]
## Commands array
commands = ["cat /proc/sys/kernel/random/entropy_avail"]
## override the default metric name of "exec"
name_override = "entropy_available"
## override the field name of "value"
# value_field_name = "value"
## Data format to consume.
## Each data format has its own unique set of configuration options, read
## more about them here:
## https://github.com/influxdata/telegraf/blob/master/docs/DATA_FORMATS_INPUT.md
data_format = "value"
data_type = "integer" # required
```
### Metric name
It is recommended to set `name_override` to a measurement name that makes sense
for your metric, otherwise it will just be set to the name of the plugin.
### Datatype
You **must** tell Telegraf what type of metric to collect by using the
`data_type` configuration option. Available options are:
- `integer`: converts the received data to an integer value. This setting will
produce an error on non-integer data.
- `float`: converts the received data to a floating-point value. This setting
will treat integers as floating-point values and produces an error
on data that cannot be converted (e.g. strings).
- `string`: outputs the data as a string.
- `base64`: outputs the data as a base64 encoded string.
- `boolean`: converts the received data to a boolean value. This setting will
produce an error on any data except for `true` and `false` strings.
- `auto_integer`: converts the received data to an integer value if possible and
will return the data as string otherwise. This is helpful for
mixed-type data.
- `auto_float`: converts the received data to a floating-point value if possible
and will return the data as string otherwise. This is helpful
for mixed-type data. Integer data will be treated as
floating-point values.
**NOTE**: The `auto` conversions might convert data to their prioritized type
by accident, for example if a string data-source provides `"55"` it will be
converted to integer/float. This might break outputs that require the same
datatype within a field or column. It is thus recommended to use *strict* typing
whenever possible.

View file

@ -0,0 +1,125 @@
package value
import (
"bytes"
"encoding/base64"
"fmt"
"strconv"
"strings"
"time"
"github.com/influxdata/telegraf"
"github.com/influxdata/telegraf/metric"
"github.com/influxdata/telegraf/plugins/parsers"
)
type Parser struct {
DataType string `toml:"data_type"`
FieldName string `toml:"value_field_name"`
MetricName string `toml:"-"`
DefaultTags map[string]string `toml:"-"`
}
func (v *Parser) Init() error {
switch v.DataType {
case "", "int", "integer":
v.DataType = "int"
case "float", "long":
v.DataType = "float"
case "str", "string":
v.DataType = "string"
case "base64":
v.DataType = "base64"
case "bool", "boolean":
v.DataType = "bool"
case "auto_integer", "auto_float":
// Do nothing both are valid
default:
return fmt.Errorf("unknown datatype %q", v.DataType)
}
if v.FieldName == "" {
v.FieldName = "value"
}
return nil
}
func (v *Parser) Parse(buf []byte) ([]telegraf.Metric, error) {
vStr := string(bytes.TrimSpace(bytes.Trim(buf, "\x00")))
// unless it's a string, separate out any fields in the buffer,
// ignore anything but the last.
if v.DataType != "string" {
values := strings.Fields(vStr)
if len(values) < 1 {
return nil, nil
}
vStr = values[len(values)-1]
}
var value interface{}
var err error
switch v.DataType {
case "int":
value, err = strconv.Atoi(vStr)
case "float":
value, err = strconv.ParseFloat(vStr, 64)
case "string":
value = vStr
case "base64":
value = base64.StdEncoding.EncodeToString(buf)
case "bool":
value, err = strconv.ParseBool(vStr)
case "auto_integer":
value, err = strconv.Atoi(vStr)
if err != nil {
value = vStr
err = nil
}
case "auto_float":
value, err = strconv.ParseFloat(vStr, 64)
if err != nil {
value = vStr
err = nil
}
}
if err != nil {
return nil, err
}
fields := map[string]interface{}{v.FieldName: value}
m := metric.New(v.MetricName, v.DefaultTags,
fields, time.Now().UTC())
return []telegraf.Metric{m}, nil
}
func (v *Parser) ParseLine(line string) (telegraf.Metric, error) {
metrics, err := v.Parse([]byte(line))
if err != nil {
return nil, err
}
if len(metrics) < 1 {
return nil, fmt.Errorf("can not parse the line: %s, for data format: value", line)
}
return metrics[0], nil
}
func (v *Parser) SetDefaultTags(tags map[string]string) {
v.DefaultTags = tags
}
func init() {
parsers.Add("value",
func(defaultMetricName string) telegraf.Parser {
return &Parser{
FieldName: "value",
MetricName: defaultMetricName,
}
},
)
}

View file

@ -0,0 +1,346 @@
package value
import (
"testing"
"time"
"github.com/stretchr/testify/require"
"github.com/influxdata/telegraf"
"github.com/influxdata/telegraf/metric"
"github.com/influxdata/telegraf/testutil"
)
func TestParseValidValues(t *testing.T) {
tests := []struct {
name string
dtype string
input []byte
expected interface{}
}{
{
name: "integer",
dtype: "integer",
input: []byte("55"),
expected: int64(55),
},
{
name: "float",
dtype: "float",
input: []byte("64"),
expected: float64(64),
},
{
name: "string",
dtype: "string",
input: []byte("foobar"),
expected: "foobar",
},
{
name: "base64",
dtype: "base64",
input: []byte("foobar"),
expected: "Zm9vYmFy",
},
{
name: "boolean",
dtype: "boolean",
input: []byte("true"),
expected: true,
},
{
name: "multiple integers",
dtype: "integer",
input: []byte(`55 45 223 12 999`),
expected: int64(999),
},
{
name: "auto integer",
dtype: "auto_integer",
input: []byte("55"),
expected: int64(55),
},
{
name: "auto integer with string",
dtype: "auto_integer",
input: []byte("foobar"),
expected: "foobar",
},
{
name: "auto integer with float",
dtype: "auto_integer",
input: []byte("55.0"),
expected: "55.0",
},
{
name: "auto float",
dtype: "auto_float",
input: []byte("64.2"),
expected: float64(64.2),
},
{
name: "auto float with string",
dtype: "auto_float",
input: []byte("foobar"),
expected: "foobar",
},
{
name: "auto float with integer",
dtype: "auto_float",
input: []byte("64"),
expected: float64(64),
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
expected := metric.New(
"value_test",
map[string]string{},
map[string]interface{}{"value": tt.expected},
time.Unix(0, 0),
)
plugin := Parser{
MetricName: "value_test",
DataType: tt.dtype,
}
require.NoError(t, plugin.Init())
actual, err := plugin.Parse(tt.input)
require.NoError(t, err)
require.Len(t, actual, 1)
testutil.RequireMetricEqual(t, expected, actual[0], testutil.IgnoreTime())
})
}
}
func TestParseLineValidValues(t *testing.T) {
tests := []struct {
name string
dtype string
input string
expected interface{}
}{
{
name: "integer",
dtype: "integer",
input: "55",
expected: int64(55),
},
{
name: "float",
dtype: "float",
input: "64",
expected: float64(64),
},
{
name: "string",
dtype: "string",
input: "foobar",
expected: "foobar",
},
{
name: "base64",
dtype: "base64",
input: "foobar",
expected: "Zm9vYmFy",
},
{
name: "boolean",
dtype: "boolean",
input: "true",
expected: true,
},
{
name: "multiple integers",
dtype: "integer",
input: `55 45 223 12 999`,
expected: int64(999),
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
expected := metric.New(
"value_test",
map[string]string{},
map[string]interface{}{"value": tt.expected},
time.Unix(0, 0),
)
plugin := Parser{
MetricName: "value_test",
DataType: tt.dtype,
}
require.NoError(t, plugin.Init())
actual, err := plugin.ParseLine(tt.input)
require.NoError(t, err)
testutil.RequireMetricEqual(t, expected, actual, testutil.IgnoreTime())
})
}
}
func TestParseCustomFieldName(t *testing.T) {
parser := Parser{
MetricName: "value_test",
DataType: "integer",
FieldName: "penguin",
}
require.NoError(t, parser.Init())
metrics, err := parser.Parse([]byte(`55`))
require.NoError(t, err)
require.Equal(t, map[string]interface{}{"penguin": int64(55)}, metrics[0].Fields())
}
func TestParseInvalidValues(t *testing.T) {
tests := []struct {
name string
dtype string
input []byte
}{
{
name: "integer",
dtype: "integer",
input: []byte("55.0"),
},
{
name: "float",
dtype: "float",
input: []byte("foobar"),
},
{
name: "boolean",
dtype: "boolean",
input: []byte("213"),
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
plugin := Parser{
MetricName: "value_test",
DataType: tt.dtype,
}
require.NoError(t, plugin.Init())
actual, err := plugin.Parse(tt.input)
require.ErrorContains(t, err, "invalid syntax")
require.Empty(t, actual)
})
}
}
func TestParseLineInvalidValues(t *testing.T) {
tests := []struct {
name string
dtype string
input string
}{
{
name: "integer",
dtype: "integer",
input: "55.0",
},
{
name: "float",
dtype: "float",
input: "foobar",
},
{
name: "boolean",
dtype: "boolean",
input: "213",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
plugin := Parser{
MetricName: "value_test",
DataType: tt.dtype,
}
require.NoError(t, plugin.Init())
actual, err := plugin.ParseLine(tt.input)
require.ErrorContains(t, err, "invalid syntax")
require.Empty(t, actual)
})
}
}
func TestParseValidValuesDefaultTags(t *testing.T) {
expected := metric.New(
"value_test",
map[string]string{"test": "tag"},
map[string]interface{}{"value": int64(55)},
time.Unix(0, 0),
)
plugin := Parser{
MetricName: "value_test",
DataType: "integer",
}
require.NoError(t, plugin.Init())
plugin.SetDefaultTags(map[string]string{"test": "tag"})
actual, err := plugin.Parse([]byte("55"))
require.NoError(t, err)
require.Len(t, actual, 1)
testutil.RequireMetricEqual(t, expected, actual[0], testutil.IgnoreTime())
}
func TestParseValuesWithNullCharacter(t *testing.T) {
parser := Parser{
MetricName: "value_test",
DataType: "integer",
}
require.NoError(t, parser.Init())
metrics, err := parser.Parse([]byte("55\x00"))
require.NoError(t, err)
require.Len(t, metrics, 1)
require.Equal(t, "value_test", metrics[0].Name())
require.Equal(t, map[string]interface{}{
"value": int64(55),
}, metrics[0].Fields())
require.Equal(t, map[string]string{}, metrics[0].Tags())
}
func TestInvalidDatatype(t *testing.T) {
parser := Parser{
MetricName: "value_test",
DataType: "foo",
}
require.ErrorContains(t, parser.Init(), "unknown datatype")
}
const benchmarkData = `5`
func TestBenchmarkData(t *testing.T) {
plugin := &Parser{}
require.NoError(t, plugin.Init())
expected := []telegraf.Metric{
metric.New(
"",
map[string]string{},
map[string]interface{}{
"value": 5,
},
time.Unix(0, 0),
),
}
actual, err := plugin.Parse([]byte(benchmarkData))
require.NoError(t, err)
testutil.RequireMetricsEqual(t, expected, actual, testutil.IgnoreTime(), testutil.SortMetrics())
}
func BenchmarkParsing(b *testing.B) {
plugin := &Parser{}
require.NoError(b, plugin.Init())
for n := 0; n < b.N; n++ {
//nolint:errcheck // Benchmarking so skip the error check to avoid the unnecessary operations
plugin.Parse([]byte(benchmarkData))
}
}