blob: 5623b24a161fee3f1e350d42099ac3ece1668e08 [file] [log] [blame]
khenaidoo26721882021-08-11 17:42:52 -04001// 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}