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 @@
# Instrumental Output Plugin
This plugin writes metrics to the [Instrumental Collector API][instrumental]
and requires a project-specific API token.
Instrumental accepts stats in a format very close to Graphite, with the only
difference being that the type of stat (gauge, increment) is the first token,
separated from the metric itself by whitespace. The `increment` type is only
used if the metric comes in as a counter via the [statsd input plugin][statsd].
⭐ Telegraf v0.13.1
🏷️ applications
💻 all
[instrumental]: https://instrumentalapp.com/docs/tcp-collector
[statsd]: /plugins/inputs/statsd/README.md
## 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
## Secret-store support
This plugin supports secrets from secret-stores for the `api_token` option.
See the [secret-store documentation][SECRETSTORE] for more details on how
to use them.
[SECRETSTORE]: ../../../docs/CONFIGURATION.md#secret-store-secrets
## Configuration
```toml @sample.conf
# Configuration for sending metrics to an Instrumental project
[[outputs.instrumental]]
## Project API Token (required)
api_token = "API Token" # required
## Prefix the metrics with a given name
prefix = ""
## Stats output template (Graphite formatting)
## see https://github.com/influxdata/telegraf/blob/master/docs/DATA_FORMATS_OUTPUT.md#graphite
template = "host.tags.measurement.field"
## Timeout in seconds to connect
timeout = "2s"
## Debug true - Print communication to Instrumental
debug = false
```

View file

@ -0,0 +1,212 @@
//go:generate ../../../tools/readme_config_includer/generator
package instrumental
import (
"bytes"
_ "embed"
"errors"
"fmt"
"io"
"net"
"regexp"
"strings"
"time"
"github.com/influxdata/telegraf"
"github.com/influxdata/telegraf/config"
"github.com/influxdata/telegraf/plugins/outputs"
"github.com/influxdata/telegraf/plugins/serializers/graphite"
)
//go:embed sample.conf
var sampleConfig string
var (
ValueIncludesBadChar = regexp.MustCompile("[^[:digit:].]")
MetricNameReplacer = regexp.MustCompile("[^-[:alnum:]_.]+")
)
type Instrumental struct {
Host string `toml:"host"`
Port int `toml:"port"`
APIToken config.Secret `toml:"api_token"`
Prefix string `toml:"prefix"`
DataFormat string `toml:"data_format"`
Template string `toml:"template"`
Templates []string `toml:"templates"`
Timeout config.Duration `toml:"timeout"`
Debug bool `toml:"debug"`
Log telegraf.Logger `toml:"-"`
conn net.Conn
serializer *graphite.GraphiteSerializer
}
const (
DefaultHost = "collector.instrumentalapp.com"
DefaultPort = 8000
HelloMessage = "hello version go/telegraf/1.1\n"
AuthFormat = "authenticate %s\n"
HandshakeFormat = HelloMessage + AuthFormat
)
func (*Instrumental) SampleConfig() string {
return sampleConfig
}
func (i *Instrumental) Init() error {
s := &graphite.GraphiteSerializer{
Prefix: i.Prefix,
Template: i.Template,
TagSanitizeMode: "strict",
Separator: ".",
Templates: i.Templates,
}
if err := s.Init(); err != nil {
return err
}
i.serializer = s
return nil
}
func (i *Instrumental) Connect() error {
addr := fmt.Sprintf("%s:%d", i.Host, i.Port)
connection, err := net.DialTimeout("tcp", addr, time.Duration(i.Timeout))
if err != nil {
i.conn = nil
return err
}
err = i.authenticate(connection)
if err != nil {
i.conn = nil
return err
}
return nil
}
func (i *Instrumental) Close() error {
err := i.conn.Close()
i.conn = nil
return err
}
func (i *Instrumental) Write(metrics []telegraf.Metric) error {
if i.conn == nil {
err := i.Connect()
if err != nil {
return fmt.Errorf("failed to (re)connect to Instrumental. Error: %w", err)
}
}
var points []string
var metricType string
for _, m := range metrics {
// Pull the metric_type out of the metric's tags. We don't want the type
// to show up with the other tags pulled from the system, as they go in the
// beginning of the line instead.
// e.g we want:
//
// increment some_prefix.host.tag1.tag2.tag3.field value timestamp
//
// vs
//
// increment some_prefix.host.tag1.tag2.tag3.counter.field value timestamp
//
metricType = m.Tags()["metric_type"]
m.RemoveTag("metric_type")
buf, err := i.serializer.Serialize(m)
if err != nil {
i.Log.Debugf("Could not serialize metric: %v", err)
continue
}
switch metricType {
case "counter":
fallthrough
case "histogram":
metricType = "increment"
default:
metricType = "gauge"
}
buffer := bytes.NewBuffer(buf)
for {
line, err := buffer.ReadBytes('\n')
if err != nil {
break
}
stat := string(line)
// decompose "metric.name value time"
splitStat := strings.SplitN(stat, " ", 3)
name := splitStat[0]
value := splitStat[1]
timestamp := splitStat[2]
// replace invalid components of metric name with underscore
cleanMetric := MetricNameReplacer.ReplaceAllString(name, "_")
if !ValueIncludesBadChar.MatchString(value) {
points = append(points, fmt.Sprintf("%s %s %s %s", metricType, cleanMetric, value, timestamp))
}
}
}
allPoints := strings.Join(points, "")
if _, err := fmt.Fprint(i.conn, allPoints); err != nil {
if errors.Is(err, io.EOF) {
_ = i.Close()
}
return err
}
// force the connection closed after sending data
// to deal with various disconnection scenarios and eschew holding
// open idle connections en masse
_ = i.Close()
return nil
}
func (i *Instrumental) authenticate(conn net.Conn) error {
token, err := i.APIToken.Get()
if err != nil {
return fmt.Errorf("getting token failed: %w", err)
}
defer token.Destroy()
if _, err := fmt.Fprintf(conn, HandshakeFormat, token.TemporaryString()); err != nil {
return err
}
// The response here will either be two "ok"s or an error message.
responses := make([]byte, 512)
if _, err = conn.Read(responses); err != nil {
return err
}
if string(responses)[:6] != "ok\nok\n" {
return fmt.Errorf("authentication failed: %s", responses)
}
i.conn = conn
return nil
}
func init() {
outputs.Add("instrumental", func() telegraf.Output {
return &Instrumental{
Host: DefaultHost,
Port: DefaultPort,
Template: graphite.DefaultTemplate,
}
})
}

