Initial QSfera import
This commit is contained in:
+202
@@ -0,0 +1,202 @@
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
package cpu
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"math"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/shirou/gopsutil/v4/internal/common"
|
||||
)
|
||||
|
||||
// TimesStat contains the amounts of time the CPU has spent performing different
|
||||
// kinds of work. Time units are in seconds. It is based on linux /proc/stat file.
|
||||
type TimesStat struct {
|
||||
CPU string `json:"cpu"`
|
||||
User float64 `json:"user"`
|
||||
System float64 `json:"system"`
|
||||
Idle float64 `json:"idle"`
|
||||
Nice float64 `json:"nice"`
|
||||
Iowait float64 `json:"iowait"`
|
||||
Irq float64 `json:"irq"`
|
||||
Softirq float64 `json:"softirq"`
|
||||
Steal float64 `json:"steal"`
|
||||
Guest float64 `json:"guest"`
|
||||
GuestNice float64 `json:"guestNice"`
|
||||
}
|
||||
|
||||
type InfoStat struct {
|
||||
CPU int32 `json:"cpu"`
|
||||
VendorID string `json:"vendorId"`
|
||||
Family string `json:"family"`
|
||||
Model string `json:"model"`
|
||||
Stepping int32 `json:"stepping"`
|
||||
PhysicalID string `json:"physicalId"`
|
||||
CoreID string `json:"coreId"`
|
||||
Cores int32 `json:"cores"`
|
||||
ModelName string `json:"modelName"`
|
||||
Mhz float64 `json:"mhz"`
|
||||
CacheSize int32 `json:"cacheSize"`
|
||||
Flags []string `json:"flags"`
|
||||
Microcode string `json:"microcode"`
|
||||
}
|
||||
|
||||
type lastPercent struct {
|
||||
sync.Mutex
|
||||
lastCPUTimes []TimesStat
|
||||
lastPerCPUTimes []TimesStat
|
||||
}
|
||||
|
||||
var (
|
||||
lastCPUPercent lastPercent
|
||||
invoke common.Invoker = common.Invoke{}
|
||||
)
|
||||
|
||||
func init() {
|
||||
lastCPUPercent.Lock()
|
||||
lastCPUPercent.lastCPUTimes, _ = Times(false)
|
||||
lastCPUPercent.lastPerCPUTimes, _ = Times(true)
|
||||
lastCPUPercent.Unlock()
|
||||
}
|
||||
|
||||
// Counts returns the number of physical or logical cores in the system
|
||||
func Counts(logical bool) (int, error) {
|
||||
return CountsWithContext(context.Background(), logical)
|
||||
}
|
||||
|
||||
func (c TimesStat) String() string {
|
||||
v := []string{
|
||||
`"cpu":"` + c.CPU + `"`,
|
||||
`"user":` + strconv.FormatFloat(c.User, 'f', 1, 64),
|
||||
`"system":` + strconv.FormatFloat(c.System, 'f', 1, 64),
|
||||
`"idle":` + strconv.FormatFloat(c.Idle, 'f', 1, 64),
|
||||
`"nice":` + strconv.FormatFloat(c.Nice, 'f', 1, 64),
|
||||
`"iowait":` + strconv.FormatFloat(c.Iowait, 'f', 1, 64),
|
||||
`"irq":` + strconv.FormatFloat(c.Irq, 'f', 1, 64),
|
||||
`"softirq":` + strconv.FormatFloat(c.Softirq, 'f', 1, 64),
|
||||
`"steal":` + strconv.FormatFloat(c.Steal, 'f', 1, 64),
|
||||
`"guest":` + strconv.FormatFloat(c.Guest, 'f', 1, 64),
|
||||
`"guestNice":` + strconv.FormatFloat(c.GuestNice, 'f', 1, 64),
|
||||
}
|
||||
|
||||
return `{` + strings.Join(v, ",") + `}`
|
||||
}
|
||||
|
||||
// Deprecated: Total returns the total number of seconds in a CPUTimesStat
|
||||
// Please do not use this internal function.
|
||||
func (c TimesStat) Total() float64 {
|
||||
total := c.User + c.System + c.Idle + c.Nice + c.Iowait + c.Irq +
|
||||
c.Softirq + c.Steal + c.Guest + c.GuestNice
|
||||
|
||||
return total
|
||||
}
|
||||
|
||||
func (c InfoStat) String() string {
|
||||
s, _ := json.Marshal(c)
|
||||
return string(s)
|
||||
}
|
||||
|
||||
func getAllBusy(t TimesStat) (float64, float64) {
|
||||
tot := t.Total()
|
||||
if runtime.GOOS == "linux" {
|
||||
tot -= t.Guest // Linux 2.6.24+
|
||||
tot -= t.GuestNice // Linux 3.2.0+
|
||||
}
|
||||
|
||||
busy := tot - t.Idle - t.Iowait
|
||||
|
||||
return tot, busy
|
||||
}
|
||||
|
||||
func calculateBusy(t1, t2 TimesStat) float64 {
|
||||
t1All, t1Busy := getAllBusy(t1)
|
||||
t2All, t2Busy := getAllBusy(t2)
|
||||
|
||||
if t2Busy <= t1Busy {
|
||||
return 0
|
||||
}
|
||||
if t2All <= t1All {
|
||||
return 100
|
||||
}
|
||||
return math.Min(100, math.Max(0, (t2Busy-t1Busy)/(t2All-t1All)*100))
|
||||
}
|
||||
|
||||
func calculateAllBusy(t1, t2 []TimesStat) ([]float64, error) {
|
||||
// Make sure the CPU measurements have the same length.
|
||||
if len(t1) != len(t2) {
|
||||
return nil, fmt.Errorf(
|
||||
"received two CPU counts: %d != %d",
|
||||
len(t1), len(t2),
|
||||
)
|
||||
}
|
||||
|
||||
ret := make([]float64, len(t1))
|
||||
for i, t := range t2 {
|
||||
ret[i] = calculateBusy(t1[i], t)
|
||||
}
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
// Percent calculates the percentage of cpu used either per CPU or combined.
|
||||
// If an interval of 0 is given it will compare the current cpu times against the last call.
|
||||
// Returns one value per cpu, or a single value if percpu is set to false.
|
||||
func Percent(interval time.Duration, percpu bool) ([]float64, error) {
|
||||
return PercentWithContext(context.Background(), interval, percpu)
|
||||
}
|
||||
|
||||
func PercentWithContext(ctx context.Context, interval time.Duration, percpu bool) ([]float64, error) {
|
||||
if interval <= 0 {
|
||||
return percentUsedFromLastCallWithContext(ctx, percpu)
|
||||
}
|
||||
|
||||
// Get CPU usage at the start of the interval.
|
||||
cpuTimes1, err := TimesWithContext(ctx, percpu)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := common.Sleep(ctx, interval); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// And at the end of the interval.
|
||||
cpuTimes2, err := TimesWithContext(ctx, percpu)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return calculateAllBusy(cpuTimes1, cpuTimes2)
|
||||
}
|
||||
|
||||
func percentUsedFromLastCall(percpu bool) ([]float64, error) {
|
||||
return percentUsedFromLastCallWithContext(context.Background(), percpu)
|
||||
}
|
||||
|
||||
func percentUsedFromLastCallWithContext(ctx context.Context, percpu bool) ([]float64, error) {
|
||||
cpuTimes, err := TimesWithContext(ctx, percpu)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
lastCPUPercent.Lock()
|
||||
defer lastCPUPercent.Unlock()
|
||||
var lastTimes []TimesStat
|
||||
if percpu {
|
||||
lastTimes = lastCPUPercent.lastPerCPUTimes
|
||||
lastCPUPercent.lastPerCPUTimes = cpuTimes
|
||||
} else {
|
||||
lastTimes = lastCPUPercent.lastCPUTimes
|
||||
lastCPUPercent.lastCPUTimes = cpuTimes
|
||||
}
|
||||
|
||||
if lastTimes == nil {
|
||||
return nil, errors.New("error getting times for cpu percent. lastTimes was nil")
|
||||
}
|
||||
return calculateAllBusy(lastTimes, cpuTimes)
|
||||
}
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
//go:build aix
|
||||
|
||||
package cpu
|
||||
|
||||
import (
|
||||
"context"
|
||||
)
|
||||
|
||||
func Times(percpu bool) ([]TimesStat, error) {
|
||||
return TimesWithContext(context.Background(), percpu)
|
||||
}
|
||||
|
||||
func Info() ([]InfoStat, error) {
|
||||
return InfoWithContext(context.Background())
|
||||
}
|
||||
+79
@@ -0,0 +1,79 @@
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
//go:build aix && cgo
|
||||
|
||||
package cpu
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/power-devops/perfstat"
|
||||
)
|
||||
|
||||
func TimesWithContext(ctx context.Context, percpu bool) ([]TimesStat, error) {
|
||||
var ret []TimesStat
|
||||
if percpu {
|
||||
cpus, err := perfstat.CpuStat()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, c := range cpus {
|
||||
ct := &TimesStat{
|
||||
CPU: c.Name,
|
||||
Idle: float64(c.Idle),
|
||||
User: float64(c.User),
|
||||
System: float64(c.Sys),
|
||||
Iowait: float64(c.Wait),
|
||||
}
|
||||
ret = append(ret, *ct)
|
||||
}
|
||||
} else {
|
||||
c, err := perfstat.CpuTotalStat()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ct := &TimesStat{
|
||||
CPU: "cpu-total",
|
||||
Idle: float64(c.Idle),
|
||||
User: float64(c.User),
|
||||
System: float64(c.Sys),
|
||||
Iowait: float64(c.Wait),
|
||||
}
|
||||
ret = append(ret, *ct)
|
||||
}
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
func InfoWithContext(ctx context.Context) ([]InfoStat, error) {
|
||||
c, err := perfstat.CpuTotalStat()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
p, err := perfstat.LparInfo()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
info := InfoStat{
|
||||
CPU: 0,
|
||||
ModelName: c.Description,
|
||||
Mhz: float64(c.ProcessorHz / 1000000),
|
||||
Cores: int32(p.OnlineVCpus),
|
||||
}
|
||||
result := []InfoStat{info}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func CountsWithContext(ctx context.Context, logical bool) (int, error) {
|
||||
if logical {
|
||||
c, err := perfstat.CpuTotalStat()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return c.NCpusCfg, nil
|
||||
}
|
||||
// For physical count, use the number of online virtual CPUs (before SMT multiplications).
|
||||
p, err := perfstat.LparInfo()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return int(p.OnlineVCpus), nil
|
||||
}
|
||||
+157
@@ -0,0 +1,157 @@
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
//go:build aix && !cgo
|
||||
|
||||
package cpu
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/shirou/gopsutil/v4/internal/common"
|
||||
)
|
||||
|
||||
func TimesWithContext(ctx context.Context, percpu bool) ([]TimesStat, error) {
|
||||
var ret []TimesStat
|
||||
if percpu {
|
||||
perOut, err := invoke.CommandWithContext(ctx, "sar", "-u", "-P", "ALL", "10", "1")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
lines := strings.Split(string(perOut), "\n")
|
||||
if len(lines) < 6 {
|
||||
return []TimesStat{}, common.ErrNotImplementedError
|
||||
}
|
||||
|
||||
hp := strings.Fields(lines[5]) // headers
|
||||
for l := 6; l < len(lines)-1; l++ {
|
||||
ct := &TimesStat{}
|
||||
v := strings.Fields(lines[l]) // values
|
||||
for i, header := range hp {
|
||||
// We're done in any of these use cases
|
||||
if i >= len(v) || v[0] == "-" {
|
||||
break
|
||||
}
|
||||
|
||||
// Position variable for v
|
||||
pos := i
|
||||
// There is a missing field at the beginning of all but the first line
|
||||
// so adjust the position
|
||||
if l > 6 {
|
||||
pos = i - 1
|
||||
}
|
||||
// We don't want invalid positions
|
||||
if pos < 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
if t, err := strconv.ParseFloat(v[pos], 64); err == nil {
|
||||
switch header {
|
||||
case `cpu`:
|
||||
ct.CPU = strconv.FormatFloat(t, 'f', -1, 64)
|
||||
case `%usr`:
|
||||
ct.User = t
|
||||
case `%sys`:
|
||||
ct.System = t
|
||||
case `%wio`:
|
||||
ct.Iowait = t
|
||||
case `%idle`:
|
||||
ct.Idle = t
|
||||
}
|
||||
}
|
||||
}
|
||||
// Valid CPU data, so append it
|
||||
ret = append(ret, *ct)
|
||||
}
|
||||
} else {
|
||||
out, err := invoke.CommandWithContext(ctx, "sar", "-u", "10", "1")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
lines := strings.Split(string(out), "\n")
|
||||
if len(lines) < 5 {
|
||||
return []TimesStat{}, common.ErrNotImplementedError
|
||||
}
|
||||
|
||||
ct := &TimesStat{CPU: "cpu-total"}
|
||||
h := strings.Fields(lines[len(lines)-3]) // headers
|
||||
v := strings.Fields(lines[len(lines)-2]) // values
|
||||
for i, header := range h {
|
||||
if t, err := strconv.ParseFloat(v[i], 64); err == nil {
|
||||
switch header {
|
||||
case `%usr`:
|
||||
ct.User = t
|
||||
case `%sys`:
|
||||
ct.System = t
|
||||
case `%wio`:
|
||||
ct.Iowait = t
|
||||
case `%idle`:
|
||||
ct.Idle = t
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ret = append(ret, *ct)
|
||||
}
|
||||
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
func InfoWithContext(ctx context.Context) ([]InfoStat, error) {
|
||||
out, err := invoke.CommandWithContext(ctx, "prtconf")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
ret := InfoStat{}
|
||||
for _, line := range strings.Split(string(out), "\n") {
|
||||
switch {
|
||||
case strings.HasPrefix(line, "Number Of Processors:"):
|
||||
p := strings.Fields(line)
|
||||
if len(p) > 3 {
|
||||
if t, err := strconv.ParseUint(p[3], 10, 64); err == nil {
|
||||
ret.Cores = int32(t)
|
||||
}
|
||||
}
|
||||
case strings.HasPrefix(line, "Processor Clock Speed:"):
|
||||
p := strings.Fields(line)
|
||||
if len(p) > 4 {
|
||||
if t, err := strconv.ParseFloat(p[3], 64); err == nil {
|
||||
switch strings.ToUpper(p[4]) {
|
||||
case "MHZ":
|
||||
ret.Mhz = t
|
||||
case "GHZ":
|
||||
ret.Mhz = t * 1000.0
|
||||
case "KHZ":
|
||||
ret.Mhz = t / 1000.0
|
||||
default:
|
||||
ret.Mhz = t
|
||||
}
|
||||
}
|
||||
}
|
||||
case strings.HasPrefix(line, "System Model:"):
|
||||
p := strings.Split(string(line), ":")
|
||||
if p != nil {
|
||||
ret.VendorID = strings.TrimSpace(p[1])
|
||||
}
|
||||
case strings.HasPrefix(line, "Processor Type:"):
|
||||
p := strings.Split(string(line), ":")
|
||||
if p != nil {
|
||||
c := strings.Split(string(p[1]), "_")
|
||||
if c != nil {
|
||||
ret.Family = strings.TrimSpace(c[0])
|
||||
ret.Model = strings.TrimSpace(c[1])
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return []InfoStat{ret}, nil
|
||||
}
|
||||
|
||||
func CountsWithContext(ctx context.Context, _ bool) (int, error) {
|
||||
info, err := InfoWithContext(ctx)
|
||||
if err == nil {
|
||||
return int(info[0].Cores), nil
|
||||
}
|
||||
return 0, err
|
||||
}
|
||||
+195
@@ -0,0 +1,195 @@
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
//go:build darwin
|
||||
|
||||
package cpu
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
"unsafe"
|
||||
|
||||
"github.com/tklauser/go-sysconf"
|
||||
"golang.org/x/sys/unix"
|
||||
|
||||
"github.com/shirou/gopsutil/v4/internal/common"
|
||||
)
|
||||
|
||||
// sys/resource.h
|
||||
const (
|
||||
CPUser = 0
|
||||
cpNice = 1
|
||||
cpSys = 2
|
||||
cpIntr = 3
|
||||
cpIdle = 4
|
||||
cpUStates = 5
|
||||
)
|
||||
|
||||
// mach/machine.h
|
||||
const (
|
||||
cpuStateUser = 0
|
||||
cpuStateSystem = 1
|
||||
cpuStateIdle = 2
|
||||
cpuStateNice = 3
|
||||
cpuStateMax = 4
|
||||
)
|
||||
|
||||
// mach/processor_info.h
|
||||
const (
|
||||
processorCpuLoadInfo = 2 //nolint:revive //FIXME
|
||||
)
|
||||
|
||||
type hostCpuLoadInfoData struct { //nolint:revive //FIXME
|
||||
cpuTicks [cpuStateMax]uint32
|
||||
}
|
||||
|
||||
// default value. from time.h
|
||||
var ClocksPerSec = float64(128)
|
||||
|
||||
func init() {
|
||||
clkTck, err := sysconf.Sysconf(sysconf.SC_CLK_TCK)
|
||||
// ignore errors
|
||||
if err == nil {
|
||||
ClocksPerSec = float64(clkTck)
|
||||
}
|
||||
}
|
||||
|
||||
func Times(percpu bool) ([]TimesStat, error) {
|
||||
return TimesWithContext(context.Background(), percpu)
|
||||
}
|
||||
|
||||
func TimesWithContext(_ context.Context, percpu bool) ([]TimesStat, error) {
|
||||
sys, err := common.NewSystemLib()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer sys.Close()
|
||||
|
||||
if percpu {
|
||||
return perCPUTimes(sys)
|
||||
}
|
||||
|
||||
return allCPUTimes(sys)
|
||||
}
|
||||
|
||||
// Returns only one CPUInfoStat on FreeBSD
|
||||
func Info() ([]InfoStat, error) {
|
||||
return InfoWithContext(context.Background())
|
||||
}
|
||||
|
||||
func InfoWithContext(_ context.Context) ([]InfoStat, error) {
|
||||
var ret []InfoStat
|
||||
|
||||
c := InfoStat{}
|
||||
c.ModelName, _ = unix.Sysctl("machdep.cpu.brand_string")
|
||||
family, _ := unix.SysctlUint32("machdep.cpu.family")
|
||||
c.Family = strconv.FormatUint(uint64(family), 10)
|
||||
model, _ := unix.SysctlUint32("machdep.cpu.model")
|
||||
c.Model = strconv.FormatUint(uint64(model), 10)
|
||||
stepping, _ := unix.SysctlUint32("machdep.cpu.stepping")
|
||||
c.Stepping = int32(stepping)
|
||||
features, err := unix.Sysctl("machdep.cpu.features")
|
||||
if err == nil {
|
||||
for _, v := range strings.Fields(features) {
|
||||
c.Flags = append(c.Flags, strings.ToLower(v))
|
||||
}
|
||||
}
|
||||
leaf7Features, err := unix.Sysctl("machdep.cpu.leaf7_features")
|
||||
if err == nil {
|
||||
for _, v := range strings.Fields(leaf7Features) {
|
||||
c.Flags = append(c.Flags, strings.ToLower(v))
|
||||
}
|
||||
}
|
||||
extfeatures, err := unix.Sysctl("machdep.cpu.extfeatures")
|
||||
if err == nil {
|
||||
for _, v := range strings.Fields(extfeatures) {
|
||||
c.Flags = append(c.Flags, strings.ToLower(v))
|
||||
}
|
||||
}
|
||||
cores, _ := unix.SysctlUint32("machdep.cpu.core_count")
|
||||
c.Cores = int32(cores)
|
||||
cacheSize, _ := unix.SysctlUint32("machdep.cpu.cache.size")
|
||||
c.CacheSize = int32(cacheSize)
|
||||
c.VendorID, _ = unix.Sysctl("machdep.cpu.vendor")
|
||||
|
||||
v, err := getFrequency()
|
||||
if err == nil {
|
||||
c.Mhz = v
|
||||
}
|
||||
|
||||
return append(ret, c), nil
|
||||
}
|
||||
|
||||
func CountsWithContext(_ context.Context, logical bool) (int, error) {
|
||||
var cpuArgument string
|
||||
if logical {
|
||||
cpuArgument = "hw.logicalcpu"
|
||||
} else {
|
||||
cpuArgument = "hw.physicalcpu"
|
||||
}
|
||||
|
||||
count, err := unix.SysctlUint32(cpuArgument)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
return int(count), nil
|
||||
}
|
||||
|
||||
func perCPUTimes(sys *common.SystemLib) ([]TimesStat, error) {
|
||||
var count, ncpu uint32
|
||||
var cpuload *hostCpuLoadInfoData
|
||||
|
||||
status := sys.HostProcessorInfo(sys.MachHostSelf(), processorCpuLoadInfo,
|
||||
&ncpu, uintptr(unsafe.Pointer(&cpuload)), &count)
|
||||
|
||||
if status != common.KERN_SUCCESS {
|
||||
return nil, fmt.Errorf("host_processor_info error=%d", status)
|
||||
}
|
||||
|
||||
if cpuload == nil {
|
||||
return nil, errors.New("host_processor_info returned nil cpuload")
|
||||
}
|
||||
|
||||
defer sys.VMDeallocate(sys.MachTaskSelf(), uintptr(unsafe.Pointer(cpuload)), uintptr(ncpu))
|
||||
|
||||
ret := []TimesStat{}
|
||||
loads := unsafe.Slice(cpuload, ncpu)
|
||||
|
||||
for i := 0; i < int(ncpu); i++ {
|
||||
c := TimesStat{
|
||||
CPU: fmt.Sprintf("cpu%d", i),
|
||||
User: float64(loads[i].cpuTicks[cpuStateUser]) / ClocksPerSec,
|
||||
System: float64(loads[i].cpuTicks[cpuStateSystem]) / ClocksPerSec,
|
||||
Nice: float64(loads[i].cpuTicks[cpuStateNice]) / ClocksPerSec,
|
||||
Idle: float64(loads[i].cpuTicks[cpuStateIdle]) / ClocksPerSec,
|
||||
}
|
||||
ret = append(ret, c)
|
||||
}
|
||||
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
func allCPUTimes(sys *common.SystemLib) ([]TimesStat, error) {
|
||||
var cpuload hostCpuLoadInfoData
|
||||
count := uint32(cpuStateMax)
|
||||
|
||||
status := sys.HostStatistics(sys.MachHostSelf(), common.HOST_CPU_LOAD_INFO,
|
||||
uintptr(unsafe.Pointer(&cpuload)), &count)
|
||||
|
||||
if status != common.KERN_SUCCESS {
|
||||
return nil, fmt.Errorf("host_statistics error=%d", status)
|
||||
}
|
||||
|
||||
c := TimesStat{
|
||||
CPU: "cpu-total",
|
||||
User: float64(cpuload.cpuTicks[cpuStateUser]) / ClocksPerSec,
|
||||
System: float64(cpuload.cpuTicks[cpuStateSystem]) / ClocksPerSec,
|
||||
Nice: float64(cpuload.cpuTicks[cpuStateNice]) / ClocksPerSec,
|
||||
Idle: float64(cpuload.cpuTicks[cpuStateIdle]) / ClocksPerSec,
|
||||
}
|
||||
|
||||
return []TimesStat{c}, nil
|
||||
}
|
||||
+83
@@ -0,0 +1,83 @@
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
//go:build darwin && arm64
|
||||
|
||||
package cpu
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"sync"
|
||||
"unsafe"
|
||||
|
||||
"github.com/shirou/gopsutil/v4/internal/common"
|
||||
)
|
||||
|
||||
// Keep IOKit and CoreFoundation libraries open for the process lifetime.
|
||||
// See: https://github.com/shirou/gopsutil/issues/1832
|
||||
var (
|
||||
cpuLibOnce sync.Once
|
||||
cpuIOKit *common.IOKitLib
|
||||
cpuCF *common.CoreFoundationLib
|
||||
cpuLibErr error
|
||||
)
|
||||
|
||||
func initCPULibraries() {
|
||||
cpuIOKit, cpuLibErr = common.NewIOKitLib()
|
||||
if cpuLibErr != nil {
|
||||
return
|
||||
}
|
||||
cpuCF, cpuLibErr = common.NewCoreFoundationLib()
|
||||
}
|
||||
|
||||
// https://github.com/shoenig/go-m1cpu/blob/v0.1.6/cpu.go
|
||||
func getFrequency() (float64, error) {
|
||||
cpuLibOnce.Do(initCPULibraries)
|
||||
if cpuLibErr != nil {
|
||||
return 0, cpuLibErr
|
||||
}
|
||||
|
||||
iokit := cpuIOKit
|
||||
corefoundation := cpuCF
|
||||
|
||||
matching := iokit.IOServiceMatching("AppleARMIODevice")
|
||||
|
||||
var iterator uint32
|
||||
if status := iokit.IOServiceGetMatchingServices(common.KIOMainPortDefault, uintptr(matching), &iterator); status != common.KERN_SUCCESS {
|
||||
return 0.0, fmt.Errorf("IOServiceGetMatchingServices error=%d", status)
|
||||
}
|
||||
defer iokit.IOObjectRelease(iterator)
|
||||
|
||||
pCorekey := corefoundation.CFStringCreateWithCString(common.KCFAllocatorDefault, "voltage-states5-sram", common.KCFStringEncodingUTF8)
|
||||
defer corefoundation.CFRelease(uintptr(pCorekey))
|
||||
|
||||
var pCoreHz uint32
|
||||
for {
|
||||
service := iokit.IOIteratorNext(iterator)
|
||||
if service <= 0 {
|
||||
break
|
||||
}
|
||||
|
||||
buf := common.NewCStr(512)
|
||||
iokit.IORegistryEntryGetName(service, buf)
|
||||
|
||||
if buf.GoString() == "pmgr" {
|
||||
pCoreRef := iokit.IORegistryEntryCreateCFProperty(service, uintptr(pCorekey), common.KCFAllocatorDefault, common.KNilOptions)
|
||||
length := corefoundation.CFDataGetLength(uintptr(pCoreRef))
|
||||
data := corefoundation.CFDataGetBytePtr(uintptr(pCoreRef))
|
||||
|
||||
// composite uint32 from the byte array
|
||||
buf := unsafe.Slice((*byte)(data), length)
|
||||
|
||||
// combine the bytes into a uint32 value
|
||||
b := buf[length-8 : length-4]
|
||||
pCoreHz = binary.LittleEndian.Uint32(b)
|
||||
corefoundation.CFRelease(uintptr(pCoreRef))
|
||||
iokit.IOObjectRelease(service)
|
||||
break
|
||||
}
|
||||
|
||||
iokit.IOObjectRelease(service)
|
||||
}
|
||||
|
||||
return float64(pCoreHz / 1_000_000), nil
|
||||
}
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
//go:build darwin && !arm64
|
||||
|
||||
package cpu
|
||||
|
||||
import "golang.org/x/sys/unix"
|
||||
|
||||
func getFrequency() (float64, error) {
|
||||
// Use the rated frequency of the CPU. This is a static value and does not
|
||||
// account for low power or Turbo Boost modes.
|
||||
cpuFrequency, err := unix.SysctlUint64("hw.cpufrequency")
|
||||
return float64(cpuFrequency) / 1000000.0, err
|
||||
}
|
||||
+162
@@ -0,0 +1,162 @@
|
||||
// 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+Stepping\s*=\s*(.+)`)
|
||||
featuresMatch = regexp.MustCompile(`Features=.+<(.+)>`)
|
||||
featuresMatch2 = regexp.MustCompile(`Features2=[a-f\dx]+<(.+)>`)
|
||||
cpuEnd = regexp.MustCompile(`^Trying to mount root`)
|
||||
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(_ 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 DragonflyBSD. 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(_ context.Context) ([]InfoStat, error) {
|
||||
const dmesgBoot = "/var/run/dmesg.boot"
|
||||
|
||||
c, 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)
|
||||
|
||||
var num int
|
||||
var buf string
|
||||
if buf, err = unix.Sysctl("hw.cpu_topology.tree"); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
num = strings.Count(buf, "CHIP")
|
||||
c.Cores = int32(strings.Count(string(buf), "CORE") / num)
|
||||
|
||||
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, error) {
|
||||
c := InfoStat{}
|
||||
lines, err := common.ReadLines(fileName)
|
||||
if err != nil {
|
||||
return c, fmt.Errorf("could not read %s: %w", fileName, err)
|
||||
}
|
||||
|
||||
for _, line := range lines {
|
||||
if matches := cpuEnd.FindStringSubmatch(line); matches != nil {
|
||||
break
|
||||
} else if matches := originMatch.FindStringSubmatch(line); matches != nil {
|
||||
c.VendorID = matches[1]
|
||||
t, err := strconv.ParseInt(matches[2], 10, 32)
|
||||
if err != nil {
|
||||
return c, fmt.Errorf("unable to parse DragonflyBSD 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))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return c, nil
|
||||
}
|
||||
|
||||
func CountsWithContext(_ context.Context, _ bool) (int, error) {
|
||||
return runtime.NumCPU(), nil
|
||||
}
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
package cpu
|
||||
|
||||
type cpuTimes struct {
|
||||
User uint64
|
||||
Nice uint64
|
||||
Sys uint64
|
||||
Intr uint64
|
||||
Idle uint64
|
||||
}
|
||||
+31
@@ -0,0 +1,31 @@
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
//go:build !darwin && !linux && !freebsd && !openbsd && !netbsd && !solaris && !windows && !dragonfly && !plan9 && !aix
|
||||
|
||||
package cpu
|
||||
|
||||
import (
|
||||
"context"
|
||||
"runtime"
|
||||
|
||||
"github.com/shirou/gopsutil/v4/internal/common"
|
||||
)
|
||||
|
||||
func Times(percpu bool) ([]TimesStat, error) {
|
||||
return TimesWithContext(context.Background(), percpu)
|
||||
}
|
||||
|
||||
func TimesWithContext(ctx context.Context, percpu bool) ([]TimesStat, error) {
|
||||
return []TimesStat{}, common.ErrNotImplementedError
|
||||
}
|
||||
|
||||
func Info() ([]InfoStat, error) {
|
||||
return InfoWithContext(context.Background())
|
||||
}
|
||||
|
||||
func InfoWithContext(ctx context.Context) ([]InfoStat, error) {
|
||||
return []InfoStat{}, common.ErrNotImplementedError
|
||||
}
|
||||
|
||||
func CountsWithContext(ctx context.Context, logical bool) (int, error) {
|
||||
return runtime.NumCPU(), nil
|
||||
}
|
||||
+174
@@ -0,0 +1,174 @@
|
||||
// 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(_ 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(_ 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, err := common.ReadLines(fileName)
|
||||
if err != nil {
|
||||
return c, 0, fmt.Errorf("could not read %s: %w", fileName, err)
|
||||
}
|
||||
|
||||
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(_ context.Context, _ bool) (int, error) {
|
||||
return runtime.NumCPU(), nil
|
||||
}
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
package cpu
|
||||
|
||||
type cpuTimes struct {
|
||||
User uint32
|
||||
Nice uint32
|
||||
Sys uint32
|
||||
Intr uint32
|
||||
Idle uint32
|
||||
}
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
package cpu
|
||||
|
||||
type cpuTimes struct {
|
||||
User uint64
|
||||
Nice uint64
|
||||
Sys uint64
|
||||
Intr uint64
|
||||
Idle uint64
|
||||
}
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
package cpu
|
||||
|
||||
type cpuTimes struct {
|
||||
User uint32
|
||||
Nice uint32
|
||||
Sys uint32
|
||||
Intr uint32
|
||||
Idle uint32
|
||||
}
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
package cpu
|
||||
|
||||
type cpuTimes struct {
|
||||
User uint64
|
||||
Nice uint64
|
||||
Sys uint64
|
||||
Intr uint64
|
||||
Idle uint64
|
||||
}
|
||||
+531
@@ -0,0 +1,531 @@
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
//go:build linux
|
||||
|
||||
package cpu
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/tklauser/go-sysconf"
|
||||
|
||||
"github.com/shirou/gopsutil/v4/internal/common"
|
||||
)
|
||||
|
||||
var ClocksPerSec = float64(100)
|
||||
|
||||
var armModelToModelName = map[uint64]string{
|
||||
0x810: "ARM810",
|
||||
0x920: "ARM920",
|
||||
0x922: "ARM922",
|
||||
0x926: "ARM926",
|
||||
0x940: "ARM940",
|
||||
0x946: "ARM946",
|
||||
0x966: "ARM966",
|
||||
0xa20: "ARM1020",
|
||||
0xa22: "ARM1022",
|
||||
0xa26: "ARM1026",
|
||||
0xb02: "ARM11 MPCore",
|
||||
0xb36: "ARM1136",
|
||||
0xb56: "ARM1156",
|
||||
0xb76: "ARM1176",
|
||||
0xc05: "Cortex-A5",
|
||||
0xc07: "Cortex-A7",
|
||||
0xc08: "Cortex-A8",
|
||||
0xc09: "Cortex-A9",
|
||||
0xc0d: "Cortex-A17",
|
||||
0xc0f: "Cortex-A15",
|
||||
0xc0e: "Cortex-A17",
|
||||
0xc14: "Cortex-R4",
|
||||
0xc15: "Cortex-R5",
|
||||
0xc17: "Cortex-R7",
|
||||
0xc18: "Cortex-R8",
|
||||
0xc20: "Cortex-M0",
|
||||
0xc21: "Cortex-M1",
|
||||
0xc23: "Cortex-M3",
|
||||
0xc24: "Cortex-M4",
|
||||
0xc27: "Cortex-M7",
|
||||
0xc60: "Cortex-M0+",
|
||||
0xd01: "Cortex-A32",
|
||||
0xd02: "Cortex-A34",
|
||||
0xd03: "Cortex-A53",
|
||||
0xd04: "Cortex-A35",
|
||||
0xd05: "Cortex-A55",
|
||||
0xd06: "Cortex-A65",
|
||||
0xd07: "Cortex-A57",
|
||||
0xd08: "Cortex-A72",
|
||||
0xd09: "Cortex-A73",
|
||||
0xd0a: "Cortex-A75",
|
||||
0xd0b: "Cortex-A76",
|
||||
0xd0c: "Neoverse-N1",
|
||||
0xd0d: "Cortex-A77",
|
||||
0xd0e: "Cortex-A76AE",
|
||||
0xd13: "Cortex-R52",
|
||||
0xd20: "Cortex-M23",
|
||||
0xd21: "Cortex-M33",
|
||||
0xd40: "Neoverse-V1",
|
||||
0xd41: "Cortex-A78",
|
||||
0xd42: "Cortex-A78AE",
|
||||
0xd43: "Cortex-A65AE",
|
||||
0xd44: "Cortex-X1",
|
||||
0xd46: "Cortex-A510",
|
||||
0xd47: "Cortex-A710",
|
||||
0xd48: "Cortex-X2",
|
||||
0xd49: "Neoverse-N2",
|
||||
0xd4a: "Neoverse-E1",
|
||||
0xd4b: "Cortex-A78C",
|
||||
0xd4c: "Cortex-X1C",
|
||||
0xd4d: "Cortex-A715",
|
||||
0xd4e: "Cortex-X3",
|
||||
0xd4f: "Neoverse-V2",
|
||||
0xd81: "Cortex-A720",
|
||||
0xd82: "Cortex-X4",
|
||||
0xd84: "Neoverse-V3",
|
||||
0xd85: "Cortex-X925",
|
||||
0xd87: "Cortex-A725",
|
||||
0xd8e: "Neoverse-N3",
|
||||
}
|
||||
|
||||
func init() {
|
||||
clkTck, err := sysconf.Sysconf(sysconf.SC_CLK_TCK)
|
||||
// ignore errors
|
||||
if err == nil {
|
||||
ClocksPerSec = float64(clkTck)
|
||||
}
|
||||
}
|
||||
|
||||
func Times(percpu bool) ([]TimesStat, error) {
|
||||
return TimesWithContext(context.Background(), percpu)
|
||||
}
|
||||
|
||||
func TimesWithContext(ctx context.Context, percpu bool) ([]TimesStat, error) {
|
||||
filename := common.HostProcWithContext(ctx, "stat")
|
||||
lines := []string{}
|
||||
var err error
|
||||
if percpu {
|
||||
statlines, err := common.ReadLines(filename)
|
||||
if err != nil || len(statlines) < 2 {
|
||||
return []TimesStat{}, nil
|
||||
}
|
||||
for _, line := range statlines[1:] {
|
||||
if !strings.HasPrefix(line, "cpu") {
|
||||
break
|
||||
}
|
||||
lines = append(lines, line)
|
||||
}
|
||||
} else {
|
||||
lines, err = common.ReadLinesOffsetN(filename, 0, 1)
|
||||
if err != nil || len(lines) == 0 {
|
||||
return []TimesStat{}, nil
|
||||
}
|
||||
}
|
||||
|
||||
ret := make([]TimesStat, 0, len(lines))
|
||||
|
||||
for _, line := range lines {
|
||||
ct, err := parseStatLine(line)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
ret = append(ret, *ct)
|
||||
|
||||
}
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
func sysCPUPath(ctx context.Context, cpu int32, relPath string) string {
|
||||
return common.HostSysWithContext(ctx, fmt.Sprintf("devices/system/cpu/cpu%d", cpu), relPath)
|
||||
}
|
||||
|
||||
func finishCPUInfo(ctx context.Context, c *InfoStat) {
|
||||
var lines []string
|
||||
var err error
|
||||
var value float64
|
||||
|
||||
if c.CoreID == "" {
|
||||
lines, err = common.ReadLines(sysCPUPath(ctx, c.CPU, "topology/core_id"))
|
||||
if err == nil {
|
||||
c.CoreID = lines[0]
|
||||
}
|
||||
}
|
||||
|
||||
// override the value of c.Mhz with cpufreq/cpuinfo_max_freq regardless
|
||||
// of the value from /proc/cpuinfo because we want to report the maximum
|
||||
// clock-speed of the CPU for c.Mhz, matching the behaviour of Windows
|
||||
lines, err = common.ReadLines(sysCPUPath(ctx, c.CPU, "cpufreq/cpuinfo_max_freq"))
|
||||
// if we encounter errors below such as there are no cpuinfo_max_freq file,
|
||||
// we just ignore. so let Mhz is 0.
|
||||
if err != nil || len(lines) == 0 {
|
||||
return
|
||||
}
|
||||
value, err = strconv.ParseFloat(lines[0], 64)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
c.Mhz = value / 1000.0 // value is in kHz
|
||||
if c.Mhz > 9999 {
|
||||
c.Mhz /= 1000.0 // value in Hz
|
||||
}
|
||||
}
|
||||
|
||||
// CPUInfo on linux will return 1 item per physical thread.
|
||||
//
|
||||
// CPUs have three levels of counting: sockets, cores, threads.
|
||||
// Cores with HyperThreading count as having 2 threads per core.
|
||||
// Sockets often come with many physical CPU cores.
|
||||
// For example a single socket board with two cores each with HT will
|
||||
// return 4 CPUInfoStat structs on Linux and the "Cores" field set to 1.
|
||||
func Info() ([]InfoStat, error) {
|
||||
return InfoWithContext(context.Background())
|
||||
}
|
||||
|
||||
func InfoWithContext(ctx context.Context) ([]InfoStat, error) {
|
||||
filename := common.HostProcWithContext(ctx, "cpuinfo")
|
||||
lines, err := common.ReadLines(filename)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not read %s: %w", filename, err)
|
||||
}
|
||||
|
||||
var ret []InfoStat
|
||||
var processorName string
|
||||
|
||||
c := InfoStat{CPU: -1, Cores: 1}
|
||||
for _, line := range lines {
|
||||
fields := strings.SplitN(line, ":", 2)
|
||||
if len(fields) < 2 {
|
||||
continue
|
||||
}
|
||||
key := strings.TrimSpace(fields[0])
|
||||
value := strings.TrimSpace(fields[1])
|
||||
|
||||
switch key {
|
||||
case "Processor":
|
||||
processorName = value
|
||||
case "processor", "cpu number":
|
||||
if c.CPU >= 0 {
|
||||
finishCPUInfo(ctx, &c)
|
||||
ret = append(ret, c)
|
||||
}
|
||||
c = InfoStat{Cores: 1, ModelName: processorName}
|
||||
t, err := strconv.ParseInt(value, 10, 64)
|
||||
if err != nil {
|
||||
return ret, err
|
||||
}
|
||||
c.CPU = int32(t)
|
||||
case "vendorId", "vendor_id":
|
||||
c.VendorID = value
|
||||
if strings.Contains(value, "S390") {
|
||||
processorName = "S390"
|
||||
}
|
||||
case "mvendorid":
|
||||
if !strings.HasPrefix(value, "0x") {
|
||||
continue
|
||||
}
|
||||
|
||||
if v, err := strconv.ParseUint(value[2:], 16, 32); err == nil {
|
||||
switch v {
|
||||
case 0x31e:
|
||||
c.VendorID = "Andes"
|
||||
case 0x029:
|
||||
c.VendorID = "Microchip"
|
||||
case 0x127:
|
||||
c.VendorID = "MIPS"
|
||||
case 0x489:
|
||||
c.VendorID = "SiFive"
|
||||
case 0x5b7:
|
||||
c.VendorID = "T-Head"
|
||||
}
|
||||
}
|
||||
case "CPU implementer":
|
||||
if v, err := strconv.ParseUint(value, 0, 8); err == nil {
|
||||
switch v {
|
||||
case 0x41:
|
||||
c.VendorID = "ARM"
|
||||
case 0x42:
|
||||
c.VendorID = "Broadcom"
|
||||
case 0x43:
|
||||
c.VendorID = "Cavium"
|
||||
case 0x44:
|
||||
c.VendorID = "DEC"
|
||||
case 0x46:
|
||||
c.VendorID = "Fujitsu"
|
||||
case 0x48:
|
||||
c.VendorID = "HiSilicon"
|
||||
case 0x49:
|
||||
c.VendorID = "Infineon"
|
||||
case 0x4d:
|
||||
c.VendorID = "Motorola/Freescale"
|
||||
case 0x4e:
|
||||
c.VendorID = "NVIDIA"
|
||||
case 0x50:
|
||||
c.VendorID = "APM"
|
||||
case 0x51:
|
||||
c.VendorID = "Qualcomm"
|
||||
case 0x56:
|
||||
c.VendorID = "Marvell"
|
||||
case 0x61:
|
||||
c.VendorID = "Apple"
|
||||
case 0x69:
|
||||
c.VendorID = "Intel"
|
||||
case 0xc0:
|
||||
c.VendorID = "Ampere"
|
||||
}
|
||||
}
|
||||
case "cpu family", "marchid":
|
||||
c.Family = value
|
||||
case "model", "CPU part", "mimpid":
|
||||
c.Model = value
|
||||
// if CPU is arm based, model name is found via model number. refer to: arch/arm64/kernel/cpuinfo.c
|
||||
if c.VendorID == "ARM" {
|
||||
if v, err := strconv.ParseUint(c.Model, 0, 16); err == nil {
|
||||
modelName, exist := armModelToModelName[v]
|
||||
if exist {
|
||||
c.ModelName = modelName
|
||||
} else {
|
||||
c.ModelName = "Undefined"
|
||||
}
|
||||
}
|
||||
}
|
||||
case "Model Name", "model name", "cpu", "uarch":
|
||||
c.ModelName = value
|
||||
if strings.Contains(value, "POWER") {
|
||||
c.Model = strings.Split(value, " ")[0]
|
||||
c.Family = "POWER"
|
||||
c.VendorID = "IBM"
|
||||
}
|
||||
case "stepping", "revision", "CPU revision":
|
||||
val := value
|
||||
|
||||
if key == "revision" {
|
||||
val = strings.Split(value, ".")[0]
|
||||
}
|
||||
|
||||
if strings.EqualFold(val, "unknown") {
|
||||
continue
|
||||
}
|
||||
|
||||
t, err := strconv.ParseInt(val, 10, 64)
|
||||
if err != nil {
|
||||
return ret, err
|
||||
}
|
||||
c.Stepping = int32(t)
|
||||
case "cpu MHz", "clock", "cpu MHz dynamic":
|
||||
// treat this as the fallback value, thus we ignore error
|
||||
if t, err := strconv.ParseFloat(strings.Replace(value, "MHz", "", 1), 64); err == nil {
|
||||
c.Mhz = t
|
||||
}
|
||||
case "cache size":
|
||||
t, err := strconv.ParseInt(strings.Replace(value, " KB", "", 1), 10, 64)
|
||||
if err != nil {
|
||||
return ret, err
|
||||
}
|
||||
c.CacheSize = int32(t)
|
||||
case "physical id", "hart":
|
||||
c.PhysicalID = value
|
||||
case "core id":
|
||||
c.CoreID = value
|
||||
case "flags", "Features":
|
||||
c.Flags = strings.FieldsFunc(value, func(r rune) bool {
|
||||
return r == ',' || r == ' '
|
||||
})
|
||||
case "isa", "hart isa":
|
||||
if len(c.Flags) != 0 || !strings.HasPrefix(value, "rv64") {
|
||||
continue
|
||||
}
|
||||
c.Flags = riscvISAParse(value)
|
||||
case "microcode":
|
||||
c.Microcode = value
|
||||
}
|
||||
}
|
||||
if c.CPU >= 0 {
|
||||
finishCPUInfo(ctx, &c)
|
||||
ret = append(ret, c)
|
||||
}
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
func parseStatLine(line string) (*TimesStat, error) {
|
||||
fields := strings.Fields(line)
|
||||
|
||||
if len(fields) < 8 {
|
||||
return nil, errors.New("stat does not contain cpu info")
|
||||
}
|
||||
|
||||
if !strings.HasPrefix(fields[0], "cpu") {
|
||||
return nil, errors.New("not contain cpu")
|
||||
}
|
||||
|
||||
cpu := fields[0]
|
||||
if cpu == "cpu" {
|
||||
cpu = "cpu-total"
|
||||
}
|
||||
user, err := strconv.ParseFloat(fields[1], 64)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
nice, err := strconv.ParseFloat(fields[2], 64)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
system, err := strconv.ParseFloat(fields[3], 64)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
idle, err := strconv.ParseFloat(fields[4], 64)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
iowait, err := strconv.ParseFloat(fields[5], 64)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
irq, err := strconv.ParseFloat(fields[6], 64)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
softirq, err := strconv.ParseFloat(fields[7], 64)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
ct := &TimesStat{
|
||||
CPU: cpu,
|
||||
User: user / ClocksPerSec,
|
||||
Nice: nice / ClocksPerSec,
|
||||
System: system / ClocksPerSec,
|
||||
Idle: idle / ClocksPerSec,
|
||||
Iowait: iowait / ClocksPerSec,
|
||||
Irq: irq / ClocksPerSec,
|
||||
Softirq: softirq / ClocksPerSec,
|
||||
}
|
||||
if len(fields) > 8 { // Linux >= 2.6.11
|
||||
steal, err := strconv.ParseFloat(fields[8], 64)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ct.Steal = steal / ClocksPerSec
|
||||
}
|
||||
if len(fields) > 9 { // Linux >= 2.6.24
|
||||
guest, err := strconv.ParseFloat(fields[9], 64)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ct.Guest = guest / ClocksPerSec
|
||||
}
|
||||
if len(fields) > 10 { // Linux >= 3.2.0
|
||||
guestNice, err := strconv.ParseFloat(fields[10], 64)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ct.GuestNice = guestNice / ClocksPerSec
|
||||
}
|
||||
|
||||
return ct, nil
|
||||
}
|
||||
|
||||
func CountsWithContext(ctx context.Context, logical bool) (int, error) {
|
||||
if logical {
|
||||
ret := 0
|
||||
// https://github.com/giampaolo/psutil/blob/d01a9eaa35a8aadf6c519839e987a49d8be2d891/psutil/_pslinux.py#L599
|
||||
procCpuinfo := common.HostProcWithContext(ctx, "cpuinfo")
|
||||
lines, err := common.ReadLines(procCpuinfo)
|
||||
if err == nil {
|
||||
for _, line := range lines {
|
||||
line = strings.ToLower(line)
|
||||
if strings.HasPrefix(line, "processor") {
|
||||
_, err = strconv.ParseInt(strings.TrimSpace(line[strings.IndexByte(line, ':')+1:]), 10, 32)
|
||||
if err == nil {
|
||||
ret++
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if ret == 0 {
|
||||
procStat := common.HostProcWithContext(ctx, "stat")
|
||||
lines, err = common.ReadLines(procStat)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
for _, line := range lines {
|
||||
if len(line) >= 4 && strings.HasPrefix(line, "cpu") && '0' <= line[3] && line[3] <= '9' { // `^cpu\d` regexp matching
|
||||
ret++
|
||||
}
|
||||
}
|
||||
}
|
||||
return ret, nil
|
||||
}
|
||||
// physical cores
|
||||
// https://github.com/giampaolo/psutil/blob/8415355c8badc9c94418b19bdf26e622f06f0cce/psutil/_pslinux.py#L615-L628
|
||||
threadSiblingsLists := make(map[string]bool)
|
||||
// These 2 files are the same but */core_cpus_list is newer while */thread_siblings_list is deprecated and may disappear in the future.
|
||||
// https://www.kernel.org/doc/Documentation/admin-guide/cputopology.rst
|
||||
// https://github.com/giampaolo/psutil/pull/1727#issuecomment-707624964
|
||||
// https://lkml.org/lkml/2019/2/26/41
|
||||
for _, glob := range []string{"devices/system/cpu/cpu[0-9]*/topology/core_cpus_list", "devices/system/cpu/cpu[0-9]*/topology/thread_siblings_list"} {
|
||||
if files, err := filepath.Glob(common.HostSysWithContext(ctx, glob)); err == nil {
|
||||
for _, file := range files {
|
||||
lines, err := common.ReadLines(file)
|
||||
if err != nil || len(lines) != 1 {
|
||||
continue
|
||||
}
|
||||
threadSiblingsLists[lines[0]] = true
|
||||
}
|
||||
ret := len(threadSiblingsLists)
|
||||
if ret != 0 {
|
||||
return ret, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
// https://github.com/giampaolo/psutil/blob/122174a10b75c9beebe15f6c07dcf3afbe3b120d/psutil/_pslinux.py#L631-L652
|
||||
filename := common.HostProcWithContext(ctx, "cpuinfo")
|
||||
lines, err := common.ReadLines(filename)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
mapping := make(map[int]int)
|
||||
currentInfo := make(map[string]int)
|
||||
for _, line := range lines {
|
||||
line = strings.ToLower(strings.TrimSpace(line))
|
||||
if line == "" {
|
||||
// new section
|
||||
id, okID := currentInfo["physical id"]
|
||||
cores, okCores := currentInfo["cpu cores"]
|
||||
if okID && okCores {
|
||||
mapping[id] = cores
|
||||
}
|
||||
currentInfo = make(map[string]int)
|
||||
continue
|
||||
}
|
||||
fields := strings.SplitN(line, ":", 2)
|
||||
if len(fields) < 2 {
|
||||
continue
|
||||
}
|
||||
fields[0] = strings.TrimSpace(fields[0])
|
||||
if fields[0] == "physical id" || fields[0] == "cpu cores" {
|
||||
val, err := strconv.ParseInt(strings.TrimSpace(fields[1]), 10, 32)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
currentInfo[fields[0]] = int(val)
|
||||
}
|
||||
}
|
||||
ret := 0
|
||||
for _, v := range mapping {
|
||||
ret += v
|
||||
}
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
func riscvISAParse(s string) []string {
|
||||
ext := strings.Split(s, "_")
|
||||
if len(ext[0]) <= 4 {
|
||||
return nil
|
||||
}
|
||||
// the base extensions must "rv64" prefix
|
||||
base := strings.Split(ext[0][4:], "")
|
||||
return append(base, ext[1:]...)
|
||||
}
|
||||
+121
@@ -0,0 +1,121 @@
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
//go:build netbsd
|
||||
|
||||
package cpu
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"runtime"
|
||||
"unsafe"
|
||||
|
||||
"github.com/tklauser/go-sysconf"
|
||||
"golang.org/x/sys/unix"
|
||||
|
||||
"github.com/shirou/gopsutil/v4/internal/common"
|
||||
)
|
||||
|
||||
const (
|
||||
// sys/sysctl.h
|
||||
ctlKern = 1 // "high kernel": proc, limits
|
||||
ctlHw = 6 // CTL_HW
|
||||
kernCpTime = 51 // KERN_CPTIME
|
||||
)
|
||||
|
||||
var ClocksPerSec = float64(100)
|
||||
|
||||
func init() {
|
||||
clkTck, err := sysconf.Sysconf(sysconf.SC_CLK_TCK)
|
||||
// ignore errors
|
||||
if err == nil {
|
||||
ClocksPerSec = float64(clkTck)
|
||||
}
|
||||
}
|
||||
|
||||
func Times(percpu bool) ([]TimesStat, error) {
|
||||
return TimesWithContext(context.Background(), percpu)
|
||||
}
|
||||
|
||||
func TimesWithContext(_ context.Context, percpu bool) ([]TimesStat, error) {
|
||||
ret := make([]TimesStat, 0)
|
||||
if !percpu {
|
||||
mib := []int32{ctlKern, kernCpTime}
|
||||
buf, _, err := common.CallSyscall(mib)
|
||||
if err != nil {
|
||||
return ret, err
|
||||
}
|
||||
times := (*cpuTimes)(unsafe.Pointer(&buf[0]))
|
||||
ret = append(ret, TimesStat{
|
||||
CPU: "cpu-total",
|
||||
User: float64(times.User),
|
||||
Nice: float64(times.Nice),
|
||||
System: float64(times.Sys),
|
||||
Idle: float64(times.Idle),
|
||||
Irq: float64(times.Intr),
|
||||
})
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
ncpu, err := unix.SysctlUint32("hw.ncpu")
|
||||
if err != nil {
|
||||
return ret, err
|
||||
}
|
||||
|
||||
var i uint32
|
||||
for i = 0; i < ncpu; i++ {
|
||||
mib := []int32{ctlKern, kernCpTime, int32(i)}
|
||||
buf, _, err := common.CallSyscall(mib)
|
||||
if err != nil {
|
||||
return ret, err
|
||||
}
|
||||
|
||||
stats := (*cpuTimes)(unsafe.Pointer(&buf[0]))
|
||||
ret = append(ret, TimesStat{
|
||||
CPU: fmt.Sprintf("cpu%d", i),
|
||||
User: float64(stats.User),
|
||||
Nice: float64(stats.Nice),
|
||||
System: float64(stats.Sys),
|
||||
Idle: float64(stats.Idle),
|
||||
Irq: float64(stats.Intr),
|
||||
})
|
||||
}
|
||||
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
// Returns only one (minimal) CPUInfoStat on NetBSD
|
||||
func Info() ([]InfoStat, error) {
|
||||
return InfoWithContext(context.Background())
|
||||
}
|
||||
|
||||
func InfoWithContext(_ context.Context) ([]InfoStat, error) {
|
||||
var ret []InfoStat
|
||||
var err error
|
||||
|
||||
c := InfoStat{}
|
||||
|
||||
mhz, err := unix.Sysctl("machdep.dmi.processor-frequency")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
_, err = fmt.Sscanf(mhz, "%f", &c.Mhz)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
ncpu, err := unix.SysctlUint32("hw.ncpuonline")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
c.Cores = int32(ncpu)
|
||||
|
||||
if c.ModelName, err = unix.Sysctl("machdep.dmi.processor-version"); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return append(ret, c), nil
|
||||
}
|
||||
|
||||
func CountsWithContext(_ context.Context, _ bool) (int, error) {
|
||||
return runtime.NumCPU(), nil
|
||||
}
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
package cpu
|
||||
|
||||
type cpuTimes struct {
|
||||
User uint64
|
||||
Nice uint64
|
||||
Sys uint64
|
||||
Intr uint64
|
||||
Idle uint64
|
||||
}
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
package cpu
|
||||
|
||||
type cpuTimes struct {
|
||||
User uint32
|
||||
Nice uint32
|
||||
Sys uint32
|
||||
Intr uint32
|
||||
Idle uint32
|
||||
}
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
package cpu
|
||||
|
||||
type cpuTimes struct {
|
||||
User uint64
|
||||
Nice uint64
|
||||
Sys uint64
|
||||
Intr uint64
|
||||
Idle uint64
|
||||
}
|
||||
+139
@@ -0,0 +1,139 @@
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
//go:build openbsd
|
||||
|
||||
package cpu
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"runtime"
|
||||
"unsafe"
|
||||
|
||||
"github.com/tklauser/go-sysconf"
|
||||
"golang.org/x/sys/unix"
|
||||
|
||||
"github.com/shirou/gopsutil/v4/internal/common"
|
||||
)
|
||||
|
||||
const (
|
||||
// sys/sched.h
|
||||
cpuOnline = 0x0001 // CPUSTATS_ONLINE
|
||||
|
||||
// sys/sysctl.h
|
||||
ctlKern = 1 // "high kernel": proc, limits
|
||||
ctlHw = 6 // CTL_HW
|
||||
smt = 24 // HW_SMT
|
||||
kernCpTime = 40 // KERN_CPTIME
|
||||
kernCPUStats = 85 // KERN_CPUSTATS
|
||||
)
|
||||
|
||||
var ClocksPerSec = float64(128)
|
||||
|
||||
type cpuStats struct {
|
||||
// cs_time[CPUSTATES]
|
||||
User uint64
|
||||
Nice uint64
|
||||
Sys uint64
|
||||
Spin uint64
|
||||
Intr uint64
|
||||
Idle uint64
|
||||
|
||||
// cs_flags
|
||||
Flags uint64
|
||||
}
|
||||
|
||||
func init() {
|
||||
clkTck, err := sysconf.Sysconf(sysconf.SC_CLK_TCK)
|
||||
// ignore errors
|
||||
if err == nil {
|
||||
ClocksPerSec = float64(clkTck)
|
||||
}
|
||||
}
|
||||
|
||||
func Times(percpu bool) ([]TimesStat, error) {
|
||||
return TimesWithContext(context.Background(), percpu)
|
||||
}
|
||||
|
||||
func TimesWithContext(_ context.Context, percpu bool) ([]TimesStat, error) {
|
||||
ret := make([]TimesStat, 0)
|
||||
if !percpu {
|
||||
mib := []int32{ctlKern, kernCpTime}
|
||||
buf, _, err := common.CallSyscall(mib)
|
||||
if err != nil {
|
||||
return ret, err
|
||||
}
|
||||
times := (*cpuTimes)(unsafe.Pointer(&buf[0]))
|
||||
ret = append(ret, TimesStat{
|
||||
CPU: "cpu-total",
|
||||
User: float64(times.User) / ClocksPerSec,
|
||||
Nice: float64(times.Nice) / ClocksPerSec,
|
||||
System: float64(times.Sys) / ClocksPerSec,
|
||||
Idle: float64(times.Idle) / ClocksPerSec,
|
||||
Irq: float64(times.Intr) / ClocksPerSec,
|
||||
})
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
ncpu, err := unix.SysctlUint32("hw.ncpu")
|
||||
if err != nil {
|
||||
return ret, err
|
||||
}
|
||||
|
||||
var i uint32
|
||||
for i = 0; i < ncpu; i++ {
|
||||
mib := []int32{ctlKern, kernCPUStats, int32(i)}
|
||||
buf, _, err := common.CallSyscall(mib)
|
||||
if err != nil {
|
||||
return ret, err
|
||||
}
|
||||
|
||||
stats := (*cpuStats)(unsafe.Pointer(&buf[0]))
|
||||
if (stats.Flags & cpuOnline) == 0 {
|
||||
continue
|
||||
}
|
||||
ret = append(ret, TimesStat{
|
||||
CPU: fmt.Sprintf("cpu%d", i),
|
||||
User: float64(stats.User) / ClocksPerSec,
|
||||
Nice: float64(stats.Nice) / ClocksPerSec,
|
||||
System: float64(stats.Sys) / ClocksPerSec,
|
||||
Idle: float64(stats.Idle) / ClocksPerSec,
|
||||
Irq: float64(stats.Intr) / ClocksPerSec,
|
||||
})
|
||||
}
|
||||
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
// Returns only one (minimal) CPUInfoStat on OpenBSD
|
||||
func Info() ([]InfoStat, error) {
|
||||
return InfoWithContext(context.Background())
|
||||
}
|
||||
|
||||
func InfoWithContext(_ context.Context) ([]InfoStat, error) {
|
||||
var ret []InfoStat
|
||||
var err error
|
||||
|
||||
c := InfoStat{}
|
||||
|
||||
mhz, err := unix.SysctlUint32("hw.cpuspeed")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
c.Mhz = float64(mhz)
|
||||
|
||||
ncpu, err := unix.SysctlUint32("hw.ncpuonline")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
c.Cores = int32(ncpu)
|
||||
|
||||
if c.ModelName, err = unix.Sysctl("hw.model"); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return append(ret, c), nil
|
||||
}
|
||||
|
||||
func CountsWithContext(_ context.Context, _ bool) (int, error) {
|
||||
return runtime.NumCPU(), nil
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
package cpu
|
||||
|
||||
type cpuTimes struct {
|
||||
User uint32
|
||||
Nice uint32
|
||||
Sys uint32
|
||||
Spin uint32
|
||||
Intr uint32
|
||||
Idle uint32
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
package cpu
|
||||
|
||||
type cpuTimes struct {
|
||||
User uint64
|
||||
Nice uint64
|
||||
Sys uint64
|
||||
Spin uint64
|
||||
Intr uint64
|
||||
Idle uint64
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
package cpu
|
||||
|
||||
type cpuTimes struct {
|
||||
User uint32
|
||||
Nice uint32
|
||||
Sys uint32
|
||||
Spin uint32
|
||||
Intr uint32
|
||||
Idle uint32
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
package cpu
|
||||
|
||||
type cpuTimes struct {
|
||||
User uint64
|
||||
Nice uint64
|
||||
Sys uint64
|
||||
Spin uint64
|
||||
Intr uint64
|
||||
Idle uint64
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
package cpu
|
||||
|
||||
type cpuTimes struct {
|
||||
User uint64
|
||||
Nice uint64
|
||||
Sys uint64
|
||||
Spin uint64
|
||||
Intr uint64
|
||||
Idle uint64
|
||||
}
|
||||
+51
@@ -0,0 +1,51 @@
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
//go:build plan9
|
||||
|
||||
package cpu
|
||||
|
||||
import (
|
||||
"context"
|
||||
"os"
|
||||
"runtime"
|
||||
|
||||
stats "github.com/lufia/plan9stats"
|
||||
|
||||
"github.com/shirou/gopsutil/v4/internal/common"
|
||||
)
|
||||
|
||||
func Times(percpu bool) ([]TimesStat, error) {
|
||||
return TimesWithContext(context.Background(), percpu)
|
||||
}
|
||||
|
||||
func TimesWithContext(ctx context.Context, _ bool) ([]TimesStat, error) {
|
||||
// BUG: percpu flag is not supported yet.
|
||||
root := os.Getenv("HOST_ROOT")
|
||||
c, err := stats.ReadCPUType(ctx, stats.WithRootDir(root))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
s, err := stats.ReadCPUStats(ctx, stats.WithRootDir(root))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return []TimesStat{
|
||||
{
|
||||
CPU: c.Name,
|
||||
User: s.User.Seconds(),
|
||||
System: s.Sys.Seconds(),
|
||||
Idle: s.Idle.Seconds(),
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
func Info() ([]InfoStat, error) {
|
||||
return InfoWithContext(context.Background())
|
||||
}
|
||||
|
||||
func InfoWithContext(_ context.Context) ([]InfoStat, error) {
|
||||
return []InfoStat{}, common.ErrNotImplementedError
|
||||
}
|
||||
|
||||
func CountsWithContext(_ context.Context, _ bool) (int, error) {
|
||||
return runtime.NumCPU(), nil
|
||||
}
|
||||
+267
@@ -0,0 +1,267 @@
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
package cpu
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"regexp"
|
||||
"runtime"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/tklauser/go-sysconf"
|
||||
)
|
||||
|
||||
var ClocksPerSec = float64(128)
|
||||
|
||||
func init() {
|
||||
clkTck, err := sysconf.Sysconf(sysconf.SC_CLK_TCK)
|
||||
// ignore errors
|
||||
if err == nil {
|
||||
ClocksPerSec = float64(clkTck)
|
||||
}
|
||||
}
|
||||
|
||||
// sum all values in a float64 map with float64 keys
|
||||
func msum(x map[float64]float64) float64 {
|
||||
total := 0.0
|
||||
for _, y := range x {
|
||||
total += y
|
||||
}
|
||||
return total
|
||||
}
|
||||
|
||||
func Times(percpu bool) ([]TimesStat, error) {
|
||||
return TimesWithContext(context.Background(), percpu)
|
||||
}
|
||||
|
||||
var kstatSplit = regexp.MustCompile(`[:\s]+`)
|
||||
|
||||
func TimesWithContext(ctx context.Context, percpu bool) ([]TimesStat, error) {
|
||||
kstatSysOut, err := invoke.CommandWithContext(ctx, "kstat", "-p", "cpu_stat:*:*:/^idle$|^user$|^kernel$|^iowait$|^swap$/")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("cannot execute kstat: %w", err)
|
||||
}
|
||||
cpu := make(map[float64]float64)
|
||||
idle := make(map[float64]float64)
|
||||
user := make(map[float64]float64)
|
||||
kern := make(map[float64]float64)
|
||||
iowt := make(map[float64]float64)
|
||||
// swap := make(map[float64]float64)
|
||||
for _, line := range strings.Split(string(kstatSysOut), "\n") {
|
||||
fields := kstatSplit.Split(line, -1)
|
||||
if fields[0] != "cpu_stat" {
|
||||
continue
|
||||
}
|
||||
cpuNumber, err := strconv.ParseFloat(fields[1], 64)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("cannot parse cpu number: %w", err)
|
||||
}
|
||||
cpu[cpuNumber] = cpuNumber
|
||||
switch fields[3] {
|
||||
case "idle":
|
||||
idle[cpuNumber], err = strconv.ParseFloat(fields[4], 64)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("cannot parse idle: %w", err)
|
||||
}
|
||||
case "user":
|
||||
user[cpuNumber], err = strconv.ParseFloat(fields[4], 64)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("cannot parse user: %w", err)
|
||||
}
|
||||
case "kernel":
|
||||
kern[cpuNumber], err = strconv.ParseFloat(fields[4], 64)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("cannot parse kernel: %w", err)
|
||||
}
|
||||
case "iowait":
|
||||
iowt[cpuNumber], err = strconv.ParseFloat(fields[4], 64)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("cannot parse iowait: %w", err)
|
||||
}
|
||||
// not sure how this translates, don't report, add to kernel, something else?
|
||||
/*case "swap":
|
||||
swap[cpuNumber], err = strconv.ParseFloat(fields[4], 64)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("cannot parse swap: %s", err)
|
||||
} */
|
||||
}
|
||||
}
|
||||
ret := make([]TimesStat, 0, len(cpu))
|
||||
if percpu {
|
||||
for _, c := range cpu {
|
||||
ct := &TimesStat{
|
||||
CPU: fmt.Sprintf("cpu%d", int(cpu[c])),
|
||||
Idle: idle[c] / ClocksPerSec,
|
||||
User: user[c] / ClocksPerSec,
|
||||
System: kern[c] / ClocksPerSec,
|
||||
Iowait: iowt[c] / ClocksPerSec,
|
||||
}
|
||||
ret = append(ret, *ct)
|
||||
}
|
||||
} else {
|
||||
ct := &TimesStat{
|
||||
CPU: "cpu-total",
|
||||
Idle: msum(idle) / ClocksPerSec,
|
||||
User: msum(user) / ClocksPerSec,
|
||||
System: msum(kern) / ClocksPerSec,
|
||||
Iowait: msum(iowt) / ClocksPerSec,
|
||||
}
|
||||
ret = append(ret, *ct)
|
||||
}
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
func Info() ([]InfoStat, error) {
|
||||
return InfoWithContext(context.Background())
|
||||
}
|
||||
|
||||
func InfoWithContext(ctx context.Context) ([]InfoStat, error) {
|
||||
psrInfoOut, err := invoke.CommandWithContext(ctx, "psrinfo", "-p", "-v")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("cannot execute psrinfo: %w", err)
|
||||
}
|
||||
|
||||
procs, err := parseProcessorInfo(string(psrInfoOut))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error parsing psrinfo output: %w", err)
|
||||
}
|
||||
|
||||
isaInfoOut, err := invoke.CommandWithContext(ctx, "isainfo", "-b", "-v")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("cannot execute isainfo: %w", err)
|
||||
}
|
||||
|
||||
flags, err := parseISAInfo(string(isaInfoOut))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error parsing isainfo output: %w", err)
|
||||
}
|
||||
|
||||
result := make([]InfoStat, 0, len(flags))
|
||||
for i := range procs {
|
||||
procWithFlags := procs[i]
|
||||
procWithFlags.Flags = flags
|
||||
result = append(result, procWithFlags)
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
var flagsMatch = regexp.MustCompile(`[\w.]+`)
|
||||
|
||||
func parseISAInfo(cmdOutput string) ([]string, error) {
|
||||
words := flagsMatch.FindAllString(cmdOutput, -1)
|
||||
|
||||
// Sanity check the output
|
||||
if len(words) < 4 || words[1] != "bit" || words[3] != "applications" {
|
||||
return nil, errors.New("attempted to parse invalid isainfo output")
|
||||
}
|
||||
|
||||
flags := words[4:]
|
||||
sort.Strings(flags)
|
||||
|
||||
return flags, nil
|
||||
}
|
||||
|
||||
var psrInfoMatch = regexp.MustCompile(`The physical processor has (?:([\d]+) virtual processors? \(([\d-]+)\)|([\d]+) cores and ([\d]+) virtual processors[^\n]+)\n(?:\s+ The core has.+\n)*\s+.+ \((\w+) ([\S]+) family (.+) model (.+) step (.+) clock (.+) MHz\)\n[\s]*(.*)`)
|
||||
|
||||
const (
|
||||
psrNumCoresOffset = 1
|
||||
psrNumCoresHTOffset = 3
|
||||
psrNumHTOffset = 4
|
||||
psrVendorIDOffset = 5
|
||||
psrFamilyOffset = 7
|
||||
psrModelOffset = 8
|
||||
psrStepOffset = 9
|
||||
psrClockOffset = 10
|
||||
psrModelNameOffset = 11
|
||||
)
|
||||
|
||||
func parseProcessorInfo(cmdOutput string) ([]InfoStat, error) {
|
||||
matches := psrInfoMatch.FindAllStringSubmatch(cmdOutput, -1)
|
||||
|
||||
var infoStatCount int32
|
||||
result := make([]InfoStat, 0, len(matches))
|
||||
for physicalIndex, physicalCPU := range matches {
|
||||
var step int32
|
||||
var clock float64
|
||||
|
||||
if physicalCPU[psrStepOffset] != "" {
|
||||
stepParsed, err := strconv.ParseInt(physicalCPU[psrStepOffset], 10, 32)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("cannot parse value %q for step as 32-bit integer: %w", physicalCPU[9], err)
|
||||
}
|
||||
step = int32(stepParsed)
|
||||
}
|
||||
|
||||
if physicalCPU[psrClockOffset] != "" {
|
||||
clockParsed, err := strconv.ParseInt(physicalCPU[psrClockOffset], 10, 64)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("cannot parse value %q for clock as 32-bit integer: %w", physicalCPU[10], err)
|
||||
}
|
||||
clock = float64(clockParsed)
|
||||
}
|
||||
|
||||
var err error
|
||||
var numCores int64
|
||||
var numHT int64
|
||||
switch {
|
||||
case physicalCPU[psrNumCoresOffset] != "":
|
||||
numCores, err = strconv.ParseInt(physicalCPU[psrNumCoresOffset], 10, 32)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("cannot parse value %q for core count as 32-bit integer: %w", physicalCPU[1], err)
|
||||
}
|
||||
|
||||
for i := 0; i < int(numCores); i++ {
|
||||
result = append(result, InfoStat{
|
||||
CPU: infoStatCount,
|
||||
PhysicalID: strconv.Itoa(physicalIndex),
|
||||
CoreID: strconv.Itoa(i),
|
||||
Cores: 1,
|
||||
VendorID: physicalCPU[psrVendorIDOffset],
|
||||
ModelName: physicalCPU[psrModelNameOffset],
|
||||
Family: physicalCPU[psrFamilyOffset],
|
||||
Model: physicalCPU[psrModelOffset],
|
||||
Stepping: step,
|
||||
Mhz: clock,
|
||||
})
|
||||
infoStatCount++
|
||||
}
|
||||
case physicalCPU[psrNumCoresHTOffset] != "":
|
||||
numCores, err = strconv.ParseInt(physicalCPU[psrNumCoresHTOffset], 10, 32)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("cannot parse value %q for core count as 32-bit integer: %w", physicalCPU[3], err)
|
||||
}
|
||||
|
||||
numHT, err = strconv.ParseInt(physicalCPU[psrNumHTOffset], 10, 32)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("cannot parse value %q for hyperthread count as 32-bit integer: %w", physicalCPU[4], err)
|
||||
}
|
||||
|
||||
for i := 0; i < int(numCores); i++ {
|
||||
result = append(result, InfoStat{
|
||||
CPU: infoStatCount,
|
||||
PhysicalID: strconv.Itoa(physicalIndex),
|
||||
CoreID: strconv.Itoa(i),
|
||||
Cores: int32(numHT) / int32(numCores),
|
||||
VendorID: physicalCPU[psrVendorIDOffset],
|
||||
ModelName: physicalCPU[psrModelNameOffset],
|
||||
Family: physicalCPU[psrFamilyOffset],
|
||||
Model: physicalCPU[psrModelOffset],
|
||||
Stepping: step,
|
||||
Mhz: clock,
|
||||
})
|
||||
infoStatCount++
|
||||
}
|
||||
default:
|
||||
return nil, errors.New("values for cores with and without hyperthreading are both set")
|
||||
}
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func CountsWithContext(_ context.Context, _ bool) (int, error) {
|
||||
return runtime.NumCPU(), nil
|
||||
}
|
||||
+477
@@ -0,0 +1,477 @@
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
//go:build windows
|
||||
|
||||
package cpu
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"math/bits"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
"syscall"
|
||||
"unsafe"
|
||||
|
||||
"golang.org/x/sys/windows"
|
||||
"golang.org/x/sys/windows/registry"
|
||||
|
||||
"github.com/shirou/gopsutil/v4/internal/common"
|
||||
)
|
||||
|
||||
var (
|
||||
procGetNativeSystemInfo = common.Modkernel32.NewProc("GetNativeSystemInfo")
|
||||
procGetLogicalProcessorInformationEx = common.Modkernel32.NewProc("GetLogicalProcessorInformationEx")
|
||||
procGetSystemFirmwareTable = common.Modkernel32.NewProc("GetSystemFirmwareTable")
|
||||
procCallNtPowerInformation = common.ModPowrProf.NewProc("CallNtPowerInformation")
|
||||
)
|
||||
|
||||
type win32_Processor struct { //nolint:revive //FIXME
|
||||
Family uint16
|
||||
Manufacturer string
|
||||
Name string
|
||||
NumberOfLogicalProcessors uint32
|
||||
NumberOfCores uint32
|
||||
ProcessorID *string
|
||||
Stepping *string
|
||||
MaxClockSpeed uint32
|
||||
}
|
||||
|
||||
// SYSTEM_PROCESSOR_PERFORMANCE_INFORMATION
|
||||
// defined in windows api doc with the following
|
||||
// https://docs.microsoft.com/en-us/windows/desktop/api/winternl/nf-winternl-ntquerysysteminformation#system_processor_performance_information
|
||||
// additional fields documented here
|
||||
// https://www.geoffchappell.com/studies/windows/km/ntoskrnl/api/ex/sysinfo/processor_performance.htm
|
||||
type win32_SystemProcessorPerformanceInformation struct { //nolint:revive //FIXME
|
||||
IdleTime int64 // idle time in 100ns (this is not a filetime).
|
||||
KernelTime int64 // kernel time in 100ns. kernel time includes idle time. (this is not a filetime).
|
||||
UserTime int64 // usertime in 100ns (this is not a filetime).
|
||||
DpcTime int64 // dpc time in 100ns (this is not a filetime).
|
||||
InterruptTime int64 // interrupt time in 100ns
|
||||
InterruptCount uint64 // ULONG needs to be uint64
|
||||
}
|
||||
|
||||
// https://learn.microsoft.com/en-us/windows/win32/power/processor-power-information-str
|
||||
type processorPowerInformation struct {
|
||||
number uint32 // http://download.microsoft.com/download/a/d/f/adf1347d-08dc-41a4-9084-623b1194d4b2/MoreThan64proc.docx
|
||||
maxMhz uint32
|
||||
currentMhz uint32
|
||||
mhzLimit uint32
|
||||
maxIdleState uint32
|
||||
currentIdleState uint32
|
||||
}
|
||||
|
||||
const (
|
||||
ClocksPerSec = 10000000.0
|
||||
|
||||
// systemProcessorPerformanceInformationClass information class to query with NTQuerySystemInformation
|
||||
// https://processhacker.sourceforge.io/doc/ntexapi_8h.html#ad5d815b48e8f4da1ef2eb7a2f18a54e0
|
||||
win32_SystemProcessorPerformanceInformationClass = 8 //nolint:revive //FIXME
|
||||
|
||||
// size of systemProcessorPerformanceInfoSize in memory
|
||||
win32_SystemProcessorPerformanceInfoSize = uint32(unsafe.Sizeof(win32_SystemProcessorPerformanceInformation{})) //nolint:revive //FIXME
|
||||
|
||||
firmwareTableProviderSignatureRSMB = 0x52534d42 // "RSMB" https://gitlab.winehq.org/dreamer/wine/-/blame/wine-7.0-rc6/dlls/ntdll/unix/system.c#L230
|
||||
smBiosHeaderSize = 8 // SMBIOS header size
|
||||
smbiosEndOfTable = 127 // Minimum length for processor structure
|
||||
smbiosTypeProcessor = 4 // SMBIOS Type 4: Processor Information
|
||||
smbiosProcessorMinLength = 0x18 // Minimum length for processor structure
|
||||
|
||||
centralProcessorRegistryKey = `HARDWARE\DESCRIPTION\System\CentralProcessor`
|
||||
)
|
||||
|
||||
type relationship uint32
|
||||
|
||||
// https://learn.microsoft.com/en-us/windows/win32/api/sysinfoapi/nf-sysinfoapi-getlogicalprocessorinformationex
|
||||
const (
|
||||
relationProcessorCore = relationship(0)
|
||||
relationProcessorPackage = relationship(3)
|
||||
)
|
||||
|
||||
const (
|
||||
kAffinitySize = unsafe.Sizeof(int(0))
|
||||
// https://learn.microsoft.com/en-us/windows-hardware/drivers/kernel/interrupt-affinity-and-priority
|
||||
maxLogicalProcessorsPerGroup = uint32(unsafe.Sizeof(kAffinitySize * 8))
|
||||
// https://learn.microsoft.com/en-us/windows-hardware/drivers/ddi/wdm/ne-wdm-power_information_level
|
||||
processorInformation = 11
|
||||
)
|
||||
|
||||
// Times returns times stat per cpu and combined for all CPUs
|
||||
func Times(percpu bool) ([]TimesStat, error) {
|
||||
return TimesWithContext(context.Background(), percpu)
|
||||
}
|
||||
|
||||
func TimesWithContext(_ context.Context, percpu bool) ([]TimesStat, error) {
|
||||
if percpu {
|
||||
return perCPUTimes()
|
||||
}
|
||||
|
||||
var ret []TimesStat
|
||||
var lpIdleTime common.FILETIME
|
||||
var lpKernelTime common.FILETIME
|
||||
var lpUserTime common.FILETIME
|
||||
// GetSystemTimes returns 0 for error, in which case we check err,
|
||||
// see https://pkg.go.dev/golang.org/x/sys/windows#LazyProc.Call
|
||||
r, _, err := common.ProcGetSystemTimes.Call(
|
||||
uintptr(unsafe.Pointer(&lpIdleTime)),
|
||||
uintptr(unsafe.Pointer(&lpKernelTime)),
|
||||
uintptr(unsafe.Pointer(&lpUserTime)))
|
||||
if r == 0 {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
LOT := float64(0.0000001)
|
||||
HIT := (LOT * 4294967296.0)
|
||||
idle := ((HIT * float64(lpIdleTime.DwHighDateTime)) + (LOT * float64(lpIdleTime.DwLowDateTime)))
|
||||
user := ((HIT * float64(lpUserTime.DwHighDateTime)) + (LOT * float64(lpUserTime.DwLowDateTime)))
|
||||
kernel := ((HIT * float64(lpKernelTime.DwHighDateTime)) + (LOT * float64(lpKernelTime.DwLowDateTime)))
|
||||
system := (kernel - idle)
|
||||
|
||||
ret = append(ret, TimesStat{
|
||||
CPU: "cpu-total",
|
||||
Idle: float64(idle),
|
||||
User: float64(user),
|
||||
System: float64(system),
|
||||
})
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
func Info() ([]InfoStat, error) {
|
||||
return InfoWithContext(context.Background())
|
||||
}
|
||||
|
||||
// this function iterates over each set bit in the package affinity mask, each bit represent a logical processor in a group (assuming you are iteriang over a package mask)
|
||||
// the function is used also to compute the global logical processor number
|
||||
// https://learn.microsoft.com/en-us/windows/win32/procthread/processor-groups
|
||||
// see https://learn.microsoft.com/en-us/windows/win32/api/winnt/ns-winnt-group_affinity
|
||||
// and https://learn.microsoft.com/en-us/windows/win32/api/winnt/ns-winnt-processor_relationship
|
||||
// and https://learn.microsoft.com/en-us/windows/win32/api/winnt/ns-winnt-system_logical_processor_information_ex
|
||||
func forEachSetBit64(mask uint64, fn func(bit int)) {
|
||||
m := mask
|
||||
for m != 0 {
|
||||
b := bits.TrailingZeros64(m)
|
||||
fn(b)
|
||||
m &= m - 1
|
||||
}
|
||||
}
|
||||
|
||||
func getProcessorPowerInformation(ctx context.Context) ([]processorPowerInformation, error) {
|
||||
numLP, countErr := CountsWithContext(ctx, true)
|
||||
if countErr != nil {
|
||||
return nil, fmt.Errorf("failed to get logical processor count: %w", countErr)
|
||||
}
|
||||
if numLP <= 0 {
|
||||
return nil, fmt.Errorf("invalid logical processor count: %d", numLP)
|
||||
}
|
||||
|
||||
ppiSize := uintptr(numLP) * unsafe.Sizeof(processorPowerInformation{})
|
||||
buf := make([]byte, ppiSize)
|
||||
ppi, _, err := procCallNtPowerInformation.Call(
|
||||
uintptr(processorInformation),
|
||||
0,
|
||||
0,
|
||||
uintptr(unsafe.Pointer(&buf[0])),
|
||||
uintptr(ppiSize),
|
||||
)
|
||||
if ppi != 0 {
|
||||
return nil, fmt.Errorf("CallNtPowerInformation failed with code %d: %w", ppi, err)
|
||||
}
|
||||
ppis := unsafe.Slice((*processorPowerInformation)(unsafe.Pointer(&buf[0])), numLP)
|
||||
return ppis, nil
|
||||
}
|
||||
|
||||
func InfoWithContext(ctx context.Context) ([]InfoStat, error) {
|
||||
var ret []InfoStat
|
||||
processorPackages, err := getSystemLogicalProcessorInformationEx(relationProcessorPackage)
|
||||
if err != nil {
|
||||
return ret, fmt.Errorf("failed to get processor package information: %w", err)
|
||||
}
|
||||
|
||||
ppis, powerInformationErr := getProcessorPowerInformation(ctx)
|
||||
if powerInformationErr != nil {
|
||||
return ret, fmt.Errorf("failed to get processor power information: %w", powerInformationErr)
|
||||
}
|
||||
|
||||
family, processorId, smBIOSErr := getSMBIOSProcessorInfo()
|
||||
if smBIOSErr != nil {
|
||||
return ret, smBIOSErr
|
||||
}
|
||||
|
||||
for i, pkg := range processorPackages {
|
||||
logicalCount := 0
|
||||
maxMhz := 0
|
||||
model := ""
|
||||
vendorId := ""
|
||||
// iterate over each set bit in the package affinity mask
|
||||
for _, ga := range pkg.processor.groupMask {
|
||||
g := int(ga.group)
|
||||
forEachSetBit64(uint64(ga.mask), func(bit int) {
|
||||
// the global logical processor label
|
||||
globalLpl := g*int(maxLogicalProcessorsPerGroup) + bit
|
||||
if globalLpl >= 0 && globalLpl < len(ppis) {
|
||||
logicalCount++
|
||||
m := int(ppis[globalLpl].maxMhz)
|
||||
if m > maxMhz {
|
||||
maxMhz = m
|
||||
}
|
||||
}
|
||||
|
||||
registryKeyPath := filepath.Join(centralProcessorRegistryKey, strconv.Itoa(globalLpl))
|
||||
key, err := registry.OpenKey(registry.LOCAL_MACHINE, registryKeyPath, registry.QUERY_VALUE|registry.READ)
|
||||
if err == nil {
|
||||
model = getRegistryStringValueIfUnset(key, "ProcessorNameString", model)
|
||||
vendorId = getRegistryStringValueIfUnset(key, "VendorIdentifier", vendorId)
|
||||
_ = key.Close()
|
||||
}
|
||||
})
|
||||
}
|
||||
ret = append(ret, InfoStat{
|
||||
CPU: int32(i),
|
||||
Family: strconv.FormatUint(uint64(family), 10),
|
||||
VendorID: vendorId,
|
||||
ModelName: model,
|
||||
Cores: int32(logicalCount),
|
||||
PhysicalID: processorId,
|
||||
Mhz: float64(maxMhz),
|
||||
Flags: []string{},
|
||||
})
|
||||
}
|
||||
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
// perCPUTimes returns times stat per cpu, per core and overall for all CPUs
|
||||
func perCPUTimes() ([]TimesStat, error) {
|
||||
var ret []TimesStat
|
||||
stats, err := perfInfo()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for core, v := range stats {
|
||||
c := TimesStat{
|
||||
CPU: fmt.Sprintf("cpu%d", core),
|
||||
User: float64(v.UserTime) / ClocksPerSec,
|
||||
System: float64(v.KernelTime-v.IdleTime) / ClocksPerSec,
|
||||
Idle: float64(v.IdleTime) / ClocksPerSec,
|
||||
Irq: float64(v.InterruptTime) / ClocksPerSec,
|
||||
}
|
||||
ret = append(ret, c)
|
||||
}
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
// makes call to Windows API function to retrieve performance information for each core
|
||||
func perfInfo() ([]win32_SystemProcessorPerformanceInformation, error) {
|
||||
// Make maxResults large for safety.
|
||||
// We can't invoke the api call with a results array that's too small.
|
||||
// If we have more than 2056 cores on a single host, then it's probably the future.
|
||||
maxBuffer := 2056
|
||||
// buffer for results from the windows proc
|
||||
resultBuffer := make([]win32_SystemProcessorPerformanceInformation, maxBuffer)
|
||||
// size of the buffer in memory
|
||||
bufferSize := uintptr(win32_SystemProcessorPerformanceInfoSize) * uintptr(maxBuffer)
|
||||
// size of the returned response
|
||||
var retSize uint32
|
||||
|
||||
// Invoke windows api proc.
|
||||
// The returned err from the windows dll proc will always be non-nil even when successful.
|
||||
// See https://godoc.org/golang.org/x/sys/windows#LazyProc.Call for more information
|
||||
retCode, _, err := common.ProcNtQuerySystemInformation.Call(
|
||||
win32_SystemProcessorPerformanceInformationClass, // System Information Class -> SystemProcessorPerformanceInformation
|
||||
uintptr(unsafe.Pointer(&resultBuffer[0])), // pointer to first element in result buffer
|
||||
bufferSize, // size of the buffer in memory
|
||||
uintptr(unsafe.Pointer(&retSize)), // pointer to the size of the returned results the windows proc will set this
|
||||
)
|
||||
|
||||
// check return code for errors
|
||||
if retCode != 0 {
|
||||
return nil, fmt.Errorf("call to NtQuerySystemInformation returned %d. err: %s", retCode, err.Error())
|
||||
}
|
||||
|
||||
// calculate the number of returned elements based on the returned size
|
||||
numReturnedElements := retSize / win32_SystemProcessorPerformanceInfoSize
|
||||
|
||||
// trim results to the number of returned elements
|
||||
resultBuffer = resultBuffer[:numReturnedElements]
|
||||
|
||||
return resultBuffer, nil
|
||||
}
|
||||
|
||||
// SystemInfo is an equivalent representation of SYSTEM_INFO in the Windows API.
|
||||
// https://msdn.microsoft.com/en-us/library/ms724958%28VS.85%29.aspx?f=255&MSPPError=-2147217396
|
||||
// https://github.com/elastic/go-windows/blob/bb1581babc04d5cb29a2bfa7a9ac6781c730c8dd/kernel32.go#L43
|
||||
type systemInfo struct {
|
||||
wProcessorArchitecture uint16
|
||||
wReserved uint16
|
||||
dwPageSize uint32
|
||||
lpMinimumApplicationAddress uintptr
|
||||
lpMaximumApplicationAddress uintptr
|
||||
dwActiveProcessorMask uintptr
|
||||
dwNumberOfProcessors uint32
|
||||
dwProcessorType uint32
|
||||
dwAllocationGranularity uint32
|
||||
wProcessorLevel uint16
|
||||
wProcessorRevision uint16
|
||||
}
|
||||
|
||||
type groupAffinity struct {
|
||||
mask uintptr // https://learn.microsoft.com/en-us/windows-hardware/drivers/kernel/interrupt-affinity-and-priority#about-kaffinity
|
||||
group uint16
|
||||
reserved [3]uint16
|
||||
}
|
||||
|
||||
// https://learn.microsoft.com/en-us/windows/win32/api/winnt/ns-winnt-processor_relationship
|
||||
type processorRelationship struct {
|
||||
flags byte
|
||||
efficientClass byte
|
||||
reserved [20]byte
|
||||
groupCount uint16
|
||||
groupMask [1]groupAffinity
|
||||
}
|
||||
|
||||
// https://learn.microsoft.com/en-us/windows/win32/api/winnt/ns-winnt-system_logical_processor_information_ex
|
||||
type systemLogicalProcessorInformationEx struct {
|
||||
relationship uint32
|
||||
size uint32
|
||||
processor processorRelationship
|
||||
}
|
||||
|
||||
// getSMBIOSProcessorInfo reads the SMBIOS Type 4 (Processor Information) structure and returns the Processor Family and ProcessorId fields.
|
||||
// If not found, returns 0 and an empty string.
|
||||
func getSMBIOSProcessorInfo() (family uint8, processorId string, err error) {
|
||||
// https://learn.microsoft.com/en-us/windows/win32/api/sysinfoapi/nf-sysinfoapi-getsystemfirmwaretable
|
||||
size, _, err := procGetSystemFirmwareTable.Call(
|
||||
uintptr(firmwareTableProviderSignatureRSMB),
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
)
|
||||
if size == 0 {
|
||||
return 0, "", fmt.Errorf("failed to get SMBIOS table size: %w", err)
|
||||
}
|
||||
buf := make([]byte, size)
|
||||
ret, _, err := procGetSystemFirmwareTable.Call(
|
||||
uintptr(firmwareTableProviderSignatureRSMB),
|
||||
0,
|
||||
uintptr(unsafe.Pointer(&buf[0])),
|
||||
uintptr(size),
|
||||
)
|
||||
if ret == 0 {
|
||||
return 0, "", fmt.Errorf("failed to read SMBIOS table: %w", err)
|
||||
}
|
||||
// https://wiki.osdev.org/System_Management_BIOS
|
||||
i := smBiosHeaderSize // skip SMBIOS header (first 8 bytes)
|
||||
maxIterations := len(buf) * 2
|
||||
iterations := 0
|
||||
for i < len(buf) && iterations < maxIterations {
|
||||
iterations++
|
||||
if i+4 > len(buf) {
|
||||
break
|
||||
}
|
||||
typ := buf[i]
|
||||
length := buf[i+1]
|
||||
if typ == smbiosEndOfTable {
|
||||
break
|
||||
}
|
||||
if typ == smbiosTypeProcessor && length >= smbiosProcessorMinLength && i+int(length) <= len(buf) {
|
||||
// Ensure we have enough bytes for procIdBytes
|
||||
if i+16 > len(buf) {
|
||||
break
|
||||
}
|
||||
// Get the processor family from byte at offset 6
|
||||
family = buf[i+6]
|
||||
// Extract processor ID bytes (8 bytes total) from offsets 8-15
|
||||
procIdBytes := buf[i+8 : i+16]
|
||||
// Convert first 4 bytes to 32-bit EAX register value (little endian)
|
||||
eax := uint32(procIdBytes[0]) | uint32(procIdBytes[1])<<8 | uint32(procIdBytes[2])<<16 | uint32(procIdBytes[3])<<24
|
||||
// Convert last 4 bytes to 32-bit EDX register value (little endian)
|
||||
edx := uint32(procIdBytes[4]) | uint32(procIdBytes[5])<<8 | uint32(procIdBytes[6])<<16 | uint32(procIdBytes[7])<<24
|
||||
// Format processor ID as 16 character hex string (EDX+EAX)
|
||||
procId := fmt.Sprintf("%08X%08X", edx, eax)
|
||||
return family, procId, nil
|
||||
}
|
||||
// skip to next structure
|
||||
j := i + int(length)
|
||||
innerIterations := 0
|
||||
maxInner := len(buf) // failsafe for inner loop
|
||||
for j+1 < len(buf) && innerIterations < maxInner {
|
||||
innerIterations++
|
||||
if buf[j] == 0 && buf[j+1] == 0 {
|
||||
j += 2
|
||||
break
|
||||
}
|
||||
j++
|
||||
}
|
||||
if innerIterations >= maxInner {
|
||||
break // malformed buffer, avoid infinite loop
|
||||
}
|
||||
i = j
|
||||
}
|
||||
return 0, "", fmt.Errorf("SMBIOS processor information not found: %w", syscall.ERROR_NOT_FOUND)
|
||||
}
|
||||
|
||||
func getSystemLogicalProcessorInformationEx(relationship relationship) ([]systemLogicalProcessorInformationEx, error) {
|
||||
var length uint32
|
||||
// First call to determine the required buffer size
|
||||
_, _, err := procGetLogicalProcessorInformationEx.Call(uintptr(relationship), 0, uintptr(unsafe.Pointer(&length)))
|
||||
if err != nil && !errors.Is(err, windows.ERROR_INSUFFICIENT_BUFFER) {
|
||||
return nil, fmt.Errorf("failed to get buffer size: %w", err)
|
||||
}
|
||||
|
||||
// Allocate the buffer
|
||||
buffer := make([]byte, length)
|
||||
|
||||
// Second call to retrieve the processor information
|
||||
_, _, err = procGetLogicalProcessorInformationEx.Call(uintptr(relationship), uintptr(unsafe.Pointer(&buffer[0])), uintptr(unsafe.Pointer(&length)))
|
||||
if err != nil && !errors.Is(err, windows.NTE_OP_OK) {
|
||||
return nil, fmt.Errorf("failed to get logical processor information: %w", err)
|
||||
}
|
||||
|
||||
// Convert the byte slice into a slice of systemLogicalProcessorInformationEx structs
|
||||
offset := uintptr(0)
|
||||
var infos []systemLogicalProcessorInformationEx
|
||||
for offset < uintptr(length) {
|
||||
info := (*systemLogicalProcessorInformationEx)(unsafe.Pointer(uintptr(unsafe.Pointer(&buffer[0])) + offset))
|
||||
infos = append(infos, *info)
|
||||
offset += uintptr(info.size)
|
||||
}
|
||||
|
||||
return infos, nil
|
||||
}
|
||||
|
||||
func getPhysicalCoreCount() (int, error) {
|
||||
infos, err := getSystemLogicalProcessorInformationEx(relationProcessorCore)
|
||||
return len(infos), err
|
||||
}
|
||||
|
||||
func getRegistryStringValueIfUnset(key registry.Key, keyName, value string) string {
|
||||
if value != "" {
|
||||
return value
|
||||
}
|
||||
val, _, err := key.GetStringValue(keyName)
|
||||
if err == nil {
|
||||
return strings.TrimSpace(val)
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func CountsWithContext(_ context.Context, logical bool) (int, error) {
|
||||
if logical {
|
||||
// Get logical processor count https://github.com/giampaolo/psutil/blob/d01a9eaa35a8aadf6c519839e987a49d8be2d891/psutil/_psutil_windows.c#L97
|
||||
ret := windows.GetActiveProcessorCount(windows.ALL_PROCESSOR_GROUPS)
|
||||
if ret != 0 {
|
||||
return int(ret), nil
|
||||
}
|
||||
|
||||
var sInfo systemInfo
|
||||
_, _, err := procGetNativeSystemInfo.Call(uintptr(unsafe.Pointer(&sInfo)))
|
||||
if sInfo.dwNumberOfProcessors == 0 {
|
||||
return 0, err
|
||||
}
|
||||
return int(sInfo.dwNumberOfProcessors), nil
|
||||
}
|
||||
|
||||
// Get physical core count https://github.com/giampaolo/psutil/blob/d01a9eaa35a8aadf6c519839e987a49d8be2d891/psutil/_psutil_windows.c#L499
|
||||
return getPhysicalCoreCount()
|
||||
}
|
||||
Reference in New Issue
Block a user