diff --git a/plugins/inputs/statsd/README.md b/plugins/inputs/statsd/README.md index 891c59198..a376484c2 100644 --- a/plugins/inputs/statsd/README.md +++ b/plugins/inputs/statsd/README.md @@ -81,6 +81,11 @@ See the [CONFIGURATION.md][CONFIGURATION.md] for more details. ## https://docs.datadoghq.com/developers/metrics/types/?tab=distribution#definition datadog_distributions = false + ## Keep or drop the container id as tag. Included as optional field + ## in DogStatsD protocol v1.2 if source is running in Kubernetes + ## https://docs.datadoghq.com/developers/dogstatsd/datagram_shell/?tab=metrics#dogstatsd-protocol-v12 + datadog_keep_container_tag = false + ## Statsd data translation templates, more info can be read here: ## https://github.com/influxdata/telegraf/blob/master/docs/TEMPLATE_PATTERN.md # templates = [ @@ -259,6 +264,7 @@ measurements and tags. - **parse_data_dog_tags** boolean: Enable parsing of tags in DataDog's dogstatsd format () - **datadog_extensions** boolean: Enable parsing of DataDog's extensions to dogstatsd format () - **datadog_distributions** boolean: Enable parsing of the Distribution metric in DataDog's dogstatsd format () +- **datadog_keep_container_tag** boolean: Keep or drop the container id as tag. Included as optional field in DogStatsD protocol v1.2 if source is running in Kubernetes. - **max_ttl** config.Duration: Max duration (TTL) for each metric to stay cached/reported without being updated. ## Statsd bucket -> InfluxDB line-protocol Templates diff --git a/plugins/inputs/statsd/sample.conf b/plugins/inputs/statsd/sample.conf index 7c63699b9..4f7fbb50a 100644 --- a/plugins/inputs/statsd/sample.conf +++ b/plugins/inputs/statsd/sample.conf @@ -54,6 +54,11 @@ ## https://docs.datadoghq.com/developers/metrics/types/?tab=distribution#definition datadog_distributions = false + ## Keep or drop the container id as tag. Included as optional field + ## in DogStatsD protocol v1.2 if source is running in Kubernetes + ## https://docs.datadoghq.com/developers/dogstatsd/datagram_shell/?tab=metrics#dogstatsd-protocol-v12 + datadog_keep_container_tag = false + ## Statsd data translation templates, more info can be read here: ## https://github.com/influxdata/telegraf/blob/master/docs/TEMPLATE_PATTERN.md # templates = [ diff --git a/plugins/inputs/statsd/statsd.go b/plugins/inputs/statsd/statsd.go index 6579f0d32..b90794db2 100644 --- a/plugins/inputs/statsd/statsd.go +++ b/plugins/inputs/statsd/statsd.go @@ -96,6 +96,11 @@ type Statsd struct { // https://docs.datadoghq.com/developers/metrics/types/?tab=distribution#definition DataDogDistributions bool `toml:"datadog_distributions"` + // Either to keep or drop the container id as tag. + // Requires the DataDogExtension flag to be enabled. + // https://docs.datadoghq.com/developers/dogstatsd/datagram_shell/?tab=metrics#dogstatsd-protocol-v12 + DataDogKeepContainerTag bool `toml:"datadog_keep_container_tag"` + // UDPPacketSize is deprecated, it's only here for legacy support // we now always create 1 max size buffer and then copy only what we need // into the in channel @@ -577,6 +582,11 @@ func (s *Statsd) parseStatsdLine(line string) error { if len(segment) > 0 && segment[0] == '#' { // we have ourselves a tag; they are comma separated parseDataDogTags(lineTags, segment[1:]) + } else if len(segment) > 0 && strings.HasPrefix(segment, "c:") { + // This is optional container ID field + if s.DataDogKeepContainerTag { + lineTags["container"] = segment[2:] + } } else { recombinedSegments = append(recombinedSegments, segment) } diff --git a/plugins/inputs/statsd/statsd_test.go b/plugins/inputs/statsd/statsd_test.go index c8af8fa71..2f7a0fa83 100644 --- a/plugins/inputs/statsd/statsd_test.go +++ b/plugins/inputs/statsd/statsd_test.go @@ -1206,6 +1206,157 @@ func TestParse_DataDogTags(t *testing.T) { } } +func TestParse_DataDogContainerID(t *testing.T) { + tests := []struct { + name string + line string + keep bool + expected []telegraf.Metric + }{ + { + name: "counter", + line: "my_counter:1|c|#host:localhost,endpoint:/:tenant?/oauth/ro|c:f76b5a1c03caa192580874b253c158010ade668cf03080a57aa8283919d56e75", + keep: true, + expected: []telegraf.Metric{ + testutil.MustMetric( + "my_counter", + map[string]string{ + "endpoint": "/:tenant?/oauth/ro", + "host": "localhost", + "metric_type": "counter", + "container": "f76b5a1c03caa192580874b253c158010ade668cf03080a57aa8283919d56e75", + }, + map[string]interface{}{ + "value": 1, + }, + time.Now(), + telegraf.Counter, + ), + }, + }, + { + name: "gauge", + line: "my_gauge:10.1|g|#live|c:f76b5a1c03caa192580874b253c158010ade668cf03080a57aa8283919d56e75", + keep: true, + expected: []telegraf.Metric{ + testutil.MustMetric( + "my_gauge", + map[string]string{ + "live": "true", + "metric_type": "gauge", + "container": "f76b5a1c03caa192580874b253c158010ade668cf03080a57aa8283919d56e75", + }, + map[string]interface{}{ + "value": 10.1, + }, + time.Now(), + telegraf.Gauge, + ), + }, + }, + { + name: "set", + line: "my_set:1|s|#host:localhost|c:f76b5a1c03caa192580874b253c158010ade668cf03080a57aa8283919d56e75", + keep: true, + expected: []telegraf.Metric{ + testutil.MustMetric( + "my_set", + map[string]string{ + "host": "localhost", + "metric_type": "set", + "container": "f76b5a1c03caa192580874b253c158010ade668cf03080a57aa8283919d56e75", + }, + map[string]interface{}{ + "value": 1, + }, + time.Now(), + ), + }, + }, + { + name: "timer", + line: "my_timer:3|ms|@0.1|#live,host:localhost|c:f76b5a1c03caa192580874b253c158010ade668cf03080a57aa8283919d56e75", + keep: true, + expected: []telegraf.Metric{ + testutil.MustMetric( + "my_timer", + map[string]string{ + "host": "localhost", + "live": "true", + "metric_type": "timing", + "container": "f76b5a1c03caa192580874b253c158010ade668cf03080a57aa8283919d56e75", + }, + map[string]interface{}{ + "count": 10, + "lower": float64(3), + "mean": float64(3), + "median": float64(3), + "stddev": float64(0), + "sum": float64(30), + "upper": float64(3), + }, + time.Now(), + ), + }, + }, + { + name: "empty tag set", + line: "cpu:42|c|#|c:f76b5a1c03caa192580874b253c158010ade668cf03080a57aa8283919d56e75", + keep: true, + expected: []telegraf.Metric{ + testutil.MustMetric( + "cpu", + map[string]string{ + "metric_type": "counter", + "container": "f76b5a1c03caa192580874b253c158010ade668cf03080a57aa8283919d56e75", + }, + map[string]interface{}{ + "value": 42, + }, + time.Now(), + telegraf.Counter, + ), + }, + }, + { + name: "drop it", + line: "cpu:42|c|#live,host:localhost|c:f76b5a1c03caa192580874b253c158010ade668cf03080a57aa8283919d56e75", + keep: false, + expected: []telegraf.Metric{ + testutil.MustMetric( + "cpu", + map[string]string{ + "host": "localhost", + "live": "true", + "metric_type": "counter", + }, + map[string]interface{}{ + "value": 42, + }, + time.Now(), + telegraf.Counter, + ), + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + var acc testutil.Accumulator + + s := NewTestStatsd() + s.DataDogExtensions = true + s.DataDogKeepContainerTag = tt.keep + + require.NoError(t, s.parseStatsdLine(tt.line)) + require.NoError(t, s.Gather(&acc)) + + testutil.RequireMetricsEqual(t, tt.expected, acc.GetTelegrafMetrics(), + testutil.SortMetrics(), testutil.IgnoreTime()) + }) + } +} + // Test that statsd buckets are parsed to measurement names properly func TestParseName(t *testing.T) { s := NewTestStatsd()