View file

@ -0,0 +1,243 @@
package instrumental
import (
"bufio"
"errors"
"io"
"net"
"net/textproto"
"sync"
"testing"
"time"
"github.com/stretchr/testify/require"
"github.com/influxdata/telegraf"
"github.com/influxdata/telegraf/config"
"github.com/influxdata/telegraf/metric"
)
func TestWrite(t *testing.T) {
var wg sync.WaitGroup
wg.Add(1)
port := TCPServer(t, &wg)
i := Instrumental{
Host: "127.0.0.1",
Port: port,
APIToken: config.NewSecret([]byte("abc123token")),
Prefix: "my.prefix",
}
require.NoError(t, i.Init())
// Default to gauge
m1 := metric.New(
"mymeasurement",
map[string]string{"host": "192.168.0.1"},
map[string]interface{}{"myfield": float64(3.14)},
time.Date(2010, time.November, 10, 23, 0, 0, 0, time.UTC),
)
m2 := metric.New(
"mymeasurement",
map[string]string{"host": "192.168.0.1", "metric_type": "set"},
map[string]interface{}{"value": float64(3.14)},
time.Date(2010, time.November, 10, 23, 0, 0, 0, time.UTC),
)
metrics := []telegraf.Metric{m1, m2}
err := i.Write(metrics)
require.NoError(t, err)
// Counter and Histogram are increments
m3 := metric.New(
"my_histogram",
map[string]string{"host": "192.168.0.1", "metric_type": "histogram"},
map[string]interface{}{"value": float64(3.14)},
time.Date(2010, time.November, 10, 23, 0, 0, 0, time.UTC),
)
// We will modify metric names that won't be accepted by Instrumental
m4 := metric.New(
"bad_metric_name",
map[string]string{"host": "192.168.0.1:8888::123", "metric_type": "counter"},
map[string]interface{}{"value": 1},
time.Date(2010, time.November, 10, 23, 0, 0, 0, time.UTC),
)
// We will drop metric values that won't be accepted by Instrumental
m5 := metric.New(
"bad_values",
map[string]string{"host": "192.168.0.1", "metric_type": "counter"},
map[string]interface{}{"value": "\" 3:30\""},
time.Date(2010, time.November, 10, 23, 0, 0, 0, time.UTC),
)
m6 := metric.New(
"my_counter",
map[string]string{"host": "192.168.0.1", "metric_type": "counter"},
map[string]interface{}{"value": float64(3.14)},
time.Date(2010, time.November, 10, 23, 0, 0, 0, time.UTC),
)
metrics = []telegraf.Metric{m3, m4, m5, m6}
err = i.Write(metrics)
require.NoError(t, err)
wg.Wait()
}
func TCPServer(t *testing.T, wg *sync.WaitGroup) int {
tcpServer, err := net.Listen("tcp", "127.0.0.1:0")
require.NoError(t, err)
go func() {
defer wg.Done()
defer tcpServer.Close()
conn, err := tcpServer.Accept()
if err != nil {
t.Error(err)
return
}
defer func() {
if err := conn.Close(); err != nil {
t.Error(err)
}
}()
err = conn.SetDeadline(time.Now().Add(1 * time.Second))
if err != nil {
t.Error(err)
return
}
reader := bufio.NewReader(conn)
tp := textproto.NewReader(reader)
helloExpected := "hello version go/telegraf/1.1"
hello, err := tp.ReadLine()
if err != nil {
t.Error(err)
return
} else if hello != helloExpected {
t.Errorf("expected %q, got %q", helloExpected, hello)
return
}
authExpected := "authenticate abc123token"
auth, err := tp.ReadLine()
if err != nil {
t.Error(err)
return
} else if auth != authExpected {
t.Errorf("expected %q, got %q", authExpected, auth)
return
}
_, err = conn.Write([]byte("ok\nok\n"))
if err != nil {
t.Error(err)
return
}
data1Expected := "gauge my.prefix.192_168_0_1.mymeasurement.myfield 3.14 1289430000"
data1, err := tp.ReadLine()
if err != nil {
t.Error(err)
return
} else if data1 != data1Expected {
t.Errorf("expected %q, got %q", data1Expected, data1)
return
}
data2Expected := "gauge my.prefix.192_168_0_1.mymeasurement 3.14 1289430000"
data2, err := tp.ReadLine()
if err != nil {
t.Error(err)
return
} else if data2 != data2Expected {
t.Errorf("expected %q, got %q", data2Expected, data2)
return
}
conn, err = tcpServer.Accept()
if err != nil {
t.Error(err)
return
}
err = conn.SetDeadline(time.Now().Add(1 * time.Second))
if err != nil {
t.Error(err)
return
}
reader = bufio.NewReader(conn)
tp = textproto.NewReader(reader)
helloExpected = "hello version go/telegraf/1.1"
hello, err = tp.ReadLine()
if err != nil {
t.Error(err)
return
} else if hello != helloExpected {
t.Errorf("expected %q, got %q", helloExpected, hello)
return
}
authExpected = "authenticate abc123token"
auth, err = tp.ReadLine()
if err != nil {
t.Error(err)
return
} else if auth != authExpected {
t.Errorf("expected %q, got %q", authExpected, auth)
return
}
_, err = conn.Write([]byte("ok\nok\n"))
if err != nil {
t.Error(err)
return
}
data3Expected := "increment my.prefix.192_168_0_1.my_histogram 3.14 1289430000"
data3, err := tp.ReadLine()
if err != nil {
t.Error(err)
return
} else if data3 != data3Expected {
t.Errorf("expected %q, got %q", data3Expected, data3)
return
}
data4Expected := "increment my.prefix.192_168_0_1_8888_123.bad_metric_name 1 1289430000"
data4, err := tp.ReadLine()
if err != nil {
t.Error(err)
return
} else if data4 != data4Expected {
t.Errorf("expected %q, got %q", data4Expected, data4)
return
}
data5Expected := "increment my.prefix.192_168_0_1.my_counter 3.14 1289430000"
data5, err := tp.ReadLine()
if err != nil {
t.Error(err)
return
} else if data5 != data5Expected {
t.Errorf("expected %q, got %q", data5Expected, data5)
return
}
data6Expected := ""
data6, err := tp.ReadLine()
if !errors.Is(err, io.EOF) {
t.Error(err)
return
} else if data6 != data6Expected {
t.Errorf("expected %q, got %q", data6Expected, data6)
return
}
}()
return tcpServer.Addr().(*net.TCPAddr).Port
}

View file

@ -0,0 +1,13 @@
# Configuration for sending metrics to an Instrumental project
[[outputs.instrumental]]
## Project API Token (required)
api_token = "API Token" # required
## Prefix the metrics with a given name
prefix = ""
## Stats output template (Graphite formatting)
## see https://github.com/influxdata/telegraf/blob/master/docs/DATA_FORMATS_OUTPUT.md#graphite
template = "host.tags.measurement.field"
## Timeout in seconds to connect
timeout = "2s"
## Debug true - Print communication to Instrumental
debug = false