diff --git a/etc/telegraf.conf b/etc/telegraf.conf old mode 100644 new mode 100755 index 4081cf484..f0d8a3361 --- 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..bfb7a6e3b --- /dev/null +++ b/plugins/inputs/conntrack/README.md @@ -0,0 +1,35 @@ +# Conntrack Plugin + +Collects conntrack stats from the configured directories and files. + +### Configuration: + +```toml + # 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"] +``` + +### 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..113489bf4 --- /dev/null +++ b/plugins/inputs/conntrack/conntrack.go @@ -0,0 +1,112 @@ +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 = ` + # 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"] +` + +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_test.go b/plugins/inputs/conntrack/conntrack_test.go new file mode 100644 index 000000000..b5a012984 --- /dev/null +++ b/plugins/inputs/conntrack/conntrack_test.go @@ -0,0 +1,86 @@ +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), + }) +}