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