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
59
plugins/parsers/value/README.md
Normal file
59
plugins/parsers/value/README.md
Normal 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.
|
125
plugins/parsers/value/parser.go
Normal file
125
plugins/parsers/value/parser.go
Normal 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,
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
346
plugins/parsers/value/parser_test.go
Normal file
346
plugins/parsers/value/parser_test.go
Normal 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))
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue