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	"regexp"
23	"strconv"
24	"strings"
25
26	"github.com/prometheus/procfs/internal/util"
27)
28
29// CPUInfo contains general information about a system CPU found in /proc/cpuinfo
30type CPUInfo struct {
31	Processor       uint
32	VendorID        string
33	CPUFamily       string
34	Model           string
35	ModelName       string
36	Stepping        string
37	Microcode       string
38	CPUMHz          float64
39	CacheSize       string
40	PhysicalID      string
41	Siblings        uint
42	CoreID          string
43	CPUCores        uint
44	APICID          string
45	InitialAPICID   string
46	FPU             string
47	FPUException    string
48	CPUIDLevel      uint
49	WP              string
50	Flags           []string
51	Bugs            []string
52	BogoMips        float64
53	CLFlushSize     uint
54	CacheAlignment  uint
55	AddressSizes    string
56	PowerManagement string
57}
58
59var (
60	cpuinfoClockRegexp          = regexp.MustCompile(`([\d.]+)`)
61	cpuinfoS390XProcessorRegexp = regexp.MustCompile(`^processor\s+(\d+):.*`)
62)
63
64// CPUInfo returns information about current system CPUs.
65// See https://www.kernel.org/doc/Documentation/filesystems/proc.txt
66func (fs FS) CPUInfo() ([]CPUInfo, error) {
67	data, err := util.ReadFileNoStat(fs.proc.Path("cpuinfo"))
68	if err != nil {
69		return nil, err
70	}
71	return parseCPUInfo(data)
72}
73
74func parseCPUInfoX86(info []byte) ([]CPUInfo, error) {
75	scanner := bufio.NewScanner(bytes.NewReader(info))
76
77	// find the first "processor" line
78	firstLine := firstNonEmptyLine(scanner)
79	if !strings.HasPrefix(firstLine, "processor") || !strings.Contains(firstLine, ":") {
80		return nil, errors.New("invalid cpuinfo file: " + firstLine)
81	}
82	field := strings.SplitN(firstLine, ": ", 2)
83	v, err := strconv.ParseUint(field[1], 0, 32)
84	if err != nil {
85		return nil, err
86	}
87	firstcpu := CPUInfo{Processor: uint(v)}
88	cpuinfo := []CPUInfo{firstcpu}
89	i := 0
90
91	for scanner.Scan() {
92		line := scanner.Text()
93		if !strings.Contains(line, ":") {
94			continue
95		}
96		field := strings.SplitN(line, ": ", 2)
97		switch strings.TrimSpace(field[0]) {
98		case "processor":
99			cpuinfo = append(cpuinfo, CPUInfo{}) // start of the next processor
100			i++
101			v, err := strconv.ParseUint(field[1], 0, 32)
102			if err != nil {
103				return nil, err
104			}
105			cpuinfo[i].Processor = uint(v)
106		case "vendor", "vendor_id":
107			cpuinfo[i].VendorID = field[1]
108		case "cpu family":
109			cpuinfo[i].CPUFamily = field[1]
110		case "model":
111			cpuinfo[i].Model = field[1]
112		case "model name":
113			cpuinfo[i].ModelName = field[1]
114		case "stepping":
115			cpuinfo[i].Stepping = field[1]
116		case "microcode":
117			cpuinfo[i].Microcode = field[1]
118		case "cpu MHz":
119			v, err := strconv.ParseFloat(field[1], 64)
120			if err != nil {
121				return nil, err
122			}
123			cpuinfo[i].CPUMHz = v
124		case "cache size":
125			cpuinfo[i].CacheSize = field[1]
126		case "physical id":
127			cpuinfo[i].PhysicalID = field[1]
128		case "siblings":
129			v, err := strconv.ParseUint(field[1], 0, 32)
130			if err != nil {
131				return nil, err
132			}
133			cpuinfo[i].Siblings = uint(v)
134		case "core id":
135			cpuinfo[i].CoreID = field[1]
136		case "cpu cores":
137			v, err := strconv.ParseUint(field[1], 0, 32)
138			if err != nil {
139				return nil, err
140			}
141			cpuinfo[i].CPUCores = uint(v)
142		case "apicid":
143			cpuinfo[i].APICID = field[1]
144		case "initial apicid":
145			cpuinfo[i].InitialAPICID = field[1]
146		case "fpu":
147			cpuinfo[i].FPU = field[1]
148		case "fpu_exception":
149			cpuinfo[i].FPUException = field[1]
150		case "cpuid level":
151			v, err := strconv.ParseUint(field[1], 0, 32)
152			if err != nil {
153				return nil, err
154			}
155			cpuinfo[i].CPUIDLevel = uint(v)
156		case "wp":
157			cpuinfo[i].WP = field[1]
158		case "flags":
159			cpuinfo[i].Flags = strings.Fields(field[1])
160		case "bugs":
161			cpuinfo[i].Bugs = strings.Fields(field[1])
162		case "bogomips":
163			v, err := strconv.ParseFloat(field[1], 64)
164			if err != nil {
165				return nil, err
166			}
167			cpuinfo[i].BogoMips = v
168		case "clflush size":
169			v, err := strconv.ParseUint(field[1], 0, 32)
170			if err != nil {
171				return nil, err
172			}
173			cpuinfo[i].CLFlushSize = uint(v)
174		case "cache_alignment":
175			v, err := strconv.ParseUint(field[1], 0, 32)
176			if err != nil {
177				return nil, err
178			}
179			cpuinfo[i].CacheAlignment = uint(v)
180		case "address sizes":
181			cpuinfo[i].AddressSizes = field[1]
182		case "power management":
183			cpuinfo[i].PowerManagement = field[1]
184		}
185	}
186	return cpuinfo, nil
187}
188
189func parseCPUInfoARM(info []byte) ([]CPUInfo, error) {
190	scanner := bufio.NewScanner(bytes.NewReader(info))
191
192	firstLine := firstNonEmptyLine(scanner)
193	match, _ := regexp.MatchString("^[Pp]rocessor", firstLine)
194	if !match || !strings.Contains(firstLine, ":") {
195		return nil, errors.New("invalid cpuinfo file: " + firstLine)
196	}
197	field := strings.SplitN(firstLine, ": ", 2)
198	cpuinfo := []CPUInfo{}
199	featuresLine := ""
200	commonCPUInfo := CPUInfo{}
201	i := 0
202	if strings.TrimSpace(field[0]) == "Processor" {
203		commonCPUInfo = CPUInfo{ModelName: field[1]}
204		i = -1
205	} else {
206		v, err := strconv.ParseUint(field[1], 0, 32)
207		if err != nil {
208			return nil, err
209		}
210		firstcpu := CPUInfo{Processor: uint(v)}
211		cpuinfo = []CPUInfo{firstcpu}
212	}
213
214	for scanner.Scan() {
215		line := scanner.Text()
216		if !strings.Contains(line, ":") {
217			continue
218		}
219		field := strings.SplitN(line, ": ", 2)
220		switch strings.TrimSpace(field[0]) {
221		case "processor":
222			cpuinfo = append(cpuinfo, commonCPUInfo) // start of the next processor
223			i++
224			v, err := strconv.ParseUint(field[1], 0, 32)
225			if err != nil {
226				return nil, err
227			}
228			cpuinfo[i].Processor = uint(v)
229		case "BogoMIPS":
230			if i == -1 {
231				cpuinfo = append(cpuinfo, commonCPUInfo) // There is only one processor
232				i++
233				cpuinfo[i].Processor = 0
234			}
235			v, err := strconv.ParseFloat(field[1], 64)
236			if err != nil {
237				return nil, err
238			}
239			cpuinfo[i].BogoMips = v
240		case "Features":
241			featuresLine = line
242		case "model name":
243			cpuinfo[i].ModelName = field[1]
244		}
245	}
246	fields := strings.SplitN(featuresLine, ": ", 2)
247	for i := range cpuinfo {
248		cpuinfo[i].Flags = strings.Fields(fields[1])
249	}
250	return cpuinfo, nil
251
252}
253
254func parseCPUInfoS390X(info []byte) ([]CPUInfo, error) {
255	scanner := bufio.NewScanner(bytes.NewReader(info))
256
257	firstLine := firstNonEmptyLine(scanner)
258	if !strings.HasPrefix(firstLine, "vendor_id") || !strings.Contains(firstLine, ":") {
259		return nil, errors.New("invalid cpuinfo file: " + firstLine)
260	}
261	field := strings.SplitN(firstLine, ": ", 2)
262	cpuinfo := []CPUInfo{}
263	commonCPUInfo := CPUInfo{VendorID: field[1]}
264
265	for scanner.Scan() {
266		line := scanner.Text()
267		if !strings.Contains(line, ":") {
268			continue
269		}
270		field := strings.SplitN(line, ": ", 2)
271		switch strings.TrimSpace(field[0]) {
272		case "bogomips per cpu":
273			v, err := strconv.ParseFloat(field[1], 64)
274			if err != nil {
275				return nil, err
276			}
277			commonCPUInfo.BogoMips = v
278		case "features":
279			commonCPUInfo.Flags = strings.Fields(field[1])
280		}
281		if strings.HasPrefix(line, "processor") {
282			match := cpuinfoS390XProcessorRegexp.FindStringSubmatch(line)
283			if len(match) < 2 {
284				return nil, errors.New("Invalid line found in cpuinfo: " + line)
285			}
286			cpu := commonCPUInfo
287			v, err := strconv.ParseUint(match[1], 0, 32)
288			if err != nil {
289				return nil, err
290			}
291			cpu.Processor = uint(v)
292			cpuinfo = append(cpuinfo, cpu)
293		}
294		if strings.HasPrefix(line, "cpu number") {
295			break
296		}
297	}
298
299	i := 0
300	for scanner.Scan() {
301		line := scanner.Text()
302		if !strings.Contains(line, ":") {
303			continue
304		}
305		field := strings.SplitN(line, ": ", 2)
306		switch strings.TrimSpace(field[0]) {
307		case "cpu number":
308			i++
309		case "cpu MHz dynamic":
310			clock := cpuinfoClockRegexp.FindString(strings.TrimSpace(field[1]))
311			v, err := strconv.ParseFloat(clock, 64)
312			if err != nil {
313				return nil, err
314			}
315			cpuinfo[i].CPUMHz = v
316		}
317	}
318
319	return cpuinfo, nil
320}
321
322func parseCPUInfoMips(info []byte) ([]CPUInfo, error) {
323	scanner := bufio.NewScanner(bytes.NewReader(info))
324
325	// find the first "processor" line
326	firstLine := firstNonEmptyLine(scanner)
327	if !strings.HasPrefix(firstLine, "system type") || !strings.Contains(firstLine, ":") {
328		return nil, errors.New("invalid cpuinfo file: " + firstLine)
329	}
330	field := strings.SplitN(firstLine, ": ", 2)
331	cpuinfo := []CPUInfo{}
332	systemType := field[1]
333
334	i := 0
335
336	for scanner.Scan() {
337		line := scanner.Text()
338		if !strings.Contains(line, ":") {
339			continue
340		}
341		field := strings.SplitN(line, ": ", 2)
342		switch strings.TrimSpace(field[0]) {
343		case "processor":
344			v, err := strconv.ParseUint(field[1], 0, 32)
345			if err != nil {
346				return nil, err
347			}
348			i = int(v)
349			cpuinfo = append(cpuinfo, CPUInfo{}) // start of the next processor
350			cpuinfo[i].Processor = uint(v)
351			cpuinfo[i].VendorID = systemType
352		case "cpu model":
353			cpuinfo[i].ModelName = field[1]
354		case "BogoMIPS":
355			v, err := strconv.ParseFloat(field[1], 64)
356			if err != nil {
357				return nil, err
358			}
359			cpuinfo[i].BogoMips = v
360		}
361	}
362	return cpuinfo, nil
363}
364
365func parseCPUInfoPPC(info []byte) ([]CPUInfo, error) {
366	scanner := bufio.NewScanner(bytes.NewReader(info))
367
368	firstLine := firstNonEmptyLine(scanner)
369	if !strings.HasPrefix(firstLine, "processor") || !strings.Contains(firstLine, ":") {
370		return nil, errors.New("invalid cpuinfo file: " + firstLine)
371	}
372	field := strings.SplitN(firstLine, ": ", 2)
373	v, err := strconv.ParseUint(field[1], 0, 32)
374	if err != nil {
375		return nil, err
376	}
377	firstcpu := CPUInfo{Processor: uint(v)}
378	cpuinfo := []CPUInfo{firstcpu}
379	i := 0
380
381	for scanner.Scan() {
382		line := scanner.Text()
383		if !strings.Contains(line, ":") {
384			continue
385		}
386		field := strings.SplitN(line, ": ", 2)
387		switch strings.TrimSpace(field[0]) {
388		case "processor":
389			cpuinfo = append(cpuinfo, CPUInfo{}) // start of the next processor
390			i++
391			v, err := strconv.ParseUint(field[1], 0, 32)
392			if err != nil {
393				return nil, err
394			}
395			cpuinfo[i].Processor = uint(v)
396		case "cpu":
397			cpuinfo[i].VendorID = field[1]
398		case "clock":
399			clock := cpuinfoClockRegexp.FindString(strings.TrimSpace(field[1]))
400			v, err := strconv.ParseFloat(clock, 64)
401			if err != nil {
402				return nil, err
403			}
404			cpuinfo[i].CPUMHz = v
405		}
406	}
407	return cpuinfo, nil
408}
409
410// firstNonEmptyLine advances the scanner to the first non-empty line
411// and returns the contents of that line
412func firstNonEmptyLine(scanner *bufio.Scanner) string {
413	for scanner.Scan() {
414		line := scanner.Text()
415		if strings.TrimSpace(line) != "" {
416			return line
417		}
418	}
419	return ""
420}
421