// SPDX-License-Identifier: BSD-3-Clause package process import ( "context" "encoding/json" "errors" "runtime" "sort" "sync" "time" "github.com/shirou/gopsutil/v4/cpu" "github.com/shirou/gopsutil/v4/internal/common" "github.com/shirou/gopsutil/v4/mem" "github.com/shirou/gopsutil/v4/net" ) var ( invoke common.Invoker = common.Invoke{} ErrorNoChildren = errors.New("process does not have children") // Deprecated: ErrorNoChildren is never returned by process.Children(), check its returned []*Process slice length instead ErrorProcessNotRunning = errors.New("process does not exist") ErrorNotPermitted = errors.New("operation not permitted") ) type Process struct { Pid int32 `json:"pid"` name string status string parent int32 parentMutex sync.RWMutex // for windows ppid cache numCtxSwitches *NumCtxSwitchesStat uids []uint32 gids []uint32 groups []uint32 numThreads int32 memInfo *MemoryInfoStat sigInfo *SignalInfoStat createTime int64 lastCPUTimes *cpu.TimesStat lastCPUTime time.Time tgid int32 } // Process status const ( // Running marks a task a running or runnable (on the run queue) Running = "running" // Blocked marks a task waiting on a short, uninterruptible operation (usually I/O) Blocked = "blocked" // Idle marks a task sleeping for more than about 20 seconds Idle = "idle" // Lock marks a task waiting to acquire a lock Lock = "lock" // Sleep marks task waiting for short, interruptible operation Sleep = "sleep" // Stop marks a stopped process Stop = "stop" // Wait marks an idle interrupt thread (or paging in pre 2.6.xx Linux) Wait = "wait" // Zombie marks a defunct process, terminated but not reaped by its parent Zombie = "zombie" // Solaris states. See https://github.com/collectd/collectd/blob/1da3305c10c8ff9a63081284cf3d4bb0f6daffd8/src/processes.c#L2115 Daemon = "daemon" Detached = "detached" System = "system" Orphan = "orphan" UnknownState = "" ) type OpenFilesStat struct { Path string `json:"path"` Fd uint64 `json:"fd"` } type MemoryInfoStat struct { RSS uint64 `json:"rss"` // bytes VMS uint64 `json:"vms"` // bytes HWM uint64 `json:"hwm"` // bytes Data uint64 `json:"data"` // bytes Stack uint64 `json:"stack"` // bytes Locked uint64 `json:"locked"` // bytes Swap uint64 `json:"swap"` // bytes } type SignalInfoStat struct { PendingProcess uint64 `json:"pending_process"` PendingThread uint64 `json:"pending_thread"` Blocked uint64 `json:"blocked"` Ignored uint64 `json:"ignored"` Caught uint64 `json:"caught"` } type RlimitStat struct { Resource int32 `json:"resource"` Soft uint64 `json:"soft"` Hard uint64 `json:"hard"` Used uint64 `json:"used"` } type IOCountersStat struct { // ReadCount is a number of read I/O operations such as syscalls. ReadCount uint64 `json:"readCount"` // WriteCount is a number of read I/O operations such as syscalls. WriteCount uint64 `json:"writeCount"` // ReadBytes is a number of all I/O read in bytes. This includes disk I/O on Linux and Windows. ReadBytes uint64 `json:"readBytes"` // WriteBytes is a number of all I/O write in bytes. This includes disk I/O on Linux and Windows. WriteBytes uint64 `json:"writeBytes"` // DiskReadBytes is a number of disk I/O write in bytes. Currently only Linux has this value. DiskReadBytes uint64 `json:"diskReadBytes"` // DiskWriteBytes is a number of disk I/O read in bytes. Currently only Linux has this value. DiskWriteBytes uint64 `json:"diskWriteBytes"` } type NumCtxSwitchesStat struct { Voluntary int64 `json:"voluntary"` Involuntary int64 `json:"involuntary"` } type PageFaultsStat struct { MinorFaults uint64 `json:"minorFaults"` MajorFaults uint64 `json:"majorFaults"` ChildMinorFaults uint64 `json:"childMinorFaults"` ChildMajorFaults uint64 `json:"childMajorFaults"` } // Resource limit constants are from /usr/include/x86_64-linux-gnu/bits/resource.h // from libc6-dev package in Ubuntu 16.10 const ( RLIMIT_CPU int32 = 0 RLIMIT_FSIZE int32 = 1 RLIMIT_DATA int32 = 2 RLIMIT_STACK int32 = 3 RLIMIT_CORE int32 = 4 RLIMIT_RSS int32 = 5 RLIMIT_NPROC int32 = 6 RLIMIT_NOFILE int32 = 7 RLIMIT_MEMLOCK int32 = 8 RLIMIT_AS int32 = 9 RLIMIT_LOCKS int32 = 10 RLIMIT_SIGPENDING int32 = 11 RLIMIT_MSGQUEUE int32 = 12 RLIMIT_NICE int32 = 13 RLIMIT_RTPRIO int32 = 14 RLIMIT_RTTIME int32 = 15 ) func (p Process) String() string { s, _ := json.Marshal(p) return string(s) } func (o OpenFilesStat) String() string { s, _ := json.Marshal(o) return string(s) } func (m MemoryInfoStat) String() string { s, _ := json.Marshal(m) return string(s) } func (r RlimitStat) String() string { s, _ := json.Marshal(r) return string(s) } func (i IOCountersStat) String() string { s, _ := json.Marshal(i) return string(s) } func (p NumCtxSwitchesStat) String() string { s, _ := json.Marshal(p) return string(s) } var enableBootTimeCache bool // EnableBootTimeCache change cache behavior of BootTime. If true, cache BootTime value. Default is false. func EnableBootTimeCache(enable bool) { enableBootTimeCache = enable } // Pids returns a slice of process ID list which are running now. func Pids() ([]int32, error) { return PidsWithContext(context.Background()) } func PidsWithContext(ctx context.Context) ([]int32, error) { pids, err := pidsWithContext(ctx) sort.Slice(pids, func(i, j int) bool { return pids[i] < pids[j] }) return pids, err } // Processes returns a slice of pointers to Process structs for all // currently running processes. func Processes() ([]*Process, error) { return ProcessesWithContext(context.Background()) } // NewProcess creates a new Process instance, it only stores the pid and // checks that the process exists. Other method on Process can be used // to get more information about the process. An error will be returned // if the process does not exist. func NewProcess(pid int32) (*Process, error) { return NewProcessWithContext(context.Background(), pid) } func NewProcessWithContext(ctx context.Context, pid int32) (*Process, error) { p := &Process{ Pid: pid, } exists, err := PidExistsWithContext(ctx, pid) if err != nil { return p, err } if !exists { return p, ErrorProcessNotRunning } p.CreateTimeWithContext(ctx) return p, nil } func PidExists(pid int32) (bool, error) { return PidExistsWithContext(context.Background(), pid) } // Background returns true if the process is in background, false otherwise. func (p *Process) Background() (bool, error) { return p.BackgroundWithContext(context.Background()) } func (p *Process) BackgroundWithContext(ctx context.Context) (bool, error) { fg, err := p.ForegroundWithContext(ctx) if err != nil { return false, err } return !fg, err } // If interval is 0, return difference from last call(non-blocking). // If interval > 0, wait interval sec and return difference between start and end. func (p *Process) Percent(interval time.Duration) (float64, error) { return p.PercentWithContext(context.Background(), interval) } func (p *Process) PercentWithContext(ctx context.Context, interval time.Duration) (float64, error) { cpuTimes, err := p.TimesWithContext(ctx) if err != nil { return 0, err } now := time.Now() if interval > 0 { p.lastCPUTimes = cpuTimes p.lastCPUTime = now if err := common.Sleep(ctx, interval); err != nil { return 0, err } cpuTimes, err = p.TimesWithContext(ctx) now = time.Now() if err != nil { return 0, err } } else if p.lastCPUTimes == nil { // invoked first time p.lastCPUTimes = cpuTimes p.lastCPUTime = now return 0, nil } numcpu := runtime.NumCPU() delta := (now.Sub(p.lastCPUTime).Seconds()) * float64(numcpu) ret := calculatePercent(p.lastCPUTimes, cpuTimes, delta, numcpu) p.lastCPUTimes = cpuTimes p.lastCPUTime = now return ret, nil } // IsRunning returns whether the process is still running or not. func (p *Process) IsRunning() (bool, error) { return p.IsRunningWithContext(context.Background()) } func (p *Process) IsRunningWithContext(ctx context.Context) (bool, error) { createTime, err := p.CreateTimeWithContext(ctx) if err != nil { return false, err } p2, err := NewProcessWithContext(ctx, p.Pid) if errors.Is(err, ErrorProcessNotRunning) { return false, nil } createTime2, err := p2.CreateTimeWithContext(ctx) if err != nil { return false, err } return createTime == createTime2, nil } // CreateTime returns created time of the process in milliseconds since the epoch, in UTC. func (p *Process) CreateTime() (int64, error) { return p.CreateTimeWithContext(context.Background()) } func (p *Process) CreateTimeWithContext(ctx context.Context) (int64, error) { if p.createTime != 0 { return p.createTime, nil } createTime, err := p.createTimeWithContext(ctx) p.createTime = createTime return p.createTime, err } func calculatePercent(t1, t2 *cpu.TimesStat, delta float64, numcpu int) float64 { if delta == 0 { return 0 } // https://github.com/giampaolo/psutil/blob/c034e6692cf736b5e87d14418a8153bb03f6cf42/psutil/__init__.py#L1064 deltaProc := (t2.User - t1.User) + (t2.System - t1.System) if deltaProc <= 0 { return 0 } overallPercent := ((deltaProc / delta) * 100) * float64(numcpu) return overallPercent } // MemoryPercent returns how many percent of the total RAM this process uses func (p *Process) MemoryPercent() (float32, error) { return p.MemoryPercentWithContext(context.Background()) } func (p *Process) MemoryPercentWithContext(ctx context.Context) (float32, error) { machineMemory, err := mem.VirtualMemoryWithContext(ctx) if err != nil { return 0, err } total := machineMemory.Total processMemory, err := p.MemoryInfoWithContext(ctx) if err != nil { return 0, err } used := processMemory.RSS return (100 * float32(used) / float32(total)), nil } // CPUPercent returns how many percent of the CPU time this process uses func (p *Process) CPUPercent() (float64, error) { return p.CPUPercentWithContext(context.Background()) } func (p *Process) CPUPercentWithContext(ctx context.Context) (float64, error) { createTime, err := p.createTimeWithContext(ctx) if err != nil { return 0, err } cput, err := p.TimesWithContext(ctx) if err != nil { return 0, err } created := time.Unix(0, createTime*int64(time.Millisecond)) totalTime := time.Since(created).Seconds() if totalTime <= 0 { return 0, nil } return 100 * cput.Total() / totalTime, nil } // Groups returns all group IDs(include supplementary groups) of the process as a slice of the int func (p *Process) Groups() ([]uint32, error) { return p.GroupsWithContext(context.Background()) } // Ppid returns Parent Process ID of the process. func (p *Process) Ppid() (int32, error) { return p.PpidWithContext(context.Background()) } // Name returns name of the process. func (p *Process) Name() (string, error) { return p.NameWithContext(context.Background()) } // Exe returns executable path of the process. func (p *Process) Exe() (string, error) { return p.ExeWithContext(context.Background()) } // Cmdline returns the command line arguments of the process as a string with // each argument separated by 0x20 ascii character. func (p *Process) Cmdline() (string, error) { return p.CmdlineWithContext(context.Background()) } // CmdlineSlice returns the command line arguments of the process as a slice with each // element being an argument. // // On Windows, this assumes the command line is encoded according to the convention accepted by // [golang.org/x/sys/windows.CmdlineToArgv] (the most common convention). If this is not suitable, // you should instead use [Process.Cmdline] and parse the command line according to your specific // requirements. func (p *Process) CmdlineSlice() ([]string, error) { return p.CmdlineSliceWithContext(context.Background()) } // Cwd returns current working directory of the process. func (p *Process) Cwd() (string, error) { return p.CwdWithContext(context.Background()) } // Parent returns parent Process of the process. func (p *Process) Parent() (*Process, error) { return p.ParentWithContext(context.Background()) } // ParentWithContext returns parent Process of the process. func (p *Process) ParentWithContext(ctx context.Context) (*Process, error) { ppid, err := p.PpidWithContext(ctx) if err != nil { return nil, err } return NewProcessWithContext(ctx, ppid) } // Status returns the process status. // Return value could be one of these. // R: Running S: Sleep T: Stop I: Idle // Z: Zombie W: Wait L: Lock // The character is same within all supported platforms. func (p *Process) Status() ([]string, error) { return p.StatusWithContext(context.Background()) } // Foreground returns true if the process is in foreground, false otherwise. func (p *Process) Foreground() (bool, error) { return p.ForegroundWithContext(context.Background()) } // Uids returns user ids of the process as a slice of the int func (p *Process) Uids() ([]uint32, error) { return p.UidsWithContext(context.Background()) } // Gids returns group ids of the process as a slice of the int func (p *Process) Gids() ([]uint32, error) { return p.GidsWithContext(context.Background()) } // Terminal returns a terminal which is associated with the process. func (p *Process) Terminal() (string, error) { return p.TerminalWithContext(context.Background()) } // Nice returns a nice value (priority). func (p *Process) Nice() (int32, error) { return p.NiceWithContext(context.Background()) } // IOnice returns process I/O nice value (priority). func (p *Process) IOnice() (int32, error) { return p.IOniceWithContext(context.Background()) } // Rlimit returns Resource Limits. func (p *Process) Rlimit() ([]RlimitStat, error) { return p.RlimitWithContext(context.Background()) } // RlimitUsage returns Resource Limits. // If gatherUsed is true, the currently used value will be gathered and added // to the resulting RlimitStat. func (p *Process) RlimitUsage(gatherUsed bool) ([]RlimitStat, error) { return p.RlimitUsageWithContext(context.Background(), gatherUsed) } // IOCounters returns IO Counters. func (p *Process) IOCounters() (*IOCountersStat, error) { return p.IOCountersWithContext(context.Background()) } // NumCtxSwitches returns the number of the context switches of the process. func (p *Process) NumCtxSwitches() (*NumCtxSwitchesStat, error) { return p.NumCtxSwitchesWithContext(context.Background()) } // NumFDs returns the number of File Descriptors used by the process. func (p *Process) NumFDs() (int32, error) { return p.NumFDsWithContext(context.Background()) } // NumThreads returns the number of threads used by the process. func (p *Process) NumThreads() (int32, error) { return p.NumThreadsWithContext(context.Background()) } func (p *Process) Threads() (map[int32]*cpu.TimesStat, error) { return p.ThreadsWithContext(context.Background()) } // Times returns CPU times of the process. func (p *Process) Times() (*cpu.TimesStat, error) { return p.TimesWithContext(context.Background()) } // CPUAffinity returns CPU affinity of the process. func (p *Process) CPUAffinity() ([]int32, error) { return p.CPUAffinityWithContext(context.Background()) } // MemoryInfo returns generic process memory information, // such as RSS and VMS. func (p *Process) MemoryInfo() (*MemoryInfoStat, error) { return p.MemoryInfoWithContext(context.Background()) } // MemoryInfoEx returns platform-specific process memory information. func (p *Process) MemoryInfoEx() (*MemoryInfoExStat, error) { return p.MemoryInfoExWithContext(context.Background()) } // PageFaults returns the process's page fault counters. func (p *Process) PageFaults() (*PageFaultsStat, error) { return p.PageFaultsWithContext(context.Background()) } // Children returns the children of the process represented as a slice // of pointers to Process type. func (p *Process) Children() ([]*Process, error) { return p.ChildrenWithContext(context.Background()) } // OpenFiles returns a slice of OpenFilesStat opend by the process. // OpenFilesStat includes a file path and file descriptor. func (p *Process) OpenFiles() ([]OpenFilesStat, error) { return p.OpenFilesWithContext(context.Background()) } // Connections returns a slice of net.ConnectionStat used by the process. // This returns all kind of the connection. This means TCP, UDP or UNIX. func (p *Process) Connections() ([]net.ConnectionStat, error) { return p.ConnectionsWithContext(context.Background()) } // ConnectionsMax returns a slice of net.ConnectionStat used by the process at most `max`. func (p *Process) ConnectionsMax(maxConn int) ([]net.ConnectionStat, error) { return p.ConnectionsMaxWithContext(context.Background(), maxConn) } // MemoryMaps get memory maps from /proc/(pid)/smaps func (p *Process) MemoryMaps(grouped bool) (*[]MemoryMapsStat, error) { return p.MemoryMapsWithContext(context.Background(), grouped) } // Tgid returns thread group id of the process. func (p *Process) Tgid() (int32, error) { return p.TgidWithContext(context.Background()) } // SendSignal sends a unix.Signal to the process. func (p *Process) SendSignal(sig Signal) error { return p.SendSignalWithContext(context.Background(), sig) } // Suspend sends SIGSTOP to the process. func (p *Process) Suspend() error { return p.SuspendWithContext(context.Background()) } // Resume sends SIGCONT to the process. func (p *Process) Resume() error { return p.ResumeWithContext(context.Background()) } // Terminate sends SIGTERM to the process. func (p *Process) Terminate() error { return p.TerminateWithContext(context.Background()) } // Kill sends SIGKILL to the process. func (p *Process) Kill() error { return p.KillWithContext(context.Background()) } // Username returns a username of the process. func (p *Process) Username() (string, error) { return p.UsernameWithContext(context.Background()) } // Environ returns the environment variables of the process. func (p *Process) Environ() ([]string, error) { return p.EnvironWithContext(context.Background()) } // convertStatusChar as reported by the ps command across different platforms. func convertStatusChar(letter string) string { // Sources // Darwin: http://www.mywebuniversity.com/Man_Pages/Darwin/man_ps.html // FreeBSD: https://www.freebsd.org/cgi/man.cgi?ps // Linux https://man7.org/linux/man-pages/man1/ps.1.html // OpenBSD: https://man.openbsd.org/ps.1#state // Solaris: https://github.com/collectd/collectd/blob/1da3305c10c8ff9a63081284cf3d4bb0f6daffd8/src/processes.c#L2115 switch letter { case "A": return Daemon case "D", "U": return Blocked case "E": return Detached case "I": return Idle case "L": return Lock case "O": return Orphan case "R": return Running case "S": return Sleep case "T", "t": // "t" is used by Linux to signal stopped by the debugger during tracing return Stop case "W": return Wait case "Y": return System case "Z": return Zombie default: return UnknownState } }
// SPDX-License-Identifier: BSD-3-Clause //go:build linux package process import ( "bufio" "bytes" "context" "encoding/json" "fmt" "math" "os" "path/filepath" "sort" "strconv" "strings" "github.com/tklauser/go-sysconf" "golang.org/x/sys/unix" "github.com/shirou/gopsutil/v4/cpu" "github.com/shirou/gopsutil/v4/internal/common" "github.com/shirou/gopsutil/v4/net" ) var pageSize = uint64(os.Getpagesize()) const prioProcess = 0 // linux/resource.h var clockTicks = 100 // default value func init() { clkTck, err := sysconf.Sysconf(sysconf.SC_CLK_TCK) // ignore errors if err == nil { clockTicks = int(clkTck) } } // MemoryInfoExStat is different between OSes type MemoryInfoExStat struct { RSS uint64 `json:"rss"` // bytes VMS uint64 `json:"vms"` // bytes Shared uint64 `json:"shared"` // bytes Text uint64 `json:"text"` // bytes Lib uint64 `json:"lib"` // bytes Data uint64 `json:"data"` // bytes Dirty uint64 `json:"dirty"` // bytes } func (m MemoryInfoExStat) String() string { s, _ := json.Marshal(m) return string(s) } type MemoryMapsStat struct { Path string `json:"path"` Rss uint64 `json:"rss"` Size uint64 `json:"size"` Pss uint64 `json:"pss"` SharedClean uint64 `json:"sharedClean"` SharedDirty uint64 `json:"sharedDirty"` PrivateClean uint64 `json:"privateClean"` PrivateDirty uint64 `json:"privateDirty"` Referenced uint64 `json:"referenced"` Anonymous uint64 `json:"anonymous"` Swap uint64 `json:"swap"` } // String returns JSON value of the process. func (m MemoryMapsStat) String() string { s, _ := json.Marshal(m) return string(s) } func (p *Process) PpidWithContext(ctx context.Context) (int32, error) { _, ppid, _, _, _, _, _, err := p.fillFromStatWithContext(ctx) if err != nil { return -1, err } return ppid, nil } func (p *Process) NameWithContext(ctx context.Context) (string, error) { if p.name == "" { if err := p.fillNameWithContext(ctx); err != nil { return "", err } } return p.name, nil } func (p *Process) TgidWithContext(ctx context.Context) (int32, error) { if p.tgid == 0 { if err := p.fillFromStatusWithContext(ctx); err != nil { return 0, err } } return p.tgid, nil } func (p *Process) ExeWithContext(ctx context.Context) (string, error) { return p.fillFromExeWithContext(ctx) } func (p *Process) CmdlineWithContext(ctx context.Context) (string, error) { return p.fillFromCmdlineWithContext(ctx) } func (p *Process) CmdlineSliceWithContext(ctx context.Context) ([]string, error) { return p.fillSliceFromCmdlineWithContext(ctx) } func (p *Process) createTimeWithContext(ctx context.Context) (int64, error) { _, _, _, createTime, _, _, _, err := p.fillFromStatWithContext(ctx) if err != nil { return 0, err } return createTime, nil } func (p *Process) CwdWithContext(ctx context.Context) (string, error) { return p.fillFromCwdWithContext(ctx) } func (p *Process) StatusWithContext(ctx context.Context) ([]string, error) { err := p.fillFromStatusWithContext(ctx) if err != nil { return []string{""}, err } return []string{p.status}, 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 statPath := common.HostProcWithContext(ctx, strconv.Itoa(int(pid)), "stat") contents, err := os.ReadFile(statPath) if err != nil { return false, err } fields := strings.Fields(string(contents)) if len(fields) < 8 { return false, fmt.Errorf("insufficient data in %s", statPath) } pgid := fields[4] tpgid := fields[7] return pgid == tpgid, nil } func (p *Process) UidsWithContext(ctx context.Context) ([]uint32, error) { err := p.fillFromStatusWithContext(ctx) if err != nil { return []uint32{}, err } return p.uids, nil } func (p *Process) GidsWithContext(ctx context.Context) ([]uint32, error) { err := p.fillFromStatusWithContext(ctx) if err != nil { return []uint32{}, err } return p.gids, nil } func (p *Process) GroupsWithContext(ctx context.Context) ([]uint32, error) { err := p.fillFromStatusWithContext(ctx) if err != nil { return []uint32{}, err } return p.groups, nil } func (p *Process) TerminalWithContext(ctx context.Context) (string, error) { t, _, _, _, _, _, _, err := p.fillFromStatWithContext(ctx) if err != nil { return "", err } termmap, err := getTerminalMap() if err != nil { return "", err } terminal := termmap[t] return terminal, nil } func (p *Process) NiceWithContext(ctx context.Context) (int32, error) { _, _, _, _, _, nice, _, err := p.fillFromStatWithContext(ctx) if err != nil { return 0, err } return nice, nil } func (*Process) IOniceWithContext(_ context.Context) (int32, error) { return 0, common.ErrNotImplementedError } func (p *Process) RlimitWithContext(ctx context.Context) ([]RlimitStat, error) { return p.RlimitUsageWithContext(ctx, false) } func (p *Process) RlimitUsageWithContext(ctx context.Context, gatherUsed bool) ([]RlimitStat, error) { rlimits, err := p.fillFromLimitsWithContext(ctx) if !gatherUsed || err != nil { return rlimits, err } _, _, _, _, rtprio, nice, _, err := p.fillFromStatWithContext(ctx) if err != nil { return nil, err } if err := p.fillFromStatusWithContext(ctx); err != nil { return nil, err } for i := range rlimits { rs := &rlimits[i] switch rs.Resource { case RLIMIT_CPU: times, err := p.TimesWithContext(ctx) if err != nil { return nil, err } rs.Used = uint64(times.User + times.System) case RLIMIT_DATA: rs.Used = uint64(p.memInfo.Data) case RLIMIT_STACK: rs.Used = uint64(p.memInfo.Stack) case RLIMIT_RSS: rs.Used = uint64(p.memInfo.RSS) case RLIMIT_NOFILE: n, err := p.NumFDsWithContext(ctx) if err != nil { return nil, err } rs.Used = uint64(n) case RLIMIT_MEMLOCK: rs.Used = uint64(p.memInfo.Locked) case RLIMIT_AS: rs.Used = uint64(p.memInfo.VMS) case RLIMIT_LOCKS: // TODO we can get the used value from /proc/$pid/locks. But linux doesn't enforce it, so not a high priority. case RLIMIT_SIGPENDING: rs.Used = p.sigInfo.PendingProcess case RLIMIT_NICE: // The rlimit for nice is a little unusual, in that 0 means the niceness cannot be decreased beyond the current value, but it can be increased. // So effectively: if rs.Soft == 0 { rs.Soft = rs.Used } rs.Used = uint64(nice) case RLIMIT_RTPRIO: rs.Used = uint64(rtprio) } } return rlimits, err } func (p *Process) IOCountersWithContext(ctx context.Context) (*IOCountersStat, error) { return p.fillFromIOWithContext(ctx) } func (p *Process) NumCtxSwitchesWithContext(ctx context.Context) (*NumCtxSwitchesStat, error) { err := p.fillFromStatusWithContext(ctx) if err != nil { return nil, err } return p.numCtxSwitches, nil } func (p *Process) NumFDsWithContext(ctx context.Context) (int32, error) { _, fnames, err := p.fillFromfdListWithContext(ctx) return int32(len(fnames)), err } func (p *Process) NumThreadsWithContext(ctx context.Context) (int32, error) { err := p.fillFromStatusWithContext(ctx) if err != nil { return 0, err } return p.numThreads, nil } func (p *Process) ThreadsWithContext(ctx context.Context) (map[int32]*cpu.TimesStat, error) { ret := make(map[int32]*cpu.TimesStat) taskPath := common.HostProcWithContext(ctx, strconv.Itoa(int(p.Pid)), "task") tids, err := readPidsFromDir(taskPath) if err != nil { return nil, err } for _, tid := range tids { _, _, cpuTimes, _, _, _, _, err := p.fillFromTIDStatWithContext(ctx, tid) if err != nil { return nil, err } ret[tid] = cpuTimes } return ret, nil } func (p *Process) TimesWithContext(ctx context.Context) (*cpu.TimesStat, error) { _, _, cpuTimes, _, _, _, _, err := p.fillFromStatWithContext(ctx) if err != nil { return nil, err } return cpuTimes, nil } func (*Process) CPUAffinityWithContext(_ context.Context) ([]int32, error) { return nil, common.ErrNotImplementedError } func (p *Process) MemoryInfoWithContext(ctx context.Context) (*MemoryInfoStat, error) { meminfo, _, err := p.fillFromStatmWithContext(ctx) if err != nil { return nil, err } return meminfo, nil } func (p *Process) MemoryInfoExWithContext(ctx context.Context) (*MemoryInfoExStat, error) { _, memInfoEx, err := p.fillFromStatmWithContext(ctx) if err != nil { return nil, err } return memInfoEx, nil } func (p *Process) PageFaultsWithContext(ctx context.Context) (*PageFaultsStat, error) { _, _, _, _, _, _, pageFaults, err := p.fillFromStatWithContext(ctx) if err != nil { return nil, err } return pageFaults, nil } func (p *Process) ChildrenWithContext(ctx context.Context) ([]*Process, error) { statFiles, err := filepath.Glob(common.HostProcWithContext(ctx, "[0-9]*/stat")) if err != nil { return nil, err } ret := make([]*Process, 0, len(statFiles)) for _, statFile := range statFiles { statContents, err := os.ReadFile(statFile) if err != nil { continue } fields := splitProcStat(statContents) pid, err := strconv.ParseInt(fields[1], 10, 32) if err != nil { continue } ppid, err := strconv.ParseInt(fields[4], 10, 32) if err != nil { continue } if ppid == int64(p.Pid) { np, err := NewProcessWithContext(ctx, int32(pid)) if err != nil { continue } ret = append(ret, np) } } sort.Slice(ret, func(i, j int) bool { return ret[i].Pid < ret[j].Pid }) return ret, nil } func (p *Process) OpenFilesWithContext(ctx context.Context) ([]OpenFilesStat, error) { _, ofs, err := p.fillFromfdWithContext(ctx) return ofs, err } 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 (p *Process) MemoryMapsWithContext(ctx context.Context, grouped bool) (*[]MemoryMapsStat, error) { pid := p.Pid var ret []MemoryMapsStat smapsPath := common.HostProcWithContext(ctx, strconv.Itoa(int(pid)), "smaps") if grouped { ret = make([]MemoryMapsStat, 1) // If smaps_rollup exists (require kernel >= 4.15), then we will use it // for pre-summed memory information for a process. smapsRollupPath := common.HostProcWithContext(ctx, strconv.Itoa(int(pid)), "smaps_rollup") if _, err := os.Stat(smapsRollupPath); !os.IsNotExist(err) { smapsPath = smapsRollupPath } } contents, err := os.ReadFile(smapsPath) if err != nil { return nil, err } lines := strings.Split(string(contents), "\n") // function of parsing a block getBlock := func(firstLine []string, block []string) (MemoryMapsStat, error) { m := MemoryMapsStat{} if len(firstLine) >= 6 { m.Path = strings.Join(firstLine[5:], " ") } for _, line := range block { if strings.Contains(line, "VmFlags") { continue } field := strings.Split(line, ":") if len(field) < 2 { continue } v := strings.Trim(field[1], "kB") // remove last "kB" v = strings.TrimSpace(v) t, err := strconv.ParseUint(v, 10, 64) if err != nil { return m, err } switch field[0] { case "Size": m.Size = t case "Rss": m.Rss = t case "Pss": m.Pss = t case "Shared_Clean": m.SharedClean = t case "Shared_Dirty": m.SharedDirty = t case "Private_Clean": m.PrivateClean = t case "Private_Dirty": m.PrivateDirty = t case "Referenced": m.Referenced = t case "Anonymous": m.Anonymous = t case "Swap": m.Swap = t } } return m, nil } var firstLine []string blocks := make([]string, 0, 16) for i, line := range lines { fields := strings.Fields(line) if (len(fields) > 0 && !strings.HasSuffix(fields[0], ":")) || i == len(lines)-1 { // new block section if len(firstLine) > 0 && len(blocks) > 0 { g, err := getBlock(firstLine, blocks) if err != nil { return &ret, err } if grouped { ret[0].Size += g.Size ret[0].Rss += g.Rss ret[0].Pss += g.Pss ret[0].SharedClean += g.SharedClean ret[0].SharedDirty += g.SharedDirty ret[0].PrivateClean += g.PrivateClean ret[0].PrivateDirty += g.PrivateDirty ret[0].Referenced += g.Referenced ret[0].Anonymous += g.Anonymous ret[0].Swap += g.Swap } else { ret = append(ret, g) } } // starts new block blocks = make([]string, 0, 16) firstLine = fields } else { blocks = append(blocks, line) } } return &ret, nil } func (p *Process) EnvironWithContext(ctx context.Context) ([]string, error) { environPath := common.HostProcWithContext(ctx, strconv.Itoa(int(p.Pid)), "environ") environContent, err := os.ReadFile(environPath) if err != nil { return nil, err } return strings.Split(string(environContent), "\000"), nil } /** ** Internal functions **/ func limitToUint(val string) (uint64, error) { if val == "unlimited" { return math.MaxUint64, nil } res, err := strconv.ParseUint(val, 10, 64) if err != nil { return 0, err } return res, nil } // Get num_fds from /proc/(pid)/limits func (p *Process) fillFromLimitsWithContext(ctx context.Context) ([]RlimitStat, error) { pid := p.Pid limitsFile := common.HostProcWithContext(ctx, strconv.Itoa(int(pid)), "limits") d, err := os.Open(limitsFile) if err != nil { return nil, err } defer d.Close() var limitStats []RlimitStat limitsScanner := bufio.NewScanner(d) for limitsScanner.Scan() { var statItem RlimitStat str := strings.Fields(limitsScanner.Text()) // Remove the header line if strings.Contains(str[len(str)-1], "Units") { continue } // Assert that last item is a Hard limit statItem.Hard, err = limitToUint(str[len(str)-1]) if err != nil { // On error remove last item and try once again since it can be unit or header line str = str[:len(str)-1] statItem.Hard, err = limitToUint(str[len(str)-1]) if err != nil { return nil, err } } // Remove last item from string str = str[:len(str)-1] // Now last item is a Soft limit statItem.Soft, err = limitToUint(str[len(str)-1]) if err != nil { return nil, err } // Remove last item from string str = str[:len(str)-1] // The rest is a stats name resourceName := strings.Join(str, " ") switch resourceName { case "Max cpu time": statItem.Resource = RLIMIT_CPU case "Max file size": statItem.Resource = RLIMIT_FSIZE case "Max data size": statItem.Resource = RLIMIT_DATA case "Max stack size": statItem.Resource = RLIMIT_STACK case "Max core file size": statItem.Resource = RLIMIT_CORE case "Max resident set": statItem.Resource = RLIMIT_RSS case "Max processes": statItem.Resource = RLIMIT_NPROC case "Max open files": statItem.Resource = RLIMIT_NOFILE case "Max locked memory": statItem.Resource = RLIMIT_MEMLOCK case "Max address space": statItem.Resource = RLIMIT_AS case "Max file locks": statItem.Resource = RLIMIT_LOCKS case "Max pending signals": statItem.Resource = RLIMIT_SIGPENDING case "Max msgqueue size": statItem.Resource = RLIMIT_MSGQUEUE case "Max nice priority": statItem.Resource = RLIMIT_NICE case "Max realtime priority": statItem.Resource = RLIMIT_RTPRIO case "Max realtime timeout": statItem.Resource = RLIMIT_RTTIME default: continue } limitStats = append(limitStats, statItem) } if err := limitsScanner.Err(); err != nil { return nil, err } return limitStats, nil } // Get list of /proc/(pid)/fd files func (p *Process) fillFromfdListWithContext(ctx context.Context) (string, []string, error) { pid := p.Pid statPath := common.HostProcWithContext(ctx, strconv.Itoa(int(pid)), "fd") d, err := os.Open(statPath) if err != nil { return statPath, []string{}, err } defer d.Close() fnames, err := d.Readdirnames(-1) return statPath, fnames, err } // Get num_fds from /proc/(pid)/fd func (p *Process) fillFromfdWithContext(ctx context.Context) (int32, []OpenFilesStat, error) { statPath, fnames, err := p.fillFromfdListWithContext(ctx) if err != nil { return 0, nil, err } numFDs := int32(len(fnames)) openfiles := make([]OpenFilesStat, 0, numFDs) for _, fd := range fnames { fpath := filepath.Join(statPath, fd) path, err := common.Readlink(fpath) if err != nil { continue } t, err := strconv.ParseUint(fd, 10, 64) if err != nil { return numFDs, openfiles, err } o := OpenFilesStat{ Path: path, Fd: t, } openfiles = append(openfiles, o) } return numFDs, openfiles, nil } // Get cwd from /proc/(pid)/cwd func (p *Process) fillFromCwdWithContext(ctx context.Context) (string, error) { pid := p.Pid cwdPath := common.HostProcWithContext(ctx, strconv.Itoa(int(pid)), "cwd") cwd, err := os.Readlink(cwdPath) if err != nil { return "", err } return string(cwd), nil } // Get exe from /proc/(pid)/exe func (p *Process) fillFromExeWithContext(ctx context.Context) (string, error) { pid := p.Pid exePath := common.HostProcWithContext(ctx, strconv.Itoa(int(pid)), "exe") exe, err := os.Readlink(exePath) if err != nil { return "", err } return string(exe), nil } // Get cmdline from /proc/(pid)/cmdline func (p *Process) fillFromCmdlineWithContext(ctx context.Context) (string, error) { pid := p.Pid cmdPath := common.HostProcWithContext(ctx, strconv.Itoa(int(pid)), "cmdline") cmdline, err := os.ReadFile(cmdPath) if err != nil { return "", err } ret := strings.FieldsFunc(string(cmdline), func(r rune) bool { return r == '\u0000' }) return strings.Join(ret, " "), nil } func (p *Process) fillSliceFromCmdlineWithContext(ctx context.Context) ([]string, error) { pid := p.Pid cmdPath := common.HostProcWithContext(ctx, strconv.Itoa(int(pid)), "cmdline") cmdline, err := os.ReadFile(cmdPath) if err != nil { return nil, err } if len(cmdline) == 0 { return nil, nil } cmdline = bytes.TrimRight(cmdline, "\x00") parts := bytes.Split(cmdline, []byte{0}) var strParts []string for _, p := range parts { strParts = append(strParts, string(p)) } return strParts, nil } // Get IO status from /proc/(pid)/io func (p *Process) fillFromIOWithContext(ctx context.Context) (*IOCountersStat, error) { pid := p.Pid ioPath := common.HostProcWithContext(ctx, strconv.Itoa(int(pid)), "io") ioline, err := os.ReadFile(ioPath) if err != nil { return nil, err } lines := strings.Split(string(ioline), "\n") ret := &IOCountersStat{} for _, line := range lines { field := strings.Fields(line) if len(field) < 2 { continue } t, err := strconv.ParseUint(field[1], 10, 64) if err != nil { return nil, err } param := strings.TrimSuffix(field[0], ":") switch param { case "syscr": ret.ReadCount = t case "syscw": ret.WriteCount = t case "read_bytes": ret.DiskReadBytes = t case "write_bytes": ret.DiskWriteBytes = t case "rchar": ret.ReadBytes = t case "wchar": ret.WriteBytes = t } } return ret, nil } // Get memory info from /proc/(pid)/statm func (p *Process) fillFromStatmWithContext(ctx context.Context) (*MemoryInfoStat, *MemoryInfoExStat, error) { pid := p.Pid memPath := common.HostProcWithContext(ctx, strconv.Itoa(int(pid)), "statm") contents, err := os.ReadFile(memPath) if err != nil { return nil, nil, err } fields := strings.Split(string(contents), " ") vms, err := strconv.ParseUint(fields[0], 10, 64) if err != nil { return nil, nil, err } rss, err := strconv.ParseUint(fields[1], 10, 64) if err != nil { return nil, nil, err } memInfo := &MemoryInfoStat{ RSS: rss * pageSize, VMS: vms * pageSize, } shared, err := strconv.ParseUint(fields[2], 10, 64) if err != nil { return nil, nil, err } text, err := strconv.ParseUint(fields[3], 10, 64) if err != nil { return nil, nil, err } lib, err := strconv.ParseUint(fields[4], 10, 64) if err != nil { return nil, nil, err } dirty, err := strconv.ParseUint(fields[5], 10, 64) if err != nil { return nil, nil, err } memInfoEx := &MemoryInfoExStat{ RSS: rss * pageSize, VMS: vms * pageSize, Shared: shared * pageSize, Text: text * pageSize, Lib: lib * pageSize, Dirty: dirty * pageSize, } return memInfo, memInfoEx, nil } // Get name from /proc/(pid)/comm or /proc/(pid)/status func (p *Process) fillNameWithContext(ctx context.Context) error { err := p.fillFromCommWithContext(ctx) if err == nil && p.name != "" && len(p.name) < 15 { return nil } return p.fillFromStatusWithContext(ctx) } // Get name from /proc/(pid)/comm func (p *Process) fillFromCommWithContext(ctx context.Context) error { pid := p.Pid statPath := common.HostProcWithContext(ctx, strconv.Itoa(int(pid)), "comm") contents, err := os.ReadFile(statPath) if err != nil { return err } p.name = strings.TrimSuffix(string(contents), "\n") return nil } // Get various status from /proc/(pid)/status func (p *Process) fillFromStatus() error { return p.fillFromStatusWithContext(context.Background()) } func (p *Process) fillFromStatusWithContext(ctx context.Context) error { pid := p.Pid statPath := common.HostProcWithContext(ctx, strconv.Itoa(int(pid)), "status") contents, err := os.ReadFile(statPath) if err != nil { return err } lines := strings.Split(string(contents), "\n") p.numCtxSwitches = &NumCtxSwitchesStat{} p.memInfo = &MemoryInfoStat{} p.sigInfo = &SignalInfoStat{} for _, line := range lines { tabParts := strings.SplitN(line, "\t", 2) if len(tabParts) < 2 { continue } value := tabParts[1] switch strings.TrimRight(tabParts[0], ":") { case "Name": p.name = strings.Trim(value, " \t") if len(p.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) { p.name = extendedName } } } // Ensure we have a copy and not reference into slice p.name = string([]byte(p.name)) case "State": p.status = convertStatusChar(value[0:1]) // Ensure we have a copy and not reference into slice p.status = string([]byte(p.status)) case "PPid", "Ppid": pval, err := strconv.ParseInt(value, 10, 32) if err != nil { return err } p.parent = int32(pval) case "Tgid": pval, err := strconv.ParseInt(value, 10, 32) if err != nil { return err } p.tgid = int32(pval) case "Uid": p.uids = make([]uint32, 0, 4) for _, i := range strings.Split(value, "\t") { v, err := strconv.ParseInt(i, 10, 32) if err != nil { return err } p.uids = append(p.uids, uint32(v)) } case "Gid": p.gids = make([]uint32, 0, 4) for _, i := range strings.Split(value, "\t") { v, err := strconv.ParseInt(i, 10, 32) if err != nil { return err } p.gids = append(p.gids, uint32(v)) } case "Groups": groups := strings.Fields(value) p.groups = make([]uint32, 0, len(groups)) for _, i := range groups { v, err := strconv.ParseUint(i, 10, 32) if err != nil { return err } p.groups = append(p.groups, uint32(v)) } case "Threads": v, err := strconv.ParseInt(value, 10, 32) if err != nil { return err } p.numThreads = int32(v) case "voluntary_ctxt_switches": v, err := strconv.ParseInt(value, 10, 64) if err != nil { return err } p.numCtxSwitches.Voluntary = v case "nonvoluntary_ctxt_switches": v, err := strconv.ParseInt(value, 10, 64) if err != nil { return err } p.numCtxSwitches.Involuntary = v case "VmRSS": value = strings.Trim(value, " kB") // remove last "kB" v, err := strconv.ParseUint(value, 10, 64) if err != nil { return err } p.memInfo.RSS = v * 1024 case "VmSize": value = strings.Trim(value, " kB") // remove last "kB" v, err := strconv.ParseUint(value, 10, 64) if err != nil { return err } p.memInfo.VMS = v * 1024 case "VmSwap": value = strings.Trim(value, " kB") // remove last "kB" v, err := strconv.ParseUint(value, 10, 64) if err != nil { return err } p.memInfo.Swap = v * 1024 case "VmHWM": value = strings.Trim(value, " kB") // remove last "kB" v, err := strconv.ParseUint(value, 10, 64) if err != nil { return err } p.memInfo.HWM = v * 1024 case "VmData": value = strings.Trim(value, " kB") // remove last "kB" v, err := strconv.ParseUint(value, 10, 64) if err != nil { return err } p.memInfo.Data = v * 1024 case "VmStk": value = strings.Trim(value, " kB") // remove last "kB" v, err := strconv.ParseUint(value, 10, 64) if err != nil { return err } p.memInfo.Stack = v * 1024 case "VmLck": value = strings.Trim(value, " kB") // remove last "kB" v, err := strconv.ParseUint(value, 10, 64) if err != nil { return err } p.memInfo.Locked = v * 1024 case "SigPnd": if len(value) > 16 { value = value[len(value)-16:] } v, err := strconv.ParseUint(value, 16, 64) if err != nil { return err } p.sigInfo.PendingThread = v case "ShdPnd": if len(value) > 16 { value = value[len(value)-16:] } v, err := strconv.ParseUint(value, 16, 64) if err != nil { return err } p.sigInfo.PendingProcess = v case "SigBlk": if len(value) > 16 { value = value[len(value)-16:] } v, err := strconv.ParseUint(value, 16, 64) if err != nil { return err } p.sigInfo.Blocked = v case "SigIgn": if len(value) > 16 { value = value[len(value)-16:] } v, err := strconv.ParseUint(value, 16, 64) if err != nil { return err } p.sigInfo.Ignored = v case "SigCgt": if len(value) > 16 { value = value[len(value)-16:] } v, err := strconv.ParseUint(value, 16, 64) if err != nil { return err } p.sigInfo.Caught = v } } return nil } func (p *Process) fillFromTIDStat(tid int32) (uint64, int32, *cpu.TimesStat, int64, uint32, int32, *PageFaultsStat, error) { return p.fillFromTIDStatWithContext(context.Background(), tid) } func (p *Process) fillFromTIDStatWithContext(ctx context.Context, tid int32) (uint64, int32, *cpu.TimesStat, int64, uint32, int32, *PageFaultsStat, error) { pid := p.Pid var statPath string if tid == -1 { statPath = common.HostProcWithContext(ctx, strconv.Itoa(int(pid)), "stat") } else { statPath = common.HostProcWithContext(ctx, strconv.Itoa(int(pid)), "task", strconv.Itoa(int(tid)), "stat") } contents, err := os.ReadFile(statPath) if err != nil { return 0, 0, nil, 0, 0, 0, nil, err } // Indexing from one, as described in `man proc` about the file /proc/[pid]/stat fields := splitProcStat(contents) terminal, err := strconv.ParseUint(fields[7], 10, 64) if err != nil { return 0, 0, nil, 0, 0, 0, nil, err } ppid, err := strconv.ParseInt(fields[4], 10, 32) if err != nil { return 0, 0, nil, 0, 0, 0, nil, err } utime, err := strconv.ParseFloat(fields[14], 64) if err != nil { return 0, 0, nil, 0, 0, 0, nil, err } stime, err := strconv.ParseFloat(fields[15], 64) if err != nil { return 0, 0, nil, 0, 0, 0, nil, err } // There is no such thing as iotime in stat file. As an approximation, we // will use delayacct_blkio_ticks (aggregated block I/O delays, as per Linux // docs). Note: I am assuming at least Linux 2.6.18 var iotime float64 if len(fields) > 42 { iotime, err = strconv.ParseFloat(fields[42], 64) if err != nil { iotime = 0 // Ancient linux version, most likely } } else { iotime = 0 // e.g. SmartOS containers } cpuTimes := &cpu.TimesStat{ CPU: "cpu", User: utime / float64(clockTicks), System: stime / float64(clockTicks), Iowait: iotime / float64(clockTicks), } bootTime, _ := common.BootTimeWithContext(ctx, enableBootTimeCache) t, err := strconv.ParseUint(fields[22], 10, 64) if err != nil { return 0, 0, nil, 0, 0, 0, nil, err } createTime := int64((t * 1000 / uint64(clockTicks)) + uint64(bootTime*1000)) rtpriority, err := strconv.ParseInt(fields[18], 10, 32) if err != nil { return 0, 0, nil, 0, 0, 0, nil, err } if rtpriority < 0 { rtpriority = rtpriority*-1 - 1 } else { rtpriority = 0 } // p.Nice = mustParseInt32(fields[18]) // use syscall instead of parse Stat file snice, _ := unix.Getpriority(prioProcess, int(pid)) nice := int32(snice) // FIXME: is this true? minFault, err := strconv.ParseUint(fields[10], 10, 64) if err != nil { return 0, 0, nil, 0, 0, 0, nil, err } cMinFault, err := strconv.ParseUint(fields[11], 10, 64) if err != nil { return 0, 0, nil, 0, 0, 0, nil, err } majFault, err := strconv.ParseUint(fields[12], 10, 64) if err != nil { return 0, 0, nil, 0, 0, 0, nil, err } cMajFault, err := strconv.ParseUint(fields[13], 10, 64) if err != nil { return 0, 0, nil, 0, 0, 0, nil, err } faults := &PageFaultsStat{ MinorFaults: minFault, MajorFaults: majFault, ChildMinorFaults: cMinFault, ChildMajorFaults: cMajFault, } return terminal, int32(ppid), cpuTimes, createTime, uint32(rtpriority), nice, faults, nil } func (p *Process) fillFromStatWithContext(ctx context.Context) (uint64, int32, *cpu.TimesStat, int64, uint32, int32, *PageFaultsStat, error) { return p.fillFromTIDStatWithContext(ctx, -1) } func pidsWithContext(ctx context.Context) ([]int32, error) { return readPidsFromDir(common.HostProcWithContext(ctx)) } func ProcessesWithContext(ctx context.Context) ([]*Process, error) { out := []*Process{} pids, err := PidsWithContext(ctx) if err != nil { return out, err } for _, pid := range pids { p, err := NewProcessWithContext(ctx, pid) if err != nil { continue } out = append(out, p) } return out, nil } func readPidsFromDir(path string) ([]int32, error) { var ret []int32 d, err := os.Open(path) if err != nil { return nil, err } defer d.Close() fnames, err := d.Readdirnames(-1) if err != nil { return nil, err } for _, fname := range fnames { pid, err := strconv.ParseInt(fname, 10, 32) if err != nil { // if not numeric name, just skip continue } ret = append(ret, int32(pid)) } return ret, nil } func splitProcStat(content []byte) []string { nameStart := bytes.IndexByte(content, '(') nameEnd := bytes.LastIndexByte(content, ')') restFields := strings.Fields(string(content[nameEnd+2:])) // +2 skip ') ' name := content[nameStart+1 : nameEnd] pid := strings.TrimSpace(string(content[:nameStart])) fields := make([]string, 3, len(restFields)+3) fields[1] = string(pid) fields[2] = string(name) fields = append(fields, restFields...) return fields }
// SPDX-License-Identifier: BSD-3-Clause //go:build linux || freebsd || openbsd || darwin || solaris package process import ( "context" "errors" "fmt" "os" "os/user" "path/filepath" "strconv" "strings" "syscall" "golang.org/x/sys/unix" "github.com/shirou/gopsutil/v4/internal/common" ) type Signal = syscall.Signal // POSIX func getTerminalMap() (map[uint64]string, error) { ret := make(map[uint64]string) var termfiles []string d, err := os.Open("/dev") if err != nil { return nil, err } defer d.Close() devnames, err := d.Readdirnames(-1) if err != nil { return nil, err } for _, devname := range devnames { if strings.HasPrefix(devname, "/dev/tty") { termfiles = append(termfiles, "/dev/tty/"+devname) } } var ptsnames []string ptsd, err := os.Open("/dev/pts") if err != nil { ptsnames, _ = filepath.Glob("/dev/ttyp*") if ptsnames == nil { return nil, err } } defer ptsd.Close() if ptsnames == nil { defer ptsd.Close() ptsnames, err = ptsd.Readdirnames(-1) if err != nil { return nil, err } for _, ptsname := range ptsnames { termfiles = append(termfiles, "/dev/pts/"+ptsname) } } else { termfiles = ptsnames } for _, name := range termfiles { stat := unix.Stat_t{} err = unix.Stat(name, &stat) if err != nil { return nil, err } rdev := uint64(stat.Rdev) ret[rdev] = strings.ReplaceAll(name, "/dev", "") } return ret, nil } // isMount is a port of python's os.path.ismount() // https://github.com/python/cpython/blob/08ff4369afca84587b1c82034af4e9f64caddbf2/Lib/posixpath.py#L186-L216 // https://docs.python.org/3/library/os.path.html#os.path.ismount func isMount(path string) bool { // Check symlinkness with os.Lstat; unix.DT_LNK is not portable fileInfo, err := os.Lstat(path) if err != nil { return false } if fileInfo.Mode()&os.ModeSymlink != 0 { return false } var stat1 unix.Stat_t if err := unix.Lstat(path, &stat1); err != nil { return false } parent := filepath.Join(path, "..") var stat2 unix.Stat_t if err := unix.Lstat(parent, &stat2); err != nil { return false } return stat1.Dev != stat2.Dev || stat1.Ino == stat2.Ino } func PidExistsWithContext(ctx context.Context, pid int32) (bool, error) { if pid <= 0 { return false, fmt.Errorf("invalid pid %v", pid) } proc, err := os.FindProcess(int(pid)) if err != nil { return false, err } defer proc.Release() if isMount(common.HostProcWithContext(ctx)) { // if /<HOST_PROC>/proc exists and is mounted, check if /<HOST_PROC>/proc/<PID> folder exists _, err := os.Stat(common.HostProcWithContext(ctx, strconv.Itoa(int(pid)))) if os.IsNotExist(err) { return false, nil } return err == nil, err } // procfs does not exist or is not mounted, check PID existence by signalling the pid err = proc.Signal(syscall.Signal(0)) if err == nil { return true, nil } if errors.Is(err, os.ErrProcessDone) { return false, nil } var errno syscall.Errno if !errors.As(err, &errno) { return false, err } switch errno { case syscall.ESRCH: return false, nil case syscall.EPERM: return true, nil } return false, err } func (p *Process) SendSignalWithContext(_ context.Context, sig syscall.Signal) error { process, err := os.FindProcess(int(p.Pid)) if err != nil { return err } defer process.Release() err = process.Signal(sig) if err != nil { return err } return nil } func (p *Process) SuspendWithContext(ctx context.Context) error { return p.SendSignalWithContext(ctx, unix.SIGSTOP) } func (p *Process) ResumeWithContext(ctx context.Context) error { return p.SendSignalWithContext(ctx, unix.SIGCONT) } func (p *Process) TerminateWithContext(ctx context.Context) error { return p.SendSignalWithContext(ctx, unix.SIGTERM) } func (p *Process) KillWithContext(ctx context.Context) error { return p.SendSignalWithContext(ctx, unix.SIGKILL) } func (p *Process) UsernameWithContext(ctx context.Context) (string, error) { uids, err := p.UidsWithContext(ctx) if err != nil { return "", err } if len(uids) > 0 { u, err := user.LookupId(strconv.Itoa(int(uids[0]))) if err != nil { return "", err } return u.Username, nil } return "", nil }