621 lines
17 KiB
Go
621 lines
17 KiB
Go
package process
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"errors"
|
|
"runtime"
|
|
"sort"
|
|
"sync"
|
|
"time"
|
|
|
|
"github.com/shirou/gopsutil/v3/cpu"
|
|
"github.com/shirou/gopsutil/v3/internal/common"
|
|
"github.com/shirou/gopsutil/v3/mem"
|
|
"github.com/shirou/gopsutil/v3/net"
|
|
)
|
|
|
|
var (
|
|
invoke common.Invoker = common.Invoke{}
|
|
ErrorNoChildren = errors.New("process does not have children")
|
|
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 []int32
|
|
gids []int32
|
|
groups []int32
|
|
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 uint64 `json:"readCount"`
|
|
WriteCount uint64 `json:"writeCount"`
|
|
ReadBytes uint64 `json:"readBytes"`
|
|
WriteBytes uint64 `json:"writeBytes"`
|
|
}
|
|
|
|
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)
|
|
}
|
|
|
|
// 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
|
|
}
|
|
delta_proc := t2.Total() - t1.Total()
|
|
overall_percent := ((delta_proc / delta) * 100) * float64(numcpu)
|
|
return overall_percent
|
|
}
|
|
|
|
// 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
|
|
}
|
|
|
|
// CPU_Percent 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) {
|
|
crt_time, 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, crt_time*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() ([]int32, 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.
|
|
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() ([]int32, error) {
|
|
return p.UidsWithContext(context.Background())
|
|
}
|
|
|
|
// Gids returns group ids of the process as a slice of the int
|
|
func (p *Process) Gids() ([]int32, 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())
|
|
}
|
|
|
|
// PageFaultsInfo 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())
|
|
}
|
|
|
|
// Connections returns a slice of net.ConnectionStat used by the process at most `max`.
|
|
func (p *Process) ConnectionsMax(max int) ([]net.ConnectionStat, error) {
|
|
return p.ConnectionsMaxWithContext(context.Background(), max)
|
|
}
|
|
|
|
// 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
|
|
}
|
|
}
|