@@ -16,6 +16,8 @@ package procfs
1616import (
1717 "bufio"
1818 "bytes"
19+ "errors"
20+ "regexp"
1921 "strconv"
2022 "strings"
2123
@@ -52,6 +54,41 @@ 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+ cpuinfoClockRegexp = regexp .MustCompile (`([\d.]+)` )
72+ cpuinfoS390XProcessorRegexp = regexp .MustCompile (`^processor\s+(\d+):.*` )
73+ )
74+
75+ // cpuinfoDetectFormat attempts to determine the format used by the cpuinfo.
76+ // This format corresponds to the platform generating the /proc/cpuinfo file.
77+ // Returns "unknown"
78+ func cpuinfoDetectFormat (info []byte ) string {
79+ switch {
80+ case cpuinfoX86Regexp .Match (info ):
81+ return platformX86
82+ case cpuinfoARMRegexp .Match (info ):
83+ return platformARM
84+ case cpuinfoPPCRegexp .Match (info ):
85+ return platformPPC
86+ case cpuinfoS390XRegexp .Match (info ):
87+ return platformS390X
88+ }
89+ return platformUnknown
90+ }
91+
5592// CPUInfo returns information about current system CPUs.
5693// See https://www.kernel.org/doc/Documentation/filesystems/proc.txt
5794func (fs FS ) CPUInfo () ([]CPUInfo , error ) {
@@ -64,12 +101,40 @@ func (fs FS) CPUInfo() ([]CPUInfo, error) {
64101
65102// parseCPUInfo parses data from /proc/cpuinfo
66103func parseCPUInfo (info []byte ) ([]CPUInfo , error ) {
67- cpuinfo := []CPUInfo {}
68- i := - 1
104+ platform := cpuinfoDetectFormat (info )
105+ switch platform {
106+ case platformX86 :
107+ return parseCPUInfoX86 (info )
108+ case platformARM :
109+ return parseCPUInfoARM (info )
110+ case platformS390X :
111+ return parseCPUInfoS390X (info )
112+ case platformPPC :
113+ return parseCPUInfoPPC (info )
114+ }
115+ return nil , errors .New ("unable to determine format of 'cpuinfo'" )
116+ }
117+
118+ func parseCPUInfoX86 (info []byte ) ([]CPUInfo , error ) {
69119 scanner := bufio .NewScanner (bytes .NewReader (info ))
120+
121+ // find the first "processor" line
122+ firstLine := firstNonEmptyLine (scanner )
123+ if ! strings .HasPrefix (firstLine , "processor" ) || ! strings .Contains (firstLine , ":" ) {
124+ return nil , errors .New ("invalid cpuinfo file: " + firstLine )
125+ }
126+ field := strings .SplitN (firstLine , ": " , 2 )
127+ v , err := strconv .ParseUint (field [1 ], 0 , 32 )
128+ if err != nil {
129+ return nil , err
130+ }
131+ firstcpu := CPUInfo {Processor : uint (v )}
132+ cpuinfo := []CPUInfo {firstcpu }
133+ i := 0
134+
70135 for scanner .Scan () {
71136 line := scanner .Text ()
72- if strings .TrimSpace (line ) == "" {
137+ if ! strings .Contains (line , ":" ) {
73138 continue
74139 }
75140 field := strings .SplitN (line , ": " , 2 )
@@ -82,7 +147,7 @@ func parseCPUInfo(info []byte) ([]CPUInfo, error) {
82147 return nil , err
83148 }
84149 cpuinfo [i ].Processor = uint (v )
85- case "vendor_id" :
150+ case "vendor" , " vendor_id" :
86151 cpuinfo [i ].VendorID = field [1 ]
87152 case "cpu family" :
88153 cpuinfo [i ].CPUFamily = field [1 ]
@@ -163,5 +228,175 @@ func parseCPUInfo(info []byte) ([]CPUInfo, error) {
163228 }
164229 }
165230 return cpuinfo , nil
231+ }
232+
233+ func parseCPUInfoARM (info []byte ) ([]CPUInfo , error ) {
234+ scanner := bufio .NewScanner (bytes .NewReader (info ))
235+
236+ firstLine := firstNonEmptyLine (scanner )
237+ if ! strings .HasPrefix (firstLine , "Processor" ) || ! strings .Contains (firstLine , ":" ) {
238+ return nil , errors .New ("invalid cpuinfo file: " + firstLine )
239+ }
240+ field := strings .SplitN (firstLine , ": " , 2 )
241+ commonCPUInfo := CPUInfo {VendorID : field [1 ]}
242+
243+ cpuinfo := []CPUInfo {}
244+ i := - 1
245+ featuresLine := ""
246+
247+ for scanner .Scan () {
248+ line := scanner .Text ()
249+ if ! strings .Contains (line , ":" ) {
250+ continue
251+ }
252+ field := strings .SplitN (line , ": " , 2 )
253+ switch strings .TrimSpace (field [0 ]) {
254+ case "processor" :
255+ cpuinfo = append (cpuinfo , commonCPUInfo ) // start of the next processor
256+ i ++
257+ v , err := strconv .ParseUint (field [1 ], 0 , 32 )
258+ if err != nil {
259+ return nil , err
260+ }
261+ cpuinfo [i ].Processor = uint (v )
262+ case "BogoMIPS" :
263+ v , err := strconv .ParseFloat (field [1 ], 64 )
264+ if err != nil {
265+ return nil , err
266+ }
267+ cpuinfo [i ].BogoMips = v
268+ case "Features" :
269+ featuresLine = line
270+ }
271+ }
272+ fields := strings .SplitN (featuresLine , ": " , 2 )
273+ for i := range cpuinfo {
274+ cpuinfo [i ].Flags = strings .Fields (fields [1 ])
275+ }
276+ return cpuinfo , nil
277+ }
278+
279+ func parseCPUInfoS390X (info []byte ) ([]CPUInfo , error ) {
280+ scanner := bufio .NewScanner (bytes .NewReader (info ))
281+
282+ firstLine := firstNonEmptyLine (scanner )
283+ if ! strings .HasPrefix (firstLine , "vendor_id" ) || ! strings .Contains (firstLine , ":" ) {
284+ return nil , errors .New ("invalid cpuinfo file: " + firstLine )
285+ }
286+ field := strings .SplitN (firstLine , ": " , 2 )
287+ cpuinfo := []CPUInfo {}
288+ commonCPUInfo := CPUInfo {VendorID : field [1 ]}
166289
290+ for scanner .Scan () {
291+ line := scanner .Text ()
292+ if ! strings .Contains (line , ":" ) {
293+ continue
294+ }
295+ field := strings .SplitN (line , ": " , 2 )
296+ switch strings .TrimSpace (field [0 ]) {
297+ case "bogomips per cpu" :
298+ v , err := strconv .ParseFloat (field [1 ], 64 )
299+ if err != nil {
300+ return nil , err
301+ }
302+ commonCPUInfo .BogoMips = v
303+ case "features" :
304+ commonCPUInfo .Flags = strings .Fields (field [1 ])
305+ }
306+ if strings .HasPrefix (line , "processor" ) {
307+ match := cpuinfoS390XProcessorRegexp .FindStringSubmatch (line )
308+ if len (match ) < 2 {
309+ return nil , errors .New ("Invalid line found in cpuinfo: " + line )
310+ }
311+ cpu := commonCPUInfo
312+ v , err := strconv .ParseUint (match [1 ], 0 , 32 )
313+ if err != nil {
314+ return nil , err
315+ }
316+ cpu .Processor = uint (v )
317+ cpuinfo = append (cpuinfo , cpu )
318+ }
319+ if strings .HasPrefix (line , "cpu number" ) {
320+ break
321+ }
322+ }
323+
324+ i := 0
325+ for scanner .Scan () {
326+ line := scanner .Text ()
327+ if ! strings .Contains (line , ":" ) {
328+ continue
329+ }
330+ field := strings .SplitN (line , ": " , 2 )
331+ switch strings .TrimSpace (field [0 ]) {
332+ case "cpu number" :
333+ i ++
334+ case "cpu MHz dynamic" :
335+ clock := cpuinfoClockRegexp .FindString (strings .TrimSpace (field [1 ]))
336+ v , err := strconv .ParseFloat (clock , 64 )
337+ if err != nil {
338+ return nil , err
339+ }
340+ cpuinfo [i ].CPUMHz = v
341+ }
342+ }
343+
344+ return cpuinfo , nil
345+ }
346+
347+ func parseCPUInfoPPC (info []byte ) ([]CPUInfo , error ) {
348+ scanner := bufio .NewScanner (bytes .NewReader (info ))
349+
350+ firstLine := firstNonEmptyLine (scanner )
351+ if ! strings .HasPrefix (firstLine , "processor" ) || ! strings .Contains (firstLine , ":" ) {
352+ return nil , errors .New ("invalid cpuinfo file: " + firstLine )
353+ }
354+ field := strings .SplitN (firstLine , ": " , 2 )
355+ v , err := strconv .ParseUint (field [1 ], 0 , 32 )
356+ if err != nil {
357+ return nil , err
358+ }
359+ firstcpu := CPUInfo {Processor : uint (v )}
360+ cpuinfo := []CPUInfo {firstcpu }
361+ i := 0
362+
363+ for scanner .Scan () {
364+ line := scanner .Text ()
365+ if ! strings .Contains (line , ":" ) {
366+ continue
367+ }
368+ field := strings .SplitN (line , ": " , 2 )
369+ switch strings .TrimSpace (field [0 ]) {
370+ case "processor" :
371+ cpuinfo = append (cpuinfo , CPUInfo {}) // start of the next processor
372+ i ++
373+ v , err := strconv .ParseUint (field [1 ], 0 , 32 )
374+ if err != nil {
375+ return nil , err
376+ }
377+ cpuinfo [i ].Processor = uint (v )
378+ case "cpu" :
379+ cpuinfo [i ].VendorID = field [1 ]
380+ case "clock" :
381+ clock := cpuinfoClockRegexp .FindString (strings .TrimSpace (field [1 ]))
382+ v , err := strconv .ParseFloat (clock , 64 )
383+ if err != nil {
384+ return nil , err
385+ }
386+ cpuinfo [i ].CPUMHz = v
387+ }
388+ }
389+ return cpuinfo , nil
390+ }
391+
392+ // firstNonEmptyLine advances the scanner to the first non-empty line
393+ // and returns the contents of that line
394+ func firstNonEmptyLine (scanner * bufio.Scanner ) string {
395+ for scanner .Scan () {
396+ line := scanner .Text ()
397+ if strings .TrimSpace (line ) != "" {
398+ return line
399+ }
400+ }
401+ return ""
167402}
0 commit comments