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