consul/vendor/github.com/shirou/gopsutil/process/process_linux.go

891 lines
21 KiB
Go

// +build linux
package process
import (
"bufio"
"bytes"
"encoding/json"
"errors"
"fmt"
"io/ioutil"
"math"
"os"
"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")
PageSize = uint64(os.Getpagesize())
)
const (
PrioProcess = 0 // linux/resource.h
ClockTicks = 100 // C.sysconf(C._SC_CLK_TCK)
)
// 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)
}
// 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) {
p := &Process{
Pid: int32(pid),
}
file, err := os.Open(common.HostProc(strconv.Itoa(int(p.Pid))))
defer file.Close()
return p, err
}
// Ppid returns Parent Process ID of the process.
func (p *Process) Ppid() (int32, error) {
_, ppid, _, _, _, err := p.fillFromStat()
if err != nil {
return -1, err
}
return ppid, nil
}
// Name returns name of the process.
func (p *Process) Name() (string, error) {
if p.name == "" {
if err := p.fillFromStatus(); err != nil {
return "", err
}
}
return p.name, nil
}
// Exe returns executable path of the process.
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()
}
// CreateTime returns created time of the process in seconds since the epoch, in UTC.
func (p *Process) CreateTime() (int64, error) {
_, _, _, createTime, _, err := p.fillFromStat()
if err != nil {
return 0, err
}
return createTime, nil
}
// Cwd returns current working directory of the process.
func (p *Process) Cwd() (string, error) {
return p.fillFromCwd()
}
// Parent returns parent Process of the process.
func (p *Process) Parent() (*Process, error) {
err := p.fillFromStatus()
if err != nil {
return nil, err
}
if p.parent == 0 {
return nil, fmt.Errorf("wrong number of parents")
}
return NewProcess(p.parent)
}
// 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 charactor is same within all supported platforms.
func (p *Process) Status() (string, error) {
err := p.fillFromStatus()
if err != nil {
return "", err
}
return p.status, nil
}
// Uids returns user ids of the process as a slice of the int
func (p *Process) Uids() ([]int32, error) {
err := p.fillFromStatus()
if err != nil {
return []int32{}, err
}
return p.uids, nil
}
// Gids returns group ids of the process as a slice of the int
func (p *Process) Gids() ([]int32, error) {
err := p.fillFromStatus()
if err != nil {
return []int32{}, err
}
return p.gids, nil
}
// Terminal returns a terminal which is associated with the process.
func (p *Process) Terminal() (string, error) {
terminal, _, _, _, _, err := p.fillFromStat()
if err != nil {
return "", err
}
return terminal, nil
}
// Nice returns a nice value (priority).
// Notice: gopsutil can not set nice value.
func (p *Process) Nice() (int32, error) {
_, _, _, _, nice, err := p.fillFromStat()
if err != nil {
return 0, err
}
return nice, nil
}
// IOnice returns process I/O nice value (priority).
func (p *Process) IOnice() (int32, error) {
return 0, common.ErrNotImplementedError
}
// Rlimit returns Resource Limits.
func (p *Process) Rlimit() ([]RlimitStat, error) {
return p.fillFromLimits()
}
// IOCounters returns IO Counters.
func (p *Process) IOCounters() (*IOCountersStat, error) {
return p.fillFromIO()
}
// NumCtxSwitches returns the number of the context switches of the process.
func (p *Process) NumCtxSwitches() (*NumCtxSwitchesStat, error) {
err := p.fillFromStatus()
if err != nil {
return nil, err
}
return p.numCtxSwitches, nil
}
// NumFDs returns the number of File Descriptors used by the process.
func (p *Process) NumFDs() (int32, error) {
numFds, _, err := p.fillFromfd()
return numFds, err
}
// NumThreads returns the number of threads used by the process.
func (p *Process) NumThreads() (int32, error) {
err := p.fillFromStatus()
if err != nil {
return 0, err
}
return p.numThreads, nil
}
// Threads returns a map of threads
//
// Notice: Not implemented yet. always returns empty map.
func (p *Process) Threads() (map[string]string, error) {
ret := make(map[string]string, 0)
return ret, nil
}
// Times returns CPU times of the process.
func (p *Process) Times() (*cpu.TimesStat, error) {
_, _, cpuTimes, _, _, err := p.fillFromStat()
if err != nil {
return nil, err
}
return cpuTimes, nil
}
// CPUAffinity returns CPU affinity of the process.
//
// Notice: Not implemented yet.
func (p *Process) CPUAffinity() ([]int32, error) {
return nil, common.ErrNotImplementedError
}
// MemoryInfo returns platform in-dependend memory information, such as RSS, VMS and Swap
func (p *Process) MemoryInfo() (*MemoryInfoStat, error) {
meminfo, _, err := p.fillFromStatm()
if err != nil {
return nil, err
}
return meminfo, nil
}
// MemoryInfoEx returns platform dependend memory information.
func (p *Process) MemoryInfoEx() (*MemoryInfoExStat, error) {
_, memInfoEx, err := p.fillFromStatm()
if err != nil {
return nil, err
}
return memInfoEx, nil
}
// Children returns a slice of Process of the process.
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
}
// OpenFiles returns a slice of OpenFilesStat opend by the process.
// OpenFilesStat includes a file path and file descriptor.
func (p *Process) OpenFiles() ([]OpenFilesStat, error) {
_, ofs, err := p.fillFromfd()
if err != nil {
return nil, err
}
ret := make([]OpenFilesStat, len(ofs))
for i, o := range ofs {
ret[i] = *o
}
return ret, nil
}
// Connections returns a slice of net.ConnectionStat used by the process.
// This returns all kind of the connection. This measn TCP, UDP or UNIX.
func (p *Process) Connections() ([]net.ConnectionStat, error) {
return net.ConnectionsPid("all", p.Pid)
}
// NetIOCounters returns NetIOCounters of the process.
func (p *Process) NetIOCounters(pernic bool) ([]net.IOCountersStat, error) {
filename := common.HostProc(strconv.Itoa(int(p.Pid)), "net/dev")
return net.IOCountersByFile(pernic, filename)
}
// IsRunning returns whether the process is running or not.
// Not implemented yet.
func (p *Process) IsRunning() (bool, error) {
return true, common.ErrNotImplementedError
}
// 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
**/
func limitToInt(val string) (int32, error) {
if val == "unlimited" {
return math.MaxInt32, nil
} else {
res, err := strconv.ParseInt(val, 10, 32)
if err != nil {
return 0, err
}
return int32(res), nil
}
}
// Get num_fds from /proc/(pid)/limits
func (p *Process) fillFromLimits() ([]RlimitStat, error) {
pid := p.Pid
limitsFile := common.HostProc(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 = limitToInt(str[len(str)-1])
if err != nil {
// On error remove last item an try once again since it can be unit or header line
str = str[:len(str)-1]
statItem.Hard, err = limitToInt(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 = limitToInt(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 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))
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) 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 len(cmdline) == 0 {
return nil, nil
}
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")
if len(p.name) >= 15 {
cmdlineSlice, err := p.CmdlineSlice()
if err != nil {
return err
}
if len(cmdlineSlice) > 0 {
extendedName := filepath.Base(cmdlineSlice[0])
if strings.HasPrefix(extendedName, p.name) {
p.name = extendedName
}
}
}
case "State":
p.status = value[0:1]
case "PPid", "Ppid":
pval, err := strconv.ParseInt(value, 10, 32)
if err != nil {
return err
}
p.parent = 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 "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.TimesStat, 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.TimesStat{
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
}
// Pids returns a slice of process ID list which are running now.
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
}