// +build linux package gopsutil import ( "io/ioutil" "os" "path/filepath" "strconv" "strings" "sync" "syscall" ) const ( PRIO_PROCESS = 0 // linux/resource.h ) // Memory_info_ex is different between OSes type Memory_info_exStat 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 } type Memory_mapsStat struct { Path string `json:"path"` Rss uint64 `json:"rss"` Size uint64 `json:"size"` Pss uint64 `json:"pss"` Shared_clean uint64 `json:"shared_clean"` Shared_dirty uint64 `json:"shared_dirty"` Private_clean uint64 `json:"private_clean"` Private_dirty uint64 `json:"private_dirty"` Referenced uint64 `json:"referenced"` Anonymous uint64 `json:"anonymous"` Swap uint64 `json:"swap"` } type fillFunc func(pid int32, p *Process) error func NewProcess(pid int32) (*Process, error) { p := &Process{ Pid: int32(pid), } // Fill Process information from fillFuncs var wg sync.WaitGroup funcs := []fillFunc{fillFromStat, fillFromStatus, fillFromfd, fillFromCmdline, fillFromStatm, fillFromCwd, fillFromExe} wg.Add(len(funcs)) for _, f := range funcs { go func(f fillFunc) { wg.Done() f(pid, p) }(f) } wg.Wait() return p, nil } // Get memory maps from /proc/(pid)/smaps // This is a function. Because Memory map information is very big. func (p *Process) Memory_Maps() (*[]Memory_mapsStat, error) { pid := p.Pid ret := make([]Memory_mapsStat, 0) 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 get_block := func(first_line []string, block []string) Memory_mapsStat { m := Memory_mapsStat{} m.Path = first_line[len(first_line)-1] for _, line := range block { field := strings.Split(line, ":") if len(field) < 2 { continue } v := strings.Trim(field[1], " kB") // remove last "kB" switch field[0] { case "Size": m.Size = parseUint64(v) case "Rss": m.Rss = parseUint64(v) case "Pss": m.Pss = parseUint64(v) case "Shared_Clean": m.Shared_clean = parseUint64(v) case "Shared_Dirty": m.Shared_dirty = parseUint64(v) case "Private_Clean": m.Private_clean = parseUint64(v) case "Private_Dirty": m.Private_dirty = parseUint64(v) case "Referenced": m.Referenced = parseUint64(v) case "Anonymous": m.Anonymous = parseUint64(v) case "Swap": m.Swap = parseUint64(v) } } return m } 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 { ret = append(ret, get_block(field, blocks)) } // starts new block blocks = make([]string, 16) } else { blocks = append(blocks, line) } } return &ret, nil } // Parse to int32 without error func parseInt32(val string) int32 { vv, _ := strconv.ParseInt(val, 10, 32) return int32(vv) } // Parse to uint64 without error func parseUint64(val string) uint64 { vv, _ := strconv.ParseInt(val, 10, 64) return uint64(vv) } // Get num_fds from /proc/(pid)/fd func fillFromfd(pid int32, p *Process) error { statPath := filepath.Join("/", "proc", strconv.Itoa(int(pid)), "fd") d, err := os.Open(statPath) if err != nil { return err } defer d.Close() fnames, err := d.Readdirnames(-1) num_fds := len(fnames) p.Num_fds = int32(num_fds) openfiles := make([]Open_filesStat, num_fds) for _, fd := range fnames{ fpath := filepath.Join(statPath, fd) filepath, err := os.Readlink(fpath) if err != nil { continue } o := Open_filesStat{ Path: filepath, Fd: parseUint64(fd), } openfiles = append(openfiles, o) } p.Open_files = openfiles return nil } // Get cwd from /proc/(pid)/cwd func fillFromCwd(pid int32, p *Process) error { cwdPath := filepath.Join("/", "proc", strconv.Itoa(int(pid)), "cwd") cwd, err := os.Readlink(cwdPath) if err != nil { return err } p.Cwd = string(cwd) return nil } // Get exe from /proc/(pid)/exe func fillFromExe(pid int32, p *Process) error { exePath := filepath.Join("/", "proc", strconv.Itoa(int(pid)), "exe") exe, err := os.Readlink(exePath) if err != nil { return err } p.Exe = string(exe) return nil } // Get cmdline from /proc/(pid)/cmdline func fillFromCmdline(pid int32, p *Process) error { cmdPath := filepath.Join("/", "proc", strconv.Itoa(int(pid)), "cmdline") cmdline, err := ioutil.ReadFile(cmdPath) if err != nil { return err } // remove \u0000 p.Cmdline = strings.TrimFunc(string(cmdline), func(r rune) bool { if r == '\u0000' { return true } return false }) return nil } // Get memory info from /proc/(pid)/statm func fillFromStatm(pid int32, p *Process) error { memPath := filepath.Join("/", "proc", strconv.Itoa(int(pid)), "statm") contents, err := ioutil.ReadFile(memPath) if err != nil { return err } fields := strings.Split(string(contents), " ") rss := parseUint64(fields[0]) * PAGESIZE vms := parseUint64(fields[1]) * PAGESIZE p.Memory_info = Memory_infoStat{ RSS: rss, VMS: vms, } p.Memory_info_ex = Memory_info_exStat{ RSS: rss, VMS: vms, Shared: parseUint64(fields[2]) * PAGESIZE, Text: parseUint64(fields[3]) * PAGESIZE, Lib: parseUint64(fields[4]) * PAGESIZE, Dirty: parseUint64(fields[5]) * PAGESIZE, } return nil } // Get various status from /proc/(pid)/status func fillFromStatus(pid int32, p *Process) error { 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") for _, line := range lines { field := strings.Split(line, ":") if len(field) < 2 { continue } // fmt.Printf("%s ->__%s__\n", field[0], strings.Trim(field[1], " \t")) switch field[0] { case "Name": p.Name = strings.Trim(field[1], " \t") case "State": // get between "(" and ")" s := strings.Index(field[1], "(") + 1 e := strings.Index(field[1], "(") + 1 p.Status = field[1][s:e] // case "PPid": // filled by fillFromStat case "Uid": for _, i := range strings.Split(field[1], "\t") { p.Uids = append(p.Uids, parseInt32(i)) } case "Gid": for _, i := range strings.Split(field[1], "\t") { p.Gids = append(p.Uids, parseInt32(i)) } case "Threads": p.Num_Threads = parseInt32(field[1]) } } return nil } func fillFromStat(pid int32, p *Process) error { statPath := filepath.Join("/", "proc", strconv.Itoa(int(pid)), "stat") contents, err := ioutil.ReadFile(statPath) if err != nil { return err } fields := strings.Fields(string(contents)) termmap, err := getTerminalMap() if err == nil { p.Terminal = termmap[parseUint64(fields[6])] } p.Ppid = parseInt32(fields[3]) utime, _ := strconv.ParseFloat(fields[13], 64) stime, _ := strconv.ParseFloat(fields[14], 64) p.Cpu_times = CPU_TimesStat{ Cpu: "cpu", User: float32(utime * (1000 / CLOCK_TICKS)), System: float32(stime * (1000 / CLOCK_TICKS)), } boot_time, _ := Boot_time() ctime := ((parseUint64(fields[21]) / uint64(CLOCK_TICKS)) + uint64(boot_time)) * 1000 p.Create_time = int64(ctime) // p.Nice = parseInt32(fields[18]) // use syscall instead of parse Stat file nice, _ := syscall.Getpriority(PRIO_PROCESS, int(pid)) p.Nice = int32(nice) // FIXME: is this true? return nil } func processes() ([]*Process, error) { ret := make([]*Process, 0) pids, err := Pids() if err != nil { return ret, err } for _, pid := range pids { p, err := NewProcess(pid) if err != nil { continue // FIXME: should return error? } ret = append(ret, p) } return ret, nil } func Pids() ([]int32, error) { ret := make([]int32, 0) 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 }