Go Language로 특정 Process의 CPU, Memory 사용량을 계산하고 싶다면,
아래의 코드를 Build해서 사용하면 된다.
참고: Linux, MacOS, Unix, Windows 모두 동작하는 코드임.
// Filename: proc_usage.go
package main
import (
"errors"
"fmt"
"io/ioutil"
"math"
"os/exec"
"path"
"runtime"
"strconv"
"strings"
"sync"
)
const (
statTypePS = "ps"
statTypeProc = "proc"
)
// SysInfo will record cpu and memory data
type SysInfo struct {
CPU float64
Memory float64
}
// Stat will store CPU time struct
type Stat struct {
utime float64
stime float64
cutime float64
cstime float64
start float64
rss float64
uptime float64
}
type fn func(int) (*SysInfo, error)
var fnMap map[string]fn
var platform string
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 init() {
platform = runtime.GOOS
if eol = "\n"; 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["openbsd"] = wrapper("proc")
fnMap["aix"] = wrapper("ps")
fnMap["linux"] = wrapper("proc")
fnMap["netbsd"] = wrapper("proc")
fnMap["win"] = wrapper("win")
if platform == "linux" || platform == "netbsd" || platform == "openbsd" {
initProc()
}
}
func initProc() {
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 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)
return ret
}
func parseFloat(val string) float64 {
floatVal, _ := strconv.ParseFloat(val, 64)
return floatVal
}
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 _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)
return sysInfo, err
}
// Filename: main.go
package main
import (
"os"
"fmt"
"time"
"strconv"
)
func main() {
myPid, _ := strconv.Atoi(os.Args[1])
for i:= 0; i < 100; i++ {
sysInfo, _ := GetStat(myPid)
fmt.Println("CPU Usage :", sysInfo.CPU)
fmt.Println("Mem Usage(RSS):", sysInfo.Memory)
time.Sleep(5 * time.Second)
}
}
위와 같이 Go source code를 모두 작성했다면, 아래처럼 build하고 실행하면 된다.
$ go mod init andrew.space/proc_usage
go: creating new go.mod: module andrew.space/proc_usage
go: to add module requirements and sums:
go mod tidy
$ go mod tidy
$ go build
$ ./proc_usage 4314
CPU Usage : 52.92167225853122
Mem Usage(RSS): 2.018664448e+09
CPU Usage : 39.800000000000004
Mem Usage(RSS): 2.018664448e+09
CPU Usage : 47.30538922366738
Mem Usage(RSS): 2.018664448e+09
...
...
top 명령으로 본 것과 결과가 동일했다.