From 4b3b16ef1a4fd9f90a8d5e6858579b38c7eaccd8 Mon Sep 17 00:00:00 2001 From: Matteo Cerutti Date: Thu, 8 Jun 2017 21:17:31 +0100 Subject: [PATCH] Add wildcard support for container inclusion/exclusion (#2793) --- plugins/inputs/docker/README.md | 10 +++- plugins/inputs/docker/docker.go | 74 +++++++++++++++++++++++----- plugins/inputs/docker/docker_test.go | 68 +++++++++++++++++++++++++ 3 files changed, 138 insertions(+), 14 deletions(-) diff --git a/plugins/inputs/docker/README.md b/plugins/inputs/docker/README.md index 12741d940bcbc..19102631bbee7 100644 --- a/plugins/inputs/docker/README.md +++ b/plugins/inputs/docker/README.md @@ -20,14 +20,22 @@ for the stat structure can be found ## To use TCP, set endpoint = "tcp://[ip]:[port]" ## To use environment variables (ie, docker-machine), set endpoint = "ENV" endpoint = "unix:///var/run/docker.sock" - ## Only collect metrics for these containers, collect all if empty + + ## Only collect metrics for these containers. Values will be appended to container_name_include. + ## Deprecated (1.4.0), use container_name_include container_names = [] + + ## Containers to include and exclude. Collect all if empty. Globs accepted. + container_name_include = [] + container_name_exclude = [] + ## Timeout for docker list, info, and stats commands timeout = "5s" ## Whether to report for each container per-device blkio (8:0, 8:1...) and ## network (eth0, eth1, ...) stats or not perdevice = true + ## Whether to report for each container total blkio and network stats or not total = false diff --git a/plugins/inputs/docker/docker.go b/plugins/inputs/docker/docker.go index 13ade3945a676..ca7b70f9f8223 100644 --- a/plugins/inputs/docker/docker.go +++ b/plugins/inputs/docker/docker.go @@ -24,24 +24,33 @@ type DockerLabelFilter struct { labelExclude filter.Filter } +type DockerContainerFilter struct { + containerInclude filter.Filter + containerExclude filter.Filter +} + // Docker object type Docker struct { Endpoint string ContainerNames []string + Timeout internal.Duration PerDevice bool `toml:"perdevice"` Total bool `toml:"total"` TagEnvironment []string `toml:"tag_env"` LabelInclude []string `toml:"docker_label_include"` LabelExclude []string `toml:"docker_label_exclude"` + LabelFilter DockerLabelFilter - LabelFilter DockerLabelFilter + ContainerInclude []string `toml:"container_name_include"` + ContainerExclude []string `toml:"container_name_exclude"` + ContainerFilter DockerContainerFilter client *client.Client engine_host string - testing bool - labelFiltersCreated bool + testing bool + filtersCreated bool } // infoWrapper wraps client.Client.List for testing. @@ -110,8 +119,15 @@ var sampleConfig = ` ## To use TCP, set endpoint = "tcp://[ip]:[port]" ## To use environment variables (ie, docker-machine), set endpoint = "ENV" endpoint = "unix:///var/run/docker.sock" + ## Only collect metrics for these containers, collect all if empty container_names = [] + + ## Containers to include and exclude. Globs accepted. + ## Note that an empty array for both will include all containers + container_name_include = [] + container_name_exclude = [] + ## Timeout for docker list, info, and stats commands timeout = "5s" @@ -161,13 +177,18 @@ func (d *Docker) Gather(acc telegraf.Accumulator) error { } d.client = c } + // Create label filters if not already created - if !d.labelFiltersCreated { + if !d.filtersCreated { err := d.createLabelFilters() if err != nil { return err } - d.labelFiltersCreated = true + err = d.createContainerFilters() + if err != nil { + return err + } + d.filtersCreated = true } // Get daemon info @@ -306,9 +327,12 @@ func (d *Docker) gatherContainer( "container_image": imageName, "container_version": imageVersion, } - if len(d.ContainerNames) > 0 { - if !sliceContains(cname, d.ContainerNames) { - return nil + + if len(d.ContainerInclude) > 0 || len(d.ContainerExclude) > 0 { + if len(d.ContainerInclude) == 0 || !d.ContainerFilter.containerInclude.Match(cname) { + if len(d.ContainerExclude) == 0 || d.ContainerFilter.containerExclude.Match(cname) { + return nil + } } } @@ -656,8 +680,32 @@ func parseSize(sizeStr string) (int64, error) { return int64(size), nil } +func (d *Docker) createContainerFilters() error { + if len(d.ContainerNames) > 0 { + d.ContainerInclude = append(d.ContainerInclude, d.ContainerNames...) + } + + if len(d.ContainerInclude) != 0 { + var err error + d.ContainerFilter.containerInclude, err = filter.Compile(d.ContainerInclude) + if err != nil { + return err + } + } + + if len(d.ContainerExclude) != 0 { + var err error + d.ContainerFilter.containerExclude, err = filter.Compile(d.ContainerExclude) + if err != nil { + return err + } + } + + return nil +} + func (d *Docker) createLabelFilters() error { - if len(d.LabelInclude) != 0 && d.LabelFilter.labelInclude == nil { + if len(d.LabelInclude) != 0 { var err error d.LabelFilter.labelInclude, err = filter.Compile(d.LabelInclude) if err != nil { @@ -665,7 +713,7 @@ func (d *Docker) createLabelFilters() error { } } - if len(d.LabelExclude) != 0 && d.LabelFilter.labelExclude == nil { + if len(d.LabelExclude) != 0 { var err error d.LabelFilter.labelExclude, err = filter.Compile(d.LabelExclude) if err != nil { @@ -679,9 +727,9 @@ func (d *Docker) createLabelFilters() error { func init() { inputs.Add("docker", func() telegraf.Input { return &Docker{ - PerDevice: true, - Timeout: internal.Duration{Duration: time.Second * 5}, - labelFiltersCreated: false, + PerDevice: true, + Timeout: internal.Duration{Duration: time.Second * 5}, + filtersCreated: false, } }) } diff --git a/plugins/inputs/docker/docker_test.go b/plugins/inputs/docker/docker_test.go index 025d482fe0f2d..45797df5c3536 100644 --- a/plugins/inputs/docker/docker_test.go +++ b/plugins/inputs/docker/docker_test.go @@ -18,6 +18,7 @@ func TestDockerGatherContainerStats(t *testing.T) { "container_name": "redis", "container_image": "redis/image", } + gatherContainerStats(stats, &acc, tags, "123456789", true, true) // test docker_container_net measurement @@ -295,6 +296,73 @@ func TestDockerGatherLabels(t *testing.T) { } } +var gatherContainerNames = []struct { + include []string + exclude []string + expected []string + notexpected []string +}{ + {[]string{}, []string{}, []string{"etcd", "etcd2"}, []string{}}, + {[]string{"*"}, []string{}, []string{"etcd", "etcd2"}, []string{}}, + {[]string{"etc*"}, []string{}, []string{"etcd", "etcd2"}, []string{}}, + {[]string{"etcd"}, []string{}, []string{"etcd"}, []string{"etcd2"}}, + {[]string{"etcd2*"}, []string{}, []string{"etcd2"}, []string{"etcd"}}, + {[]string{}, []string{"etc*"}, []string{}, []string{"etcd", "etcd2"}}, + {[]string{}, []string{"etcd"}, []string{"etcd2"}, []string{"etcd"}}, + {[]string{"*"}, []string{"*"}, []string{"etcd", "etcd2"}, []string{}}, + {[]string{}, []string{"*"}, []string{""}, []string{"etcd", "etcd2"}}, +} + +func TestContainerNames(t *testing.T) { + for _, tt := range gatherContainerNames { + var acc testutil.Accumulator + + d := Docker{ + client: nil, + testing: true, + ContainerInclude: tt.include, + ContainerExclude: tt.exclude, + } + + err := d.Gather(&acc) + require.NoError(t, err) + + for _, metric := range acc.Metrics { + if metric.Measurement == "docker_container_cpu" { + if val, ok := metric.Tags["container_name"]; ok { + var found bool = false + for _, cname := range tt.expected { + if val == cname { + found = true + break + } + } + if !found { + t.Errorf("Got unexpected container of %s. Test was -> Include: %s, Exclude: %s", val, tt.include, tt.exclude) + } + } + } + } + + for _, metric := range acc.Metrics { + if metric.Measurement == "docker_container_cpu" { + if val, ok := metric.Tags["container_name"]; ok { + var found bool = false + for _, cname := range tt.notexpected { + if val == cname { + found = true + break + } + } + if found { + t.Errorf("Got unexpected container of %s. Test was -> Include: %s, Exclude: %s", val, tt.include, tt.exclude) + } + } + } + } + } +} + func TestDockerGatherInfo(t *testing.T) { var acc testutil.Accumulator d := Docker{