Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add systemd unit pid matching to procstat #3459

Merged
merged 1 commit into from
Nov 13, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
add systemd unit pid matching to procstat
  • Loading branch information
phemmer committed Nov 13, 2017
commit 13b02e8dbfb47ad3a59cc7e903225a7d1a1b3af3
12 changes: 7 additions & 5 deletions plugins/inputs/procstat/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,11 @@ The procstat plugin can be used to monitor system resource usage by an
individual process using their /proc data.

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
plugin will use `pgrep` when executable name is provided to obtain the pid.
Procstat plugin will transmit IO, memory, cpu, file descriptor related
measurements for every process specified. A prefix can be set to isolate
individual process specific measurements.
line pattern matching, by username, by systemd unit name, or by cgroup name/path
(in this order or priority). Procstat plugin will use `pgrep` when executable
name is provided to obtain the pid. Procstat plugin will transmit IO, memory,
cpu, file descriptor related measurements for every process specified. A prefix
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.
On the other hand, if an executable is used an "exe" tag will be generated. Possible tag names:
Expand All @@ -19,6 +19,8 @@ On the other hand, if an executable is used an "exe" tag will be generated. Poss
* exe
* pattern
* user
* systemd_unit
* cgroup

Additionally the plugin will tag processes by their PID (pid_tag = true in the config) and their process name:

Expand Down
68 changes: 68 additions & 0 deletions plugins/inputs/procstat/procstat.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
package procstat

import (
"bytes"
"fmt"
"io/ioutil"
"os/exec"
"strconv"
"time"

Expand All @@ -24,6 +27,8 @@ type Procstat struct {
Prefix string
ProcessName string
User string
SystemdUnit string
CGroup string `toml:"cgroup"`
PidTag bool

pidFinder PIDFinder
Expand All @@ -42,6 +47,10 @@ var sampleConfig = `
# pattern = "nginx"
## user as argument for pgrep (ie, pgrep -u <user>)
# user = "nginx"
## Systemd unit name
# systemd_unit = "nginx.service"
## CGroup name or path
# cgroup = "systemd/system.slice/nginx.service"

## override for process_name
## This is optional; default is sourced from /proc/<pid>/status
Expand Down Expand Up @@ -275,13 +284,72 @@ func (p *Procstat) findPids() ([]PID, map[string]string, error) {
} else if p.User != "" {
pids, err = f.Uid(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 {
err = fmt.Errorf("Either exe, pid_file, user, or pattern has to be specified")
}

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() {
inputs.Add("procstat", func() telegraf.Input {
return &Procstat{}
Expand Down