pkg.re/essentialkaos/ek.10@v12.41.0+incompatible/system/info_cpu_linux.go (about) 1 package system 2 3 // ////////////////////////////////////////////////////////////////////////////////// // 4 // // 5 // Copyright (c) 2022 ESSENTIAL KAOS // 6 // Apache License, Version 2.0 <https://www.apache.org/licenses/LICENSE-2.0> // 7 // // 8 // ////////////////////////////////////////////////////////////////////////////////// // 9 10 import ( 11 "bufio" 12 "errors" 13 "io/ioutil" 14 "strconv" 15 "strings" 16 "time" 17 18 "pkg.re/essentialkaos/ek.v12/strutil" 19 ) 20 21 // ////////////////////////////////////////////////////////////////////////////////// // 22 23 // Path to file with CPU stats in procfs 24 var procStatFile = "/proc/stat" 25 26 // Path to file with info about CPU 27 var cpuInfoFile = "/proc/cpuinfo" 28 29 // Files with CPU info 30 var ( 31 cpuPossibleFile = "/sys/devices/system/cpu/possible" 32 cpuPresentFile = "/sys/devices/system/cpu/present" 33 cpuOnlineFile = "/sys/devices/system/cpu/online" 34 cpuOfflineFile = "/sys/devices/system/cpu/offline" 35 ) 36 37 // ////////////////////////////////////////////////////////////////////////////////// // 38 39 // GetCPUUsage returns info about CPU usage 40 func GetCPUUsage(duration time.Duration) (*CPUUsage, error) { 41 c1, err := GetCPUStats() 42 43 if err != nil { 44 return nil, err 45 } 46 47 time.Sleep(duration) 48 49 c2, err := GetCPUStats() 50 51 if err != nil { 52 return nil, err 53 } 54 55 return CalculateCPUUsage(c1, c2), nil 56 } 57 58 // It's ok to have so complex method for calculation 59 // codebeat:disable[CYCLO] 60 61 // CalculateCPUUsage calcualtes CPU usage based on CPUStats 62 func CalculateCPUUsage(c1, c2 *CPUStats) *CPUUsage { 63 prevIdle := c1.Idle + c1.Wait 64 idle := c2.Idle + c2.Wait 65 66 prevNonIdle := c1.User + c1.Nice + c1.System + c1.IRQ + c1.SRQ + c1.Steal 67 nonIdle := c2.User + c2.Nice + c2.System + c2.IRQ + c2.SRQ + c2.Steal 68 69 prevTotal := prevIdle + prevNonIdle 70 total := idle + nonIdle 71 72 totalDiff := float64(total - prevTotal) 73 idleDiff := float64(idle - prevIdle) 74 allTotalDiff := float64(c2.Total - c1.Total) 75 76 return &CPUUsage{ 77 System: (float64(c2.System-c1.System) / allTotalDiff) * 100, 78 User: (float64(c2.User-c1.User) / allTotalDiff) * 100, 79 Nice: (float64(c2.Nice-c1.Nice) / allTotalDiff) * 100, 80 Wait: (float64(c2.Wait-c1.Wait) / allTotalDiff) * 100, 81 Idle: (float64(c2.Idle-c1.Idle) / allTotalDiff) * 100, 82 Average: ((totalDiff - idleDiff) / totalDiff) * 100.0, 83 Count: c2.Count, 84 } 85 } 86 87 // codebeat:enable[CYCLO] 88 89 // GetCPUStats returns basic CPU stats 90 func GetCPUStats() (*CPUStats, error) { 91 s, closer, err := getFileScanner(procStatFile) 92 93 if err != nil { 94 return nil, err 95 } 96 97 defer closer() 98 99 return parseCPUStats(s) 100 } 101 102 // GetCPUInfo returns slice with info about CPUs 103 func GetCPUInfo() ([]*CPUInfo, error) { 104 s, closer, err := getFileScanner(cpuInfoFile) 105 106 if err != nil { 107 return nil, err 108 } 109 110 defer closer() 111 112 return parseCPUInfo(s) 113 } 114 115 // GetCPUCount returns info about CPU 116 func GetCPUCount() (CPUCount, error) { 117 possible, err := ioutil.ReadFile(cpuPossibleFile) 118 119 if err != nil { 120 return CPUCount{}, err 121 } 122 123 present, err := ioutil.ReadFile(cpuPresentFile) 124 125 if err != nil { 126 return CPUCount{}, err 127 } 128 129 online, err := ioutil.ReadFile(cpuOnlineFile) 130 131 if err != nil { 132 return CPUCount{}, err 133 } 134 135 offline, err := ioutil.ReadFile(cpuOfflineFile) 136 137 if err != nil { 138 return CPUCount{}, err 139 } 140 141 return CPUCount{ 142 Possible: parseCPUCountInfo(string(possible)), 143 Present: parseCPUCountInfo(string(present)), 144 Online: parseCPUCountInfo(string(online)), 145 Offline: parseCPUCountInfo(string(offline)), 146 }, nil 147 } 148 149 // ////////////////////////////////////////////////////////////////////////////////// // 150 151 // codebeat:disable[LOC,ABC,CYCLO] 152 153 // parseCPUStats parses cpu stats data 154 func parseCPUStats(s *bufio.Scanner) (*CPUStats, error) { 155 var err error 156 157 stats := &CPUStats{} 158 159 for s.Scan() { 160 text := s.Text() 161 162 if len(text) < 4 || text[:3] != "cpu" { 163 continue 164 } 165 166 if text[:4] != "cpu " { 167 stats.Count++ 168 continue 169 } 170 171 stats.User, err = strconv.ParseUint(strutil.ReadField(text, 1, true), 10, 64) 172 173 if err != nil { 174 return nil, errors.New("Can't parse field 1 as unsigned integer in " + procStatFile) 175 } 176 177 stats.Nice, err = strconv.ParseUint(strutil.ReadField(text, 2, true), 10, 64) 178 179 if err != nil { 180 return nil, errors.New("Can't parse field 2 as unsigned integer in " + procStatFile) 181 } 182 183 stats.System, err = strconv.ParseUint(strutil.ReadField(text, 3, true), 10, 64) 184 185 if err != nil { 186 return nil, errors.New("Can't parse field 3 as unsigned integer in " + procStatFile) 187 } 188 189 stats.Idle, err = strconv.ParseUint(strutil.ReadField(text, 4, true), 10, 64) 190 191 if err != nil { 192 return nil, errors.New("Can't parse field 4 as unsigned integer in " + procStatFile) 193 } 194 195 stats.Wait, err = strconv.ParseUint(strutil.ReadField(text, 5, true), 10, 64) 196 197 if err != nil { 198 return nil, errors.New("Can't parse field 5 as unsigned integer in " + procStatFile) 199 } 200 201 stats.IRQ, err = strconv.ParseUint(strutil.ReadField(text, 6, true), 10, 64) 202 203 if err != nil { 204 return nil, errors.New("Can't parse field 6 as unsigned integer in " + procStatFile) 205 } 206 207 stats.SRQ, err = strconv.ParseUint(strutil.ReadField(text, 7, true), 10, 64) 208 209 if err != nil { 210 return nil, errors.New("Can't parse field 7 as unsigned integer in " + procStatFile) 211 } 212 213 stats.Steal, err = strconv.ParseUint(strutil.ReadField(text, 8, true), 10, 64) 214 215 if err != nil { 216 return nil, errors.New("Can't parse field 8 as unsigned integer in " + procStatFile) 217 } 218 219 } 220 221 if stats.Count == 0 { 222 return nil, errors.New("Can't parse file " + procStatFile) 223 } 224 225 stats.Total = stats.User + stats.System + stats.Nice + stats.Idle + stats.Wait + stats.IRQ + stats.SRQ + stats.Steal 226 227 return stats, nil 228 } 229 230 // parseCPUInfo parses cpu info data 231 func parseCPUInfo(s *bufio.Scanner) ([]*CPUInfo, error) { 232 var ( 233 err error 234 info []*CPUInfo 235 vendor string 236 model string 237 siblings int 238 cores int 239 cache uint64 240 speed float64 241 id int 242 ) 243 244 for s.Scan() { 245 text := s.Text() 246 247 switch { 248 case strings.HasPrefix(text, "vendor_id"): 249 vendor = strings.Trim(strutil.ReadField(text, 1, false, ":"), " ") 250 251 case strings.HasPrefix(text, "model name"): 252 model = strings.Trim(strutil.ReadField(text, 1, false, ":"), " ") 253 254 case strings.HasPrefix(text, "cache size"): 255 cache, err = parseSize(strings.Trim(strutil.ReadField(text, 1, false, ":"), " KB")) 256 257 case strings.HasPrefix(text, "cpu MHz"): 258 speed, err = strconv.ParseFloat(strings.Trim(strutil.ReadField(text, 1, false, ":"), " "), 64) 259 260 case strings.HasPrefix(text, "physical id"): 261 id, err = strconv.Atoi(strings.Trim(strutil.ReadField(text, 1, false, ":"), " ")) 262 263 case strings.HasPrefix(text, "siblings"): 264 siblings, err = strconv.Atoi(strings.Trim(strutil.ReadField(text, 1, false, ":"), " ")) 265 266 case strings.HasPrefix(text, "cpu cores"): 267 cores, err = strconv.Atoi(strings.Trim(strutil.ReadField(text, 1, false, ":"), " ")) 268 269 case strings.HasPrefix(text, "flags"): 270 if len(info) < id+1 { 271 info = append(info, &CPUInfo{vendor, model, cores, siblings, cache, nil}) 272 } 273 274 if id < len(info) && info[id] != nil { 275 info[id].Speed = append(info[id].Speed, speed) 276 } 277 } 278 279 if err != nil { 280 return nil, err 281 } 282 } 283 284 if vendor == "" && model == "" { 285 return nil, errors.New("Can't parse cpuinfo file") 286 } 287 288 return info, nil 289 } 290 291 // codebeat:enable[LOC,ABC,CYCLO] 292 293 // parseCPUCountInfo parses CPU count data 294 func parseCPUCountInfo(data string) uint32 { 295 startNum := strings.Trim(strutil.ReadField(data, 0, false, "-"), "\n\r") 296 endNum := strings.Trim(strutil.ReadField(data, 1, false, "-"), "\n\r") 297 298 start, _ := strconv.ParseUint(startNum, 10, 32) 299 end, _ := strconv.ParseUint(endNum, 10, 32) 300 301 return uint32(end-start) + 1 302 } 303 304 // getCPUArchBits returns CPU architecture bits (32/64) 305 func getCPUArchBits() int { 306 s, closer, err := getFileScanner(cpuInfoFile) 307 308 if err != nil { 309 return 0 310 } 311 312 defer closer() 313 314 for s.Scan() { 315 text := s.Text() 316 317 if !strings.HasPrefix(text, "flags") { 318 continue 319 } 320 321 // lm - 64 / tm - 32 / rm - 16 322 if strings.Contains(text, " lm ") { 323 return 64 324 } 325 } 326 327 return 32 328 }