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,73 @@
# Warp10 Output Plugin
This plugin writes metrics to the [Warp 10][warp10] service.
⭐ Telegraf v1.14.0
🏷️ cloud, datastore
💻 all
[warp10]: https://www.warp10.io
## 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 `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
# Write metrics to Warp 10
[[outputs.warp10]]
# Prefix to add to the measurement.
prefix = "telegraf."
# URL of the Warp 10 server
warp_url = "http://localhost:8080"
# Write token to access your app on warp 10
token = "Token"
# Warp 10 query timeout
# timeout = "15s"
## Print Warp 10 error body
# print_error_body = false
## Max string error size
# max_string_error_size = 511
## Optional TLS Config
# tls_ca = "/etc/telegraf/ca.pem"
# tls_cert = "/etc/telegraf/cert.pem"
# tls_key = "/etc/telegraf/key.pem"
## Use TLS but skip chain & host verification
# insecure_skip_verify = false
```
## Output Format
Metrics are converted and sent using the [Geo Time Series][] (GTS) input format.
The class name of the reading is produced by combining the value of the
`prefix` option, the measurement name, and the field key. A dot (`.`)
character is used as the joining character.
The GTS form provides support for the Telegraf integer, float, boolean, and
string types directly. Unsigned integer fields will be capped to the largest
64-bit integer (2^63-1) in case of overflow.
Timestamps are sent in microsecond precision.
[Geo Time Series]: https://www.warp10.io/content/03_Documentation/03_Interacting_with_Warp_10/03_Ingesting_data/02_GTS_input_format

View file

@ -0,0 +1,26 @@
# Write metrics to Warp 10
[[outputs.warp10]]
# Prefix to add to the measurement.
prefix = "telegraf."
# URL of the Warp 10 server
warp_url = "http://localhost:8080"
# Write token to access your app on warp 10
token = "Token"
# Warp 10 query timeout
# timeout = "15s"
## Print Warp 10 error body
# print_error_body = false
## Max string error size
# max_string_error_size = 511
## Optional TLS Config
# tls_ca = "/etc/telegraf/ca.pem"
# tls_cert = "/etc/telegraf/cert.pem"
# tls_key = "/etc/telegraf/key.pem"
## Use TLS but skip chain & host verification
# insecure_skip_verify = false

View file

@ -0,0 +1,299 @@
//go:generate ../../../tools/readme_config_includer/generator
package warp10
import (
"bytes"
_ "embed"
"errors"
"fmt"
"io"
"math"
"net/http"
"net/url"
"sort"
"strconv"
"strings"
"time"
"github.com/influxdata/telegraf"
"github.com/influxdata/telegraf/config"
"github.com/influxdata/telegraf/plugins/common/tls"
"github.com/influxdata/telegraf/plugins/outputs"
)
//go:embed sample.conf
var sampleConfig string
const (
defaultClientTimeout = 15 * time.Second
)
// Warp10 output plugin
type Warp10 struct {
Prefix string `toml:"prefix"`
WarpURL string `toml:"warp_url"`
Token config.Secret `toml:"token"`
Timeout config.Duration `toml:"timeout"`
PrintErrorBody bool `toml:"print_error_body"`
MaxStringErrorSize int `toml:"max_string_error_size"`
client *http.Client
tls.ClientConfig
Log telegraf.Logger `toml:"-"`
}
// MetricLine Warp 10 metrics
type MetricLine struct {
Metric string
Timestamp int64
Value string
Tags string
}
func (w *Warp10) createClient() (*http.Client, error) {
tlsCfg, err := w.ClientConfig.TLSConfig()
if err != nil {
return nil, err
}
if w.Timeout == 0 {
w.Timeout = config.Duration(defaultClientTimeout)
}
client := &http.Client{
Transport: &http.Transport{
TLSClientConfig: tlsCfg,
Proxy: http.ProxyFromEnvironment,
},
Timeout: time.Duration(w.Timeout),
}
return client, nil
}
func (*Warp10) SampleConfig() string {
return sampleConfig
}
// Connect to warp10
func (w *Warp10) Connect() error {
client, err := w.createClient()
if err != nil {
return err
}
w.client = client
return nil
}
// GenWarp10Payload compute Warp 10 metrics payload
func (w *Warp10) GenWarp10Payload(metrics []telegraf.Metric) string {
collectString := make([]string, 0)
for _, mm := range metrics {
for _, field := range mm.FieldList() {
metric := &MetricLine{
Metric: fmt.Sprintf("%s%s", w.Prefix, mm.Name()+"."+field.Key),
Timestamp: mm.Time().UnixNano() / 1000,
}
metricValue, err := buildValue(field.Value)
if err != nil {
w.Log.Errorf("Could not encode value: %v", err)
continue
}
metric.Value = metricValue
tagsSlice := buildTags(mm.TagList())
metric.Tags = strings.Join(tagsSlice, ",")
messageLine := fmt.Sprintf("%d// %s{%s} %s\n", metric.Timestamp, metric.Metric, metric.Tags, metric.Value)
collectString = append(collectString, messageLine)
}
}
return strings.Join(collectString, "")
}
// Write metrics to Warp10
func (w *Warp10) Write(metrics []telegraf.Metric) error {
payload := w.GenWarp10Payload(metrics)
if payload == "" {
return nil
}
addr := w.WarpURL + "/api/v0/update"
req, err := http.NewRequest("POST", addr, bytes.NewBufferString(payload))
if err != nil {
return fmt.Errorf("unable to create new request %q: %w", addr, err)
}
req.Header.Set("Content-Type", "text/plain")
token, err := w.Token.Get()
if err != nil {
return fmt.Errorf("getting token failed: %w", err)
}
req.Header.Set("X-Warp10-Token", token.String())
token.Destroy()
resp, err := w.client.Do(req)
if err != nil {
return err
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
if w.PrintErrorBody {
//nolint:errcheck // err can be ignored since it is just for logging
body, _ := io.ReadAll(resp.Body)
return errors.New(w.WarpURL + ": " + HandleError(string(body), w.MaxStringErrorSize))
}
if len(resp.Status) < w.MaxStringErrorSize {
return errors.New(w.WarpURL + ": " + resp.Status)
}
return errors.New(w.WarpURL + ": " + resp.Status[0:w.MaxStringErrorSize])
}
return nil
}
func buildTags(tags []*telegraf.Tag) []string {
tagsString := make([]string, 0, len(tags)+1)
for _, tag := range tags {
key := url.QueryEscape(tag.Key)
value := url.QueryEscape(tag.Value)
tagsString = append(tagsString, fmt.Sprintf("%s=%s", key, value))
}
tagsString = append(tagsString, "source=telegraf")
sort.Strings(tagsString)
return tagsString
}
func buildValue(v interface{}) (string, error) {
var retv string
switch p := v.(type) {
case int64:
retv = intToString(p)
case string:
retv = fmt.Sprintf("'%s'", strings.ReplaceAll(p, "'", "\\'"))
case bool:
retv = boolToString(p)
case uint64:
if p <= uint64(math.MaxInt64) {
retv = strconv.FormatInt(int64(p), 10)
} else {
retv = strconv.FormatInt(math.MaxInt64, 10)
}
case float64:
retv = floatToString(p)
default:
return "", fmt.Errorf("unsupported type: %T", v)
}
return retv, nil
}
func intToString(inputNum int64) string {
return strconv.FormatInt(inputNum, 10)
}
func boolToString(inputBool bool) string {
return strconv.FormatBool(inputBool)
}
/*
Warp10 supports Infinity/-Infinity/NaN
<'
// class{label=value} 42.0
0// class-1{label=value}{attribute=value} 42
=1// Infinity
'>
PARSE
<'
// class{label=value} 42.0
0// class-1{label=value}{attribute=value} 42
=1// -Infinity
'>
PARSE
<'
// class{label=value} 42.0
0// class-1{label=value}{attribute=value} 42
=1// NaN
'>
PARSE
*/
func floatToString(inputNum float64) string {
switch {
case math.IsNaN(inputNum):
return "NaN"
case math.IsInf(inputNum, -1):
return "-Infinity"
case math.IsInf(inputNum, 1):
return "Infinity"
}
return strconv.FormatFloat(inputNum, 'f', 6, 64)
}
// Close close
func (*Warp10) Close() error {
return nil
}
// Init Warp10 struct
func (w *Warp10) Init() error {
if w.MaxStringErrorSize <= 0 {
w.MaxStringErrorSize = 511
}
return nil
}
func init() {
outputs.Add("warp10", func() telegraf.Output {
return &Warp10{}
})
}
// HandleError read http error body and return a corresponding error
func HandleError(body string, maxStringSize int) string {
if body == "" {
return "Empty return"
}
if strings.Contains(body, "Invalid token") {
return "Invalid token"
}
if strings.Contains(body, "Write token missing") {
return "Write token missing"
}
if strings.Contains(body, "Token Expired") {
return "Token Expired"
}
if strings.Contains(body, "Token revoked") {
return "Token revoked"
}
if strings.Contains(body, "exceed your Monthly Active Data Streams limit") || strings.Contains(body, "exceed the Monthly Active Data Streams limit") {
return "Exceeded Monthly Active Data Streams limit"
}
if strings.Contains(body, "Daily Data Points limit being already exceeded") {
return "Exceeded Daily Data Points limit"
}
if strings.Contains(body, "Application suspended or closed") {
return "Application suspended or closed"
}
if strings.Contains(body, "broken pipe") {
return "broken pipe"
}
if len(body) < maxStringSize {
return body
}
return body[0:maxStringSize]
}

View file

@ -0,0 +1,150 @@
package warp10
import (
"math"
"testing"
"github.com/stretchr/testify/require"
"github.com/influxdata/telegraf/config"
"github.com/influxdata/telegraf/testutil"
)
type ErrorTest struct {
Message string
Expected string
}
func TestWriteWarp10(t *testing.T) {
w := Warp10{
Prefix: "unit.test",
WarpURL: "http://localhost:8090",
Token: config.NewSecret([]byte("WRITE")),
}
payload := w.GenWarp10Payload(testutil.MockMetrics())
require.Exactly(t, "1257894000000000// unit.testtest1.value{source=telegraf,tag1=value1} 1.000000\n", payload)
}
func TestWriteWarp10ValueNaN(t *testing.T) {
w := Warp10{
Prefix: "unit.test",
WarpURL: "http://localhost:8090",
Token: config.NewSecret([]byte("WRITE")),
}
payload := w.GenWarp10Payload(testutil.MockMetricsWithValue(math.NaN()))
require.Exactly(t, "1257894000000000// unit.testtest1.value{source=telegraf,tag1=value1} NaN\n", payload)
}
func TestWriteWarp10ValueInfinity(t *testing.T) {
w := Warp10{
Prefix: "unit.test",
WarpURL: "http://localhost:8090",
Token: config.NewSecret([]byte("WRITE")),
}
payload := w.GenWarp10Payload(testutil.MockMetricsWithValue(math.Inf(1)))
require.Exactly(t, "1257894000000000// unit.testtest1.value{source=telegraf,tag1=value1} Infinity\n", payload)
}
func TestWriteWarp10ValueMinusInfinity(t *testing.T) {
w := Warp10{
Prefix: "unit.test",
WarpURL: "http://localhost:8090",
Token: config.NewSecret([]byte("WRITE")),
}
payload := w.GenWarp10Payload(testutil.MockMetricsWithValue(math.Inf(-1)))
require.Exactly(t, "1257894000000000// unit.testtest1.value{source=telegraf,tag1=value1} -Infinity\n", payload)
}
func TestWriteWarp10EncodedTags(t *testing.T) {
w := Warp10{
Prefix: "unit.test",
WarpURL: "http://localhost:8090",
Token: config.NewSecret([]byte("WRITE")),
}
metrics := testutil.MockMetrics()
for _, metric := range metrics {
metric.AddTag("encoded{tag", "value1,value2")
}
payload := w.GenWarp10Payload(metrics)
require.Exactly(t, "1257894000000000// unit.testtest1.value{encoded%7Btag=value1%2Cvalue2,source=telegraf,tag1=value1} 1.000000\n", payload)
}
func TestHandleWarp10Error(t *testing.T) {
tests := [...]*ErrorTest{
{
Message: `
<html>
<head>
<meta http-equiv="Content-Type" content="text/html;charset=utf-8"/>
<title>Error 500 io.warp10.script.WarpScriptException: Invalid token.</title>
</head>
<body><h2>HTTP ERROR 500</h2>
<p>Problem accessing /api/v0/update. Reason:
<pre> io.warp10.script.WarpScriptException: Invalid token.</pre></p>
</body>
</html>
`,
Expected: "Invalid token",
},
{
Message: `
<html>
<head>
<meta http-equiv="Content-Type" content="text/html;charset=utf-8"/>
<title>Error 500 io.warp10.script.WarpScriptException: Token Expired.</title>
</head>
<body><h2>HTTP ERROR 500</h2>
<p>Problem accessing /api/v0/update. Reason:
<pre> io.warp10.script.WarpScriptException: Token Expired.</pre></p>
</body>
</html>
`,
Expected: "Token Expired",
},
{
Message: `
<html>
<head>
<meta http-equiv="Content-Type" content="text/html;charset=utf-8"/>
<title>Error 500 io.warp10.script.WarpScriptException: Token revoked.</title>
</head>
<body><h2>HTTP ERROR 500</h2>
<p>Problem accessing /api/v0/update. Reason:
<pre> io.warp10.script.WarpScriptException: Token revoked.</pre></p>
</body>
</html>
`,
Expected: "Token revoked",
},
{
Message: `
<html>
<head>
<meta http-equiv="Content-Type" content="text/html;charset=utf-8"/>
<title>Error 500 io.warp10.script.WarpScriptException: Write token missing.</title>
</head>
<body><h2>HTTP ERROR 500</h2>
<p>Problem accessing /api/v0/update. Reason:
<pre> io.warp10.script.WarpScriptException: Write token missing.</pre></p>
</body>
</html>
`,
Expected: "Write token missing",
},
{
Message: `<title>Error 503: server unavailable</title>`,
Expected: "<title>Error 503: server unavailable</title>",
},
}
for _, handledError := range tests {
payload := HandleError(handledError.Message, 511)
require.Exactly(t, handledError.Expected, payload)
}
}