165 lines
4.4 KiB
Go
165 lines
4.4 KiB
Go
|
//go:generate ../../../tools/readme_config_includer/generator
|
||
|
//go:build linux
|
||
|
|
||
|
package conntrack
|
||
|
|
||
|
import (
|
||
|
_ "embed"
|
||
|
"errors"
|
||
|
"fmt"
|
||
|
"os"
|
||
|
"path/filepath"
|
||
|
"strconv"
|
||
|
"strings"
|
||
|
|
||
|
"github.com/influxdata/telegraf"
|
||
|
"github.com/influxdata/telegraf/internal/choice"
|
||
|
"github.com/influxdata/telegraf/plugins/common/psutil"
|
||
|
"github.com/influxdata/telegraf/plugins/inputs"
|
||
|
)
|
||
|
|
||
|
//go:embed sample.conf
|
||
|
var sampleConfig string
|
||
|
|
||
|
var (
|
||
|
dfltDirs = []string{
|
||
|
"/proc/sys/net/ipv4/netfilter",
|
||
|
"/proc/sys/net/netfilter",
|
||
|
}
|
||
|
|
||
|
dfltFiles = []string{
|
||
|
"ip_conntrack_count",
|
||
|
"ip_conntrack_max",
|
||
|
"nf_conntrack_count",
|
||
|
"nf_conntrack_max",
|
||
|
}
|
||
|
)
|
||
|
|
||
|
const (
|
||
|
inputName = "conntrack"
|
||
|
)
|
||
|
|
||
|
type Conntrack struct {
|
||
|
Collect []string `toml:"collect"`
|
||
|
Dirs []string `toml:"dirs"`
|
||
|
Files []string `toml:"files"`
|
||
|
ps psutil.PS
|
||
|
}
|
||
|
|
||
|
func (*Conntrack) SampleConfig() string {
|
||
|
return sampleConfig
|
||
|
}
|
||
|
|
||
|
func (c *Conntrack) Init() error {
|
||
|
c.setDefaults()
|
||
|
|
||
|
if err := choice.CheckSlice(c.Collect, []string{"all", "percpu"}); err != nil {
|
||
|
return fmt.Errorf("config option 'collect': %w", err)
|
||
|
}
|
||
|
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
func (c *Conntrack) Gather(acc telegraf.Accumulator) error {
|
||
|
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 := os.ReadFile(fName)
|
||
|
if err != nil {
|
||
|
acc.AddError(fmt.Errorf("failed to read file %q: %w", fName, err))
|
||
|
continue
|
||
|
}
|
||
|
|
||
|
v := strings.TrimSpace(string(contents))
|
||
|
fields[metricKey], err = strconv.ParseFloat(v, 64)
|
||
|
if err != nil {
|
||
|
acc.AddError(fmt.Errorf("failed to parse metric, expected number but "+
|
||
|
" found %q: %w", v, err))
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
for _, metric := range c.Collect {
|
||
|
perCPU := metric == "percpu"
|
||
|
stats, err := c.ps.NetConntrack(perCPU)
|
||
|
if err != nil {
|
||
|
acc.AddError(fmt.Errorf("failed to retrieve conntrack statistics: %w", err))
|
||
|
}
|
||
|
|
||
|
if len(stats) == 0 {
|
||
|
acc.AddError(errors.New("conntrack input failed to collect stats"))
|
||
|
}
|
||
|
|
||
|
cpuTag := "all"
|
||
|
for i, sts := range stats {
|
||
|
if perCPU {
|
||
|
cpuTag = fmt.Sprintf("cpu%d", i)
|
||
|
}
|
||
|
tags := map[string]string{
|
||
|
"cpu": cpuTag,
|
||
|
}
|
||
|
|
||
|
statFields := map[string]interface{}{
|
||
|
"entries": sts.Entries, // entries in the conntrack table
|
||
|
"searched": sts.Searched, // conntrack table lookups performed
|
||
|
"found": sts.Found, // searched entries which were successful
|
||
|
"new": sts.New, // entries added which were not expected before
|
||
|
"invalid": sts.Invalid, // packets seen which can not be tracked
|
||
|
"ignore": sts.Ignore, // packets seen which are already connected to an entry
|
||
|
"delete": sts.Delete, // entries which were removed
|
||
|
"delete_list": sts.DeleteList, // entries which were put to dying list
|
||
|
"insert": sts.Insert, // entries inserted into the list
|
||
|
"insert_failed": sts.InsertFailed, // insertion attempted but failed (same entry exists)
|
||
|
"drop": sts.Drop, // packets dropped due to conntrack failure
|
||
|
"early_drop": sts.EarlyDrop, // dropped entries to make room for new ones, if maxsize reached
|
||
|
"icmp_error": sts.IcmpError, // Subset of invalid. Packets that can't be tracked d/t error
|
||
|
"expect_new": sts.ExpectNew, // Entries added after an expectation was already present
|
||
|
"expect_create": sts.ExpectCreate, // Expectations added
|
||
|
"expect_delete": sts.ExpectDelete, // Expectations deleted
|
||
|
"search_restart": sts.SearchRestart, // onntrack table lookups restarted due to hashtable resizes
|
||
|
}
|
||
|
acc.AddCounter(inputName, statFields, tags)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if len(fields) == 0 {
|
||
|
return errors.New("conntrack input failed to collect metrics, make sure that the kernel module is loaded")
|
||
|
}
|
||
|
|
||
|
acc.AddFields(inputName, fields, nil)
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
func (c *Conntrack) setDefaults() {
|
||
|
if len(c.Dirs) == 0 {
|
||
|
c.Dirs = dfltDirs
|
||
|
}
|
||
|
|
||
|
if len(c.Files) == 0 {
|
||
|
c.Files = dfltFiles
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func init() {
|
||
|
inputs.Add(inputName, func() telegraf.Input {
|
||
|
return &Conntrack{
|
||
|
ps: psutil.NewSystemPS(),
|
||
|
}
|
||
|
})
|
||
|
}
|