1package cpu
2
3import (
4	"context"
5	"fmt"
6	"reflect"
7	"regexp"
8	"runtime"
9	"strconv"
10	"strings"
11	"unsafe"
12
13	"github.com/shirou/gopsutil/internal/common"
14	"github.com/tklauser/go-sysconf"
15	"golang.org/x/sys/unix"
16)
17
18var ClocksPerSec = float64(128)
19var cpuMatch = regexp.MustCompile(`^CPU:`)
20var originMatch = regexp.MustCompile(`Origin\s*=\s*"(.+)"\s+Id\s*=\s*(.+)\s+Stepping\s*=\s*(.+)`)
21var featuresMatch = regexp.MustCompile(`Features=.+<(.+)>`)
22var featuresMatch2 = regexp.MustCompile(`Features2=[a-f\dx]+<(.+)>`)
23var cpuEnd = regexp.MustCompile(`^Trying to mount root`)
24var cpuTimesSize int
25var emptyTimes cpuTimes
26
27func init() {
28	clkTck, err := sysconf.Sysconf(sysconf.SC_CLK_TCK)
29	// ignore errors
30	if err == nil {
31		ClocksPerSec = float64(clkTck)
32	}
33}
34
35func timeStat(name string, t *cpuTimes) *TimesStat {
36	return &TimesStat{
37		User:   float64(t.User) / ClocksPerSec,
38		Nice:   float64(t.Nice) / ClocksPerSec,
39		System: float64(t.Sys) / ClocksPerSec,
40		Idle:   float64(t.Idle) / ClocksPerSec,
41		Irq:    float64(t.Intr) / ClocksPerSec,
42		CPU:    name,
43	}
44}
45
46func Times(percpu bool) ([]TimesStat, error) {
47	return TimesWithContext(context.Background(), percpu)
48}
49
50func TimesWithContext(ctx context.Context, percpu bool) ([]TimesStat, error) {
51	if percpu {
52		buf, err := unix.SysctlRaw("kern.cp_times")
53		if err != nil {
54			return nil, err
55		}
56
57		// We can't do this in init due to the conflict with cpu.init()
58		if cpuTimesSize == 0 {
59			cpuTimesSize = int(reflect.TypeOf(cpuTimes{}).Size())
60		}
61
62		ncpus := len(buf) / cpuTimesSize
63		ret := make([]TimesStat, 0, ncpus)
64		for i := 0; i < ncpus; i++ {
65			times := (*cpuTimes)(unsafe.Pointer(&buf[i*cpuTimesSize]))
66			if *times == emptyTimes {
67				// CPU not present
68				continue
69			}
70			ret = append(ret, *timeStat(fmt.Sprintf("cpu%d", len(ret)), times))
71		}
72		return ret, nil
73	}
74
75	buf, err := unix.SysctlRaw("kern.cp_time")
76	if err != nil {
77		return nil, err
78	}
79
80	times := (*cpuTimes)(unsafe.Pointer(&buf[0]))
81	return []TimesStat{*timeStat("cpu-total", times)}, nil
82}
83
84// Returns only one InfoStat on DragonflyBSD.  The information regarding core
85// count, however is accurate and it is assumed that all InfoStat attributes
86// are the same across CPUs.
87func Info() ([]InfoStat, error) {
88	return InfoWithContext(context.Background())
89}
90
91func InfoWithContext(ctx context.Context) ([]InfoStat, error) {
92	const dmesgBoot = "/var/run/dmesg.boot"
93
94	c, err := parseDmesgBoot(dmesgBoot)
95	if err != nil {
96		return nil, err
97	}
98
99	var u32 uint32
100	if u32, err = unix.SysctlUint32("hw.clockrate"); err != nil {
101		return nil, err
102	}
103	c.Mhz = float64(u32)
104
105	var num int
106	var buf string
107	if buf, err = unix.Sysctl("hw.cpu_topology.tree"); err != nil {
108		return nil, err
109	}
110	num = strings.Count(buf, "CHIP")
111	c.Cores = int32(strings.Count(string(buf), "CORE") / num)
112
113	if c.ModelName, err = unix.Sysctl("hw.model"); err != nil {
114		return nil, err
115	}
116
117	ret := make([]InfoStat, num)
118	for i := 0; i < num; i++ {
119		ret[i] = c
120	}
121
122	return ret, nil
123}
124
125func parseDmesgBoot(fileName string) (InfoStat, error) {
126	c := InfoStat{}
127	lines, _ := common.ReadLines(fileName)
128	for _, line := range lines {
129		if matches := cpuEnd.FindStringSubmatch(line); matches != nil {
130			break
131		} else if matches := originMatch.FindStringSubmatch(line); matches != nil {
132			c.VendorID = matches[1]
133			t, err := strconv.ParseInt(matches[2], 10, 32)
134			if err != nil {
135				return c, fmt.Errorf("unable to parse DragonflyBSD CPU stepping information from %q: %v", line, err)
136			}
137			c.Stepping = int32(t)
138		} else if matches := featuresMatch.FindStringSubmatch(line); matches != nil {
139			for _, v := range strings.Split(matches[1], ",") {
140				c.Flags = append(c.Flags, strings.ToLower(v))
141			}
142		} else if matches := featuresMatch2.FindStringSubmatch(line); matches != nil {
143			for _, v := range strings.Split(matches[1], ",") {
144				c.Flags = append(c.Flags, strings.ToLower(v))
145			}
146		}
147	}
148
149	return c, nil
150}
151
152func CountsWithContext(ctx context.Context, logical bool) (int, error) {
153	return runtime.NumCPU(), nil
154}
155