From 701a74be41d2830146f10c3cff1c93a4acff19a2 Mon Sep 17 00:00:00 2001 From: uubulb Date: Wed, 4 Sep 2024 22:19:05 +0800 Subject: [PATCH 1/3] feat(cpu, mem, sensors)(darwin): cgo-free implementations --- cpu/cpu_darwin.go | 92 ++++++++++++- cpu/cpu_darwin_cgo.go | 111 ---------------- cpu/cpu_darwin_nocgo.go | 14 -- go.mod | 1 + go.sum | 2 + internal/common/common_darwin.go | 217 +++++++++++++++++++++++++++++++ mem/mem_darwin.go | 58 +++++++++ mem/mem_darwin_cgo.go | 58 --------- mem/mem_darwin_nocgo.go | 89 ------------- sensors/darwin_arm_sensors.h | 110 ---------------- sensors/sensors_darwin.go | 181 ++++++++++++++++++++++++++ sensors/sensors_darwin_arm64.go | 186 ++++++++++++++++++++++++++ sensors/sensors_darwin_cgo.go | 95 -------------- sensors/sensors_darwin_nocgo.go | 14 -- sensors/smc_darwin.c | 170 ------------------------ sensors/smc_darwin.h | 33 ----- 16 files changed, 735 insertions(+), 696 deletions(-) delete mode 100644 cpu/cpu_darwin_cgo.go delete mode 100644 cpu/cpu_darwin_nocgo.go delete mode 100644 mem/mem_darwin_cgo.go delete mode 100644 mem/mem_darwin_nocgo.go delete mode 100644 sensors/darwin_arm_sensors.h create mode 100644 sensors/sensors_darwin.go create mode 100644 sensors/sensors_darwin_arm64.go delete mode 100644 sensors/sensors_darwin_cgo.go delete mode 100644 sensors/sensors_darwin_nocgo.go delete mode 100644 sensors/smc_darwin.c delete mode 100644 sensors/smc_darwin.h diff --git a/cpu/cpu_darwin.go b/cpu/cpu_darwin.go index 79a458b8e..29d9a71be 100644 --- a/cpu/cpu_darwin.go +++ b/cpu/cpu_darwin.go @@ -5,12 +5,16 @@ package cpu import ( "context" + "fmt" "strconv" "strings" + "unsafe" "github.com/shoenig/go-m1cpu" "github.com/tklauser/go-sysconf" "golang.org/x/sys/unix" + + "github.com/shirou/gopsutil/v4/internal/common" ) // sys/resource.h @@ -23,6 +27,24 @@ const ( cpUStates = 5 ) +// mach/machine.h +const ( + cpuStateUser = 0 + cpuStateSystem = 1 + cpuStateIdle = 2 + cpuStateNice = 3 + cpuStateMax = 4 +) + +// mach/processor_info.h +const ( + processorCpuLoadInfo = 2 +) + +type hostCpuLoadInfoData struct { + cpuTicks [cpuStateMax]uint32 +} + // default value. from time.h var ClocksPerSec = float64(128) @@ -39,11 +61,17 @@ func Times(percpu bool) ([]TimesStat, error) { } func TimesWithContext(ctx context.Context, percpu bool) ([]TimesStat, error) { + lib, err := common.NewLibrary(common.Kernel) + if err != nil { + return nil, err + } + defer lib.Close() + if percpu { - return perCPUTimes() + return perCPUTimes(lib) } - return allCPUTimes() + return allCPUTimes(lib) } // Returns only one CPUInfoStat on FreeBSD @@ -115,3 +143,63 @@ func CountsWithContext(ctx context.Context, logical bool) (int, error) { return int(count), nil } + +func perCPUTimes(machLib *common.Library) ([]TimesStat, error) { + machHostSelf := common.GetFunc[common.MachHostSelfFunc](machLib, common.MachHostSelfSym) + machTaskSelf := common.GetFunc[common.MachTaskSelfFunc](machLib, common.MachTaskSelfSym) + hostProcessorInfo := common.GetFunc[common.HostProcessorInfoFunc](machLib, common.HostProcessorInfoSym) + vmDeallocate := common.GetFunc[common.VMDeallocateFunc](machLib, common.VMDeallocateSym) + + var count, ncpu uint32 + var cpuload *hostCpuLoadInfoData + + status := hostProcessorInfo(machHostSelf(), processorCpuLoadInfo, &ncpu, uintptr(unsafe.Pointer(&cpuload)), &count) + + if status != common.KERN_SUCCESS { + return nil, fmt.Errorf("host_processor_info error=%d", status) + } + + defer vmDeallocate(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(machLib *common.Library) ([]TimesStat, error) { + machHostSelf := common.GetFunc[common.MachHostSelfFunc](machLib, common.MachHostSelfSym) + hostStatistics := common.GetFunc[common.HostStatisticsFunc](machLib, common.HostStatisticsSym) + + var cpuload hostCpuLoadInfoData + count := uint32(cpuStateMax) + + status := hostStatistics(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 +} diff --git a/cpu/cpu_darwin_cgo.go b/cpu/cpu_darwin_cgo.go deleted file mode 100644 index 3a02024c5..000000000 --- a/cpu/cpu_darwin_cgo.go +++ /dev/null @@ -1,111 +0,0 @@ -// SPDX-License-Identifier: BSD-3-Clause -//go:build darwin && cgo - -package cpu - -/* -#include -#include -#include -#include -#include -#include -#include -#if TARGET_OS_MAC -#include -#endif -#include -#include -*/ -import "C" - -import ( - "bytes" - "encoding/binary" - "fmt" - "unsafe" -) - -// these CPU times for darwin is borrowed from influxdb/telegraf. - -func perCPUTimes() ([]TimesStat, error) { - var ( - count C.mach_msg_type_number_t - cpuload *C.processor_cpu_load_info_data_t - ncpu C.natural_t - ) - - status := C.host_processor_info(C.host_t(C.mach_host_self()), - C.PROCESSOR_CPU_LOAD_INFO, - &ncpu, - (*C.processor_info_array_t)(unsafe.Pointer(&cpuload)), - &count) - - if status != C.KERN_SUCCESS { - return nil, fmt.Errorf("host_processor_info error=%d", status) - } - - // jump through some cgo casting hoops and ensure we properly free - // the memory that cpuload points to - target := C.vm_map_t(C.mach_task_self_) - address := C.vm_address_t(uintptr(unsafe.Pointer(cpuload))) - defer C.vm_deallocate(target, address, C.vm_size_t(ncpu)) - - // the body of struct processor_cpu_load_info - // aka processor_cpu_load_info_data_t - var cpu_ticks [C.CPU_STATE_MAX]uint32 - - // copy the cpuload array to a []byte buffer - // where we can binary.Read the data - size := int(ncpu) * binary.Size(cpu_ticks) - buf := (*[1 << 30]byte)(unsafe.Pointer(cpuload))[:size:size] - - bbuf := bytes.NewBuffer(buf) - - var ret []TimesStat - - for i := 0; i < int(ncpu); i++ { - err := binary.Read(bbuf, binary.LittleEndian, &cpu_ticks) - if err != nil { - return nil, err - } - - c := TimesStat{ - CPU: fmt.Sprintf("cpu%d", i), - User: float64(cpu_ticks[C.CPU_STATE_USER]) / ClocksPerSec, - System: float64(cpu_ticks[C.CPU_STATE_SYSTEM]) / ClocksPerSec, - Nice: float64(cpu_ticks[C.CPU_STATE_NICE]) / ClocksPerSec, - Idle: float64(cpu_ticks[C.CPU_STATE_IDLE]) / ClocksPerSec, - } - - ret = append(ret, c) - } - - return ret, nil -} - -func allCPUTimes() ([]TimesStat, error) { - var count C.mach_msg_type_number_t - var cpuload C.host_cpu_load_info_data_t - - count = C.HOST_CPU_LOAD_INFO_COUNT - - status := C.host_statistics(C.host_t(C.mach_host_self()), - C.HOST_CPU_LOAD_INFO, - C.host_info_t(unsafe.Pointer(&cpuload)), - &count) - - if status != C.KERN_SUCCESS { - return nil, fmt.Errorf("host_statistics error=%d", status) - } - - c := TimesStat{ - CPU: "cpu-total", - User: float64(cpuload.cpu_ticks[C.CPU_STATE_USER]) / ClocksPerSec, - System: float64(cpuload.cpu_ticks[C.CPU_STATE_SYSTEM]) / ClocksPerSec, - Nice: float64(cpuload.cpu_ticks[C.CPU_STATE_NICE]) / ClocksPerSec, - Idle: float64(cpuload.cpu_ticks[C.CPU_STATE_IDLE]) / ClocksPerSec, - } - - return []TimesStat{c}, nil -} diff --git a/cpu/cpu_darwin_nocgo.go b/cpu/cpu_darwin_nocgo.go deleted file mode 100644 index 1af8566a6..000000000 --- a/cpu/cpu_darwin_nocgo.go +++ /dev/null @@ -1,14 +0,0 @@ -// SPDX-License-Identifier: BSD-3-Clause -//go:build darwin && !cgo - -package cpu - -import "github.com/shirou/gopsutil/v4/internal/common" - -func perCPUTimes() ([]TimesStat, error) { - return []TimesStat{}, common.ErrNotImplementedError -} - -func allCPUTimes() ([]TimesStat, error) { - return []TimesStat{}, common.ErrNotImplementedError -} diff --git a/go.mod b/go.mod index 4ead2443f..79ecebbf8 100644 --- a/go.mod +++ b/go.mod @@ -3,6 +3,7 @@ module github.com/shirou/gopsutil/v4 go 1.18 require ( + github.com/ebitengine/purego v0.7.1 github.com/google/go-cmp v0.6.0 github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c diff --git a/go.sum b/go.sum index 7f6b7fb4c..a2896dba0 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,7 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/ebitengine/purego v0.7.1 h1:6/55d26lG3o9VCZX8lping+bZcmShseiqlh2bnUDiPA= +github.com/ebitengine/purego v0.7.1/go.mod h1:ah1In8AOtksoNK6yk5z1HTJeUkC1Ez4Wk2idgGslMwQ= github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY= github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= diff --git a/internal/common/common_darwin.go b/internal/common/common_darwin.go index 53f9ae8d9..0a1da931b 100644 --- a/internal/common/common_darwin.go +++ b/internal/common/common_darwin.go @@ -5,11 +5,13 @@ package common import ( "context" + "fmt" "os" "os/exec" "strings" "unsafe" + "github.com/ebitengine/purego" "golang.org/x/sys/unix" ) @@ -64,3 +66,218 @@ func CallSyscall(mib []int32) ([]byte, uint64, error) { return buf, length, nil } + +// Library represents a dynamic library loaded by purego. +type Library struct { + addr uintptr + path string + close func() +} + +// library paths +const ( + IOKit = "/System/Library/Frameworks/IOKit.framework/IOKit" + CoreFoundation = "/System/Library/Frameworks/CoreFoundation.framework/CoreFoundation" + Kernel = "/usr/lib/system/libsystem_kernel.dylib" +) + +func NewLibrary(path string) (*Library, error) { + lib, err := purego.Dlopen(path, purego.RTLD_LAZY|purego.RTLD_GLOBAL) + if err != nil { + return nil, err + } + + closeFunc := func() { + purego.Dlclose(lib) + } + + return &Library{ + addr: lib, + path: path, + close: closeFunc, + }, nil +} + +func (lib *Library) Dlsym(symbol string) (uintptr, error) { + return purego.Dlsym(lib.addr, symbol) +} + +func GetFunc[T any](lib *Library, symbol string) T { + var fptr T + purego.RegisterLibFunc(&fptr, lib.addr, symbol) + return fptr +} + +func (lib *Library) Close() { + lib.close() +} + +// status codes +const ( + KERN_SUCCESS = 0 +) + +// IOKit functions and symbols. +type ( + IOServiceGetMatchingServiceFunc func(mainPort uint32, matching uintptr) uint32 + IOServiceMatchingFunc func(name string) unsafe.Pointer + IOServiceOpenFunc func(service, owningTask, connType uint32, connect *uint32) int + IOServiceCloseFunc func(connect uint32) int + IOObjectReleaseFunc func(object uint32) int + IOConnectCallStructMethodFunc func(connection, selector uint32, inputStruct, inputStructCnt, outputStruct uintptr, outputStructCnt *uintptr) int + + IOHIDEventSystemClientCreateFunc func(allocator uintptr) unsafe.Pointer + IOHIDEventSystemClientSetMatchingFunc func(client, match uintptr) int + IOHIDServiceClientCopyEventFunc func(service uintptr, eventType int64, + options int32, timeout int64) unsafe.Pointer + IOHIDServiceClientCopyPropertyFunc func(service, property uintptr) unsafe.Pointer + IOHIDEventGetFloatValueFunc func(event uintptr, field int32) float64 + IOHIDEventSystemClientCopyServicesFunc func(client uintptr) unsafe.Pointer +) + +const ( + IOServiceGetMatchingServiceSym = "IOServiceGetMatchingService" + IOServiceMatchingSym = "IOServiceMatching" + IOServiceOpenSym = "IOServiceOpen" + IOServiceCloseSym = "IOServiceClose" + IOObjectReleaseSym = "IOObjectRelease" + IOConnectCallStructMethodSym = "IOConnectCallStructMethod" + + IOHIDEventSystemClientCreateSym = "IOHIDEventSystemClientCreate" + IOHIDEventSystemClientSetMatchingSym = "IOHIDEventSystemClientSetMatching" + IOHIDServiceClientCopyEventSym = "IOHIDServiceClientCopyEvent" + IOHIDServiceClientCopyPropertySym = "IOHIDServiceClientCopyProperty" + IOHIDEventGetFloatValueSym = "IOHIDEventGetFloatValue" + IOHIDEventSystemClientCopyServicesSym = "IOHIDEventSystemClientCopyServices" +) + +const ( + KIOHIDEventTypeTemperature = 15 +) + +// CoreFoundation functions and symbols. +type ( + CFNumberCreateFunc func(allocator uintptr, theType int32, valuePtr uintptr) unsafe.Pointer + CFDictionaryCreateFunc func(allocator uintptr, keys, values *unsafe.Pointer, numValues int32, + keyCallBacks, valueCallBacks uintptr) unsafe.Pointer + CFArrayGetCountFunc func(theArray uintptr) int32 + 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) + CFStringCreateWithCStringFunc func(alloc uintptr, cStr string, encoding uint32) unsafe.Pointer + CFReleaseFunc func(cf uintptr) +) + +const ( + CFNumberCreateSym = "CFNumberCreate" + CFDictionaryCreateSym = "CFDictionaryCreate" + CFArrayGetCountSym = "CFArrayGetCount" + CFArrayGetValueAtIndexSym = "CFArrayGetValueAtIndex" + CFStringCreateMutableSym = "CFStringCreateMutable" + CFStringGetLengthSym = "CFStringGetLength" + CFStringGetCStringSym = "CFStringGetCString" + CFStringCreateWithCStringSym = "CFStringCreateWithCString" + CFReleaseSym = "CFRelease" +) + +const ( + KCFStringEncodingUTF8 = 0x08000100 + KCFNumberIntType = 9 + KCFAllocatorDefault = 0 +) + +// Kernel functions and symbols. +type ( + HostProcessorInfoFunc func(host uint32, flavor int, outProcessorCount *uint32, outProcessorInfo uintptr, + outProcessorInfoCnt *uint32) int + HostStatisticsFunc func(host uint32, flavor int, hostInfoOut uintptr, hostInfoOutCnt *uint32) int + MachHostSelfFunc func() uint32 + MachTaskSelfFunc func() uint32 + VMDeallocateFunc func(targetTask uint32, vmAddress, vmSize uintptr) int +) + +const ( + HostProcessorInfoSym = "host_processor_info" + HostStatisticsSym = "host_statistics" + MachHostSelfSym = "mach_host_self" + MachTaskSelfSym = "mach_task_self" + VMDeallocateSym = "vm_deallocate" +) + +const ( + HOST_VM_INFO = 2 + HOST_CPU_LOAD_INFO = 3 + + HOST_VM_INFO_COUNT = 0xf +) + +// SMC represents a SMC instance. +type SMC struct { + lib *Library + conn uint32 + callStruct IOConnectCallStructMethodFunc +} + +const ioServiceSMC = "AppleSMC" + +const ( + KSMCUserClientOpen = 0 + KSMCUserClientClose = 1 + KSMCHandleYPCEvent = 2 + KSMCReadKey = 5 + KSMCWriteKey = 6 + KSMCGetKeyCount = 7 + KSMCGetKeyFromIndex = 8 + KSMCGetKeyInfo = 9 +) + +const ( + KSMCSuccess = 0 + KSMCError = 1 + KSMCKeyNotFound = 132 +) + +func NewSMC(ioKit *Library) (*SMC, error) { + if ioKit.path != IOKit { + return nil, fmt.Errorf("library is not IOKit") + } + + ioServiceGetMatchingService := GetFunc[IOServiceGetMatchingServiceFunc](ioKit, IOServiceGetMatchingServiceSym) + ioServiceMatching := GetFunc[IOServiceMatchingFunc](ioKit, IOServiceMatchingSym) + ioServiceOpen := GetFunc[IOServiceOpenFunc](ioKit, IOServiceOpenSym) + ioObjectRelease := GetFunc[IOObjectReleaseFunc](ioKit, IOObjectReleaseSym) + machTaskSelf := GetFunc[MachTaskSelfFunc](ioKit, MachTaskSelfSym) + + ioConnectCallStructMethod := GetFunc[IOConnectCallStructMethodFunc](ioKit, IOConnectCallStructMethodSym) + + service := ioServiceGetMatchingService(0, uintptr(ioServiceMatching(ioServiceSMC))) + if service == 0 { + return nil, fmt.Errorf("ERROR: %s NOT FOUND", ioServiceSMC) + } + + var conn uint32 + if result := ioServiceOpen(service, machTaskSelf(), 0, &conn); result != 0 { + return nil, fmt.Errorf("ERROR: IOServiceOpen failed") + } + + ioObjectRelease(service) + return &SMC{ + lib: ioKit, + conn: conn, + callStruct: ioConnectCallStructMethod, + }, nil +} + +func (s *SMC) CallStruct(selector uint32, inputStruct, inputStructCnt, outputStruct uintptr, outputStructCnt *uintptr) int { + return s.callStruct(s.conn, selector, inputStruct, inputStructCnt, outputStruct, outputStructCnt) +} + +func (s *SMC) Close() error { + ioServiceClose := GetFunc[IOServiceCloseFunc](s.lib, IOServiceCloseSym) + + if result := ioServiceClose(s.conn); result != 0 { + return fmt.Errorf("ERROR: IOServiceClose failed") + } + return nil +} diff --git a/mem/mem_darwin.go b/mem/mem_darwin.go index a33c5f125..4442cbc11 100644 --- a/mem/mem_darwin.go +++ b/mem/mem_darwin.go @@ -70,3 +70,61 @@ func SwapDevices() ([]*SwapDevice, error) { func SwapDevicesWithContext(ctx context.Context) ([]*SwapDevice, error) { return nil, common.ErrNotImplementedError } + +type vmStatisticsData struct { + freeCount uint32 + activeCount uint32 + inactiveCount uint32 + wireCount uint32 + _ [44]byte // Not used here +} + +// VirtualMemory returns VirtualmemoryStat. +func VirtualMemory() (*VirtualMemoryStat, error) { + return VirtualMemoryWithContext(context.Background()) +} + +func VirtualMemoryWithContext(ctx context.Context) (*VirtualMemoryStat, error) { + machLib, err := common.NewLibrary(common.Kernel) + if err != nil { + return nil, err + } + defer machLib.Close() + + hostStatistics := common.GetFunc[common.HostStatisticsFunc](machLib, common.HostStatisticsSym) + machHostSelf := common.GetFunc[common.MachHostSelfFunc](machLib, common.MachHostSelfSym) + + count := uint32(common.HOST_VM_INFO_COUNT) + var vmstat vmStatisticsData + + status := hostStatistics(machHostSelf(), common.HOST_VM_INFO, + uintptr(unsafe.Pointer(&vmstat)), &count) + + if status != common.KERN_SUCCESS { + return nil, fmt.Errorf("host_statistics error=%d", status) + } + + pageSizeAddr, _ := machLib.Dlsym("vm_kernel_page_size") + pageSize := **(**uint64)(unsafe.Pointer(&pageSizeAddr)) + total, err := getHwMemsize() + if err != nil { + return nil, err + } + totalCount := uint32(total / pageSize) + + availableCount := vmstat.inactiveCount + vmstat.freeCount + usedPercent := 100 * float64(totalCount-availableCount) / float64(totalCount) + + usedCount := totalCount - availableCount + + return &VirtualMemoryStat{ + Total: total, + Available: pageSize * uint64(availableCount), + Used: pageSize * uint64(usedCount), + UsedPercent: usedPercent, + Free: pageSize * uint64(vmstat.freeCount), + Active: pageSize * uint64(vmstat.activeCount), + Inactive: pageSize * uint64(vmstat.inactiveCount), + Wired: pageSize * uint64(vmstat.wireCount), + }, nil +} diff --git a/mem/mem_darwin_cgo.go b/mem/mem_darwin_cgo.go deleted file mode 100644 index cc6657d04..000000000 --- a/mem/mem_darwin_cgo.go +++ /dev/null @@ -1,58 +0,0 @@ -// SPDX-License-Identifier: BSD-3-Clause -//go:build darwin && cgo - -package mem - -/* -#include -#include -*/ -import "C" - -import ( - "context" - "fmt" - "unsafe" -) - -// VirtualMemory returns VirtualmemoryStat. -func VirtualMemory() (*VirtualMemoryStat, error) { - return VirtualMemoryWithContext(context.Background()) -} - -func VirtualMemoryWithContext(ctx context.Context) (*VirtualMemoryStat, error) { - count := C.mach_msg_type_number_t(C.HOST_VM_INFO_COUNT) - var vmstat C.vm_statistics_data_t - - status := C.host_statistics(C.host_t(C.mach_host_self()), - C.HOST_VM_INFO, - C.host_info_t(unsafe.Pointer(&vmstat)), - &count) - - if status != C.KERN_SUCCESS { - return nil, fmt.Errorf("host_statistics error=%d", status) - } - - pageSize := uint64(C.vm_kernel_page_size) - total, err := getHwMemsize() - if err != nil { - return nil, err - } - totalCount := C.natural_t(total / pageSize) - - availableCount := vmstat.inactive_count + vmstat.free_count - usedPercent := 100 * float64(totalCount-availableCount) / float64(totalCount) - - usedCount := totalCount - availableCount - - return &VirtualMemoryStat{ - Total: total, - Available: pageSize * uint64(availableCount), - Used: pageSize * uint64(usedCount), - UsedPercent: usedPercent, - Free: pageSize * uint64(vmstat.free_count), - Active: pageSize * uint64(vmstat.active_count), - Inactive: pageSize * uint64(vmstat.inactive_count), - Wired: pageSize * uint64(vmstat.wire_count), - }, nil -} diff --git a/mem/mem_darwin_nocgo.go b/mem/mem_darwin_nocgo.go deleted file mode 100644 index 097a93e63..000000000 --- a/mem/mem_darwin_nocgo.go +++ /dev/null @@ -1,89 +0,0 @@ -// SPDX-License-Identifier: BSD-3-Clause -//go:build darwin && !cgo - -package mem - -import ( - "context" - "strconv" - "strings" - - "golang.org/x/sys/unix" -) - -// Runs vm_stat and returns Free and inactive pages -func getVMStat(vms *VirtualMemoryStat) error { - out, err := invoke.Command("vm_stat") - if err != nil { - return err - } - return parseVMStat(string(out), vms) -} - -func parseVMStat(out string, vms *VirtualMemoryStat) error { - var err error - - lines := strings.Split(out, "\n") - pagesize := uint64(unix.Getpagesize()) - for _, line := range lines { - fields := strings.Split(line, ":") - if len(fields) < 2 { - continue - } - key := strings.TrimSpace(fields[0]) - value := strings.Trim(fields[1], " .") - switch key { - case "Pages free": - free, e := strconv.ParseUint(value, 10, 64) - if e != nil { - err = e - } - vms.Free = free * pagesize - case "Pages inactive": - inactive, e := strconv.ParseUint(value, 10, 64) - if e != nil { - err = e - } - vms.Inactive = inactive * pagesize - case "Pages active": - active, e := strconv.ParseUint(value, 10, 64) - if e != nil { - err = e - } - vms.Active = active * pagesize - case "Pages wired down": - wired, e := strconv.ParseUint(value, 10, 64) - if e != nil { - err = e - } - vms.Wired = wired * pagesize - } - } - return err -} - -// VirtualMemory returns VirtualmemoryStat. -func VirtualMemory() (*VirtualMemoryStat, error) { - return VirtualMemoryWithContext(context.Background()) -} - -func VirtualMemoryWithContext(ctx context.Context) (*VirtualMemoryStat, error) { - ret := &VirtualMemoryStat{} - - total, err := getHwMemsize() - if err != nil { - return nil, err - } - err = getVMStat(ret) - if err != nil { - return nil, err - } - - ret.Available = ret.Free + ret.Inactive - ret.Total = total - - ret.Used = ret.Total - ret.Available - ret.UsedPercent = 100 * float64(ret.Used) / float64(ret.Total) - - return ret, nil -} diff --git a/sensors/darwin_arm_sensors.h b/sensors/darwin_arm_sensors.h deleted file mode 100644 index b1d2ebb81..000000000 --- a/sensors/darwin_arm_sensors.h +++ /dev/null @@ -1,110 +0,0 @@ -// SPDX-FileCopyrightText: Copyright (c) 2016-2018, "freedom" Koan-Sin Tan -// SPDX-License-Identifier: BSD-3-Clause -// https://github.com/freedomtan/sensors/blob/master/sensors/sensors.m -#import -#import -#include - -typedef struct __IOHIDEvent *IOHIDEventRef; -typedef struct __IOHIDServiceClient *IOHIDServiceClientRef; -typedef double IOHIDFloat; - -IOHIDEventSystemClientRef IOHIDEventSystemClientCreate(CFAllocatorRef allocator); - -int IOHIDEventSystemClientSetMatching(IOHIDEventSystemClientRef client, CFDictionaryRef match); - -IOHIDEventRef IOHIDServiceClientCopyEvent(IOHIDServiceClientRef, int64_t, int32_t, int64_t); - -CFStringRef IOHIDServiceClientCopyProperty(IOHIDServiceClientRef service, CFStringRef property); - -IOHIDFloat IOHIDEventGetFloatValue(IOHIDEventRef event, int32_t field); - -NSDictionary *matching(int page, int usage) { - NSDictionary *dict = @{ - @"PrimaryUsagePage" : [NSNumber numberWithInt:page], - @"PrimaryUsage" : [NSNumber numberWithInt:usage], - }; - - return dict; -} - -NSArray *getProductNames(NSDictionary *sensors) { - IOHIDEventSystemClientRef system = IOHIDEventSystemClientCreate(kCFAllocatorDefault); - - IOHIDEventSystemClientSetMatching(system, (__bridge CFDictionaryRef)sensors); - NSArray *matchingsrvs = (__bridge NSArray *)IOHIDEventSystemClientCopyServices(system); - - long count = [matchingsrvs count]; - NSMutableArray *array = [[NSMutableArray alloc] init]; - - for (int i = 0; i < count; i++) { - IOHIDServiceClientRef sc = (IOHIDServiceClientRef)matchingsrvs[i]; - NSString *name = (NSString *)IOHIDServiceClientCopyProperty(sc, (__bridge CFStringRef)@"Product"); - - if (name) { - [array addObject:name]; - } else { - [array addObject:@"noname"]; - } - } - - return array; -} - -#define IOHIDEventFieldBase(type) (type << 16) -#define kIOHIDEventTypeTemperature 15 -#define kIOHIDEventTypePower 25 - -NSArray *getThermalValues(NSDictionary *sensors) { - IOHIDEventSystemClientRef system = IOHIDEventSystemClientCreate(kCFAllocatorDefault); - - IOHIDEventSystemClientSetMatching(system, (__bridge CFDictionaryRef)sensors); - NSArray *matchingsrvs = (__bridge NSArray *)IOHIDEventSystemClientCopyServices(system); - - long count = [matchingsrvs count]; - NSMutableArray *array = [[NSMutableArray alloc] init]; - - for (int i = 0; i < count; i++) { - IOHIDServiceClientRef sc = (IOHIDServiceClientRef)matchingsrvs[i]; - IOHIDEventRef event = IOHIDServiceClientCopyEvent(sc, kIOHIDEventTypeTemperature, 0, 0); - - NSNumber *value; - double temp = 0.0; - - if (event != 0) { - temp = IOHIDEventGetFloatValue(event, IOHIDEventFieldBase(kIOHIDEventTypeTemperature)); - } - - value = [NSNumber numberWithDouble:temp]; - [array addObject:value]; - } - - return array; -} - -NSString *dumpNamesValues(NSArray *kvsN, NSArray *kvsV) { - NSMutableString *valueString = [[NSMutableString alloc] init]; - int count = [kvsN count]; - - for (int i = 0; i < count; i++) { - NSString *output = [NSString stringWithFormat:@"%s:%lf\n", [kvsN[i] UTF8String], [kvsV[i] doubleValue]]; - [valueString appendString:output]; - } - - return valueString; -} - -char *getThermals() { - NSDictionary *thermalSensors = matching(0xff00, 5); - NSArray *thermalNames = getProductNames(thermalSensors); - NSArray *thermalValues = getThermalValues(thermalSensors); - NSString *result = dumpNamesValues(thermalNames, thermalValues); - char *finalStr = strdup([result UTF8String]); - - CFRelease(thermalSensors); - CFRelease(thermalNames); - CFRelease(thermalValues); - CFRelease(result); - - return finalStr; -} diff --git a/sensors/sensors_darwin.go b/sensors/sensors_darwin.go new file mode 100644 index 000000000..b58bf96f8 --- /dev/null +++ b/sensors/sensors_darwin.go @@ -0,0 +1,181 @@ +// SPDX-License-Identifier: BSD-3-Clause +//go:build darwin && !arm64 + +package sensors + +import ( + "context" + "fmt" + "unsafe" + + "github.com/shirou/gopsutil/v4/internal/common" +) + +func TemperaturesWithContext(ctx context.Context) ([]TemperatureStat, error) { + ioKit, err := common.NewLibrary(common.IOKit) + if err != nil { + return nil, err + } + defer ioKit.Close() + + smc, err := common.NewSMC(ioKit) + if err != nil { + return nil, err + } + defer smc.Close() + + var temperatures []TemperatureStat + for _, key := range temperatureKeys { + temperatures = append(temperatures, TemperatureStat{ + SensorKey: key, + Temperature: getTemperature(smc, key), + }) + } + + return temperatures, nil +} + +var temperatureKeys = []string{ + "TA0P", // AMBIENT_AIR_0 + "TA1P", // AMBIENT_AIR_1 + "TC0D", // CPU_0_DIODE + "TC0H", // CPU_0_HEATSINK + "TC0P", // CPU_0_PROXIMITY + "TB0T", // ENCLOSURE_BASE_0 + "TB1T", // ENCLOSURE_BASE_1 + "TB2T", // ENCLOSURE_BASE_2 + "TB3T", // ENCLOSURE_BASE_3 + "TG0D", // GPU_0_DIODE + "TG0H", // GPU_0_HEATSINK + "TG0P", // GPU_0_PROXIMITY + "TH0P", // HARD_DRIVE_BAY + "TM0S", // MEMORY_SLOT_0 + "TM0P", // MEMORY_SLOTS_PROXIMITY + "TN0H", // NORTHBRIDGE + "TN0D", // NORTHBRIDGE_DIODE + "TN0P", // NORTHBRIDGE_PROXIMITY + "TI0P", // THUNDERBOLT_0 + "TI1P", // THUNDERBOLT_1 + "TW0P", // WIRELESS_MODULE +} + +type smcReturn struct { + data [32]uint8 + dataType uint32 + dataSize uint32 + kSMC uint8 +} + +type smcPLimitData struct { + version uint16 + length uint16 + cpuPLimit uint32 + gpuPLimit uint32 + memPLimit uint32 +} + +type smcKeyInfoData struct { + dataSize uint32 + dataType uint32 + dataAttributes uint8 +} + +type smcVersion struct { + major byte + minor byte + build byte + reserved byte + release uint16 +} + +type smcParamStruct struct { + key uint32 + vers smcVersion + plimitData smcPLimitData + keyInfo smcKeyInfoData + result uint8 + status uint8 + data8 uint8 + data32 uint32 + bytes [32]byte +} + +const ( + smcKeySize = 4 + dataTypeSp78 = "sp78" +) + +func getTemperature(smc *common.SMC, key string) float64 { + result, err := readSMC(smc, key) + if err != nil { + return 0.0 + } + + if result.dataSize == 2 && result.dataType == toUint32(dataTypeSp78) { + return 0.0 + } + + return float64(result.data[0]) +} + +func readSMC(smc *common.SMC, key string) (*smcReturn, error) { + input := new(smcParamStruct) + resultSmc := new(smcReturn) + + input.key = toUint32(key) + input.data8 = common.KSMCGetKeyInfo + + result, err := callSMC(smc, input) + resultSmc.kSMC = result.result + + if err != nil || result.result != common.KSMCSuccess { + return resultSmc, fmt.Errorf("ERROR: IOConnectCallStructMethod failed") + } + + resultSmc.dataSize = uint32(result.keyInfo.dataSize) + resultSmc.dataType = uint32(result.keyInfo.dataSize) + + input.keyInfo.dataSize = result.keyInfo.dataSize + input.data8 = common.KSMCReadKey + + result, err = callSMC(smc, input) + resultSmc.kSMC = result.result + + if err != nil || result.result != common.KSMCSuccess { + return resultSmc, err + } + + resultSmc.data = result.bytes + return resultSmc, nil +} + +func callSMC(smc *common.SMC, input *smcParamStruct) (*smcParamStruct, error) { + output := new(smcParamStruct) + inputCnt := unsafe.Sizeof(*input) + outputCnt := unsafe.Sizeof(*output) + + result := smc.CallStruct(common.KSMCHandleYPCEvent, + uintptr(unsafe.Pointer(input)), inputCnt, uintptr(unsafe.Pointer(output)), &outputCnt) + + if result != 0 { + return output, fmt.Errorf("ERROR: IOConnectCallStructMethod failed") + } + + return output, nil +} + +func toUint32(key string) uint32 { + if len(key) != smcKeySize { + return 0 + } + + var ans uint32 = 0 + var shift uint32 = 24 + + for i := 0; i < smcKeySize; i++ { + ans += uint32(key[i]) << shift + shift -= 8 + } + + return ans +} diff --git a/sensors/sensors_darwin_arm64.go b/sensors/sensors_darwin_arm64.go new file mode 100644 index 000000000..2cf5b4869 --- /dev/null +++ b/sensors/sensors_darwin_arm64.go @@ -0,0 +1,186 @@ +// SPDX-License-Identifier: BSD-3-Clause +//go:build darwin && arm64 + +package sensors + +import ( + "context" + "unsafe" + + "github.com/shirou/gopsutil/v4/internal/common" +) + +func ReadTemperaturesArm() []TemperatureStat { + temperatures, _ := TemperaturesWithContext(context.Background()) + return temperatures +} + +func TemperaturesWithContext(ctx context.Context) ([]TemperatureStat, error) { + ioKit, err := common.NewLibrary(common.IOKit) + if err != nil { + return nil, err + } + defer ioKit.Close() + + coreFoundation, err := common.NewLibrary(common.CoreFoundation) + if err != nil { + return nil, err + } + defer coreFoundation.Close() + + ta := &temperatureArm{ + ioKit: ioKit, + cf: coreFoundation, + cfRelease: common.GetFunc[common.CFReleaseFunc](coreFoundation, common.CFReleaseSym), + cfStringCreateWithCString: common.GetFunc[common.CFStringCreateWithCStringFunc](coreFoundation, common.CFStringCreateWithCStringSym), + cfArrayGetCount: common.GetFunc[common.CFArrayGetCountFunc](coreFoundation, common.CFArrayGetCountSym), + cfArrayGetValueAtIndex: common.GetFunc[common.CFArrayGetValueAtIndexFunc](coreFoundation, common.CFArrayGetValueAtIndexSym), + ioHIDEventSystemClientCreate: common.GetFunc[common.IOHIDEventSystemClientCreateFunc](ioKit, common.IOHIDEventSystemClientCreateSym), + ioHIDEventSystemClientSetMatching: common.GetFunc[common.IOHIDEventSystemClientSetMatchingFunc](ioKit, common.IOHIDEventSystemClientSetMatchingSym), + ioHIDEventSystemClientCopyServices: common.GetFunc[common.IOHIDEventSystemClientCopyServicesFunc](ioKit, common.IOHIDEventSystemClientCopyServicesSym), + } + + ta.matching(0xff00, 5) + thermalNames := ta.getProductNames() + thermalValues := ta.getThermalValues() + result := dumpNameValues(thermalNames, thermalValues) + + ta.cfRelease(uintptr(ta.sensors)) + return result, nil +} + +func dumpNameValues(kvsN []string, kvsV []float64) []TemperatureStat { + count := len(kvsN) + temperatureMap := make(map[string]TemperatureStat) + + for i := 0; i < count; i++ { + temperatureMap[kvsN[i]] = TemperatureStat{ + SensorKey: kvsN[i], + Temperature: kvsV[i], + } + } + + temperatures := make([]TemperatureStat, 0, len(temperatureMap)) + for _, stat := range temperatureMap { + temperatures = append(temperatures, stat) + } + + return temperatures +} + +type temperatureArm struct { + ioKit *common.Library + cf *common.Library + + cfRelease common.CFReleaseFunc + cfStringCreateWithCString common.CFStringCreateWithCStringFunc + cfArrayGetCount common.CFArrayGetCountFunc + cfArrayGetValueAtIndex common.CFArrayGetValueAtIndexFunc + + ioHIDEventSystemClientCreate common.IOHIDEventSystemClientCreateFunc + ioHIDEventSystemClientSetMatching common.IOHIDEventSystemClientSetMatchingFunc + ioHIDEventSystemClientCopyServices common.IOHIDEventSystemClientCopyServicesFunc + + sensors unsafe.Pointer +} + +func (ta *temperatureArm) getProductNames() []string { + ioHIDServiceClientCopyProperty := common.GetFunc[common.IOHIDServiceClientCopyPropertyFunc](ta.ioKit, common.IOHIDServiceClientCopyPropertySym) + + cfStringGetLength := common.GetFunc[common.CFStringGetLengthFunc](ta.cf, common.CFStringGetLengthSym) + cfStringGetCString := common.GetFunc[common.CFStringGetCStringFunc](ta.cf, common.CFStringGetCStringSym) + + var names []string + system := ta.ioHIDEventSystemClientCreate(common.KCFAllocatorDefault) + + ta.ioHIDEventSystemClientSetMatching(uintptr(system), uintptr(ta.sensors)) + matchingsrvs := ta.ioHIDEventSystemClientCopyServices(uintptr(system)) + + count := ta.cfArrayGetCount(uintptr(matchingsrvs)) + + var i int32 + str := ta.cfStr("Product") + 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 // null terminator + buf := make([]byte, length-1) + cfStringGetCString(uintptr(name), &buf[0], length, common.KCFStringEncodingUTF8) + + names = append(names, string(buf)) + ta.cfRelease(uintptr(name)) + } else { + names = append(names, "noname") + } + } + + ta.cfRelease(uintptr(matchingsrvs)) + ta.cfRelease(uintptr(str)) + return names +} + +func (ta *temperatureArm) getThermalValues() []float64 { + ioHIDServiceClientCopyEvent := common.GetFunc[common.IOHIDServiceClientCopyEventFunc](ta.ioKit, common.IOHIDServiceClientCopyEventSym) + ioHIDEventGetFloatValue := common.GetFunc[common.IOHIDEventGetFloatValueFunc](ta.ioKit, common.IOHIDEventGetFloatValueSym) + + system := ta.ioHIDEventSystemClientCreate(common.KCFAllocatorDefault) + + ta.ioHIDEventSystemClientSetMatching(uintptr(system), uintptr(ta.sensors)) + matchingsrvs := ta.ioHIDEventSystemClientCopyServices(uintptr(system)) + + count := ta.cfArrayGetCount(uintptr(matchingsrvs)) + + var values []float64 + var i int32 + for i = 0; i < count; i++ { + sc := ta.cfArrayGetValueAtIndex(uintptr(matchingsrvs), i) + event := ioHIDServiceClientCopyEvent(uintptr(sc), common.KIOHIDEventTypeTemperature, 0, 0) + temp := 0.0 + + if event != nil { + temp = ioHIDEventGetFloatValue(uintptr(event), ioHIDEventFieldBase(common.KIOHIDEventTypeTemperature)) + ta.cfRelease(uintptr(event)) + } + + values = append(values, temp) + } + + ta.cfRelease(uintptr(matchingsrvs)) + return values +} + +func (ta *temperatureArm) matching(page, usage int) { + cfNumberCreate := common.GetFunc[common.CFNumberCreateFunc](ta.cf, common.CFNumberCreateSym) + cfDictionaryCreate := common.GetFunc[common.CFDictionaryCreateFunc](ta.cf, common.CFDictionaryCreateSym) + + pageNum := cfNumberCreate(common.KCFAllocatorDefault, common.KCFNumberIntType, uintptr(unsafe.Pointer(&page))) + usageNum := cfNumberCreate(common.KCFAllocatorDefault, common.KCFNumberIntType, uintptr(unsafe.Pointer(&usage))) + + k1 := ta.cfStr("PrimaryUsagePage") + k2 := ta.cfStr("PrimaryUsage") + + keys := []unsafe.Pointer{k1, k2} + values := []unsafe.Pointer{pageNum, usageNum} + + kCFTypeDictionaryKeyCallBacks, _ := ta.cf.Dlsym("kCFTypeDictionaryKeyCallBacks") + kCFTypeDictionaryValueCallBacks, _ := ta.cf.Dlsym("kCFTypeDictionaryValueCallBacks") + + 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 { + return ta.cfStringCreateWithCString(common.KCFAllocatorDefault, str, common.KCFStringEncodingUTF8) +} + +func ioHIDEventFieldBase(i int32) int32 { + return i << 16 +} diff --git a/sensors/sensors_darwin_cgo.go b/sensors/sensors_darwin_cgo.go deleted file mode 100644 index aa3d29120..000000000 --- a/sensors/sensors_darwin_cgo.go +++ /dev/null @@ -1,95 +0,0 @@ -// SPDX-License-Identifier: BSD-3-Clause -//go:build darwin && cgo - -package sensors - -// #cgo CFLAGS: -x objective-c -// #cgo LDFLAGS: -framework Foundation -framework IOKit -// #include "smc_darwin.h" -// #include "darwin_arm_sensors.h" -import "C" -import ( - "bufio" - "context" - "math" - "runtime" - "strconv" - "strings" - "unsafe" -) - -func ReadTemperaturesArm() []TemperatureStat { - cStr := C.getThermals() - defer C.free(unsafe.Pointer(cStr)) - - var stats []TemperatureStat - goStr := C.GoString(cStr) - scanner := bufio.NewScanner(strings.NewReader(goStr)) - for scanner.Scan() { - split := strings.Split(scanner.Text(), ":") - if len(split) != 2 { - continue - } - - val, err := strconv.ParseFloat(split[1], 32) - if err != nil { - continue - } - - sensorKey := strings.Split(split[0], " ")[0] - - val = math.Abs(val) - - stats = append(stats, TemperatureStat{ - SensorKey: sensorKey, - Temperature: float64(val), - }) - } - - return stats -} - -func TemperaturesWithContext(ctx context.Context) ([]TemperatureStat, error) { - if runtime.GOARCH == "arm64" { - return ReadTemperaturesArm(), nil - } - - temperatureKeys := []string{ - C.AMBIENT_AIR_0, - C.AMBIENT_AIR_1, - C.CPU_0_DIODE, - C.CPU_0_HEATSINK, - C.CPU_0_PROXIMITY, - C.ENCLOSURE_BASE_0, - C.ENCLOSURE_BASE_1, - C.ENCLOSURE_BASE_2, - C.ENCLOSURE_BASE_3, - C.GPU_0_DIODE, - C.GPU_0_HEATSINK, - C.GPU_0_PROXIMITY, - C.HARD_DRIVE_BAY, - C.MEMORY_SLOT_0, - C.MEMORY_SLOTS_PROXIMITY, - C.NORTHBRIDGE, - C.NORTHBRIDGE_DIODE, - C.NORTHBRIDGE_PROXIMITY, - C.THUNDERBOLT_0, - C.THUNDERBOLT_1, - C.WIRELESS_MODULE, - } - var temperatures []TemperatureStat - - C.gopsutil_v4_open_smc() - defer C.gopsutil_v4_close_smc() - - for _, key := range temperatureKeys { - ckey := C.CString(key) - defer C.free(unsafe.Pointer(ckey)) - temperatures = append(temperatures, TemperatureStat{ - SensorKey: key, - Temperature: float64(C.gopsutil_v4_get_temperature(ckey)), - }) - } - - return temperatures, nil -} diff --git a/sensors/sensors_darwin_nocgo.go b/sensors/sensors_darwin_nocgo.go deleted file mode 100644 index 45c85069e..000000000 --- a/sensors/sensors_darwin_nocgo.go +++ /dev/null @@ -1,14 +0,0 @@ -// SPDX-License-Identifier: BSD-3-Clause -//go:build darwin && !cgo - -package sensors - -import ( - "context" - - "github.com/shirou/gopsutil/v4/internal/common" -) - -func TemperaturesWithContext(ctx context.Context) ([]TemperatureStat, error) { - return []TemperatureStat{}, common.ErrNotImplementedError -} diff --git a/sensors/smc_darwin.c b/sensors/smc_darwin.c deleted file mode 100644 index c91a90cd0..000000000 --- a/sensors/smc_darwin.c +++ /dev/null @@ -1,170 +0,0 @@ -// SPDX-License-Identifier: BSD-3-Clause -#include -#include -#include "smc_darwin.h" - -#define IOSERVICE_SMC "AppleSMC" -#define IOSERVICE_MODEL "IOPlatformExpertDevice" - -#define DATA_TYPE_SP78 "sp78" - -typedef enum { - kSMCUserClientOpen = 0, - kSMCUserClientClose = 1, - kSMCHandleYPCEvent = 2, - kSMCReadKey = 5, - kSMCWriteKey = 6, - kSMCGetKeyCount = 7, - kSMCGetKeyFromIndex = 8, - kSMCGetKeyInfo = 9, -} selector_t; - -typedef struct { - unsigned char major; - unsigned char minor; - unsigned char build; - unsigned char reserved; - unsigned short release; -} SMCVersion; - -typedef struct { - uint16_t version; - uint16_t length; - uint32_t cpuPLimit; - uint32_t gpuPLimit; - uint32_t memPLimit; -} SMCPLimitData; - -typedef struct { - IOByteCount data_size; - uint32_t data_type; - uint8_t data_attributes; -} SMCKeyInfoData; - -typedef struct { - uint32_t key; - SMCVersion vers; - SMCPLimitData p_limit_data; - SMCKeyInfoData key_info; - uint8_t result; - uint8_t status; - uint8_t data8; - uint32_t data32; - uint8_t bytes[32]; -} SMCParamStruct; - -typedef enum { - kSMCSuccess = 0, - kSMCError = 1, - kSMCKeyNotFound = 0x84, -} kSMC_t; - -typedef struct { - uint8_t data[32]; - uint32_t data_type; - uint32_t data_size; - kSMC_t kSMC; -} smc_return_t; - -static const int SMC_KEY_SIZE = 4; // number of characters in an SMC key. -static io_connect_t conn; // our connection to the SMC. - -kern_return_t gopsutil_v4_open_smc(void) { - kern_return_t result; - io_service_t service; - - service = IOServiceGetMatchingService(0, IOServiceMatching(IOSERVICE_SMC)); - if (service == 0) { - // Note: IOServiceMatching documents 0 on failure - printf("ERROR: %s NOT FOUND\n", IOSERVICE_SMC); - return kIOReturnError; - } - - result = IOServiceOpen(service, mach_task_self(), 0, &conn); - IOObjectRelease(service); - - return result; -} - -kern_return_t gopsutil_v4_close_smc(void) { return IOServiceClose(conn); } - -static uint32_t to_uint32(char *key) { - uint32_t ans = 0; - uint32_t shift = 24; - - if (strlen(key) != SMC_KEY_SIZE) { - return 0; - } - - for (int i = 0; i < SMC_KEY_SIZE; i++) { - ans += key[i] << shift; - shift -= 8; - } - - return ans; -} - -static kern_return_t call_smc(SMCParamStruct *input, SMCParamStruct *output) { - kern_return_t result; - size_t input_cnt = sizeof(SMCParamStruct); - size_t output_cnt = sizeof(SMCParamStruct); - - result = IOConnectCallStructMethod(conn, kSMCHandleYPCEvent, input, input_cnt, - output, &output_cnt); - - if (result != kIOReturnSuccess) { - result = err_get_code(result); - } - return result; -} - -static kern_return_t read_smc(char *key, smc_return_t *result_smc) { - kern_return_t result; - SMCParamStruct input; - SMCParamStruct output; - - memset(&input, 0, sizeof(SMCParamStruct)); - memset(&output, 0, sizeof(SMCParamStruct)); - memset(result_smc, 0, sizeof(smc_return_t)); - - input.key = to_uint32(key); - input.data8 = kSMCGetKeyInfo; - - result = call_smc(&input, &output); - result_smc->kSMC = output.result; - - if (result != kIOReturnSuccess || output.result != kSMCSuccess) { - return result; - } - - result_smc->data_size = output.key_info.data_size; - result_smc->data_type = output.key_info.data_type; - - input.key_info.data_size = output.key_info.data_size; - input.data8 = kSMCReadKey; - - result = call_smc(&input, &output); - result_smc->kSMC = output.result; - - if (result != kIOReturnSuccess || output.result != kSMCSuccess) { - return result; - } - - memcpy(result_smc->data, output.bytes, sizeof(output.bytes)); - - return result; -} - -double gopsutil_v4_get_temperature(char *key) { - kern_return_t result; - smc_return_t result_smc; - - result = read_smc(key, &result_smc); - - if (!(result == kIOReturnSuccess) && result_smc.data_size == 2 && - result_smc.data_type == to_uint32(DATA_TYPE_SP78)) { - return 0.0; - } - - return (double)result_smc.data[0]; -} diff --git a/sensors/smc_darwin.h b/sensors/smc_darwin.h deleted file mode 100644 index e49abb512..000000000 --- a/sensors/smc_darwin.h +++ /dev/null @@ -1,33 +0,0 @@ -// SPDX-License-Identifier: BSD-3-Clause -#ifndef __SMC_H__ -#define __SMC_H__ 1 - -#include - -#define AMBIENT_AIR_0 "TA0P" -#define AMBIENT_AIR_1 "TA1P" -#define CPU_0_DIODE "TC0D" -#define CPU_0_HEATSINK "TC0H" -#define CPU_0_PROXIMITY "TC0P" -#define ENCLOSURE_BASE_0 "TB0T" -#define ENCLOSURE_BASE_1 "TB1T" -#define ENCLOSURE_BASE_2 "TB2T" -#define ENCLOSURE_BASE_3 "TB3T" -#define GPU_0_DIODE "TG0D" -#define GPU_0_HEATSINK "TG0H" -#define GPU_0_PROXIMITY "TG0P" -#define HARD_DRIVE_BAY "TH0P" -#define MEMORY_SLOT_0 "TM0S" -#define MEMORY_SLOTS_PROXIMITY "TM0P" -#define NORTHBRIDGE "TN0H" -#define NORTHBRIDGE_DIODE "TN0D" -#define NORTHBRIDGE_PROXIMITY "TN0P" -#define THUNDERBOLT_0 "TI0P" -#define THUNDERBOLT_1 "TI1P" -#define WIRELESS_MODULE "TW0P" - -kern_return_t gopsutil_v4_open_smc(void); -kern_return_t gopsutil_v4_close_smc(void); -double gopsutil_v4_get_temperature(char *); - -#endif // __SMC_H__ From 9e6efdb991614ad075c2f8d7416c356ab6b7fe6b Mon Sep 17 00:00:00 2001 From: uubulb Date: Tue, 17 Sep 2024 17:04:23 +0800 Subject: [PATCH 2/3] update disk & cpu & process --- README.md | 2 +- common/env.go | 15 +- cpu/cpu_darwin.go | 15 +- cpu/cpu_darwin_arm64.go | 80 +++++++++ cpu/cpu_darwin_fallback.go | 13 ++ cpu/cpu_darwin_test.go | 5 +- disk/disk_darwin.go | 200 ++++++++++++++++++++++ disk/disk_darwin_cgo.go | 45 ----- disk/disk_darwin_nocgo.go | 14 -- disk/iostat_darwin.c | 131 -------------- disk/iostat_darwin.h | 34 ---- go.mod | 3 +- go.sum | 7 +- internal/common/common_darwin.go | 117 +++++++++++-- internal/common/common_unix.go | 20 --- mem/mem_darwin.go | 2 +- process/process.go | 2 +- process/process_darwin.go | 283 ++++++++++++++++++++++++------- process/process_darwin_amd64.go | 21 +++ process/process_darwin_arm64.go | 21 +++ process/process_darwin_cgo.go | 222 ------------------------ process/process_darwin_nocgo.go | 134 --------------- process/process_freebsd.go | 18 +- process/process_linux.go | 32 +++- process/process_openbsd.go | 18 +- process/types_darwin.go | 3 + 26 files changed, 721 insertions(+), 736 deletions(-) create mode 100644 cpu/cpu_darwin_arm64.go create mode 100644 cpu/cpu_darwin_fallback.go delete mode 100644 disk/disk_darwin_cgo.go delete mode 100644 disk/disk_darwin_nocgo.go delete mode 100644 disk/iostat_darwin.c delete mode 100644 disk/iostat_darwin.h delete mode 100644 process/process_darwin_cgo.go delete mode 100644 process/process_darwin_nocgo.go diff --git a/README.md b/README.md index 4ce50b6ba..675a572a1 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # gopsutil: psutil for golang -[![Test](https://github.com/shirou/gopsutil/actions/workflows/test.yml/badge.svg)](https://github.com/shirou/gopsutil/actions/workflows/test.yml) [![Coverage Status](https://coveralls.io/repos/github/shirou/gopsutil/badge.svg?branch=master)](https://coveralls.io/github/shirou/gopsutil?branch=master) [![Go Reference](https://pkg.go.dev/badge/github.com/shirou/gopsutil/v4.svg)](https://pkg.go.dev/github.com/shirou/gopsutil/v4) [![Go Documentation](https://godocs.io/github.com/shirou/gopsutil/v4?status.svg)](https://godocs.io/github.com/shirou/gopsutil/v4) [![Calendar Versioning](https://img.shields.io/badge/calver-vMAJOR.YY.MM-22bfda.svg)](https://calver.org/) +[![Test](https://github.com/shirou/gopsutil/actions/workflows/test.yml/badge.svg)](https://github.com/shirou/gopsutil/actions/workflows/test.yml) [![Go Reference](https://pkg.go.dev/badge/github.com/shirou/gopsutil/v4.svg)](https://pkg.go.dev/github.com/shirou/gopsutil/v4) [![Calendar Versioning](https://img.shields.io/badge/calver-vMAJOR.YY.MM-22bfda.svg)](https://calver.org/) This is a port of psutil (https://github.com/giampaolo/psutil). The challenge is porting all psutil functions on some architectures. diff --git a/common/env.go b/common/env.go index 4acad1fd1..47e471c40 100644 --- a/common/env.go +++ b/common/env.go @@ -12,13 +12,14 @@ type EnvKeyType string var EnvKey = EnvKeyType("env") const ( - HostProcEnvKey EnvKeyType = "HOST_PROC" - HostSysEnvKey EnvKeyType = "HOST_SYS" - HostEtcEnvKey EnvKeyType = "HOST_ETC" - HostVarEnvKey EnvKeyType = "HOST_VAR" - HostRunEnvKey EnvKeyType = "HOST_RUN" - HostDevEnvKey EnvKeyType = "HOST_DEV" - HostRootEnvKey EnvKeyType = "HOST_ROOT" + HostProcEnvKey EnvKeyType = "HOST_PROC" + HostSysEnvKey EnvKeyType = "HOST_SYS" + HostEtcEnvKey EnvKeyType = "HOST_ETC" + HostVarEnvKey EnvKeyType = "HOST_VAR" + HostRunEnvKey EnvKeyType = "HOST_RUN" + HostDevEnvKey EnvKeyType = "HOST_DEV" + HostRootEnvKey EnvKeyType = "HOST_ROOT" + HostProcMountinfo EnvKeyType = "HOST_PROC_MOUNTINFO" ) type EnvMap map[EnvKeyType]string diff --git a/cpu/cpu_darwin.go b/cpu/cpu_darwin.go index 29d9a71be..b3e3a668d 100644 --- a/cpu/cpu_darwin.go +++ b/cpu/cpu_darwin.go @@ -10,7 +10,6 @@ import ( "strings" "unsafe" - "github.com/shoenig/go-m1cpu" "github.com/tklauser/go-sysconf" "golang.org/x/sys/unix" @@ -61,7 +60,7 @@ func Times(percpu bool) ([]TimesStat, error) { } func TimesWithContext(ctx context.Context, percpu bool) ([]TimesStat, error) { - lib, err := common.NewLibrary(common.Kernel) + lib, err := common.NewLibrary(common.System) if err != nil { return nil, err } @@ -114,15 +113,9 @@ func InfoWithContext(ctx context.Context) ([]InfoStat, error) { c.CacheSize = int32(cacheSize) c.VendorID, _ = unix.Sysctl("machdep.cpu.vendor") - if m1cpu.IsAppleSilicon() { - c.Mhz = float64(m1cpu.PCoreHz() / 1_000_000) - } else { - // 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") - if err == nil { - c.Mhz = float64(cpuFrequency) / 1000000.0 - } + v, err := getFrequency() + if err == nil { + c.Mhz = v } return append(ret, c), nil diff --git a/cpu/cpu_darwin_arm64.go b/cpu/cpu_darwin_arm64.go new file mode 100644 index 000000000..503184243 --- /dev/null +++ b/cpu/cpu_darwin_arm64.go @@ -0,0 +1,80 @@ +// SPDX-License-Identifier: BSD-3-Clause +//go:build darwin && arm64 + +package cpu + +import ( + "encoding/binary" + "fmt" + "unsafe" + + "github.com/shirou/gopsutil/v4/internal/common" +) + +// https://github.com/shoenig/go-m1cpu/blob/v0.1.6/cpu.go +func getFrequency() (float64, error) { + ioKit, err := common.NewLibrary(common.IOKit) + if err != nil { + return 0, err + } + defer ioKit.Close() + + coreFoundation, err := common.NewLibrary(common.CoreFoundation) + if err != nil { + return 0, err + } + defer coreFoundation.Close() + + ioServiceMatching := common.GetFunc[common.IOServiceMatchingFunc](ioKit, common.IOServiceMatchingSym) + ioServiceGetMatchingServices := common.GetFunc[common.IOServiceGetMatchingServicesFunc](ioKit, common.IOServiceGetMatchingServicesSym) + ioIteratorNext := common.GetFunc[common.IOIteratorNextFunc](ioKit, common.IOIteratorNextSym) + ioRegistryEntryGetName := common.GetFunc[common.IORegistryEntryGetNameFunc](ioKit, common.IORegistryEntryGetNameSym) + ioRegistryEntryCreateCFProperty := common.GetFunc[common.IORegistryEntryCreateCFPropertyFunc](ioKit, common.IORegistryEntryCreateCFPropertySym) + ioObjectRelease := common.GetFunc[common.IOObjectReleaseFunc](ioKit, common.IOObjectReleaseSym) + + cfStringCreateWithCString := common.GetFunc[common.CFStringCreateWithCStringFunc](coreFoundation, common.CFStringCreateWithCStringSym) + cfDataGetLength := common.GetFunc[common.CFDataGetLengthFunc](coreFoundation, common.CFDataGetLengthSym) + cfDataGetBytePtr := common.GetFunc[common.CFDataGetBytePtrFunc](coreFoundation, common.CFDataGetBytePtrSym) + cfRelease := common.GetFunc[common.CFReleaseFunc](coreFoundation, common.CFReleaseSym) + + matching := ioServiceMatching("AppleARMIODevice") + + var iterator uint32 + if status := ioServiceGetMatchingServices(common.KIOMainPortDefault, uintptr(matching), &iterator); status != common.KERN_SUCCESS { + return 0.0, fmt.Errorf("IOServiceGetMatchingServices error=%d", status) + } + defer ioObjectRelease(iterator) + + pCorekey := cfStringCreateWithCString(common.KCFAllocatorDefault, "voltage-states5-sram", common.KCFStringEncodingUTF8) + defer cfRelease(uintptr(pCorekey)) + + var pCoreHz uint32 + for { + service := ioIteratorNext(iterator) + if !(service > 0) { + break + } + + buf := make([]byte, 512) + ioRegistryEntryGetName(service, &buf[0]) + + if common.GoString(&buf[0]) == "pmgr" { + pCoreRef := ioRegistryEntryCreateCFProperty(service, uintptr(pCorekey), common.KCFAllocatorDefault, common.KNilOptions) + length := cfDataGetLength(uintptr(pCoreRef)) + data := 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) + ioObjectRelease(service) + break + } + + ioObjectRelease(service) + } + + return float64(pCoreHz / 1_000_000), nil +} diff --git a/cpu/cpu_darwin_fallback.go b/cpu/cpu_darwin_fallback.go new file mode 100644 index 000000000..b9e52aba1 --- /dev/null +++ b/cpu/cpu_darwin_fallback.go @@ -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 +} diff --git a/cpu/cpu_darwin_test.go b/cpu/cpu_darwin_test.go index 5548cf961..20a8da62c 100644 --- a/cpu/cpu_darwin_test.go +++ b/cpu/cpu_darwin_test.go @@ -5,13 +5,12 @@ package cpu import ( "os" + "runtime" "testing" - - "github.com/shoenig/go-m1cpu" ) func TestInfo_AppleSilicon(t *testing.T) { - if !m1cpu.IsAppleSilicon() { + if runtime.GOARCH != "arm64" { t.Skip("wrong cpu type") } diff --git a/disk/disk_darwin.go b/disk/disk_darwin.go index 6ed7400e2..52afb2317 100644 --- a/disk/disk_darwin.go +++ b/disk/disk_darwin.go @@ -5,6 +5,8 @@ package disk import ( "context" + "fmt" + "unsafe" "golang.org/x/sys/unix" @@ -92,3 +94,201 @@ func SerialNumberWithContext(ctx context.Context, name string) (string, error) { func LabelWithContext(ctx context.Context, name string) (string, error) { return "", common.ErrNotImplementedError } + +func IOCountersWithContext(ctx context.Context, names ...string) (map[string]IOCountersStat, error) { + ioKit, err := common.NewLibrary(common.IOKit) + if err != nil { + return nil, err + } + defer ioKit.Close() + + coreFoundation, err := common.NewLibrary(common.CoreFoundation) + if err != nil { + return nil, err + } + defer coreFoundation.Close() + + ioServiceMatching := common.GetFunc[common.IOServiceMatchingFunc](ioKit, common.IOServiceMatchingSym) + ioServiceGetMatchingServices := common.GetFunc[common.IOServiceGetMatchingServicesFunc](ioKit, common.IOServiceGetMatchingServicesSym) + ioIteratorNext := common.GetFunc[common.IOIteratorNextFunc](ioKit, common.IOIteratorNextSym) + ioObjectRelease := common.GetFunc[common.IOObjectReleaseFunc](ioKit, common.IOObjectReleaseSym) + + cfDictionaryAddValue := common.GetFunc[common.CFDictionaryAddValueFunc](coreFoundation, common.CFDictionaryAddValueSym) + cfStringCreateWithCString := common.GetFunc[common.CFStringCreateWithCStringFunc](coreFoundation, common.CFStringCreateWithCStringSym) + cfRelease := common.GetFunc[common.CFReleaseFunc](coreFoundation, common.CFReleaseSym) + + kCFBooleanTruePtr, _ := coreFoundation.Dlsym("kCFBooleanTrue") + + match := ioServiceMatching("IOMedia") + + key := cfStringCreateWithCString(common.KCFAllocatorDefault, common.KIOMediaWholeKey, common.KCFStringEncodingUTF8) + defer cfRelease(uintptr(key)) + + var drives uint32 + kCFBooleanTrue := **(**uintptr)(unsafe.Pointer(&kCFBooleanTruePtr)) + cfDictionaryAddValue(uintptr(match), uintptr(key), kCFBooleanTrue) + if status := ioServiceGetMatchingServices(common.KIOMainPortDefault, uintptr(match), &drives); status != common.KERN_SUCCESS { + return nil, fmt.Errorf("IOServiceGetMatchingServices error=%d", status) + } + defer ioObjectRelease(drives) + + ic := &ioCounters{ + ioKit: ioKit, + coreFoundation: coreFoundation, + + ioRegistryEntryCreateCFProperties: common.GetFunc[common.IORegistryEntryCreateCFPropertiesFunc](ioKit, common.IORegistryEntryCreateCFPropertiesSym), + ioObjectRelease: ioObjectRelease, + + cfStringCreateWithCString: cfStringCreateWithCString, + cfDictionaryGetValue: common.GetFunc[common.CFDictionaryGetValueFunc](coreFoundation, common.CFDictionaryGetValueSym), + cfNumberGetValue: common.GetFunc[common.CFNumberGetValueFunc](coreFoundation, common.CFNumberGetValueSym), + cfRelease: cfRelease, + } + + stats := make([]IOCountersStat, 0, 16) + for { + d := ioIteratorNext(drives) + if !(d > 0) { + break + } + + stat, err := ic.getDriveStat(d) + if err != nil { + return nil, err + } + + if stat != nil { + stats = append(stats, *stat) + } + + ioObjectRelease(d) + } + + ret := make(map[string]IOCountersStat, 0) + for i := 0; i < len(stats); i++ { + if len(names) > 0 && !common.StringsHas(names, stats[i].Name) { + continue + } + + stats[i].ReadTime = stats[i].ReadTime / 1000 / 1000 // note: read/write time are in ns, but we want ms. + stats[i].WriteTime = stats[i].WriteTime / 1000 / 1000 + stats[i].IoTime = stats[i].ReadTime + stats[i].WriteTime + + ret[stats[i].Name] = stats[i] + } + + return ret, nil +} + +const ( + kIOBSDNameKey = "BSD Name" + kIOMediaSizeKey = "Size" + kIOMediaPreferredBlockSizeKey = "Preferred Block Size" + + kIOBlockStorageDriverStatisticsKey = "Statistics" + kIOBlockStorageDriverStatisticsBytesReadKey = "Bytes (Read)" + kIOBlockStorageDriverStatisticsBytesWrittenKey = "Bytes (Write)" + kIOBlockStorageDriverStatisticsReadsKey = "Operations (Read)" + kIOBlockStorageDriverStatisticsWritesKey = "Operations (Write)" + kIOBlockStorageDriverStatisticsTotalReadTimeKey = "Total Time (Read)" + kIOBlockStorageDriverStatisticsTotalWriteTimeKey = "Total Time (Write)" +) + +type ioCounters struct { + ioKit *common.Library + coreFoundation *common.Library + + ioRegistryEntryCreateCFProperties common.IORegistryEntryCreateCFPropertiesFunc + ioObjectRelease common.IOObjectReleaseFunc + + cfStringCreateWithCString common.CFStringCreateWithCStringFunc + cfDictionaryGetValue common.CFDictionaryGetValueFunc + cfNumberGetValue common.CFNumberGetValueFunc + cfRelease common.CFReleaseFunc +} + +func (i *ioCounters) getDriveStat(d uint32) (*IOCountersStat, error) { + ioRegistryEntryGetParentEntry := common.GetFunc[common.IORegistryEntryGetParentEntryFunc](i.ioKit, common.IORegistryEntryGetParentEntrySym) + ioObjectConformsTo := common.GetFunc[common.IOObjectConformsToFunc](i.ioKit, common.IOObjectConformsToSym) + + cfStringGetLength := common.GetFunc[common.CFStringGetLengthFunc](i.coreFoundation, common.CFStringGetLengthSym) + cfStringGetCString := common.GetFunc[common.CFStringGetCStringFunc](i.coreFoundation, common.CFStringGetCStringSym) + + var parent uint32 + if status := ioRegistryEntryGetParentEntry(d, common.KIOServicePlane, &parent); status != common.KERN_SUCCESS { + return nil, fmt.Errorf("IORegistryEntryGetParentEntry error=%d", status) + } + defer i.ioObjectRelease(parent) + + if !ioObjectConformsTo(parent, "IOBlockStorageDriver") { + //return nil, fmt.Errorf("ERROR: the object is not of the IOBlockStorageDriver class") + return nil, nil + } + + var props unsafe.Pointer + if status := i.ioRegistryEntryCreateCFProperties(d, unsafe.Pointer(&props), common.KCFAllocatorDefault, common.KNilOptions); status != common.KERN_SUCCESS { + return nil, fmt.Errorf("IORegistryEntryCreateCFProperties error=%d", status) + } + defer i.cfRelease(uintptr(props)) + + 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) + + stat, err := i.fillStat(parent) + if err != nil { + return nil, err + } + + if stat != nil { + stat.Name = string(buf) + return stat, nil + } + return nil, nil +} + +func (i *ioCounters) fillStat(d uint32) (*IOCountersStat, error) { + var props unsafe.Pointer + status := i.ioRegistryEntryCreateCFProperties(d, unsafe.Pointer(&props), common.KCFAllocatorDefault, common.KNilOptions) + if status != common.KERN_SUCCESS { + return nil, fmt.Errorf("IORegistryEntryCreateCFProperties error=%d", status) + } + if props == nil { + return nil, nil + } + defer i.cfRelease(uintptr(props)) + + 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") + } + + var stat IOCountersStat + statstab := map[string]uintptr{ + kIOBlockStorageDriverStatisticsBytesReadKey: unsafe.Offsetof(stat.ReadBytes), + kIOBlockStorageDriverStatisticsBytesWrittenKey: unsafe.Offsetof(stat.WriteBytes), + kIOBlockStorageDriverStatisticsReadsKey: unsafe.Offsetof(stat.ReadCount), + kIOBlockStorageDriverStatisticsWritesKey: unsafe.Offsetof(stat.WriteCount), + kIOBlockStorageDriverStatisticsTotalReadTimeKey: unsafe.Offsetof(stat.ReadTime), + kIOBlockStorageDriverStatisticsTotalWriteTimeKey: unsafe.Offsetof(stat.WriteTime), + } + + 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))) + } + } + + return &stat, nil +} + +func (i *ioCounters) cfStr(str string) unsafe.Pointer { + return i.cfStringCreateWithCString(common.KCFAllocatorDefault, str, common.KCFStringEncodingUTF8) +} diff --git a/disk/disk_darwin_cgo.go b/disk/disk_darwin_cgo.go deleted file mode 100644 index 3a98b61e5..000000000 --- a/disk/disk_darwin_cgo.go +++ /dev/null @@ -1,45 +0,0 @@ -// SPDX-License-Identifier: BSD-3-Clause -//go:build darwin && cgo && !ios - -package disk - -/* -#cgo LDFLAGS: -framework CoreFoundation -framework IOKit -#include -#include -#include "iostat_darwin.h" -*/ -import "C" - -import ( - "context" - - "github.com/shirou/gopsutil/v4/internal/common" -) - -func IOCountersWithContext(ctx context.Context, names ...string) (map[string]IOCountersStat, error) { - var buf [C.NDRIVE]C.DriveStats - n, err := C.gopsutil_v4_readdrivestat(&buf[0], C.int(len(buf))) - if err != nil { - return nil, err - } - ret := make(map[string]IOCountersStat, 0) - for i := 0; i < int(n); i++ { - d := IOCountersStat{ - ReadBytes: uint64(buf[i].read), - WriteBytes: uint64(buf[i].written), - ReadCount: uint64(buf[i].nread), - WriteCount: uint64(buf[i].nwrite), - ReadTime: uint64(buf[i].readtime / 1000 / 1000), // note: read/write time are in ns, but we want ms. - WriteTime: uint64(buf[i].writetime / 1000 / 1000), - IoTime: uint64((buf[i].readtime + buf[i].writetime) / 1000 / 1000), - Name: C.GoString(&buf[i].name[0]), - } - if len(names) > 0 && !common.StringsHas(names, d.Name) { - continue - } - - ret[d.Name] = d - } - return ret, nil -} diff --git a/disk/disk_darwin_nocgo.go b/disk/disk_darwin_nocgo.go deleted file mode 100644 index 8d55ca314..000000000 --- a/disk/disk_darwin_nocgo.go +++ /dev/null @@ -1,14 +0,0 @@ -// SPDX-License-Identifier: BSD-3-Clause -//go:build (darwin && !cgo) || ios - -package disk - -import ( - "context" - - "github.com/shirou/gopsutil/v4/internal/common" -) - -func IOCountersWithContext(ctx context.Context, names ...string) (map[string]IOCountersStat, error) { - return nil, common.ErrNotImplementedError -} diff --git a/disk/iostat_darwin.c b/disk/iostat_darwin.c deleted file mode 100644 index ba1e4c505..000000000 --- a/disk/iostat_darwin.c +++ /dev/null @@ -1,131 +0,0 @@ -// SPDX-License-Identifier: BSD-3-Clause -// SPDX-FileCopyrightText: Copyright (c) 2017, kadota kyohei -// https://github.com/lufia/iostat/blob/9f7362b77ad333b26c01c99de52a11bdb650ded2/iostat_darwin.c -#include -#include -#include "iostat_darwin.h" - -#define IOKIT 1 /* to get io_name_t in device_types.h */ - -#include -#include -#include -#include - -#include - -static int getdrivestat(io_registry_entry_t d, DriveStats *stat); -static int fillstat(io_registry_entry_t d, DriveStats *stat); - -int -gopsutil_v4_readdrivestat(DriveStats a[], int n) -{ - CFMutableDictionaryRef match; - io_iterator_t drives; - io_registry_entry_t d; - kern_return_t status; - int na, rv; - - match = IOServiceMatching("IOMedia"); - CFDictionaryAddValue(match, CFSTR(kIOMediaWholeKey), kCFBooleanTrue); - status = IOServiceGetMatchingServices(0, match, &drives); - if(status != KERN_SUCCESS) - return -1; - - na = 0; - while(na < n && (d=IOIteratorNext(drives)) > 0){ - rv = getdrivestat(d, &a[na]); - if(rv < 0) - return -1; - if(rv > 0) - na++; - IOObjectRelease(d); - } - IOObjectRelease(drives); - return na; -} - -static int -getdrivestat(io_registry_entry_t d, DriveStats *stat) -{ - io_registry_entry_t parent; - kern_return_t status; - CFDictionaryRef props; - CFStringRef name; - CFNumberRef num; - int rv; - - memset(stat, 0, sizeof *stat); - status = IORegistryEntryGetParentEntry(d, kIOServicePlane, &parent); - if(status != KERN_SUCCESS) - return -1; - if(!IOObjectConformsTo(parent, "IOBlockStorageDriver")){ - IOObjectRelease(parent); - return 0; - } - - status = IORegistryEntryCreateCFProperties(d, (CFMutableDictionaryRef *)&props, kCFAllocatorDefault, kNilOptions); - if(status != KERN_SUCCESS){ - IOObjectRelease(parent); - return -1; - } - name = (CFStringRef)CFDictionaryGetValue(props, CFSTR(kIOBSDNameKey)); - CFStringGetCString(name, stat->name, NAMELEN, CFStringGetSystemEncoding()); - num = (CFNumberRef)CFDictionaryGetValue(props, CFSTR(kIOMediaSizeKey)); - CFNumberGetValue(num, kCFNumberSInt64Type, &stat->size); - num = (CFNumberRef)CFDictionaryGetValue(props, CFSTR(kIOMediaPreferredBlockSizeKey)); - CFNumberGetValue(num, kCFNumberSInt64Type, &stat->blocksize); - CFRelease(props); - - rv = fillstat(parent, stat); - IOObjectRelease(parent); - if(rv < 0) - return -1; - return 1; -} - -static struct { - char *key; - size_t off; -} statstab[] = { - {kIOBlockStorageDriverStatisticsBytesReadKey, offsetof(DriveStats, read)}, - {kIOBlockStorageDriverStatisticsBytesWrittenKey, offsetof(DriveStats, written)}, - {kIOBlockStorageDriverStatisticsReadsKey, offsetof(DriveStats, nread)}, - {kIOBlockStorageDriverStatisticsWritesKey, offsetof(DriveStats, nwrite)}, - {kIOBlockStorageDriverStatisticsTotalReadTimeKey, offsetof(DriveStats, readtime)}, - {kIOBlockStorageDriverStatisticsTotalWriteTimeKey, offsetof(DriveStats, writetime)}, - {kIOBlockStorageDriverStatisticsLatentReadTimeKey, offsetof(DriveStats, readlat)}, - {kIOBlockStorageDriverStatisticsLatentWriteTimeKey, offsetof(DriveStats, writelat)}, -}; - -static int -fillstat(io_registry_entry_t d, DriveStats *stat) -{ - CFDictionaryRef props, v; - CFNumberRef num; - kern_return_t status; - typeof(statstab[0]) *bp, *ep; - - status = IORegistryEntryCreateCFProperties(d, (CFMutableDictionaryRef *)&props, kCFAllocatorDefault, kNilOptions); - if(status != KERN_SUCCESS) - return -1; - v = (CFDictionaryRef)CFDictionaryGetValue(props, CFSTR(kIOBlockStorageDriverStatisticsKey)); - if(v == NULL){ - CFRelease(props); - return -1; - } - - ep = &statstab[sizeof(statstab)/sizeof(statstab[0])]; - for(bp = &statstab[0]; bp < ep; bp++){ - CFStringRef s; - - s = CFStringCreateWithCString(kCFAllocatorDefault, bp->key, CFStringGetSystemEncoding()); - num = (CFNumberRef)CFDictionaryGetValue(v, s); - if(num) - CFNumberGetValue(num, kCFNumberSInt64Type, ((char*)stat)+bp->off); - CFRelease(s); - } - - CFRelease(props); - return 0; -} diff --git a/disk/iostat_darwin.h b/disk/iostat_darwin.h deleted file mode 100644 index 7b702aaa0..000000000 --- a/disk/iostat_darwin.h +++ /dev/null @@ -1,34 +0,0 @@ -// SPDX-License-Identifier: BSD-3-Clause -// SPDX-FileCopyrightText: Copyright (c) 2017, kadota kyohei -// https://github.com/lufia/iostat/blob/9f7362b77ad333b26c01c99de52a11bdb650ded2/iostat_darwin.h -typedef struct DriveStats DriveStats; -typedef struct CPUStats CPUStats; - -enum { - NDRIVE = 16, - NAMELEN = 31 -}; - -struct DriveStats { - char name[NAMELEN+1]; - int64_t size; - int64_t blocksize; - - int64_t read; - int64_t written; - int64_t nread; - int64_t nwrite; - int64_t readtime; - int64_t writetime; - int64_t readlat; - int64_t writelat; -}; - -struct CPUStats { - natural_t user; - natural_t nice; - natural_t sys; - natural_t idle; -}; - -extern int gopsutil_v4_readdrivestat(DriveStats a[], int n); diff --git a/go.mod b/go.mod index 79ecebbf8..56f786fe8 100644 --- a/go.mod +++ b/go.mod @@ -7,11 +7,10 @@ require ( github.com/google/go-cmp v0.6.0 github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c - github.com/shoenig/go-m1cpu v0.1.6 github.com/stretchr/testify v1.9.0 github.com/tklauser/go-sysconf v0.3.12 github.com/yusufpapurcu/wmi v1.2.4 - golang.org/x/sys v0.24.0 + golang.org/x/sys v0.25.0 ) require ( diff --git a/go.sum b/go.sum index a2896dba0..10c491d73 100644 --- a/go.sum +++ b/go.sum @@ -13,9 +13,6 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c h1:ncq/mPwQF4JjgDlrVEn3C11VoGHZN7m8qihwgMEtzYw= github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE= -github.com/shoenig/go-m1cpu v0.1.6 h1:nxdKQNcEB6vzgA2E2bvzKIYRuNj7XNJ4S/aRSwKzFtM= -github.com/shoenig/go-m1cpu v0.1.6/go.mod h1:1JJMcUBvfNwpq05QDQVAnx3gUHr9IYF7GNg9SUEw2VQ= -github.com/shoenig/test v0.6.4 h1:kVTaSd7WLz5WZ2IaoM0RSzRsUD+m8wRR+5qvntpn4LU= github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/tklauser/go-sysconf v0.3.12 h1:0QaGUFOdQaIVdPgfITYzaTegZvdCjmYO52cSFAEVmqU= @@ -28,8 +25,8 @@ golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.24.0 h1:Twjiwq9dn6R1fQcyiK+wQyHWfaz/BJB+YIpzU/Cv3Xg= -golang.org/x/sys v0.24.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34= +golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= diff --git a/internal/common/common_darwin.go b/internal/common/common_darwin.go index 0a1da931b..b473f8866 100644 --- a/internal/common/common_darwin.go +++ b/internal/common/common_darwin.go @@ -78,7 +78,7 @@ type Library struct { const ( IOKit = "/System/Library/Frameworks/IOKit.framework/IOKit" CoreFoundation = "/System/Library/Frameworks/CoreFoundation.framework/CoreFoundation" - Kernel = "/usr/lib/system/libsystem_kernel.dylib" + System = "/usr/lib/libSystem.B.dylib" ) func NewLibrary(path string) (*Library, error) { @@ -119,12 +119,19 @@ const ( // IOKit functions and symbols. type ( - IOServiceGetMatchingServiceFunc func(mainPort uint32, matching uintptr) uint32 - IOServiceMatchingFunc func(name string) unsafe.Pointer - IOServiceOpenFunc func(service, owningTask, connType uint32, connect *uint32) int - IOServiceCloseFunc func(connect uint32) int - IOObjectReleaseFunc func(object uint32) int - IOConnectCallStructMethodFunc func(connection, selector uint32, inputStruct, inputStructCnt, outputStruct uintptr, outputStructCnt *uintptr) int + IOServiceGetMatchingServiceFunc func(mainPort uint32, matching uintptr) uint32 + IOServiceGetMatchingServicesFunc func(mainPort uint32, matching uintptr, existing *uint32) int + IOServiceMatchingFunc func(name string) unsafe.Pointer + 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 + 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 + IOObjectConformsToFunc func(object uint32, className string) bool + IOObjectReleaseFunc func(object uint32) int + IOConnectCallStructMethodFunc func(connection, selector uint32, inputStruct, inputStructCnt, outputStruct uintptr, outputStructCnt *uintptr) int IOHIDEventSystemClientCreateFunc func(allocator uintptr) unsafe.Pointer IOHIDEventSystemClientSetMatchingFunc func(client, match uintptr) int @@ -136,12 +143,19 @@ type ( ) const ( - IOServiceGetMatchingServiceSym = "IOServiceGetMatchingService" - IOServiceMatchingSym = "IOServiceMatching" - IOServiceOpenSym = "IOServiceOpen" - IOServiceCloseSym = "IOServiceClose" - IOObjectReleaseSym = "IOObjectRelease" - IOConnectCallStructMethodSym = "IOConnectCallStructMethod" + IOServiceGetMatchingServiceSym = "IOServiceGetMatchingService" + IOServiceGetMatchingServicesSym = "IOServiceGetMatchingServices" + IOServiceMatchingSym = "IOServiceMatching" + IOServiceOpenSym = "IOServiceOpen" + IOServiceCloseSym = "IOServiceClose" + IOIteratorNextSym = "IOIteratorNext" + IORegistryEntryGetNameSym = "IORegistryEntryGetName" + IORegistryEntryGetParentEntrySym = "IORegistryEntryGetParentEntry" + IORegistryEntryCreateCFPropertySym = "IORegistryEntryCreateCFProperty" + IORegistryEntryCreateCFPropertiesSym = "IORegistryEntryCreateCFProperties" + IOObjectConformsToSym = "IOObjectConformsTo" + IOObjectReleaseSym = "IOObjectRelease" + IOConnectCallStructMethodSym = "IOConnectCallStructMethod" IOHIDEventSystemClientCreateSym = "IOHIDEventSystemClientCreate" IOHIDEventSystemClientSetMatchingSym = "IOHIDEventSystemClientSetMatching" @@ -152,49 +166,77 @@ const ( ) const ( + KIOMainPortDefault = 0 + KIOHIDEventTypeTemperature = 15 + + KNilOptions = 0 +) + +const ( + KIOMediaWholeKey = "Media" + KIOServicePlane = "IOService" ) // CoreFoundation functions and symbols. type ( + CFGetTypeIDFunc func(cf uintptr) int32 CFNumberCreateFunc func(allocator uintptr, theType int32, valuePtr uintptr) unsafe.Pointer + CFNumberGetValueFunc func(num uintptr, theType int32, valuePtr uintptr) bool CFDictionaryCreateFunc func(allocator uintptr, keys, values *unsafe.Pointer, numValues int32, keyCallBacks, valueCallBacks uintptr) unsafe.Pointer + CFDictionaryAddValueFunc func(theDict, key, value uintptr) + CFDictionaryGetValueFunc func(theDict, key uintptr) unsafe.Pointer CFArrayGetCountFunc func(theArray uintptr) int32 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) CFStringCreateWithCStringFunc func(alloc uintptr, cStr string, encoding uint32) unsafe.Pointer + CFDataGetLengthFunc func(theData uintptr) int32 + CFDataGetBytePtrFunc func(theData uintptr) unsafe.Pointer CFReleaseFunc func(cf uintptr) ) const ( + CFGetTypeIDSym = "CFGetTypeID" CFNumberCreateSym = "CFNumberCreate" + CFNumberGetValueSym = "CFNumberGetValue" CFDictionaryCreateSym = "CFDictionaryCreate" + CFDictionaryAddValueSym = "CFDictionaryAddValue" + CFDictionaryGetValueSym = "CFDictionaryGetValue" CFArrayGetCountSym = "CFArrayGetCount" CFArrayGetValueAtIndexSym = "CFArrayGetValueAtIndex" CFStringCreateMutableSym = "CFStringCreateMutable" CFStringGetLengthSym = "CFStringGetLength" CFStringGetCStringSym = "CFStringGetCString" CFStringCreateWithCStringSym = "CFStringCreateWithCString" + CFDataGetLengthSym = "CFDataGetLength" + CFDataGetBytePtrSym = "CFDataGetBytePtr" CFReleaseSym = "CFRelease" ) const ( KCFStringEncodingUTF8 = 0x08000100 + KCFNumberSInt64Type = 4 KCFNumberIntType = 9 KCFAllocatorDefault = 0 ) // Kernel functions and symbols. +type MachTimeBaseInfo struct { + Numer uint32 + Denom uint32 +} + type ( - HostProcessorInfoFunc func(host uint32, flavor int, outProcessorCount *uint32, outProcessorInfo uintptr, + HostProcessorInfoFunc func(host uint32, flavor int32, outProcessorCount *uint32, outProcessorInfo uintptr, outProcessorInfoCnt *uint32) int - HostStatisticsFunc func(host uint32, flavor int, hostInfoOut uintptr, hostInfoOutCnt *uint32) int - MachHostSelfFunc func() uint32 - MachTaskSelfFunc func() uint32 - VMDeallocateFunc func(targetTask uint32, vmAddress, vmSize uintptr) int + HostStatisticsFunc func(host uint32, flavor int32, hostInfoOut uintptr, hostInfoOutCnt *uint32) int + MachHostSelfFunc func() uint32 + MachTaskSelfFunc func() uint32 + MachTimeBaseInfoFunc func(info uintptr) int + VMDeallocateFunc func(targetTask uint32, vmAddress, vmSize uintptr) int ) const ( @@ -202,16 +244,40 @@ const ( HostStatisticsSym = "host_statistics" MachHostSelfSym = "mach_host_self" MachTaskSelfSym = "mach_task_self" + MachTimeBaseInfoSym = "mach_timebase_info" VMDeallocateSym = "vm_deallocate" ) const ( + CTL_KERN = 1 + KERN_ARGMAX = 8 + KERN_PROCARGS2 = 49 + HOST_VM_INFO = 2 HOST_CPU_LOAD_INFO = 3 HOST_VM_INFO_COUNT = 0xf ) +// System functions and symbols. +type ( + ProcPidPathFunc func(pid int32, buffer uintptr, bufferSize uint32) int32 + ProcPidInfoFunc func(pid, flavor int32, arg uint64, buffer uintptr, bufferSize int32) int32 +) + +const ( + SysctlSym = "sysctl" + ProcPidPathSym = "proc_pidpath" + ProcPidInfoSym = "proc_pidinfo" +) + +const ( + MAXPATHLEN = 1024 + PROC_PIDPATHINFO_MAXSIZE = 4 * MAXPATHLEN + PROC_PIDTASKINFO = 4 + PROC_PIDVNODEPATHINFO = 9 +) + // SMC represents a SMC instance. type SMC struct { lib *Library @@ -281,3 +347,18 @@ func (s *SMC) Close() error { } return nil } + +// https://github.com/ebitengine/purego/blob/main/internal/strings/strings.go#L26 +func GoString(cStr *byte) string { + if cStr == nil { + return "" + } + var length int + for { + if *(*byte)(unsafe.Add(unsafe.Pointer(cStr), uintptr(length))) == '\x00' { + break + } + length++ + } + return string(unsafe.Slice(cStr, length)) +} diff --git a/internal/common/common_unix.go b/internal/common/common_unix.go index 2715b890b..c9f91b169 100644 --- a/internal/common/common_unix.go +++ b/internal/common/common_unix.go @@ -40,23 +40,3 @@ func CallLsofWithContext(ctx context.Context, invoke Invoker, pid int32, args .. } return ret, nil } - -func CallPgrepWithContext(ctx context.Context, invoke Invoker, pid int32) ([]int32, error) { - out, err := invoke.CommandWithContext(ctx, "pgrep", "-P", strconv.Itoa(int(pid))) - if err != nil { - return []int32{}, err - } - lines := strings.Split(string(out), "\n") - ret := make([]int32, 0, len(lines)) - for _, l := range lines { - if len(l) == 0 { - continue - } - i, err := strconv.ParseInt(l, 10, 32) - if err != nil { - continue - } - ret = append(ret, int32(i)) - } - return ret, nil -} diff --git a/mem/mem_darwin.go b/mem/mem_darwin.go index 4442cbc11..a4c15f691 100644 --- a/mem/mem_darwin.go +++ b/mem/mem_darwin.go @@ -85,7 +85,7 @@ func VirtualMemory() (*VirtualMemoryStat, error) { } func VirtualMemoryWithContext(ctx context.Context) (*VirtualMemoryStat, error) { - machLib, err := common.NewLibrary(common.Kernel) + machLib, err := common.NewLibrary(common.System) if err != nil { return nil, err } diff --git a/process/process.go b/process/process.go index d73f1f972..70411c616 100644 --- a/process/process.go +++ b/process/process.go @@ -18,7 +18,7 @@ import ( var ( invoke common.Invoker = common.Invoke{} - ErrorNoChildren = errors.New("process does not have children") + ErrorNoChildren = errors.New("process does not have children") // Deprecated: ErrorNoChildren is never returned by process.Children(), check its returned []*Process slice length instead ErrorProcessNotRunning = errors.New("process does not exist") ErrorNotPermitted = errors.New("operation not permitted") ) diff --git a/process/process_darwin.go b/process/process_darwin.go index 66b3684ea..05c7562b7 100644 --- a/process/process_darwin.go +++ b/process/process_darwin.go @@ -4,15 +4,20 @@ package process import ( + "bytes" "context" + "encoding/binary" "fmt" "path/filepath" + "runtime" + "sort" "strconv" "strings" + "unsafe" - "github.com/tklauser/go-sysconf" "golang.org/x/sys/unix" + "github.com/shirou/gopsutil/v4/cpu" "github.com/shirou/gopsutil/v4/internal/common" "github.com/shirou/gopsutil/v4/net" ) @@ -27,16 +32,6 @@ const ( KernProcPathname = 12 // path to executable ) -var clockTicks = 100 // default value - -func init() { - clkTck, err := sysconf.Sysconf(sysconf.SC_CLK_TCK) - // ignore errors - if err == nil { - clockTicks = int(clkTck) - } -} - type _Ctype_struct___0 struct { Pad uint64 } @@ -186,65 +181,22 @@ func (p *Process) IOCountersWithContext(ctx context.Context) (*IOCountersStat, e return nil, common.ErrNotImplementedError } -func convertCPUTimes(s string) (ret float64, err error) { - var t int - var _tmp string - if strings.Contains(s, ":") { - _t := strings.Split(s, ":") - switch len(_t) { - case 3: - hour, err := strconv.ParseInt(_t[0], 10, 32) - if err != nil { - return ret, err - } - t += int(hour) * 60 * 60 * clockTicks - - mins, err := strconv.ParseInt(_t[1], 10, 32) - if err != nil { - return ret, err - } - t += int(mins) * 60 * clockTicks - _tmp = _t[2] - case 2: - mins, err := strconv.ParseInt(_t[0], 10, 32) - if err != nil { - return ret, err - } - t += int(mins) * 60 * clockTicks - _tmp = _t[1] - case 1, 0: - _tmp = s - default: - return ret, fmt.Errorf("wrong cpu time string") - } - } else { - _tmp = s - } - - _t := strings.Split(_tmp, ".") - if err != nil { - return ret, err - } - h, err := strconv.ParseInt(_t[0], 10, 32) - t += int(h) * clockTicks - h, err = strconv.ParseInt(_t[1], 10, 32) - t += int(h) - return float64(t) / float64(clockTicks), nil -} - func (p *Process) ChildrenWithContext(ctx context.Context) ([]*Process, error) { - pids, err := common.CallPgrepWithContext(ctx, invoke, p.Pid) + procs, err := ProcessesWithContext(ctx) if err != nil { - return nil, err + return nil, nil } - ret := make([]*Process, 0, len(pids)) - for _, pid := range pids { - np, err := NewProcessWithContext(ctx, pid) + ret := make([]*Process, 0, len(procs)) + for _, proc := range procs { + ppid, err := proc.PpidWithContext(ctx) if err != nil { - return nil, err + continue + } + if ppid == p.Pid { + ret = append(ret, proc) } - ret = append(ret, np) } + sort.Slice(ret, func(i, j int) bool { return ret[i].Pid < ret[j].Pid }) return ret, nil } @@ -323,3 +275,206 @@ func callPsWithContext(ctx context.Context, arg string, pid int32, threadOption return ret, nil } + +var ( + procPidPath common.ProcPidPathFunc + procPidInfo common.ProcPidInfoFunc + machTimeBaseInfo common.MachTimeBaseInfoFunc +) + +func registerFuncs() (*common.Library, error) { + lib, err := common.NewLibrary(common.System) + if err != nil { + return nil, err + } + + procPidPath = common.GetFunc[common.ProcPidPathFunc](lib, common.ProcPidPathSym) + procPidInfo = common.GetFunc[common.ProcPidInfoFunc](lib, common.ProcPidInfoSym) + machTimeBaseInfo = common.GetFunc[common.MachTimeBaseInfoFunc](lib, common.MachTimeBaseInfoSym) + + return lib, nil +} + +func getTimeScaleToNanoSeconds() float64 { + var timeBaseInfo common.MachTimeBaseInfo + + machTimeBaseInfo(uintptr(unsafe.Pointer(&timeBaseInfo))) + + return float64(timeBaseInfo.Numer) / float64(timeBaseInfo.Denom) +} + +func (p *Process) ExeWithContext(ctx context.Context) (string, error) { + lib, err := registerFuncs() + if err != nil { + return "", err + } + defer lib.Close() + + buf := make([]byte, common.PROC_PIDPATHINFO_MAXSIZE) + ret := procPidPath(p.Pid, uintptr(unsafe.Pointer(&buf[0])), common.PROC_PIDPATHINFO_MAXSIZE) + + if ret <= 0 { + return "", fmt.Errorf("unknown error: proc_pidpath returned %d", ret) + } + + return common.GoString(&buf[0]), nil +} + +// sys/proc_info.h +type vnodePathInfo struct { + _ [152]byte + vipPath [common.MAXPATHLEN]byte + _ [1176]byte +} + +// CwdWithContext retrieves the Current Working Directory for the given process. +// It uses the proc_pidinfo from libproc and will only work for processes the +// EUID can access. Otherwise "operation not permitted" will be returned as the +// error. +// Note: This might also work for other *BSD OSs. +func (p *Process) CwdWithContext(ctx context.Context) (string, error) { + lib, err := registerFuncs() + if err != nil { + return "", err + } + defer lib.Close() + + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + var vpi vnodePathInfo + const vpiSize = int32(unsafe.Sizeof(vpi)) + ret := procPidInfo(p.Pid, common.PROC_PIDVNODEPATHINFO, 0, uintptr(unsafe.Pointer(&vpi)), vpiSize) + errno, _ := lib.Dlsym("errno") + err = *(**unix.Errno)(unsafe.Pointer(&errno)) + if err == unix.EPERM { + return "", ErrorNotPermitted + } + + if ret <= 0 { + return "", fmt.Errorf("unknown error: proc_pidinfo returned %d", ret) + } + + if ret != vpiSize { + return "", fmt.Errorf("too few bytes; expected %d, got %d", vpiSize, ret) + } + return common.GoString(&vpi.vipPath[0]), nil +} + +func procArgs(pid int32) ([]byte, int, error) { + procargs, _, err := common.CallSyscall([]int32{common.CTL_KERN, common.KERN_PROCARGS2, pid}) + if err != nil { + return nil, 0, err + } + nargs := procargs[:4] + return procargs, int(binary.LittleEndian.Uint32(nargs)), nil +} + +func (p *Process) CmdlineSliceWithContext(ctx context.Context) ([]string, error) { + return p.cmdlineSliceWithContext(ctx, true) +} + +func (p *Process) cmdlineSliceWithContext(ctx context.Context, fallback bool) ([]string, error) { + pargs, nargs, err := procArgs(p.Pid) + if err != nil { + return nil, err + } + // The first bytes hold the nargs int, skip it. + args := bytes.Split((pargs)[unsafe.Sizeof(int(0)):], []byte{0}) + var argStr string + // The first element is the actual binary/command path. + // command := args[0] + var argSlice []string + // var envSlice []string + // All other, non-zero elements are arguments. The first "nargs" elements + // are the arguments. Everything else in the slice is then the environment + // of the process. + for _, arg := range args[1:] { + argStr = string(arg[:]) + if len(argStr) > 0 { + if nargs > 0 { + argSlice = append(argSlice, argStr) + nargs-- + continue + } + break + // envSlice = append(envSlice, argStr) + } + } + return argSlice, err +} + +// cmdNameWithContext returns the command name (including spaces) without any arguments +func (p *Process) cmdNameWithContext(ctx context.Context) (string, error) { + r, err := p.cmdlineSliceWithContext(ctx, false) + if err != nil { + return "", err + } + + if len(r) == 0 { + return "", nil + } + + return r[0], err +} + +func (p *Process) CmdlineWithContext(ctx context.Context) (string, error) { + r, err := p.CmdlineSliceWithContext(ctx) + if err != nil { + return "", err + } + return strings.Join(r, " "), err +} + +func (p *Process) NumThreadsWithContext(ctx context.Context) (int32, error) { + lib, err := registerFuncs() + if err != nil { + return 0, err + } + defer lib.Close() + + var ti ProcTaskInfo + const tiSize = int32(unsafe.Sizeof(ti)) + procPidInfo(p.Pid, common.PROC_PIDTASKINFO, 0, uintptr(unsafe.Pointer(&ti)), tiSize) + + return int32(ti.Threadnum), nil +} + +func (p *Process) TimesWithContext(ctx context.Context) (*cpu.TimesStat, error) { + lib, err := registerFuncs() + if err != nil { + return nil, err + } + defer lib.Close() + + var ti ProcTaskInfo + const tiSize = int32(unsafe.Sizeof(ti)) + procPidInfo(p.Pid, common.PROC_PIDTASKINFO, 0, uintptr(unsafe.Pointer(&ti)), tiSize) + + timescaleToNanoSeconds := getTimeScaleToNanoSeconds() + ret := &cpu.TimesStat{ + CPU: "cpu", + User: float64(ti.Total_user) * timescaleToNanoSeconds / 1e9, + System: float64(ti.Total_system) * timescaleToNanoSeconds / 1e9, + } + return ret, nil +} + +func (p *Process) MemoryInfoWithContext(ctx context.Context) (*MemoryInfoStat, error) { + lib, err := registerFuncs() + if err != nil { + return nil, err + } + defer lib.Close() + + var ti ProcTaskInfo + const tiSize = int32(unsafe.Sizeof(ti)) + procPidInfo(p.Pid, common.PROC_PIDTASKINFO, 0, uintptr(unsafe.Pointer(&ti)), tiSize) + + ret := &MemoryInfoStat{ + RSS: uint64(ti.Resident_size), + VMS: uint64(ti.Virtual_size), + Swap: uint64(ti.Pageins), + } + return ret, nil +} diff --git a/process/process_darwin_amd64.go b/process/process_darwin_amd64.go index a13522473..890a5d533 100644 --- a/process/process_darwin_amd64.go +++ b/process/process_darwin_amd64.go @@ -212,6 +212,27 @@ type Posix_cred struct { type Label struct{} +type ProcTaskInfo struct { + Virtual_size uint64 + Resident_size uint64 + Total_user uint64 + Total_system uint64 + Threads_user uint64 + Threads_system uint64 + Policy int32 + Faults int32 + Pageins int32 + Cow_faults int32 + Messages_sent int32 + Messages_received int32 + Syscalls_mach int32 + Syscalls_unix int32 + Csw int32 + Threadnum int32 + Numrunning int32 + Priority int32 +} + type AuditinfoAddr struct { Auid uint32 Mask AuMask diff --git a/process/process_darwin_arm64.go b/process/process_darwin_arm64.go index f1f3df365..8075cf227 100644 --- a/process/process_darwin_arm64.go +++ b/process/process_darwin_arm64.go @@ -190,6 +190,27 @@ type Posix_cred struct{} type Label struct{} +type ProcTaskInfo struct { + Virtual_size uint64 + Resident_size uint64 + Total_user uint64 + Total_system uint64 + Threads_user uint64 + Threads_system uint64 + Policy int32 + Faults int32 + Pageins int32 + Cow_faults int32 + Messages_sent int32 + Messages_received int32 + Syscalls_mach int32 + Syscalls_unix int32 + Csw int32 + Threadnum int32 + Numrunning int32 + Priority int32 +} + type AuditinfoAddr struct { Auid uint32 Mask AuMask diff --git a/process/process_darwin_cgo.go b/process/process_darwin_cgo.go deleted file mode 100644 index bbdfc963e..000000000 --- a/process/process_darwin_cgo.go +++ /dev/null @@ -1,222 +0,0 @@ -// SPDX-License-Identifier: BSD-3-Clause -//go:build darwin && cgo - -package process - -// #include -// #include -// #include -// #include -// #include -// #include -// #include -import "C" - -import ( - "bytes" - "context" - "fmt" - "strings" - "syscall" - "unsafe" - - "github.com/shirou/gopsutil/v4/cpu" -) - -var ( - argMax int - timescaleToNanoSeconds float64 -) - -func init() { - argMax = getArgMax() - timescaleToNanoSeconds = getTimeScaleToNanoSeconds() -} - -func getArgMax() int { - var ( - mib = [...]C.int{C.CTL_KERN, C.KERN_ARGMAX} - argmax C.int - size C.size_t = C.ulong(unsafe.Sizeof(argmax)) - ) - retval := C.sysctl(&mib[0], 2, unsafe.Pointer(&argmax), &size, C.NULL, 0) - if retval == 0 { - return int(argmax) - } - return 0 -} - -func getTimeScaleToNanoSeconds() float64 { - var timeBaseInfo C.struct_mach_timebase_info - - C.mach_timebase_info(&timeBaseInfo) - - return float64(timeBaseInfo.numer) / float64(timeBaseInfo.denom) -} - -func (p *Process) ExeWithContext(ctx context.Context) (string, error) { - var c C.char // need a var for unsafe.Sizeof need a var - const bufsize = C.PROC_PIDPATHINFO_MAXSIZE * unsafe.Sizeof(c) - buffer := (*C.char)(C.malloc(C.size_t(bufsize))) - defer C.free(unsafe.Pointer(buffer)) - - ret, err := C.proc_pidpath(C.int(p.Pid), unsafe.Pointer(buffer), C.uint32_t(bufsize)) - if err != nil { - return "", err - } - if ret <= 0 { - return "", fmt.Errorf("unknown error: proc_pidpath returned %d", ret) - } - - return C.GoString(buffer), nil -} - -// CwdWithContext retrieves the Current Working Directory for the given process. -// It uses the proc_pidinfo from libproc and will only work for processes the -// EUID can access. Otherwise "operation not permitted" will be returned as the -// error. -// Note: This might also work for other *BSD OSs. -func (p *Process) CwdWithContext(ctx context.Context) (string, error) { - const vpiSize = C.sizeof_struct_proc_vnodepathinfo - vpi := (*C.struct_proc_vnodepathinfo)(C.malloc(vpiSize)) - defer C.free(unsafe.Pointer(vpi)) - ret, err := C.proc_pidinfo(C.int(p.Pid), C.PROC_PIDVNODEPATHINFO, 0, unsafe.Pointer(vpi), vpiSize) - if err != nil { - // fmt.Printf("ret: %d %T\n", ret, err) - if err == syscall.EPERM { - return "", ErrorNotPermitted - } - return "", err - } - if ret <= 0 { - return "", fmt.Errorf("unknown error: proc_pidinfo returned %d", ret) - } - if ret != C.sizeof_struct_proc_vnodepathinfo { - return "", fmt.Errorf("too few bytes; expected %d, got %d", vpiSize, ret) - } - return C.GoString(&vpi.pvi_cdir.vip_path[0]), err -} - -func procArgs(pid int32) ([]byte, int, error) { - var ( - mib = [...]C.int{C.CTL_KERN, C.KERN_PROCARGS2, C.int(pid)} - size C.size_t = C.ulong(argMax) - nargs C.int - result []byte - ) - procargs := (*C.char)(C.malloc(C.ulong(argMax))) - defer C.free(unsafe.Pointer(procargs)) - retval, err := C.sysctl(&mib[0], 3, unsafe.Pointer(procargs), &size, C.NULL, 0) - if retval == 0 { - C.memcpy(unsafe.Pointer(&nargs), unsafe.Pointer(procargs), C.sizeof_int) - result = C.GoBytes(unsafe.Pointer(procargs), C.int(size)) - // fmt.Printf("size: %d %d\n%s\n", size, nargs, hex.Dump(result)) - return result, int(nargs), nil - } - return nil, 0, err -} - -func (p *Process) CmdlineSliceWithContext(ctx context.Context) ([]string, error) { - return p.cmdlineSliceWithContext(ctx, true) -} - -func (p *Process) cmdlineSliceWithContext(ctx context.Context, fallback bool) ([]string, error) { - pargs, nargs, err := procArgs(p.Pid) - if err != nil { - return nil, err - } - // The first bytes hold the nargs int, skip it. - args := bytes.Split((pargs)[C.sizeof_int:], []byte{0}) - var argStr string - // The first element is the actual binary/command path. - // command := args[0] - var argSlice []string - // var envSlice []string - // All other, non-zero elements are arguments. The first "nargs" elements - // are the arguments. Everything else in the slice is then the environment - // of the process. - for _, arg := range args[1:] { - argStr = string(arg[:]) - if len(argStr) > 0 { - if nargs > 0 { - argSlice = append(argSlice, argStr) - nargs-- - continue - } - break - // envSlice = append(envSlice, argStr) - } - } - return argSlice, err -} - -// cmdNameWithContext returns the command name (including spaces) without any arguments -func (p *Process) cmdNameWithContext(ctx context.Context) (string, error) { - r, err := p.cmdlineSliceWithContext(ctx, false) - if err != nil { - return "", err - } - - if len(r) == 0 { - return "", nil - } - - return r[0], err -} - -func (p *Process) CmdlineWithContext(ctx context.Context) (string, error) { - r, err := p.CmdlineSliceWithContext(ctx) - if err != nil { - return "", err - } - return strings.Join(r, " "), err -} - -func (p *Process) NumThreadsWithContext(ctx context.Context) (int32, error) { - const tiSize = C.sizeof_struct_proc_taskinfo - ti := (*C.struct_proc_taskinfo)(C.malloc(tiSize)) - defer C.free(unsafe.Pointer(ti)) - - _, err := C.proc_pidinfo(C.int(p.Pid), C.PROC_PIDTASKINFO, 0, unsafe.Pointer(ti), tiSize) - if err != nil { - return 0, err - } - - return int32(ti.pti_threadnum), nil -} - -func (p *Process) TimesWithContext(ctx context.Context) (*cpu.TimesStat, error) { - const tiSize = C.sizeof_struct_proc_taskinfo - ti := (*C.struct_proc_taskinfo)(C.malloc(tiSize)) - defer C.free(unsafe.Pointer(ti)) - - _, err := C.proc_pidinfo(C.int(p.Pid), C.PROC_PIDTASKINFO, 0, unsafe.Pointer(ti), tiSize) - if err != nil { - return nil, err - } - - ret := &cpu.TimesStat{ - CPU: "cpu", - User: float64(ti.pti_total_user) * timescaleToNanoSeconds / 1e9, - System: float64(ti.pti_total_system) * timescaleToNanoSeconds / 1e9, - } - return ret, nil -} - -func (p *Process) MemoryInfoWithContext(ctx context.Context) (*MemoryInfoStat, error) { - const tiSize = C.sizeof_struct_proc_taskinfo - ti := (*C.struct_proc_taskinfo)(C.malloc(tiSize)) - defer C.free(unsafe.Pointer(ti)) - - _, err := C.proc_pidinfo(C.int(p.Pid), C.PROC_PIDTASKINFO, 0, unsafe.Pointer(ti), tiSize) - if err != nil { - return nil, err - } - - ret := &MemoryInfoStat{ - RSS: uint64(ti.pti_resident_size), - VMS: uint64(ti.pti_virtual_size), - Swap: uint64(ti.pti_pageins), - } - return ret, nil -} diff --git a/process/process_darwin_nocgo.go b/process/process_darwin_nocgo.go deleted file mode 100644 index d498c9377..000000000 --- a/process/process_darwin_nocgo.go +++ /dev/null @@ -1,134 +0,0 @@ -// SPDX-License-Identifier: BSD-3-Clause -//go:build darwin && !cgo - -package process - -import ( - "context" - "fmt" - "strconv" - "strings" - - "github.com/shirou/gopsutil/v4/cpu" - "github.com/shirou/gopsutil/v4/internal/common" -) - -func (p *Process) CwdWithContext(ctx context.Context) (string, error) { - return "", common.ErrNotImplementedError -} - -func (p *Process) ExeWithContext(ctx context.Context) (string, error) { - out, err := invoke.CommandWithContext(ctx, "lsof", "-p", strconv.Itoa(int(p.Pid)), "-Fpfn") - if err != nil { - return "", fmt.Errorf("bad call to lsof: %w", err) - } - txtFound := 0 - lines := strings.Split(string(out), "\n") - fallback := "" - for i := 1; i < len(lines); i++ { - if lines[i] == "ftxt" { - txtFound++ - if txtFound == 1 { - fallback = lines[i-1][1:] - } - if txtFound == 2 { - return lines[i-1][1:], nil - } - } - } - if fallback != "" { - return fallback, nil - } - return "", fmt.Errorf("missing txt data returned by lsof") -} - -func (p *Process) CmdlineWithContext(ctx context.Context) (string, error) { - r, err := callPsWithContext(ctx, "command", p.Pid, false, false) - if err != nil { - return "", err - } - return strings.Join(r[0], " "), err -} - -func (p *Process) cmdNameWithContext(ctx context.Context) (string, error) { - r, err := callPsWithContext(ctx, "command", p.Pid, false, true) - if err != nil { - return "", err - } - if len(r) > 0 && len(r[0]) > 0 { - return r[0][0], err - } - - return "", err -} - -// CmdlineSliceWithContext returns the command line arguments of the process as a slice with each -// element being an argument. Because of current deficiencies in the way that the command -// line arguments are found, single arguments that have spaces in the will actually be -// reported as two separate items. In order to do something better CGO would be needed -// to use the native darwin functions. -func (p *Process) CmdlineSliceWithContext(ctx context.Context) ([]string, error) { - r, err := callPsWithContext(ctx, "command", p.Pid, false, false) - if err != nil { - return nil, err - } - return r[0], err -} - -func (p *Process) NumThreadsWithContext(ctx context.Context) (int32, error) { - r, err := callPsWithContext(ctx, "utime,stime", p.Pid, true, false) - if err != nil { - return 0, err - } - return int32(len(r)), nil -} - -func (p *Process) TimesWithContext(ctx context.Context) (*cpu.TimesStat, error) { - r, err := callPsWithContext(ctx, "utime,stime", p.Pid, false, false) - if err != nil { - return nil, err - } - - utime, err := convertCPUTimes(r[0][0]) - if err != nil { - return nil, err - } - stime, err := convertCPUTimes(r[0][1]) - if err != nil { - return nil, err - } - - ret := &cpu.TimesStat{ - CPU: "cpu", - User: utime, - System: stime, - } - return ret, nil -} - -func (p *Process) MemoryInfoWithContext(ctx context.Context) (*MemoryInfoStat, error) { - r, err := callPsWithContext(ctx, "rss,vsize,pagein", p.Pid, false, false) - if err != nil { - return nil, err - } - rss, err := strconv.ParseInt(r[0][0], 10, 64) - if err != nil { - return nil, err - } - vms, err := strconv.ParseInt(r[0][1], 10, 64) - if err != nil { - return nil, err - } - pagein, err := strconv.ParseInt(r[0][2], 10, 64) - if err != nil { - return nil, err - } - - ret := &MemoryInfoStat{ - RSS: uint64(rss) * 1024, - VMS: uint64(vms) * 1024, - Swap: uint64(pagein), - } - - return ret, nil -} diff --git a/process/process_freebsd.go b/process/process_freebsd.go index 436dcf030..76373736b 100644 --- a/process/process_freebsd.go +++ b/process/process_freebsd.go @@ -8,6 +8,7 @@ import ( "context" "errors" "path/filepath" + "sort" "strconv" "strings" @@ -269,18 +270,21 @@ func (p *Process) MemoryInfoWithContext(ctx context.Context) (*MemoryInfoStat, e } func (p *Process) ChildrenWithContext(ctx context.Context) ([]*Process, error) { - pids, err := common.CallPgrepWithContext(ctx, invoke, p.Pid) + procs, err := ProcessesWithContext(ctx) if err != nil { - return nil, err + return nil, nil } - ret := make([]*Process, 0, len(pids)) - for _, pid := range pids { - np, err := NewProcessWithContext(ctx, pid) + ret := make([]*Process, 0, len(procs)) + for _, proc := range procs { + ppid, err := proc.PpidWithContext(ctx) if err != nil { - return nil, err + continue + } + if ppid == p.Pid { + ret = append(ret, proc) } - ret = append(ret, np) } + sort.Slice(ret, func(i, j int) bool { return ret[i].Pid < ret[j].Pid }) return ret, nil } diff --git a/process/process_linux.go b/process/process_linux.go index 7aff0448d..4f1669346 100644 --- a/process/process_linux.go +++ b/process/process_linux.go @@ -12,6 +12,7 @@ import ( "math" "os" "path/filepath" + "sort" "strconv" "strings" @@ -338,21 +339,34 @@ func (p *Process) PageFaultsWithContext(ctx context.Context) (*PageFaultsStat, e } func (p *Process) ChildrenWithContext(ctx context.Context) ([]*Process, error) { - pids, err := common.CallPgrepWithContext(ctx, invoke, p.Pid) + statFiles, err := filepath.Glob(common.HostProcWithContext(ctx, "[0-9]*/stat")) if err != nil { return nil, err } - if len(pids) == 0 { - return nil, ErrorNoChildren - } - ret := make([]*Process, 0, len(pids)) - for _, pid := range pids { - np, err := NewProcessWithContext(ctx, pid) + ret := make([]*Process, 0, len(statFiles)) + for _, statFile := range statFiles { + statContents, err := os.ReadFile(statFile) if err != nil { - return nil, err + continue + } + fields := splitProcStat(statContents) + pid, err := strconv.ParseInt(fields[1], 10, 32) + if err != nil { + continue + } + ppid, err := strconv.ParseInt(fields[4], 10, 32) + if err != nil { + continue + } + if int32(ppid) == p.Pid { + np, err := NewProcessWithContext(ctx, int32(pid)) + if err != nil { + continue + } + ret = append(ret, np) } - ret = append(ret, np) } + sort.Slice(ret, func(i, j int) bool { return ret[i].Pid < ret[j].Pid }) return ret, nil } diff --git a/process/process_openbsd.go b/process/process_openbsd.go index e2d0ab462..5e8a9e0b4 100644 --- a/process/process_openbsd.go +++ b/process/process_openbsd.go @@ -11,6 +11,7 @@ import ( "fmt" "io" "path/filepath" + "sort" "strconv" "strings" "unsafe" @@ -286,18 +287,21 @@ func (p *Process) MemoryInfoWithContext(ctx context.Context) (*MemoryInfoStat, e } func (p *Process) ChildrenWithContext(ctx context.Context) ([]*Process, error) { - pids, err := common.CallPgrepWithContext(ctx, invoke, p.Pid) + procs, err := ProcessesWithContext(ctx) if err != nil { - return nil, err + return nil, nil } - ret := make([]*Process, 0, len(pids)) - for _, pid := range pids { - np, err := NewProcessWithContext(ctx, pid) + ret := make([]*Process, 0, len(procs)) + for _, proc := range procs { + ppid, err := proc.PpidWithContext(ctx) if err != nil { - return nil, err + continue + } + if ppid == p.Pid { + ret = append(ret, proc) } - ret = append(ret, np) } + sort.Slice(ret, func(i, j int) bool { return ret[i].Pid < ret[j].Pid }) return ret, nil } diff --git a/process/types_darwin.go b/process/types_darwin.go index 7dc9351dd..a38cba04f 100644 --- a/process/types_darwin.go +++ b/process/types_darwin.go @@ -53,6 +53,7 @@ package process #include #include #include +#include #include #include #include @@ -154,6 +155,8 @@ type Posix_cred C.struct_posix_cred type Label C.struct_label +type ProcTaskInfo C.struct_proc_taskinfo + type ( AuditinfoAddr C.struct_auditinfo_addr AuMask C.struct_au_mask From f56b53a15517a3a006f10197072f3fe651763df6 Mon Sep 17 00:00:00 2001 From: uubulb Date: Tue, 24 Sep 2024 23:11:59 +0800 Subject: [PATCH 3/3] update purego --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 56f786fe8..0dca2553a 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,7 @@ module github.com/shirou/gopsutil/v4 go 1.18 require ( - github.com/ebitengine/purego v0.7.1 + github.com/ebitengine/purego v0.8.0 github.com/google/go-cmp v0.6.0 github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c diff --git a/go.sum b/go.sum index 10c491d73..8863f6f3b 100644 --- a/go.sum +++ b/go.sum @@ -1,7 +1,7 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/ebitengine/purego v0.7.1 h1:6/55d26lG3o9VCZX8lping+bZcmShseiqlh2bnUDiPA= -github.com/ebitengine/purego v0.7.1/go.mod h1:ah1In8AOtksoNK6yk5z1HTJeUkC1Ez4Wk2idgGslMwQ= +github.com/ebitengine/purego v0.8.0 h1:JbqvnEzRvPpxhCJzJJ2y0RbiZ8nyjccVUrSM3q+GvvE= +github.com/ebitengine/purego v0.8.0/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ= github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY= github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=