1// 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}
482