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

feat(inputs.procstat): Obtain process information through supervisor #13417

Merged
merged 14 commits into from
Nov 13, 2023
10 changes: 10 additions & 0 deletions plugins/inputs/procstat/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ Processes can be selected for monitoring using one of several methods:
- user
- systemd_unit
- cgroup
- supervisor_unit
- win_service

## Global configuration options <!-- @/docs/includes/plugin_config.md -->
Expand Down Expand Up @@ -41,6 +42,8 @@ See the [CONFIGURATION.md][CONFIGURATION.md] for more details.
# include_systemd_children = false
## CGroup name or path, supports globs
# cgroup = "systemd/system.slice/nginx.service"
## Supervisor service names of hypervisorctl management
# supervisor_units = ["webserver", "proxy"]

## Windows service name
# win_service = ""
Expand Down Expand Up @@ -78,6 +81,11 @@ See the [CONFIGURATION.md][CONFIGURATION.md] for more details.
Preliminary support for Windows has been added, however you may prefer using
the `win_perf_counters` input plugin as a more mature alternative.

### Darwin specifics

If you use this plugin with `supervisor_units` *and* `pattern` on Darwin, you
**have to** use the `pgrep` finder as the underlying library relies on `pgrep`.

### Permissions

Some files or directories may require elevated permissions. As such a user may
Expand Down Expand Up @@ -109,6 +117,7 @@ Below are an example set of tags and fields:
- systemd_unit (when defined)
- cgroup (when defined)
- cgroup_full (when cgroup or systemd_unit is used with glob)
- supervisor_unit (when defined)
- win_service (when defined)
- fields:
- child_major_faults (int)
Expand Down Expand Up @@ -179,6 +188,7 @@ Below are an example set of tags and fields:
- user
- systemd_unit
- cgroup
- supervisor_unit
- win_service
- result
- fields:
Expand Down
35 changes: 35 additions & 0 deletions plugins/inputs/procstat/native_finder.go
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,41 @@ func (pg *NativeFinder) FullPattern(pattern string) ([]PID, error) {
return pids, err
}

// ChildPattern matches children pids on the command line when the process was executed
func (pg *NativeFinder) ChildPattern(pattern string) ([]PID, error) {
regxPattern, err := regexp.Compile(pattern)
if err != nil {
return nil, fmt.Errorf("compiling regexp failed: %w", err)
}

procs, err := process.Processes()
if err != nil {
return nil, fmt.Errorf("getting processes failed: %w", err)
}

var pids []PID
for _, p := range procs {
cmd, err := p.Cmdline()
if err != nil || !regxPattern.MatchString(cmd) {
continue
}

parent, err := process.NewProcess(p.Pid)
if err != nil {
return nil, fmt.Errorf("unable to get process %d: %w", p.Pid, err)
}
children, err := parent.Children()
if err != nil {
return nil, fmt.Errorf("unable to get children of process %d: %w", p.Pid, err)
chenbt-hz marked this conversation as resolved.
Show resolved Hide resolved
}

for _, child := range children {
pids = append(pids, PID(child.Pid))
}
}
return pids, err
}

func (pg *NativeFinder) FastProcessList() ([]*process.Process, error) {
pids, err := process.Pids()
if err != nil {
Expand Down
53 changes: 43 additions & 10 deletions plugins/inputs/procstat/native_finder_test.go
Original file line number Diff line number Diff line change
@@ -1,29 +1,62 @@
package procstat

import (
"context"
"os"
"os/exec"
"runtime"
"testing"

"github.com/stretchr/testify/require"
)

func BenchmarkPattern(b *testing.B) {
f, err := NewNativeFinder()
finder, err := NewNativeFinder()
require.NoError(b, err)
for n := 0; n < b.N; n++ {
_, err := f.Pattern(".*")
if err != nil {
panic(err)
}
_, err = finder.Pattern(".*")
require.NoError(b, err)
}
}

func BenchmarkFullPattern(b *testing.B) {
f, err := NewNativeFinder()
finder, err := NewNativeFinder()
require.NoError(b, err)
for n := 0; n < b.N; n++ {
_, err := f.FullPattern(".*")
if err != nil {
panic(err)
}
_, err := finder.FullPattern(".*")
require.NoError(b, err)
}
}

func TestChildPattern(t *testing.T) {
if runtime.GOOS == "windows" || runtime.GOOS == "darwin" {
t.Skip("Skipping test on unsupported platform")
}

// Get our own process name
parentName, err := os.Executable()
require.NoError(t, err)

// Spawn two child processes and get their PIDs
expected := make([]PID, 0, 2)
ctx, cancel := context.WithCancel(context.Background())
defer cancel()

// First process
cmd1 := exec.CommandContext(ctx, "/bin/sh")
require.NoError(t, cmd1.Start(), "starting first command failed")
expected = append(expected, PID(cmd1.Process.Pid))

// Second process
cmd2 := exec.CommandContext(ctx, "/bin/sh")
require.NoError(t, cmd2.Start(), "starting first command failed")
expected = append(expected, PID(cmd2.Process.Pid))

// Use the plugin to find the children
finder, err := NewNativeFinder()
require.NoError(t, err)

childs, err := finder.ChildPattern(parentName)
require.NoError(t, err)
require.ElementsMatch(t, expected, childs)
}
5 changes: 1 addition & 4 deletions plugins/inputs/procstat/native_finder_windows_test.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package procstat

import (
"fmt"
"os/user"
"testing"

Expand All @@ -16,7 +15,6 @@ func TestGather_RealPatternIntegration(t *testing.T) {
require.NoError(t, err)
pids, err := pg.Pattern(`procstat`)
require.NoError(t, err)
fmt.Println(pids)
require.NotEmpty(t, pids)
}

Expand All @@ -26,9 +24,9 @@ func TestGather_RealFullPatternIntegration(t *testing.T) {
}
pg, err := NewNativeFinder()
require.NoError(t, err)

pids, err := pg.FullPattern(`%procstat%`)
require.NoError(t, err)
fmt.Println(pids)
require.NotEmpty(t, pids)
}

Expand All @@ -42,6 +40,5 @@ func TestGather_RealUserIntegration(t *testing.T) {
require.NoError(t, err)
pids, err := pg.UID(currentUser.Username)
require.NoError(t, err)
fmt.Println(pids)
require.NotEmpty(t, pids)
}
26 changes: 26 additions & 0 deletions plugins/inputs/procstat/pgrep.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,32 @@ func (pg *Pgrep) FullPattern(pattern string) ([]PID, error) {
return find(pg.path, args)
}

func (pg *Pgrep) ChildPattern(pattern string) ([]PID, error) {
args := []string{"-P", pattern}
out, err := run(pg.path, args)
chenbt-hz marked this conversation as resolved.
Show resolved Hide resolved
if err != nil {
return nil, err
}

pids := []PID{}
pid, err := strconv.ParseInt(pattern, 10, 32)
if err != nil {
return nil, err
}
pids = append(pids, PID(pid))

fields := strings.Fields(out)
for _, field := range fields {
pid, err := strconv.ParseInt(field, 10, 32)
if err != nil {
return pids, err
}
pids = append(pids, PID(pid))
}

return pids, nil
}

func find(path string, args []string) ([]PID, error) {
out, err := run(path, args)
if err != nil {
Expand Down
1 change: 1 addition & 0 deletions plugins/inputs/procstat/process.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ type PIDFinder interface {
Pattern(pattern string) ([]PID, error)
UID(user string) ([]PID, error)
FullPattern(path string) ([]PID, error)
ChildPattern(path string) ([]PID, error)
}

type Proc struct {
Expand Down