// +build linux package process import ( "encoding/json" "io/ioutil" "os" "path/filepath" "strconv" "strings" "syscall" common "github.com/influxdb/telegraf/plugins/system/ps/common" cpu "github.com/influxdb/telegraf/plugins/system/ps/cpu" host "github.com/influxdb/telegraf/plugins/system/ps/host" net "github.com/influxdb/telegraf/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 }