diff --git a/plugins/inputs/system/HUGEPAGES.md b/plugins/inputs/system/HUGEPAGES.md new file mode 100644 index 000000000..4c103cc04 --- /dev/null +++ b/plugins/inputs/system/HUGEPAGES.md @@ -0,0 +1,35 @@ +# Hugepages Input Plugin + +The hugepages plugin gathers hugepages metrics including per NUMA node + +### Configuration: + +```toml +# Description +[[inputs.hugepages]] + ## Path to a NUMA nodes + # numa_node_path = "/sys/devices/system/node" + ## Path to a meminfo file + # meminfo_path = "/proc/meminfo" +``` + +### Measurements & Fields: + +- hugepages + - free (int, kB) + - nr (int, kB) + - HugePages_Total (int, kB) + - HugePages_Free (int, kB) + +### Tags: + +- hugepages has the following tags: + - node + +### Example Output: + +``` +$ ./telegraf -config telegraf.conf -input-filter hugepages -test +> hugepages,host=maxpc,node=node0 free=0i,nr=0i 1467618621000000000 +> hugepages,host=maxpc,name=meminfo HugePages_Free=0i,HugePages_Total=0i 1467618621000000000 +``` diff --git a/plugins/inputs/system/hugepages.go b/plugins/inputs/system/hugepages.go new file mode 100644 index 000000000..5c2a6508f --- /dev/null +++ b/plugins/inputs/system/hugepages.go @@ -0,0 +1,184 @@ +package system + +import ( + "bytes" + "fmt" + "io/ioutil" + "strconv" + "strings" + + "github.com/influxdata/telegraf" + "github.com/influxdata/telegraf/plugins/inputs" +) + +var ( + newlineByte = []byte("\n") + colonByte = []byte(":") + kbPrecisionByte = []byte("kB") +) + +// default file paths +const ( + // the path where statistics are kept per NUMA nodes + NUMA_NODE_PATH = "/sys/devices/system/node" + // the path to the meminfo file which is produced by kernel + MEMINFO_PATH = "/proc/meminfo" + + // hugepages stat field names on meminfo file + HUGE_PAGES_TOTAL = "HugePages_Total" + HUGE_PAGES_FREE = "HugePages_Free" +) + +var hugepagesSampleConfig = ` + ## Path to a NUMA nodes + # numa_node_path = "/sys/devices/system/node" + ## Path to a meminfo file + # meminfo_path = "/proc/meminfo" +` + +// Mem is the +type Hugepages struct { + NUMANodePath string `toml:"numa_node_path"` + MeminfoPath string `toml:"meminfo_path"` +} + +func (mem *Hugepages) Description() string { + return "Collects hugepages metrics from kernel and per NUMA node" +} + +func (mem *Hugepages) SampleConfig() string { + return hugepagesSampleConfig +} + +func (mem *Hugepages) Gather(acc telegraf.Accumulator) error { + mem.loadPaths() + err := mem.GatherStatsPerNode(acc) + if err != nil { + return err + } + + err = mem.GatherStatsFromMeminfo(acc) + if err != nil { + return err + } + return nil +} + +// GatherHugepagesStatsPerNode collects hugepages stats per NUMA nodes +func (mem *Hugepages) GatherStatsPerNode(acc telegraf.Accumulator) error { + numaNodeMetrics, err := statsPerNUMA(mem.NUMANodePath) + if err != nil { + return err + } + + for k, v := range numaNodeMetrics { + metrics := make(map[string]interface{}) + tags := map[string]string{ + "node": k, + } + metrics["free"] = v.Free + metrics["nr"] = v.NR + acc.AddFields("hugepages", metrics, tags) + } + return nil +} + +// GatherHugepagesStatsFromMeminfo collects hugepages statistics from meminfo file +func (mem *Hugepages) GatherStatsFromMeminfo(acc telegraf.Accumulator) error { + tags := map[string]string{ + "name": "meminfo", + } + metrics := make(map[string]interface{}) + meminfoMetrics, err := statsFromMeminfo(mem.MeminfoPath) + if err != nil { + return err + } + + for k, v := range meminfoMetrics { + metrics[k] = v + } + acc.AddFields("hugepages", metrics, tags) + return nil +} + +type HugepagesNUMAStats struct { + Free int + NR int +} + +// statsPerNUMA gathers hugepages statistics from each NUMA node +func statsPerNUMA(path string) (map[string]HugepagesNUMAStats, error) { + var hugepagesStats = make(map[string]HugepagesNUMAStats) + dirs, err := ioutil.ReadDir(path) + if err != nil { + return hugepagesStats, err + } + + for _, d := range dirs { + if !(d.IsDir() && strings.HasPrefix(d.Name(), "node")) { + continue + } + + hugepagesFree := fmt.Sprintf("%s/%s/hugepages/hugepages-2048kB/free_hugepages", path, d.Name()) + hugepagesNR := fmt.Sprintf("%s/%s/hugepages/hugepages-2048kB/nr_hugepages", path, d.Name()) + + free, err := ioutil.ReadFile(hugepagesFree) + if err != nil { + return hugepagesStats, err + } + + nr, err := ioutil.ReadFile(hugepagesNR) + if err != nil { + return hugepagesStats, err + } + + f, _ := strconv.Atoi(string(bytes.TrimSuffix(free, newlineByte))) + n, _ := strconv.Atoi(string(bytes.TrimSuffix(nr, newlineByte))) + + hugepagesStats[d.Name()] = HugepagesNUMAStats{Free: f, NR: n} + + } + return hugepagesStats, nil +} + +// statsFromMeminfo gathers hugepages statistics from kernel +func statsFromMeminfo(path string) (map[string]interface{}, error) { + stats := map[string]interface{}{} + meminfo, err := ioutil.ReadFile(path) + if err != nil { + return stats, err + } + lines := bytes.Split(meminfo, newlineByte) + for _, l := range lines { + if bytes.Contains(l, kbPrecisionByte) { + continue + } + fields := bytes.Fields(l) + if len(fields) < 2 { + continue + } + fieldName := string(bytes.TrimSuffix(fields[0], colonByte)) + if fieldName == HUGE_PAGES_TOTAL || fieldName == HUGE_PAGES_FREE { + val, _ := strconv.Atoi(string(fields[1])) + stats[fieldName] = val + } + } + return stats, nil +} + +// loadPaths can be used to read paths firstly from config +// if it is empty then try read from env variables +func (mem *Hugepages) loadPaths() { + if mem.NUMANodePath == "" { + mem.NUMANodePath = NUMA_NODE_PATH + } + if mem.MeminfoPath == "" { + mem.MeminfoPath = MEMINFO_PATH + } +} + +func init() { + inputs.Add("hugepages", func() telegraf.Input { + return &Hugepages{} + }) +} diff --git a/plugins/inputs/system/hugepages_test.go b/plugins/inputs/system/hugepages_test.go new file mode 100644 index 000000000..820d1c84e --- /dev/null +++ b/plugins/inputs/system/hugepages_test.go @@ -0,0 +1,43 @@ +package system + +import ( + "testing" + + "github.com/influxdata/telegraf/testutil" +) + +var hugepages = Hugepages{ + NUMANodePath: "./testdata/node", + MeminfoPath: "./testdata/meminfo", +} + +func init() { + hugepages.loadPaths() +} + +func TestHugepagesStatsFromMeminfo(t *testing.T) { + acc := &testutil.Accumulator{} + err := hugepages.GatherStatsFromMeminfo(acc) + if err != nil { + t.Fatal(err) + } + + fields := map[string]interface{}{ + "HugePages_Total": int(666), + "HugePages_Free": int(999), + } + acc.AssertContainsFields(t, "hugepages", fields) +} + +func TestHugepagesStatsPerNode(t *testing.T) { + acc := &testutil.Accumulator{} + err := hugepages.GatherStatsPerNode(acc) + if err != nil { + t.Fatal(err) + } + fields := map[string]interface{}{ + "free": int(123), + "nr": int(456), + } + acc.AssertContainsFields(t, "hugepages", fields) +} diff --git a/plugins/inputs/system/testdata/meminfo b/plugins/inputs/system/testdata/meminfo new file mode 100644 index 000000000..1160c2df6 --- /dev/null +++ b/plugins/inputs/system/testdata/meminfo @@ -0,0 +1,45 @@ +MemTotal: 5961204 kB +MemFree: 5201764 kB +MemAvailable: 5761624 kB +Buffers: 120248 kB +Cached: 419660 kB +SwapCached: 0 kB +Active: 370656 kB +Inactive: 247364 kB +Active(anon): 79032 kB +Inactive(anon): 552 kB +Active(file): 291624 kB +Inactive(file): 246812 kB +Unevictable: 0 kB +Mlocked: 0 kB +SwapTotal: 0 kB +SwapFree: 0 kB +Dirty: 4 kB +Writeback: 0 kB +AnonPages: 78164 kB +Mapped: 114164 kB +Shmem: 1452 kB +Slab: 77180 kB +SReclaimable: 57676 kB +SUnreclaim: 19504 kB +KernelStack: 2832 kB +PageTables: 8692 kB +NFS_Unstable: 0 kB +Bounce: 0 kB +WritebackTmp: 0 kB +CommitLimit: 2980600 kB +Committed_AS: 555492 kB +VmallocTotal: 34359738367 kB +VmallocUsed: 0 kB +VmallocChunk: 0 kB +HardwareCorrupted: 0 kB +AnonHugePages: 0 kB +CmaTotal: 0 kB +CmaFree: 0 kB +HugePages_Total: 666 +HugePages_Free: 999 +HugePages_Rsvd: 0 +HugePages_Surp: 0 +Hugepagesize: 2048 kB +DirectMap4k: 84224 kB +DirectMap2M: 6055936 kB diff --git a/plugins/inputs/system/testdata/node/node0/hugepages/hugepages-2048kB/free_hugepages b/plugins/inputs/system/testdata/node/node0/hugepages/hugepages-2048kB/free_hugepages new file mode 100644 index 000000000..190a18037 --- /dev/null +++ b/plugins/inputs/system/testdata/node/node0/hugepages/hugepages-2048kB/free_hugepages @@ -0,0 +1 @@ +123 diff --git a/plugins/inputs/system/testdata/node/node0/hugepages/hugepages-2048kB/nr_hugepages b/plugins/inputs/system/testdata/node/node0/hugepages/hugepages-2048kB/nr_hugepages new file mode 100644 index 000000000..8d38505c1 --- /dev/null +++ b/plugins/inputs/system/testdata/node/node0/hugepages/hugepages-2048kB/nr_hugepages @@ -0,0 +1 @@ +456