281 lines
6.6 KiB
Go
281 lines
6.6 KiB
Go
|
//go:generate ../../../tools/readme_config_includer/generator
|
||
|
//go:build linux
|
||
|
|
||
|
package hugepages
|
||
|
|
||
|
import (
|
||
|
"bytes"
|
||
|
_ "embed"
|
||
|
"errors"
|
||
|
"fmt"
|
||
|
"os"
|
||
|
"path/filepath"
|
||
|
"strconv"
|
||
|
"strings"
|
||
|
|
||
|
"github.com/influxdata/telegraf"
|
||
|
"github.com/influxdata/telegraf/plugins/inputs"
|
||
|
)
|
||
|
|
||
|
//go:embed sample.conf
|
||
|
var sampleConfig string
|
||
|
|
||
|
var (
|
||
|
newlineByte = []byte("\n")
|
||
|
colonByte = []byte(":")
|
||
|
|
||
|
hugepagesMetricsRoot = map[string]string{
|
||
|
"free_hugepages": "free",
|
||
|
"nr_hugepages": "total",
|
||
|
"nr_hugepages_mempolicy": "mempolicy",
|
||
|
"nr_overcommit_hugepages": "overcommit",
|
||
|
"resv_hugepages": "reserved",
|
||
|
"surplus_hugepages": "surplus",
|
||
|
}
|
||
|
|
||
|
hugepagesMetricsPerNUMANode = map[string]string{
|
||
|
"free_hugepages": "free",
|
||
|
"nr_hugepages": "total",
|
||
|
"surplus_hugepages": "surplus",
|
||
|
}
|
||
|
|
||
|
hugepagesMetricsFromMeminfo = map[string]string{
|
||
|
"HugePages_Total": "total",
|
||
|
"HugePages_Free": "free",
|
||
|
"HugePages_Rsvd": "reserved",
|
||
|
"HugePages_Surp": "surplus",
|
||
|
"Hugepagesize": "size_kb",
|
||
|
"Hugetlb": "tlb_kb",
|
||
|
"AnonHugePages": "anonymous_kb",
|
||
|
"ShmemHugePages": "shared_kb",
|
||
|
"FileHugePages": "file_kb",
|
||
|
}
|
||
|
)
|
||
|
|
||
|
const (
|
||
|
// path to root huge page control directory
|
||
|
rootHugepagePath = "/sys/kernel/mm/hugepages"
|
||
|
// path where per NUMA node statistics are kept
|
||
|
numaNodePath = "/sys/devices/system/node"
|
||
|
// path to the meminfo file
|
||
|
meminfoPath = "/proc/meminfo"
|
||
|
|
||
|
rootHugepages = "root"
|
||
|
perNodeHugepages = "per_node"
|
||
|
meminfoHugepages = "meminfo"
|
||
|
)
|
||
|
|
||
|
type Hugepages struct {
|
||
|
Types []string `toml:"types"`
|
||
|
|
||
|
gatherRoot bool
|
||
|
gatherPerNode bool
|
||
|
gatherMeminfo bool
|
||
|
|
||
|
rootHugepagePath string
|
||
|
numaNodePath string
|
||
|
meminfoPath string
|
||
|
}
|
||
|
|
||
|
func (*Hugepages) SampleConfig() string {
|
||
|
return sampleConfig
|
||
|
}
|
||
|
|
||
|
func (h *Hugepages) Init() error {
|
||
|
err := h.parseHugepagesConfig()
|
||
|
if err != nil {
|
||
|
return err
|
||
|
}
|
||
|
|
||
|
h.rootHugepagePath = rootHugepagePath
|
||
|
h.numaNodePath = numaNodePath
|
||
|
h.meminfoPath = meminfoPath
|
||
|
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
func (h *Hugepages) Gather(acc telegraf.Accumulator) error {
|
||
|
if h.gatherRoot {
|
||
|
if err := h.gatherRootStats(acc); err != nil {
|
||
|
return fmt.Errorf("gathering root stats failed: %w", err)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if h.gatherPerNode {
|
||
|
if err := h.gatherStatsPerNode(acc); err != nil {
|
||
|
return fmt.Errorf("gathering per node stats failed: %w", err)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if h.gatherMeminfo {
|
||
|
if err := h.gatherStatsFromMeminfo(acc); err != nil {
|
||
|
return fmt.Errorf("gathering meminfo stats failed: %w", err)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
// gatherStatsPerNode collects root hugepages statistics
|
||
|
func (h *Hugepages) gatherRootStats(acc telegraf.Accumulator) error {
|
||
|
return gatherFromHugepagePath(acc, "hugepages_"+rootHugepages, h.rootHugepagePath, hugepagesMetricsRoot, nil)
|
||
|
}
|
||
|
|
||
|
// gatherStatsPerNode collects hugepages statistics per NUMA node
|
||
|
func (h *Hugepages) gatherStatsPerNode(acc telegraf.Accumulator) error {
|
||
|
nodeDirs, err := os.ReadDir(h.numaNodePath)
|
||
|
if err != nil {
|
||
|
return err
|
||
|
}
|
||
|
|
||
|
// read metrics from: node*/hugepages/hugepages-*/*
|
||
|
for _, nodeDir := range nodeDirs {
|
||
|
if !nodeDir.IsDir() || !strings.HasPrefix(nodeDir.Name(), "node") {
|
||
|
continue
|
||
|
}
|
||
|
|
||
|
nodeNumber := strings.TrimPrefix(nodeDir.Name(), "node")
|
||
|
_, err := strconv.Atoi(nodeNumber)
|
||
|
if err != nil {
|
||
|
continue
|
||
|
}
|
||
|
|
||
|
perNodeTags := map[string]string{
|
||
|
"node": nodeNumber,
|
||
|
}
|
||
|
hugepagesPath := filepath.Join(h.numaNodePath, nodeDir.Name(), "hugepages")
|
||
|
err = gatherFromHugepagePath(acc, "hugepages_"+perNodeHugepages, hugepagesPath, hugepagesMetricsPerNUMANode, perNodeTags)
|
||
|
if err != nil {
|
||
|
return err
|
||
|
}
|
||
|
}
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
func gatherFromHugepagePath(acc telegraf.Accumulator, measurement, path string, fileFilter, defaultTags map[string]string) error {
|
||
|
// read metrics from: hugepages/hugepages-*/*
|
||
|
hugepagesDirs, err := os.ReadDir(path)
|
||
|
if err != nil {
|
||
|
return fmt.Errorf("reading root dir failed: %w", err)
|
||
|
}
|
||
|
|
||
|
for _, hugepagesDir := range hugepagesDirs {
|
||
|
if !hugepagesDir.IsDir() || !strings.HasPrefix(hugepagesDir.Name(), "hugepages-") {
|
||
|
continue
|
||
|
}
|
||
|
|
||
|
hugepagesSize := strings.TrimPrefix(strings.TrimSuffix(hugepagesDir.Name(), "kB"), "hugepages-")
|
||
|
_, err := strconv.Atoi(hugepagesSize)
|
||
|
if err != nil {
|
||
|
continue
|
||
|
}
|
||
|
|
||
|
metricsPath := filepath.Join(path, hugepagesDir.Name())
|
||
|
metricFiles, err := os.ReadDir(metricsPath)
|
||
|
if err != nil {
|
||
|
return fmt.Errorf("reading metric dir failed: %w", err)
|
||
|
}
|
||
|
|
||
|
metrics := make(map[string]interface{})
|
||
|
for _, metricFile := range metricFiles {
|
||
|
metricName, ok := fileFilter[metricFile.Name()]
|
||
|
if mode := metricFile.Type(); !mode.IsRegular() || !ok {
|
||
|
continue
|
||
|
}
|
||
|
|
||
|
metricFullPath := filepath.Join(metricsPath, metricFile.Name())
|
||
|
metricBytes, err := os.ReadFile(metricFullPath)
|
||
|
if err != nil {
|
||
|
return err
|
||
|
}
|
||
|
|
||
|
metricValue, err := strconv.Atoi(string(bytes.TrimSuffix(metricBytes, newlineByte)))
|
||
|
if err != nil {
|
||
|
return fmt.Errorf("failed to convert content of %q: %w", metricFullPath, err)
|
||
|
}
|
||
|
|
||
|
metrics[metricName] = metricValue
|
||
|
}
|
||
|
|
||
|
if len(metrics) == 0 {
|
||
|
continue
|
||
|
}
|
||
|
|
||
|
tags := make(map[string]string)
|
||
|
for key, value := range defaultTags {
|
||
|
tags[key] = value
|
||
|
}
|
||
|
tags["size_kb"] = hugepagesSize
|
||
|
|
||
|
acc.AddFields(measurement, metrics, tags)
|
||
|
}
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
// gatherStatsFromMeminfo collects hugepages statistics from meminfo file
|
||
|
func (h *Hugepages) gatherStatsFromMeminfo(acc telegraf.Accumulator) error {
|
||
|
meminfo, err := os.ReadFile(h.meminfoPath)
|
||
|
if err != nil {
|
||
|
return err
|
||
|
}
|
||
|
|
||
|
metrics := make(map[string]interface{})
|
||
|
lines := bytes.Split(meminfo, newlineByte)
|
||
|
for _, line := range lines {
|
||
|
fields := bytes.Fields(line)
|
||
|
if len(fields) < 2 {
|
||
|
continue
|
||
|
}
|
||
|
fieldName := string(bytes.TrimSuffix(fields[0], colonByte))
|
||
|
metricName, ok := hugepagesMetricsFromMeminfo[fieldName]
|
||
|
if !ok {
|
||
|
continue
|
||
|
}
|
||
|
|
||
|
fieldValue, err := strconv.Atoi(string(fields[1]))
|
||
|
if err != nil {
|
||
|
return fmt.Errorf("failed to convert content of %q: %w", fieldName, err)
|
||
|
}
|
||
|
|
||
|
metrics[metricName] = fieldValue
|
||
|
}
|
||
|
|
||
|
acc.AddFields("hugepages_"+meminfoHugepages, metrics, make(map[string]string))
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
func (h *Hugepages) parseHugepagesConfig() error {
|
||
|
// default
|
||
|
if h.Types == nil {
|
||
|
h.gatherRoot = true
|
||
|
h.gatherMeminfo = true
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
// empty array
|
||
|
if len(h.Types) == 0 {
|
||
|
return errors.New("plugin was configured with nothing to read")
|
||
|
}
|
||
|
|
||
|
for _, hugepagesType := range h.Types {
|
||
|
switch hugepagesType {
|
||
|
case rootHugepages:
|
||
|
h.gatherRoot = true
|
||
|
case perNodeHugepages:
|
||
|
h.gatherPerNode = true
|
||
|
case meminfoHugepages:
|
||
|
h.gatherMeminfo = true
|
||
|
default:
|
||
|
return fmt.Errorf("provided hugepages type %q is not valid", hugepagesType)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
func init() {
|
||
|
inputs.Add("hugepages", func() telegraf.Input {
|
||
|
return &Hugepages{}
|
||
|
})
|
||
|
}
|