diff --git a/go.mod b/go.mod index f0831309..a6027135 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,6 @@ module github.com/google/gops go 1.13 require ( - github.com/keybase/go-ps v0.0.0-20190827175125-91aafc93ba19 github.com/shirou/gopsutil/v3 v3.22.10 github.com/spf13/cobra v1.6.1 github.com/xlab/treeprint v1.1.0 diff --git a/go.sum b/go.sum index 1f1199c3..cf2f2c40 100644 --- a/go.sum +++ b/go.sum @@ -9,8 +9,6 @@ github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/inconshreveable/mousetrap v1.0.1 h1:U3uMjPSQEBMNp1lFxmllqCPM6P5u/Xq7Pgzkat/bFNc= github.com/inconshreveable/mousetrap v1.0.1/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= -github.com/keybase/go-ps v0.0.0-20190827175125-91aafc93ba19 h1:WjT3fLi9n8YWh/Ih8Q1LHAPsTqGddPcHqscN+PJ3i68= -github.com/keybase/go-ps v0.0.0-20190827175125-91aafc93ba19/go.mod h1:hY+WOq6m2FpbvyrI93sMaypsttvaIL5nhVR92dTMUcQ= github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 h1:6E+4a0GO5zZEnZ81pIr0yLvtUWk2if982qA3F3QD6H4= github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= diff --git a/goprocess/goprocess.go b/goprocess/goprocess.go index 18fa051d..dd7eecf1 100644 --- a/goprocess/goprocess.go +++ b/goprocess/goprocess.go @@ -10,7 +10,7 @@ import ( "sync" "github.com/google/gops/internal" - ps "github.com/keybase/go-ps" + "github.com/shirou/gopsutil/v3/process" ) // P represents a Go process. @@ -26,7 +26,7 @@ type P struct { // FindAll returns all the Go processes currently running on this host. func FindAll() []P { const concurrencyLimit = 10 // max number of concurrent workers - pss, err := ps.Processes() + pss, err := process.Processes() if err != nil { return nil } @@ -34,10 +34,10 @@ func FindAll() []P { } // Allows to inject isGo for testing. -type isGoFunc func(ps.Process) (path, version string, agent, ok bool, err error) +type isGoFunc func(*process.Process) (path, version string, agent, ok bool, err error) -func findAll(pss []ps.Process, isGo isGoFunc, concurrencyLimit int) []P { - input := make(chan ps.Process, len(pss)) +func findAll(pss []*process.Process, isGo isGoFunc, concurrencyLimit int) []P { + input := make(chan *process.Process, len(pss)) output := make(chan P, len(pss)) for _, ps := range pss { @@ -63,10 +63,19 @@ func findAll(pss []ps.Process, isGo isGoFunc, concurrencyLimit int) []P { if !ok { continue } + ppid, err := pr.Ppid() + if err != nil { + continue + } + name, err := pr.Name() + if err != nil { + continue + } + output <- P{ - PID: pr.Pid(), - PPID: pr.PPid(), - Exec: pr.Executable(), + PID: int(pr.Pid), + PPID: int(ppid), + Exec: name, Path: path, BuildVersion: version, Agent: agent, @@ -86,18 +95,26 @@ func findAll(pss []ps.Process, isGo isGoFunc, concurrencyLimit int) []P { // Find finds info about the process identified with the given PID. func Find(pid int) (p P, ok bool, err error) { - pr, err := ps.FindProcess(pid) + pr, err := process.NewProcess(int32(pid)) if err != nil { return P{}, false, err } path, version, agent, ok, err := isGo(pr) - if !ok { + if !ok || err != nil { return P{}, false, nil } + ppid, err := pr.Ppid() + if err != nil { + return P{}, false, err + } + name, err := pr.Name() + if err != nil { + return P{}, false, err + } return P{ - PID: pr.Pid(), - PPID: pr.PPid(), - Exec: pr.Executable(), + PID: int(pr.Pid), + PPID: int(ppid), + Exec: name, Path: path, BuildVersion: version, Agent: agent, @@ -108,12 +125,12 @@ func Find(pid int) (p P, ok bool, err error) { // in the process' binary and determines if the process // if a Go process or not. If the process is a Go process, // it reports PID, binary name and full path of the binary. -func isGo(pr ps.Process) (path, version string, agent, ok bool, err error) { - if pr.Pid() == 0 { +func isGo(pr *process.Process) (path, version string, agent, ok bool, err error) { + if pr.Pid == 0 { // ignore system process return } - path, err = pr.Path() + path, err = pr.Exe() if err != nil { return } @@ -122,7 +139,7 @@ func isGo(pr ps.Process) (path, version string, agent, ok bool, err error) { return } ok = true - pidfile, err := internal.PIDFile(pr.Pid()) + pidfile, err := internal.PIDFile(int(pr.Pid)) if err == nil { _, err := os.Stat(pidfile) agent = err == nil diff --git a/goprocess/goprocess_test.go b/goprocess/goprocess_test.go index dd48e5e2..0db1e35d 100644 --- a/goprocess/goprocess_test.go +++ b/goprocess/goprocess_test.go @@ -1,11 +1,16 @@ package goprocess import ( + "io/ioutil" + "os" + "path/filepath" "reflect" + "runtime" "sort" + "strconv" "testing" - "github.com/keybase/go-ps" + "github.com/shirou/gopsutil/v3/process" ) func BenchmarkFindAll(b *testing.B) { @@ -16,12 +21,21 @@ func BenchmarkFindAll(b *testing.B) { // TestFindAll tests findAll implementation function. func TestFindAll(t *testing.T) { + testProcess, err := process.NewProcess(int32(os.Getpid())) + if err != nil { + t.Errorf("failed to get current process: %v", err) + } + testPpid, _ := testProcess.Ppid() + testExec, _ := testProcess.Name() + wantProcess := P{PID: int(testProcess.Pid), PPID: int(testPpid), Exec: testExec} + for _, tc := range []struct { name string concurrencyLimit int - input []ps.Process + input []*process.Process goPIDs []int want []P + mock bool }{{ name: "no processes", concurrencyLimit: 10, @@ -30,28 +44,57 @@ func TestFindAll(t *testing.T) { }, { name: "non-Go process", concurrencyLimit: 10, - input: fakeProcessesWithPIDs(1), + input: []*process.Process{testProcess}, want: nil, }, { name: "Go process", concurrencyLimit: 10, - input: fakeProcessesWithPIDs(1), - goPIDs: []int{1}, - want: []P{{PID: 1}}, + input: []*process.Process{testProcess}, + goPIDs: []int{int(testProcess.Pid)}, + want: []P{wantProcess}, }, { name: "filters Go processes", concurrencyLimit: 10, input: fakeProcessesWithPIDs(1, 2, 3, 4, 5, 6, 7), goPIDs: []int{1, 3, 5, 7}, want: []P{{PID: 1}, {PID: 3}, {PID: 5}, {PID: 7}}, + mock: true, }, { name: "Go processes above max concurrency (issue #123)", concurrencyLimit: 2, input: fakeProcessesWithPIDs(1, 2, 3, 4, 5, 6, 7), goPIDs: []int{1, 3, 5, 7}, want: []P{{PID: 1}, {PID: 3}, {PID: 5}, {PID: 7}}, + mock: true, }} { t.Run(tc.name, func(t *testing.T) { + if tc.mock { + if runtime.GOOS != "linux" { + t.Skip() + } + tempDir, err := ioutil.TempDir("", "") + if err != nil { + t.Errorf("failed to create temp dir: %v", err) + } + defer os.RemoveAll(tempDir) + for _, p := range tc.input { + os.Mkdir(filepath.Join(tempDir, strconv.Itoa(int(p.Pid))), 0o755) + ioutil.WriteFile(filepath.Join(tempDir, strconv.Itoa(int(p.Pid)), "stat"), []byte( + `1440024 () R 0 1440024 0 34821 1440024 4194304 134 0 0 0 0 0 0 0 20 0 1 0 95120609 6746112 274 18446744073709551615 94467689938944 94467690036601 140724224197808 0 0 0 0 0 0 0 0 0 17 11 0 0 0 0 0 94467690068048 94467690071296 94467715629056 140724224199226 140724224199259 140724224199259 140724224204780 0`, + ), 0o644) + ioutil.WriteFile(filepath.Join(tempDir, strconv.Itoa(int(p.Pid)), "status"), []byte( + `Name: +Umask: 0022 +State: R (running) +Tgid: 1440366 +Ngid: 0 +Pid: 1440366 +PPid: 0 +`, + ), 0o644) + } + os.Setenv("HOST_PROC", tempDir) + } actual := findAll(tc.input, fakeIsGo(tc.goPIDs), tc.concurrencyLimit) sort.Slice(actual, func(i, j int) bool { return actual[i].PID < actual[j].PID }) if !reflect.DeepEqual(actual, tc.want) { @@ -63,9 +106,9 @@ func TestFindAll(t *testing.T) { } func fakeIsGo(goPIDs []int) isGoFunc { - return func(pr ps.Process) (path, version string, agent, ok bool, err error) { + return func(pr *process.Process) (path, version string, agent, ok bool, err error) { for _, p := range goPIDs { - if p == pr.Pid() { + if p == int(pr.Pid) { ok = true return } @@ -74,19 +117,10 @@ func fakeIsGo(goPIDs []int) isGoFunc { } } -func fakeProcessesWithPIDs(pids ...int) []ps.Process { - p := make([]ps.Process, 0, len(pids)) +func fakeProcessesWithPIDs(pids ...int) []*process.Process { + p := make([]*process.Process, 0, len(pids)) for _, pid := range pids { - p = append(p, fakeProcess{pid: pid}) + p = append(p, &process.Process{Pid: int32(pid)}) } return p } - -type fakeProcess struct { - ps.Process - pid int -} - -func (p fakeProcess) Pid() int { return p.pid } -func (p fakeProcess) PPid() int { return 0 } -func (p fakeProcess) Executable() string { return "" }