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