1// Copyright 2019 The Prometheus Authors 2// Licensed under the Apache License, Version 2.0 (the "License"); 3// you may not use this file except in compliance with the License. 4// You may obtain a copy of the License at 5// 6// http://www.apache.org/licenses/LICENSE-2.0 7// 8// Unless required by applicable law or agreed to in writing, software 9// distributed under the License is distributed on an "AS IS" BASIS, 10// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11// See the License for the specific language governing permissions and 12// limitations under the License. 13 14// +build linux 15 16package procfs 17 18import ( 19 "bufio" 20 "bytes" 21 "errors" 22 "fmt" 23 "regexp" 24 "strconv" 25 "strings" 26 27 "github.com/prometheus/procfs/internal/util" 28) 29 30// CPUInfo contains general information about a system CPU found in /proc/cpuinfo 31type CPUInfo struct { 32 Processor uint 33 VendorID string 34 CPUFamily string 35 Model string 36 ModelName string 37 Stepping string 38 Microcode string 39 CPUMHz float64 40 CacheSize string 41 PhysicalID string 42 Siblings uint 43 CoreID string 44 CPUCores uint 45 APICID string 46 InitialAPICID string 47 FPU string 48 FPUException string 49 CPUIDLevel uint 50 WP string 51 Flags []string 52 Bugs []string 53 BogoMips float64 54 CLFlushSize uint 55 CacheAlignment uint 56 AddressSizes string 57 PowerManagement string 58} 59 60var ( 61 cpuinfoClockRegexp = regexp.MustCompile(`([\d.]+)`) 62 cpuinfoS390XProcessorRegexp = regexp.MustCompile(`^processor\s+(\d+):.*`) 63) 64 65// CPUInfo returns information about current system CPUs. 66// See https://www.kernel.org/doc/Documentation/filesystems/proc.txt 67func (fs FS) CPUInfo() ([]CPUInfo, error) { 68 data, err := util.ReadFileNoStat(fs.proc.Path("cpuinfo")) 69 if err != nil { 70 return nil, err 71 } 72 return parseCPUInfo(data) 73} 74 75func parseCPUInfoX86(info []byte) ([]CPUInfo, error) { 76 scanner := bufio.NewScanner(bytes.NewReader(info)) 77 78 // find the first "processor" line 79 firstLine := firstNonEmptyLine(scanner) 80 if !strings.HasPrefix(firstLine, "processor") || !strings.Contains(firstLine, ":") { 81 return nil, fmt.Errorf("invalid cpuinfo file: %q", firstLine) 82 } 83 field := strings.SplitN(firstLine, ": ", 2) 84 v, err := strconv.ParseUint(field[1], 0, 32) 85 if err != nil { 86 return nil, err 87 } 88 firstcpu := CPUInfo{Processor: uint(v)} 89 cpuinfo := []CPUInfo{firstcpu} 90 i := 0 91 92 for scanner.Scan() { 93 line := scanner.Text() 94 if !strings.Contains(line, ":") { 95 continue 96 } 97 field := strings.SplitN(line, ": ", 2) 98 switch strings.TrimSpace(field[0]) { 99 case "processor": 100 cpuinfo = append(cpuinfo, CPUInfo{}) // start of the next processor 101 i++ 102 v, err := strconv.ParseUint(field[1], 0, 32) 103 if err != nil { 104 return nil, err 105 } 106 cpuinfo[i].Processor = uint(v) 107 case "vendor", "vendor_id": 108 cpuinfo[i].VendorID = field[1] 109 case "cpu family": 110 cpuinfo[i].CPUFamily = field[1] 111 case "model": 112 cpuinfo[i].Model = field[1] 113 case "model name": 114 cpuinfo[i].ModelName = field[1] 115 case "stepping": 116 cpuinfo[i].Stepping = field[1] 117 case "microcode": 118 cpuinfo[i].Microcode = field[1] 119 case "cpu MHz": 120 v, err := strconv.ParseFloat(field[1], 64) 121 if err != nil { 122 return nil, err 123 } 124 cpuinfo[i].CPUMHz = v 125 case "cache size": 126 cpuinfo[i].CacheSize = field[1] 127 case "physical id": 128 cpuinfo[i].PhysicalID = field[1] 129 case "siblings": 130 v, err := strconv.ParseUint(field[1], 0, 32) 131 if err != nil { 132 return nil, err 133 } 134 cpuinfo[i].Siblings = uint(v) 135 case "core id": 136 cpuinfo[i].CoreID = field[1] 137 case "cpu cores": 138 v, err := strconv.ParseUint(field[1], 0, 32) 139 if err != nil { 140 return nil, err 141 } 142 cpuinfo[i].CPUCores = uint(v) 143 case "apicid": 144 cpuinfo[i].APICID = field[1] 145 case "initial apicid": 146 cpuinfo[i].InitialAPICID = field[1] 147 case "fpu": 148 cpuinfo[i].FPU = field[1] 149 case "fpu_exception": 150 cpuinfo[i].FPUException = field[1] 151 case "cpuid level": 152 v, err := strconv.ParseUint(field[1], 0, 32) 153 if err != nil { 154 return nil, err 155 } 156 cpuinfo[i].CPUIDLevel = uint(v) 157 case "wp": 158 cpuinfo[i].WP = field[1] 159 case "flags": 160 cpuinfo[i].Flags = strings.Fields(field[1]) 161 case "bugs": 162 cpuinfo[i].Bugs = strings.Fields(field[1]) 163 case "bogomips": 164 v, err := strconv.ParseFloat(field[1], 64) 165 if err != nil { 166 return nil, err 167 } 168 cpuinfo[i].BogoMips = v 169 case "clflush size": 170 v, err := strconv.ParseUint(field[1], 0, 32) 171 if err != nil { 172 return nil, err 173 } 174 cpuinfo[i].CLFlushSize = uint(v) 175 case "cache_alignment": 176 v, err := strconv.ParseUint(field[1], 0, 32) 177 if err != nil { 178 return nil, err 179 } 180 cpuinfo[i].CacheAlignment = uint(v) 181 case "address sizes": 182 cpuinfo[i].AddressSizes = field[1] 183 case "power management": 184 cpuinfo[i].PowerManagement = field[1] 185 } 186 } 187 return cpuinfo, nil 188} 189 190func parseCPUInfoARM(info []byte) ([]CPUInfo, error) { 191 scanner := bufio.NewScanner(bytes.NewReader(info)) 192 193 firstLine := firstNonEmptyLine(scanner) 194 match, _ := regexp.MatchString("^[Pp]rocessor", firstLine) 195 if !match || !strings.Contains(firstLine, ":") { 196 return nil, fmt.Errorf("invalid cpuinfo file: %q", firstLine) 197 } 198 field := strings.SplitN(firstLine, ": ", 2) 199 cpuinfo := []CPUInfo{} 200 featuresLine := "" 201 commonCPUInfo := CPUInfo{} 202 i := 0 203 if strings.TrimSpace(field[0]) == "Processor" { 204 commonCPUInfo = CPUInfo{ModelName: field[1]} 205 i = -1 206 } else { 207 v, err := strconv.ParseUint(field[1], 0, 32) 208 if err != nil { 209 return nil, err 210 } 211 firstcpu := CPUInfo{Processor: uint(v)} 212 cpuinfo = []CPUInfo{firstcpu} 213 } 214 215 for scanner.Scan() { 216 line := scanner.Text() 217 if !strings.Contains(line, ":") { 218 continue 219 } 220 field := strings.SplitN(line, ": ", 2) 221 switch strings.TrimSpace(field[0]) { 222 case "processor": 223 cpuinfo = append(cpuinfo, commonCPUInfo) // start of the next processor 224 i++ 225 v, err := strconv.ParseUint(field[1], 0, 32) 226 if err != nil { 227 return nil, err 228 } 229 cpuinfo[i].Processor = uint(v) 230 case "BogoMIPS": 231 if i == -1 { 232 cpuinfo = append(cpuinfo, commonCPUInfo) // There is only one processor 233 i++ 234 cpuinfo[i].Processor = 0 235 } 236 v, err := strconv.ParseFloat(field[1], 64) 237 if err != nil { 238 return nil, err 239 } 240 cpuinfo[i].BogoMips = v 241 case "Features": 242 featuresLine = line 243 case "model name": 244 cpuinfo[i].ModelName = field[1] 245 } 246 } 247 fields := strings.SplitN(featuresLine, ": ", 2) 248 for i := range cpuinfo { 249 cpuinfo[i].Flags = strings.Fields(fields[1]) 250 } 251 return cpuinfo, nil 252 253} 254 255func parseCPUInfoS390X(info []byte) ([]CPUInfo, error) { 256 scanner := bufio.NewScanner(bytes.NewReader(info)) 257 258 firstLine := firstNonEmptyLine(scanner) 259 if !strings.HasPrefix(firstLine, "vendor_id") || !strings.Contains(firstLine, ":") { 260 return nil, fmt.Errorf("invalid cpuinfo file: %q", firstLine) 261 } 262 field := strings.SplitN(firstLine, ": ", 2) 263 cpuinfo := []CPUInfo{} 264 commonCPUInfo := CPUInfo{VendorID: field[1]} 265 266 for scanner.Scan() { 267 line := scanner.Text() 268 if !strings.Contains(line, ":") { 269 continue 270 } 271 field := strings.SplitN(line, ": ", 2) 272 switch strings.TrimSpace(field[0]) { 273 case "bogomips per cpu": 274 v, err := strconv.ParseFloat(field[1], 64) 275 if err != nil { 276 return nil, err 277 } 278 commonCPUInfo.BogoMips = v 279 case "features": 280 commonCPUInfo.Flags = strings.Fields(field[1]) 281 } 282 if strings.HasPrefix(line, "processor") { 283 match := cpuinfoS390XProcessorRegexp.FindStringSubmatch(line) 284 if len(match) < 2 { 285 return nil, fmt.Errorf("invalid cpuinfo file: %q", firstLine) 286 } 287 cpu := commonCPUInfo 288 v, err := strconv.ParseUint(match[1], 0, 32) 289 if err != nil { 290 return nil, err 291 } 292 cpu.Processor = uint(v) 293 cpuinfo = append(cpuinfo, cpu) 294 } 295 if strings.HasPrefix(line, "cpu number") { 296 break 297 } 298 } 299 300 i := 0 301 for scanner.Scan() { 302 line := scanner.Text() 303 if !strings.Contains(line, ":") { 304 continue 305 } 306 field := strings.SplitN(line, ": ", 2) 307 switch strings.TrimSpace(field[0]) { 308 case "cpu number": 309 i++ 310 case "cpu MHz dynamic": 311 clock := cpuinfoClockRegexp.FindString(strings.TrimSpace(field[1])) 312 v, err := strconv.ParseFloat(clock, 64) 313 if err != nil { 314 return nil, err 315 } 316 cpuinfo[i].CPUMHz = v 317 case "physical id": 318 cpuinfo[i].PhysicalID = field[1] 319 case "core id": 320 cpuinfo[i].CoreID = field[1] 321 case "cpu cores": 322 v, err := strconv.ParseUint(field[1], 0, 32) 323 if err != nil { 324 return nil, err 325 } 326 cpuinfo[i].CPUCores = uint(v) 327 case "siblings": 328 v, err := strconv.ParseUint(field[1], 0, 32) 329 if err != nil { 330 return nil, err 331 } 332 cpuinfo[i].Siblings = uint(v) 333 } 334 } 335 336 return cpuinfo, nil 337} 338 339func parseCPUInfoMips(info []byte) ([]CPUInfo, error) { 340 scanner := bufio.NewScanner(bytes.NewReader(info)) 341 342 // find the first "processor" line 343 firstLine := firstNonEmptyLine(scanner) 344 if !strings.HasPrefix(firstLine, "system type") || !strings.Contains(firstLine, ":") { 345 return nil, fmt.Errorf("invalid cpuinfo file: %q", firstLine) 346 } 347 field := strings.SplitN(firstLine, ": ", 2) 348 cpuinfo := []CPUInfo{} 349 systemType := field[1] 350 351 i := 0 352 353 for scanner.Scan() { 354 line := scanner.Text() 355 if !strings.Contains(line, ":") { 356 continue 357 } 358 field := strings.SplitN(line, ": ", 2) 359 switch strings.TrimSpace(field[0]) { 360 case "processor": 361 v, err := strconv.ParseUint(field[1], 0, 32) 362 if err != nil { 363 return nil, err 364 } 365 i = int(v) 366 cpuinfo = append(cpuinfo, CPUInfo{}) // start of the next processor 367 cpuinfo[i].Processor = uint(v) 368 cpuinfo[i].VendorID = systemType 369 case "cpu model": 370 cpuinfo[i].ModelName = field[1] 371 case "BogoMIPS": 372 v, err := strconv.ParseFloat(field[1], 64) 373 if err != nil { 374 return nil, err 375 } 376 cpuinfo[i].BogoMips = v 377 } 378 } 379 return cpuinfo, nil 380} 381 382func parseCPUInfoPPC(info []byte) ([]CPUInfo, error) { 383 scanner := bufio.NewScanner(bytes.NewReader(info)) 384 385 firstLine := firstNonEmptyLine(scanner) 386 if !strings.HasPrefix(firstLine, "processor") || !strings.Contains(firstLine, ":") { 387 return nil, fmt.Errorf("invalid cpuinfo file: %q", firstLine) 388 } 389 field := strings.SplitN(firstLine, ": ", 2) 390 v, err := strconv.ParseUint(field[1], 0, 32) 391 if err != nil { 392 return nil, err 393 } 394 firstcpu := CPUInfo{Processor: uint(v)} 395 cpuinfo := []CPUInfo{firstcpu} 396 i := 0 397 398 for scanner.Scan() { 399 line := scanner.Text() 400 if !strings.Contains(line, ":") { 401 continue 402 } 403 field := strings.SplitN(line, ": ", 2) 404 switch strings.TrimSpace(field[0]) { 405 case "processor": 406 cpuinfo = append(cpuinfo, CPUInfo{}) // start of the next processor 407 i++ 408 v, err := strconv.ParseUint(field[1], 0, 32) 409 if err != nil { 410 return nil, err 411 } 412 cpuinfo[i].Processor = uint(v) 413 case "cpu": 414 cpuinfo[i].VendorID = field[1] 415 case "clock": 416 clock := cpuinfoClockRegexp.FindString(strings.TrimSpace(field[1])) 417 v, err := strconv.ParseFloat(clock, 64) 418 if err != nil { 419 return nil, err 420 } 421 cpuinfo[i].CPUMHz = v 422 } 423 } 424 return cpuinfo, nil 425} 426 427func parseCPUInfoRISCV(info []byte) ([]CPUInfo, error) { 428 scanner := bufio.NewScanner(bytes.NewReader(info)) 429 430 firstLine := firstNonEmptyLine(scanner) 431 if !strings.HasPrefix(firstLine, "processor") || !strings.Contains(firstLine, ":") { 432 return nil, fmt.Errorf("invalid cpuinfo file: %q", firstLine) 433 } 434 field := strings.SplitN(firstLine, ": ", 2) 435 v, err := strconv.ParseUint(field[1], 0, 32) 436 if err != nil { 437 return nil, err 438 } 439 firstcpu := CPUInfo{Processor: uint(v)} 440 cpuinfo := []CPUInfo{firstcpu} 441 i := 0 442 443 for scanner.Scan() { 444 line := scanner.Text() 445 if !strings.Contains(line, ":") { 446 continue 447 } 448 field := strings.SplitN(line, ": ", 2) 449 switch strings.TrimSpace(field[0]) { 450 case "processor": 451 v, err := strconv.ParseUint(field[1], 0, 32) 452 if err != nil { 453 return nil, err 454 } 455 i = int(v) 456 cpuinfo = append(cpuinfo, CPUInfo{}) // start of the next processor 457 cpuinfo[i].Processor = uint(v) 458 case "hart": 459 cpuinfo[i].CoreID = field[1] 460 case "isa": 461 cpuinfo[i].ModelName = field[1] 462 } 463 } 464 return cpuinfo, nil 465} 466 467func parseCPUInfoDummy(_ []byte) ([]CPUInfo, error) { // nolint:unused,deadcode 468 return nil, errors.New("not implemented") 469} 470 471// firstNonEmptyLine advances the scanner to the first non-empty line 472// and returns the contents of that line 473func firstNonEmptyLine(scanner *bufio.Scanner) string { 474 for scanner.Scan() { 475 line := scanner.Text() 476 if strings.TrimSpace(line) != "" { 477 return line 478 } 479 } 480 return "" 481} 482