Skip to content

Commit

Permalink
feat(dynatrace-output): remove special handling from counters (influx…
Browse files Browse the repository at this point in the history
…data#9675)

Co-authored-by: Armin Ruech <armin.ruech@dynatrace.com>
  • Loading branch information
dyladan and arminru authored Sep 8, 2021
1 parent ba1484c commit 95ef674
Show file tree
Hide file tree
Showing 5 changed files with 67 additions and 73 deletions.
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ require (
github.com/docker/go-connections v0.4.0 // indirect
github.com/docker/go-units v0.4.0 // indirect
github.com/doclambda/protobufquery v0.0.0-20210317203640-88ffabe06a60
github.com/dynatrace-oss/dynatrace-metric-utils-go v0.2.0
github.com/dynatrace-oss/dynatrace-metric-utils-go v0.3.0
github.com/eapache/go-resiliency v1.2.0 // indirect
github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21 // indirect
github.com/eapache/queue v1.1.0 // indirect
Expand Down
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -507,8 +507,8 @@ github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4/go.mod h1:Htrtb
github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo=
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
github.com/dynatrace-oss/dynatrace-metric-utils-go v0.2.0 h1:TEG5Jj7RYM2JBCUH3nLqCmSZy6srnaefvXxjUTCuHyA=
github.com/dynatrace-oss/dynatrace-metric-utils-go v0.2.0/go.mod h1:qw0E9EJ0PnSlhWawDNuqE0zhc1hqOBUCFIAj3dd9DNw=
github.com/dynatrace-oss/dynatrace-metric-utils-go v0.3.0 h1:q2Ayh9s6Cr75bS5URiOUAoyFXemgKQaBJphbhAaJHCY=
github.com/dynatrace-oss/dynatrace-metric-utils-go v0.3.0/go.mod h1:qw0E9EJ0PnSlhWawDNuqE0zhc1hqOBUCFIAj3dd9DNw=
github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs=
github.com/eapache/go-resiliency v1.2.0 h1:v7g92e/KSN71Rq7vSThKaWIq68fL4YHvWyiUKorFR1Q=
github.com/eapache/go-resiliency v1.2.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs=
Expand Down
8 changes: 5 additions & 3 deletions plugins/outputs/dynatrace/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,12 @@

This plugin sends Telegraf metrics to [Dynatrace](https://www.dynatrace.com) via the [Dynatrace Metrics API V2](https://www.dynatrace.com/support/help/dynatrace-api/environment-api/metric-v2/). It may be run alongside the Dynatrace OneAgent for automatic authentication or it may be run standalone on a host without a OneAgent by specifying a URL and API Token.
More information on the plugin can be found in the [Dynatrace documentation](https://www.dynatrace.com/support/help/how-to-use-dynatrace/metrics/metric-ingestion/ingestion-methods/telegraf/).
All metrics are reported as gauges, unless they are specified to be delta counters using the `additional_counters` config option (see below).
See the [Dynatrace Metrics ingestion protocol documentation](https://www.dynatrace.com/support/help/how-to-use-dynatrace/metrics/metric-ingestion/metric-ingestion-protocol) for details on the types defined there.

## Requirements

You will either need a Dynatrace OneAgent (version 1.201 or higher) installed on the same host as Telegraf; or a Dynatrace environment with version 1.202 or higher. Monotonic counters (e.g. `diskio.reads`, `system.uptime`) require Dynatrace 208 or later.
You will either need a Dynatrace OneAgent (version 1.201 or higher) installed on the same host as Telegraf; or a Dynatrace environment with version 1.202 or higher.

- Telegraf minimum version: Telegraf 1.16

Expand Down Expand Up @@ -65,7 +67,7 @@ You can learn more about how to use the Dynatrace API [here](https://www.dynatra
prefix = "telegraf"
## Flag for skipping the tls certificate check, just for testing purposes, should be false by default
insecure_skip_verify = false
## If you want to convert values represented as gauges to counters, add the metric names here
## If you want metrics to be treated and reported as delta counters, add the metric names here
additional_counters = [ ]

## Optional dimensions to be added to every metric
Expand Down Expand Up @@ -119,7 +121,7 @@ insecure_skip_verify = false

*required*: `false`

If you want to convert values represented as gauges to counters, add the metric names here.
If you want a metric to be treated and reported as a delta counter, add its name to this list.

```toml
additional_counters = [ ]
Expand Down
24 changes: 11 additions & 13 deletions plugins/outputs/dynatrace/dynatrace.go
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ const sampleConfig = `
## Connection timeout, defaults to "5s" if not set.
timeout = "5s"
## If you want to convert values represented as gauges to counters, add the metric names here
## If you want metrics to be treated and reported as delta counters, add the metric names here
additional_counters = [ ]
## Optional dimensions to be added to every metric
Expand Down Expand Up @@ -122,16 +122,10 @@ func (d *Dynatrace) Write(metrics []telegraf.Metric) error {
dims = append(dims, dimensions.NewDimension(tag.Key, tag.Value))
}

metricType := tm.Type()
for _, field := range tm.FieldList() {
metricName := tm.Name() + "." + field.Key
for _, i := range d.AddCounterMetrics {
if metricName == i {
metricType = telegraf.Counter
}
}

typeOpt := getTypeOption(metricType, field)
typeOpt := d.getTypeOption(tm, field)

if typeOpt == nil {
// Unsupported type. Log only once per unsupported metric name
Expand Down Expand Up @@ -267,15 +261,19 @@ func init() {
})
}

func getTypeOption(metricType telegraf.ValueType, field *telegraf.Field) dtMetric.MetricOption {
if metricType == telegraf.Counter {
func (d *Dynatrace) getTypeOption(metric telegraf.Metric, field *telegraf.Field) dtMetric.MetricOption {
metricName := metric.Name() + "." + field.Key
for _, i := range d.AddCounterMetrics {
if metricName != i {
continue
}
switch v := field.Value.(type) {
case float64:
return dtMetric.WithFloatCounterValueTotal(v)
return dtMetric.WithFloatCounterValueDelta(v)
case uint64:
return dtMetric.WithIntCounterValueTotal(int64(v))
return dtMetric.WithIntCounterValueDelta(int64(v))
case int64:
return dtMetric.WithIntCounterValueTotal(v)
return dtMetric.WithIntCounterValueDelta(v)
default:
return nil
}
Expand Down
102 changes: 48 additions & 54 deletions plugins/outputs/dynatrace/dynatrace_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,13 @@ package dynatrace

import (
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
"net/http/httptest"
"regexp"
"sort"
"strings"
"testing"
"time"

Expand Down Expand Up @@ -123,49 +126,81 @@ func TestMissingAPIToken(t *testing.T) {
}

func TestSendMetrics(t *testing.T) {
expected := []string{}

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,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)

lines := strings.Split(bodyString, "\n")

sort.Strings(lines)
sort.Strings(expected)

expectedString := strings.Join(expected, "\n")
foundString := strings.Join(lines, "\n")
if foundString != expectedString {
t.Errorf("Metric encoding failed. expected: %#v but got: %#v", expectedString, foundString)
}
w.WriteHeader(http.StatusOK)
err = json.NewEncoder(w).Encode(`{"linesOk":10,"linesInvalid":0,"error":null}`)
err = json.NewEncoder(w).Encode(fmt.Sprintf(`{"linesOk":%d,"linesInvalid":0,"error":null}`, len(lines)))
require.NoError(t, err)
}))
defer ts.Close()

d := &Dynatrace{}
d := &Dynatrace{
URL: ts.URL,
APIToken: "123",
Log: testutil.Logger{},
AddCounterMetrics: []string{},
}

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

// Simple metrics are exported as a gauge unless in additional_counters
expected = append(expected, "simple_metric.value,dt.metrics.source=telegraf gauge,3.14 1289430000000")
expected = append(expected, "simple_metric.counter,dt.metrics.source=telegraf count,delta=5 1289430000000")
d.AddCounterMetrics = append(d.AddCounterMetrics, "simple_metric.counter")
m1 := metric.New(
"mymeasurement",
"simple_metric",
map[string]string{},
map[string]interface{}{"myfield": float64(3.14)},
map[string]interface{}{"value": float64(3.14), "counter": 5},
time.Date(2010, time.November, 10, 23, 0, 0, 0, time.UTC),
)

// Even if Type() returns counter, all metrics are treated as a gauge unless explicitly added to additional_counters
expected = append(expected, "counter_type.value,dt.metrics.source=telegraf gauge,3.14 1289430000000")
expected = append(expected, "counter_type.counter,dt.metrics.source=telegraf count,delta=5 1289430000000")
d.AddCounterMetrics = append(d.AddCounterMetrics, "counter_type.counter")
m2 := metric.New(
"mymeasurement",
"counter_type",
map[string]string{},
map[string]interface{}{"value": float64(3.14)},
map[string]interface{}{"value": float64(3.14), "counter": 5},
time.Date(2010, time.November, 10, 23, 0, 0, 0, time.UTC),
telegraf.Counter,
)

metrics := []telegraf.Metric{m1, m2}
expected = append(expected, "complex_metric.int,dt.metrics.source=telegraf gauge,1 1289430000000")
expected = append(expected, "complex_metric.int64,dt.metrics.source=telegraf gauge,2 1289430000000")
expected = append(expected, "complex_metric.float,dt.metrics.source=telegraf gauge,3 1289430000000")
expected = append(expected, "complex_metric.float64,dt.metrics.source=telegraf gauge,4 1289430000000")
expected = append(expected, "complex_metric.true,dt.metrics.source=telegraf gauge,1 1289430000000")
expected = append(expected, "complex_metric.false,dt.metrics.source=telegraf gauge,0 1289430000000")
m3 := metric.New(
"complex_metric",
map[string]string{},
map[string]interface{}{"int": 1, "int64": int64(2), "float": 3.0, "float64": float64(4.0), "true": true, "false": false},
time.Date(2010, time.November, 10, 23, 0, 0, 0, time.UTC),
)

metrics := []telegraf.Metric{m1, m2, m3}

err = d.Write(metrics)
require.NoError(t, err)
Expand Down Expand Up @@ -475,47 +510,6 @@ func TestStaticDimensionsOverrideMetric(t *testing.T) {
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)
// check the encoded result
bodyBytes, err := ioutil.ReadAll(r.Body)
require.NoError(t, err)
bodyString := string(bodyBytes)
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)
}
err = json.NewEncoder(w).Encode(`{"linesOk":1,"linesInvalid":0,"error":null}`)
require.NoError(t, err)
}))
defer ts.Close()

d := &Dynatrace{}

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)
}

var warnfCalledTimes int

type loggerStub struct {
Expand Down

0 comments on commit 95ef674

Please sign in to comment.