Update dynatrace output (#9363)
- export timestamps - enrich dimensions with OneAgent data - Add default dimensions feature
This commit is contained in:
parent
d7afebf7e6
commit
769f582245
|
|
@ -16,7 +16,7 @@ The Dynatrace exporter may be enabled by adding an `[[outputs.dynatrace]]` secti
|
|||
All configurations are optional, but if a `url` other than the OneAgent metric ingestion endpoint is specified then an `api_token` is required.
|
||||
To see all available options, see [Configuration](#configuration) below.
|
||||
|
||||
### Running alongside Dynatrace OneAgent
|
||||
### Running alongside Dynatrace OneAgent (preferred)
|
||||
|
||||
If you run the Telegraf agent on a host or VM that is monitored by the Dynatrace OneAgent then you only need to enable the plugin, but need no further configuration. The Dynatrace Telegraf output plugin will send all metrics to the OneAgent which will use its secure and load balanced connection to send the metrics to your Dynatrace SaaS or Managed environment.
|
||||
Depending on your environment, you might have to enable metrics ingestion on the OneAgent first as described in the [Dynatrace documentation](https://www.dynatrace.com/support/help/how-to-use-dynatrace/metrics/metric-ingestion/ingestion-methods/telegraf/).
|
||||
|
|
@ -28,7 +28,7 @@ Note: The name and identifier of the host running Telegraf will be added as a di
|
|||
## No options are required. By default, metrics will be exported via the OneAgent on the local host.
|
||||
```
|
||||
|
||||
## Running standalone
|
||||
### Running standalone
|
||||
|
||||
If you run the Telegraf agent on a host or VM without a OneAgent you will need to configure the environment API endpoint to send the metrics to and an API token for security.
|
||||
|
||||
|
|
@ -55,14 +55,6 @@ You can learn more about how to use the Dynatrace API [here](https://www.dynatra
|
|||
|
||||
## Configuration
|
||||
|
||||
### `url`
|
||||
|
||||
*required*: `false`
|
||||
|
||||
*default*: Local OneAgent endpoint
|
||||
|
||||
Set your Dynatrace environment URL (e.g.: `https://{your-environment-id}.live.dynatrace.com/api/v2/metrics/ingest`) if you do not use a OneAgent or wish to export metrics directly to a Dynatrace metrics v2 endpoint. If a URL is set to anything other than the local OneAgent endpoint, then an API token is required.
|
||||
|
||||
```toml
|
||||
[[outputs.dynatrace]]
|
||||
## Leave empty or use the local ingest endpoint of your OneAgent monitored host (e.g.: http://127.0.0.1:14499/metrics/ingest).
|
||||
|
|
@ -75,6 +67,21 @@ Set your Dynatrace environment URL (e.g.: `https://{your-environment-id}.live.dy
|
|||
insecure_skip_verify = false
|
||||
## If you want to convert values represented as gauges to counters, add the metric names here
|
||||
additional_counters = [ ]
|
||||
|
||||
## Optional dimensions to be added to every metric
|
||||
[outputs.dynatrace.default_dimensions]
|
||||
default_key = "default value"
|
||||
```
|
||||
|
||||
### `url`
|
||||
|
||||
*required*: `false`
|
||||
|
||||
*default*: Local OneAgent endpoint
|
||||
|
||||
Set your Dynatrace environment URL (e.g.: `https://{your-environment-id}.live.dynatrace.com/api/v2/metrics/ingest`, see the [Dynatrace documentation](https://www.dynatrace.com/support/help/dynatrace-api/environment-api/metric-v2/post-ingest-metrics/) for details) if you do not use a OneAgent or wish to export metrics directly to a Dynatrace metrics v2 endpoint. If a URL is set to anything other than the local OneAgent endpoint, then an API token is required.
|
||||
|
||||
```toml
|
||||
url = "https://{your-environment-id}.live.dynatrace.com/api/v2/metrics/ingest"
|
||||
```
|
||||
|
||||
|
|
@ -125,9 +132,8 @@ additional_counters = [ ]
|
|||
Default dimensions that will be added to every exported metric.
|
||||
|
||||
```toml
|
||||
default_dimensions = {
|
||||
key = "value"
|
||||
}
|
||||
[outputs.dynatrace.default_dimensions]
|
||||
default_key = "default value"
|
||||
```
|
||||
|
||||
## Limitations
|
||||
|
|
|
|||
|
|
@ -20,12 +20,16 @@ import (
|
|||
|
||||
// Dynatrace Configuration for the Dynatrace output plugin
|
||||
type Dynatrace struct {
|
||||
URL string `toml:"url"`
|
||||
APIToken string `toml:"api_token"`
|
||||
Prefix string `toml:"prefix"`
|
||||
Log telegraf.Logger `toml:"-"`
|
||||
Timeout config.Duration `toml:"timeout"`
|
||||
AddCounterMetrics []string `toml:"additional_counters"`
|
||||
URL string `toml:"url"`
|
||||
APIToken string `toml:"api_token"`
|
||||
Prefix string `toml:"prefix"`
|
||||
Log telegraf.Logger `toml:"-"`
|
||||
Timeout config.Duration `toml:"timeout"`
|
||||
AddCounterMetrics []string `toml:"additional_counters"`
|
||||
DefaultDimensions map[string]string `toml:"default_dimensions"`
|
||||
|
||||
normalizedDefaultDimensions dimensions.NormalizedDimensionList
|
||||
normalizedStaticDimensions dimensions.NormalizedDimensionList
|
||||
|
||||
tls.ClientConfig
|
||||
|
||||
|
|
@ -67,6 +71,10 @@ const sampleConfig = `
|
|||
|
||||
## If you want to convert values represented as gauges to counters, add the metric names here
|
||||
additional_counters = [ ]
|
||||
|
||||
## Optional dimensions to be added to every metric
|
||||
# [outputs.dynatrace.default_dimensions]
|
||||
# default_key = "default value"
|
||||
`
|
||||
|
||||
// Connect Connects the Dynatrace output plugin to the Telegraf stream
|
||||
|
|
@ -140,10 +148,12 @@ func (d *Dynatrace) Write(metrics []telegraf.Metric) error {
|
|||
dtMetric.WithPrefix(d.Prefix),
|
||||
dtMetric.WithDimensions(
|
||||
dimensions.MergeLists(
|
||||
// dimensions.NewNormalizedDimensionList(e.opts.DefaultDimensions...),
|
||||
d.normalizedDefaultDimensions,
|
||||
dimensions.NewNormalizedDimensionList(dims...),
|
||||
d.normalizedStaticDimensions,
|
||||
),
|
||||
),
|
||||
dtMetric.WithTimestamp(tm.Time()),
|
||||
typeOpt,
|
||||
)
|
||||
|
||||
|
|
@ -236,6 +246,14 @@ func (d *Dynatrace) Init() error {
|
|||
},
|
||||
Timeout: time.Duration(d.Timeout),
|
||||
}
|
||||
|
||||
dims := []dimensions.Dimension{}
|
||||
for key, value := range d.DefaultDimensions {
|
||||
dims = append(dims, dimensions.NewDimension(key, value))
|
||||
}
|
||||
d.normalizedDefaultDimensions = dimensions.NewNormalizedDimensionList(dims...)
|
||||
d.normalizedStaticDimensions = dimensions.NewNormalizedDimensionList(dimensions.NewDimension("dt.metrics.source", "telegraf"))
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ import (
|
|||
"time"
|
||||
|
||||
"github.com/dynatrace-oss/dynatrace-metric-utils-go/metric/apiconstants"
|
||||
"github.com/dynatrace-oss/dynatrace-metric-utils-go/metric/dimensions"
|
||||
"github.com/influxdata/telegraf"
|
||||
"github.com/influxdata/telegraf/config"
|
||||
"github.com/influxdata/telegraf/metric"
|
||||
|
|
@ -121,13 +122,13 @@ func TestMissingAPIToken(t *testing.T) {
|
|||
require.Error(t, err)
|
||||
}
|
||||
|
||||
func TestSendMetric(t *testing.T) {
|
||||
func TestSendMetrics(t *testing.T) {
|
||||
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
// check the encoded result
|
||||
bodyBytes, err := ioutil.ReadAll(r.Body)
|
||||
require.NoError(t, err)
|
||||
bodyString := string(bodyBytes)
|
||||
expected := "mymeasurement.myfield,host=192.168.0.1 gauge,3.14\nmymeasurement.value,host=192.168.0.2 count,3.14"
|
||||
expected := "mymeasurement.myfield,dt.metrics.source=telegraf gauge,3.14 1289430000000\nmymeasurement.value,dt.metrics.source=telegraf count,3.14 1289430000000"
|
||||
if bodyString != expected {
|
||||
t.Errorf("Metric encoding failed. expected: %#v but got: %#v", expected, bodyString)
|
||||
}
|
||||
|
|
@ -151,14 +152,14 @@ func TestSendMetric(t *testing.T) {
|
|||
|
||||
m1 := metric.New(
|
||||
"mymeasurement",
|
||||
map[string]string{"host": "192.168.0.1"},
|
||||
map[string]string{},
|
||||
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.2"},
|
||||
map[string]string{},
|
||||
map[string]interface{}{"value": float64(3.14)},
|
||||
time.Date(2010, time.November, 10, 23, 0, 0, 0, time.UTC),
|
||||
telegraf.Counter,
|
||||
|
|
@ -176,11 +177,14 @@ func TestSendSingleMetricWithUnorderedTags(t *testing.T) {
|
|||
bodyBytes, err := ioutil.ReadAll(r.Body)
|
||||
require.NoError(t, err)
|
||||
bodyString := string(bodyBytes)
|
||||
// use regex because dimension order isn't guaranteed
|
||||
require.Equal(t, len(bodyString), 94)
|
||||
require.Regexp(t, regexp.MustCompile(`^mymeasurement\.myfield`), bodyString)
|
||||
require.Regexp(t, regexp.MustCompile(`a=test`), bodyString)
|
||||
require.Regexp(t, regexp.MustCompile(`b=test`), bodyString)
|
||||
require.Regexp(t, regexp.MustCompile(`c=test`), bodyString)
|
||||
require.Regexp(t, regexp.MustCompile(`gauge,3.14$`), bodyString)
|
||||
require.Regexp(t, regexp.MustCompile(`dt.metrics.source=telegraf`), bodyString)
|
||||
require.Regexp(t, regexp.MustCompile(`gauge,3.14 1289430000000$`), bodyString)
|
||||
w.WriteHeader(http.StatusOK)
|
||||
err = json.NewEncoder(w).Encode(`{"linesOk":1,"linesInvalid":0,"error":null}`)
|
||||
require.NoError(t, err)
|
||||
|
|
@ -219,7 +223,7 @@ func TestSendMetricWithoutTags(t *testing.T) {
|
|||
bodyBytes, err := ioutil.ReadAll(r.Body)
|
||||
require.NoError(t, err)
|
||||
bodyString := string(bodyBytes)
|
||||
expected := "mymeasurement.myfield gauge,3.14"
|
||||
expected := "mymeasurement.myfield,dt.metrics.source=telegraf gauge,3.14 1289430000000"
|
||||
if bodyString != expected {
|
||||
t.Errorf("Metric encoding failed. expected: %#v but got: %#v", expected, bodyString)
|
||||
}
|
||||
|
|
@ -261,13 +265,14 @@ func TestSendMetricWithUpperCaseTagKeys(t *testing.T) {
|
|||
require.NoError(t, err)
|
||||
bodyString := string(bodyBytes)
|
||||
|
||||
// expected := "mymeasurement.myfield,b_b=test,ccc=test,aaa=test gauge,3.14"
|
||||
// use regex because dimension order isn't guaranteed
|
||||
require.Equal(t, len(bodyString), 100)
|
||||
require.Regexp(t, regexp.MustCompile(`^mymeasurement\.myfield`), bodyString)
|
||||
require.Regexp(t, regexp.MustCompile(`aaa=test`), bodyString)
|
||||
require.Regexp(t, regexp.MustCompile(`b_b=test`), bodyString)
|
||||
require.Regexp(t, regexp.MustCompile(`ccc=test`), bodyString)
|
||||
require.Regexp(t, regexp.MustCompile(`gauge,3.14$`), bodyString)
|
||||
require.Regexp(t, regexp.MustCompile(`dt.metrics.source=telegraf`), bodyString)
|
||||
require.Regexp(t, regexp.MustCompile(`gauge,3.14 1289430000000$`), bodyString)
|
||||
|
||||
err = json.NewEncoder(w).Encode(`{"linesOk":1,"linesInvalid":0,"error":null}`)
|
||||
require.NoError(t, err)
|
||||
|
|
@ -307,8 +312,9 @@ func TestSendBooleanMetricWithoutTags(t *testing.T) {
|
|||
require.NoError(t, err)
|
||||
bodyString := string(bodyBytes)
|
||||
// use regex because field order isn't guaranteed
|
||||
require.Contains(t, bodyString, "mymeasurement.yes gauge,1")
|
||||
require.Contains(t, bodyString, "mymeasurement.no gauge,0")
|
||||
require.Equal(t, len(bodyString), 132)
|
||||
require.Contains(t, bodyString, "mymeasurement.yes,dt.metrics.source=telegraf gauge,1 1289430000000")
|
||||
require.Contains(t, bodyString, "mymeasurement.no,dt.metrics.source=telegraf gauge,0 1289430000000")
|
||||
err = json.NewEncoder(w).Encode(`{"linesOk":1,"linesInvalid":0,"error":null}`)
|
||||
require.NoError(t, err)
|
||||
}))
|
||||
|
|
@ -339,6 +345,136 @@ func TestSendBooleanMetricWithoutTags(t *testing.T) {
|
|||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
func TestSendMetricWithDefaultDimensions(t *testing.T) {
|
||||
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.WriteHeader(http.StatusOK)
|
||||
// check the encoded result
|
||||
bodyBytes, err := ioutil.ReadAll(r.Body)
|
||||
require.NoError(t, err)
|
||||
bodyString := string(bodyBytes)
|
||||
// use regex because field order isn't guaranteed
|
||||
require.Equal(t, len(bodyString), 79)
|
||||
require.Regexp(t, regexp.MustCompile("^mymeasurement.value"), bodyString)
|
||||
require.Regexp(t, regexp.MustCompile("dt.metrics.source=telegraf"), bodyString)
|
||||
require.Regexp(t, regexp.MustCompile("dim=value"), bodyString)
|
||||
require.Regexp(t, regexp.MustCompile("gauge,32 1289430000000$"), bodyString)
|
||||
err = json.NewEncoder(w).Encode(`{"linesOk":1,"linesInvalid":0,"error":null}`)
|
||||
require.NoError(t, err)
|
||||
}))
|
||||
defer ts.Close()
|
||||
|
||||
d := &Dynatrace{DefaultDimensions: map[string]string{"dim": "value"}}
|
||||
|
||||
d.URL = ts.URL
|
||||
d.APIToken = "123"
|
||||
d.Log = testutil.Logger{}
|
||||
err := d.Init()
|
||||
require.NoError(t, err)
|
||||
err = d.Connect()
|
||||
require.NoError(t, err)
|
||||
|
||||
// Init metrics
|
||||
|
||||
m1 := metric.New(
|
||||
"mymeasurement",
|
||||
map[string]string{},
|
||||
map[string]interface{}{"value": 32},
|
||||
time.Date(2010, time.November, 10, 23, 0, 0, 0, time.UTC),
|
||||
)
|
||||
|
||||
metrics := []telegraf.Metric{m1}
|
||||
|
||||
err = d.Write(metrics)
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
func TestMetricDimensionsOverrideDefault(t *testing.T) {
|
||||
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.WriteHeader(http.StatusOK)
|
||||
// check the encoded result
|
||||
bodyBytes, err := ioutil.ReadAll(r.Body)
|
||||
require.NoError(t, err)
|
||||
bodyString := string(bodyBytes)
|
||||
// use regex because field order isn't guaranteed
|
||||
require.Equal(t, len(bodyString), 80)
|
||||
require.Regexp(t, regexp.MustCompile("^mymeasurement.value"), bodyString)
|
||||
require.Regexp(t, regexp.MustCompile("dt.metrics.source=telegraf"), bodyString)
|
||||
require.Regexp(t, regexp.MustCompile("dim=metric"), bodyString)
|
||||
require.Regexp(t, regexp.MustCompile("gauge,32 1289430000000$"), bodyString)
|
||||
err = json.NewEncoder(w).Encode(`{"linesOk":1,"linesInvalid":0,"error":null}`)
|
||||
require.NoError(t, err)
|
||||
}))
|
||||
defer ts.Close()
|
||||
|
||||
d := &Dynatrace{DefaultDimensions: map[string]string{"dim": "default"}}
|
||||
|
||||
d.URL = ts.URL
|
||||
d.APIToken = "123"
|
||||
d.Log = testutil.Logger{}
|
||||
err := d.Init()
|
||||
require.NoError(t, err)
|
||||
err = d.Connect()
|
||||
require.NoError(t, err)
|
||||
|
||||
// Init metrics
|
||||
|
||||
m1 := metric.New(
|
||||
"mymeasurement",
|
||||
map[string]string{"dim": "metric"},
|
||||
map[string]interface{}{"value": 32},
|
||||
time.Date(2010, time.November, 10, 23, 0, 0, 0, time.UTC),
|
||||
)
|
||||
|
||||
metrics := []telegraf.Metric{m1}
|
||||
|
||||
err = d.Write(metrics)
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
func TestStaticDimensionsOverrideMetric(t *testing.T) {
|
||||
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.WriteHeader(http.StatusOK)
|
||||
// check the encoded result
|
||||
bodyBytes, err := ioutil.ReadAll(r.Body)
|
||||
require.NoError(t, err)
|
||||
bodyString := string(bodyBytes)
|
||||
// use regex because field order isn't guaranteed
|
||||
require.Equal(t, len(bodyString), 53)
|
||||
require.Regexp(t, regexp.MustCompile("^mymeasurement.value"), bodyString)
|
||||
require.Regexp(t, regexp.MustCompile("dim=static"), bodyString)
|
||||
require.Regexp(t, regexp.MustCompile("gauge,32 1289430000000$"), bodyString)
|
||||
err = json.NewEncoder(w).Encode(`{"linesOk":1,"linesInvalid":0,"error":null}`)
|
||||
require.NoError(t, err)
|
||||
}))
|
||||
defer ts.Close()
|
||||
|
||||
d := &Dynatrace{DefaultDimensions: map[string]string{"dim": "default"}}
|
||||
|
||||
d.URL = ts.URL
|
||||
d.APIToken = "123"
|
||||
d.Log = testutil.Logger{}
|
||||
err := d.Init()
|
||||
require.NoError(t, err)
|
||||
err = d.Connect()
|
||||
require.NoError(t, err)
|
||||
|
||||
d.normalizedStaticDimensions = dimensions.NewNormalizedDimensionList(dimensions.NewDimension("dim", "static"))
|
||||
|
||||
// Init metrics
|
||||
|
||||
m1 := metric.New(
|
||||
"mymeasurement",
|
||||
map[string]string{"dim": "metric"},
|
||||
map[string]interface{}{"value": 32},
|
||||
time.Date(2010, time.November, 10, 23, 0, 0, 0, time.UTC),
|
||||
)
|
||||
|
||||
metrics := []telegraf.Metric{m1}
|
||||
|
||||
err = d.Write(metrics)
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
func TestSendCounterMetricWithoutTags(t *testing.T) {
|
||||
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.WriteHeader(http.StatusOK)
|
||||
|
|
@ -346,7 +482,7 @@ func TestSendCounterMetricWithoutTags(t *testing.T) {
|
|||
bodyBytes, err := ioutil.ReadAll(r.Body)
|
||||
require.NoError(t, err)
|
||||
bodyString := string(bodyBytes)
|
||||
expected := "mymeasurement.value gauge,32"
|
||||
expected := "mymeasurement.value,dt.metrics.source=telegraf gauge,32 1289430000000"
|
||||
if bodyString != expected {
|
||||
t.Errorf("Metric encoding failed. expected: %#v but got: %#v", expected, bodyString)
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue