pkg.re/essentialkaos/ek@v12.36.0+incompatible/system/process/process_cpu.go (about) 1 // +build linux 2 3 package process 4 5 // ////////////////////////////////////////////////////////////////////////////////// // 6 // // 7 // Copyright (c) 2021 ESSENTIAL KAOS // 8 // Apache License, Version 2.0 <https://www.apache.org/licenses/LICENSE-2.0> // 9 // // 10 // ////////////////////////////////////////////////////////////////////////////////// // 11 12 import ( 13 "bufio" 14 "errors" 15 "fmt" 16 "os" 17 "strconv" 18 "time" 19 20 "pkg.re/essentialkaos/ek.v12/strutil" 21 ) 22 23 // ////////////////////////////////////////////////////////////////////////////////// // 24 25 // Process state flags 26 const ( 27 STATE_RUNNING = "R" 28 STATE_SLEEPING = "S" 29 STATE_DISK_WAIT = "D" 30 STATE_ZOMBIE = "Z" 31 STATE_STOPPED = "T" 32 STATE_DEAD = "X" 33 STATE_WAKEKILL = "K" 34 STATE_WAKING = "W" 35 STATE_PARKED = "P" 36 ) 37 38 // ////////////////////////////////////////////////////////////////////////////////// // 39 40 // ProcInfo contains partial info from /proc/[PID]/stat 41 type ProcInfo struct { 42 PID int `json:"pid"` // The process ID 43 Comm string `json:"comm"` // The filename of the executable, in parentheses 44 State string `json:"state"` // Process state 45 PPID int `json:"ppid"` // The PID of the parent of this process 46 Session int `json:"session"` // The session ID of the process 47 TTYNR int `json:"tty_nr"` // The controlling terminal of the process 48 TPGid int `json:"tpgid"` // The ID of the foreground process group of the controlling terminal of the process 49 UTime uint64 `json:"utime"` // Amount of time that this process has been scheduled in user mode, measured in clock ticks 50 STime uint64 `json:"stime"` // Amount of time that this process has been scheduled in kernel mode, measured in clock ticks 51 CUTime uint64 `json:"cutime"` // Amount of time that this process's waited-for children have been scheduled in user mode, measured in clock ticks 52 CSTime uint64 `json:"cstime"` // Amount of time that this process's waited-for children have been scheduled in kernel mode, measured in clock ticks 53 Priority int `json:"priority"` // Priority 54 Nice int `json:"nice"` // The nice value 55 NumThreads int `json:"num_threads"` // Number of threads in this process 56 } 57 58 // ProcSample contains value for usage calculation 59 type ProcSample uint 60 61 // ////////////////////////////////////////////////////////////////////////////////// // 62 63 // ToSample converts ProcInfo to ProcSample for CPU usage calculation 64 func (pi *ProcInfo) ToSample() ProcSample { 65 return ProcSample(pi.UTime + pi.STime + pi.CUTime + pi.CSTime) 66 } 67 68 // ////////////////////////////////////////////////////////////////////////////////// // 69 70 // GetInfo returns process info from procfs 71 func GetInfo(pid int) (*ProcInfo, error) { 72 fd, err := os.OpenFile(procFS+"/"+strconv.Itoa(pid)+"/stat", os.O_RDONLY, 0) 73 74 if err != nil { 75 return nil, err 76 } 77 78 defer fd.Close() 79 80 r := bufio.NewReader(fd) 81 text, _ := r.ReadString('\n') 82 83 if len(text) < 20 { 84 return nil, errors.New("Can't parse stat file for given process") 85 } 86 87 return parseStatData(text) 88 } 89 90 // codebeat:disable[LOC,ABC] 91 92 // GetSample returns ProcSample for CPU usage calculation 93 func GetSample(pid int) (ProcSample, error) { 94 fd, err := os.OpenFile(procFS+"/"+strconv.Itoa(pid)+"/stat", os.O_RDONLY, 0) 95 96 if err != nil { 97 return 0, err 98 } 99 100 defer fd.Close() 101 102 r := bufio.NewReader(fd) 103 text, _ := r.ReadString('\n') 104 105 if len(text) < 20 { 106 return 0, errors.New("Can't parse stat file for given process") 107 } 108 109 return parseSampleData(text) 110 } 111 112 // codebeat:enable[LOC,ABC] 113 114 // CalculateCPUUsage calculates CPU usage 115 func CalculateCPUUsage(s1, s2 ProcSample, duration time.Duration) float64 { 116 total := float64(s2 - s1) 117 seconds := float64(duration) / float64(time.Second) 118 119 return 100.0 * ((total / getHZ()) / seconds) 120 } 121 122 // ////////////////////////////////////////////////////////////////////////////////// // 123 124 // codebeat:disable[LOC,ABC] 125 126 // parseStatData parses CPU stats data 127 func parseStatData(text string) (*ProcInfo, error) { 128 var err error 129 130 info := &ProcInfo{} 131 132 for i := 0; i < 20; i++ { 133 switch i { 134 case 0: 135 info.PID, err = parseIntField(strutil.ReadField(text, i, true), i) 136 case 1: 137 info.Comm = strutil.ReadField(text, i, true) 138 case 2: 139 info.State = strutil.ReadField(text, i, true) 140 case 3: 141 info.PPID, err = parseIntField(strutil.ReadField(text, i, true), i) 142 case 5: 143 info.Session, err = parseIntField(strutil.ReadField(text, i, true), i) 144 case 6: 145 info.TTYNR, err = parseIntField(strutil.ReadField(text, i, true), i) 146 case 7: 147 info.TPGid, err = parseIntField(strutil.ReadField(text, i, true), i) 148 case 13: 149 info.UTime, err = parseUint64Field(strutil.ReadField(text, i, true), i) 150 case 14: 151 info.STime, err = parseUint64Field(strutil.ReadField(text, i, true), i) 152 case 15: 153 info.CUTime, err = parseUint64Field(strutil.ReadField(text, i, true), i) 154 case 16: 155 info.CSTime, err = parseUint64Field(strutil.ReadField(text, i, true), i) 156 case 17: 157 info.Priority, err = parseIntField(strutil.ReadField(text, i, true), i) 158 case 18: 159 info.Nice, err = parseIntField(strutil.ReadField(text, i, true), i) 160 case 19: 161 info.NumThreads, err = parseIntField(strutil.ReadField(text, i, true), i) 162 } 163 164 if err != nil { 165 return nil, err 166 } 167 } 168 169 return info, nil 170 } 171 172 // parseSampleData extracts CPU sample info 173 func parseSampleData(text string) (ProcSample, error) { 174 var err error 175 var utime, stime, cutime, cstime uint64 176 177 for i := 13; i < 17; i++ { 178 value := strutil.ReadField(text, i, true) 179 180 switch i { 181 case 13: 182 utime, err = parseUint64Field(value, i) 183 case 14: 184 stime, err = parseUint64Field(value, i) 185 case 15: 186 cutime, err = parseUint64Field(value, i) 187 case 16: 188 cstime, err = parseUint64Field(value, i) 189 } 190 191 if err != nil { 192 return ProcSample(0), err 193 } 194 } 195 196 return ProcSample(utime + stime + cutime + cstime), nil 197 } 198 199 // codebeat:enable[LOC,ABC] 200 201 // parseIntField parses int value of field 202 func parseIntField(s string, field int) (int, error) { 203 v, err := strconv.Atoi(s) 204 205 if err != nil { 206 return 0, fmt.Errorf("Can't parse stat field %d: %w", field, err) 207 } 208 209 return v, nil 210 } 211 212 // parseIntField parses uint value of field 213 func parseUint64Field(s string, field int) (uint64, error) { 214 v, err := strconv.ParseUint(s, 10, 64) 215 216 if err != nil { 217 return 0, fmt.Errorf("Can't parse stat field %d: %w", field, err) 218 } 219 220 return v, nil 221 } 222 223 // getHZ returns number of processor clock ticks per second 224 func getHZ() float64 { 225 // CLK_TCK is a constant on Linux 226 // https://git.musl-libc.org/cgit/musl/tree/src/conf/sysconf.c#n30 227 return 100.0 228 }