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,59 @@
# Librato Output Plugin
This plugin writes metrics to the [Librato][librato] service. It requires an
`api_user` and `api_token` which can be obtained [here][tokens] for your
account.
The `source_tag` option in the Configuration file is used to send contextual
information from Point Tags to the API. Besides from this, the plugin currently
does not send any additional associated Point Tags.
> [!IMPOTANT]
> If the point value being sent cannot be converted to a `float64`, the metric
> is skipped.
⭐ Telegraf v0.2.0
🏷️ cloud, datastore
💻 all
[librato]: https://www.librato.com/
[tokens]: https://metrics.librato.com/account/api_tokens
## 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_user` and
`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 Librato API to send metrics to.
[[outputs.librato]]
## Librato API Docs
## http://dev.librato.com/v1/metrics-authentication
## Librato API user
api_user = "telegraf@influxdb.com" # required.
## Librato API token
api_token = "my-secret-token" # required.
## Debug
# debug = false
## Connection timeout.
# timeout = "5s"
## Output source Template (same as graphite buckets)
## see https://github.com/influxdata/telegraf/blob/master/docs/DATA_FORMATS_OUTPUT.md#graphite
## This template is used in librato's source (not metric's name)
template = "host"
```

View file

@ -0,0 +1,254 @@
//go:generate ../../../tools/readme_config_includer/generator
package librato
import (
"bytes"
_ "embed"
"encoding/json"
"errors"
"fmt"
"io"
"net/http"
"regexp"
"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
// Librato structure for configuration and client
type Librato struct {
APIUser config.Secret `toml:"api_user"`
APIToken config.Secret `toml:"api_token"`
Debug bool `toml:"debug"`
SourceTag string `toml:"source_tag" deprecated:"1.0.0;1.35.0;use 'template' instead"`
Timeout config.Duration `toml:"timeout"`
Template string `toml:"template"`
Log telegraf.Logger `toml:"-"`
APIUrl string
client *http.Client
}
// https://www.librato.com/docs/kb/faq/best_practices/naming_convention_metrics_sources.html#naming-limitations-for-sources-and-metrics
var reUnacceptedChar = regexp.MustCompile("[^.a-zA-Z0-9_-]")
// LMetrics is the default struct for Librato's API format
type LMetrics struct {
Gauges []*Gauge `json:"gauges"`
}
// Gauge is the gauge format for Librato's API format
type Gauge struct {
Name string `json:"name"`
Value float64 `json:"value"`
Source string `json:"source"`
MeasureTime int64 `json:"measure_time"`
}
const libratoAPI = "https://metrics-api.librato.com/v1/metrics"
// NewLibrato is the main constructor for librato output plugins
func NewLibrato(apiURL string) *Librato {
return &Librato{
APIUrl: apiURL,
Template: "host",
}
}
func (*Librato) SampleConfig() string {
return sampleConfig
}
// Connect is the default output plugin connection function who make sure it
// can connect to the endpoint
func (l *Librato) Connect() error {
if l.APIUser.Empty() || l.APIToken.Empty() {
return errors.New("api_user and api_token required")
}
l.client = &http.Client{
Transport: &http.Transport{
Proxy: http.ProxyFromEnvironment,
},
Timeout: time.Duration(l.Timeout),
}
return nil
}
func (l *Librato) Write(metrics []telegraf.Metric) error {
if len(metrics) == 0 {
return nil
}
if l.Template == "" {
l.Template = "host"
}
if l.SourceTag != "" {
l.Template = l.SourceTag
}
var tempGauges []*Gauge
for _, m := range metrics {
if gauges, err := l.buildGauges(m); err == nil {
for _, gauge := range gauges {
tempGauges = append(tempGauges, gauge)
l.Log.Debugf("Got a gauge: %v", gauge)
}
} else {
l.Log.Infof("Unable to build Gauge for %s, skipping", m.Name())
l.Log.Debugf("Couldn't build gauge: %v", err)
}
}
metricCounter := len(tempGauges)
// make sure we send a batch of maximum 300
sizeBatch := 300
for start := 0; start < metricCounter; start += sizeBatch {
err := l.writeBatch(start, sizeBatch, metricCounter, tempGauges)
if err != nil {
return err
}
}
return nil
}
func (l *Librato) writeBatch(start, sizeBatch, metricCounter int, tempGauges []*Gauge) error {
lmetrics := LMetrics{}
end := start + sizeBatch
if end > metricCounter {
end = metricCounter
sizeBatch = end - start
}
lmetrics.Gauges = make([]*Gauge, sizeBatch)
copy(lmetrics.Gauges, tempGauges[start:end])
metricsBytes, err := json.Marshal(lmetrics)
if err != nil {
return fmt.Errorf("unable to marshal Metrics: %w", err)
}
l.Log.Debugf("Librato request: %v", string(metricsBytes))
req, err := http.NewRequest(
"POST",
l.APIUrl,
bytes.NewBuffer(metricsBytes))
if err != nil {
return fmt.Errorf("unable to create http.Request: %w", err)
}
req.Header.Add("Content-Type", "application/json")
user, err := l.APIUser.Get()
if err != nil {
return fmt.Errorf("getting user failed: %w", err)
}
token, err := l.APIToken.Get()
if err != nil {
user.Destroy()
return fmt.Errorf("getting token failed: %w", err)
}
req.SetBasicAuth(user.String(), token.String())
user.Destroy()
token.Destroy()
resp, err := l.client.Do(req)
if err != nil {
l.Log.Debugf("Error POSTing metrics: %v", err.Error())
return fmt.Errorf("error POSTing metrics: %w", err)
}
defer resp.Body.Close()
if resp.StatusCode != 200 || l.Debug {
htmlData, err := io.ReadAll(resp.Body)
if err != nil {
l.Log.Debugf("Couldn't get response! (%v)", err)
}
if resp.StatusCode != 200 {
return fmt.Errorf(
"received bad status code, %d\n %s",
resp.StatusCode,
string(htmlData))
}
l.Log.Debugf("Librato response: %v", string(htmlData))
}
return nil
}
func (l *Librato) buildGauges(m telegraf.Metric) ([]*Gauge, error) {
if m.Time().Unix() == 0 {
return nil, fmt.Errorf("time was zero %s", m.Name())
}
metricSource := graphite.InsertField(graphite.SerializeBucketName("", m.Tags(), l.Template, ""), "value")
if metricSource == "" {
return nil, fmt.Errorf("undeterminable Source type from Field, %s", l.Template)
}
gauges := make([]*Gauge, 0, len(m.Fields()))
for fieldName, value := range m.Fields() {
metricName := m.Name()
if fieldName != "value" {
metricName = fmt.Sprintf("%s.%s", m.Name(), fieldName)
}
gauge := &Gauge{
Source: reUnacceptedChar.ReplaceAllString(metricSource, "-"),
Name: reUnacceptedChar.ReplaceAllString(metricName, "-"),
MeasureTime: m.Time().Unix(),
}
if !verifyValue(value) {
continue
}
if err := gauge.setValue(value); err != nil {
return nil, fmt.Errorf("unable to extract value from Fields: %w", err)
}
gauges = append(gauges, gauge)
}
l.Log.Debugf("Built gauges: %v", gauges)
return gauges, nil
}
func verifyValue(v interface{}) bool {
switch v.(type) {
case string:
return false
default:
return true
}
}
func (g *Gauge) setValue(v interface{}) error {
switch d := v.(type) {
case int64:
g.Value = float64(d)
case uint64:
g.Value = float64(d)
case float64:
g.Value = d
case bool:
if d {
g.Value = float64(1.0)
} else {
g.Value = float64(0.0)
}
default:
return fmt.Errorf("undeterminable type %+v", d)
}
return nil
}
// Close is used to close the connection to librato Output
func (*Librato) Close() error {
return nil
}
func init() {
outputs.Add("librato", func() telegraf.Output {
return NewLibrato(libratoAPI)
})
}

View file

@ -0,0 +1,273 @@
package librato
import (
"errors"
"net/http"
"net/http/httptest"
"reflect"
"testing"
"time"
"github.com/stretchr/testify/require"
"github.com/influxdata/telegraf"
"github.com/influxdata/telegraf/config"
"github.com/influxdata/telegraf/metric"
"github.com/influxdata/telegraf/testutil"
)
var (
fakeURL = "http://test.librato.com"
)
func newTestLibrato(testURL string) *Librato {
l := NewLibrato(testURL)
l.Log = testutil.Logger{}
return l
}
func TestUriOverride(t *testing.T) {
ts := httptest.NewServer(
http.HandlerFunc(
func(w http.ResponseWriter, _ *http.Request) {
w.WriteHeader(http.StatusOK)
}))
defer ts.Close()
l := newTestLibrato(ts.URL)
l.APIUser = config.NewSecret([]byte("telegraf@influxdb.com"))
l.APIToken = config.NewSecret([]byte("123456"))
require.NoError(t, l.Connect())
require.NoError(t, l.Write([]telegraf.Metric{newHostMetric(int32(0), "name", "host")}))
}
func TestBadStatusCode(t *testing.T) {
ts := httptest.NewServer(
http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
w.WriteHeader(http.StatusServiceUnavailable)
}))
defer ts.Close()
l := newTestLibrato(ts.URL)
l.APIUser = config.NewSecret([]byte("telegraf@influxdb.com"))
l.APIToken = config.NewSecret([]byte("123456"))
require.NoError(t, l.Connect())
err := l.Write([]telegraf.Metric{newHostMetric(int32(0), "name", "host")})
require.ErrorContains(t, err, "received bad status code, 503")
}
func TestBuildGauge(t *testing.T) {
mtime := time.Date(2009, time.November, 10, 23, 0, 0, 0, time.UTC).Unix()
var gaugeTests = []struct {
ptIn telegraf.Metric
outGauge *Gauge
err error
}{
{
newHostMetric(0.0, "test1", "host1"),
&Gauge{
Name: "test1",
MeasureTime: mtime,
Value: 0.0,
Source: "host1",
},
nil,
},
{
newHostMetric(1.0, "test2", "host2"),
&Gauge{
Name: "test2",
MeasureTime: mtime,
Value: 1.0,
Source: "host2",
},
nil,
},
{
newHostMetric(10, "test3", "host3"),
&Gauge{
Name: "test3",
MeasureTime: mtime,
Value: 10.0,
Source: "host3",
},
nil,
},
{
newHostMetric(int32(112345), "test4", "host4"),
&Gauge{
Name: "test4",
MeasureTime: mtime,
Value: 112345.0,
Source: "host4",
},
nil,
},
{
newHostMetric(int64(112345), "test5", "host5"),
&Gauge{
Name: "test5",
MeasureTime: mtime,
Value: 112345.0,
Source: "host5",
},
nil,
},
{
newHostMetric(float32(11234.5), "test6", "host6"),
&Gauge{
Name: "test6",
MeasureTime: mtime,
Value: 11234.5,
Source: "host6",
},
nil,
},
{
newHostMetric("11234.5", "test7", "host7"),
nil,
nil,
},
}
l := newTestLibrato(fakeURL)
for _, gt := range gaugeTests {
gauges, err := l.buildGauges(gt.ptIn)
if err != nil && gt.err == nil {
t.Errorf("%s: unexpected error, %+v\n", gt.ptIn.Name(), err)
}
if gt.err != nil && err == nil {
t.Errorf("%s: expected an error (%s) but none returned",
gt.ptIn.Name(), gt.err.Error())
}
if len(gauges) != 0 && gt.outGauge == nil {
t.Errorf("%s: unexpected gauge, %+v\n", gt.ptIn.Name(), gt.outGauge)
}
if len(gauges) == 0 {
continue
}
if gt.err == nil && !reflect.DeepEqual(gauges[0], gt.outGauge) {
t.Errorf("%s: \nexpected %+v\ngot %+v\n",
gt.ptIn.Name(), gt.outGauge, gauges[0])
}
}
}
func newHostMetric(value interface{}, name, host string) telegraf.Metric {
m := metric.New(
name,
map[string]string{"host": host},
map[string]interface{}{"value": value},
time.Date(2009, time.November, 10, 23, 0, 0, 0, time.UTC),
)
return m
}
func TestBuildGaugeWithSource(t *testing.T) {
mtime := time.Date(2009, time.November, 10, 23, 0, 0, 0, time.UTC)
pt1 := metric.New(
"test1",
map[string]string{"hostname": "192.168.0.1", "tag1": "value1"},
map[string]interface{}{"value": 0.0},
mtime,
)
pt2 := metric.New(
"test2",
map[string]string{"hostnam": "192.168.0.1", "tag1": "value1"},
map[string]interface{}{"value": 1.0},
mtime,
)
pt3 := metric.New(
"test3",
map[string]string{
"hostname": "192.168.0.1",
"tag2": "value2",
"tag1": "value1"},
map[string]interface{}{"value": 1.0},
mtime,
)
pt4 := metric.New(
"test4",
map[string]string{
"hostname": "192.168.0.1",
"tag2": "value2",
"tag1": "value1"},
map[string]interface{}{"value": 1.0},
mtime,
)
var gaugeTests = []struct {
ptIn telegraf.Metric
template string
outGauge *Gauge
err error
}{
{
pt1,
"hostname",
&Gauge{
Name: "test1",
MeasureTime: mtime.Unix(),
Value: 0.0,
Source: "192_168_0_1",
},
nil,
},
{
pt2,
"hostname",
&Gauge{
Name: "test2",
MeasureTime: mtime.Unix(),
Value: 1.0,
},
errors.New("undeterminable Source type from Field, hostname"),
},
{
pt3,
"tags",
&Gauge{
Name: "test3",
MeasureTime: mtime.Unix(),
Value: 1.0,
Source: "192_168_0_1.value1.value2",
},
nil,
},
{
pt4,
"hostname.tag2",
&Gauge{
Name: "test4",
MeasureTime: mtime.Unix(),
Value: 1.0,
Source: "192_168_0_1.value2",
},
nil,
},
}
l := newTestLibrato(fakeURL)
for _, gt := range gaugeTests {
l.Template = gt.template
gauges, err := l.buildGauges(gt.ptIn)
if err != nil && gt.err == nil {
t.Errorf("%s: unexpected error, %+v\n", gt.ptIn.Name(), err)
}
if gt.err != nil && err == nil {
t.Errorf(
"%s: expected an error (%s) but none returned",
gt.ptIn.Name(),
gt.err.Error())
}
if len(gauges) == 0 {
continue
}
if gt.err == nil && !reflect.DeepEqual(gauges[0], gt.outGauge) {
t.Errorf(
"%s: \nexpected %+v\ngot %+v\n",
gt.ptIn.Name(),
gt.outGauge, gauges[0])
}
}
}

View file

@ -0,0 +1,16 @@
# Configuration for Librato API to send metrics to.
[[outputs.librato]]
## Librato API Docs
## http://dev.librato.com/v1/metrics-authentication
## Librato API user
api_user = "telegraf@influxdb.com" # required.
## Librato API token
api_token = "my-secret-token" # required.
## Debug
# debug = false
## Connection timeout.
# timeout = "5s"
## Output source Template (same as graphite buckets)
## see https://github.com/influxdata/telegraf/blob/master/docs/DATA_FORMATS_OUTPUT.md#graphite
## This template is used in librato's source (not metric's name)
template = "host"