summaryrefslogtreecommitdiff
path: root/vendor/github.com/shirou/gopsutil/v4/process/process_linux.go
diff options
context:
space:
mode:
authormo khan <mo@mokhan.ca>2025-05-11 21:12:57 -0600
committermo khan <mo@mokhan.ca>2025-05-11 21:12:57 -0600
commit60440f90dca28e99a31dd328c5f6d5dc0f9b6a2e (patch)
tree2f54adf55086516f162f0a55a5347e6b25f7f176 /vendor/github.com/shirou/gopsutil/v4/process/process_linux.go
parent05ca9b8d3a9c7203a3a3b590beaa400900bd9007 (diff)
chore: vendor go dependencies
Diffstat (limited to 'vendor/github.com/shirou/gopsutil/v4/process/process_linux.go')
-rw-r--r--vendor/github.com/shirou/gopsutil/v4/process/process_linux.go1206
1 files changed, 1206 insertions, 0 deletions
diff --git a/vendor/github.com/shirou/gopsutil/v4/process/process_linux.go b/vendor/github.com/shirou/gopsutil/v4/process/process_linux.go
new file mode 100644
index 0000000..68a8c88
--- /dev/null
+++ b/vendor/github.com/shirou/gopsutil/v4/process/process_linux.go
@@ -0,0 +1,1206 @@
+// 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 (p *Process) IOniceWithContext(ctx 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 (p *Process) CPUAffinityWithContext(ctx 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 int32(ppid) == 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)
+ if err != nil {
+ return nil, err
+ }
+ ret := make([]OpenFilesStat, len(ofs))
+ for i, o := range ofs {
+ ret[i] = *o
+ }
+
+ 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 (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))
+
+ var openfiles []*OpenFilesStat
+ for _, fd := range fnames {
+ fpath := filepath.Join(statPath, fd)
+ filepath, err := os.Readlink(fpath)
+ if err != nil {
+ continue
+ }
+ t, err := strconv.ParseUint(fd, 10, 64)
+ if err != nil {
+ return numFDs, openfiles, err
+ }
+ o := &OpenFilesStat{
+ Path: filepath,
+ 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
+}