package process
import (
"bufio"
"bytes"
"context"
"encoding/json"
"fmt"
"math"
"os"
"path/filepath"
"strconv"
"strings"
"github.com/tklauser/go-sysconf"
"golang.org/x/sys/unix"
"github.com/shirou/gopsutil/v3/cpu"
"github.com/shirou/gopsutil/v3/internal/common"
"github.com/shirou/gopsutil/v3/net"
)
var pageSize = uint64 (os .Getpagesize ())
const prioProcess = 0
var clockTicks = 100
func init() {
clkTck , err := sysconf .Sysconf (sysconf .SC_CLK_TCK )
if err == nil {
clockTicks = int (clkTck )
}
}
type MemoryInfoExStat struct {
RSS uint64 `json:"rss"`
VMS uint64 `json:"vms"`
Shared uint64 `json:"shared"`
Text uint64 `json:"text"`
Lib uint64 `json:"lib"`
Data uint64 `json:"data"`
Dirty uint64 `json:"dirty"`
}
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"`
}
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 ) {
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 ) ([]int32 , error ) {
err := p .fillFromStatusWithContext (ctx )
if err != nil {
return []int32 {}, err
}
return p .uids , nil
}
func (p *Process ) GidsWithContext (ctx context .Context ) ([]int32 , error ) {
err := p .fillFromStatusWithContext (ctx )
if err != nil {
return []int32 {}, err
}
return p .gids , nil
}
func (p *Process ) GroupsWithContext (ctx context .Context ) ([]int32 , error ) {
err := p .fillFromStatusWithContext (ctx )
if err != nil {
return []int32 {}, 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 :
case RLIMIT_SIGPENDING :
rs .Used = p .sigInfo .PendingProcess
case RLIMIT_NICE :
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 ) {
pids , err := common .CallPgrepWithContext (ctx , invoke , p .Pid )
if err != nil {
return nil , err
}
if len (pids ) == 0 {
return nil , ErrorNoChildren
}
ret := make ([]*Process , 0 , len (pids ))
for _ , pid := range pids {
np , err := NewProcessWithContext (ctx , pid )
if err != nil {
return nil , err
}
ret = append (ret , np )
}
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 , max int ) ([]net .ConnectionStat , error ) {
return net .ConnectionsPidMaxWithContext (ctx , "all" , p .Pid , max )
}
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 )
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" )
getBlock := func (firstLine []string , block []string ) (MemoryMapsStat , error ) {
m := MemoryMapsStat {}
m .Path = firstLine [len (firstLine )-1 ]
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" )
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 {
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 )
}
}
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
}
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
}
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 ())
if strings .Contains (str [len (str )-1 ], "Units" ) {
continue
}
statItem .Hard , err = limitToUint (str [len (str )-1 ])
if err != nil {
str = str [:len (str )-1 ]
statItem .Hard , err = limitToUint (str [len (str )-1 ])
if err != nil {
return nil , err
}
}
str = str [:len (str )-1 ]
statItem .Soft , err = limitToUint (str [len (str )-1 ])
if err != nil {
return nil , err
}
str = str [:len (str )-1 ]
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
}
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
}
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
}
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
}
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
}
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
}
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 .ReadBytes = t
case "write_bytes" :
ret .WriteBytes = t
}
}
return ret , nil
}
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
}
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 )
}
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
}
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
}
}
}
p .name = string ([]byte (p .name ))
case "State" :
p .status = convertStatusChar (value [0 :1 ])
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 ([]int32 , 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 , int32 (v ))
}
case "Gid" :
p .gids = make ([]int32 , 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 , int32 (v ))
}
case "Groups" :
groups := strings .Fields (value )
p .groups = make ([]int32 , 0 , len (groups ))
for _ , i := range groups {
v , err := strconv .ParseInt (i , 10 , 32 )
if err != nil {
return err
}
p .groups = append (p .groups , int32 (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" )
v , err := strconv .ParseUint (value , 10 , 64 )
if err != nil {
return err
}
p .memInfo .RSS = v * 1024
case "VmSize" :
value := strings .Trim (value , " 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" )
v , err := strconv .ParseUint (value , 10 , 64 )
if err != nil {
return err
}
p .memInfo .Swap = v * 1024
case "VmHWM" :
value := strings .Trim (value , " 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" )
v , err := strconv .ParseUint (value , 10 , 64 )
if err != nil {
return err
}
p .memInfo .Data = v * 1024
case "VmStk" :
value := strings .Trim (value , " 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" )
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
}
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
}
var iotime float64
if len (fields ) > 42 {
iotime , err = strconv .ParseFloat (fields [42 ], 64 )
if err != nil {
iotime = 0
}
} else {
iotime = 0
}
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
}
ctime := (t / uint64 (clockTicks )) + uint64 (bootTime )
createTime := int64 (ctime * 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
}
snice , _ := unix .Getpriority (prioProcess , int (pid ))
nice := int32 (snice )
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 {
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 :]))
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
}
The pages are generated with Golds v0.8.2 . (GOOS=linux GOARCH=amd64)
Golds is a Go 101 project developed by Tapir Liu .
PR and bug reports are welcome and can be submitted to the issue list .
Please follow @zigo_101 (reachable from the left QR code) to get the latest news of Golds .