@@ -16,6 +16,8 @@ package procfs
1616import (
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
5791func (fs FS ) CPUInfo () ([]CPUInfo , error ) {
@@ -64,9 +98,37 @@ func (fs FS) CPUInfo() ([]CPUInfo, error) {
6498
6599// parseCPUInfo parses data from /proc/cpuinfo
66100func 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