Skip to content

Commit 0c0164d

Browse files
committed
multiplatform support for cpuinfo
This adds basic handling of reading /proc/cpuinfo for ARM, PPC, and s390x. Signed-off-by: Paul Gier <[email protected]>
1 parent 436b8d6 commit 0c0164d

File tree

3 files changed

+372
-5
lines changed

3 files changed

+372
-5
lines changed

.circleci/config.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ jobs:
3434
steps:
3535
- checkout
3636
- run: sudo pip install codespell
37-
- run: codespell --skip=".git,./vendor,ttar,go.mod,go.sum" -L uint,packages\'
37+
- run: codespell --skip=".git,./vendor,ttar,go.mod,go.sum" -L uint,packages,te\'
3838

3939
workflows:
4040
version: 2

cpuinfo.go

Lines changed: 208 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@ package procfs
1616
import (
1717
"bufio"
1818
"bytes"
19+
"errors"
20+
"regexp"
1921
"strconv"
2022
"strings"
2123

@@ -52,6 +54,38 @@ type CPUInfo struct {
5254
PowerManagement string
5355
}
5456

57+
const (
58+
platformX86 = "x86"
59+
platformARM = "arm"
60+
platformS390X = "s390x"
61+
platformPPC = "ppc"
62+
platformUnknown = "unknown"
63+
)
64+
65+
var (
66+
cpuinfoX86Regexp = regexp.MustCompile("(?m)^\\s*processor\\s*:\\s*\\d+\\s*vendor")
67+
cpuinfoARMRegexp = regexp.MustCompile("^\\s*Processor\\s*:\\s*ARM")
68+
cpuinfoS390XRegexp = regexp.MustCompile("^\\s*vendor_id\\s*:\\s*IBM/S390")
69+
cpuinfoPPCRegexp = regexp.MustCompile("(?m)^\\s*processor\\s*:\\s*\\d+\\s+cpu\\s+:\\s+POWER")
70+
)
71+
72+
// cpuinfoDetectFormat attempts to determine the format used by the cpuinfo.
73+
// This format corresponds to the platform generating the /proc/cpuinfo file.
74+
// Returns "unknown"
75+
func cpuinfoDetectFormat(info []byte) string {
76+
switch {
77+
case cpuinfoX86Regexp.Match(info):
78+
return platformX86
79+
case cpuinfoARMRegexp.Match(info):
80+
return platformARM
81+
case cpuinfoPPCRegexp.Match(info):
82+
return platformPPC
83+
case cpuinfoS390XRegexp.Match(info):
84+
return platformS390X
85+
}
86+
return platformUnknown
87+
}
88+
5589
// CPUInfo returns information about current system CPUs.
5690
// See https://www.kernel.org/doc/Documentation/filesystems/proc.txt
5791
func (fs FS) CPUInfo() ([]CPUInfo, error) {
@@ -64,9 +98,37 @@ func (fs FS) CPUInfo() ([]CPUInfo, error) {
6498

6599
// parseCPUInfo parses data from /proc/cpuinfo
66100
func parseCPUInfo(info []byte) ([]CPUInfo, error) {
67-
cpuinfo := []CPUInfo{}
68-
i := -1
101+
platform := cpuinfoDetectFormat(info)
102+
switch platform {
103+
case platformX86:
104+
return parseCPUInfoX86(info)
105+
case platformARM:
106+
return parseCPUInfoARM(info)
107+
case platformS390X:
108+
return parseCPUInfoS390X(info)
109+
case platformPPC:
110+
return parseCPUInfoPPC(info)
111+
}
112+
return nil, errors.New("unable to determine format of 'cpuinfo'")
113+
}
114+
115+
func parseCPUInfoX86(info []byte) ([]CPUInfo, error) {
69116
scanner := bufio.NewScanner(bytes.NewReader(info))
117+
118+
// find the first "processor" line
119+
firstLine := firstNonEmptyLine(scanner)
120+
if !strings.HasPrefix(firstLine, "processor") || !strings.Contains(firstLine, ":") {
121+
return nil, errors.New("invalid cpuinfo file: " + firstLine)
122+
}
123+
field := strings.SplitN(firstLine, ": ", 2)
124+
v, err := strconv.ParseUint(field[1], 0, 32)
125+
if err != nil {
126+
return nil, err
127+
}
128+
firstcpu := CPUInfo{Processor: uint(v)}
129+
cpuinfo := []CPUInfo{firstcpu}
130+
i := 0
131+
70132
for scanner.Scan() {
71133
line := scanner.Text()
72134
if strings.TrimSpace(line) == "" {
@@ -82,7 +144,7 @@ func parseCPUInfo(info []byte) ([]CPUInfo, error) {
82144
return nil, err
83145
}
84146
cpuinfo[i].Processor = uint(v)
85-
case "vendor_id":
147+
case "vendor", "vendor_id":
86148
cpuinfo[i].VendorID = field[1]
87149
case "cpu family":
88150
cpuinfo[i].CPUFamily = field[1]
@@ -163,5 +225,148 @@ func parseCPUInfo(info []byte) ([]CPUInfo, error) {
163225
}
164226
}
165227
return cpuinfo, nil
228+
}
229+
230+
func parseCPUInfoARM(info []byte) ([]CPUInfo, error) {
231+
scanner := bufio.NewScanner(bytes.NewReader(info))
232+
233+
firstLine := firstNonEmptyLine(scanner)
234+
if !strings.HasPrefix(firstLine, "Processor") || !strings.Contains(firstLine, ":") {
235+
return nil, errors.New("invalid cpuinfo file: " + firstLine)
236+
}
237+
field := strings.SplitN(firstLine, ": ", 2)
238+
commonCPUInfo := CPUInfo{VendorID: field[1]}
239+
240+
cpuinfo := []CPUInfo{}
241+
i := -1
242+
featuresLine := ""
243+
244+
for scanner.Scan() {
245+
line := scanner.Text()
246+
if strings.TrimSpace(line) == "" {
247+
continue
248+
}
249+
field := strings.SplitN(line, ": ", 2)
250+
switch strings.TrimSpace(field[0]) {
251+
case "processor":
252+
cpuinfo = append(cpuinfo, commonCPUInfo) // start of the next processor
253+
i++
254+
v, err := strconv.ParseUint(field[1], 0, 32)
255+
if err != nil {
256+
return nil, err
257+
}
258+
cpuinfo[i].Processor = uint(v)
259+
case "BogoMIPS":
260+
v, err := strconv.ParseFloat(field[1], 64)
261+
if err != nil {
262+
return nil, err
263+
}
264+
cpuinfo[i].BogoMips = v
265+
case "Features":
266+
featuresLine = line
267+
break
268+
}
269+
}
270+
fields := strings.SplitN(featuresLine, ": ", 2)
271+
for i := range cpuinfo {
272+
cpuinfo[i].Flags = strings.Fields(fields[1])
273+
}
274+
return cpuinfo, nil
275+
}
276+
277+
var (
278+
cpuinfoS390XProcessorRegexp = regexp.MustCompile("^processor\\s+(\\d+):.*")
279+
)
280+
281+
func parseCPUInfoS390X(info []byte) ([]CPUInfo, error) {
282+
scanner := bufio.NewScanner(bytes.NewReader(info))
283+
284+
firstLine := firstNonEmptyLine(scanner)
285+
if !strings.HasPrefix(firstLine, "vendor_id") || !strings.Contains(firstLine, ":") {
286+
return nil, errors.New("invalid cpuinfo file: " + firstLine)
287+
}
288+
field := strings.SplitN(firstLine, ": ", 2)
289+
cpuinfo := []CPUInfo{}
290+
commonCPUInfo := CPUInfo{VendorID: field[1]}
291+
292+
for scanner.Scan() {
293+
line := scanner.Text()
294+
if strings.TrimSpace(line) == "" {
295+
continue
296+
}
297+
field := strings.SplitN(line, ": ", 2)
298+
switch strings.TrimSpace(field[0]) {
299+
case "bogomips per cpu":
300+
v, err := strconv.ParseFloat(field[1], 64)
301+
if err != nil {
302+
return nil, err
303+
}
304+
commonCPUInfo.BogoMips = v
305+
case "features":
306+
commonCPUInfo.Flags = strings.Fields(field[1])
307+
}
308+
if strings.HasPrefix(line, "processor") {
309+
match := cpuinfoS390XProcessorRegexp.FindStringSubmatch(line)
310+
cpu := commonCPUInfo
311+
v, err := strconv.ParseUint(match[1], 0, 32)
312+
if err != nil {
313+
return nil, err
314+
}
315+
cpu.Processor = uint(v)
316+
cpuinfo = append(cpuinfo, cpu)
317+
}
318+
}
319+
320+
return cpuinfo, nil
321+
}
322+
323+
func parseCPUInfoPPC(info []byte) ([]CPUInfo, error) {
324+
scanner := bufio.NewScanner(bytes.NewReader(info))
325+
326+
firstLine := firstNonEmptyLine(scanner)
327+
if !strings.HasPrefix(firstLine, "processor") || !strings.Contains(firstLine, ":") {
328+
return nil, errors.New("invalid cpuinfo file: " + firstLine)
329+
}
330+
field := strings.SplitN(firstLine, ": ", 2)
331+
v, err := strconv.ParseUint(field[1], 0, 32)
332+
if err != nil {
333+
return nil, err
334+
}
335+
firstcpu := CPUInfo{Processor: uint(v)}
336+
cpuinfo := []CPUInfo{firstcpu}
337+
i := 0
338+
339+
for scanner.Scan() {
340+
line := scanner.Text()
341+
if strings.TrimSpace(line) == "" {
342+
continue
343+
}
344+
field := strings.SplitN(line, ": ", 2)
345+
switch strings.TrimSpace(field[0]) {
346+
case "processor":
347+
cpuinfo = append(cpuinfo, CPUInfo{}) // start of the next processor
348+
i++
349+
v, err := strconv.ParseUint(field[1], 0, 32)
350+
if err != nil {
351+
return nil, err
352+
}
353+
cpuinfo[i].Processor = uint(v)
354+
case "cpu":
355+
cpuinfo[i].VendorID = field[1]
356+
357+
}
358+
}
359+
return cpuinfo, nil
360+
}
166361

362+
// firstNonEmptyLine advances the scanner to the first non-empty line
363+
// and returns the contents of that line
364+
func firstNonEmptyLine(scanner *bufio.Scanner) string {
365+
for scanner.Scan() {
366+
line := scanner.Text()
367+
if strings.TrimSpace(line) != "" {
368+
return line
369+
}
370+
}
371+
return ""
167372
}

0 commit comments

Comments
 (0)