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,51 @@
# Wavefront
The `wavefront` serializer translates the Telegraf metric format to the [Wavefront Data Format](https://docs.wavefront.com/wavefront_data_format.html).
## Configuration
```toml
[[outputs.file]]
files = ["stdout"]
## Use Strict rules to sanitize metric and tag names from invalid characters
## When enabled forward slash (/) and comma (,) will be accepted
# wavefront_use_strict = false
## point tags to use as the source name for Wavefront (if none found, host will be used)
# wavefront_source_override = ["hostname", "address", "agent_host", "node_host"]
## Data format to output.
## 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_OUTPUT.md
data_format = "wavefront"
## Users who wish their prefix paths to not be converted may set the following:
## default behavior (enabled prefix/path conversion): prod.prefix.name.metric.name
## configurable behavior (disabled prefix/path conversion): prod.prefix_name.metric_name
# wavefront_disable_prefix_conversion = true
```
## Metrics
A Wavefront metric is equivalent to a single field value of a Telegraf measurement.
The Wavefront metric name will be: `<measurement_name>.<field_name>`
If a prefix is specified it will be honored.
Only boolean and numeric metrics will be serialized, all other types will generate
an error.
## Example
The following Telegraf metric
```text
cpu,cpu=cpu0,host=testHost user=12,idle=88,system=0 1234567890
```
will serialize into the following Wavefront metrics
```text
"cpu.user" 12.000000 1234567890 source="testHost" "cpu"="cpu0"
"cpu.idle" 88.000000 1234567890 source="testHost" "cpu"="cpu0"
"cpu.system" 0.000000 1234567890 source="testHost" "cpu"="cpu0"
```

View file

@ -0,0 +1,31 @@
package wavefront
import "strings"
// catch many of the invalid chars that could appear in a metric or tag name
var sanitizedChars = strings.NewReplacer(
"!", "-", "@", "-", "#", "-", "$", "-", "%", "-", "^", "-", "&", "-",
"*", "-", "(", "-", ")", "-", "+", "-", "`", "-", "'", "-", "\"", "-",
"[", "-", "]", "-", "{", "-", "}", "-", ":", "-", ";", "-", "<", "-",
">", "-", ",", "-", "?", "-", "/", "-", "\\", "-", "|", "-", " ", "-",
"=", "-",
)
// catch many of the invalid chars that could appear in a metric or tag name
var strictSanitizedChars = strings.NewReplacer(
"!", "-", "@", "-", "#", "-", "$", "-", "%", "-", "^", "-", "&", "-",
"*", "-", "(", "-", ")", "-", "+", "-", "`", "-", "'", "-", "\"", "-",
"[", "-", "]", "-", "{", "-", "}", "-", ":", "-", ";", "-", "<", "-",
">", "-", "?", "-", "\\", "-", "|", "-", " ", "-", "=", "-",
)
var tagValueReplacer = strings.NewReplacer("\"", "\\\"", "*", "-")
var pathReplacer = strings.NewReplacer("_", ".")
func Sanitize(strict bool, val string) string {
if strict {
return strictSanitizedChars.Replace(val)
}
return sanitizedChars.Replace(val)
}

View file

@ -0,0 +1,196 @@
package wavefront
import (
"log"
"strconv"
"sync"
"github.com/influxdata/telegraf"
"github.com/influxdata/telegraf/plugins/serializers"
)
type Serializer struct {
Prefix string `toml:"prefix"`
UseStrict bool `toml:"wavefront_use_strict"`
SourceOverride []string `toml:"wavefront_source_override"`
DisablePrefixConversions bool `toml:"wavefront_disable_prefix_conversion"`
scratch buffer
mu sync.Mutex // buffer mutex
}
type MetricPoint struct {
Metric string
Value float64
Timestamp int64
Source string
Tags map[string]string
}
func (s *Serializer) serializeMetric(m telegraf.Metric) {
const metricSeparator = "."
for fieldName, value := range m.Fields() {
var name string
if fieldName == "value" {
name = s.Prefix + m.Name()
} else {
name = s.Prefix + m.Name() + metricSeparator + fieldName
}
name = Sanitize(s.UseStrict, name)
if !s.DisablePrefixConversions {
name = pathReplacer.Replace(name)
}
metricValue, valid := buildValue(value, name)
if !valid {
// bad value continue to next metric
continue
}
source, tags := s.buildTags(m.Tags())
metric := MetricPoint{
Metric: name,
Timestamp: m.Time().Unix(),
Value: metricValue,
Source: source,
Tags: tags,
}
formatMetricPoint(&s.scratch, &metric, s)
}
}
// Serialize : Serialize based on Wavefront format
func (s *Serializer) Serialize(m telegraf.Metric) ([]byte, error) {
s.mu.Lock()
s.scratch.Reset()
s.serializeMetric(m)
out := s.scratch.Copy()
s.mu.Unlock()
return out, nil
}
func (s *Serializer) SerializeBatch(metrics []telegraf.Metric) ([]byte, error) {
s.mu.Lock()
s.scratch.Reset()
for _, m := range metrics {
s.serializeMetric(m)
}
out := s.scratch.Copy()
s.mu.Unlock()
return out, nil
}
func (s *Serializer) findSourceTag(mTags map[string]string) string {
if src, ok := mTags["source"]; ok {
delete(mTags, "source")
return src
}
for _, src := range s.SourceOverride {
if source, ok := mTags[src]; ok {
delete(mTags, src)
mTags["telegraf_host"] = mTags["host"]
return source
}
}
return mTags["host"]
}
func (s *Serializer) buildTags(mTags map[string]string) (string, map[string]string) {
// Remove all empty tags.
for k, v := range mTags {
if v == "" {
delete(mTags, k)
}
}
source := s.findSourceTag(mTags)
delete(mTags, "host")
return tagValueReplacer.Replace(source), mTags
}
func buildValue(v interface{}, name string) (val float64, valid bool) {
switch p := v.(type) {
case bool:
if p {
return 1, true
}
return 0, true
case int64:
return float64(p), true
case uint64:
return float64(p), true
case float64:
return p, true
case string:
// return false but don't log
return 0, false
default:
// log a debug message
log.Printf("D! Serializer [wavefront] unexpected type: %T, with value: %v, for :%s\n",
v, v, name)
return 0, false
}
}
func formatMetricPoint(b *buffer, metricPoint *MetricPoint, s *Serializer) []byte {
b.WriteChar('"')
b.WriteString(metricPoint.Metric)
b.WriteString(`" `)
b.WriteFloat64(metricPoint.Value)
b.WriteChar(' ')
b.WriteUint64(uint64(metricPoint.Timestamp))
b.WriteString(` source="`)
b.WriteString(metricPoint.Source)
b.WriteChar('"')
for k, v := range metricPoint.Tags {
b.WriteString(` "`)
b.WriteString(Sanitize(s.UseStrict, k))
b.WriteString(`"="`)
b.WriteString(tagValueReplacer.Replace(v))
b.WriteChar('"')
}
b.WriteChar('\n')
return *b
}
type buffer []byte
func (b *buffer) Reset() { *b = (*b)[:0] }
func (b *buffer) Copy() []byte {
p := make([]byte, 0, len(*b))
return append(p, *b...)
}
func (b *buffer) WriteString(s string) {
*b = append(*b, s...)
}
// WriteChar has this name instead of WriteByte because the 'stdmethods' check
// of 'go vet' wants WriteByte to have the signature:
//
// func (b *buffer) WriteByte(c byte) error { ... }
func (b *buffer) WriteChar(c byte) {
*b = append(*b, c)
}
func (b *buffer) WriteUint64(val uint64) {
*b = strconv.AppendUint(*b, val, 10)
}
func (b *buffer) WriteFloat64(val float64) {
*b = strconv.AppendFloat(*b, val, 'f', 6, 64)
}
func init() {
serializers.Add("wavefront",
func() telegraf.Serializer {
return &Serializer{}
},
)
}

View file

@ -0,0 +1,311 @@
package wavefront
import (
"fmt"
"reflect"
"strings"
"testing"
"time"
"github.com/stretchr/testify/require"
"github.com/influxdata/telegraf/metric"
"github.com/influxdata/telegraf/plugins/serializers"
)
func TestBuildTags(t *testing.T) {
var tagTests = []struct {
ptIn map[string]string
outTags map[string]string
outSource string
}{
{
map[string]string{"one": "two", "three": "four", "host": "testHost"},
map[string]string{"one": "two", "three": "four"},
"testHost",
},
{
map[string]string{"aaa": "bbb", "host": "testHost"},
map[string]string{"aaa": "bbb"},
"testHost",
},
{
map[string]string{"bbb": "789", "aaa": "123", "host": "testHost"},
map[string]string{"aaa": "123", "bbb": "789"},
"testHost",
},
{
map[string]string{"host": "aaa", "dc": "bbb"},
map[string]string{"dc": "bbb"},
"aaa",
},
{
map[string]string{"instanceid": "i-0123456789", "host": "aaa", "dc": "bbb"},
map[string]string{"dc": "bbb", "telegraf_host": "aaa"},
"i-0123456789",
},
{
map[string]string{"instance-id": "i-0123456789", "host": "aaa", "dc": "bbb"},
map[string]string{"dc": "bbb", "telegraf_host": "aaa"},
"i-0123456789",
},
{
map[string]string{"instanceid": "i-0123456789", "host": "aaa", "hostname": "ccc", "dc": "bbb"},
map[string]string{"dc": "bbb", "hostname": "ccc", "telegraf_host": "aaa"},
"i-0123456789",
},
{
map[string]string{"instanceid": "i-0123456789", "host": "aaa", "snmp_host": "ccc", "dc": "bbb"},
map[string]string{"dc": "bbb", "snmp_host": "ccc", "telegraf_host": "aaa"},
"i-0123456789",
},
{
map[string]string{"host": "aaa", "snmp_host": "ccc", "dc": "bbb"},
map[string]string{"dc": "bbb", "telegraf_host": "aaa"},
"ccc",
},
}
s := &Serializer{SourceOverride: []string{"instanceid", "instance-id", "hostname", "snmp_host", "node_host"}}
for _, tt := range tagTests {
source, tags := s.buildTags(tt.ptIn)
if !reflect.DeepEqual(tags, tt.outTags) {
t.Errorf("\nexpected\t%+v\nreceived\t%+v\n", tt.outTags, tags)
}
if source != tt.outSource {
t.Errorf("\nexpected\t%s\nreceived\t%s\n", tt.outSource, source)
}
}
}
func TestBuildTagsHostTag(t *testing.T) {
var tagTests = []struct {
ptIn map[string]string
outTags map[string]string
outSource string
}{
{
map[string]string{"one": "two", "host": "testHost", "snmp_host": "snmpHost"},
map[string]string{"telegraf_host": "testHost", "one": "two"},
"snmpHost",
},
}
s := &Serializer{SourceOverride: []string{"snmp_host"}}
for _, tt := range tagTests {
source, tags := s.buildTags(tt.ptIn)
if !reflect.DeepEqual(tags, tt.outTags) {
t.Errorf("\nexpected\t%+v\nreceived\t%+v\n", tt.outTags, tags)
}
if source != tt.outSource {
t.Errorf("\nexpected\t%s\nreceived\t%s\n", tt.outSource, source)
}
}
}
func TestFormatMetricPoint(t *testing.T) {
var pointTests = []struct {
ptIn *MetricPoint
out string
}{
{
&MetricPoint{
Metric: "cpu.idle",
Value: 1,
Timestamp: 1554172967,
Source: "testHost",
Tags: map[string]string{"aaa": "bbb"},
},
"\"cpu.idle\" 1.000000 1554172967 source=\"testHost\" \"aaa\"=\"bbb\"\n",
},
{
&MetricPoint{
Metric: "cpu.idle",
Value: 1,
Timestamp: 1554172967,
Source: "testHost",
Tags: map[string]string{"sp&c!al/chars,": "get*replaced"},
},
"\"cpu.idle\" 1.000000 1554172967 source=\"testHost\" \"sp-c-al-chars-\"=\"get-replaced\"\n",
},
}
s := &Serializer{}
for _, pt := range pointTests {
bout := formatMetricPoint(new(buffer), pt.ptIn, s)
sout := string(bout[:])
if sout != pt.out {
t.Errorf("\nexpected\t%s\nreceived\t%s\n", pt.out, sout)
}
}
}
func TestUseStrict(t *testing.T) {
var pointTests = []struct {
ptIn *MetricPoint
out string
}{
{
&MetricPoint{
Metric: "cpu.idle",
Value: 1,
Timestamp: 1554172967,
Source: "testHost",
Tags: map[string]string{"sp&c!al/chars,": "get*replaced"},
},
"\"cpu.idle\" 1.000000 1554172967 source=\"testHost\" \"sp-c-al/chars,\"=\"get-replaced\"\n",
},
}
s := &Serializer{UseStrict: true}
for _, pt := range pointTests {
bout := formatMetricPoint(new(buffer), pt.ptIn, s)
sout := string(bout[:])
if sout != pt.out {
t.Errorf("\nexpected\t%s\nreceived\t%s\n", pt.out, sout)
}
}
}
func TestSerializeMetricFloat(t *testing.T) {
now := time.Now()
tags := map[string]string{
"cpu": "cpu0",
"host": "realHost",
}
fields := map[string]interface{}{
"usage_idle": float64(91.5),
}
m := metric.New("cpu", tags, fields, now)
s := &Serializer{}
buf, err := s.Serialize(m)
require.NoError(t, err)
mS := strings.Split(strings.TrimSpace(string(buf)), "\n")
expS := []string{fmt.Sprintf("\"cpu.usage.idle\" 91.500000 %d source=\"realHost\" \"cpu\"=\"cpu0\"", now.UnixNano()/1000000000)}
require.Equal(t, expS, mS)
}
func TestSerializeMetricInt(t *testing.T) {
now := time.Now()
tags := map[string]string{
"cpu": "cpu0",
"host": "realHost",
}
fields := map[string]interface{}{
"usage_idle": int64(91),
}
m := metric.New("cpu", tags, fields, now)
s := &Serializer{}
buf, err := s.Serialize(m)
require.NoError(t, err)
mS := strings.Split(strings.TrimSpace(string(buf)), "\n")
expS := []string{fmt.Sprintf("\"cpu.usage.idle\" 91.000000 %d source=\"realHost\" \"cpu\"=\"cpu0\"", now.UnixNano()/1000000000)}
require.Equal(t, expS, mS)
}
func TestSerializeMetricBoolTrue(t *testing.T) {
now := time.Now()
tags := map[string]string{
"cpu": "cpu0",
"host": "realHost",
}
fields := map[string]interface{}{
"usage_idle": true,
}
m := metric.New("cpu", tags, fields, now)
s := &Serializer{}
buf, err := s.Serialize(m)
require.NoError(t, err)
mS := strings.Split(strings.TrimSpace(string(buf)), "\n")
expS := []string{fmt.Sprintf("\"cpu.usage.idle\" 1.000000 %d source=\"realHost\" \"cpu\"=\"cpu0\"", now.UnixNano()/1000000000)}
require.Equal(t, expS, mS)
}
func TestSerializeMetricBoolFalse(t *testing.T) {
now := time.Now()
tags := map[string]string{
"cpu": "cpu0",
"host": "realHost",
}
fields := map[string]interface{}{
"usage_idle": false,
}
m := metric.New("cpu", tags, fields, now)
s := &Serializer{}
buf, err := s.Serialize(m)
require.NoError(t, err)
mS := strings.Split(strings.TrimSpace(string(buf)), "\n")
expS := []string{fmt.Sprintf("\"cpu.usage.idle\" 0.000000 %d source=\"realHost\" \"cpu\"=\"cpu0\"", now.UnixNano()/1000000000)}
require.Equal(t, expS, mS)
}
func TestSerializeMetricFieldValue(t *testing.T) {
now := time.Now()
tags := map[string]string{
"cpu": "cpu0",
"host": "realHost",
}
fields := map[string]interface{}{
"value": int64(91),
}
m := metric.New("cpu", tags, fields, now)
s := &Serializer{}
buf, err := s.Serialize(m)
require.NoError(t, err)
mS := strings.Split(strings.TrimSpace(string(buf)), "\n")
expS := []string{fmt.Sprintf("\"cpu\" 91.000000 %d source=\"realHost\" \"cpu\"=\"cpu0\"", now.UnixNano()/1000000000)}
require.Equal(t, expS, mS)
}
func TestSerializeMetricPrefix(t *testing.T) {
now := time.Now()
tags := map[string]string{
"cpu": "cpu0",
"host": "realHost",
}
fields := map[string]interface{}{
"usage_idle": int64(91),
}
m := metric.New("cpu", tags, fields, now)
s := &Serializer{Prefix: "telegraf."}
buf, err := s.Serialize(m)
require.NoError(t, err)
mS := strings.Split(strings.TrimSpace(string(buf)), "\n")
expS := []string{fmt.Sprintf("\"telegraf.cpu.usage.idle\" 91.000000 %d source=\"realHost\" \"cpu\"=\"cpu0\"", now.UnixNano()/1000000000)}
require.Equal(t, expS, mS)
}
func BenchmarkSerialize(b *testing.B) {
s := &Serializer{}
metrics := serializers.BenchmarkMetrics(b)
b.ResetTimer()
for i := 0; i < b.N; i++ {
_, err := s.Serialize(metrics[i%len(metrics)])
require.NoError(b, err)
}
}
func BenchmarkSerializeBatch(b *testing.B) {
s := &Serializer{}
m := serializers.BenchmarkMetrics(b)
metrics := m[:]
b.ResetTimer()
for i := 0; i < b.N; i++ {
_, err := s.SerializeBatch(metrics)
require.NoError(b, err)
}
}