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