diff --git a/cpuinfo.go b/cpuinfo.go index bb3be99f..8c299516 100644 --- a/cpuinfo.go +++ b/cpuinfo.go @@ -17,13 +17,12 @@ package procfs import ( "bufio" - "bytes" "errors" + "io" + "os" "regexp" "strconv" "strings" - - "github.com/prometheus/procfs/internal/util" ) // CPUInfo contains general information about a system CPU found in /proc/cpuinfo @@ -64,36 +63,26 @@ var ( // CPUInfo returns information about current system CPUs. // See https://www.kernel.org/doc/Documentation/filesystems/proc.txt func (fs FS) CPUInfo() ([]CPUInfo, error) { - data, err := util.ReadFileNoStat(fs.proc.Path("cpuinfo")) + file, err := os.Open(fs.proc.Path("cpuinfo")) if err != nil { return nil, err } - return parseCPUInfo(data) + defer file.Close() + return parseCPUInfo(file) } -func parseCPUInfoX86(info []byte) ([]CPUInfo, error) { - scanner := bufio.NewScanner(bytes.NewReader(info)) +func parseCPUInfoX86(info io.Reader) ([]CPUInfo, error) { + scanner := bufio.NewScanner(info) - // find the first "processor" line - firstLine := firstNonEmptyLine(scanner) - if !strings.HasPrefix(firstLine, "processor") || !strings.Contains(firstLine, ":") { - return nil, errors.New("invalid cpuinfo file: " + firstLine) - } - field := strings.SplitN(firstLine, ": ", 2) - v, err := strconv.ParseUint(field[1], 0, 32) - if err != nil { - return nil, err - } - firstcpu := CPUInfo{Processor: uint(v)} - cpuinfo := []CPUInfo{firstcpu} - i := 0 + cpuinfo := []CPUInfo{} + i := -1 for scanner.Scan() { line := scanner.Text() - if !strings.Contains(line, ":") { + field := strings.SplitN(line, ": ", 2) + if len(field) != 2 { continue } - field := strings.SplitN(line, ": ", 2) switch strings.TrimSpace(field[0]) { case "processor": cpuinfo = append(cpuinfo, CPUInfo{}) // start of the next processor @@ -183,22 +172,16 @@ func parseCPUInfoX86(info []byte) ([]CPUInfo, error) { cpuinfo[i].PowerManagement = field[1] } } - return cpuinfo, nil + return cpuinfo, scanner.Err() } -func parseCPUInfoARM(info []byte) ([]CPUInfo, error) { - scanner := bufio.NewScanner(bytes.NewReader(info)) +func parseCPUInfoARM(info io.Reader) ([]CPUInfo, error) { + scanner := bufio.NewScanner(info) - firstLine := firstNonEmptyLine(scanner) - if !strings.HasPrefix(firstLine, "Processor") || !strings.Contains(firstLine, ":") { - return nil, errors.New("invalid cpuinfo file: " + firstLine) - } - field := strings.SplitN(firstLine, ": ", 2) - commonCPUInfo := CPUInfo{VendorID: field[1]} - - cpuinfo := []CPUInfo{} - i := -1 - featuresLine := "" + cpuinfo := []CPUInfo{{}} + i := 0 + var features []string + modelName := "" for scanner.Scan() { line := scanner.Text() @@ -207,14 +190,23 @@ func parseCPUInfoARM(info []byte) ([]CPUInfo, error) { } field := strings.SplitN(line, ": ", 2) switch strings.TrimSpace(field[0]) { + case "Processor": + // this appears at the first line in kernels < 3.8 + modelName = field[1] + cpuinfo[i].VendorID = field[1] case "processor": - cpuinfo = append(cpuinfo, commonCPUInfo) // start of the next processor - i++ v, err := strconv.ParseUint(field[1], 0, 32) if err != nil { return nil, err } - cpuinfo[i].Processor = uint(v) + if v != 0 { + cpuinfo = append(cpuinfo, CPUInfo{VendorID: modelName}) // start of the next processor + i++ + cpuinfo[i].Processor = uint(v) + } + case "model name": + // this is what kernel 3.8+ gives instead of "Processor:" + cpuinfo[i].VendorID = field[1] case "BogoMIPS": v, err := strconv.ParseFloat(field[1], 64) if err != nil { @@ -222,34 +214,32 @@ func parseCPUInfoARM(info []byte) ([]CPUInfo, error) { } cpuinfo[i].BogoMips = v case "Features": - featuresLine = line + if len(features) == 0 { + // only parse once + features = strings.Fields(field[1]) + } + cpuinfo[i].Flags = features } } - fields := strings.SplitN(featuresLine, ": ", 2) - for i := range cpuinfo { - cpuinfo[i].Flags = strings.Fields(fields[1]) - } - return cpuinfo, nil + + return cpuinfo, scanner.Err() } -func parseCPUInfoS390X(info []byte) ([]CPUInfo, error) { - scanner := bufio.NewScanner(bytes.NewReader(info)) +func parseCPUInfoS390X(info io.Reader) ([]CPUInfo, error) { + scanner := bufio.NewScanner(info) - firstLine := firstNonEmptyLine(scanner) - if !strings.HasPrefix(firstLine, "vendor_id") || !strings.Contains(firstLine, ":") { - return nil, errors.New("invalid cpuinfo file: " + firstLine) - } - field := strings.SplitN(firstLine, ": ", 2) cpuinfo := []CPUInfo{} - commonCPUInfo := CPUInfo{VendorID: field[1]} + commonCPUInfo := CPUInfo{} for scanner.Scan() { line := scanner.Text() - if !strings.Contains(line, ":") { + field := strings.SplitN(line, ": ", 2) + if len(field) != 2 { continue } - field := strings.SplitN(line, ": ", 2) switch strings.TrimSpace(field[0]) { + case "vendor_id": + commonCPUInfo.VendorID = field[1] case "bogomips per cpu": v, err := strconv.ParseFloat(field[1], 64) if err != nil { @@ -297,31 +287,21 @@ func parseCPUInfoS390X(info []byte) ([]CPUInfo, error) { } } - return cpuinfo, nil + return cpuinfo, scanner.Err() } -func parseCPUInfoPPC(info []byte) ([]CPUInfo, error) { - scanner := bufio.NewScanner(bytes.NewReader(info)) +func parseCPUInfoPPC(info io.Reader) ([]CPUInfo, error) { + scanner := bufio.NewScanner(info) - firstLine := firstNonEmptyLine(scanner) - if !strings.HasPrefix(firstLine, "processor") || !strings.Contains(firstLine, ":") { - return nil, errors.New("invalid cpuinfo file: " + firstLine) - } - field := strings.SplitN(firstLine, ": ", 2) - v, err := strconv.ParseUint(field[1], 0, 32) - if err != nil { - return nil, err - } - firstcpu := CPUInfo{Processor: uint(v)} - cpuinfo := []CPUInfo{firstcpu} - i := 0 + cpuinfo := []CPUInfo{} + i := -1 for scanner.Scan() { line := scanner.Text() - if !strings.Contains(line, ":") { + field := strings.SplitN(line, ": ", 2) + if len(field) != 2 { continue } - field := strings.SplitN(line, ": ", 2) switch strings.TrimSpace(field[0]) { case "processor": cpuinfo = append(cpuinfo, CPUInfo{}) // start of the next processor @@ -342,17 +322,5 @@ func parseCPUInfoPPC(info []byte) ([]CPUInfo, error) { cpuinfo[i].CPUMHz = v } } - return cpuinfo, nil -} - -// firstNonEmptyLine advances the scanner to the first non-empty line -// and returns the contents of that line -func firstNonEmptyLine(scanner *bufio.Scanner) string { - for scanner.Scan() { - line := scanner.Text() - if strings.TrimSpace(line) != "" { - return line - } - } - return "" + return cpuinfo, scanner.Err() } diff --git a/cpuinfo_test.go b/cpuinfo_test.go index b6869ace..492a1a8b 100644 --- a/cpuinfo_test.go +++ b/cpuinfo_test.go @@ -15,11 +15,29 @@ package procfs -import "testing" +import ( + "strings" + "testing" +) const ( - cpuinfoArm7 = ` -Processor : ARMv7 Processor rev 5 (v7l) + // non-SMP system before kernel v3.8, commit + // https://github.com/torvalds/linux/commit/b4b8f770eb + cpuinfoArm6 = `Processor : ARMv6-compatible processor rev 5 (v6l) +BogoMIPS : 791.34 +Features : swp half thumb fastmult vfp edsp java +CPU implementer : 0x41 +CPU architecture: 6TEJ +CPU variant : 0x1 +CPU part : 0xb36 +CPU revision : 5 + +Hardware : IMAPX200 +Revision : 0000 +Serial : 0000000000000000` + + // SMP system before kernel 3.8 + cpuinfoArm7old = `Processor : ARMv7 Processor rev 5 (v7l) processor : 0 BogoMIPS : 2400.00 @@ -37,8 +55,18 @@ Hardware : sun8i Revision : 0000 Serial : 5400503583203c3c040e` - cpuinfoS390x = ` -vendor_id : IBM/S390 + // kernel v3.8+ + cpuinfoArm7 = `processor : 0 +model name : ARMv7 Processor rev 2 (v7l) +BogoMIPS : 50.00 +Features : half thumb fastmult vfp edsp thumbee vfpv3 tls idiva idivt vfpd32 lpae +CPU implementer : 0x56 +CPU architecture: 7 +CPU variant : 0x2 +CPU part : 0x584 +CPU revision : 2` + + cpuinfoS390x = `vendor_id : IBM/S390 # processors : 4 bogomips per cpu: 3033.00 max thread id : 0 @@ -69,11 +97,9 @@ cpu MHz static : 5000 cpu number : 3 cpu MHz dynamic : 5000 -cpu MHz static : 5000 -` +cpu MHz static : 5000` - cpuinfoPpc64 = ` -processor : 0 + cpuinfoPpc64 = `processor : 0 cpu : POWER7 (architected), altivec supported clock : 3000.000000MHz revision : 2.1 (pvr 003f 0201) @@ -153,8 +179,23 @@ func TestCPUInfoX86(t *testing.T) { } } -func TestCPUInfoParseARM(t *testing.T) { - cpuinfo, err := parseCPUInfoARM([]byte(cpuinfoArm7)) +func TestCPUInfoParseARM6(t *testing.T) { + cpuinfo, err := parseCPUInfoARM(strings.NewReader(cpuinfoArm6)) + if err != nil || cpuinfo == nil { + t.Fatalf("unable to parse arm cpu info: %v", err) + } + if want, have := 1, len(cpuinfo); want != have { + t.Errorf("want number of processors %v, have %v", want, have) + } + if want, have := "ARMv6-compatible processor rev 5 (v6l)", cpuinfo[0].VendorID; want != have { + t.Errorf("want vendor %v, have %v", want, have) + } + if want, have := "thumb", cpuinfo[0].Flags[2]; want != have { + t.Errorf("want flag %v, have %v", want, have) + } +} +func TestCPUInfoParseARM7old(t *testing.T) { + cpuinfo, err := parseCPUInfoARM(strings.NewReader(cpuinfoArm7old)) if err != nil || cpuinfo == nil { t.Fatalf("unable to parse arm cpu info: %v", err) } @@ -169,8 +210,23 @@ func TestCPUInfoParseARM(t *testing.T) { } } +func TestCPUInfoParseARM(t *testing.T) { + cpuinfo, err := parseCPUInfoARM(strings.NewReader(cpuinfoArm7)) + if err != nil || cpuinfo == nil { + t.Fatalf("unable to parse arm cpu info: %v", err) + } + if want, have := 1, len(cpuinfo); want != have { + t.Errorf("want number of processors %v, have %v", want, have) + } + if want, have := "ARMv7 Processor rev 2 (v7l)", cpuinfo[0].VendorID; want != have { + t.Errorf("want vendor %v, have %v", want, have) + } + if want, have := "fastmult", cpuinfo[0].Flags[2]; want != have { + t.Errorf("want flag %v, have %v", want, have) + } +} func TestCPUInfoParseS390X(t *testing.T) { - cpuinfo, err := parseCPUInfoS390X([]byte(cpuinfoS390x)) + cpuinfo, err := parseCPUInfoS390X(strings.NewReader(cpuinfoS390x)) if err != nil || cpuinfo == nil { t.Fatalf("unable to parse s390x cpu info: %v", err) } @@ -189,7 +245,7 @@ func TestCPUInfoParseS390X(t *testing.T) { } func TestCPUInfoParsePPC(t *testing.T) { - cpuinfo, err := parseCPUInfoPPC([]byte(cpuinfoPpc64)) + cpuinfo, err := parseCPUInfoPPC(strings.NewReader(cpuinfoPpc64)) if err != nil || cpuinfo == nil { t.Fatalf("unable to parse ppc cpu info: %v", err) }