From 88e9018f7727cf7e2fa92e694d43bd9ba7250802 Mon Sep 17 00:00:00 2001 From: Cameron Sparr Date: Tue, 8 Mar 2016 11:42:31 +0100 Subject: [PATCH] Cross platform support for the 'processes' plugin closes #798 --- plugins/inputs/system/processes.go | 140 +++++++++++++++++++----- plugins/inputs/system/processes_test.go | 6 +- 2 files changed, 117 insertions(+), 29 deletions(-) diff --git a/plugins/inputs/system/processes.go b/plugins/inputs/system/processes.go index c4b791e3c81ef..02b67276e27fc 100644 --- a/plugins/inputs/system/processes.go +++ b/plugins/inputs/system/processes.go @@ -1,12 +1,19 @@ +// +build !windows + package system import ( + "bytes" "fmt" + "io/ioutil" "log" + "os" + "os/exec" + "path" + "runtime" "github.com/influxdata/telegraf" "github.com/influxdata/telegraf/plugins/inputs" - "github.com/shirou/gopsutil/process" ) type Processes struct { @@ -19,41 +26,122 @@ func (_ *Processes) Description() string { func (_ *Processes) SampleConfig() string { return "" } func (s *Processes) Gather(acc telegraf.Accumulator) error { - pids, err := process.Pids() + fields := map[string]interface{}{ + "blocked": int64(0), + "zombie": int64(0), + "stopped": int64(0), + "running": int64(0), + "sleeping": int64(0), + } + + usePS := true + switch runtime.GOOS { + case "freebsd": + fields["idle"] = int64(0) + fields["wait"] = int64(0) + case "darwin": + fields["idle"] = int64(0) + case "openbsd": + fields["idle"] = int64(0) + case "linux": + usePS = false + fields["paging"] = int64(0) + } + + if usePS { + if err := fromPS(fields); err != nil { + return err + } + } else { + if err := fromProc(fields); err != nil { + return err + } + } + + acc.AddFields("processes", fields, nil) + return nil +} + +// exec `ps` to get all process states +func fromPS(fields map[string]interface{}) error { + bin, err := exec.LookPath("ps") if err != nil { - return fmt.Errorf("error getting pids list: %s", err) + return err } - // TODO handle other OS (Windows/BSD/Solaris/OSX) - fields := map[string]interface{}{ - "paging": uint64(0), - "blocked": uint64(0), - "zombie": uint64(0), - "stopped": uint64(0), - "running": uint64(0), - "sleeping": uint64(0), - } - for _, pid := range pids { - process, err := process.NewProcess(pid) - if err != nil { - log.Printf("Can not get process %d status: %s", pid, err) + + out, err := exec.Command(bin, "axo", "state").Output() + if err != nil { + return err + } + + for _, status := range bytes.Fields(out) { + switch status[0] { + case 'W': + fields["wait"] = fields["wait"].(int64) + int64(1) + case 'U', 'D': + // Also known as uninterruptible sleep or disk sleep + fields["blocked"] = fields["blocked"].(int64) + int64(1) + case 'Z': + fields["zombie"] = fields["zombie"].(int64) + int64(1) + case 'T': + fields["stopped"] = fields["stopped"].(int64) + int64(1) + case 'R': + fields["running"] = fields["running"].(int64) + int64(1) + case 'S': + fields["sleeping"] = fields["sleeping"].(int64) + int64(1) + case 'I': + fields["idle"] = fields["idle"].(int64) + int64(1) + default: + log.Printf("processes: Unknown state [ %s ] from ps", + string(status[0])) + } + } + return nil +} + +// get process states from /proc/(pid)/stat files +func fromProc(fields map[string]interface{}) error { + procDirs, err := ioutil.ReadDir("/proc") + if err != nil { + return err + } + + for _, dir := range procDirs { + statFile := path.Join("/proc", dir.Name(), "stat") + if _, err := os.Stat(statFile); os.IsNotExist(err) { continue } - status, err := process.Status() + + data, err := ioutil.ReadFile(statFile) if err != nil { - log.Printf("Can not get process %d status: %s\n", pid, err) - continue + return err } - _, exists := fields[status] - if !exists { - log.Printf("Status '%s' for process with pid: %d\n", status, pid) - continue + + stats := bytes.Fields(data) + if len(stats) < 3 { + return fmt.Errorf("Something is terribly wrong with %s", statFile) + } + switch stats[2][0] { + case 'R': + fields["running"] = fields["running"].(int64) + int64(1) + case 'S': + fields["sleeping"] = fields["sleeping"].(int64) + int64(1) + case 'D': + fields["blocked"] = fields["blocked"].(int64) + int64(1) + case 'Z': + fields["zombies"] = fields["zombies"].(int64) + int64(1) + case 'T', 't': + fields["stopped"] = fields["stopped"].(int64) + int64(1) + case 'W': + fields["paging"] = fields["paging"].(int64) + int64(1) + default: + log.Printf("processes: Unknown state [ %s ] in file %s", + string(stats[2][0]), statFile) } - fields[status] = fields[status].(uint64) + uint64(1) } - - acc.AddFields("processes", fields, nil) return nil } + func init() { inputs.Add("processes", func() telegraf.Input { return &Processes{} diff --git a/plugins/inputs/system/processes_test.go b/plugins/inputs/system/processes_test.go index 246884711082a..58c37576da7c7 100644 --- a/plugins/inputs/system/processes_test.go +++ b/plugins/inputs/system/processes_test.go @@ -15,7 +15,7 @@ func TestProcesses(t *testing.T) { err := processes.Gather(&acc) require.NoError(t, err) - assert.True(t, acc.HasUIntField("processes", "running")) - assert.True(t, acc.HasUIntField("processes", "sleeping")) - assert.True(t, acc.HasUIntField("processes", "stopped")) + assert.True(t, acc.HasIntField("processes", "running")) + assert.True(t, acc.HasIntField("processes", "sleeping")) + assert.True(t, acc.HasIntField("processes", "stopped")) }