Adding upstream version 1.34.4.
Signed-off-by: Daniel Baumann <daniel@debian.org>
This commit is contained in:
parent
e393c3af3f
commit
4978089aab
4963 changed files with 677545 additions and 0 deletions
59
plugins/outputs/librato/README.md
Normal file
59
plugins/outputs/librato/README.md
Normal 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"
|
||||
```
|
254
plugins/outputs/librato/librato.go
Normal file
254
plugins/outputs/librato/librato.go
Normal 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)
|
||||
})
|
||||
}
|
273
plugins/outputs/librato/librato_test.go
Normal file
273
plugins/outputs/librato/librato_test.go
Normal 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])
|
||||
}
|
||||
}
|
||||
}
|
16
plugins/outputs/librato/sample.conf
Normal file
16
plugins/outputs/librato/sample.conf
Normal 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"
|
Loading…
Add table
Add a link
Reference in a new issue