Adding upstream version 1.34.4.
Signed-off-by: Daniel Baumann <daniel@debian.org>
This commit is contained in:
parent
e393c3af3f
commit
4978089aab
4963 changed files with 677545 additions and 0 deletions
305
plugins/inputs/cgroup/cgroup_linux.go
Normal file
305
plugins/inputs/cgroup/cgroup_linux.go
Normal file
|
@ -0,0 +1,305 @@
|
|||
//go:build linux
|
||||
|
||||
package cgroup
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/influxdata/telegraf"
|
||||
)
|
||||
|
||||
const metricName = "cgroup"
|
||||
|
||||
func (cg *CGroup) Gather(acc telegraf.Accumulator) error {
|
||||
list := make(chan pathInfo)
|
||||
go cg.generateDirs(list)
|
||||
|
||||
for dir := range list {
|
||||
if dir.err != nil {
|
||||
acc.AddError(dir.err)
|
||||
continue
|
||||
}
|
||||
if err := cg.gatherDir(acc, dir.path); err != nil {
|
||||
acc.AddError(err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (cg *CGroup) gatherDir(acc telegraf.Accumulator, dir string) error {
|
||||
fields := make(map[string]interface{})
|
||||
|
||||
list := make(chan pathInfo)
|
||||
go cg.generateFiles(dir, list)
|
||||
|
||||
for file := range list {
|
||||
if file.err != nil {
|
||||
return file.err
|
||||
}
|
||||
|
||||
raw, err := os.ReadFile(file.path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(raw) == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
fd := fileData{data: raw, path: file.path}
|
||||
if err := fd.parse(fields); err != nil {
|
||||
if !cg.logged[file.path] {
|
||||
acc.AddError(err)
|
||||
}
|
||||
cg.logged[file.path] = true
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
tags := map[string]string{"path": dir}
|
||||
|
||||
acc.AddFields(metricName, fields, tags)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// ======================================================================
|
||||
|
||||
type pathInfo struct {
|
||||
path string
|
||||
err error
|
||||
}
|
||||
|
||||
func isDir(pathToCheck string) (bool, error) {
|
||||
result, err := os.Stat(pathToCheck)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
return result.IsDir(), nil
|
||||
}
|
||||
|
||||
func (cg *CGroup) generateDirs(list chan<- pathInfo) {
|
||||
defer close(list)
|
||||
for _, dir := range cg.Paths {
|
||||
// getting all dirs that match the pattern 'dir'
|
||||
items, err := filepath.Glob(dir)
|
||||
if err != nil {
|
||||
list <- pathInfo{err: err}
|
||||
return
|
||||
}
|
||||
|
||||
for _, item := range items {
|
||||
ok, err := isDir(item)
|
||||
if err != nil {
|
||||
list <- pathInfo{err: err}
|
||||
return
|
||||
}
|
||||
// supply only dirs
|
||||
if ok {
|
||||
list <- pathInfo{path: item}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (cg *CGroup) generateFiles(dir string, list chan<- pathInfo) {
|
||||
dir = strings.Replace(dir, "\\", "\\\\", -1)
|
||||
|
||||
defer close(list)
|
||||
for _, file := range cg.Files {
|
||||
// getting all file paths that match the pattern 'dir + file'
|
||||
// path.Base make sure that file variable does not contains part of path
|
||||
items, err := filepath.Glob(path.Join(dir, path.Base(file)))
|
||||
if err != nil {
|
||||
list <- pathInfo{err: err}
|
||||
return
|
||||
}
|
||||
|
||||
for _, item := range items {
|
||||
ok, err := isDir(item)
|
||||
if err != nil {
|
||||
list <- pathInfo{err: err}
|
||||
return
|
||||
}
|
||||
// supply only files not dirs
|
||||
if !ok {
|
||||
list <- pathInfo{path: item}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ======================================================================
|
||||
|
||||
type fileData struct {
|
||||
data []byte
|
||||
path string
|
||||
}
|
||||
|
||||
func (fd *fileData) format() (*fileFormat, error) {
|
||||
for _, ff := range fileFormats {
|
||||
ok, err := ff.match(fd.data)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if ok {
|
||||
return &ff, nil
|
||||
}
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("%v: unknown file format", fd.path)
|
||||
}
|
||||
|
||||
func (fd *fileData) parse(fields map[string]interface{}) error {
|
||||
format, err := fd.format()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
format.parser(filepath.Base(fd.path), fields, fd.data)
|
||||
return nil
|
||||
}
|
||||
|
||||
// ======================================================================
|
||||
|
||||
type fileFormat struct {
|
||||
name string
|
||||
pattern string
|
||||
parser func(measurement string, fields map[string]interface{}, b []byte)
|
||||
}
|
||||
|
||||
const keyPattern = "[[:alnum:]:_.]+"
|
||||
const valuePattern = "(?:max|[\\d-\\.]+)"
|
||||
|
||||
var fileFormats = [...]fileFormat{
|
||||
// VAL\n
|
||||
{
|
||||
name: "Single value",
|
||||
pattern: "^" + valuePattern + "\n$",
|
||||
parser: func(measurement string, fields map[string]interface{}, b []byte) {
|
||||
re := regexp.MustCompile("^(" + valuePattern + ")\n$")
|
||||
matches := re.FindAllStringSubmatch(string(b), -1)
|
||||
fields[measurement] = numberOrString(matches[0][1])
|
||||
},
|
||||
},
|
||||
// VAL0\n
|
||||
// VAL1\n
|
||||
// ...
|
||||
{
|
||||
name: "New line separated values",
|
||||
pattern: "^(" + valuePattern + "\n){2,}$",
|
||||
parser: func(measurement string, fields map[string]interface{}, b []byte) {
|
||||
re := regexp.MustCompile("(" + valuePattern + ")\n")
|
||||
matches := re.FindAllStringSubmatch(string(b), -1)
|
||||
for i, v := range matches {
|
||||
fields[measurement+"."+strconv.Itoa(i)] = numberOrString(v[1])
|
||||
}
|
||||
},
|
||||
},
|
||||
// VAL0 VAL1 ...\n
|
||||
{
|
||||
name: "Space separated values",
|
||||
pattern: "^(" + valuePattern + " ?)+\n$",
|
||||
parser: func(measurement string, fields map[string]interface{}, b []byte) {
|
||||
re := regexp.MustCompile("(" + valuePattern + ")")
|
||||
matches := re.FindAllStringSubmatch(string(b), -1)
|
||||
for i, v := range matches {
|
||||
fields[measurement+"."+strconv.Itoa(i)] = numberOrString(v[1])
|
||||
}
|
||||
},
|
||||
},
|
||||
// KEY0 ... VAL0\n
|
||||
// KEY1 ... VAL1\n
|
||||
// ...
|
||||
{
|
||||
name: "Space separated keys and value, separated by new line",
|
||||
pattern: "^((" + keyPattern + " )+" + valuePattern + "\n)+$",
|
||||
parser: func(measurement string, fields map[string]interface{}, b []byte) {
|
||||
re := regexp.MustCompile("((?:" + keyPattern + " ?)+) (" + valuePattern + ")\n")
|
||||
matches := re.FindAllStringSubmatch(string(b), -1)
|
||||
for _, v := range matches {
|
||||
k := strings.ReplaceAll(v[1], " ", ".")
|
||||
fields[measurement+"."+k] = numberOrString(v[2])
|
||||
}
|
||||
},
|
||||
},
|
||||
// NAME0 KEY0=VAL0 ...\n
|
||||
// NAME1 KEY1=VAL1 ...\n
|
||||
// ...
|
||||
{
|
||||
name: "Equal sign separated key-value pairs, multiple lines with name",
|
||||
pattern: fmt.Sprintf("^(%s( %s=%s)+\n)+$", keyPattern, keyPattern, valuePattern),
|
||||
parser: func(measurement string, fields map[string]interface{}, b []byte) {
|
||||
lines := strings.Split(string(b), "\n")
|
||||
for _, line := range lines {
|
||||
f := strings.Fields(line)
|
||||
if len(f) == 0 {
|
||||
continue
|
||||
}
|
||||
name := f[0]
|
||||
for _, field := range f[1:] {
|
||||
k, v, found := strings.Cut(field, "=")
|
||||
if found {
|
||||
fields[strings.Join([]string{measurement, name, k}, ".")] = numberOrString(v)
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
// KEY0=VAL0 KEY1=VAL1 ...\n
|
||||
{
|
||||
name: "Equal sign separated key-value pairs on a single line",
|
||||
pattern: fmt.Sprintf("^(%s=%s ?)+\n$", keyPattern, valuePattern),
|
||||
parser: func(measurement string, fields map[string]interface{}, b []byte) {
|
||||
f := strings.Fields(string(b))
|
||||
if len(f) == 0 {
|
||||
return
|
||||
}
|
||||
for _, field := range f {
|
||||
k, v, found := strings.Cut(field, "=")
|
||||
if found {
|
||||
fields[strings.Join([]string{measurement, k}, ".")] = numberOrString(v)
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
func numberOrString(s string) interface{} {
|
||||
i, err := strconv.ParseInt(s, 10, 64)
|
||||
if err == nil {
|
||||
return i
|
||||
}
|
||||
if s == "max" {
|
||||
return int64(math.MaxInt64)
|
||||
}
|
||||
|
||||
// Care should be taken to always interpret each field as the same type on every cycle.
|
||||
// *.pressure files follow the PSI format and contain numbers with fractional parts
|
||||
// that always have a decimal separator, even when the fractional part is 0 (e.g., "0.00"),
|
||||
// thus they will always be interpreted as floats.
|
||||
// https://www.kernel.org/doc/Documentation/accounting/psi.txt
|
||||
f, err := strconv.ParseFloat(s, 64)
|
||||
if err == nil {
|
||||
return f
|
||||
}
|
||||
|
||||
return s
|
||||
}
|
||||
|
||||
func (f fileFormat) match(b []byte) (bool, error) {
|
||||
ok, err := regexp.Match(f.pattern, b)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
if ok {
|
||||
return true, nil
|
||||
}
|
||||
return false, nil
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue