mirror of
https://github.com/shirou/gopsutil.git
synced 2025-04-24 13:48:56 +08:00

pgrep -P $PID exits with status of 1 (and nothing in stdout nor stderr) both if a process doesn't exist or it doesn't have child processes, so we don't use it anymore on these OSes. We sort PIDs as pgrep did. Also deprecate the ErrorNoChildren error when there are no child processes, this is erroneous (simply check for the length of the returned slice, plus this is not an error per se), this was only returned on linux anyway. Fixes #1698
346 lines
7.4 KiB
Go
346 lines
7.4 KiB
Go
// SPDX-License-Identifier: BSD-3-Clause
|
|
//go:build freebsd
|
|
|
|
package process
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"errors"
|
|
"path/filepath"
|
|
"sort"
|
|
"strconv"
|
|
"strings"
|
|
|
|
"golang.org/x/sys/unix"
|
|
|
|
cpu "github.com/shirou/gopsutil/v4/cpu"
|
|
"github.com/shirou/gopsutil/v4/internal/common"
|
|
net "github.com/shirou/gopsutil/v4/net"
|
|
)
|
|
|
|
func pidsWithContext(ctx context.Context) ([]int32, error) {
|
|
var ret []int32
|
|
procs, err := ProcessesWithContext(ctx)
|
|
if err != nil {
|
|
return ret, nil
|
|
}
|
|
|
|
for _, p := range procs {
|
|
ret = append(ret, p.Pid)
|
|
}
|
|
|
|
return ret, nil
|
|
}
|
|
|
|
func (p *Process) PpidWithContext(ctx context.Context) (int32, error) {
|
|
k, err := p.getKProc()
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
|
|
return k.Ppid, nil
|
|
}
|
|
|
|
func (p *Process) NameWithContext(ctx context.Context) (string, error) {
|
|
k, err := p.getKProc()
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
name := common.IntToString(k.Comm[:])
|
|
|
|
if len(name) >= 15 {
|
|
cmdlineSlice, err := p.CmdlineSliceWithContext(ctx)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
if len(cmdlineSlice) > 0 {
|
|
extendedName := filepath.Base(cmdlineSlice[0])
|
|
if strings.HasPrefix(extendedName, p.name) {
|
|
name = extendedName
|
|
}
|
|
}
|
|
}
|
|
|
|
return name, nil
|
|
}
|
|
|
|
func (p *Process) CwdWithContext(ctx context.Context) (string, error) {
|
|
return "", common.ErrNotImplementedError
|
|
}
|
|
|
|
func (p *Process) ExeWithContext(ctx context.Context) (string, error) {
|
|
mib := []int32{CTLKern, KernProc, KernProcPathname, p.Pid}
|
|
buf, _, err := common.CallSyscall(mib)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
return strings.Trim(string(buf), "\x00"), nil
|
|
}
|
|
|
|
func (p *Process) CmdlineWithContext(ctx context.Context) (string, error) {
|
|
mib := []int32{CTLKern, KernProc, KernProcArgs, p.Pid}
|
|
buf, _, err := common.CallSyscall(mib)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
ret := strings.FieldsFunc(string(buf), func(r rune) bool {
|
|
return r == '\u0000'
|
|
})
|
|
|
|
return strings.Join(ret, " "), nil
|
|
}
|
|
|
|
func (p *Process) CmdlineSliceWithContext(ctx context.Context) ([]string, error) {
|
|
mib := []int32{CTLKern, KernProc, KernProcArgs, p.Pid}
|
|
buf, _, err := common.CallSyscall(mib)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if len(buf) == 0 {
|
|
return nil, nil
|
|
}
|
|
if buf[len(buf)-1] == 0 {
|
|
buf = buf[:len(buf)-1]
|
|
}
|
|
parts := bytes.Split(buf, []byte{0})
|
|
var strParts []string
|
|
for _, p := range parts {
|
|
strParts = append(strParts, string(p))
|
|
}
|
|
|
|
return strParts, nil
|
|
}
|
|
|
|
func (p *Process) createTimeWithContext(ctx context.Context) (int64, error) {
|
|
k, err := p.getKProc()
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
return int64(k.Start.Sec)*1000 + int64(k.Start.Usec)/1000, nil
|
|
}
|
|
|
|
func (p *Process) StatusWithContext(ctx context.Context) ([]string, error) {
|
|
k, err := p.getKProc()
|
|
if err != nil {
|
|
return []string{""}, err
|
|
}
|
|
var s string
|
|
switch k.Stat {
|
|
case SIDL:
|
|
s = Idle
|
|
case SRUN:
|
|
s = Running
|
|
case SSLEEP:
|
|
s = Sleep
|
|
case SSTOP:
|
|
s = Stop
|
|
case SZOMB:
|
|
s = Zombie
|
|
case SWAIT:
|
|
s = Wait
|
|
case SLOCK:
|
|
s = Lock
|
|
}
|
|
|
|
return []string{s}, nil
|
|
}
|
|
|
|
func (p *Process) ForegroundWithContext(ctx context.Context) (bool, error) {
|
|
// see https://github.com/shirou/gopsutil/issues/596#issuecomment-432707831 for implementation details
|
|
pid := p.Pid
|
|
out, err := invoke.CommandWithContext(ctx, "ps", "-o", "stat=", "-p", strconv.Itoa(int(pid)))
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
return strings.IndexByte(string(out), '+') != -1, nil
|
|
}
|
|
|
|
func (p *Process) UidsWithContext(ctx context.Context) ([]uint32, error) {
|
|
k, err := p.getKProc()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
uids := make([]uint32, 0, 3)
|
|
|
|
uids = append(uids, uint32(k.Ruid), uint32(k.Uid), uint32(k.Svuid))
|
|
|
|
return uids, nil
|
|
}
|
|
|
|
func (p *Process) GidsWithContext(ctx context.Context) ([]uint32, error) {
|
|
k, err := p.getKProc()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
gids := make([]uint32, 0, 3)
|
|
gids = append(gids, uint32(k.Rgid), uint32(k.Ngroups), uint32(k.Svgid))
|
|
|
|
return gids, nil
|
|
}
|
|
|
|
func (p *Process) GroupsWithContext(ctx context.Context) ([]uint32, error) {
|
|
k, err := p.getKProc()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
groups := make([]uint32, k.Ngroups)
|
|
for i := int16(0); i < k.Ngroups; i++ {
|
|
groups[i] = uint32(k.Groups[i])
|
|
}
|
|
|
|
return groups, nil
|
|
}
|
|
|
|
func (p *Process) TerminalWithContext(ctx context.Context) (string, error) {
|
|
k, err := p.getKProc()
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
ttyNr := uint64(k.Tdev)
|
|
|
|
termmap, err := getTerminalMap()
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
return termmap[ttyNr], nil
|
|
}
|
|
|
|
func (p *Process) NiceWithContext(ctx context.Context) (int32, error) {
|
|
k, err := p.getKProc()
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
return int32(k.Nice), nil
|
|
}
|
|
|
|
func (p *Process) IOCountersWithContext(ctx context.Context) (*IOCountersStat, error) {
|
|
k, err := p.getKProc()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return &IOCountersStat{
|
|
ReadCount: uint64(k.Rusage.Inblock),
|
|
WriteCount: uint64(k.Rusage.Oublock),
|
|
}, nil
|
|
}
|
|
|
|
func (p *Process) NumThreadsWithContext(ctx context.Context) (int32, error) {
|
|
k, err := p.getKProc()
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
|
|
return k.Numthreads, nil
|
|
}
|
|
|
|
func (p *Process) TimesWithContext(ctx context.Context) (*cpu.TimesStat, error) {
|
|
k, err := p.getKProc()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return &cpu.TimesStat{
|
|
CPU: "cpu",
|
|
User: float64(k.Rusage.Utime.Sec) + float64(k.Rusage.Utime.Usec)/1000000,
|
|
System: float64(k.Rusage.Stime.Sec) + float64(k.Rusage.Stime.Usec)/1000000,
|
|
}, nil
|
|
}
|
|
|
|
func (p *Process) MemoryInfoWithContext(ctx context.Context) (*MemoryInfoStat, error) {
|
|
k, err := p.getKProc()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
v, err := unix.Sysctl("vm.stats.vm.v_page_size")
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
pageSize := common.LittleEndian.Uint16([]byte(v))
|
|
|
|
return &MemoryInfoStat{
|
|
RSS: uint64(k.Rssize) * uint64(pageSize),
|
|
VMS: uint64(k.Size),
|
|
}, nil
|
|
}
|
|
|
|
func (p *Process) ChildrenWithContext(ctx context.Context) ([]*Process, error) {
|
|
procs, err := ProcessesWithContext(ctx)
|
|
if err != nil {
|
|
return nil, nil
|
|
}
|
|
ret := make([]*Process, 0, len(procs))
|
|
for _, proc := range procs {
|
|
ppid, err := proc.PpidWithContext(ctx)
|
|
if err != nil {
|
|
continue
|
|
}
|
|
if ppid == p.Pid {
|
|
ret = append(ret, proc)
|
|
}
|
|
}
|
|
sort.Slice(ret, func(i, j int) bool { return ret[i].Pid < ret[j].Pid })
|
|
return ret, nil
|
|
}
|
|
|
|
func (p *Process) ConnectionsWithContext(ctx context.Context) ([]net.ConnectionStat, error) {
|
|
return net.ConnectionsPidWithContext(ctx, "all", p.Pid)
|
|
}
|
|
|
|
func (p *Process) ConnectionsMaxWithContext(ctx context.Context, maxConn int) ([]net.ConnectionStat, error) {
|
|
return net.ConnectionsPidMaxWithContext(ctx, "all", p.Pid, maxConn)
|
|
}
|
|
|
|
func ProcessesWithContext(ctx context.Context) ([]*Process, error) {
|
|
results := []*Process{}
|
|
|
|
mib := []int32{CTLKern, KernProc, KernProcProc, 0}
|
|
buf, length, err := common.CallSyscall(mib)
|
|
if err != nil {
|
|
return results, err
|
|
}
|
|
|
|
// get kinfo_proc size
|
|
count := int(length / uint64(sizeOfKinfoProc))
|
|
|
|
// parse buf to procs
|
|
for i := 0; i < count; i++ {
|
|
b := buf[i*sizeOfKinfoProc : (i+1)*sizeOfKinfoProc]
|
|
k, err := parseKinfoProc(b)
|
|
if err != nil {
|
|
continue
|
|
}
|
|
p, err := NewProcessWithContext(ctx, int32(k.Pid))
|
|
if err != nil {
|
|
continue
|
|
}
|
|
|
|
results = append(results, p)
|
|
}
|
|
|
|
return results, nil
|
|
}
|
|
|
|
func (p *Process) getKProc() (*KinfoProc, error) {
|
|
mib := []int32{CTLKern, KernProc, KernProcPID, p.Pid}
|
|
|
|
buf, length, err := common.CallSyscall(mib)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if length != sizeOfKinfoProc {
|
|
return nil, errors.New("unexpected size of KinfoProc")
|
|
}
|
|
|
|
k, err := parseKinfoProc(buf)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return &k, nil
|
|
}
|