1
0
mirror of https://github.com/shirou/gopsutil.git synced 2025-04-26 13:48:59 +08:00

Fix spaces on long process names for MacOS

This commit is contained in:
John Blesener 2021-02-16 14:51:44 +09:00
parent 3585d276bc
commit e8b2bea47f
No known key found for this signature in database
GPG Key ID: CEBBC1A0C49EA822
4 changed files with 163 additions and 37 deletions

View File

@ -38,7 +38,7 @@ type _Ctype_struct___0 struct {
func pidsWithContext(ctx context.Context) ([]int32, error) {
var ret []int32
pids, err := callPsWithContext(ctx, "pid", 0, false)
pids, err := callPsWithContext(ctx, "pid", 0, false, false)
if err != nil {
return ret, err
}
@ -55,7 +55,7 @@ func pidsWithContext(ctx context.Context) ([]int32, error) {
}
func (p *Process) PpidWithContext(ctx context.Context) (int32, error) {
r, err := callPsWithContext(ctx, "ppid", p.Pid, false)
r, err := callPsWithContext(ctx, "ppid", p.Pid, false, false)
if err != nil {
return 0, err
}
@ -76,16 +76,16 @@ func (p *Process) NameWithContext(ctx context.Context) (string, error) {
name := common.IntToString(k.Proc.P_comm[:])
if len(name) >= 15 {
cmdlineSlice, err := p.CmdlineSliceWithContext(ctx)
cmdName, err := p.CmdNameWithContext(ctx)
if err != nil {
return "", err
}
if len(cmdlineSlice) > 0 {
extendedName := filepath.Base(cmdlineSlice[0])
if len(cmdName) > 0 {
extendedName := filepath.Base(cmdName[0])
if strings.HasPrefix(extendedName, p.name) {
name = extendedName
} else {
name = cmdlineSlice[0]
name = cmdName[0]
}
}
}
@ -94,20 +94,29 @@ func (p *Process) NameWithContext(ctx context.Context) (string, error) {
}
func (p *Process) CmdlineWithContext(ctx context.Context) (string, error) {
r, err := callPsWithContext(ctx, "command", p.Pid, false)
r, err := callPsWithContext(ctx, "command", p.Pid, false, false)
if err != nil {
return "", err
}
return strings.Join(r[0], " "), err
}
// CmdNameWithContext returns the command name (including spaces) without any arguments
func (p *Process) CmdNameWithContext(ctx context.Context) ([]string, error) {
r, err := callPsWithContext(ctx, "command", p.Pid, false, true)
if err != nil {
return nil, err
}
return r[0], err
}
// CmdlineSliceWithContext returns the command line arguments of the process as a slice with each
// element being an argument. Because of current deficiencies in the way that the command
// line arguments are found, single arguments that have spaces in the will actually be
// reported as two separate items. In order to do something better CGO would be needed
// to use the native darwin functions.
func (p *Process) CmdlineSliceWithContext(ctx context.Context) ([]string, error) {
r, err := callPsWithContext(ctx, "command", p.Pid, false)
r, err := callPsWithContext(ctx, "command", p.Pid, false, false)
if err != nil {
return nil, err
}
@ -115,7 +124,7 @@ func (p *Process) CmdlineSliceWithContext(ctx context.Context) ([]string, error)
}
func (p *Process) createTimeWithContext(ctx context.Context) (int64, error) {
r, err := callPsWithContext(ctx, "etime", p.Pid, false)
r, err := callPsWithContext(ctx, "etime", p.Pid, false, false)
if err != nil {
return 0, err
}
@ -163,7 +172,7 @@ func (p *Process) ParentWithContext(ctx context.Context) (*Process, error) {
}
func (p *Process) StatusWithContext(ctx context.Context) (string, error) {
r, err := callPsWithContext(ctx, "state", p.Pid, false)
r, err := callPsWithContext(ctx, "state", p.Pid, false, false)
if err != nil {
return "", err
}
@ -255,7 +264,7 @@ func (p *Process) IOCountersWithContext(ctx context.Context) (*IOCountersStat, e
}
func (p *Process) NumThreadsWithContext(ctx context.Context) (int32, error) {
r, err := callPsWithContext(ctx, "utime,stime", p.Pid, true)
r, err := callPsWithContext(ctx, "utime,stime", p.Pid, true, false)
if err != nil {
return 0, err
}
@ -309,7 +318,7 @@ func convertCPUTimes(s string) (ret float64, err error) {
}
func (p *Process) TimesWithContext(ctx context.Context) (*cpu.TimesStat, error) {
r, err := callPsWithContext(ctx, "utime,stime", p.Pid, false)
r, err := callPsWithContext(ctx, "utime,stime", p.Pid, false, false)
if err != nil {
return nil, err
@ -333,7 +342,7 @@ func (p *Process) TimesWithContext(ctx context.Context) (*cpu.TimesStat, error)
}
func (p *Process) MemoryInfoWithContext(ctx context.Context) (*MemoryInfoStat, error) {
r, err := callPsWithContext(ctx, "rss,vsize,pagein", p.Pid, false)
r, err := callPsWithContext(ctx, "rss,vsize,pagein", p.Pid, false, false)
if err != nil {
return nil, err
}
@ -419,9 +428,9 @@ func (p *Process) getKProc() (*KinfoProc, error) {
// call ps command.
// Return value deletes Header line(you must not input wrong arg).
// And splited by Space. Caller have responsibility to manage.
// And split by space. Caller have responsibility to manage.
// If passed arg pid is 0, get information from all process.
func callPsWithContext(ctx context.Context, arg string, pid int32, threadOption bool) ([][]string, error) {
func callPsWithContext(ctx context.Context, arg string, pid int32, threadOption bool, nameOption bool) ([][]string, error) {
bin, err := exec.LookPath("ps")
if err != nil {
return [][]string{}, err
@ -435,6 +444,10 @@ func callPsWithContext(ctx context.Context, arg string, pid int32, threadOption
} else {
cmd = []string{"-x", "-o", arg, "-p", strconv.Itoa(int(pid))}
}
if nameOption {
cmd = append(cmd, "-c")
}
out, err := invoke.CommandWithContext(ctx, bin, cmd...)
if err != nil {
return [][]string{}, err
@ -443,13 +456,19 @@ func callPsWithContext(ctx context.Context, arg string, pid int32, threadOption
var ret [][]string
for _, l := range lines[1:] {
var lr []string
for _, r := range strings.Split(l, " ") {
if r == "" {
continue
if nameOption {
lr = append(lr, l)
} else {
for _, r := range strings.Split(l, " ") {
if r == "" {
continue
}
lr = append(lr, strings.TrimSpace(r))
}
lr = append(lr, strings.TrimSpace(r))
}
if len(lr) != 0 {
ret = append(ret, lr)
}

View File

@ -312,6 +312,52 @@ func Test_Process_Name(t *testing.T) {
t.Errorf("invalid Exe %s", n)
}
}
func Test_Process_Long_Name_With_Spaces(t *testing.T) {
tmpdir, err := ioutil.TempDir("", "")
if err != nil {
t.Fatalf("unable to create temp dir %v", err)
}
defer os.RemoveAll(tmpdir) // clean up
tmpfilepath := filepath.Join(tmpdir, "loooong name with spaces.go")
tmpfile, err := os.Create(tmpfilepath)
if err != nil {
t.Fatalf("unable to create temp file %v", err)
}
tmpfilecontent := []byte("package main\nimport(\n\"time\"\n)\nfunc main(){\nfor range time.Tick(time.Second) {}\n}")
if _, err := tmpfile.Write(tmpfilecontent); err != nil {
tmpfile.Close()
t.Fatalf("unable to write temp file %v", err)
}
if err := tmpfile.Close(); err != nil {
t.Fatalf("unable to close temp file %v", err)
}
err = exec.Command("go", "build", "-o", tmpfile.Name()+".exe", tmpfile.Name()).Run()
if err != nil {
t.Fatalf("unable to build temp file %v", err)
}
cmd := exec.Command(tmpfile.Name() + ".exe")
assert.Nil(t, cmd.Start())
time.Sleep(100 * time.Millisecond)
p, err := NewProcess(int32(cmd.Process.Pid))
skipIfNotImplementedErr(t, err)
assert.Nil(t, err)
n, err := p.Name()
skipIfNotImplementedErr(t, err)
if err != nil {
t.Fatalf("getting name error %v", err)
}
basename := filepath.Base(tmpfile.Name() + ".exe")
if basename != n {
t.Fatalf("%s != %s", basename, n)
}
cmd.Process.Kill()
}
func Test_Process_Long_Name(t *testing.T) {
tmpdir, err := ioutil.TempDir("", "")
if err != nil {

View File

@ -38,7 +38,7 @@ type _Ctype_struct___0 struct {
func pidsWithContext(ctx context.Context) ([]int32, error) {
var ret []int32
pids, err := callPsWithContext(ctx, "pid", 0, false)
pids, err := callPsWithContext(ctx, "pid", 0, false, false)
if err != nil {
return ret, err
}
@ -55,7 +55,7 @@ func pidsWithContext(ctx context.Context) ([]int32, error) {
}
func (p *Process) PpidWithContext(ctx context.Context) (int32, error) {
r, err := callPsWithContext(ctx, "ppid", p.Pid, false)
r, err := callPsWithContext(ctx, "ppid", p.Pid, false, false)
if err != nil {
return 0, err
}
@ -76,16 +76,16 @@ func (p *Process) NameWithContext(ctx context.Context) (string, error) {
name := common.IntToString(k.Proc.P_comm[:])
if len(name) >= 15 {
cmdlineSlice, err := p.CmdlineSliceWithContext(ctx)
cmdName, err := p.CmdNameWithContext(ctx)
if err != nil {
return "", err
}
if len(cmdlineSlice) > 0 {
extendedName := filepath.Base(cmdlineSlice[0])
if len(cmdName) > 0 {
extendedName := filepath.Base(cmdName[0])
if strings.HasPrefix(extendedName, p.name) {
name = extendedName
} else {
name = cmdlineSlice[0]
name = cmdName[0]
}
}
}
@ -94,20 +94,29 @@ func (p *Process) NameWithContext(ctx context.Context) (string, error) {
}
func (p *Process) CmdlineWithContext(ctx context.Context) (string, error) {
r, err := callPsWithContext(ctx, "command", p.Pid, false)
r, err := callPsWithContext(ctx, "command", p.Pid, false, false)
if err != nil {
return "", err
}
return strings.Join(r[0], " "), err
}
// CmdNameWithContext returns the command name (including spaces) without any arguments
func (p *Process) CmdNameWithContext(ctx context.Context) ([]string, error) {
r, err := callPsWithContext(ctx, "command", p.Pid, false, true)
if err != nil {
return nil, err
}
return r[0], err
}
// CmdlineSliceWithContext returns the command line arguments of the process as a slice with each
// element being an argument. Because of current deficiencies in the way that the command
// line arguments are found, single arguments that have spaces in the will actually be
// reported as two separate items. In order to do something better CGO would be needed
// to use the native darwin functions.
func (p *Process) CmdlineSliceWithContext(ctx context.Context) ([]string, error) {
r, err := callPsWithContext(ctx, "command", p.Pid, false)
r, err := callPsWithContext(ctx, "command", p.Pid, false, false)
if err != nil {
return nil, err
}
@ -115,7 +124,7 @@ func (p *Process) CmdlineSliceWithContext(ctx context.Context) ([]string, error)
}
func (p *Process) createTimeWithContext(ctx context.Context) (int64, error) {
r, err := callPsWithContext(ctx, "etime", p.Pid, false)
r, err := callPsWithContext(ctx, "etime", p.Pid, false, false)
if err != nil {
return 0, err
}
@ -163,7 +172,7 @@ func (p *Process) ParentWithContext(ctx context.Context) (*Process, error) {
}
func (p *Process) StatusWithContext(ctx context.Context) ([]string, error) {
r, err := callPsWithContext(ctx, "state", p.Pid, false)
r, err := callPsWithContext(ctx, "state", p.Pid, false, false)
if err != nil {
return []string{""}, err
}
@ -255,7 +264,7 @@ func (p *Process) IOCountersWithContext(ctx context.Context) (*IOCountersStat, e
}
func (p *Process) NumThreadsWithContext(ctx context.Context) (int32, error) {
r, err := callPsWithContext(ctx, "utime,stime", p.Pid, true)
r, err := callPsWithContext(ctx, "utime,stime", p.Pid, true, false)
if err != nil {
return 0, err
}
@ -309,7 +318,7 @@ func convertCPUTimes(s string) (ret float64, err error) {
}
func (p *Process) TimesWithContext(ctx context.Context) (*cpu.TimesStat, error) {
r, err := callPsWithContext(ctx, "utime,stime", p.Pid, false)
r, err := callPsWithContext(ctx, "utime,stime", p.Pid, false, false)
if err != nil {
return nil, err
@ -333,7 +342,7 @@ func (p *Process) TimesWithContext(ctx context.Context) (*cpu.TimesStat, error)
}
func (p *Process) MemoryInfoWithContext(ctx context.Context) (*MemoryInfoStat, error) {
r, err := callPsWithContext(ctx, "rss,vsize,pagein", p.Pid, false)
r, err := callPsWithContext(ctx, "rss,vsize,pagein", p.Pid, false, false)
if err != nil {
return nil, err
}
@ -421,7 +430,7 @@ func (p *Process) getKProc() (*KinfoProc, error) {
// Return value deletes Header line(you must not input wrong arg).
// And splited by Space. Caller have responsibility to manage.
// If passed arg pid is 0, get information from all process.
func callPsWithContext(ctx context.Context, arg string, pid int32, threadOption bool) ([][]string, error) {
func callPsWithContext(ctx context.Context, arg string, pid int32, threadOption bool, nameOption bool) ([][]string, error) {
bin, err := exec.LookPath("ps")
if err != nil {
return [][]string{}, err
@ -435,6 +444,9 @@ func callPsWithContext(ctx context.Context, arg string, pid int32, threadOption
} else {
cmd = []string{"-x", "-o", arg, "-p", strconv.Itoa(int(pid))}
}
if nameOption {
cmd = append(cmd, "-c")
}
out, err := invoke.CommandWithContext(ctx, bin, cmd...)
if err != nil {
return [][]string{}, err
@ -444,11 +456,15 @@ func callPsWithContext(ctx context.Context, arg string, pid int32, threadOption
var ret [][]string
for _, l := range lines[1:] {
var lr []string
for _, r := range strings.Split(l, " ") {
if r == "" {
continue
if nameOption {
lr = append(lr, l)
} else {
for _, r := range strings.Split(l, " ") {
if r == "" {
continue
}
lr = append(lr, strings.TrimSpace(r))
}
lr = append(lr, strings.TrimSpace(r))
}
if len(lr) != 0 {
ret = append(ret, lr)

View File

@ -315,6 +315,51 @@ func Test_Process_Name(t *testing.T) {
t.Errorf("invalid Exe %s", n)
}
}
func Test_Process_Long_Name_With_Spaces(t *testing.T) {
tmpdir, err := ioutil.TempDir("", "")
if err != nil {
t.Fatalf("unable to create temp dir %v", err)
}
defer os.RemoveAll(tmpdir) // clean up
tmpfilepath := filepath.Join(tmpdir, "loooong name with spaces.go")
tmpfile, err := os.Create(tmpfilepath)
if err != nil {
t.Fatalf("unable to create temp file %v", err)
}
tmpfilecontent := []byte("package main\nimport(\n\"time\"\n)\nfunc main(){\nfor range time.Tick(time.Second) {}\n}")
if _, err := tmpfile.Write(tmpfilecontent); err != nil {
tmpfile.Close()
t.Fatalf("unable to write temp file %v", err)
}
if err := tmpfile.Close(); err != nil {
t.Fatalf("unable to close temp file %v", err)
}
err = exec.Command("go", "build", "-o", tmpfile.Name()+".exe", tmpfile.Name()).Run()
if err != nil {
t.Fatalf("unable to build temp file %v", err)
}
cmd := exec.Command(tmpfile.Name() + ".exe")
assert.Nil(t, cmd.Start())
time.Sleep(100 * time.Millisecond)
p, err := NewProcess(int32(cmd.Process.Pid))
skipIfNotImplementedErr(t, err)
assert.Nil(t, err)
n, err := p.Name()
skipIfNotImplementedErr(t, err)
if err != nil {
t.Fatalf("getting name error %v", err)
}
basename := filepath.Base(tmpfile.Name() + ".exe")
if basename != n {
t.Fatalf("%s != %s", basename, n)
}
cmd.Process.Kill()
}
func Test_Process_Long_Name(t *testing.T) {
tmpdir, err := ioutil.TempDir("", "")
if err != nil {