From cb0fa46cfbb99ecb48f68ef15696a4fb2bde0df1 Mon Sep 17 00:00:00 2001 From: strucoder Date: Thu, 6 Apr 2017 23:13:40 +0800 Subject: [PATCH 1/2] feat(dev): init --- pidusage.go | 152 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 152 insertions(+) create mode 100644 pidusage.go diff --git a/pidusage.go b/pidusage.go new file mode 100644 index 0000000..4e4b378 --- /dev/null +++ b/pidusage.go @@ -0,0 +1,152 @@ +package main + +import ( + "fmt" + "io/ioutil" + "math" + "os/exec" + "path" + "runtime" + "strconv" + "strings" +) + +// SysInfo will record cpu and memory data +type SysInfo struct { + CPU float64 + Memory float64 +} + +// Stat will store CUP time struct +type Stat struct { + utime float64 + stime float64 + cutime float64 + cstime float64 + start float64 + rss float64 + uptime float64 +} + +// type fn func(int) *SysInfo + +// var fnMap map[string]fn +var platform string +var history map[int]Stat + +// func wrapper(statType string) func(pid int) *SysInfo { +// return func(pid int) *SysInfo { +// return stat(pid, statType) +// } +// } +func init() { + platform = runtime.GOOS + if strings.Contains(platform, "win") { + platform = "win" + } + history = make(map[int]Stat) + // fnMap = make(map[string]fn) + // fnMap["darwin"] = wrapper("ps") + // fnMap["sunos"] = wrapper("ps") + // fnMap["freebsd"] = wrapper("ps") + // fnMap["aix"] = wrapper("ps") + // fnMap["linux"] = wrapper("proc") + // fnMap["netbsd"] = wrapper("proc") + // fnMap["win"] = wrapper("win") +} +func formatStdOut(stdout []byte, userfulIndex int) []string { + infoArr := strings.Split(string(stdout), "\n")[userfulIndex] + ret := strings.Fields(infoArr) + return ret +} + +func parseFloat(val string) float64 { + floatVal, _ := strconv.ParseFloat(val, 64) + return floatVal +} + +func stat(pid int, statType string) *SysInfo { + sysInfo := &SysInfo{} + _history := history[pid] + if statType == "ps" { + args := "-o pcpu,rss -p" + if platform == "aix" { + args = "-o pcpu,rssize -p" + } + stdout, _ := exec.Command("ps", args, strconv.Itoa(pid)).Output() + ret := formatStdOut(stdout, 1) + sysInfo.CPU = parseFloat(ret[0]) + sysInfo.Memory = parseFloat(ret[1]) + } else if statType == "proc" { + // default clkTck and pageSize + var clkTck float64 = 100 + var pageSize float64 = 4096 + + uptimeFileBytes, err := ioutil.ReadFile(path.Join("/proc", "uptime")) + uptime := parseFloat(strings.Split(string(uptimeFileBytes), " ")[0]) + fmt.Println("uptime", uptime) + + clkTckStdout, err := exec.Command("getconf", "CLK_TCK").Output() + if err == nil { + clkTck = parseFloat(formatStdOut(clkTckStdout, 0)[0]) + } + + pageSizeStdout, err := exec.Command("getconf", "PAGESIZE").Output() + if err == nil { + pageSize = parseFloat(formatStdOut(pageSizeStdout, 0)[0]) + } + + fmt.Println(clkTck, pageSize) + fmt.Println(path.Join("/proc", strconv.Itoa(pid), "stat")) + procStatFileBytes, err := ioutil.ReadFile(path.Join("/proc", strconv.Itoa(pid), "stat")) + infos := strings.Split(strings.SplitAfter(string(procStatFileBytes), ")")[1], " ") + stat := &Stat{ + utime: parseFloat(infos[12]), + stime: parseFloat(infos[13]), + cutime: parseFloat(infos[14]), + cstime: parseFloat(infos[15]), + start: parseFloat(infos[20]) / clkTck, + rss: parseFloat(infos[22]), + uptime: uptime, + } + + _stime := 0.0 + _utime := 0.0 + if _history.stime != 0.0 { + _stime = _history.stime + } + + if _history.utime != 0.0 { + _utime = _history.utime + } + total := stat.stime - _stime + stat.utime - _utime + total = total / clkTck + + seconds := stat.start - uptime + if _history.uptime != 0.0 { + seconds = uptime - _history.uptime + } + + seconds = math.Abs(seconds) + if seconds == 0 { + seconds = 1 + } + + history[pid] = *stat + fmt.Println(total) + sysInfo.CPU = (total / seconds) * 100 + sysInfo.Memory = stat.rss * pageSize + } + return sysInfo + +} + +// Stat will return current system CPU and memory data +// func Stat(pid int) *SysInfo { +// sysInfo := fnMap[platform](pid) +// return sysInfo +// } + +func main() { + stat(662, "proc") +} From 0382e975d50119e8be869ee803896c60af131e6f Mon Sep 17 00:00:00 2001 From: strucoder Date: Fri, 7 Apr 2017 22:56:20 +0800 Subject: [PATCH 2/2] feat(dev): support os sys except windows --- README.md | 38 ++++++++++++++++++++++++++++ pidusage.go | 64 ++++++++++++++++++++++-------------------------- pidusage_test.go | 0 3 files changed, 67 insertions(+), 35 deletions(-) create mode 100644 pidusage_test.go diff --git a/README.md b/README.md index a7925c4..a80dd9e 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,40 @@ # pidusage Cross-platform process cpu % and memory usage of a PID for golang + +Ideas from https://github.com/soyuka/pidusage but just use Golang + +## API + +```golang +import ( + "os" + "github.com/struCoder/pmgo/lib/utils" +) + +func printStat() { + sysInfo := pidusage.GetStat(os.Process.Pid) +} +``` + +## How it works + +A check on the `runtime.GOOS` is done to determine the method to use. + +### Linux +We use `/proc/{pid}/stat` in addition to the the `PAGE_SIZE` and the `CLK_TCK` direclty from `getconf()` command. Uptime comes from `proc/uptime` file because it's more accurate than the nodejs `os.uptime()`. + +Cpu usage is computed by following [those instructions](http://stackoverflow.com/questions/16726779/how-do-i-get-the-total-cpu-usage-of-an-application-from-proc-pid-stat/16736599#16736599). It keeps an history of the current processor time for the given pid so that the computed value gets more and more accurate. Don't forget to do `unmonitor(pid)` so that history gets cleared. +Cpu usage does not check the child process tree! + +Memory result is representing the RSS (resident set size) only by doing `rss*pagesize`, where `pagesize` is the result of `getconf PAGE_SIZE`. + +### On darwin, freebsd, solaris (tested on 10/11) +We use a fallback with the `ps -o pcpu,rss -p PID` command to get the same informations. + +Memory usage will also display the RSS only, process cpu usage might differ from a distribution to another. Please check the correspoding `man ps` for more insights on the subject. + +### On AIX +AIX is tricky because I have no AIX test environement, at the moment we use: `ps -o pcpu,rssize -p PID` but `/proc` results should be more accurate! If you're familiar with the AIX environment and know how to get the same results as we've got with Linux systems. + +### Windows +Next version will support diff --git a/pidusage.go b/pidusage.go index 4e4b378..717b5b2 100644 --- a/pidusage.go +++ b/pidusage.go @@ -1,7 +1,6 @@ -package main +package pidusage import ( - "fmt" "io/ioutil" "math" "os/exec" @@ -28,34 +27,37 @@ type Stat struct { uptime float64 } -// type fn func(int) *SysInfo +type fn func(int) *SysInfo -// var fnMap map[string]fn +var fnMap map[string]fn var platform string var history map[int]Stat +var eol string -// func wrapper(statType string) func(pid int) *SysInfo { -// return func(pid int) *SysInfo { -// return stat(pid, statType) -// } -// } +func wrapper(statType string) func(pid int) *SysInfo { + return func(pid int) *SysInfo { + return stat(pid, statType) + } +} func init() { platform = runtime.GOOS - if strings.Contains(platform, "win") { + eol = "\n" + if strings.Index(platform, "win") == 0 { platform = "win" + eol = "\r\n" } history = make(map[int]Stat) - // fnMap = make(map[string]fn) - // fnMap["darwin"] = wrapper("ps") - // fnMap["sunos"] = wrapper("ps") - // fnMap["freebsd"] = wrapper("ps") - // fnMap["aix"] = wrapper("ps") - // fnMap["linux"] = wrapper("proc") - // fnMap["netbsd"] = wrapper("proc") - // fnMap["win"] = wrapper("win") + fnMap = make(map[string]fn) + fnMap["darwin"] = wrapper("ps") + fnMap["sunos"] = wrapper("ps") + fnMap["freebsd"] = wrapper("ps") + fnMap["aix"] = wrapper("ps") + fnMap["linux"] = wrapper("proc") + fnMap["netbsd"] = wrapper("proc") + fnMap["win"] = wrapper("win") } func formatStdOut(stdout []byte, userfulIndex int) []string { - infoArr := strings.Split(string(stdout), "\n")[userfulIndex] + infoArr := strings.Split(string(stdout), eol)[userfulIndex] ret := strings.Fields(infoArr) return ret } @@ -76,7 +78,7 @@ func stat(pid int, statType string) *SysInfo { stdout, _ := exec.Command("ps", args, strconv.Itoa(pid)).Output() ret := formatStdOut(stdout, 1) sysInfo.CPU = parseFloat(ret[0]) - sysInfo.Memory = parseFloat(ret[1]) + sysInfo.Memory = parseFloat(ret[1]) * 1024 } else if statType == "proc" { // default clkTck and pageSize var clkTck float64 = 100 @@ -84,7 +86,6 @@ func stat(pid int, statType string) *SysInfo { uptimeFileBytes, err := ioutil.ReadFile(path.Join("/proc", "uptime")) uptime := parseFloat(strings.Split(string(uptimeFileBytes), " ")[0]) - fmt.Println("uptime", uptime) clkTckStdout, err := exec.Command("getconf", "CLK_TCK").Output() if err == nil { @@ -96,8 +97,6 @@ func stat(pid int, statType string) *SysInfo { pageSize = parseFloat(formatStdOut(pageSizeStdout, 0)[0]) } - fmt.Println(clkTck, pageSize) - fmt.Println(path.Join("/proc", strconv.Itoa(pid), "stat")) procStatFileBytes, err := ioutil.ReadFile(path.Join("/proc", strconv.Itoa(pid), "stat")) infos := strings.Split(strings.SplitAfter(string(procStatFileBytes), ")")[1], " ") stat := &Stat{ @@ -112,18 +111,18 @@ func stat(pid int, statType string) *SysInfo { _stime := 0.0 _utime := 0.0 - if _history.stime != 0.0 { + if _history.stime != 0 { _stime = _history.stime } - if _history.utime != 0.0 { + if _history.utime != 0 { _utime = _history.utime } total := stat.stime - _stime + stat.utime - _utime total = total / clkTck seconds := stat.start - uptime - if _history.uptime != 0.0 { + if _history.uptime != 0 { seconds = uptime - _history.uptime } @@ -133,7 +132,6 @@ func stat(pid int, statType string) *SysInfo { } history[pid] = *stat - fmt.Println(total) sysInfo.CPU = (total / seconds) * 100 sysInfo.Memory = stat.rss * pageSize } @@ -141,12 +139,8 @@ func stat(pid int, statType string) *SysInfo { } -// Stat will return current system CPU and memory data -// func Stat(pid int) *SysInfo { -// sysInfo := fnMap[platform](pid) -// return sysInfo -// } - -func main() { - stat(662, "proc") +// GetStat will return current system CPU and memory data +func GetStat(pid int) *SysInfo { + sysInfo := fnMap[platform](pid) + return sysInfo } diff --git a/pidusage_test.go b/pidusage_test.go new file mode 100644 index 0000000..e69de29