From 35e439016825b49d6b41223b7494f433c21eceb4 Mon Sep 17 00:00:00 2001 From: Shakeel Sorathia Date: Mon, 3 Apr 2017 13:43:15 -0700 Subject: [PATCH] Docker: optionally add labels as tags (#2425) --- CHANGELOG.md | 1 + plugins/inputs/docker/README.md | 26 ++++++++---- plugins/inputs/docker/docker.go | 61 ++++++++++++++++++++++++---- plugins/inputs/docker/docker_test.go | 55 +++++++++++++++++++++++++ plugins/inputs/docker/fake_client.go | 8 ++++ 5 files changed, 135 insertions(+), 16 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index fa4b820c7..e7da095d4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -63,6 +63,7 @@ be deprecated eventually. - [#2332](https://github.com/influxdata/telegraf/pull/2332): Add Elasticsearch 5.x output - [#2587](https://github.com/influxdata/telegraf/pull/2587): Add json timestamp units configurability - [#2597](https://github.com/influxdata/telegraf/issues/2597): Add support for Linux sysctl-fs metrics. +- [#2425](https://github.com/influxdata/telegraf/pull/2425): Support to include/exclude docker container labels as tags ### Bugfixes diff --git a/plugins/inputs/docker/README.md b/plugins/inputs/docker/README.md index 94965213f..849450b33 100644 --- a/plugins/inputs/docker/README.md +++ b/plugins/inputs/docker/README.md @@ -30,6 +30,12 @@ for the stat structure can be found perdevice = true ## Whether to report for each container total blkio and network stats or not total = false + + ## docker labels to include and exclude as tags. Globs accepted. + ## Note that an empty array for both will include all labels as tags + docker_label_include = [] + docker_label_exclude = [] + ``` ### Measurements & Fields: @@ -130,30 +136,32 @@ based on the availability of per-cpu stats on your system. ### Tags: - +#### Docker Engine tags - docker (memory_total) - unit=bytes + - engine_host - docker (pool_blocksize) - unit=bytes + - engine_host - docker_data - unit=bytes + - engine_host - docker_metadata - unit=bytes + - engine_host +#### Docker Container tags +- Tags on all containers: + - engine_host + - container_image + - container_name + - container_version - docker_container_mem specific: - - container_image - - container_name - docker_container_cpu specific: - - container_image - - container_name - cpu - docker_container_net specific: - - container_image - - container_name - network - docker_container_blkio specific: - - container_image - - container_name - device ### Example Output: diff --git a/plugins/inputs/docker/docker.go b/plugins/inputs/docker/docker.go index ec192efd5..47d1db14b 100644 --- a/plugins/inputs/docker/docker.go +++ b/plugins/inputs/docker/docker.go @@ -14,24 +14,34 @@ import ( "github.com/docker/docker/api/types" "github.com/docker/docker/client" - "github.com/influxdata/telegraf" + "github.com/influxdata/telegraf/filter" "github.com/influxdata/telegraf/internal" "github.com/influxdata/telegraf/plugins/inputs" ) +type DockerLabelFilter struct { + labelInclude filter.Filter + labelExclude filter.Filter +} + // Docker object type Docker struct { Endpoint string ContainerNames []string Timeout internal.Duration - PerDevice bool `toml:"perdevice"` - Total bool `toml:"total"` + PerDevice bool `toml:"perdevice"` + Total bool `toml:"total"` + LabelInclude []string `toml:"docker_label_include"` + LabelExclude []string `toml:"docker_label_exclude"` + + LabelFilter DockerLabelFilter client *client.Client engine_host string - testing bool + testing bool + labelFiltersCreated bool } // infoWrapper wraps client.Client.List for testing. @@ -99,6 +109,10 @@ var sampleConfig = ` ## Whether to report for each container total blkio and network stats or not total = false + ## docker labels to include and exclude as tags. Globs accepted. + ## Note that an empty array for both will include all labels as tags + docker_label_include = [] + docker_label_exclude = [] ` // Description returns input description @@ -133,6 +147,14 @@ func (d *Docker) Gather(acc telegraf.Accumulator) error { } d.client = c } + // Create label filters if not already created + if !d.labelFiltersCreated { + err := d.createLabelFilters() + if err != nil { + return err + } + d.labelFiltersCreated = true + } // Get daemon info err := d.gatherInfo(acc) @@ -293,7 +315,11 @@ func (d *Docker) gatherContainer( // Add labels to tags for k, label := range container.Labels { - tags[k] = label + if len(d.LabelInclude) == 0 || d.LabelFilter.labelInclude.Match(k) { + if len(d.LabelExclude) == 0 || !d.LabelFilter.labelExclude.Match(k) { + tags[k] = label + } + } } gatherContainerStats(v, acc, tags, container.ID, d.PerDevice, d.Total) @@ -599,11 +625,32 @@ func parseSize(sizeStr string) (int64, error) { return int64(size), nil } +func (d *Docker) createLabelFilters() error { + if len(d.LabelInclude) != 0 && d.LabelFilter.labelInclude == nil { + var err error + d.LabelFilter.labelInclude, err = filter.Compile(d.LabelInclude) + if err != nil { + return err + } + } + + if len(d.LabelExclude) != 0 && d.LabelFilter.labelExclude == nil { + var err error + d.LabelFilter.labelExclude, err = filter.Compile(d.LabelExclude) + if err != nil { + return err + } + } + + return nil +} + func init() { inputs.Add("docker", func() telegraf.Input { return &Docker{ - PerDevice: true, - Timeout: internal.Duration{Duration: time.Second * 5}, + PerDevice: true, + Timeout: internal.Duration{Duration: time.Second * 5}, + labelFiltersCreated: false, } }) } diff --git a/plugins/inputs/docker/docker_test.go b/plugins/inputs/docker/docker_test.go index f0add03ea..3e2e1607b 100644 --- a/plugins/inputs/docker/docker_test.go +++ b/plugins/inputs/docker/docker_test.go @@ -244,6 +244,57 @@ func testStats() *types.StatsJSON { return stats } +var gatherLabelsTests = []struct { + include []string + exclude []string + expected []string + notexpected []string +}{ + {[]string{}, []string{}, []string{"label1", "label2"}, []string{}}, + {[]string{"*"}, []string{}, []string{"label1", "label2"}, []string{}}, + {[]string{"lab*"}, []string{}, []string{"label1", "label2"}, []string{}}, + {[]string{"label1"}, []string{}, []string{"label1"}, []string{"label2"}}, + {[]string{"label1*"}, []string{}, []string{"label1"}, []string{"label2"}}, + {[]string{}, []string{"*"}, []string{}, []string{"label1", "label2"}}, + {[]string{}, []string{"lab*"}, []string{}, []string{"label1", "label2"}}, + {[]string{}, []string{"label1"}, []string{"label2"}, []string{"label1"}}, + {[]string{"*"}, []string{"*"}, []string{}, []string{"label1", "label2"}}, +} + +func TestDockerGatherLabels(t *testing.T) { + for _, tt := range gatherLabelsTests { + var acc testutil.Accumulator + d := Docker{ + client: nil, + testing: true, + } + + for _, label := range tt.include { + d.LabelInclude = append(d.LabelInclude, label) + } + for _, label := range tt.exclude { + d.LabelExclude = append(d.LabelExclude, label) + } + + err := d.Gather(&acc) + require.NoError(t, err) + + for _, label := range tt.expected { + if !acc.HasTag("docker_container_cpu", label) { + t.Errorf("Didn't get expected label of %s. Test was: Include: %s Exclude %s", + label, tt.include, tt.exclude) + } + } + + for _, label := range tt.notexpected { + if acc.HasTag("docker_container_cpu", label) { + t.Errorf("Got unexpected label of %s. Test was: Include: %s Exclude %s", + label, tt.include, tt.exclude) + } + } + } +} + func TestDockerGatherInfo(t *testing.T) { var acc testutil.Accumulator d := Docker{ @@ -294,6 +345,8 @@ func TestDockerGatherInfo(t *testing.T) { "cpu": "cpu3", "container_version": "v2.2.2", "engine_host": "absol", + "label1": "test_value_1", + "label2": "test_value_2", }, ) acc.AssertContainsTaggedFields(t, @@ -340,6 +393,8 @@ func TestDockerGatherInfo(t *testing.T) { "container_name": "etcd2", "container_image": "quay.io:4443/coreos/etcd", "container_version": "v2.2.2", + "label1": "test_value_1", + "label2": "test_value_2", }, ) diff --git a/plugins/inputs/docker/fake_client.go b/plugins/inputs/docker/fake_client.go index 03da23198..dcca6f235 100644 --- a/plugins/inputs/docker/fake_client.go +++ b/plugins/inputs/docker/fake_client.go @@ -92,6 +92,10 @@ func (d FakeDockerClient) ContainerList(octx context.Context, options types.Cont IP: "0.0.0.0", }, }, + Labels: map[string]string{ + "label1": "test_value_1", + "label2": "test_value_2", + }, SizeRw: 0, SizeRootFs: 0, } @@ -125,6 +129,10 @@ func (d FakeDockerClient) ContainerList(octx context.Context, options types.Cont IP: "0.0.0.0", }, }, + Labels: map[string]string{ + "label1": "test_value_1", + "label2": "test_value_2", + }, SizeRw: 0, SizeRootFs: 0, }