From 9203029aaef2858fc516bc76b9edc0f9020ba38e Mon Sep 17 00:00:00 2001 From: Omar Polo Date: Sun, 6 Feb 2022 23:47:16 +0000 Subject: [PATCH 1/6] drop compatibilty with OpenBSD < 6.4 6.3 was EOL'd more than three years ago! --- cpu/cpu_openbsd.go | 34 +++++++--------------------------- 1 file changed, 7 insertions(+), 27 deletions(-) diff --git a/cpu/cpu_openbsd.go b/cpu/cpu_openbsd.go index 701d45c51..9cad38c81 100644 --- a/cpu/cpu_openbsd.go +++ b/cpu/cpu_openbsd.go @@ -9,8 +9,6 @@ import ( "encoding/binary" "fmt" "runtime" - "strconv" - "strings" "syscall" "github.com/shirou/gopsutil/v3/internal/common" @@ -18,18 +16,17 @@ import ( "golang.org/x/sys/unix" ) -// sys/sched.h -var ( +const ( + // sys/sched.h CPUser = 0 cpNice = 1 cpSys = 2 - cpIntr = 3 - cpIdle = 4 - cpUStates = 5 -) + cpSpin = 3 + cpIntr = 4 + cpIdle = 5 + cpUStates = 6 -// sys/sysctl.h -const ( + // sys/sysctl.h ctlKern = 1 // "high kernel": proc, limits ctlHw = 6 // CTL_HW sMT = 24 // HW_sMT @@ -45,23 +42,6 @@ func init() { if err == nil { ClocksPerSec = float64(clkTck) } - - func() { - v, err := unix.Sysctl("kern.osrelease") // can't reuse host.PlatformInformation because of circular import - if err != nil { - return - } - v = strings.ToLower(v) - version, err := strconv.ParseFloat(v, 64) - if err != nil { - return - } - if version >= 6.4 { - cpIntr = 4 - cpIdle = 5 - cpUStates = 6 - } - }() } func smt() (bool, error) { From 73db06165253453bbfca1a2fab14d122bd83d56c Mon Sep 17 00:00:00 2001 From: Omar Polo Date: Sun, 6 Feb 2022 23:47:17 +0000 Subject: [PATCH 2/6] some typos + rename smt to smtEnabled for readability --- cpu/cpu_openbsd.go | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/cpu/cpu_openbsd.go b/cpu/cpu_openbsd.go index 9cad38c81..6906b9b20 100644 --- a/cpu/cpu_openbsd.go +++ b/cpu/cpu_openbsd.go @@ -18,18 +18,18 @@ import ( const ( // sys/sched.h - CPUser = 0 + cpUser = 0 cpNice = 1 cpSys = 2 cpSpin = 3 cpIntr = 4 cpIdle = 5 - cpUStates = 6 + cpuStates = 6 // sys/sysctl.h ctlKern = 1 // "high kernel": proc, limits ctlHw = 6 // CTL_HW - sMT = 24 // HW_sMT + smt = 24 // HW_SMT kernCptime = 40 // KERN_CPTIME kernCptime2 = 71 // KERN_CPTIME2 ) @@ -44,8 +44,8 @@ func init() { } } -func smt() (bool, error) { - mib := []int32{ctlHw, sMT} +func smtEnabled() (bool, error) { + mib := []int32{ctlHw, smt} buf, _, err := common.CallSyscall(mib) if err != nil { return false, err @@ -74,7 +74,7 @@ func TimesWithContext(ctx context.Context, percpu bool) ([]TimesStat, error) { ncpu = 1 } - smt, err := smt() + smt, err := smtEnabled() if err == syscall.EOPNOTSUPP { // if hw.smt is not applicable for this platform (e.g. i386), // pretend it's enabled @@ -89,7 +89,7 @@ func TimesWithContext(ctx context.Context, percpu bool) ([]TimesStat, error) { j *= 2 } - cpuTimes := make([]int32, cpUStates) + cpuTimes := make([]int32, cpuStates) var mib []int32 if percpu { mib = []int32{ctlKern, kernCptime2, int32(j)} @@ -107,7 +107,7 @@ func TimesWithContext(ctx context.Context, percpu bool) ([]TimesStat, error) { return ret, err } c := TimesStat{ - User: float64(cpuTimes[CPUser]) / ClocksPerSec, + User: float64(cpuTimes[cpUser]) / ClocksPerSec, Nice: float64(cpuTimes[cpNice]) / ClocksPerSec, System: float64(cpuTimes[cpSys]) / ClocksPerSec, Idle: float64(cpuTimes[cpIdle]) / ClocksPerSec, From 16cc7d7d730791b5fbb90704d959a5e0e2ee2586 Mon Sep 17 00:00:00 2001 From: Omar Polo Date: Sun, 6 Feb 2022 23:47:17 +0000 Subject: [PATCH 3/6] improve sysctl parsing: use native byte order We can't use unix.Sysctl* for some sysctls, so we're on our own with converting data from C arrays. Don't assume that the byte order is little endian but do the right thing. Moreover, there's a little distinction in the sizes reported by KERN_CPTIME (long[cpustates]) and KERN_CPTIME2 (u_int64_t[cpustates]) so account for that too. --- cpu/cpu_openbsd.go | 36 ++++++++++++++++++++++-------------- 1 file changed, 22 insertions(+), 14 deletions(-) diff --git a/cpu/cpu_openbsd.go b/cpu/cpu_openbsd.go index 6906b9b20..4acc7d0db 100644 --- a/cpu/cpu_openbsd.go +++ b/cpu/cpu_openbsd.go @@ -4,18 +4,19 @@ package cpu import ( - "bytes" "context" - "encoding/binary" "fmt" "runtime" "syscall" + "unsafe" "github.com/shirou/gopsutil/v3/internal/common" "github.com/tklauser/go-sysconf" "golang.org/x/sys/unix" ) +import "C" + const ( // sys/sched.h cpUser = 0 @@ -51,13 +52,8 @@ func smtEnabled() (bool, error) { return false, err } - var ret bool - br := bytes.NewReader(buf) - if err := binary.Read(br, binary.LittleEndian, &ret); err != nil { - return false, err - } - - return ret, nil + smt := *(*uint32)(unsafe.Pointer(&buf[0])) + return smt == 1, nil } func Times(percpu bool) ([]TimesStat, error) { @@ -89,7 +85,6 @@ func TimesWithContext(ctx context.Context, percpu bool) ([]TimesStat, error) { j *= 2 } - cpuTimes := make([]int32, cpuStates) var mib []int32 if percpu { mib = []int32{ctlKern, kernCptime2, int32(j)} @@ -101,11 +96,24 @@ func TimesWithContext(ctx context.Context, percpu bool) ([]TimesStat, error) { return ret, err } - br := bytes.NewReader(buf) - err = binary.Read(br, binary.LittleEndian, &cpuTimes) - if err != nil { - return ret, err + var cpuTimes [cpuStates]uint64 + if percpu { + // could use unsafe.Slice but it's only for go1.17+ + var x []uint64 + x = (*[cpuStates]uint64)(unsafe.Pointer(&buf[0]))[:] + for i := range x { + cpuTimes[i] = x[i] + } + } else { + // KERN_CPTIME yields long[CPUSTATES] and `long' is + // platform dependent + var x []C.long + x = (*[cpuStates]C.long)(unsafe.Pointer(&buf[0]))[:] + for i := range x { + cpuTimes[i] = uint64(x[i]) + } } + c := TimesStat{ User: float64(cpuTimes[cpUser]) / ClocksPerSec, Nice: float64(cpuTimes[cpNice]) / ClocksPerSec, From 57d5711d44975a0928fa81b0018d6c7c8a8002f7 Mon Sep 17 00:00:00 2001 From: Omar Polo Date: Sun, 6 Feb 2022 23:47:17 +0000 Subject: [PATCH 4/6] refactor TimesWithContext don't make assumptions on which CPUs are online and wich aren't based on hw.smt and hw.ncpuonline. Rather, use KERN_CPUSTATS to get the CPU statistics, which includes a flag field that can tell us if that CPU is online or not. --- cpu/cpu_openbsd.go | 119 +++++++++++++++++++-------------------------- 1 file changed, 49 insertions(+), 70 deletions(-) diff --git a/cpu/cpu_openbsd.go b/cpu/cpu_openbsd.go index 4acc7d0db..2339ed7e9 100644 --- a/cpu/cpu_openbsd.go +++ b/cpu/cpu_openbsd.go @@ -7,7 +7,6 @@ import ( "context" "fmt" "runtime" - "syscall" "unsafe" "github.com/shirou/gopsutil/v3/internal/common" @@ -26,13 +25,14 @@ const ( cpIntr = 4 cpIdle = 5 cpuStates = 6 + 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 - kernCptime2 = 71 // KERN_CPTIME2 + 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) @@ -45,87 +45,66 @@ func init() { } } -func smtEnabled() (bool, error) { - mib := []int32{ctlHw, smt} - buf, _, err := common.CallSyscall(mib) - if err != nil { - return false, err - } - - smt := *(*uint32)(unsafe.Pointer(&buf[0])) - return smt == 1, nil -} - func Times(percpu bool) ([]TimesStat, error) { return TimesWithContext(context.Background(), percpu) } -func TimesWithContext(ctx context.Context, percpu bool) ([]TimesStat, error) { - var ret []TimesStat - - var ncpu int - if percpu { - ncpu, _ = Counts(true) - } else { - ncpu = 1 +func cpsToTS(cpuTimes [cpuStates]uint64, name string) TimesStat { + return TimesStat{ + CPU: name, + User: float64(cpuTimes[cpUser]) / ClocksPerSec, + Nice: float64(cpuTimes[cpNice]) / ClocksPerSec, + System: float64(cpuTimes[cpSys]) / ClocksPerSec, + Idle: float64(cpuTimes[cpIdle]) / ClocksPerSec, + Irq: float64(cpuTimes[cpIntr]) / ClocksPerSec, } +} - smt, err := smtEnabled() - if err == syscall.EOPNOTSUPP { - // if hw.smt is not applicable for this platform (e.g. i386), - // pretend it's enabled - smt = true - } else if err != nil { - return nil, err - } +func TimesWithContext(ctx context.Context, percpu bool) (ret []TimesStat, err error) { + cpuTimes := [cpuStates]uint64{} - for i := 0; i < ncpu; i++ { - j := i - if !smt { - j *= 2 + if !percpu { + mib := []int32{ctlKern, kernCpTime} + buf, _, err := common.CallSyscall(mib) + if err != nil { + return ret, err } - - var mib []int32 - if percpu { - mib = []int32{ctlKern, kernCptime2, int32(j)} - } else { - mib = []int32{ctlKern, kernCptime} + var x []C.long + // could use unsafe.Slice but it's only for go1.17+ + x = (*[cpuStates]C.long)(unsafe.Pointer(&buf[0]))[:] + for i := range x { + cpuTimes[i] = uint64(x[i]) } + c := cpsToTS(cpuTimes, "cpu-total") + return []TimesStat{c}, nil + } + + ncpu, err := unix.SysctlUint32("hw.ncpu") + if err != nil { + return + } + + 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 } - var cpuTimes [cpuStates]uint64 - if percpu { - // could use unsafe.Slice but it's only for go1.17+ - var x []uint64 - x = (*[cpuStates]uint64)(unsafe.Pointer(&buf[0]))[:] - for i := range x { - cpuTimes[i] = x[i] - } - } else { - // KERN_CPTIME yields long[CPUSTATES] and `long' is - // platform dependent - var x []C.long - x = (*[cpuStates]C.long)(unsafe.Pointer(&buf[0]))[:] - for i := range x { - cpuTimes[i] = uint64(x[i]) - } + data := unsafe.Pointer(&buf[0]) + fptr := unsafe.Pointer(uintptr(data) + uintptr(8*cpuStates)) + flags := *(*uint64)(fptr) + if (flags & cpuOnline) == 0 { + continue } - c := TimesStat{ - User: float64(cpuTimes[cpUser]) / ClocksPerSec, - Nice: float64(cpuTimes[cpNice]) / ClocksPerSec, - System: float64(cpuTimes[cpSys]) / ClocksPerSec, - Idle: float64(cpuTimes[cpIdle]) / ClocksPerSec, - Irq: float64(cpuTimes[cpIntr]) / ClocksPerSec, - } - if percpu { - c.CPU = fmt.Sprintf("cpu%d", j) - } else { - c.CPU = "cpu-total" + var x []uint64 + x = (*[cpuStates]uint64)(data)[:] + for i := range x { + cpuTimes[i] = x[i] } + c := cpsToTS(cpuTimes, fmt.Sprintf("cpu%d", i)) ret = append(ret, c) } From 3c3c017f235f4d88b55fee9bfbfc92aff738aab8 Mon Sep 17 00:00:00 2001 From: Omar Polo Date: Tue, 8 Feb 2022 11:41:54 +0000 Subject: [PATCH 5/6] avoid copying kernCPUStats --- cpu/cpu_openbsd.go | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/cpu/cpu_openbsd.go b/cpu/cpu_openbsd.go index 2339ed7e9..60d02b8ae 100644 --- a/cpu/cpu_openbsd.go +++ b/cpu/cpu_openbsd.go @@ -49,7 +49,7 @@ func Times(percpu bool) ([]TimesStat, error) { return TimesWithContext(context.Background(), percpu) } -func cpsToTS(cpuTimes [cpuStates]uint64, name string) TimesStat { +func cpsToTS(cpuTimes []uint64, name string) TimesStat { return TimesStat{ CPU: name, User: float64(cpuTimes[cpUser]) / ClocksPerSec, @@ -61,8 +61,6 @@ func cpsToTS(cpuTimes [cpuStates]uint64, name string) TimesStat { } func TimesWithContext(ctx context.Context, percpu bool) (ret []TimesStat, err error) { - cpuTimes := [cpuStates]uint64{} - if !percpu { mib := []int32{ctlKern, kernCpTime} buf, _, err := common.CallSyscall(mib) @@ -72,10 +70,11 @@ func TimesWithContext(ctx context.Context, percpu bool) (ret []TimesStat, err er var x []C.long // could use unsafe.Slice but it's only for go1.17+ x = (*[cpuStates]C.long)(unsafe.Pointer(&buf[0]))[:] + cpuTimes := [cpuStates]uint64{} for i := range x { cpuTimes[i] = uint64(x[i]) } - c := cpsToTS(cpuTimes, "cpu-total") + c := cpsToTS(cpuTimes[:], "cpu-total") return []TimesStat{c}, nil } @@ -101,10 +100,7 @@ func TimesWithContext(ctx context.Context, percpu bool) (ret []TimesStat, err er var x []uint64 x = (*[cpuStates]uint64)(data)[:] - for i := range x { - cpuTimes[i] = x[i] - } - c := cpsToTS(cpuTimes, fmt.Sprintf("cpu%d", i)) + c := cpsToTS(x, fmt.Sprintf("cpu%d", i)) ret = append(ret, c) } From 73f9c8dfd5862a54baeb00c3221dc6bc44fce3fd Mon Sep 17 00:00:00 2001 From: Omar Polo Date: Thu, 24 Feb 2022 14:59:20 +0100 Subject: [PATCH 6/6] avoid cgo for cpu_openbsd Even thought OpenBSD often breaks the ABI compatibility and doesn't make *any* promise of "stability", this project aims to be "pure go" so avoid doing inter-op at the cost of artificially reducing the number of supported architectures down to amd64 and i386. To add support for another architecture (e.g. arm), add another file cpu_openbsd_${arch}.go like done for 386 and amd64. The fields are declared as `long' in C, so pick the appropriate size when declaring the struct. --- cpu/cpu_openbsd.go | 69 +++++++++++++++++++--------------------- cpu/cpu_openbsd_386.go | 10 ++++++ cpu/cpu_openbsd_amd64.go | 10 ++++++ 3 files changed, 52 insertions(+), 37 deletions(-) create mode 100644 cpu/cpu_openbsd_386.go create mode 100644 cpu/cpu_openbsd_amd64.go diff --git a/cpu/cpu_openbsd.go b/cpu/cpu_openbsd.go index 60d02b8ae..fe3329030 100644 --- a/cpu/cpu_openbsd.go +++ b/cpu/cpu_openbsd.go @@ -14,17 +14,8 @@ import ( "golang.org/x/sys/unix" ) -import "C" - const ( // sys/sched.h - cpUser = 0 - cpNice = 1 - cpSys = 2 - cpSpin = 3 - cpIntr = 4 - cpIdle = 5 - cpuStates = 6 cpuOnline = 0x0001 // CPUSTATS_ONLINE // sys/sysctl.h @@ -37,6 +28,19 @@ const ( 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 @@ -49,17 +53,6 @@ func Times(percpu bool) ([]TimesStat, error) { return TimesWithContext(context.Background(), percpu) } -func cpsToTS(cpuTimes []uint64, name string) TimesStat { - return TimesStat{ - CPU: name, - User: float64(cpuTimes[cpUser]) / ClocksPerSec, - Nice: float64(cpuTimes[cpNice]) / ClocksPerSec, - System: float64(cpuTimes[cpSys]) / ClocksPerSec, - Idle: float64(cpuTimes[cpIdle]) / ClocksPerSec, - Irq: float64(cpuTimes[cpIntr]) / ClocksPerSec, - } -} - func TimesWithContext(ctx context.Context, percpu bool) (ret []TimesStat, err error) { if !percpu { mib := []int32{ctlKern, kernCpTime} @@ -67,15 +60,16 @@ func TimesWithContext(ctx context.Context, percpu bool) (ret []TimesStat, err er if err != nil { return ret, err } - var x []C.long - // could use unsafe.Slice but it's only for go1.17+ - x = (*[cpuStates]C.long)(unsafe.Pointer(&buf[0]))[:] - cpuTimes := [cpuStates]uint64{} - for i := range x { - cpuTimes[i] = uint64(x[i]) + times := (*cpuTimes)(unsafe.Pointer(&buf[0])) + stat := 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, } - c := cpsToTS(cpuTimes[:], "cpu-total") - return []TimesStat{c}, nil + return []TimesStat{stat}, nil } ncpu, err := unix.SysctlUint32("hw.ncpu") @@ -91,17 +85,18 @@ func TimesWithContext(ctx context.Context, percpu bool) (ret []TimesStat, err er return ret, err } - data := unsafe.Pointer(&buf[0]) - fptr := unsafe.Pointer(uintptr(data) + uintptr(8*cpuStates)) - flags := *(*uint64)(fptr) - if (flags & cpuOnline) == 0 { + stats := (*cpuStats)(unsafe.Pointer(&buf[0])) + if (stats.Flags & cpuOnline) == 0 { continue } - - var x []uint64 - x = (*[cpuStates]uint64)(data)[:] - c := cpsToTS(x, fmt.Sprintf("cpu%d", i)) - ret = append(ret, c) + 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 diff --git a/cpu/cpu_openbsd_386.go b/cpu/cpu_openbsd_386.go new file mode 100644 index 000000000..5e878399a --- /dev/null +++ b/cpu/cpu_openbsd_386.go @@ -0,0 +1,10 @@ +package cpu + +type cpuTimes struct { + User uint32 + Nice uint32 + Sys uint32 + Spin uint32 + Intr uint32 + Idle uint32 +} diff --git a/cpu/cpu_openbsd_amd64.go b/cpu/cpu_openbsd_amd64.go new file mode 100644 index 000000000..d659058cd --- /dev/null +++ b/cpu/cpu_openbsd_amd64.go @@ -0,0 +1,10 @@ +package cpu + +type cpuTimes struct { + User uint64 + Nice uint64 + Sys uint64 + Spin uint64 + Intr uint64 + Idle uint64 +}