Add systemd unit pid and cgroup matching to procstat (#3459)

This commit is contained in:
Patrick Hemmer 2017-11-13 13:59:27 -05:00 committed by Daniel Nelson
parent 084f7dc53b
commit 2323d9ae48
2 changed files with 75 additions and 5 deletions

View File

@ -6,11 +6,11 @@ The procstat plugin can be used to monitor system resource usage by an
individual process using their /proc data. individual process using their /proc data.
Processes can be specified either by pid file, by executable name, by command Processes can be specified either by pid file, by executable name, by command
line pattern matching, or by username (in this order or priority. Procstat line pattern matching, by username, by systemd unit name, or by cgroup name/path
plugin will use `pgrep` when executable name is provided to obtain the pid. (in this order or priority). Procstat plugin will use `pgrep` when executable
Procstat plugin will transmit IO, memory, cpu, file descriptor related name is provided to obtain the pid. Procstat plugin will transmit IO, memory,
measurements for every process specified. A prefix can be set to isolate cpu, file descriptor related measurements for every process specified. A prefix
individual process specific measurements. can be set to isolate individual process specific measurements.
The plugin will tag processes according to how they are specified in the configuration. If a pid file is used, a "pidfile" tag will be generated. The plugin will tag processes according to how they are specified in the configuration. If a pid file is used, a "pidfile" tag will be generated.
On the other hand, if an executable is used an "exe" tag will be generated. Possible tag names: On the other hand, if an executable is used an "exe" tag will be generated. Possible tag names:
@ -19,6 +19,8 @@ On the other hand, if an executable is used an "exe" tag will be generated. Poss
* exe * exe
* pattern * pattern
* user * user
* systemd_unit
* cgroup
Additionally the plugin will tag processes by their PID (pid_tag = true in the config) and their process name: Additionally the plugin will tag processes by their PID (pid_tag = true in the config) and their process name:

View File

@ -1,7 +1,10 @@
package procstat package procstat
import ( import (
"bytes"
"fmt" "fmt"
"io/ioutil"
"os/exec"
"strconv" "strconv"
"time" "time"
@ -24,6 +27,8 @@ type Procstat struct {
Prefix string Prefix string
ProcessName string ProcessName string
User string User string
SystemdUnit string
CGroup string `toml:"cgroup"`
PidTag bool PidTag bool
pidFinder PIDFinder pidFinder PIDFinder
@ -42,6 +47,10 @@ var sampleConfig = `
# pattern = "nginx" # pattern = "nginx"
## user as argument for pgrep (ie, pgrep -u <user>) ## user as argument for pgrep (ie, pgrep -u <user>)
# user = "nginx" # user = "nginx"
## Systemd unit name
# systemd_unit = "nginx.service"
## CGroup name or path
# cgroup = "systemd/system.slice/nginx.service"
## override for process_name ## override for process_name
## This is optional; default is sourced from /proc/<pid>/status ## This is optional; default is sourced from /proc/<pid>/status
@ -275,6 +284,12 @@ func (p *Procstat) findPids() ([]PID, map[string]string, error) {
} else if p.User != "" { } else if p.User != "" {
pids, err = f.Uid(p.User) pids, err = f.Uid(p.User)
tags = map[string]string{"user": p.User} tags = map[string]string{"user": p.User}
} else if p.SystemdUnit != "" {
pids, err = p.systemdUnitPIDs()
tags = map[string]string{"systemd_unit": p.SystemdUnit}
} else if p.CGroup != "" {
pids, err = p.cgroupPIDs()
tags = map[string]string{"cgroup": p.CGroup}
} else { } else {
err = fmt.Errorf("Either exe, pid_file, user, or pattern has to be specified") err = fmt.Errorf("Either exe, pid_file, user, or pattern has to be specified")
} }
@ -282,6 +297,59 @@ func (p *Procstat) findPids() ([]PID, map[string]string, error) {
return pids, tags, err return pids, tags, err
} }
func (p *Procstat) systemdUnitPIDs() ([]PID, error) {
var pids []PID
cmd := exec.Command("systemctl", "show", p.SystemdUnit)
out, err := cmd.Output()
if err != nil {
return nil, err
}
for _, line := range bytes.Split(out, []byte{'\n'}) {
kv := bytes.SplitN(line, []byte{'='}, 2)
if len(kv) != 2 {
continue
}
if !bytes.Equal(kv[0], []byte("MainPID")) {
continue
}
if len(kv[1]) == 0 {
return nil, nil
}
pid, err := strconv.Atoi(string(kv[1]))
if err != nil {
return nil, fmt.Errorf("invalid pid '%s'", kv[1])
}
pids = append(pids, PID(pid))
}
return pids, nil
}
func (p *Procstat) cgroupPIDs() ([]PID, error) {
var pids []PID
procsPath := p.CGroup
if procsPath[0] != '/' {
procsPath = "/sys/fs/cgroup/" + procsPath
}
procsPath = procsPath + "/cgroup.procs"
out, err := ioutil.ReadFile(procsPath)
if err != nil {
return nil, err
}
for _, pidBS := range bytes.Split(out, []byte{'\n'}) {
if len(pidBS) == 0 {
continue
}
pid, err := strconv.Atoi(string(pidBS))
if err != nil {
return nil, fmt.Errorf("invalid pid '%s'", pidBS)
}
pids = append(pids, PID(pid))
}
return pids, nil
}
func init() { func init() {
inputs.Add("procstat", func() telegraf.Input { inputs.Add("procstat", func() telegraf.Input {
return &Procstat{} return &Procstat{}