Go Language로 특정 Process의 CPU, Memory 사용량을 계산하고 싶다면,
아래의 코드를 Build해서 사용하면 된다.
참고: Linux, MacOS, Unix, Windows 모두 동작하는 코드임.
| |
| |
| package main |
| |
| import ( |
| "errors" |
| "fmt" |
| "io/ioutil" |
| "math" |
| "os/exec" |
| "path" |
| "runtime" |
| "strconv" |
| "strings" |
| "sync" |
| ) |
| |
| const ( |
| statTypePS = "ps" |
| statTypeProc = "proc" |
| ) |
| |
| |
| type SysInfo struct { |
| CPU float64 |
| Memory float64 |
| } |
| |
| |
| 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 |
| |
| |
| var clkTck float64 = 100 |
| var pageSize float64 = 4096 |
| |
| 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) |
| } |
| } |
| |
| |
| func GetStat(pid int) (*SysInfo, error) { |
| sysInfo, err := fnMap[platform](pid) |
| return sysInfo, err |
| } |
| |
| |
| 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 명령으로 본 것과 결과가 동일했다.