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 "regexp" 23 "strconv" 24 "strings" 25 26 "github.com/prometheus/procfs/internal/util" 27) 28 29// CPUInfo contains general information about a system CPU found in /proc/cpuinfo 30type CPUInfo struct { 31 Processor uint 32 VendorID string 33 CPUFamily string 34 Model string 35 ModelName string 36 Stepping string 37 Microcode string 38 CPUMHz float64 39 CacheSize string 40 PhysicalID string 41 Siblings uint 42 CoreID string 43 CPUCores uint 44 APICID string 45 InitialAPICID string 46 FPU string 47 FPUException string 48 CPUIDLevel uint 49 WP string 50 Flags []string 51 Bugs []string 52 BogoMips float64 53 CLFlushSize uint 54 CacheAlignment uint 55 AddressSizes string 56 PowerManagement string 57} 58 59var ( 60 cpuinfoClockRegexp = regexp.MustCompile(`([\d.]+)`) 61 cpuinfoS390XProcessorRegexp = regexp.MustCompile(`^processor\s+(\d+):.*`) 62) 63 64// CPUInfo returns information about current system CPUs. 65// See https://www.kernel.org/doc/Documentation/filesystems/proc.txt 66func (fs FS) CPUInfo() ([]CPUInfo, error) { 67 data, err := util.ReadFileNoStat(fs.proc.Path("cpuinfo")) 68 if err != nil { 69 return nil, err 70 } 71 return parseCPUInfo(data) 72} 73 74func parseCPUInfoX86(info []byte) ([]CPUInfo, error) { 75 scanner := bufio.NewScanner(bytes.NewReader(info)) 76 77 // find the first "processor" line 78 firstLine := firstNonEmptyLine(scanner) 79 if !strings.HasPrefix(firstLine, "processor") || !strings.Contains(firstLine, ":") { 80 return nil, errors.New("invalid cpuinfo file: " + firstLine) 81 } 82 field := strings.SplitN(firstLine, ": ", 2) 83 v, err := strconv.ParseUint(field[1], 0, 32) 84 if err != nil { 85 return nil, err 86 } 87 firstcpu := CPUInfo{Processor: uint(v)} 88 cpuinfo := []CPUInfo{firstcpu} 89 i := 0 90 91 for scanner.Scan() { 92 line := scanner.Text() 93 if !strings.Contains(line, ":") { 94 continue 95 } 96 field := strings.SplitN(line, ": ", 2) 97 switch strings.TrimSpace(field[0]) { 98 case "processor": 99 cpuinfo = append(cpuinfo, CPUInfo{}) // start of the next processor 100 i++ 101 v, err := strconv.ParseUint(field[1], 0, 32) 102 if err != nil { 103 return nil, err 104 } 105 cpuinfo[i].Processor = uint(v) 106 case "vendor", "vendor_id": 107 cpuinfo[i].VendorID = field[1] 108 case "cpu family": 109 cpuinfo[i].CPUFamily = field[1] 110 case "model": 111 cpuinfo[i].Model = field[1] 112 case "model name": 113 cpuinfo[i].ModelName = field[1] 114 case "stepping": 115 cpuinfo[i].Stepping = field[1] 116 case "microcode": 117 cpuinfo[i].Microcode = field[1] 118 case "cpu MHz": 119 v, err := strconv.ParseFloat(field[1], 64) 120 if err != nil { 121 return nil, err 122 } 123 cpuinfo[i].CPUMHz = v 124 case "cache size": 125 cpuinfo[i].CacheSize = field[1] 126 case "physical id": 127 cpuinfo[i].PhysicalID = field[1] 128 case "siblings": 129 v, err := strconv.ParseUint(field[1], 0, 32) 130 if err != nil { 131 return nil, err 132 } 133 cpuinfo[i].Siblings = uint(v) 134 case "core id": 135 cpuinfo[i].CoreID = field[1] 136 case "cpu cores": 137 v, err := strconv.ParseUint(field[1], 0, 32) 138 if err != nil { 139 return nil, err 140 } 141 cpuinfo[i].CPUCores = uint(v) 142 case "apicid": 143 cpuinfo[i].APICID = field[1] 144 case "initial apicid": 145 cpuinfo[i].InitialAPICID = field[1] 146 case "fpu": 147 cpuinfo[i].FPU = field[1] 148 case "fpu_exception": 149 cpuinfo[i].FPUException = field[1] 150 case "cpuid level": 151 v, err := strconv.ParseUint(field[1], 0, 32) 152 if err != nil { 153 return nil, err 154 } 155 cpuinfo[i].CPUIDLevel = uint(v) 156 case "wp": 157 cpuinfo[i].WP = field[1] 158 case "flags": 159 cpuinfo[i].Flags = strings.Fields(field[1]) 160 case "bugs": 161 cpuinfo[i].Bugs = strings.Fields(field[1]) 162 case "bogomips": 163 v, err := strconv.ParseFloat(field[1], 64) 164 if err != nil { 165 return nil, err 166 } 167 cpuinfo[i].BogoMips = v 168 case "clflush size": 169 v, err := strconv.ParseUint(field[1], 0, 32) 170 if err != nil { 171 return nil, err 172 } 173 cpuinfo[i].CLFlushSize = uint(v) 174 case "cache_alignment": 175 v, err := strconv.ParseUint(field[1], 0, 32) 176 if err != nil { 177 return nil, err 178 } 179 cpuinfo[i].CacheAlignment = uint(v) 180 case "address sizes": 181 cpuinfo[i].AddressSizes = field[1] 182 case "power management": 183 cpuinfo[i].PowerManagement = field[1] 184 } 185 } 186 return cpuinfo, nil 187} 188 189func parseCPUInfoARM(info []byte) ([]CPUInfo, error) { 190 scanner := bufio.NewScanner(bytes.NewReader(info)) 191 192 firstLine := firstNonEmptyLine(scanner) 193 match, _ := regexp.MatchString("^[Pp]rocessor", firstLine) 194 if !match || !strings.Contains(firstLine, ":") { 195 return nil, errors.New("invalid cpuinfo file: " + firstLine) 196 } 197 field := strings.SplitN(firstLine, ": ", 2) 198 cpuinfo := []CPUInfo{} 199 featuresLine := "" 200 commonCPUInfo := CPUInfo{} 201 i := 0 202 if strings.TrimSpace(field[0]) == "Processor" { 203 commonCPUInfo = CPUInfo{ModelName: field[1]} 204 i = -1 205 } else { 206 v, err := strconv.ParseUint(field[1], 0, 32) 207 if err != nil { 208 return nil, err 209 } 210 firstcpu := CPUInfo{Processor: uint(v)} 211 cpuinfo = []CPUInfo{firstcpu} 212 } 213 214 for scanner.Scan() { 215 line := scanner.Text() 216 if !strings.Contains(line, ":") { 217 continue 218 } 219 field := strings.SplitN(line, ": ", 2) 220 switch strings.TrimSpace(field[0]) { 221 case "processor": 222 cpuinfo = append(cpuinfo, commonCPUInfo) // start of the next processor 223 i++ 224 v, err := strconv.ParseUint(field[1], 0, 32) 225 if err != nil { 226 return nil, err 227 } 228 cpuinfo[i].Processor = uint(v) 229 case "BogoMIPS": 230 if i == -1 { 231 cpuinfo = append(cpuinfo, commonCPUInfo) // There is only one processor 232 i++ 233 cpuinfo[i].Processor = 0 234 } 235 v, err := strconv.ParseFloat(field[1], 64) 236 if err != nil { 237 return nil, err 238 } 239 cpuinfo[i].BogoMips = v 240 case "Features": 241 featuresLine = line 242 case "model name": 243 cpuinfo[i].ModelName = field[1] 244 } 245 } 246 fields := strings.SplitN(featuresLine, ": ", 2) 247 for i := range cpuinfo { 248 cpuinfo[i].Flags = strings.Fields(fields[1]) 249 } 250 return cpuinfo, nil 251 252} 253 254func parseCPUInfoS390X(info []byte) ([]CPUInfo, error) { 255 scanner := bufio.NewScanner(bytes.NewReader(info)) 256 257 firstLine := firstNonEmptyLine(scanner) 258 if !strings.HasPrefix(firstLine, "vendor_id") || !strings.Contains(firstLine, ":") { 259 return nil, errors.New("invalid cpuinfo file: " + firstLine) 260 } 261 field := strings.SplitN(firstLine, ": ", 2) 262 cpuinfo := []CPUInfo{} 263 commonCPUInfo := CPUInfo{VendorID: field[1]} 264 265 for scanner.Scan() { 266 line := scanner.Text() 267 if !strings.Contains(line, ":") { 268 continue 269 } 270 field := strings.SplitN(line, ": ", 2) 271 switch strings.TrimSpace(field[0]) { 272 case "bogomips per cpu": 273 v, err := strconv.ParseFloat(field[1], 64) 274 if err != nil { 275 return nil, err 276 } 277 commonCPUInfo.BogoMips = v 278 case "features": 279 commonCPUInfo.Flags = strings.Fields(field[1]) 280 } 281 if strings.HasPrefix(line, "processor") { 282 match := cpuinfoS390XProcessorRegexp.FindStringSubmatch(line) 283 if len(match) < 2 { 284 return nil, errors.New("Invalid line found in cpuinfo: " + line) 285 } 286 cpu := commonCPUInfo 287 v, err := strconv.ParseUint(match[1], 0, 32) 288 if err != nil { 289 return nil, err 290 } 291 cpu.Processor = uint(v) 292 cpuinfo = append(cpuinfo, cpu) 293 } 294 if strings.HasPrefix(line, "cpu number") { 295 break 296 } 297 } 298 299 i := 0 300 for scanner.Scan() { 301 line := scanner.Text() 302 if !strings.Contains(line, ":") { 303 continue 304 } 305 field := strings.SplitN(line, ": ", 2) 306 switch strings.TrimSpace(field[0]) { 307 case "cpu number": 308 i++ 309 case "cpu MHz dynamic": 310 clock := cpuinfoClockRegexp.FindString(strings.TrimSpace(field[1])) 311 v, err := strconv.ParseFloat(clock, 64) 312 if err != nil { 313 return nil, err 314 } 315 cpuinfo[i].CPUMHz = v 316 } 317 } 318 319 return cpuinfo, nil 320} 321 322func parseCPUInfoMips(info []byte) ([]CPUInfo, error) { 323 scanner := bufio.NewScanner(bytes.NewReader(info)) 324 325 // find the first "processor" line 326 firstLine := firstNonEmptyLine(scanner) 327 if !strings.HasPrefix(firstLine, "system type") || !strings.Contains(firstLine, ":") { 328 return nil, errors.New("invalid cpuinfo file: " + firstLine) 329 } 330 field := strings.SplitN(firstLine, ": ", 2) 331 cpuinfo := []CPUInfo{} 332 systemType := field[1] 333 334 i := 0 335 336 for scanner.Scan() { 337 line := scanner.Text() 338 if !strings.Contains(line, ":") { 339 continue 340 } 341 field := strings.SplitN(line, ": ", 2) 342 switch strings.TrimSpace(field[0]) { 343 case "processor": 344 v, err := strconv.ParseUint(field[1], 0, 32) 345 if err != nil { 346 return nil, err 347 } 348 i = int(v) 349 cpuinfo = append(cpuinfo, CPUInfo{}) // start of the next processor 350 cpuinfo[i].Processor = uint(v) 351 cpuinfo[i].VendorID = systemType 352 case "cpu model": 353 cpuinfo[i].ModelName = field[1] 354 case "BogoMIPS": 355 v, err := strconv.ParseFloat(field[1], 64) 356 if err != nil { 357 return nil, err 358 } 359 cpuinfo[i].BogoMips = v 360 } 361 } 362 return cpuinfo, nil 363} 364 365func parseCPUInfoPPC(info []byte) ([]CPUInfo, error) { 366 scanner := bufio.NewScanner(bytes.NewReader(info)) 367 368 firstLine := firstNonEmptyLine(scanner) 369 if !strings.HasPrefix(firstLine, "processor") || !strings.Contains(firstLine, ":") { 370 return nil, errors.New("invalid cpuinfo file: " + firstLine) 371 } 372 field := strings.SplitN(firstLine, ": ", 2) 373 v, err := strconv.ParseUint(field[1], 0, 32) 374 if err != nil { 375 return nil, err 376 } 377 firstcpu := CPUInfo{Processor: uint(v)} 378 cpuinfo := []CPUInfo{firstcpu} 379 i := 0 380 381 for scanner.Scan() { 382 line := scanner.Text() 383 if !strings.Contains(line, ":") { 384 continue 385 } 386 field := strings.SplitN(line, ": ", 2) 387 switch strings.TrimSpace(field[0]) { 388 case "processor": 389 cpuinfo = append(cpuinfo, CPUInfo{}) // start of the next processor 390 i++ 391 v, err := strconv.ParseUint(field[1], 0, 32) 392 if err != nil { 393 return nil, err 394 } 395 cpuinfo[i].Processor = uint(v) 396 case "cpu": 397 cpuinfo[i].VendorID = field[1] 398 case "clock": 399 clock := cpuinfoClockRegexp.FindString(strings.TrimSpace(field[1])) 400 v, err := strconv.ParseFloat(clock, 64) 401 if err != nil { 402 return nil, err 403 } 404 cpuinfo[i].CPUMHz = v 405 } 406 } 407 return cpuinfo, nil 408} 409 410// firstNonEmptyLine advances the scanner to the first non-empty line 411// and returns the contents of that line 412func firstNonEmptyLine(scanner *bufio.Scanner) string { 413 for scanner.Scan() { 414 line := scanner.Text() 415 if strings.TrimSpace(line) != "" { 416 return line 417 } 418 } 419 return "" 420} 421