From 20b6c5435f9bc08ad7d705b27efa9f98ccf77b7c Mon Sep 17 00:00:00 2001 From: liyong Date: Sun, 16 May 2021 16:05:57 +0800 Subject: [PATCH 1/2] Optimize GetStat function on Linux platform. GetStat function benchmark results Before optimize $ go clean -testcache && go test -test.v -bench=BenchmarkGetStat goos: linux goarch: amd64 pkg: github.com/struCoder/pidusage BenchmarkGetStat BenchmarkGetStat-12 470 2690727 ns/op PASS ok github.com/struCoder/pidusage 1.533s After optimize $ go clean -testcache && go test -test.v -bench=BenchmarkGetStat goos: linux goarch: amd64 pkg: github.com/struCoder/pidusage BenchmarkGetStat BenchmarkGetStat-12 28416 36234 ns/op PASS ok github.com/struCoder/pidusage 1.472s --- go.mod | 3 +++ pidusage.go | 43 ++++++++++++++++++++++++++++--------------- pidusage_test.go | 33 +++++++++++++++++++++++++++++++++ 3 files changed, 64 insertions(+), 15 deletions(-) create mode 100644 go.mod diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..63b4966 --- /dev/null +++ b/go.mod @@ -0,0 +1,3 @@ +module github.com/struCoder/pidusage + +go 1.13 diff --git a/pidusage.go b/pidusage.go index b29ba7f..2b51459 100644 --- a/pidusage.go +++ b/pidusage.go @@ -37,6 +37,10 @@ var history map[int]Stat var historyLock sync.Mutex var eol string +// Linux platform +var clkTck float64 = 100 // default +var pageSize float64 = 4096 // default + func wrapper(statType string) func(pid int) (*SysInfo, error) { return func(pid int) (*SysInfo, error) { return stat(pid, statType) @@ -57,7 +61,25 @@ func init() { fnMap["linux"] = wrapper("proc") fnMap["netbsd"] = wrapper("proc") fnMap["win"] = wrapper("win") + + if platform == "linux" { + initLinux() + } +} + +func initLinux() { + 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]) + } + } + func formatStdOut(stdout []byte, userfulIndex int) []string { infoArr := strings.Split(string(stdout), eol)[userfulIndex] ret := strings.Fields(infoArr) @@ -85,24 +107,16 @@ func stat(pid int, statType string) (*SysInfo, error) { 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]) + if err != nil { + return nil, err } + uptime := parseFloat(strings.Split(string(uptimeFileBytes), " ")[0]) procStatFileBytes, err := ioutil.ReadFile(path.Join("/proc", strconv.Itoa(pid), "stat")) + if err != nil { + return nil, err + } splitAfter := strings.SplitAfter(string(procStatFileBytes), ")") if len(splitAfter) == 0 || len(splitAfter) == 1 { @@ -148,7 +162,6 @@ func stat(pid int, statType string) (*SysInfo, error) { sysInfo.Memory = stat.rss * pageSize } return sysInfo, nil - } // GetStat will return current system CPU and memory data diff --git a/pidusage_test.go b/pidusage_test.go index bfd2193..f7d5cbd 100644 --- a/pidusage_test.go +++ b/pidusage_test.go @@ -1 +1,34 @@ package pidusage + +import ( + "os" + "testing" +) + +var pid = os.Getpid() + +func BenchmarkGetStat(b *testing.B) { + for i := 0; i < b.N; i++ { + GetStat(pid) + } +} + +// Before optimize +// $ go clean -testcache && go test -test.v -bench=BenchmarkGetStat +// goos: linux +// goarch: amd64 +// pkg: github.com/struCoder/pidusage +// BenchmarkGetStat +// BenchmarkGetStat-12 470 2690727 ns/op +// PASS +// ok github.com/struCoder/pidusage 1.533s + +// After optimize +// $ go clean -testcache && go test -test.v -bench=BenchmarkGetStat +// goos: linux +// goarch: amd64 +// pkg: github.com/struCoder/pidusage +// BenchmarkGetStat +// BenchmarkGetStat-12 28416 36234 ns/op +// PASS +// ok github.com/struCoder/pidusage 1.472s From 17ee5167f5281d1a48fa4fe88ed73448b8f41f2c Mon Sep 17 00:00:00 2001 From: liyong Date: Sun, 16 May 2021 16:44:28 +0800 Subject: [PATCH 2/2] Refactor code. --- pidusage.go | 173 +++++++++++++++++++++++++++++----------------------- 1 file changed, 98 insertions(+), 75 deletions(-) diff --git a/pidusage.go b/pidusage.go index 2b51459..e204547 100644 --- a/pidusage.go +++ b/pidusage.go @@ -2,6 +2,7 @@ package pidusage import ( "errors" + "fmt" "io/ioutil" "math" "os/exec" @@ -12,6 +13,11 @@ import ( "sync" ) +const ( + statTypePS = "ps" + statTypeProc = "proc" +) + // SysInfo will record cpu and memory data type SysInfo struct { CPU float64 @@ -41,11 +47,6 @@ var eol string var clkTck float64 = 100 // default var pageSize float64 = 4096 // default -func wrapper(statType string) func(pid int) (*SysInfo, error) { - return func(pid int) (*SysInfo, error) { - return stat(pid, statType) - } -} func init() { platform = runtime.GOOS if eol = "\n"; strings.Index(platform, "win") == 0 { @@ -62,12 +63,12 @@ func init() { fnMap["netbsd"] = wrapper("proc") fnMap["win"] = wrapper("win") - if platform == "linux" { - initLinux() + if platform == "linux" || platform == "netbsd" { + initProc() } } -func initLinux() { +func initProc() { clkTckStdout, err := exec.Command("getconf", "CLK_TCK").Output() if err == nil { clkTck = parseFloat(formatStdOut(clkTckStdout, 0)[0]) @@ -80,6 +81,12 @@ func initLinux() { } +func wrapper(statType string) func(pid int) (*SysInfo, error) { + return func(pid int) (*SysInfo, error) { + return stat(pid, statType) + } +} + func formatStdOut(stdout []byte, userfulIndex int) []string { infoArr := strings.Split(string(stdout), eol)[userfulIndex] ret := strings.Fields(infoArr) @@ -91,79 +98,95 @@ func parseFloat(val string) float64 { return floatVal } -func stat(pid int, statType string) (*SysInfo, error) { +func statFromPS(pid int) (*SysInfo, error) { + sysInfo := &SysInfo{} + 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) + if len(ret) == 0 { + return sysInfo, errors.New("Can't find process with this PID: " + strconv.Itoa(pid)) + } + sysInfo.CPU = parseFloat(ret[0]) + sysInfo.Memory = parseFloat(ret[1]) * 1024 + return sysInfo, nil +} + +func statFromProc(pid int) (*SysInfo, error) { sysInfo := &SysInfo{} + uptimeFileBytes, err := ioutil.ReadFile(path.Join("/proc", "uptime")) + if err != nil { + return nil, err + } + uptime := parseFloat(strings.Split(string(uptimeFileBytes), " ")[0]) + + procStatFileBytes, err := ioutil.ReadFile(path.Join("/proc", strconv.Itoa(pid), "stat")) + if err != nil { + return nil, err + } + splitAfter := strings.SplitAfter(string(procStatFileBytes), ")") + + if len(splitAfter) == 0 || len(splitAfter) == 1 { + return sysInfo, errors.New("Can't find process with this PID: " + strconv.Itoa(pid)) + } + infos := strings.Split(splitAfter[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 + + historyLock.Lock() + defer historyLock.Unlock() + _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) - if len(ret) == 0 { - return sysInfo, errors.New("Can't find process with this PID: " + strconv.Itoa(pid)) - } - sysInfo.CPU = parseFloat(ret[0]) - sysInfo.Memory = parseFloat(ret[1]) * 1024 - } else if statType == "proc" { - uptimeFileBytes, err := ioutil.ReadFile(path.Join("/proc", "uptime")) - if err != nil { - return nil, err - } - uptime := parseFloat(strings.Split(string(uptimeFileBytes), " ")[0]) - - procStatFileBytes, err := ioutil.ReadFile(path.Join("/proc", strconv.Itoa(pid), "stat")) - if err != nil { - return nil, err - } - splitAfter := strings.SplitAfter(string(procStatFileBytes), ")") - - if len(splitAfter) == 0 || len(splitAfter) == 1 { - return sysInfo, errors.New("Can't find process with this PID: " + strconv.Itoa(pid)) - } - infos := strings.Split(splitAfter[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 - } - - historyLock.Lock() - history[pid] = *stat - historyLock.Unlock() - sysInfo.CPU = (total / seconds) * 100 - sysInfo.Memory = stat.rss * pageSize + + 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, nil } +func stat(pid int, statType string) (*SysInfo, error) { + switch statType { + case statTypePS: + return statFromPS(pid) + case statTypeProc: + return statFromProc(pid) + default: + return nil, fmt.Errorf("Unsupported OS %s", runtime.GOOS) + } +} + // GetStat will return current system CPU and memory data func GetStat(pid int) (*SysInfo, error) { sysInfo, err := fnMap[platform](pid)