| // SPDX-License-Identifier: BSD-3-Clause |
| package process |
| |
| import ( |
| "context" |
| "encoding/json" |
| "errors" |
| "regexp" |
| "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{} |
| strictIntPtrn = regexp.MustCompile(`^\d+$`) |
| 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 |
| } |
| } |