Skip to content

Commit

Permalink
reviving code from #1447 to a new branch
Browse files Browse the repository at this point in the history
  • Loading branch information
Russ Savage committed Jun 1, 2021
1 parent 2e7b232 commit 7d4bc95
Show file tree
Hide file tree
Showing 6 changed files with 309 additions and 0 deletions.
35 changes: 35 additions & 0 deletions plugins/inputs/system/HUGEPAGES.md
Original file line number Diff line number Diff line change
@@ -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
```
184 changes: 184 additions & 0 deletions plugins/inputs/system/hugepages.go
Original file line number Diff line number Diff line change
@@ -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{}
})
}
43 changes: 43 additions & 0 deletions plugins/inputs/system/hugepages_test.go
Original file line number Diff line number Diff line change
@@ -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)
}
45 changes: 45 additions & 0 deletions plugins/inputs/system/testdata/meminfo
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
123
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
456

0 comments on commit 7d4bc95

Please sign in to comment.