Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor(darwin): optimize string allocation #1768

Merged
merged 1 commit into from
Dec 31, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions cpu/cpu_darwin_arm64.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,10 +55,10 @@ func getFrequency() (float64, error) {
break
}

buf := make([]byte, 512)
ioRegistryEntryGetName(service, &buf[0])
buf := common.NewCStr(512)
ioRegistryEntryGetName(service, buf)

if common.GoString(&buf[0]) == "pmgr" {
if buf.GoString() == "pmgr" {
pCoreRef := ioRegistryEntryCreateCFProperty(service, uintptr(pCorekey), common.KCFAllocatorDefault, common.KNilOptions)
length := cfDataGetLength(uintptr(pCoreRef))
data := cfDataGetBytePtr(uintptr(pCoreRef))
Expand Down
16 changes: 9 additions & 7 deletions disk/disk_darwin.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ package disk

import (
"context"
"errors"
"fmt"
"unsafe"

Expand Down Expand Up @@ -234,17 +235,17 @@ func (i *ioCounters) getDriveStat(d uint32) (*IOCountersStat, error) {
key := i.cfStr(kIOBSDNameKey)
defer i.cfRelease(uintptr(key))
name := i.cfDictionaryGetValue(uintptr(props), uintptr(key))
length := cfStringGetLength(uintptr(name)) + 1
buf := make([]byte, length-1)
cfStringGetCString(uintptr(name), &buf[0], length, common.KCFStringEncodingUTF8)

buf := common.NewCStr(cfStringGetLength(uintptr(name)))
cfStringGetCString(uintptr(name), buf, buf.Length(), common.KCFStringEncodingUTF8)

stat, err := i.fillStat(parent)
if err != nil {
return nil, err
}

if stat != nil {
stat.Name = string(buf)
stat.Name = buf.GoString()
return stat, nil
}
return nil, nil
Expand All @@ -263,9 +264,10 @@ func (i *ioCounters) fillStat(d uint32) (*IOCountersStat, error) {

key := i.cfStr(kIOBlockStorageDriverStatisticsKey)
defer i.cfRelease(uintptr(key))

v := i.cfDictionaryGetValue(uintptr(props), uintptr(key))
if v == nil {
return nil, fmt.Errorf("CFDictionaryGetValue failed")
return nil, errors.New("CFDictionaryGetValue failed")
}

var stat IOCountersStat
Expand All @@ -280,10 +282,10 @@ func (i *ioCounters) fillStat(d uint32) (*IOCountersStat, error) {

for key, off := range statstab {
s := i.cfStr(key)
defer i.cfRelease(uintptr(s))
if num := i.cfDictionaryGetValue(uintptr(v), uintptr(s)); num != nil {
i.cfNumberGetValue(uintptr(num), common.KCFNumberSInt64Type, uintptr(unsafe.Pointer(uintptr(unsafe.Pointer(&stat))+off)))
i.cfNumberGetValue(uintptr(num), common.KCFNumberSInt64Type, uintptr(unsafe.Add(unsafe.Pointer(&stat), off)))
}
i.cfRelease(uintptr(s))
}

return &stat, nil
Expand Down
42 changes: 40 additions & 2 deletions internal/common/common_darwin.go
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,7 @@ type (
IOServiceOpenFunc func(service, owningTask, connType uint32, connect *uint32) int
IOServiceCloseFunc func(connect uint32) int
IOIteratorNextFunc func(iterator uint32) uint32
IORegistryEntryGetNameFunc func(entry uint32, name *byte) int
IORegistryEntryGetNameFunc func(entry uint32, name CStr) int
IORegistryEntryGetParentEntryFunc func(entry uint32, plane string, parent *uint32) int
IORegistryEntryCreateCFPropertyFunc func(entry uint32, key, allocator uintptr, options uint32) unsafe.Pointer
IORegistryEntryCreateCFPropertiesFunc func(entry uint32, properties unsafe.Pointer, allocator uintptr, options uint32) int
Expand Down Expand Up @@ -191,7 +191,7 @@ type (
CFArrayGetValueAtIndexFunc func(theArray uintptr, index int32) unsafe.Pointer
CFStringCreateMutableFunc func(alloc uintptr, maxLength int32) unsafe.Pointer
CFStringGetLengthFunc func(theString uintptr) int32
CFStringGetCStringFunc func(theString uintptr, buffer *byte, bufferSize int32, encoding uint32)
CFStringGetCStringFunc func(theString uintptr, buffer CStr, bufferSize int32, encoding uint32)
CFStringCreateWithCStringFunc func(alloc uintptr, cStr string, encoding uint32) unsafe.Pointer
CFDataGetLengthFunc func(theData uintptr) int32
CFDataGetBytePtrFunc func(theData uintptr) unsafe.Pointer
Expand Down Expand Up @@ -348,6 +348,44 @@ func (s *SMC) Close() error {
return nil
}

type CStr []byte

func NewCStr(length int32) CStr {
return make(CStr, length)
}

func (s CStr) Length() int32 {
// Include null terminator to make CFStringGetCString properly functions
return int32(len(s)) + 1
}

func (s CStr) Ptr() *byte {
if len(s) < 1 {
return nil
}

return &s[0]
}

func (c CStr) Addr() uintptr {
return uintptr(unsafe.Pointer(c.Ptr()))
}

func (s CStr) GoString() string {
if s == nil {
return ""
}

var length int
for _, char := range s {
if char == '\x00' {
break
}
length++
}
return string(s[:length])
}

// https://github.com/ebitengine/purego/blob/main/internal/strings/strings.go#L26
func GoString(cStr *byte) string {
if cStr == nil {
Expand Down
18 changes: 9 additions & 9 deletions process/process_darwin.go
Original file line number Diff line number Diff line change
Expand Up @@ -310,14 +310,14 @@ func (p *Process) ExeWithContext(ctx context.Context) (string, error) {
}
defer lib.Close()

buf := make([]byte, common.PROC_PIDPATHINFO_MAXSIZE)
ret := procPidPath(p.Pid, uintptr(unsafe.Pointer(&buf[0])), common.PROC_PIDPATHINFO_MAXSIZE)
buf := common.NewCStr(common.PROC_PIDPATHINFO_MAXSIZE)
ret := procPidPath(p.Pid, buf.Addr(), common.PROC_PIDPATHINFO_MAXSIZE)

if ret <= 0 {
return "", fmt.Errorf("unknown error: proc_pidpath returned %d", ret)
}

return common.GoString(&buf[0]), nil
return buf.GoString(), nil
}

// sys/proc_info.h
Expand All @@ -339,6 +339,7 @@ func (p *Process) CwdWithContext(ctx context.Context) (string, error) {
}
defer lib.Close()

// Lock OS thread to ensure the errno does not change
runtime.LockOSThread()
defer runtime.UnlockOSThread()

Expand Down Expand Up @@ -366,6 +367,8 @@ func procArgs(pid int32) ([]byte, int, error) {
if err != nil {
return nil, 0, err
}

// The first 4 bytes indicate the number of arguments.
nargs := procargs[:4]
return procargs, int(binary.LittleEndian.Uint32(nargs)), nil
}
Expand Down Expand Up @@ -434,8 +437,7 @@ func (p *Process) NumThreadsWithContext(ctx context.Context) (int32, error) {
defer lib.Close()

var ti ProcTaskInfo
const tiSize = int32(unsafe.Sizeof(ti))
procPidInfo(p.Pid, common.PROC_PIDTASKINFO, 0, uintptr(unsafe.Pointer(&ti)), tiSize)
procPidInfo(p.Pid, common.PROC_PIDTASKINFO, 0, uintptr(unsafe.Pointer(&ti)), int32(unsafe.Sizeof(ti)))

return int32(ti.Threadnum), nil
}
Expand All @@ -448,8 +450,7 @@ func (p *Process) TimesWithContext(ctx context.Context) (*cpu.TimesStat, error)
defer lib.Close()

var ti ProcTaskInfo
const tiSize = int32(unsafe.Sizeof(ti))
procPidInfo(p.Pid, common.PROC_PIDTASKINFO, 0, uintptr(unsafe.Pointer(&ti)), tiSize)
procPidInfo(p.Pid, common.PROC_PIDTASKINFO, 0, uintptr(unsafe.Pointer(&ti)), int32(unsafe.Sizeof(ti)))

timescaleToNanoSeconds := getTimeScaleToNanoSeconds()
ret := &cpu.TimesStat{
Expand All @@ -468,8 +469,7 @@ func (p *Process) MemoryInfoWithContext(ctx context.Context) (*MemoryInfoStat, e
defer lib.Close()

var ti ProcTaskInfo
const tiSize = int32(unsafe.Sizeof(ti))
procPidInfo(p.Pid, common.PROC_PIDTASKINFO, 0, uintptr(unsafe.Pointer(&ti)), tiSize)
procPidInfo(p.Pid, common.PROC_PIDTASKINFO, 0, uintptr(unsafe.Pointer(&ti)), int32(unsafe.Sizeof(ti)))

ret := &MemoryInfoStat{
RSS: uint64(ti.Resident_size),
Expand Down
2 changes: 1 addition & 1 deletion sensors/sensors_darwin.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ func TemperaturesWithContext(ctx context.Context) ([]TemperatureStat, error) {
}
defer smc.Close()

var temperatures []TemperatureStat
temperatures := make([]TemperatureStat, 0, len(temperatureKeys))
for _, key := range temperatureKeys {
temperatures = append(temperatures, TemperatureStat{
SensorKey: key,
Expand Down
22 changes: 11 additions & 11 deletions sensors/sensors_darwin_arm64.go
Original file line number Diff line number Diff line change
Expand Up @@ -94,8 +94,6 @@ func (ta *temperatureArm) getProductNames(system unsafe.Pointer) []string {
cfStringGetLength := common.GetFunc[common.CFStringGetLengthFunc](ta.cf, common.CFStringGetLengthSym)
cfStringGetCString := common.GetFunc[common.CFStringGetCStringFunc](ta.cf, common.CFStringGetCStringSym)

var names []string

ta.ioHIDEventSystemClientSetMatching(uintptr(system), uintptr(ta.sensors))
matchingsrvs := ta.ioHIDEventSystemClientCopyServices(uintptr(system))

Expand All @@ -110,18 +108,19 @@ func (ta *temperatureArm) getProductNames(system unsafe.Pointer) []string {
str := ta.cfStr("Product")
defer ta.cfRelease(uintptr(str))

names := make([]string, 0, count)
for i = 0; i < count; i++ {
sc := ta.cfArrayGetValueAtIndex(uintptr(matchingsrvs), i)
name := ioHIDServiceClientCopyProperty(uintptr(sc), uintptr(str))

if name != nil {
length := cfStringGetLength(uintptr(name)) + 1 // include null terminator
buf := make([]byte, length) // allocate buffer with full length
cfStringGetCString(uintptr(name), &buf[0], length, common.KCFStringEncodingUTF8)
buf := common.NewCStr(cfStringGetLength(uintptr(name)))
cfStringGetCString(uintptr(name), buf, buf.Length(), common.KCFStringEncodingUTF8)

names = append(names, string(buf[:length-1])) // remove null terminator
names = append(names, buf.GoString())
ta.cfRelease(uintptr(name))
} else {
// make sure the number of names and values are consistent
names = append(names, "noname")
}
}
Expand Down Expand Up @@ -166,11 +165,17 @@ func (ta *temperatureArm) matching(page, usage int) {
cfDictionaryCreate := common.GetFunc[common.CFDictionaryCreateFunc](ta.cf, common.CFDictionaryCreateSym)

pageNum := cfNumberCreate(common.KCFAllocatorDefault, common.KCFNumberIntType, uintptr(unsafe.Pointer(&page)))
defer ta.cfRelease(uintptr(pageNum))

usageNum := cfNumberCreate(common.KCFAllocatorDefault, common.KCFNumberIntType, uintptr(unsafe.Pointer(&usage)))
defer ta.cfRelease(uintptr(usageNum))

k1 := ta.cfStr("PrimaryUsagePage")
k2 := ta.cfStr("PrimaryUsage")

defer ta.cfRelease(uintptr(k1))
defer ta.cfRelease(uintptr(k2))

keys := []unsafe.Pointer{k1, k2}
values := []unsafe.Pointer{pageNum, usageNum}

Expand All @@ -180,11 +185,6 @@ func (ta *temperatureArm) matching(page, usage int) {
ta.sensors = cfDictionaryCreate(common.KCFAllocatorDefault, &keys[0], &values[0], 2,
kCFTypeDictionaryKeyCallBacks,
kCFTypeDictionaryValueCallBacks)

ta.cfRelease(uintptr(pageNum))
ta.cfRelease(uintptr(usageNum))
ta.cfRelease(uintptr(k1))
ta.cfRelease(uintptr(k2))
}

func (ta *temperatureArm) cfStr(str string) unsafe.Pointer {
Expand Down
25 changes: 25 additions & 0 deletions sensors/sensors_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,12 @@
package sensors

import (
"errors"
"fmt"
"os"
"testing"

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

func TestTemperatureStat_String(t *testing.T) {
Expand All @@ -19,3 +23,24 @@ func TestTemperatureStat_String(t *testing.T) {
t.Errorf("TemperatureStat string is invalid, %v", fmt.Sprintf("%v", v))
}
}

func skipIfNotImplementedErr(t *testing.T, err error) {
if errors.Is(err, common.ErrNotImplementedError) {
t.Skip("not implemented")
}
}

func TestTemperatures(t *testing.T) {
if os.Getenv("CI") != "" {
t.Skip("Skip CI")
}
v, err := SensorsTemperatures()
skipIfNotImplementedErr(t, err)
if err != nil {
t.Errorf("error %v", err)
}
if len(v) == 0 {
t.Errorf("Could not get temperature %v", v)
}
t.Log(v)
}
Loading