From 0fb2d2ffaecd18afae30c91365b340a6f23e98cb Mon Sep 17 00:00:00 2001 From: Robin Percy Date: Tue, 29 Mar 2016 21:39:50 -0700 Subject: [PATCH] Adding a conntrack input plugin - Collects conntrack stats from the configured directories and files. Applying PR feedback: - Rebased onto master - Updated README/CHANGELOG - Limited lines to 80 chars - Improved plugin docs and README - added a dummy notlinux build file Fixed up CHANGELOG and README after rebase closes #1164 --- CHANGELOG.md | 1 + README.md | 1 + etc/telegraf.conf | 10 ++ plugins/inputs/all/all.go | 1 + plugins/inputs/conntrack/README.md | 56 +++++++++ plugins/inputs/conntrack/conntrack.go | 119 ++++++++++++++++++ .../inputs/conntrack/conntrack_notlinux.go | 3 + plugins/inputs/conntrack/conntrack_test.go | 90 +++++++++++++ 8 files changed, 281 insertions(+) create mode 100644 plugins/inputs/conntrack/README.md create mode 100644 plugins/inputs/conntrack/conntrack.go create mode 100644 plugins/inputs/conntrack/conntrack_notlinux.go create mode 100644 plugins/inputs/conntrack/conntrack_test.go diff --git a/CHANGELOG.md b/CHANGELOG.md index faa36cf38..0f66771af 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,6 +19,7 @@ time before a new metric is included by the plugin. ### Features +- [#1164](https://github.com/influxdata/telegraf/pull/1164): conntrack input plugin. Thanks @robinpercy! - [#1247](https://github.com/influxdata/telegraf/pull/1247): rollbar input plugin. Thanks @francois2metz and @cduez! - [#1208](https://github.com/influxdata/telegraf/pull/1208): Standardized AWS credentials evaluation & wildcard CloudWatch dimensions. Thanks @johnrengelman! - [#1264](https://github.com/influxdata/telegraf/pull/1264): Add SSL config options to http_response plugin. diff --git a/README.md b/README.md index 1a6a04382..5adcdb39d 100644 --- a/README.md +++ b/README.md @@ -145,6 +145,7 @@ Currently implemented sources: * [cassandra](https://github.com/influxdata/telegraf/tree/master/plugins/inputs/cassandra) * [ceph](https://github.com/influxdata/telegraf/tree/master/plugins/inputs/ceph) * [chrony](https://github.com/influxdata/telegraf/tree/master/plugins/inputs/chrony) +* [conntrack](https://github.com/influxdata/telegraf/tree/master/plugins/inputs/conntrack) * [couchbase](https://github.com/influxdata/telegraf/tree/master/plugins/inputs/couchbase) * [couchdb](https://github.com/influxdata/telegraf/tree/master/plugins/inputs/couchdb) * [disque](https://github.com/influxdata/telegraf/tree/master/plugins/inputs/disque) diff --git a/etc/telegraf.conf b/etc/telegraf.conf index 4081cf484..f0d8a3361 100644 --- a/etc/telegraf.conf +++ b/etc/telegraf.conf @@ -1575,3 +1575,13 @@ # ## https://github.com/influxdata/telegraf/blob/master/docs/DATA_FORMATS_INPUT.md # data_format = "influx" +# # Collects conntrack stats from the configured directories and files. +# [[inputs.conntrack]] +# ## The following defaults would work with multiple versions of contrack. Note the nf_ and ip_ +# ## filename prefixes are mutually exclusive across conntrack versions, as are the directory locations. +# +# ## Superset of filenames to look for within the conntrack dirs. Missing files will be ignored. +# files = ["ip_conntrack_count","ip_conntrack_max","nf_conntrack_count","nf_conntrack_max"] +# +# ## Directories to search within for the conntrack files above. Missing directrories will be ignored. +# dirs = ["/proc/sys/net/ipv4/netfilter","/proc/sys/net/netfilter"] diff --git a/plugins/inputs/all/all.go b/plugins/inputs/all/all.go index c2322c436..8c12e0858 100644 --- a/plugins/inputs/all/all.go +++ b/plugins/inputs/all/all.go @@ -8,6 +8,7 @@ import ( _ "github.com/influxdata/telegraf/plugins/inputs/ceph" _ "github.com/influxdata/telegraf/plugins/inputs/chrony" _ "github.com/influxdata/telegraf/plugins/inputs/cloudwatch" + _ "github.com/influxdata/telegraf/plugins/inputs/conntrack" _ "github.com/influxdata/telegraf/plugins/inputs/couchbase" _ "github.com/influxdata/telegraf/plugins/inputs/couchdb" _ "github.com/influxdata/telegraf/plugins/inputs/disque" diff --git a/plugins/inputs/conntrack/README.md b/plugins/inputs/conntrack/README.md new file mode 100644 index 000000000..dff20337e --- /dev/null +++ b/plugins/inputs/conntrack/README.md @@ -0,0 +1,56 @@ +# Conntrack Plugin + +Collects stats from Netfilter's conntrack-tools. + +The conntrack-tools provide a mechanism for tracking various aspects of +network connections as they are processed by netfilter. At runtime, +conntrack exposes many of those connection statistics within /proc/sys/net. +Depending on your kernel version, these files can be found in either +/proc/sys/net/ipv4/netfilter or /proc/sys/net/netfilter and will be +prefixed with either ip_ or nf_. This plugin reads the files specified +in its configuration and publishes each one as a field, with the prefix +normalized to ip_. + +In order to simplify configuration in a heterogeneous environment, a superset +of directory and filenames can be specified. Any locations that don't exist +will be ignored. + +For more information on conntrack-tools, see the +[Netfilter Documentation](http://conntrack-tools.netfilter.org/). + + +### Configuration: + +```toml + # Collects conntrack stats from the configured directories and files. + [[inputs.conntrack]] + ## The following defaults would work with multiple versions of conntrack. + ## Note the nf_ and ip_ filename prefixes are mutually exclusive across + ## kernel versions, as are the directory locations. + + ## Superset of filenames to look for within the conntrack dirs. + ## Missing files will be ignored. + files = ["ip_conntrack_count","ip_conntrack_max", + "nf_conntrack_count","nf_conntrack_max"] + + ## Directories to search within for the conntrack files above. + ## Missing directrories will be ignored. + dirs = ["/proc/sys/net/ipv4/netfilter","/proc/sys/net/netfilter"] +``` + +### Measurements & Fields: + +- conntrack + - ip_conntrack_count (int, count): the number of entries in the conntrack table + - ip_conntrack_max (int, size): the max capacity of the conntrack table + +### Tags: + +This input does not use tags. + +### Example Output: + +``` +$ ./telegraf -config telegraf.conf -input-filter conntrack -test +conntrack,host=myhost ip_conntrack_count=2,ip_conntrack_max=262144 1461620427667995735 +``` diff --git a/plugins/inputs/conntrack/conntrack.go b/plugins/inputs/conntrack/conntrack.go new file mode 100644 index 000000000..68bf8adba --- /dev/null +++ b/plugins/inputs/conntrack/conntrack.go @@ -0,0 +1,119 @@ +// +build linux + +package conntrack + +import ( + "fmt" + "io/ioutil" + "os" + "strconv" + "strings" + + "github.com/influxdata/telegraf" + "github.com/influxdata/telegraf/plugins/inputs" + "log" + "path/filepath" +) + +type Conntrack struct { + Path string + Dirs []string + Files []string +} + +const ( + inputName = "conntrack" +) + +var dfltDirs = []string{ + "/proc/sys/net/ipv4/netfilter", + "/proc/sys/net/netfilter", +} + +var dfltFiles = []string{ + "ip_conntrack_count", + "ip_conntrack_max", + "nf_conntrack_count", + "nf_conntrack_max", +} + +func (c *Conntrack) setDefaults() { + if len(c.Dirs) == 0 { + c.Dirs = dfltDirs + } + + if len(c.Files) == 0 { + c.Files = dfltFiles + } +} + +func (c *Conntrack) Description() string { + return "Collects conntrack stats from the configured directories and files." +} + +var sampleConfig = ` + ## The following defaults would work with multiple versions of conntrack. + ## Note the nf_ and ip_ filename prefixes are mutually exclusive across + ## kernel versions, as are the directory locations. + + ## Superset of filenames to look for within the conntrack dirs. + ## Missing files will be ignored. + files = ["ip_conntrack_count","ip_conntrack_max", + "nf_conntrack_count","nf_conntrack_max"] + + ## Directories to search within for the conntrack files above. + ## Missing directrories will be ignored. + dirs = ["/proc/sys/net/ipv4/netfilter","/proc/sys/net/netfilter"] +` + +func (c *Conntrack) SampleConfig() string { + return sampleConfig +} + +func (c *Conntrack) Gather(acc telegraf.Accumulator) error { + c.setDefaults() + + var metricKey string + fields := make(map[string]interface{}) + + for _, dir := range c.Dirs { + for _, file := range c.Files { + // NOTE: no system will have both nf_ and ip_ prefixes, + // so we're safe to branch on suffix only. + parts := strings.SplitN(file, "_", 2) + if len(parts) < 2 { + continue + } + metricKey = "ip_" + parts[1] + + fName := filepath.Join(dir, file) + if _, err := os.Stat(fName); err != nil { + continue + } + + contents, err := ioutil.ReadFile(fName) + if err != nil { + log.Printf("failed to read file '%s': %v", fName, err) + } + + v := strings.TrimSpace(string(contents)) + fields[metricKey], err = strconv.ParseFloat(v, 64) + if err != nil { + log.Printf("failed to parse metric, expected number but "+ + " found '%s': %v", v, err) + } + } + } + + if len(fields) == 0 { + return fmt.Errorf("Conntrack input failed to collect metrics. " + + "Is the conntrack kernel module loaded?") + } + + acc.AddFields(inputName, fields, nil) + return nil +} + +func init() { + inputs.Add(inputName, func() telegraf.Input { return &Conntrack{} }) +} diff --git a/plugins/inputs/conntrack/conntrack_notlinux.go b/plugins/inputs/conntrack/conntrack_notlinux.go new file mode 100644 index 000000000..11948731b --- /dev/null +++ b/plugins/inputs/conntrack/conntrack_notlinux.go @@ -0,0 +1,3 @@ +// +build !linux + +package conntrack diff --git a/plugins/inputs/conntrack/conntrack_test.go b/plugins/inputs/conntrack/conntrack_test.go new file mode 100644 index 000000000..c457006ac --- /dev/null +++ b/plugins/inputs/conntrack/conntrack_test.go @@ -0,0 +1,90 @@ +// +build linux + +package conntrack + +import ( + "github.com/influxdata/telegraf/testutil" + "github.com/stretchr/testify/assert" + "io/ioutil" + "os" + "path" + "strconv" + "strings" + "testing" +) + +func restoreDflts(savedFiles, savedDirs []string) { + dfltFiles = savedFiles + dfltDirs = savedDirs +} + +func TestNoFilesFound(t *testing.T) { + defer restoreDflts(dfltFiles, dfltDirs) + + dfltFiles = []string{"baz.txt"} + dfltDirs = []string{"./foo/bar"} + c := &Conntrack{} + acc := &testutil.Accumulator{} + err := c.Gather(acc) + + assert.EqualError(t, err, "Conntrack input failed to collect metrics. "+ + "Is the conntrack kernel module loaded?") +} + +func TestDefaultsUsed(t *testing.T) { + defer restoreDflts(dfltFiles, dfltDirs) + tmpdir, err := ioutil.TempDir("", "tmp1") + assert.NoError(t, err) + defer os.Remove(tmpdir) + + tmpFile, err := ioutil.TempFile(tmpdir, "ip_conntrack_count") + assert.NoError(t, err) + + dfltDirs = []string{tmpdir} + fname := path.Base(tmpFile.Name()) + dfltFiles = []string{fname} + + count := 1234321 + ioutil.WriteFile(tmpFile.Name(), []byte(strconv.Itoa(count)), 0660) + c := &Conntrack{} + acc := &testutil.Accumulator{} + + c.Gather(acc) + acc.AssertContainsFields(t, inputName, map[string]interface{}{ + fname: float64(count)}) +} + +func TestConfigsUsed(t *testing.T) { + defer restoreDflts(dfltFiles, dfltDirs) + tmpdir, err := ioutil.TempDir("", "tmp1") + assert.NoError(t, err) + defer os.Remove(tmpdir) + + cntFile, err := ioutil.TempFile(tmpdir, "nf_conntrack_count") + maxFile, err := ioutil.TempFile(tmpdir, "nf_conntrack_max") + assert.NoError(t, err) + + dfltDirs = []string{tmpdir} + cntFname := path.Base(cntFile.Name()) + maxFname := path.Base(maxFile.Name()) + dfltFiles = []string{cntFname, maxFname} + + count := 1234321 + max := 9999999 + ioutil.WriteFile(cntFile.Name(), []byte(strconv.Itoa(count)), 0660) + ioutil.WriteFile(maxFile.Name(), []byte(strconv.Itoa(max)), 0660) + c := &Conntrack{} + acc := &testutil.Accumulator{} + + c.Gather(acc) + + fix := func(s string) string { + return strings.Replace(s, "nf_", "ip_", 1) + } + + acc.AssertContainsFields(t, inputName, + map[string]interface{}{ + fix(cntFname): float64(count), + fix(maxFname): float64(max), + }) +}