feat(outputs.dynatrace): add support for metric to be treated and reported as a delta counter using regular expression (#15668)
This commit is contained in:
parent
ef41198481
commit
f57d6764cf
|
|
@ -6,9 +6,10 @@ 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
|
without a OneAgent by specifying a URL and API Token. More information on the
|
||||||
plugin can be found in the [Dynatrace documentation][docs]. All metrics are
|
plugin can be found in the [Dynatrace documentation][docs]. All metrics are
|
||||||
reported as gauges, unless they are specified to be delta counters using the
|
reported as gauges, unless they are specified to be delta counters using the
|
||||||
`additional_counters` config option (see below). See the [Dynatrace Metrics
|
`additional_counters` or `additional_counters_patterns` config option
|
||||||
ingestion protocol documentation][proto-docs] for details on the types defined
|
(see below).
|
||||||
there.
|
See the [Dynatrace Metrics ingestion protocol documentation][proto-docs]
|
||||||
|
for details on the types defined there.
|
||||||
|
|
||||||
[api-v2]: https://docs.dynatrace.com/docs/shortlink/api-metrics-v2
|
[api-v2]: https://docs.dynatrace.com/docs/shortlink/api-metrics-v2
|
||||||
|
|
||||||
|
|
@ -144,6 +145,10 @@ to use them.
|
||||||
## If you want metrics to be treated and reported as delta 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 = [ ]
|
additional_counters = [ ]
|
||||||
|
|
||||||
|
## In addition or as an alternative to additional_counters, if you want metrics to be treated and
|
||||||
|
## reported as delta counters using regular expression pattern matching
|
||||||
|
additional_counters_patterns = [ ]
|
||||||
|
|
||||||
## NOTE: Due to the way TOML is parsed, tables must be at the END of the
|
## NOTE: Due to the way TOML is parsed, tables must be at the END of the
|
||||||
## plugin definition, otherwise additional config options are read as part of
|
## plugin definition, otherwise additional config options are read as part of
|
||||||
## the table
|
## the table
|
||||||
|
|
@ -216,6 +221,18 @@ to this list.
|
||||||
additional_counters = [ ]
|
additional_counters = [ ]
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### `additional_counters_patterns`
|
||||||
|
|
||||||
|
*required*: `false`
|
||||||
|
|
||||||
|
In addition or as an alternative to additional_counters, if you want a metric
|
||||||
|
to be treated and reported as a delta counter using regular expression,
|
||||||
|
add its pattern to this list.
|
||||||
|
|
||||||
|
```toml
|
||||||
|
additional_counters_patterns = [ ]
|
||||||
|
```
|
||||||
|
|
||||||
### `default_dimensions`
|
### `default_dimensions`
|
||||||
|
|
||||||
*required*: `false`
|
*required*: `false`
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
|
@ -26,12 +27,14 @@ var sampleConfig string
|
||||||
|
|
||||||
// Dynatrace Configuration for the Dynatrace output plugin
|
// Dynatrace Configuration for the Dynatrace output plugin
|
||||||
type Dynatrace struct {
|
type Dynatrace struct {
|
||||||
URL string `toml:"url"`
|
URL string `toml:"url"`
|
||||||
APIToken config.Secret `toml:"api_token"`
|
APIToken config.Secret `toml:"api_token"`
|
||||||
Prefix string `toml:"prefix"`
|
Prefix string `toml:"prefix"`
|
||||||
Log telegraf.Logger `toml:"-"`
|
Log telegraf.Logger `toml:"-"`
|
||||||
Timeout config.Duration `toml:"timeout"`
|
Timeout config.Duration `toml:"timeout"`
|
||||||
AddCounterMetrics []string `toml:"additional_counters"`
|
AddCounterMetrics []string `toml:"additional_counters"`
|
||||||
|
AddCounterMetricsPatterns []string `toml:"additional_counters_patterns"`
|
||||||
|
|
||||||
DefaultDimensions map[string]string `toml:"default_dimensions"`
|
DefaultDimensions map[string]string `toml:"default_dimensions"`
|
||||||
|
|
||||||
normalizedDefaultDimensions dimensions.NormalizedDimensionList
|
normalizedDefaultDimensions dimensions.NormalizedDimensionList
|
||||||
|
|
@ -229,10 +232,8 @@ func init() {
|
||||||
|
|
||||||
func (d *Dynatrace) getTypeOption(metric telegraf.Metric, field *telegraf.Field) dtMetric.MetricOption {
|
func (d *Dynatrace) getTypeOption(metric telegraf.Metric, field *telegraf.Field) dtMetric.MetricOption {
|
||||||
metricName := metric.Name() + "." + field.Key
|
metricName := metric.Name() + "." + field.Key
|
||||||
for _, i := range d.AddCounterMetrics {
|
if d.isCounterMetricsMatch(d.AddCounterMetrics, metricName) ||
|
||||||
if metricName != i {
|
d.isCounterMetricsPatternsMatch(d.AddCounterMetricsPatterns, metricName) {
|
||||||
continue
|
|
||||||
}
|
|
||||||
switch v := field.Value.(type) {
|
switch v := field.Value.(type) {
|
||||||
case float64:
|
case float64:
|
||||||
return dtMetric.WithFloatCounterValueDelta(v)
|
return dtMetric.WithFloatCounterValueDelta(v)
|
||||||
|
|
@ -244,7 +245,6 @@ func (d *Dynatrace) getTypeOption(metric telegraf.Metric, field *telegraf.Field)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
switch v := field.Value.(type) {
|
switch v := field.Value.(type) {
|
||||||
case float64:
|
case float64:
|
||||||
return dtMetric.WithFloatGaugeValue(v)
|
return dtMetric.WithFloatGaugeValue(v)
|
||||||
|
|
@ -261,3 +261,22 @@ func (d *Dynatrace) getTypeOption(metric telegraf.Metric, field *telegraf.Field)
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (d *Dynatrace) isCounterMetricsMatch(counterMetrics []string, metricName string) bool {
|
||||||
|
for _, i := range counterMetrics {
|
||||||
|
if i == metricName {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Dynatrace) isCounterMetricsPatternsMatch(counterPatterns []string, metricName string) bool {
|
||||||
|
for _, pattern := range counterPatterns {
|
||||||
|
regex, err := regexp.Compile(pattern)
|
||||||
|
if err == nil && regex.MatchString(metricName) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -213,6 +213,116 @@ func TestSendMetrics(t *testing.T) {
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestSendMetricsWithPatterns(t *testing.T) {
|
||||||
|
expected := []string{}
|
||||||
|
|
||||||
|
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
// check the encoded result
|
||||||
|
bodyBytes, err := io.ReadAll(r.Body)
|
||||||
|
require.NoError(t, err)
|
||||||
|
bodyString := string(bodyBytes)
|
||||||
|
|
||||||
|
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(fmt.Sprintf(`{"linesOk":%d,"linesInvalid":0,"error":null}`, len(lines)))
|
||||||
|
require.NoError(t, err)
|
||||||
|
}))
|
||||||
|
defer ts.Close()
|
||||||
|
|
||||||
|
d := &Dynatrace{
|
||||||
|
URL: ts.URL,
|
||||||
|
APIToken: config.NewSecret([]byte("123")),
|
||||||
|
Log: testutil.Logger{},
|
||||||
|
AddCounterMetrics: []string{},
|
||||||
|
AddCounterMetricsPatterns: []string{},
|
||||||
|
}
|
||||||
|
|
||||||
|
err := d.Init()
|
||||||
|
require.NoError(t, err)
|
||||||
|
err = d.Connect()
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
// Init metrics
|
||||||
|
|
||||||
|
// Simple metrics are exported as a gauge unless pattern match in additional_counters_patterns
|
||||||
|
expected = append(expected,
|
||||||
|
"simple_abc_metric.value,dt.metrics.source=telegraf gauge,3.14 1289430000000",
|
||||||
|
"simple_abc_metric.counter,dt.metrics.source=telegraf count,delta=5 1289430000000",
|
||||||
|
"simple_xyz_metric.value,dt.metrics.source=telegraf gauge,3.14 1289430000000",
|
||||||
|
"simple_xyz_metric.counter,dt.metrics.source=telegraf count,delta=5 1289430000000",
|
||||||
|
)
|
||||||
|
// Add pattern to match all metrics that match simple_[a-z]+_metric.counter
|
||||||
|
d.AddCounterMetricsPatterns = append(d.AddCounterMetricsPatterns, "simple_[a-z]+_metric.counter")
|
||||||
|
|
||||||
|
m1 := metric.New(
|
||||||
|
"simple_abc_metric",
|
||||||
|
map[string]string{},
|
||||||
|
map[string]interface{}{"value": float64(3.14), "counter": 5},
|
||||||
|
time.Date(2010, time.November, 10, 23, 0, 0, 0, time.UTC),
|
||||||
|
)
|
||||||
|
|
||||||
|
m2 := metric.New(
|
||||||
|
"simple_xyz_metric",
|
||||||
|
map[string]string{},
|
||||||
|
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 pattern match with additional_counters_patterns
|
||||||
|
expected = append(expected,
|
||||||
|
"counter_fan01_type.value,dt.metrics.source=telegraf gauge,3.14 1289430000000",
|
||||||
|
"counter_fan01_type.counter,dt.metrics.source=telegraf count,delta=5 1289430000000",
|
||||||
|
"counter_fanNaN_type.counter,dt.metrics.source=telegraf gauge,5 1289430000000",
|
||||||
|
"counter_fanNaN_type.value,dt.metrics.source=telegraf gauge,3.14 1289430000000",
|
||||||
|
)
|
||||||
|
d.AddCounterMetricsPatterns = append(d.AddCounterMetricsPatterns, "counter_fan[0-9]+_type.counter")
|
||||||
|
m3 := metric.New(
|
||||||
|
"counter_fan01_type",
|
||||||
|
map[string]string{},
|
||||||
|
map[string]interface{}{"value": float64(3.14), "counter": 5},
|
||||||
|
time.Date(2010, time.November, 10, 23, 0, 0, 0, time.UTC),
|
||||||
|
telegraf.Counter,
|
||||||
|
)
|
||||||
|
|
||||||
|
m4 := metric.New(
|
||||||
|
"counter_fanNaN_type",
|
||||||
|
map[string]string{},
|
||||||
|
map[string]interface{}{"value": float64(3.14), "counter": 5},
|
||||||
|
time.Date(2010, time.November, 10, 23, 0, 0, 0, time.UTC),
|
||||||
|
telegraf.Counter,
|
||||||
|
)
|
||||||
|
|
||||||
|
expected = append(expected,
|
||||||
|
"complex_metric.int,dt.metrics.source=telegraf gauge,1 1289430000000",
|
||||||
|
"complex_metric.int64,dt.metrics.source=telegraf gauge,2 1289430000000",
|
||||||
|
"complex_metric.float,dt.metrics.source=telegraf gauge,3 1289430000000",
|
||||||
|
"complex_metric.float64,dt.metrics.source=telegraf gauge,4 1289430000000",
|
||||||
|
"complex_metric.true,dt.metrics.source=telegraf gauge,1 1289430000000",
|
||||||
|
"complex_metric.false,dt.metrics.source=telegraf gauge,0 1289430000000",
|
||||||
|
)
|
||||||
|
|
||||||
|
m5 := 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, m4, m5}
|
||||||
|
|
||||||
|
err = d.Write(metrics)
|
||||||
|
require.NoError(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
func TestSendSingleMetricWithUnorderedTags(t *testing.T) {
|
func TestSendSingleMetricWithUnorderedTags(t *testing.T) {
|
||||||
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
// check the encoded result
|
// check the encoded result
|
||||||
|
|
|
||||||
|
|
@ -31,6 +31,10 @@
|
||||||
## If you want metrics to be treated and reported as delta 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 = [ ]
|
additional_counters = [ ]
|
||||||
|
|
||||||
|
## In addition or as an alternative to additional_counters, if you want metrics to be treated and
|
||||||
|
## reported as delta counters using regular expression pattern matching
|
||||||
|
additional_counters_patterns = [ ]
|
||||||
|
|
||||||
## NOTE: Due to the way TOML is parsed, tables must be at the END of the
|
## NOTE: Due to the way TOML is parsed, tables must be at the END of the
|
||||||
## plugin definition, otherwise additional config options are read as part of
|
## plugin definition, otherwise additional config options are read as part of
|
||||||
## the table
|
## the table
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue