Merge pull request #1 from struCoder/dev

feat(dev): support os sys except windows
This commit is contained in:
David 大伟 2017-04-07 22:57:43 +08:00 committed by GitHub
commit f84800df51
3 changed files with 184 additions and 0 deletions

View File

@ -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

146
pidusage.go Normal file
View File

@ -0,0 +1,146 @@
package pidusage
import (
"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
var eol string
func wrapper(statType string) func(pid int) *SysInfo {
return func(pid int) *SysInfo {
return stat(pid, statType)
}
}
func init() {
platform = runtime.GOOS
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")
}
func formatStdOut(stdout []byte, userfulIndex int) []string {
infoArr := strings.Split(string(stdout), eol)[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]) * 1024
} 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])
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])
}
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 {
_stime = _history.stime
}
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 {
seconds = uptime - _history.uptime
}
seconds = math.Abs(seconds)
if seconds == 0 {
seconds = 1
}
history[pid] = *stat
sysInfo.CPU = (total / seconds) * 100
sysInfo.Memory = stat.rss * pageSize
}
return sysInfo
}
// GetStat will return current system CPU and memory data
func GetStat(pid int) *SysInfo {
sysInfo := fnMap[platform](pid)
return sysInfo
}

0
pidusage_test.go Normal file
View File