Add native Go method for finding pids to procstat (#3559)
This commit is contained in:
parent
9b4177d46c
commit
551c771bba
1
Makefile
1
Makefile
|
@ -68,6 +68,7 @@ test-windows:
|
||||||
go test ./plugins/inputs/ping/...
|
go test ./plugins/inputs/ping/...
|
||||||
go test ./plugins/inputs/win_perf_counters/...
|
go test ./plugins/inputs/win_perf_counters/...
|
||||||
go test ./plugins/inputs/win_services/...
|
go test ./plugins/inputs/win_services/...
|
||||||
|
go test ./plugins/inputs/procstat/...
|
||||||
|
|
||||||
# vet runs the Go source code static analysis tool `vet` to find
|
# vet runs the Go source code static analysis tool `vet` to find
|
||||||
# any common errors.
|
# any common errors.
|
||||||
|
|
|
@ -27,8 +27,28 @@ Additionally the plugin will tag processes by their PID (pid_tag = true in the c
|
||||||
* pid
|
* pid
|
||||||
* process_name
|
* process_name
|
||||||
|
|
||||||
|
|
||||||
|
### Windows
|
||||||
|
On windows we only support exe and pattern. Both of these are implemented using WMI queries. exe is on the Name field and pattern is on the CommandLine field.
|
||||||
|
|
||||||
|
Windows Support:
|
||||||
|
* exe (WMI Name)
|
||||||
|
* pattern (WMI CommandLine)
|
||||||
|
|
||||||
|
this allows you to do fuzzy matching but only what is supported by [WMI query patterns](https://msdn.microsoft.com/en-us/library/aa392263(v=vs.85).aspx).
|
||||||
|
|
||||||
Example:
|
Example:
|
||||||
|
|
||||||
|
Windows fuzzy matching:
|
||||||
|
```[[inputs.procstat]]
|
||||||
|
exe = "%influx%"
|
||||||
|
process_name="influxd"
|
||||||
|
prefix = "influxd"
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
### Linux
|
||||||
|
|
||||||
```
|
```
|
||||||
[[inputs.procstat]]
|
[[inputs.procstat]]
|
||||||
exe = "influxd"
|
exe = "influxd"
|
||||||
|
@ -48,7 +68,6 @@ The above configuration would result in output like:
|
||||||
# Measurements
|
# Measurements
|
||||||
Note: prefix can be set by the user, per process.
|
Note: prefix can be set by the user, per process.
|
||||||
|
|
||||||
|
|
||||||
Threads related measurement names:
|
Threads related measurement names:
|
||||||
- procstat_[prefix_]num_threads value=5
|
- procstat_[prefix_]num_threads value=5
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,57 @@
|
||||||
|
package procstat
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/shirou/gopsutil/process"
|
||||||
|
)
|
||||||
|
|
||||||
|
//NativeFinder uses gopsutil to find processes
|
||||||
|
type NativeFinder struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
//NewNativeFinder ...
|
||||||
|
func NewNativeFinder() (PIDFinder, error) {
|
||||||
|
return &NativeFinder{}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
//Uid will return all pids for the given user
|
||||||
|
func (pg *NativeFinder) Uid(user string) ([]PID, error) {
|
||||||
|
var dst []PID
|
||||||
|
procs, err := process.Processes()
|
||||||
|
if err != nil {
|
||||||
|
return dst, err
|
||||||
|
}
|
||||||
|
for _, p := range procs {
|
||||||
|
username, err := p.Username()
|
||||||
|
if err != nil {
|
||||||
|
//skip, this can happen if we don't have permissions or
|
||||||
|
//the pid no longer exists
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if username == user {
|
||||||
|
dst = append(dst, PID(p.Pid))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return dst, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
//PidFile returns the pid from the pid file given.
|
||||||
|
func (pg *NativeFinder) PidFile(path string) ([]PID, error) {
|
||||||
|
var pids []PID
|
||||||
|
pidString, err := ioutil.ReadFile(path)
|
||||||
|
if err != nil {
|
||||||
|
return pids, fmt.Errorf("Failed to read pidfile '%s'. Error: '%s'",
|
||||||
|
path, err)
|
||||||
|
}
|
||||||
|
pid, err := strconv.Atoi(strings.TrimSpace(string(pidString)))
|
||||||
|
if err != nil {
|
||||||
|
return pids, err
|
||||||
|
}
|
||||||
|
pids = append(pids, PID(pid))
|
||||||
|
return pids, nil
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,59 @@
|
||||||
|
// +build !windows
|
||||||
|
|
||||||
|
package procstat
|
||||||
|
|
||||||
|
import (
|
||||||
|
"regexp"
|
||||||
|
|
||||||
|
"github.com/shirou/gopsutil/process"
|
||||||
|
)
|
||||||
|
|
||||||
|
//Pattern matches on the process name
|
||||||
|
func (pg *NativeFinder) Pattern(pattern string) ([]PID, error) {
|
||||||
|
var pids []PID
|
||||||
|
regxPattern, err := regexp.Compile(pattern)
|
||||||
|
if err != nil {
|
||||||
|
return pids, err
|
||||||
|
}
|
||||||
|
procs, err := process.Processes()
|
||||||
|
if err != nil {
|
||||||
|
return pids, err
|
||||||
|
}
|
||||||
|
for _, p := range procs {
|
||||||
|
name, err := p.Exe()
|
||||||
|
if err != nil {
|
||||||
|
//skip, this can be caused by the pid no longer existing
|
||||||
|
//or you having no permissions to access it
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if regxPattern.MatchString(name) {
|
||||||
|
pids = append(pids, PID(p.Pid))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return pids, err
|
||||||
|
}
|
||||||
|
|
||||||
|
//FullPattern matches on the command line when the proccess was executed
|
||||||
|
func (pg *NativeFinder) FullPattern(pattern string) ([]PID, error) {
|
||||||
|
var pids []PID
|
||||||
|
regxPattern, err := regexp.Compile(pattern)
|
||||||
|
if err != nil {
|
||||||
|
return pids, err
|
||||||
|
}
|
||||||
|
procs, err := process.Processes()
|
||||||
|
if err != nil {
|
||||||
|
return pids, err
|
||||||
|
}
|
||||||
|
for _, p := range procs {
|
||||||
|
cmd, err := p.Cmdline()
|
||||||
|
if err != nil {
|
||||||
|
//skip, this can be caused by the pid no longer existing
|
||||||
|
//or you having no permissions to access it
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if regxPattern.MatchString(cmd) {
|
||||||
|
pids = append(pids, PID(p.Pid))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return pids, err
|
||||||
|
}
|
|
@ -0,0 +1,91 @@
|
||||||
|
package procstat
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"regexp"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/StackExchange/wmi"
|
||||||
|
"github.com/shirou/gopsutil/process"
|
||||||
|
)
|
||||||
|
|
||||||
|
//Timeout is the timeout used when making wmi calls
|
||||||
|
var Timeout = 5 * time.Second
|
||||||
|
|
||||||
|
type queryType string
|
||||||
|
|
||||||
|
const (
|
||||||
|
like = queryType("LIKE")
|
||||||
|
equals = queryType("=")
|
||||||
|
notEqual = queryType("!=")
|
||||||
|
)
|
||||||
|
|
||||||
|
//Pattern matches on the process name
|
||||||
|
func (pg *NativeFinder) Pattern(pattern string) ([]PID, error) {
|
||||||
|
var pids []PID
|
||||||
|
regxPattern, err := regexp.Compile(pattern)
|
||||||
|
if err != nil {
|
||||||
|
return pids, err
|
||||||
|
}
|
||||||
|
procs, err := process.Processes()
|
||||||
|
if err != nil {
|
||||||
|
return pids, err
|
||||||
|
}
|
||||||
|
for _, p := range procs {
|
||||||
|
name, err := p.Name()
|
||||||
|
if err != nil {
|
||||||
|
//skip, this can be caused by the pid no longer existing
|
||||||
|
//or you having no permissions to access it
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if regxPattern.MatchString(name) {
|
||||||
|
pids = append(pids, PID(p.Pid))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return pids, err
|
||||||
|
}
|
||||||
|
|
||||||
|
//FullPattern matches the cmdLine on windows and will find a pattern using a WMI like query
|
||||||
|
func (pg *NativeFinder) FullPattern(pattern string) ([]PID, error) {
|
||||||
|
var pids []PID
|
||||||
|
procs, err := getWin32ProcsByVariable("CommandLine", like, pattern, Timeout)
|
||||||
|
if err != nil {
|
||||||
|
return pids, err
|
||||||
|
}
|
||||||
|
for _, p := range procs {
|
||||||
|
pids = append(pids, PID(p.ProcessID))
|
||||||
|
}
|
||||||
|
return pids, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
//GetWin32ProcsByVariable allows you to query any variable with a like query
|
||||||
|
func getWin32ProcsByVariable(variable string, qType queryType, value string, timeout time.Duration) ([]process.Win32_Process, error) {
|
||||||
|
var dst []process.Win32_Process
|
||||||
|
var query string
|
||||||
|
// should look like "WHERE CommandLine LIKE "procstat"
|
||||||
|
query = fmt.Sprintf("WHERE %s %s %q", variable, qType, value)
|
||||||
|
q := wmi.CreateQuery(&dst, query)
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), timeout)
|
||||||
|
defer cancel()
|
||||||
|
err := WMIQueryWithContext(ctx, q, &dst)
|
||||||
|
if err != nil {
|
||||||
|
return []process.Win32_Process{}, fmt.Errorf("could not get win32Proc: %s", err)
|
||||||
|
}
|
||||||
|
return dst, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// WMIQueryWithContext - wraps wmi.Query with a timed-out context to avoid hanging
|
||||||
|
func WMIQueryWithContext(ctx context.Context, query string, dst interface{}, connectServerArgs ...interface{}) error {
|
||||||
|
errChan := make(chan error, 1)
|
||||||
|
go func() {
|
||||||
|
errChan <- wmi.Query(query, dst, connectServerArgs...)
|
||||||
|
}()
|
||||||
|
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
return ctx.Err()
|
||||||
|
case err := <-errChan:
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,40 @@
|
||||||
|
package procstat
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"os/user"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestGather_RealPattern(t *testing.T) {
|
||||||
|
pg, err := NewNativeFinder()
|
||||||
|
require.NoError(t, err)
|
||||||
|
pids, err := pg.Pattern(`procstat`)
|
||||||
|
require.NoError(t, err)
|
||||||
|
fmt.Println(pids)
|
||||||
|
assert.Equal(t, len(pids) > 0, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGather_RealFullPattern(t *testing.T) {
|
||||||
|
pg, err := NewNativeFinder()
|
||||||
|
require.NoError(t, err)
|
||||||
|
pids, err := pg.FullPattern(`%procstat%`)
|
||||||
|
require.NoError(t, err)
|
||||||
|
fmt.Println(pids)
|
||||||
|
assert.Equal(t, len(pids) > 0, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGather_RealUser(t *testing.T) {
|
||||||
|
user, err := user.Current()
|
||||||
|
require.NoError(t, err)
|
||||||
|
pg, err := NewNativeFinder()
|
||||||
|
require.NoError(t, err)
|
||||||
|
pids, err := pg.Uid(user.Username)
|
||||||
|
require.NoError(t, err)
|
||||||
|
fmt.Println(pids)
|
||||||
|
assert.Equal(t, len(pids) > 0, true)
|
||||||
|
}
|
|
@ -8,13 +8,6 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
type PIDFinder interface {
|
|
||||||
PidFile(path string) ([]PID, error)
|
|
||||||
Pattern(pattern string) ([]PID, error)
|
|
||||||
Uid(user string) ([]PID, error)
|
|
||||||
FullPattern(path string) ([]PID, error)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Implemention of PIDGatherer that execs pgrep to find processes
|
// Implemention of PIDGatherer that execs pgrep to find processes
|
||||||
type Pgrep struct {
|
type Pgrep struct {
|
||||||
path string
|
path string
|
||||||
|
|
|
@ -23,6 +23,13 @@ type Process interface {
|
||||||
RlimitUsage(bool) ([]process.RlimitStat, error)
|
RlimitUsage(bool) ([]process.RlimitStat, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type PIDFinder interface {
|
||||||
|
PidFile(path string) ([]PID, error)
|
||||||
|
Pattern(pattern string) ([]PID, error)
|
||||||
|
Uid(user string) ([]PID, error)
|
||||||
|
FullPattern(path string) ([]PID, error)
|
||||||
|
}
|
||||||
|
|
||||||
type Proc struct {
|
type Proc struct {
|
||||||
hasCPUTimes bool
|
hasCPUTimes bool
|
||||||
tags map[string]string
|
tags map[string]string
|
||||||
|
|
|
@ -22,6 +22,7 @@ var (
|
||||||
type PID int32
|
type PID int32
|
||||||
|
|
||||||
type Procstat struct {
|
type Procstat struct {
|
||||||
|
PidFinder string `toml:"pid_finder"`
|
||||||
PidFile string `toml:"pid_file"`
|
PidFile string `toml:"pid_file"`
|
||||||
Exe string
|
Exe string
|
||||||
Pattern string
|
Pattern string
|
||||||
|
@ -32,13 +33,19 @@ type Procstat struct {
|
||||||
CGroup string `toml:"cgroup"`
|
CGroup string `toml:"cgroup"`
|
||||||
PidTag bool
|
PidTag bool
|
||||||
|
|
||||||
pidFinder PIDFinder
|
finder PIDFinder
|
||||||
|
|
||||||
createPIDFinder func() (PIDFinder, error)
|
createPIDFinder func() (PIDFinder, error)
|
||||||
procs map[PID]Process
|
procs map[PID]Process
|
||||||
createProcess func(PID) (Process, error)
|
createProcess func(PID) (Process, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
var sampleConfig = `
|
var sampleConfig = `
|
||||||
|
## pidFinder can be pgrep or native
|
||||||
|
## pgrep tries to exec pgrep
|
||||||
|
## native will work on all platforms, unix systems will use regexp.
|
||||||
|
## Windows will use WMI calls with like queries
|
||||||
|
pid_finder = "native"
|
||||||
## Must specify one of: pid_file, exe, or pattern
|
## Must specify one of: pid_file, exe, or pattern
|
||||||
## PID file to monitor process
|
## PID file to monitor process
|
||||||
pid_file = "/var/run/nginx.pid"
|
pid_file = "/var/run/nginx.pid"
|
||||||
|
@ -74,7 +81,15 @@ func (_ *Procstat) Description() string {
|
||||||
|
|
||||||
func (p *Procstat) Gather(acc telegraf.Accumulator) error {
|
func (p *Procstat) Gather(acc telegraf.Accumulator) error {
|
||||||
if p.createPIDFinder == nil {
|
if p.createPIDFinder == nil {
|
||||||
p.createPIDFinder = defaultPIDFinder
|
switch p.PidFinder {
|
||||||
|
case "native":
|
||||||
|
p.createPIDFinder = NewNativeFinder
|
||||||
|
case "pgrep":
|
||||||
|
p.createPIDFinder = NewPgrep
|
||||||
|
default:
|
||||||
|
p.createPIDFinder = defaultPIDFinder
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
if p.createProcess == nil {
|
if p.createProcess == nil {
|
||||||
p.createProcess = defaultProcess
|
p.createProcess = defaultProcess
|
||||||
|
@ -252,14 +267,15 @@ func (p *Procstat) updateProcesses(prevInfo map[PID]Process) (map[PID]Process, e
|
||||||
|
|
||||||
// Create and return PIDGatherer lazily
|
// Create and return PIDGatherer lazily
|
||||||
func (p *Procstat) getPIDFinder() (PIDFinder, error) {
|
func (p *Procstat) getPIDFinder() (PIDFinder, error) {
|
||||||
if p.pidFinder == nil {
|
|
||||||
|
if p.finder == nil {
|
||||||
f, err := p.createPIDFinder()
|
f, err := p.createPIDFinder()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
p.pidFinder = f
|
p.finder = f
|
||||||
}
|
}
|
||||||
return p.pidFinder, nil
|
return p.finder, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get matching PIDs and their initial tags
|
// Get matching PIDs and their initial tags
|
||||||
|
|
|
@ -6,6 +6,7 @@ import (
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"runtime"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
@ -349,6 +350,10 @@ func TestGather_systemdUnitPIDs(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestGather_cgroupPIDs(t *testing.T) {
|
func TestGather_cgroupPIDs(t *testing.T) {
|
||||||
|
//no cgroups in windows
|
||||||
|
if runtime.GOOS == "windows" {
|
||||||
|
t.Skip("no cgroups in windows")
|
||||||
|
}
|
||||||
td, err := ioutil.TempDir("", "")
|
td, err := ioutil.TempDir("", "")
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
defer os.RemoveAll(td)
|
defer os.RemoveAll(td)
|
||||||
|
|
Loading…
Reference in New Issue