File: cpu_freebsd.go

package info (click to toggle)
golang-github-shirou-gopsutil 4.25.2-1
  • links: PTS, VCS
  • area: main
  • in suites: experimental, forky, sid, trixie
  • size: 1,824 kB
  • sloc: makefile: 76; ansic: 19; sh: 11
file content (170 lines) | stat: -rw-r--r-- 4,633 bytes parent folder | download | duplicates (2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
// SPDX-License-Identifier: BSD-3-Clause
package cpu

import (
	"context"
	"fmt"
	"reflect"
	"regexp"
	"runtime"
	"strconv"
	"strings"
	"unsafe"

	"github.com/tklauser/go-sysconf"
	"golang.org/x/sys/unix"

	"github.com/shirou/gopsutil/v4/internal/common"
)

var (
	ClocksPerSec   = float64(128)
	cpuMatch       = regexp.MustCompile(`^CPU:`)
	originMatch    = regexp.MustCompile(`Origin\s*=\s*"(.+)"\s+Id\s*=\s*(.+)\s+Family\s*=\s*(.+)\s+Model\s*=\s*(.+)\s+Stepping\s*=\s*(.+)`)
	featuresMatch  = regexp.MustCompile(`Features=.+<(.+)>`)
	featuresMatch2 = regexp.MustCompile(`Features2=[a-f\dx]+<(.+)>`)
	cpuEnd         = regexp.MustCompile(`^Trying to mount root`)
	cpuCores       = regexp.MustCompile(`FreeBSD/SMP: (\d*) package\(s\) x (\d*) core\(s\)`)
	cpuTimesSize   int
	emptyTimes     cpuTimes
)

func init() {
	clkTck, err := sysconf.Sysconf(sysconf.SC_CLK_TCK)
	// ignore errors
	if err == nil {
		ClocksPerSec = float64(clkTck)
	}
}

func timeStat(name string, t *cpuTimes) *TimesStat {
	return &TimesStat{
		User:   float64(t.User) / ClocksPerSec,
		Nice:   float64(t.Nice) / ClocksPerSec,
		System: float64(t.Sys) / ClocksPerSec,
		Idle:   float64(t.Idle) / ClocksPerSec,
		Irq:    float64(t.Intr) / ClocksPerSec,
		CPU:    name,
	}
}

func Times(percpu bool) ([]TimesStat, error) {
	return TimesWithContext(context.Background(), percpu)
}

func TimesWithContext(ctx context.Context, percpu bool) ([]TimesStat, error) {
	if percpu {
		buf, err := unix.SysctlRaw("kern.cp_times")
		if err != nil {
			return nil, err
		}

		// We can't do this in init due to the conflict with cpu.init()
		if cpuTimesSize == 0 {
			cpuTimesSize = int(reflect.TypeOf(cpuTimes{}).Size())
		}

		ncpus := len(buf) / cpuTimesSize
		ret := make([]TimesStat, 0, ncpus)
		for i := 0; i < ncpus; i++ {
			times := (*cpuTimes)(unsafe.Pointer(&buf[i*cpuTimesSize]))
			if *times == emptyTimes {
				// CPU not present
				continue
			}
			ret = append(ret, *timeStat(fmt.Sprintf("cpu%d", len(ret)), times))
		}
		return ret, nil
	}

	buf, err := unix.SysctlRaw("kern.cp_time")
	if err != nil {
		return nil, err
	}

	times := (*cpuTimes)(unsafe.Pointer(&buf[0]))
	return []TimesStat{*timeStat("cpu-total", times)}, nil
}

// Returns only one InfoStat on FreeBSD.  The information regarding core
// count, however is accurate and it is assumed that all InfoStat attributes
// are the same across CPUs.
func Info() ([]InfoStat, error) {
	return InfoWithContext(context.Background())
}

func InfoWithContext(ctx context.Context) ([]InfoStat, error) {
	const dmesgBoot = "/var/run/dmesg.boot"

	c, num, err := parseDmesgBoot(dmesgBoot)
	if err != nil {
		return nil, err
	}

	var u32 uint32
	if u32, err = unix.SysctlUint32("hw.clockrate"); err != nil {
		return nil, err
	}
	c.Mhz = float64(u32)

	if u32, err = unix.SysctlUint32("hw.ncpu"); err != nil {
		return nil, err
	}
	c.Cores = int32(u32)

	if c.ModelName, err = unix.Sysctl("hw.model"); err != nil {
		return nil, err
	}

	ret := make([]InfoStat, num)
	for i := 0; i < num; i++ {
		ret[i] = c
	}

	return ret, nil
}

func parseDmesgBoot(fileName string) (InfoStat, int, error) {
	c := InfoStat{}
	lines, _ := common.ReadLines(fileName)
	cpuNum := 1 // default cpu num is 1
	for _, line := range lines {
		if matches := cpuEnd.FindStringSubmatch(line); matches != nil {
			break
		} else if matches := originMatch.FindStringSubmatch(line); matches != nil {
			c.VendorID = matches[1]
			c.Family = matches[3]
			c.Model = matches[4]
			t, err := strconv.ParseInt(matches[5], 10, 32)
			if err != nil {
				return c, 0, fmt.Errorf("unable to parse FreeBSD CPU stepping information from %q: %w", line, err)
			}
			c.Stepping = int32(t)
		} else if matches := featuresMatch.FindStringSubmatch(line); matches != nil {
			for _, v := range strings.Split(matches[1], ",") {
				c.Flags = append(c.Flags, strings.ToLower(v))
			}
		} else if matches := featuresMatch2.FindStringSubmatch(line); matches != nil {
			for _, v := range strings.Split(matches[1], ",") {
				c.Flags = append(c.Flags, strings.ToLower(v))
			}
		} else if matches := cpuCores.FindStringSubmatch(line); matches != nil {
			t, err := strconv.ParseInt(matches[1], 10, 32)
			if err != nil {
				return c, 0, fmt.Errorf("unable to parse FreeBSD CPU Nums from %q: %w", line, err)
			}
			cpuNum = int(t)
			t2, err := strconv.ParseInt(matches[2], 10, 32)
			if err != nil {
				return c, 0, fmt.Errorf("unable to parse FreeBSD CPU cores from %q: %w", line, err)
			}
			c.Cores = int32(t2)
		}
	}

	return c, cpuNum, nil
}

func CountsWithContext(ctx context.Context, logical bool) (int, error) {
	return runtime.NumCPU(), nil
}