github.com/isyscore/isc-gobase@v1.5.3-0.20231218061332-cbc7451899e9/system/cpu/cpu_linux.go (about) 1 //go:build linux 2 3 package cpu 4 5 import ( 6 "context" 7 "errors" 8 "fmt" 9 "github.com/isyscore/isc-gobase/system/common" 10 "github.com/tklauser/go-sysconf" 11 "path/filepath" 12 "strconv" 13 "strings" 14 ) 15 16 var ClocksPerSec = float64(100) 17 18 func init() { 19 clkTck, err := sysconf.Sysconf(sysconf.SC_CLK_TCK) 20 // ignore errors 21 if err == nil { 22 ClocksPerSec = float64(clkTck) 23 } 24 } 25 26 func Times(percpu bool) ([]TimesStat, error) { 27 return TimesWithContext(context.Background(), percpu) 28 } 29 30 func TimesWithContext(ctx context.Context, percpu bool) ([]TimesStat, error) { 31 filename := common.HostProc("stat") 32 var lines = []string{} 33 if percpu { 34 statlines, err := common.ReadLines(filename) 35 if err != nil || len(statlines) < 2 { 36 return []TimesStat{}, nil 37 } 38 for _, line := range statlines[1:] { 39 if !strings.HasPrefix(line, "cpu") { 40 break 41 } 42 lines = append(lines, line) 43 } 44 } else { 45 lines, _ = common.ReadLinesOffsetN(filename, 0, 1) 46 } 47 48 ret := make([]TimesStat, 0, len(lines)) 49 50 for _, line := range lines { 51 ct, err := parseStatLine(line) 52 if err != nil { 53 continue 54 } 55 ret = append(ret, *ct) 56 57 } 58 return ret, nil 59 } 60 61 func sysCPUPath(cpu int32, relPath string) string { 62 return common.HostSys(fmt.Sprintf("devices/system/cpu/cpu%d", cpu), relPath) 63 } 64 65 func finishCPUInfo(c *InfoStat) error { 66 var lines []string 67 var err error 68 var value float64 69 70 if len(c.CoreID) == 0 { 71 lines, err = common.ReadLines(sysCPUPath(c.CPU, "topology/core_id")) 72 if err == nil { 73 c.CoreID = lines[0] 74 } 75 } 76 77 // override the value of c.Mhz with cpufreq/cpuinfo_max_freq regardless 78 // of the value from /proc/cpuinfo because we want to report the maximum 79 // clock-speed of the CPU for c.Mhz, matching the behaviour of Windows 80 lines, err = common.ReadLines(sysCPUPath(c.CPU, "cpufreq/cpuinfo_max_freq")) 81 // if we encounter errors below such as there are no cpuinfo_max_freq file, 82 // we just ignore. so let Mhz is 0. 83 if err != nil || len(lines) == 0 { 84 return nil 85 } 86 value, err = strconv.ParseFloat(lines[0], 64) 87 if err != nil { 88 return nil 89 } 90 c.Mhz = value / 1000.0 // value is in kHz 91 if c.Mhz > 9999 { 92 c.Mhz = c.Mhz / 1000.0 // value in Hz 93 } 94 return nil 95 } 96 97 // CPUInfo on linux will return 1 item per physical thread. 98 // 99 // CPUs have three levels of counting: sockets, cores, threads. 100 // Cores with HyperThreading count as having 2 threads per core. 101 // Sockets often come with many physical CPU cores. 102 // For example a single socket board with two cores each with HT will 103 // return 4 CPUInfoStat structs on Linux and the "Cores" field set to 1. 104 func Info() ([]InfoStat, error) { 105 return InfoWithContext(context.Background()) 106 } 107 108 func InfoWithContext(ctx context.Context) ([]InfoStat, error) { 109 filename := common.HostProc("cpuinfo") 110 lines, _ := common.ReadLines(filename) 111 112 var ret []InfoStat 113 var processorName string 114 115 c := InfoStat{CPU: -1, Cores: 1} 116 for _, line := range lines { 117 fields := strings.Split(line, ":") 118 if len(fields) < 2 { 119 continue 120 } 121 key := strings.TrimSpace(fields[0]) 122 value := strings.TrimSpace(fields[1]) 123 124 switch key { 125 case "Processor": 126 processorName = value 127 case "processor": 128 if c.CPU >= 0 { 129 err := finishCPUInfo(&c) 130 if err != nil { 131 return ret, err 132 } 133 ret = append(ret, c) 134 } 135 c = InfoStat{Cores: 1, ModelName: processorName} 136 t, err := strconv.ParseInt(value, 10, 64) 137 if err != nil { 138 return ret, err 139 } 140 c.CPU = int32(t) 141 case "vendorId", "vendor_id": 142 c.VendorID = value 143 case "CPU implementer": 144 if v, err := strconv.ParseUint(value, 0, 8); err == nil { 145 switch v { 146 case 0x41: 147 c.VendorID = "ARM" 148 case 0x42: 149 c.VendorID = "Broadcom" 150 case 0x43: 151 c.VendorID = "Cavium" 152 case 0x44: 153 c.VendorID = "DEC" 154 case 0x46: 155 c.VendorID = "Fujitsu" 156 case 0x48: 157 c.VendorID = "HiSilicon" 158 case 0x49: 159 c.VendorID = "Infineon" 160 case 0x4d: 161 c.VendorID = "Motorola/Freescale" 162 case 0x4e: 163 c.VendorID = "NVIDIA" 164 case 0x50: 165 c.VendorID = "APM" 166 case 0x51: 167 c.VendorID = "Qualcomm" 168 case 0x56: 169 c.VendorID = "Marvell" 170 case 0x61: 171 c.VendorID = "Apple" 172 case 0x69: 173 c.VendorID = "Intel" 174 case 0xc0: 175 c.VendorID = "Ampere" 176 } 177 } 178 case "cpu family": 179 c.Family = value 180 case "model", "CPU part": 181 c.Model = value 182 case "model name", "cpu": 183 c.ModelName = value 184 if strings.Contains(value, "POWER8") || 185 strings.Contains(value, "POWER7") { 186 c.Model = strings.Split(value, " ")[0] 187 c.Family = "POWER" 188 c.VendorID = "IBM" 189 } 190 case "stepping", "revision", "CPU revision": 191 val := value 192 193 if key == "revision" { 194 val = strings.Split(value, ".")[0] 195 } 196 197 t, err := strconv.ParseInt(val, 10, 64) 198 if err != nil { 199 return ret, err 200 } 201 c.Stepping = int32(t) 202 case "cpu MHz", "clock": 203 // treat this as the fallback value, thus we ignore error 204 if t, err := strconv.ParseFloat(strings.Replace(value, "MHz", "", 1), 64); err == nil { 205 c.Mhz = t 206 } 207 case "cache size": 208 t, err := strconv.ParseInt(strings.Replace(value, " KB", "", 1), 10, 64) 209 if err != nil { 210 return ret, err 211 } 212 c.CacheSize = int32(t) 213 case "physical id": 214 c.PhysicalID = value 215 case "core id": 216 c.CoreID = value 217 case "flags", "Features": 218 c.Flags = strings.FieldsFunc(value, func(r rune) bool { 219 return r == ',' || r == ' ' 220 }) 221 case "microcode": 222 c.Microcode = value 223 } 224 } 225 if c.CPU >= 0 { 226 err := finishCPUInfo(&c) 227 if err != nil { 228 return ret, err 229 } 230 ret = append(ret, c) 231 } 232 return ret, nil 233 } 234 235 func parseStatLine(line string) (*TimesStat, error) { 236 fields := strings.Fields(line) 237 238 if len(fields) == 0 { 239 return nil, errors.New("stat does not contain cpu info") 240 } 241 242 if strings.HasPrefix(fields[0], "cpu") == false { 243 return nil, errors.New("not contain cpu") 244 } 245 246 cpu := fields[0] 247 if cpu == "cpu" { 248 cpu = "cpu-total" 249 } 250 user, err := strconv.ParseFloat(fields[1], 64) 251 if err != nil { 252 return nil, err 253 } 254 nice, err := strconv.ParseFloat(fields[2], 64) 255 if err != nil { 256 return nil, err 257 } 258 system, err := strconv.ParseFloat(fields[3], 64) 259 if err != nil { 260 return nil, err 261 } 262 idle, err := strconv.ParseFloat(fields[4], 64) 263 if err != nil { 264 return nil, err 265 } 266 iowait, err := strconv.ParseFloat(fields[5], 64) 267 if err != nil { 268 return nil, err 269 } 270 irq, err := strconv.ParseFloat(fields[6], 64) 271 if err != nil { 272 return nil, err 273 } 274 softirq, err := strconv.ParseFloat(fields[7], 64) 275 if err != nil { 276 return nil, err 277 } 278 279 ct := &TimesStat{ 280 CPU: cpu, 281 User: user / ClocksPerSec, 282 Nice: nice / ClocksPerSec, 283 System: system / ClocksPerSec, 284 Idle: idle / ClocksPerSec, 285 Iowait: iowait / ClocksPerSec, 286 Irq: irq / ClocksPerSec, 287 Softirq: softirq / ClocksPerSec, 288 } 289 if len(fields) > 8 { // Linux >= 2.6.11 290 steal, err := strconv.ParseFloat(fields[8], 64) 291 if err != nil { 292 return nil, err 293 } 294 ct.Steal = steal / ClocksPerSec 295 } 296 if len(fields) > 9 { // Linux >= 2.6.24 297 guest, err := strconv.ParseFloat(fields[9], 64) 298 if err != nil { 299 return nil, err 300 } 301 ct.Guest = guest / ClocksPerSec 302 } 303 if len(fields) > 10 { // Linux >= 3.2.0 304 guestNice, err := strconv.ParseFloat(fields[10], 64) 305 if err != nil { 306 return nil, err 307 } 308 ct.GuestNice = guestNice / ClocksPerSec 309 } 310 311 return ct, nil 312 } 313 314 func CountsWithContext(ctx context.Context, logical bool) (int, error) { 315 if logical { 316 ret := 0 317 // https://github.com/giampaolo/psutil/blob/d01a9eaa35a8aadf6c519839e987a49d8be2d891/psutil/_pslinux.py#L599 318 procCpuinfo := common.HostProc("cpuinfo") 319 lines, err := common.ReadLines(procCpuinfo) 320 if err == nil { 321 for _, line := range lines { 322 line = strings.ToLower(line) 323 if strings.HasPrefix(line, "processor") { 324 _, err = strconv.Atoi(strings.TrimSpace(line[strings.IndexByte(line, ':')+1:])) 325 if err == nil { 326 ret++ 327 } 328 } 329 } 330 } 331 if ret == 0 { 332 procStat := common.HostProc("stat") 333 lines, err = common.ReadLines(procStat) 334 if err != nil { 335 return 0, err 336 } 337 for _, line := range lines { 338 if len(line) >= 4 && strings.HasPrefix(line, "cpu") && '0' <= line[3] && line[3] <= '9' { // `^cpu\d` regexp matching 339 ret++ 340 } 341 } 342 } 343 return ret, nil 344 } 345 // physical cores 346 // https://github.com/giampaolo/psutil/blob/8415355c8badc9c94418b19bdf26e622f06f0cce/psutil/_pslinux.py#L615-L628 347 var threadSiblingsLists = make(map[string]bool) 348 // These 2 files are the same but */core_cpus_list is newer while */thread_siblings_list is deprecated and may disappear in the future. 349 // https://www.kernel.org/doc/Documentation/admin-guide/cputopology.rst 350 // https://github.com/giampaolo/psutil/pull/1727#issuecomment-707624964 351 // https://lkml.org/lkml/2019/2/26/41 352 for _, glob := range []string{"devices/system/cpu/cpu[0-9]*/topology/core_cpus_list", "devices/system/cpu/cpu[0-9]*/topology/thread_siblings_list"} { 353 if files, err := filepath.Glob(common.HostSys(glob)); err == nil { 354 for _, file := range files { 355 lines, err := common.ReadLines(file) 356 if err != nil || len(lines) != 1 { 357 continue 358 } 359 threadSiblingsLists[lines[0]] = true 360 } 361 ret := len(threadSiblingsLists) 362 if ret != 0 { 363 return ret, nil 364 } 365 } 366 } 367 // https://github.com/giampaolo/psutil/blob/122174a10b75c9beebe15f6c07dcf3afbe3b120d/psutil/_pslinux.py#L631-L652 368 filename := common.HostProc("cpuinfo") 369 lines, err := common.ReadLines(filename) 370 if err != nil { 371 return 0, err 372 } 373 mapping := make(map[int]int) 374 currentInfo := make(map[string]int) 375 for _, line := range lines { 376 line = strings.ToLower(strings.TrimSpace(line)) 377 if line == "" { 378 // new section 379 id, okID := currentInfo["physical id"] 380 cores, okCores := currentInfo["cpu cores"] 381 if okID && okCores { 382 mapping[id] = cores 383 } 384 currentInfo = make(map[string]int) 385 continue 386 } 387 fields := strings.Split(line, ":") 388 if len(fields) < 2 { 389 continue 390 } 391 fields[0] = strings.TrimSpace(fields[0]) 392 if fields[0] == "physical id" || fields[0] == "cpu cores" { 393 val, err := strconv.Atoi(strings.TrimSpace(fields[1])) 394 if err != nil { 395 continue 396 } 397 currentInfo[fields[0]] = val 398 } 399 } 400 ret := 0 401 for _, v := range mapping { 402 ret += v 403 } 404 return ret, nil 405 }