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/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 family": 145 c.Family = value 146 case "model": 147 c.Model = value 148 case "model name", "cpu": 149 c.ModelName = value 150 if strings.Contains(value, "POWER8") || 151 strings.Contains(value, "POWER7") { 152 c.Model = strings.Split(value, " ")[0] 153 c.Family = "POWER" 154 c.VendorID = "IBM" 155 } 156 case "stepping", "revision": 157 val := value 158 159 if key == "revision" { 160 val = strings.Split(value, ".")[0] 161 } 162 163 t, err := strconv.ParseInt(val, 10, 64) 164 if err != nil { 165 return ret, err 166 } 167 c.Stepping = int32(t) 168 case "cpu MHz", "clock": 169 // treat this as the fallback value, thus we ignore error 170 if t, err := strconv.ParseFloat(strings.Replace(value, "MHz", "", 1), 64); err == nil { 171 c.Mhz = t 172 } 173 case "cache size": 174 t, err := strconv.ParseInt(strings.Replace(value, " KB", "", 1), 10, 64) 175 if err != nil { 176 return ret, err 177 } 178 c.CacheSize = int32(t) 179 case "physical id": 180 c.PhysicalID = value 181 case "core id": 182 c.CoreID = value 183 case "flags", "Features": 184 c.Flags = strings.FieldsFunc(value, func(r rune) bool { 185 return r == ',' || r == ' ' 186 }) 187 case "microcode": 188 c.Microcode = value 189 } 190 } 191 if c.CPU >= 0 { 192 err := finishCPUInfo(&c) 193 if err != nil { 194 return ret, err 195 } 196 ret = append(ret, c) 197 } 198 return ret, nil 199} 200 201func parseStatLine(line string) (*TimesStat, error) { 202 fields := strings.Fields(line) 203 204 if len(fields) == 0 { 205 return nil, errors.New("stat does not contain cpu info") 206 } 207 208 if strings.HasPrefix(fields[0], "cpu") == false { 209 return nil, errors.New("not contain cpu") 210 } 211 212 cpu := fields[0] 213 if cpu == "cpu" { 214 cpu = "cpu-total" 215 } 216 user, err := strconv.ParseFloat(fields[1], 64) 217 if err != nil { 218 return nil, err 219 } 220 nice, err := strconv.ParseFloat(fields[2], 64) 221 if err != nil { 222 return nil, err 223 } 224 system, err := strconv.ParseFloat(fields[3], 64) 225 if err != nil { 226 return nil, err 227 } 228 idle, err := strconv.ParseFloat(fields[4], 64) 229 if err != nil { 230 return nil, err 231 } 232 iowait, err := strconv.ParseFloat(fields[5], 64) 233 if err != nil { 234 return nil, err 235 } 236 irq, err := strconv.ParseFloat(fields[6], 64) 237 if err != nil { 238 return nil, err 239 } 240 softirq, err := strconv.ParseFloat(fields[7], 64) 241 if err != nil { 242 return nil, err 243 } 244 245 ct := &TimesStat{ 246 CPU: cpu, 247 User: user / ClocksPerSec, 248 Nice: nice / ClocksPerSec, 249 System: system / ClocksPerSec, 250 Idle: idle / ClocksPerSec, 251 Iowait: iowait / ClocksPerSec, 252 Irq: irq / ClocksPerSec, 253 Softirq: softirq / ClocksPerSec, 254 } 255 if len(fields) > 8 { // Linux >= 2.6.11 256 steal, err := strconv.ParseFloat(fields[8], 64) 257 if err != nil { 258 return nil, err 259 } 260 ct.Steal = steal / ClocksPerSec 261 } 262 if len(fields) > 9 { // Linux >= 2.6.24 263 guest, err := strconv.ParseFloat(fields[9], 64) 264 if err != nil { 265 return nil, err 266 } 267 ct.Guest = guest / ClocksPerSec 268 } 269 if len(fields) > 10 { // Linux >= 3.2.0 270 guestNice, err := strconv.ParseFloat(fields[10], 64) 271 if err != nil { 272 return nil, err 273 } 274 ct.GuestNice = guestNice / ClocksPerSec 275 } 276 277 return ct, nil 278} 279 280func CountsWithContext(ctx context.Context, logical bool) (int, error) { 281 if logical { 282 ret := 0 283 // https://github.com/giampaolo/psutil/blob/d01a9eaa35a8aadf6c519839e987a49d8be2d891/psutil/_pslinux.py#L599 284 procCpuinfo := common.HostProc("cpuinfo") 285 lines, err := common.ReadLines(procCpuinfo) 286 if err == nil { 287 for _, line := range lines { 288 line = strings.ToLower(line) 289 if strings.HasPrefix(line, "processor") { 290 _, err = strconv.Atoi(strings.TrimSpace(line[strings.IndexByte(line, ':')+1:])) 291 if err == nil { 292 ret++ 293 } 294 } 295 } 296 } 297 if ret == 0 { 298 procStat := common.HostProc("stat") 299 lines, err = common.ReadLines(procStat) 300 if err != nil { 301 return 0, err 302 } 303 for _, line := range lines { 304 if len(line) >= 4 && strings.HasPrefix(line, "cpu") && '0' <= line[3] && line[3] <= '9' { // `^cpu\d` regexp matching 305 ret++ 306 } 307 } 308 } 309 return ret, nil 310 } 311 // physical cores 312 // https://github.com/giampaolo/psutil/blob/8415355c8badc9c94418b19bdf26e622f06f0cce/psutil/_pslinux.py#L615-L628 313 var threadSiblingsLists = make(map[string]bool) 314 // These 2 files are the same but */core_cpus_list is newer while */thread_siblings_list is deprecated and may disappear in the future. 315 // https://www.kernel.org/doc/Documentation/admin-guide/cputopology.rst 316 // https://github.com/giampaolo/psutil/pull/1727#issuecomment-707624964 317 // https://lkml.org/lkml/2019/2/26/41 318 for _, glob := range []string{"devices/system/cpu/cpu[0-9]*/topology/core_cpus_list", "devices/system/cpu/cpu[0-9]*/topology/thread_siblings_list"} { 319 if files, err := filepath.Glob(common.HostSys(glob)); err == nil { 320 for _, file := range files { 321 lines, err := common.ReadLines(file) 322 if err != nil || len(lines) != 1 { 323 continue 324 } 325 threadSiblingsLists[lines[0]] = true 326 } 327 ret := len(threadSiblingsLists) 328 if ret != 0 { 329 return ret, nil 330 } 331 } 332 } 333 // https://github.com/giampaolo/psutil/blob/122174a10b75c9beebe15f6c07dcf3afbe3b120d/psutil/_pslinux.py#L631-L652 334 filename := common.HostProc("cpuinfo") 335 lines, err := common.ReadLines(filename) 336 if err != nil { 337 return 0, err 338 } 339 mapping := make(map[int]int) 340 currentInfo := make(map[string]int) 341 for _, line := range lines { 342 line = strings.ToLower(strings.TrimSpace(line)) 343 if line == "" { 344 // new section 345 id, okID := currentInfo["physical id"] 346 cores, okCores := currentInfo["cpu cores"] 347 if okID && okCores { 348 mapping[id] = cores 349 } 350 currentInfo = make(map[string]int) 351 continue 352 } 353 fields := strings.Split(line, ":") 354 if len(fields) < 2 { 355 continue 356 } 357 fields[0] = strings.TrimSpace(fields[0]) 358 if fields[0] == "physical id" || fields[0] == "cpu cores" { 359 val, err := strconv.Atoi(strings.TrimSpace(fields[1])) 360 if err != nil { 361 continue 362 } 363 currentInfo[fields[0]] = val 364 } 365 } 366 ret := 0 367 for _, v := range mapping { 368 ret += v 369 } 370 return ret, nil 371} 372