1// +build linux
2
3package cpu
4
5import (
6	"context"
7	"errors"
8	"fmt"
9	"path/filepath"
10	"strconv"
11	"strings"
12
13	"github.com/shirou/gopsutil/v3/internal/common"
14	"github.com/tklauser/go-sysconf"
15)
16
17var ClocksPerSec = float64(100)
18
19func init() {
20	clkTck, err := sysconf.Sysconf(sysconf.SC_CLK_TCK)
21	// ignore errors
22	if err == nil {
23		ClocksPerSec = float64(clkTck)
24	}
25}
26
27func Times(percpu bool) ([]TimesStat, error) {
28	return TimesWithContext(context.Background(), percpu)
29}
30
31func TimesWithContext(ctx context.Context, percpu bool) ([]TimesStat, error) {
32	filename := common.HostProc("stat")
33	var lines = []string{}
34	if percpu {
35		statlines, err := common.ReadLines(filename)
36		if err != nil || len(statlines) < 2 {
37			return []TimesStat{}, nil
38		}
39		for _, line := range statlines[1:] {
40			if !strings.HasPrefix(line, "cpu") {
41				break
42			}
43			lines = append(lines, line)
44		}
45	} else {
46		lines, _ = common.ReadLinesOffsetN(filename, 0, 1)
47	}
48
49	ret := make([]TimesStat, 0, len(lines))
50
51	for _, line := range lines {
52		ct, err := parseStatLine(line)
53		if err != nil {
54			continue
55		}
56		ret = append(ret, *ct)
57
58	}
59	return ret, nil
60}
61
62func sysCPUPath(cpu int32, relPath string) string {
63	return common.HostSys(fmt.Sprintf("devices/system/cpu/cpu%d", cpu), relPath)
64}
65
66func finishCPUInfo(c *InfoStat) error {
67	var lines []string
68	var err error
69	var value float64
70
71	if len(c.CoreID) == 0 {
72		lines, err = common.ReadLines(sysCPUPath(c.CPU, "topology/core_id"))
73		if err == nil {
74			c.CoreID = lines[0]
75		}
76	}
77
78	// override the value of c.Mhz with cpufreq/cpuinfo_max_freq regardless
79	// of the value from /proc/cpuinfo because we want to report the maximum
80	// clock-speed of the CPU for c.Mhz, matching the behaviour of Windows
81	lines, err = common.ReadLines(sysCPUPath(c.CPU, "cpufreq/cpuinfo_max_freq"))
82	// if we encounter errors below such as there are no cpuinfo_max_freq file,
83	// we just ignore. so let Mhz is 0.
84	if err != nil || len(lines) == 0 {
85		return nil
86	}
87	value, err = strconv.ParseFloat(lines[0], 64)
88	if err != nil {
89		return nil
90	}
91	c.Mhz = value / 1000.0 // value is in kHz
92	if c.Mhz > 9999 {
93		c.Mhz = c.Mhz / 1000.0 // value in Hz
94	}
95	return nil
96}
97
98// CPUInfo on linux will return 1 item per physical thread.
99//
100// CPUs have three levels of counting: sockets, cores, threads.
101// Cores with HyperThreading count as having 2 threads per core.
102// Sockets often come with many physical CPU cores.
103// For example a single socket board with two cores each with HT will
104// return 4 CPUInfoStat structs on Linux and the "Cores" field set to 1.
105func Info() ([]InfoStat, error) {
106	return InfoWithContext(context.Background())
107}
108
109func InfoWithContext(ctx context.Context) ([]InfoStat, error) {
110	filename := common.HostProc("cpuinfo")
111	lines, _ := common.ReadLines(filename)
112
113	var ret []InfoStat
114	var processorName string
115
116	c := InfoStat{CPU: -1, Cores: 1}
117	for _, line := range lines {
118		fields := strings.Split(line, ":")
119		if len(fields) < 2 {
120			continue
121		}
122		key := strings.TrimSpace(fields[0])
123		value := strings.TrimSpace(fields[1])
124
125		switch key {
126		case "Processor":
127			processorName = value
128		case "processor":
129			if c.CPU >= 0 {
130				err := finishCPUInfo(&c)
131				if err != nil {
132					return ret, err
133				}
134				ret = append(ret, c)
135			}
136			c = InfoStat{Cores: 1, ModelName: processorName}
137			t, err := strconv.ParseInt(value, 10, 64)
138			if err != nil {
139				return ret, err
140			}
141			c.CPU = int32(t)
142		case "vendorId", "vendor_id":
143			c.VendorID = value
144		case "CPU implementer":
145			if v, err := strconv.ParseUint(value, 0, 8); err == nil {
146				switch v {
147				case 0x41:
148					c.VendorID = "ARM"
149				case 0x42:
150					c.VendorID = "Broadcom"
151				case 0x43:
152					c.VendorID = "Cavium"
153				case 0x44:
154					c.VendorID = "DEC"
155				case 0x46:
156					c.VendorID = "Fujitsu"
157				case 0x48:
158					c.VendorID = "HiSilicon"
159				case 0x49:
160					c.VendorID = "Infineon"
161				case 0x4d:
162					c.VendorID = "Motorola/Freescale"
163				case 0x4e:
164					c.VendorID = "NVIDIA"
165				case 0x50:
166					c.VendorID = "APM"
167				case 0x51:
168					c.VendorID = "Qualcomm"
169				case 0x56:
170					c.VendorID = "Marvell"
171				case 0x61:
172					c.VendorID = "Apple"
173				case 0x69:
174					c.VendorID = "Intel"
175				case 0xc0:
176					c.VendorID = "Ampere"
177				}
178			}
179		case "cpu family":
180			c.Family = value
181		case "model", "CPU part":
182			c.Model = value
183		case "model name", "cpu":
184			c.ModelName = value
185			if strings.Contains(value, "POWER8") ||
186				strings.Contains(value, "POWER7") {
187				c.Model = strings.Split(value, " ")[0]
188				c.Family = "POWER"
189				c.VendorID = "IBM"
190			}
191		case "stepping", "revision", "CPU revision":
192			val := value
193
194			if key == "revision" {
195				val = strings.Split(value, ".")[0]
196			}
197
198			t, err := strconv.ParseInt(val, 10, 64)
199			if err != nil {
200				return ret, err
201			}
202			c.Stepping = int32(t)
203		case "cpu MHz", "clock":
204			// treat this as the fallback value, thus we ignore error
205			if t, err := strconv.ParseFloat(strings.Replace(value, "MHz", "", 1), 64); err == nil {
206				c.Mhz = t
207			}
208		case "cache size":
209			t, err := strconv.ParseInt(strings.Replace(value, " KB", "", 1), 10, 64)
210			if err != nil {
211				return ret, err
212			}
213			c.CacheSize = int32(t)
214		case "physical id":
215			c.PhysicalID = value
216		case "core id":
217			c.CoreID = value
218		case "flags", "Features":
219			c.Flags = strings.FieldsFunc(value, func(r rune) bool {
220				return r == ',' || r == ' '
221			})
222		case "microcode":
223			c.Microcode = value
224		}
225	}
226	if c.CPU >= 0 {
227		err := finishCPUInfo(&c)
228		if err != nil {
229			return ret, err
230		}
231		ret = append(ret, c)
232	}
233	return ret, nil
234}
235
236func parseStatLine(line string) (*TimesStat, error) {
237	fields := strings.Fields(line)
238
239	if len(fields) == 0 {
240		return nil, errors.New("stat does not contain cpu info")
241	}
242
243	if strings.HasPrefix(fields[0], "cpu") == false {
244		return nil, errors.New("not contain cpu")
245	}
246
247	cpu := fields[0]
248	if cpu == "cpu" {
249		cpu = "cpu-total"
250	}
251	user, err := strconv.ParseFloat(fields[1], 64)
252	if err != nil {
253		return nil, err
254	}
255	nice, err := strconv.ParseFloat(fields[2], 64)
256	if err != nil {
257		return nil, err
258	}
259	system, err := strconv.ParseFloat(fields[3], 64)
260	if err != nil {
261		return nil, err
262	}
263	idle, err := strconv.ParseFloat(fields[4], 64)
264	if err != nil {
265		return nil, err
266	}
267	iowait, err := strconv.ParseFloat(fields[5], 64)
268	if err != nil {
269		return nil, err
270	}
271	irq, err := strconv.ParseFloat(fields[6], 64)
272	if err != nil {
273		return nil, err
274	}
275	softirq, err := strconv.ParseFloat(fields[7], 64)
276	if err != nil {
277		return nil, err
278	}
279
280	ct := &TimesStat{
281		CPU:     cpu,
282		User:    user / ClocksPerSec,
283		Nice:    nice / ClocksPerSec,
284		System:  system / ClocksPerSec,
285		Idle:    idle / ClocksPerSec,
286		Iowait:  iowait / ClocksPerSec,
287		Irq:     irq / ClocksPerSec,
288		Softirq: softirq / ClocksPerSec,
289	}
290	if len(fields) > 8 { // Linux >= 2.6.11
291		steal, err := strconv.ParseFloat(fields[8], 64)
292		if err != nil {
293			return nil, err
294		}
295		ct.Steal = steal / ClocksPerSec
296	}
297	if len(fields) > 9 { // Linux >= 2.6.24
298		guest, err := strconv.ParseFloat(fields[9], 64)
299		if err != nil {
300			return nil, err
301		}
302		ct.Guest = guest / ClocksPerSec
303	}
304	if len(fields) > 10 { // Linux >= 3.2.0
305		guestNice, err := strconv.ParseFloat(fields[10], 64)
306		if err != nil {
307			return nil, err
308		}
309		ct.GuestNice = guestNice / ClocksPerSec
310	}
311
312	return ct, nil
313}
314
315func CountsWithContext(ctx context.Context, logical bool) (int, error) {
316	if logical {
317		ret := 0
318		// https://github.com/giampaolo/psutil/blob/d01a9eaa35a8aadf6c519839e987a49d8be2d891/psutil/_pslinux.py#L599
319		procCpuinfo := common.HostProc("cpuinfo")
320		lines, err := common.ReadLines(procCpuinfo)
321		if err == nil {
322			for _, line := range lines {
323				line = strings.ToLower(line)
324				if strings.HasPrefix(line, "processor") {
325					_, err = strconv.Atoi(strings.TrimSpace(line[strings.IndexByte(line, ':')+1:]))
326					if err == nil {
327						ret++
328					}
329				}
330			}
331		}
332		if ret == 0 {
333			procStat := common.HostProc("stat")
334			lines, err = common.ReadLines(procStat)
335			if err != nil {
336				return 0, err
337			}
338			for _, line := range lines {
339				if len(line) >= 4 && strings.HasPrefix(line, "cpu") && '0' <= line[3] && line[3] <= '9' { // `^cpu\d` regexp matching
340					ret++
341				}
342			}
343		}
344		return ret, nil
345	}
346	// physical cores
347	// https://github.com/giampaolo/psutil/blob/8415355c8badc9c94418b19bdf26e622f06f0cce/psutil/_pslinux.py#L615-L628
348	var threadSiblingsLists = make(map[string]bool)
349	// These 2 files are the same but */core_cpus_list is newer while */thread_siblings_list is deprecated and may disappear in the future.
350	// https://www.kernel.org/doc/Documentation/admin-guide/cputopology.rst
351	// https://github.com/giampaolo/psutil/pull/1727#issuecomment-707624964
352	// https://lkml.org/lkml/2019/2/26/41
353	for _, glob := range []string{"devices/system/cpu/cpu[0-9]*/topology/core_cpus_list", "devices/system/cpu/cpu[0-9]*/topology/thread_siblings_list"} {
354		if files, err := filepath.Glob(common.HostSys(glob)); err == nil {
355			for _, file := range files {
356				lines, err := common.ReadLines(file)
357				if err != nil || len(lines) != 1 {
358					continue
359				}
360				threadSiblingsLists[lines[0]] = true
361			}
362			ret := len(threadSiblingsLists)
363			if ret != 0 {
364				return ret, nil
365			}
366		}
367	}
368	// https://github.com/giampaolo/psutil/blob/122174a10b75c9beebe15f6c07dcf3afbe3b120d/psutil/_pslinux.py#L631-L652
369	filename := common.HostProc("cpuinfo")
370	lines, err := common.ReadLines(filename)
371	if err != nil {
372		return 0, err
373	}
374	mapping := make(map[int]int)
375	currentInfo := make(map[string]int)
376	for _, line := range lines {
377		line = strings.ToLower(strings.TrimSpace(line))
378		if line == "" {
379			// new section
380			id, okID := currentInfo["physical id"]
381			cores, okCores := currentInfo["cpu cores"]
382			if okID && okCores {
383				mapping[id] = cores
384			}
385			currentInfo = make(map[string]int)
386			continue
387		}
388		fields := strings.Split(line, ":")
389		if len(fields) < 2 {
390			continue
391		}
392		fields[0] = strings.TrimSpace(fields[0])
393		if fields[0] == "physical id" || fields[0] == "cpu cores" {
394			val, err := strconv.Atoi(strings.TrimSpace(fields[1]))
395			if err != nil {
396				continue
397			}
398			currentInfo[fields[0]] = val
399		}
400	}
401	ret := 0
402	for _, v := range mapping {
403		ret += v
404	}
405	return ret, nil
406}
407