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,29 @@
# OpenTSDB Telnet Style Put Format Parser Plugin
The `OpenTSDB` data format parses data in OpenTSDB's Telnet style put API
format. There are no additional configuration options for OpenTSDB. The metrics
are parsed directly into Telegraf metrics.
For more detail on the format, see:
- [OpenTSDB Telnet "PUT" API guide](http://opentsdb.net/docs/build/html/api_telnet/put.html)
- [OpenTSDB data specification](http://opentsdb.net/docs/build/html/user_guide/writing/index.html#data-specification)
## Configuration
```toml
[[inputs.file]]
files = ["example"]
## 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 = "opentsdb"
```
## Example
```opentsdb
put sys.cpu.user 1356998400 42.5 host=webserver01 cpu=0
```

View file

@ -0,0 +1,121 @@
package opentsdb
import (
"bufio"
"bytes"
"errors"
"fmt"
"strconv"
"strings"
"time"
"github.com/influxdata/telegraf"
"github.com/influxdata/telegraf/metric"
"github.com/influxdata/telegraf/plugins/parsers"
)
// Parser encapsulates a OpenTSDB Parser.
type Parser struct {
DefaultTags map[string]string `toml:"-"`
Log telegraf.Logger `toml:"-"`
}
func (p *Parser) Parse(buf []byte) ([]telegraf.Metric, error) {
var metrics []telegraf.Metric
scanner := bufio.NewScanner(bytes.NewReader(buf))
for scanner.Scan() {
line := scanner.Text()
// delete LF and CR
line = strings.TrimRight(line, "\r\n")
m, err := p.ParseLine(line)
if err != nil {
p.Log.Errorf("Error parsing %q as opentsdb: %s", line, err)
// Don't let one bad line spoil a whole batch. In particular, it may
// be a valid opentsdb telnet protocol command, like "version", that
// we don't support.
continue
}
metrics = append(metrics, m)
}
if err := scanner.Err(); err != nil {
return nil, err
}
return metrics, nil
}
// ParseLine performs OpenTSDB parsing of a single line.
func (p *Parser) ParseLine(line string) (telegraf.Metric, error) {
// Break into fields ("put", name, timestamp, value, tag1, tag2, ..., tagN).
fields := strings.Fields(line)
if len(fields) < 4 || fields[0] != "put" {
return nil, errors.New("doesn't have required fields")
}
// decode the name and tags
measurement := fields[1]
tsStr := fields[2]
valueStr := fields[3]
tagStrs := fields[4:]
// Parse value.
v, err := strconv.ParseFloat(valueStr, 64)
if err != nil {
return nil, fmt.Errorf("parsing field %q value failed: %w", measurement, err)
}
fieldValues := map[string]interface{}{"value": v}
// Parse timestamp.
ts, err := strconv.ParseInt(tsStr, 10, 64)
if err != nil {
return nil, fmt.Errorf("parsing field %q time failed: %w", measurement, err)
}
var timestamp time.Time
if ts < 1e12 {
// second resolution
timestamp = time.Unix(ts, 0)
} else {
// millisecond resolution
timestamp = time.UnixMilli(ts)
}
tags := make(map[string]string, len(p.DefaultTags)+len(tagStrs))
for k, v := range p.DefaultTags {
tags[k] = v
}
for _, tag := range tagStrs {
tagValue := strings.Split(tag, "=")
if len(tagValue) != 2 {
continue
}
name := tagValue[0]
value := tagValue[1]
if name == "" || value == "" {
continue
}
tags[name] = value
}
return metric.New(measurement, tags, fieldValues, timestamp), nil
}
func (p *Parser) SetDefaultTags(tags map[string]string) {
p.DefaultTags = tags
}
func init() {
parsers.Add("opentsdb",
func(string) telegraf.Parser {
return &Parser{}
})
}

View file

@ -0,0 +1,358 @@
package opentsdb
import (
"strconv"
"testing"
"time"
"github.com/stretchr/testify/require"
"github.com/influxdata/telegraf"
"github.com/influxdata/telegraf/metric"
"github.com/influxdata/telegraf/testutil"
)
func TestParseLine(t *testing.T) {
testTime := time.Now()
testTimeSec := testTime.Round(time.Second)
testTimeMilli := testTime.Round(time.Millisecond)
strTimeSec := strconv.FormatInt(testTimeSec.Unix(), 10)
strTimeMilli := strconv.FormatInt(testTimeMilli.UnixNano()/1000000, 10)
var tests = []struct {
name string
input string
expected telegraf.Metric
}{
{
name: "minimal case",
input: "put sys.cpu.user " + strTimeSec + " 50",
expected: testutil.MustMetric(
"sys.cpu.user",
map[string]string{},
map[string]interface{}{
"value": float64(50),
},
testTimeSec,
),
},
{
name: "millisecond timestamp",
input: "put sys.cpu.user " + strTimeMilli + " 50",
expected: testutil.MustMetric(
"sys.cpu.user",
map[string]string{},
map[string]interface{}{
"value": float64(50),
},
testTimeMilli,
),
},
{
name: "floating point value",
input: "put sys.cpu.user " + strTimeSec + " 42.5",
expected: testutil.MustMetric(
"sys.cpu.user",
map[string]string{},
map[string]interface{}{
"value": float64(42.5),
},
testTimeSec,
),
},
{
name: "single tag",
input: "put sys.cpu.user " + strTimeSec + " 42.5 host=webserver01",
expected: testutil.MustMetric(
"sys.cpu.user",
map[string]string{
"host": "webserver01",
},
map[string]interface{}{
"value": float64(42.5),
},
testTimeSec,
),
},
{
name: "double tags",
input: "put sys.cpu.user " + strTimeSec + " 42.5 host=webserver01 cpu=7",
expected: testutil.MustMetric(
"sys.cpu.user",
map[string]string{
"host": "webserver01",
"cpu": "7",
},
map[string]interface{}{
"value": float64(42.5),
},
testTimeSec,
),
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
p := &Parser{Log: testutil.Logger{}}
actual, err := p.ParseLine(tt.input)
require.NoError(t, err)
testutil.RequireMetricEqual(t, tt.expected, actual)
})
}
}
func TestParse(t *testing.T) {
testTime := time.Now()
testTimeSec := testTime.Round(time.Second)
strTimeSec := strconv.FormatInt(testTimeSec.Unix(), 10)
var tests = []struct {
name string
input []byte
expected []telegraf.Metric
}{
{
name: "single line with no newline",
input: []byte("put sys.cpu.user " + strTimeSec + " 42.5 host=webserver01 cpu=7"),
expected: []telegraf.Metric{
testutil.MustMetric(
"sys.cpu.user",
map[string]string{
"host": "webserver01",
"cpu": "7",
},
map[string]interface{}{
"value": float64(42.5),
},
testTimeSec,
),
},
},
{
name: "single line with LF",
input: []byte("put sys.cpu.user " + strTimeSec + " 42.5 host=webserver01 cpu=7\n"),
expected: []telegraf.Metric{
testutil.MustMetric(
"sys.cpu.user",
map[string]string{
"host": "webserver01",
"cpu": "7",
},
map[string]interface{}{
"value": float64(42.5),
},
testTimeSec,
),
},
},
{
name: "single line with CR+LF",
input: []byte("put sys.cpu.user " + strTimeSec + " 42.5 host=webserver01 cpu=7\r\n"),
expected: []telegraf.Metric{
testutil.MustMetric(
"sys.cpu.user",
map[string]string{
"host": "webserver01",
"cpu": "7",
},
map[string]interface{}{
"value": float64(42.5),
},
testTimeSec,
),
},
},
{
name: "double lines",
input: []byte("put sys.cpu.user " + strTimeSec + " 42.5 host=webserver01 cpu=7\r\n" +
"put sys.cpu.user " + strTimeSec + " 53.5 host=webserver02 cpu=3\r\n"),
expected: []telegraf.Metric{
testutil.MustMetric(
"sys.cpu.user",
map[string]string{
"host": "webserver01",
"cpu": "7",
},
map[string]interface{}{
"value": float64(42.5),
},
testTimeSec,
),
testutil.MustMetric(
"sys.cpu.user",
map[string]string{
"host": "webserver02",
"cpu": "3",
},
map[string]interface{}{
"value": float64(53.5),
},
testTimeSec,
),
},
},
{
name: "mixed valid/invalid input",
input: []byte(
"version\r\n" +
"put sys.cpu.user " + strTimeSec + " 42.5 host=webserver01 cpu=7\r\n" +
"put sys.cpu.user " + strTimeSec + " 53.5 host=webserver02 cpu=3\r\n",
),
expected: []telegraf.Metric{
testutil.MustMetric(
"sys.cpu.user",
map[string]string{
"host": "webserver01",
"cpu": "7",
},
map[string]interface{}{
"value": float64(42.5),
},
testTimeSec,
),
testutil.MustMetric(
"sys.cpu.user",
map[string]string{
"host": "webserver02",
"cpu": "3",
},
map[string]interface{}{
"value": float64(53.5),
},
testTimeSec,
),
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
p := &Parser{Log: testutil.Logger{}}
actual, err := p.Parse(tt.input)
require.NoError(t, err)
testutil.RequireMetricsEqual(t, tt.expected, actual)
})
}
}
func TestParse_DefaultTags(t *testing.T) {
testTime := time.Now()
testTimeSec := testTime.Round(time.Second)
strTimeSec := strconv.FormatInt(testTimeSec.Unix(), 10)
var tests = []struct {
name string
input []byte
defaultTags map[string]string
expected []telegraf.Metric
}{
{
name: "single default tag",
input: []byte("put sys.cpu.user " + strTimeSec + " 42.5 host=webserver01 cpu=7"),
defaultTags: map[string]string{
"foo": "bar",
},
expected: []telegraf.Metric{
testutil.MustMetric(
"sys.cpu.user",
map[string]string{
"foo": "bar",
"host": "webserver01",
"cpu": "7",
},
map[string]interface{}{
"value": float64(42.5),
},
testTimeSec,
),
},
},
{
name: "double default tags",
input: []byte("put sys.cpu.user " + strTimeSec + " 42.5 host=webserver01 cpu=7"),
defaultTags: map[string]string{
"foo1": "bar1",
"foo2": "bar2",
},
expected: []telegraf.Metric{
testutil.MustMetric(
"sys.cpu.user",
map[string]string{
"foo1": "bar1",
"foo2": "bar2",
"host": "webserver01",
"cpu": "7",
},
map[string]interface{}{
"value": float64(42.5),
},
testTimeSec,
),
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
p := &Parser{Log: testutil.Logger{}}
p.SetDefaultTags(tt.defaultTags)
actual, err := p.Parse(tt.input)
require.NoError(t, err)
testutil.RequireMetricsEqual(t, tt.expected, actual)
})
}
}
const benchmarkData = `put benchmark_a 1653643420 4 tags_host=myhost tags_platform=python tags_sdkver=3.11.4
put benchmark_b 1653643420 5 tags_host=myhost tags_platform=python tags_sdkver=3.11.5
`
func TestBenchmarkData(t *testing.T) {
plugin := &Parser{}
expected := []telegraf.Metric{
metric.New(
"benchmark_a",
map[string]string{
"tags_host": "myhost",
"tags_platform": "python",
"tags_sdkver": "3.11.4",
},
map[string]interface{}{
"value": 4.0,
},
time.Unix(1653643420, 0),
),
metric.New(
"benchmark_b",
map[string]string{
"tags_host": "myhost",
"tags_platform": "python",
"tags_sdkver": "3.11.5",
},
map[string]interface{}{
"value": 5.0,
},
time.Unix(1653643420, 0),
),
}
actual, err := plugin.Parse([]byte(benchmarkData))
require.NoError(t, err)
testutil.RequireMetricsEqual(t, expected, actual, testutil.SortMetrics())
}
func BenchmarkParsing(b *testing.B) {
plugin := &Parser{}
for n := 0; n < b.N; n++ {
//nolint:errcheck // Benchmarking so skip the error check to avoid the unnecessary operations
plugin.Parse([]byte(benchmarkData))
}
}