1
0
mirror of https://github.com/shirou/gopsutil.git synced 2025-04-26 13:48:59 +08:00
shirou_gopsutil/process/process_linux.go
Chris Gilling b8c40ac83c process: add CmdlineSlice function for linux + freebsd
This allows for getting more exact information about each argument
especially if there are arguments that have spaces in them.

This was not implemented for darwin or for windows because they
both currently have not way of properly parsing the cmdline string.
Darwin parses the output of 'ps' which is already whitespace
segmented, and windows just has the cmdline string.
2016-02-27 18:01:49 -08:00

726 lines
16 KiB
Go

// +build linux
package process
import (
"bytes"
"encoding/json"
"errors"
"fmt"
"io/ioutil"
"os"
"os/exec"
"path/filepath"
"strconv"
"strings"
"syscall"
"github.com/shirou/gopsutil/cpu"
"github.com/shirou/gopsutil/host"
"github.com/shirou/gopsutil/internal/common"
"github.com/shirou/gopsutil/net"
)
var ErrorNoChildren = errors.New("process does not have children")
const (
PrioProcess = 0 // linux/resource.h
)
// 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:"shared_clean"`
SharedDirty uint64 `json:"shared_dirty"`
PrivateClean uint64 `json:"private_clean"`
PrivateDirty uint64 `json:"private_dirty"`
Referenced uint64 `json:"referenced"`
Anonymous uint64 `json:"anonymous"`
Swap uint64 `json:"swap"`
}
func (m MemoryMapsStat) String() string {
s, _ := json.Marshal(m)
return string(s)
}
// Create new Process instance
// This only stores Pid
func NewProcess(pid int32) (*Process, error) {
p := &Process{
Pid: int32(pid),
}
err := p.fillFromStatus()
return p, err
}
func (p *Process) Ppid() (int32, error) {
_, ppid, _, _, _, err := p.fillFromStat()
if err != nil {
return -1, err
}
return ppid, nil
}
func (p *Process) Name() (string, error) {
return p.name, nil
}
func (p *Process) Exe() (string, error) {
return p.fillFromExe()
}
// 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.fillFromCmdline()
}
// 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.fillSliceFromCmdline()
}
func (p *Process) CreateTime() (int64, error) {
_, _, _, createTime, _, err := p.fillFromStat()
if err != nil {
return 0, err
}
return createTime, nil
}
func (p *Process) Cwd() (string, error) {
return p.fillFromCwd()
}
func (p *Process) Parent() (*Process, error) {
r, err := callLsof("R", p.Pid)
if err != nil {
return nil, err
}
if len(r) != 1 { // TODO: pid 1
return nil, fmt.Errorf("wrong number of parents")
}
v, err := strconv.Atoi(r[0])
if err != nil {
return nil, err
}
return NewProcess(int32(v))
}
func (p *Process) Status() (string, error) {
err := p.fillFromStatus()
if err != nil {
return "", err
}
return p.status, nil
}
func (p *Process) Uids() ([]int32, error) {
err := p.fillFromStatus()
if err != nil {
return []int32{}, err
}
return p.uids, nil
}
func (p *Process) Gids() ([]int32, error) {
err := p.fillFromStatus()
if err != nil {
return []int32{}, err
}
return p.gids, nil
}
func (p *Process) Terminal() (string, error) {
terminal, _, _, _, _, err := p.fillFromStat()
if err != nil {
return "", err
}
return terminal, nil
}
func (p *Process) Nice() (int32, error) {
_, _, _, _, nice, err := p.fillFromStat()
if err != nil {
return 0, err
}
return nice, nil
}
func (p *Process) IOnice() (int32, error) {
return 0, common.NotImplementedError
}
func (p *Process) Rlimit() ([]RlimitStat, error) {
return nil, common.NotImplementedError
}
func (p *Process) IOCounters() (*IOCountersStat, error) {
return p.fillFromIO()
}
func (p *Process) NumCtxSwitches() (*NumCtxSwitchesStat, error) {
err := p.fillFromStatus()
if err != nil {
return nil, err
}
return p.numCtxSwitches, nil
}
func (p *Process) NumFDs() (int32, error) {
numFds, _, err := p.fillFromfd()
return numFds, err
}
func (p *Process) NumThreads() (int32, error) {
err := p.fillFromStatus()
if err != nil {
return 0, err
}
return p.numThreads, nil
}
func (p *Process) Threads() (map[string]string, error) {
ret := make(map[string]string, 0)
return ret, nil
}
func (p *Process) CPUTimes() (*cpu.CPUTimesStat, error) {
_, _, cpuTimes, _, _, err := p.fillFromStat()
if err != nil {
return nil, err
}
return cpuTimes, nil
}
func (p *Process) CPUAffinity() ([]int32, error) {
return nil, common.NotImplementedError
}
func (p *Process) MemoryInfo() (*MemoryInfoStat, error) {
meminfo, _, err := p.fillFromStatm()
if err != nil {
return nil, err
}
return meminfo, nil
}
func (p *Process) MemoryInfoEx() (*MemoryInfoExStat, error) {
_, memInfoEx, err := p.fillFromStatm()
if err != nil {
return nil, err
}
return memInfoEx, nil
}
func (p *Process) Children() ([]*Process, error) {
pids, err := common.CallPgrep(invoke, p.Pid)
if err != nil {
if pids == nil || len(pids) == 0 {
return nil, ErrorNoChildren
}
return nil, err
}
ret := make([]*Process, 0, len(pids))
for _, pid := range pids {
np, err := NewProcess(pid)
if err != nil {
return nil, err
}
ret = append(ret, np)
}
return ret, nil
}
func (p *Process) OpenFiles() ([]OpenFilesStat, error) {
_, ofs, err := p.fillFromfd()
if err != nil {
return nil, err
}
ret := make([]OpenFilesStat, 0, len(ofs))
for i, o := range ofs {
ret[i] = *o
}
return ret, nil
}
func (p *Process) Connections() ([]net.NetConnectionStat, error) {
return net.NetConnectionsPid("all", p.Pid)
}
func (p *Process) NetIOCounters(pernic bool) ([]net.NetIOCountersStat, error) {
filename := common.HostProc(strconv.Itoa(int(p.Pid)), "net/dev")
return net.NetIOCountersByFile(pernic, filename)
}
func (p *Process) IsRunning() (bool, error) {
return true, common.NotImplementedError
}
// MemoryMaps get memory maps from /proc/(pid)/smaps
func (p *Process) MemoryMaps(grouped bool) (*[]MemoryMapsStat, error) {
pid := p.Pid
var ret []MemoryMapsStat
smapsPath := common.HostProc(strconv.Itoa(int(pid)), "smaps")
contents, err := ioutil.ReadFile(smapsPath)
if err != nil {
return nil, err
}
lines := strings.Split(string(contents), "\n")
// function of parsing a block
getBlock := func(first_line []string, block []string) (MemoryMapsStat, error) {
m := MemoryMapsStat{}
m.Path = first_line[len(first_line)-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") // remove last "kB"
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
}
blocks := make([]string, 16)
for _, line := range lines {
field := strings.Split(line, " ")
if strings.HasSuffix(field[0], ":") == false {
// new block section
if len(blocks) > 0 {
g, err := getBlock(field, blocks)
if err != nil {
return &ret, err
}
ret = append(ret, g)
}
// starts new block
blocks = make([]string, 16)
} else {
blocks = append(blocks, line)
}
}
return &ret, nil
}
/**
** Internal functions
**/
// Get num_fds from /proc/(pid)/fd
func (p *Process) fillFromfd() (int32, []*OpenFilesStat, error) {
pid := p.Pid
statPath := common.HostProc(strconv.Itoa(int(pid)), "fd")
d, err := os.Open(statPath)
if err != nil {
return 0, nil, err
}
defer d.Close()
fnames, err := d.Readdirnames(-1)
numFDs := int32(len(fnames))
openfiles := make([]*OpenFilesStat, numFDs)
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) fillFromCwd() (string, error) {
pid := p.Pid
cwdPath := common.HostProc(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) fillFromExe() (string, error) {
pid := p.Pid
exePath := common.HostProc(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) fillFromCmdline() (string, error) {
pid := p.Pid
cmdPath := common.HostProc(strconv.Itoa(int(pid)), "cmdline")
cmdline, err := ioutil.ReadFile(cmdPath)
if err != nil {
return "", err
}
ret := strings.FieldsFunc(string(cmdline), func(r rune) bool {
if r == '\u0000' {
return true
}
return false
})
return strings.Join(ret, " "), nil
}
func (p *Process) fillSliceFromCmdline() ([]string, error) {
pid := p.Pid
cmdPath := common.HostProc(strconv.Itoa(int(pid)), "cmdline")
cmdline, err := ioutil.ReadFile(cmdPath)
if err != nil {
return nil, err
}
if cmdline[len(cmdline)-1] == 0 {
cmdline = cmdline[:len(cmdline)-1]
}
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) fillFromIO() (*IOCountersStat, error) {
pid := p.Pid
ioPath := common.HostProc(strconv.Itoa(int(pid)), "io")
ioline, err := ioutil.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 := field[0]
if strings.HasSuffix(param, ":") {
param = param[:len(param)-1]
}
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
}
// Get memory info from /proc/(pid)/statm
func (p *Process) fillFromStatm() (*MemoryInfoStat, *MemoryInfoExStat, error) {
pid := p.Pid
memPath := common.HostProc(strconv.Itoa(int(pid)), "statm")
contents, err := ioutil.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 various status from /proc/(pid)/status
func (p *Process) fillFromStatus() error {
pid := p.Pid
statPath := common.HostProc(strconv.Itoa(int(pid)), "status")
contents, err := ioutil.ReadFile(statPath)
if err != nil {
return err
}
lines := strings.Split(string(contents), "\n")
p.numCtxSwitches = &NumCtxSwitchesStat{}
p.memInfo = &MemoryInfoStat{}
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")
case "State":
// get between "(" and ")"
s := strings.Index(value, "(") + 1
e := strings.Index(value, ")")
p.status = value[s:e]
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 "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
}
}
return nil
}
func (p *Process) fillFromStat() (string, int32, *cpu.CPUTimesStat, int64, int32, error) {
pid := p.Pid
statPath := common.HostProc(strconv.Itoa(int(pid)), "stat")
contents, err := ioutil.ReadFile(statPath)
if err != nil {
return "", 0, nil, 0, 0, err
}
fields := strings.Fields(string(contents))
i := 1
for !strings.HasSuffix(fields[i], ")") {
i++
}
termmap, err := getTerminalMap()
terminal := ""
if err == nil {
t, err := strconv.ParseUint(fields[i+5], 10, 64)
if err != nil {
return "", 0, nil, 0, 0, err
}
terminal = termmap[t]
}
ppid, err := strconv.ParseInt(fields[i+2], 10, 32)
if err != nil {
return "", 0, nil, 0, 0, err
}
utime, err := strconv.ParseFloat(fields[i+12], 64)
if err != nil {
return "", 0, nil, 0, 0, err
}
stime, err := strconv.ParseFloat(fields[i+13], 64)
if err != nil {
return "", 0, nil, 0, 0, err
}
cpuTimes := &cpu.CPUTimesStat{
CPU: "cpu",
User: float64(utime / ClockTicks),
System: float64(stime / ClockTicks),
}
bootTime, _ := host.BootTime()
t, err := strconv.ParseUint(fields[i+20], 10, 64)
if err != nil {
return "", 0, nil, 0, 0, err
}
ctime := (t / uint64(ClockTicks)) + uint64(bootTime)
createTime := int64(ctime * 1000)
// p.Nice = mustParseInt32(fields[18])
// use syscall instead of parse Stat file
snice, _ := syscall.Getpriority(PrioProcess, int(pid))
nice := int32(snice) // FIXME: is this true?
return terminal, int32(ppid), cpuTimes, createTime, nice, nil
}
func Pids() ([]int32, error) {
var ret []int32
d, err := os.Open(common.HostProc())
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 callLsof(arg string, pid int32) ([]string, error) {
var cmd []string
if pid == 0 { // will get from all processes.
cmd = []string{"-F" + arg}
} else {
cmd = []string{"-a", "-F" + arg, "-p", strconv.Itoa(int(pid))}
}
lsof, err := exec.LookPath("lsof")
if err != nil {
return []string{}, err
}
out, err := invoke.Command(lsof, cmd...)
if err != nil {
return []string{}, err
}
lines := strings.Split(string(out), "\n")
var ret []string
for _, l := range lines[1:] {
if strings.HasPrefix(l, arg) {
ret = append(ret, l[1:]) // delete first char
}
}
return ret, nil
}