598 lines
14 KiB
Go
598 lines
14 KiB
Go
// +build linux
|
|
|
|
package process
|
|
|
|
import (
|
|
"encoding/json"
|
|
"io/ioutil"
|
|
"os"
|
|
"path/filepath"
|
|
"strconv"
|
|
"strings"
|
|
"syscall"
|
|
|
|
common "github.com/influxdb/tivan/plugins/system/ps/common"
|
|
cpu "github.com/influxdb/tivan/plugins/system/ps/cpu"
|
|
host "github.com/influxdb/tivan/plugins/system/ps/host"
|
|
net "github.com/influxdb/tivan/plugins/system/ps/net"
|
|
)
|
|
|
|
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()
|
|
}
|
|
func (p *Process) Cmdline() (string, error) {
|
|
return p.fillFromCmdline()
|
|
}
|
|
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) {
|
|
return nil, common.NotImplementedError
|
|
}
|
|
func (p *Process) Status() (string, error) {
|
|
return p.status, nil
|
|
}
|
|
func (p *Process) Uids() ([]int32, error) {
|
|
return p.uids, nil
|
|
}
|
|
func (p *Process) Gids() ([]int32, error) {
|
|
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) {
|
|
return p.numCtxSwitches, nil
|
|
}
|
|
func (p *Process) NumFDs() (int32, error) {
|
|
return 0, common.NotImplementedError
|
|
}
|
|
func (p *Process) NumThreads() (int32, error) {
|
|
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) {
|
|
return p.memInfo, nil
|
|
}
|
|
func (p *Process) MemoryInfoEx() (*MemoryInfoExStat, error) {
|
|
_, memInfoEx, err := p.fillFromStatm()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return memInfoEx, nil
|
|
}
|
|
func (p *Process) MemoryPercent() (float32, error) {
|
|
return 0, common.NotImplementedError
|
|
}
|
|
|
|
func (p *Process) Children() ([]*Process, error) {
|
|
return nil, common.NotImplementedError
|
|
}
|
|
|
|
func (p *Process) OpenFiles() ([]OpenFilesStat, error) {
|
|
return nil, common.NotImplementedError
|
|
}
|
|
|
|
func (p *Process) Connections() ([]net.NetConnectionStat, error) {
|
|
return nil, common.NotImplementedError
|
|
}
|
|
|
|
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 := filepath.Join("/", "proc", 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 := filepath.Join("/", "proc", 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 := filepath.Join("/", "proc", 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 := filepath.Join("/", "proc", 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 := filepath.Join("/", "proc", 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
|
|
}
|
|
|
|
// Get IO status from /proc/(pid)/io
|
|
func (p *Process) fillFromIO() (*IOCountersStat, error) {
|
|
pid := p.Pid
|
|
ioPath := filepath.Join("/", "proc", 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 := filepath.Join("/", "proc", 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 := filepath.Join("/", "proc", 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 := filepath.Join("/", "proc", strconv.Itoa(int(pid)), "stat")
|
|
contents, err := ioutil.ReadFile(statPath)
|
|
if err != nil {
|
|
return "", 0, nil, 0, 0, err
|
|
}
|
|
fields := strings.Fields(string(contents))
|
|
|
|
termmap, err := getTerminalMap()
|
|
terminal := ""
|
|
if err == nil {
|
|
t, err := strconv.ParseUint(fields[6], 10, 64)
|
|
if err != nil {
|
|
return "", 0, nil, 0, 0, err
|
|
}
|
|
terminal = termmap[t]
|
|
}
|
|
|
|
ppid, err := strconv.ParseInt(fields[3], 10, 32)
|
|
if err != nil {
|
|
return "", 0, nil, 0, 0, err
|
|
}
|
|
utime, err := strconv.ParseFloat(fields[13], 64)
|
|
if err != nil {
|
|
return "", 0, nil, 0, 0, err
|
|
}
|
|
|
|
stime, err := strconv.ParseFloat(fields[14], 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[21], 10, 64)
|
|
if err != nil {
|
|
return "", 0, nil, 0, 0, err
|
|
}
|
|
|
|
ctime := ((t / uint64(ClockTicks)) + uint64(bootTime)) * 1000
|
|
createTime := int64(ctime)
|
|
|
|
// 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("/proc")
|
|
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
|
|
}
|