chore(deps): bump github.com/shirou/gopsutil/v4 from 4.25.11 to 4.25.12

Bumps [github.com/shirou/gopsutil/v4](https://github.com/shirou/gopsutil) from 4.25.11 to 4.25.12.
- [Release notes](https://github.com/shirou/gopsutil/releases)
- [Commits](https://github.com/shirou/gopsutil/compare/v4.25.11...v4.25.12)

---
updated-dependencies:
- dependency-name: github.com/shirou/gopsutil/v4
  dependency-version: 4.25.12
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
This commit is contained in:
dependabot[bot]
2026-01-05 21:18:03 +00:00
committed by GitHub
parent d4163caaf9
commit dbd3bb1481
21 changed files with 724 additions and 390 deletions

2
go.mod
View File

@@ -14,7 +14,7 @@ require (
github.com/prometheus/client_model v0.6.2
github.com/prometheus/common v0.67.4
github.com/prometheus/procfs v0.19.2
github.com/shirou/gopsutil/v4 v4.25.11
github.com/shirou/gopsutil/v4 v4.25.12
github.com/spf13/pflag v1.0.10
github.com/stretchr/testify v1.11.1
go.opencensus.io v0.24.0

4
go.sum
View File

@@ -1031,8 +1031,8 @@ github.com/sclevine/spec v1.2.0/go.mod h1:W4J29eT/Kzv7/b9IWLB055Z+qvVC9vt0Arko24
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
github.com/seccomp/libseccomp-golang v0.9.1/go.mod h1:GbW5+tmTXfcxTToHLXlScSlAvWlF4P2Ca7zGrPiEpWo=
github.com/seccomp/libseccomp-golang v0.9.2-0.20210429002308-3879420cc921/go.mod h1:JA8cRccbGaA1s33RQf7Y1+q9gHmZX1yB/z9WDN1C6fg=
github.com/shirou/gopsutil/v4 v4.25.11 h1:X53gB7muL9Gnwwo2evPSE+SfOrltMoR6V3xJAXZILTY=
github.com/shirou/gopsutil/v4 v4.25.11/go.mod h1:EivAfP5x2EhLp2ovdpKSozecVXn1TmuG7SMzs/Wh4PU=
github.com/shirou/gopsutil/v4 v4.25.12 h1:e7PvW/0RmJ8p8vPGJH4jvNkOyLmbkXgXW4m6ZPic6CY=
github.com/shirou/gopsutil/v4 v4.25.12/go.mod h1:EivAfP5x2EhLp2ovdpKSozecVXn1TmuG7SMzs/Wh4PU=
github.com/shurcooL/httpfs v0.0.0-20190707220628-8d4bc4ba7749/go.mod h1:ZY1cvUeJuFPAdZ/B6v7RHavJWZn2YPVFQ1OSXhCGOkg=
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
github.com/shurcooL/vfsgen v0.0.0-20200824052919-0d455de96546/go.mod h1:TrYk7fJVaAttu97ZZKrO9UbRa8izdowaMIZcxYMbVaw=

View File

@@ -61,17 +61,17 @@ func Times(percpu bool) ([]TimesStat, error) {
}
func TimesWithContext(_ context.Context, percpu bool) ([]TimesStat, error) {
lib, err := common.NewLibrary(common.System)
sys, err := common.NewSystemLib()
if err != nil {
return nil, err
}
defer lib.Close()
defer sys.Close()
if percpu {
return perCPUTimes(lib)
return perCPUTimes(sys)
}
return allCPUTimes(lib)
return allCPUTimes(sys)
}
// Returns only one CPUInfoStat on FreeBSD
@@ -138,16 +138,12 @@ func CountsWithContext(_ 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)
func perCPUTimes(sys *common.SystemLib) ([]TimesStat, error) {
var count, ncpu uint32
var cpuload *hostCpuLoadInfoData
status := hostProcessorInfo(machHostSelf(), processorCpuLoadInfo, &ncpu, uintptr(unsafe.Pointer(&cpuload)), &count)
status := sys.HostProcessorInfo(sys.MachHostSelf(), processorCpuLoadInfo,
&ncpu, uintptr(unsafe.Pointer(&cpuload)), &count)
if status != common.KERN_SUCCESS {
return nil, fmt.Errorf("host_processor_info error=%d", status)
@@ -157,7 +153,7 @@ func perCPUTimes(machLib *common.Library) ([]TimesStat, error) {
return nil, errors.New("host_processor_info returned nil cpuload")
}
defer vmDeallocate(machTaskSelf(), uintptr(unsafe.Pointer(cpuload)), uintptr(ncpu))
defer sys.VMDeallocate(sys.MachTaskSelf(), uintptr(unsafe.Pointer(cpuload)), uintptr(ncpu))
ret := []TimesStat{}
loads := unsafe.Slice(cpuload, ncpu)
@@ -170,21 +166,17 @@ func perCPUTimes(machLib *common.Library) ([]TimesStat, error) {
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)
func allCPUTimes(sys *common.SystemLib) ([]TimesStat, error) {
var cpuload hostCpuLoadInfoData
count := uint32(cpuStateMax)
status := hostStatistics(machHostSelf(), common.HOST_CPU_LOAD_INFO,
status := sys.HostStatistics(sys.MachHostSelf(), common.HOST_CPU_LOAD_INFO,
uintptr(unsafe.Pointer(&cpuload)), &count)
if status != common.KERN_SUCCESS {

View File

@@ -13,55 +13,43 @@ import (
// https://github.com/shoenig/go-m1cpu/blob/v0.1.6/cpu.go
func getFrequency() (float64, error) {
ioKit, err := common.NewLibrary(common.IOKit)
iokit, err := common.NewIOKitLib()
if err != nil {
return 0, err
}
defer ioKit.Close()
defer iokit.Close()
coreFoundation, err := common.NewLibrary(common.CoreFoundation)
corefoundation, err := common.NewCoreFoundationLib()
if err != nil {
return 0, err
}
defer coreFoundation.Close()
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")
matching := iokit.IOServiceMatching("AppleARMIODevice")
var iterator uint32
if status := ioServiceGetMatchingServices(common.KIOMainPortDefault, uintptr(matching), &iterator); status != common.KERN_SUCCESS {
if status := iokit.IOServiceGetMatchingServices(common.KIOMainPortDefault, uintptr(matching), &iterator); status != common.KERN_SUCCESS {
return 0.0, fmt.Errorf("IOServiceGetMatchingServices error=%d", status)
}
defer ioObjectRelease(iterator)
defer iokit.IOObjectRelease(iterator)
pCorekey := cfStringCreateWithCString(common.KCFAllocatorDefault, "voltage-states5-sram", common.KCFStringEncodingUTF8)
defer cfRelease(uintptr(pCorekey))
pCorekey := corefoundation.CFStringCreateWithCString(common.KCFAllocatorDefault, "voltage-states5-sram", common.KCFStringEncodingUTF8)
defer corefoundation.CFRelease(uintptr(pCorekey))
var pCoreHz uint32
for {
service := ioIteratorNext(iterator)
service := iokit.IOIteratorNext(iterator)
if service <= 0 {
break
}
buf := common.NewCStr(512)
ioRegistryEntryGetName(service, buf)
iokit.IORegistryEntryGetName(service, buf)
if buf.GoString() == "pmgr" {
pCoreRef := ioRegistryEntryCreateCFProperty(service, uintptr(pCorekey), common.KCFAllocatorDefault, common.KNilOptions)
length := cfDataGetLength(uintptr(pCoreRef))
data := cfDataGetBytePtr(uintptr(pCoreRef))
pCoreRef := iokit.IORegistryEntryCreateCFProperty(service, uintptr(pCorekey), common.KCFAllocatorDefault, common.KNilOptions)
length := corefoundation.CFDataGetLength(uintptr(pCoreRef))
data := corefoundation.CFDataGetBytePtr(uintptr(pCoreRef))
// composite uint32 from the byte array
buf := unsafe.Slice((*byte)(data), length)
@@ -69,11 +57,12 @@ func getFrequency() (float64, error) {
// combine the bytes into a uint32 value
b := buf[length-8 : length-4]
pCoreHz = binary.LittleEndian.Uint32(b)
ioObjectRelease(service)
corefoundation.CFRelease(uintptr(pCoreRef))
iokit.IOObjectRelease(service)
break
}
ioObjectRelease(service)
iokit.IOObjectRelease(service)
}
return float64(pCoreHz / 1_000_000), nil

View File

@@ -195,7 +195,7 @@ func InfoWithContext(ctx context.Context) ([]InfoStat, error) {
c := InfoStat{CPU: -1, Cores: 1}
for _, line := range lines {
fields := strings.Split(line, ":")
fields := strings.SplitN(line, ":", 2)
if len(fields) < 2 {
continue
}
@@ -221,6 +221,25 @@ func InfoWithContext(ctx context.Context) ([]InfoStat, error) {
if strings.Contains(value, "S390") {
processorName = "S390"
}
case "mvendorid":
if !strings.HasPrefix(value, "0x") {
continue
}
if v, err := strconv.ParseUint(value[2:], 16, 32); err == nil {
switch v {
case 0x31e:
c.VendorID = "Andes"
case 0x029:
c.VendorID = "Microchip"
case 0x127:
c.VendorID = "MIPS"
case 0x489:
c.VendorID = "SiFive"
case 0x5b7:
c.VendorID = "T-Head"
}
}
case "CPU implementer":
if v, err := strconv.ParseUint(value, 0, 8); err == nil {
switch v {
@@ -256,9 +275,9 @@ func InfoWithContext(ctx context.Context) ([]InfoStat, error) {
c.VendorID = "Ampere"
}
}
case "cpu family":
case "cpu family", "marchid":
c.Family = value
case "model", "CPU part":
case "model", "CPU part", "mimpid":
c.Model = value
// if CPU is arm based, model name is found via model number. refer to: arch/arm64/kernel/cpuinfo.c
if c.VendorID == "ARM" {
@@ -271,7 +290,7 @@ func InfoWithContext(ctx context.Context) ([]InfoStat, error) {
}
}
}
case "Model Name", "model name", "cpu":
case "Model Name", "model name", "cpu", "uarch":
c.ModelName = value
if strings.Contains(value, "POWER") {
c.Model = strings.Split(value, " ")[0]
@@ -305,7 +324,7 @@ func InfoWithContext(ctx context.Context) ([]InfoStat, error) {
return ret, err
}
c.CacheSize = int32(t)
case "physical id":
case "physical id", "hart":
c.PhysicalID = value
case "core id":
c.CoreID = value
@@ -313,6 +332,11 @@ func InfoWithContext(ctx context.Context) ([]InfoStat, error) {
c.Flags = strings.FieldsFunc(value, func(r rune) bool {
return r == ',' || r == ' '
})
case "isa", "hart isa":
if len(c.Flags) != 0 || !strings.HasPrefix(value, "rv64") {
continue
}
c.Flags = riscvISAParse(value)
case "microcode":
c.Microcode = value
}
@@ -476,7 +500,7 @@ func CountsWithContext(ctx context.Context, logical bool) (int, error) {
currentInfo = make(map[string]int)
continue
}
fields := strings.Split(line, ":")
fields := strings.SplitN(line, ":", 2)
if len(fields) < 2 {
continue
}
@@ -495,3 +519,13 @@ func CountsWithContext(ctx context.Context, logical bool) (int, error) {
}
return ret, nil
}
func riscvISAParse(s string) []string {
ext := strings.Split(s, "_")
if len(ext[0]) <= 4 {
return nil
}
// the base extensions must "rv64" prefix
base := strings.Split(ext[0][4:], "")
return append(base, ext[1:]...)
}

View File

@@ -74,6 +74,12 @@ func PartitionsWithContext(_ context.Context, _ bool) ([]PartitionStat, error) {
if stat.Flags&unix.MNT_NODEV != 0 {
opts = append(opts, "nodev")
}
if stat.Flags&unix.MNT_LOCAL != 0 {
opts = append(opts, "local")
}
if stat.Flags&unix.MNT_CPROTECT != 0 {
opts = append(opts, "protect")
}
d := PartitionStat{
Device: common.ByteToString(stat.Mntfromname[:]),
Mountpoint: common.ByteToString(stat.Mntonname[:]),
@@ -152,58 +158,41 @@ func LabelWithContext(_ context.Context, _ string) (string, error) {
}
func IOCountersWithContext(_ context.Context, names ...string) (map[string]IOCountersStat, error) {
ioKit, err := common.NewLibrary(common.IOKit)
iokit, err := common.NewIOKitLib()
if err != nil {
return nil, err
}
defer ioKit.Close()
defer iokit.Close()
coreFoundation, err := common.NewLibrary(common.CoreFoundation)
corefoundation, err := common.NewCoreFoundationLib()
if err != nil {
return nil, err
}
defer coreFoundation.Close()
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)
match := iokit.IOServiceMatching("IOMedia")
cfDictionaryAddValue := common.GetFunc[common.CFDictionaryAddValueFunc](coreFoundation, common.CFDictionaryAddValueSym)
cfStringCreateWithCString := common.GetFunc[common.CFStringCreateWithCStringFunc](coreFoundation, common.CFStringCreateWithCStringSym)
cfRelease := common.GetFunc[common.CFReleaseFunc](coreFoundation, common.CFReleaseSym)
key := corefoundation.CFStringCreateWithCString(common.KCFAllocatorDefault, common.KIOMediaWholeKey, common.KCFStringEncodingUTF8)
defer corefoundation.CFRelease(uintptr(key))
kCFBooleanTruePtr, _ := coreFoundation.Dlsym("kCFBooleanTrue")
match := ioServiceMatching("IOMedia")
key := cfStringCreateWithCString(common.KCFAllocatorDefault, common.KIOMediaWholeKey, common.KCFStringEncodingUTF8)
defer cfRelease(uintptr(key))
kCFBooleanTruePtr, _ := corefoundation.Dlsym("kCFBooleanTrue")
kCFBooleanTrue := **(**uintptr)(unsafe.Pointer(&kCFBooleanTruePtr))
corefoundation.CFDictionaryAddValue(uintptr(match), uintptr(key), kCFBooleanTrue)
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 {
if status := iokit.IOServiceGetMatchingServices(common.KIOMainPortDefault, uintptr(match), &drives); status != common.KERN_SUCCESS {
return nil, fmt.Errorf("IOServiceGetMatchingServices error=%d", status)
}
defer ioObjectRelease(drives)
defer iokit.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,
iokit: iokit,
corefoundation: corefoundation,
}
stats := make([]IOCountersStat, 0, 16)
for {
d := ioIteratorNext(drives)
d := iokit.IOIteratorNext(drives)
if d <= 0 {
break
}
@@ -217,7 +206,7 @@ func IOCountersWithContext(_ context.Context, names ...string) (map[string]IOCou
stats = append(stats, *stat)
}
ioObjectRelease(d)
iokit.IOObjectRelease(d)
}
ret := make(map[string]IOCountersStat, 0)
@@ -237,9 +226,9 @@ func IOCountersWithContext(_ context.Context, names ...string) (map[string]IOCou
}
const (
kIOBSDNameKey = "BSD Name"
kIOMediaSizeKey = "Size"
kIOMediaPreferredBlockSizeKey = "Preferred Block Size"
kIOBSDNameKey = "BSD Name"
// kIOMediaSizeKey = "Size"
// kIOMediaPreferredBlockSizeKey = "Preferred Block Size"
kIOBlockStorageDriverStatisticsKey = "Statistics"
kIOBlockStorageDriverStatisticsBytesReadKey = "Bytes (Read)"
@@ -251,48 +240,34 @@ const (
)
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
iokit *common.IOKitLib
corefoundation *common.CoreFoundationLib
}
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 {
if status := i.iokit.IORegistryEntryGetParentEntry(d, common.KIOServicePlane, &parent); status != common.KERN_SUCCESS {
return nil, fmt.Errorf("IORegistryEntryGetParentEntry error=%d", status)
}
defer i.ioObjectRelease(parent)
defer i.iokit.IOObjectRelease(parent)
if !ioObjectConformsTo(parent, "IOBlockStorageDriver") {
if !i.iokit.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 {
if status := i.iokit.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))
defer i.corefoundation.CFRelease(uintptr(props))
key := i.cfStr(kIOBSDNameKey)
defer i.cfRelease(uintptr(key))
name := i.cfDictionaryGetValue(uintptr(props), uintptr(key))
defer i.corefoundation.CFRelease(uintptr(key))
name := i.corefoundation.CFDictionaryGetValue(uintptr(props), uintptr(key))
buf := common.NewCStr(cfStringGetLength(uintptr(name)))
cfStringGetCString(uintptr(name), buf, buf.Length(), common.KCFStringEncodingUTF8)
buf := common.NewCStr(i.corefoundation.CFStringGetLength(uintptr(name)))
i.corefoundation.CFStringGetCString(uintptr(name), buf, buf.Length(), common.KCFStringEncodingUTF8)
stat, err := i.fillStat(parent)
if err != nil {
@@ -308,19 +283,19 @@ func (i *ioCounters) getDriveStat(d uint32) (*IOCountersStat, error) {
func (i *ioCounters) fillStat(d uint32) (*IOCountersStat, error) {
var props unsafe.Pointer
status := i.ioRegistryEntryCreateCFProperties(d, unsafe.Pointer(&props), common.KCFAllocatorDefault, common.KNilOptions)
status := i.iokit.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))
defer i.corefoundation.CFRelease(uintptr(props))
key := i.cfStr(kIOBlockStorageDriverStatisticsKey)
defer i.cfRelease(uintptr(key))
defer i.corefoundation.CFRelease(uintptr(key))
v := i.cfDictionaryGetValue(uintptr(props), uintptr(key))
v := i.corefoundation.CFDictionaryGetValue(uintptr(props), uintptr(key))
if v == nil {
return nil, errors.New("CFDictionaryGetValue failed")
}
@@ -337,15 +312,15 @@ func (i *ioCounters) fillStat(d uint32) (*IOCountersStat, error) {
for key, off := range statstab {
s := i.cfStr(key)
if num := i.cfDictionaryGetValue(uintptr(v), uintptr(s)); num != nil {
i.cfNumberGetValue(uintptr(num), common.KCFNumberSInt64Type, uintptr(unsafe.Add(unsafe.Pointer(&stat), off)))
if num := i.corefoundation.CFDictionaryGetValue(uintptr(v), uintptr(s)); num != nil {
i.corefoundation.CFNumberGetValue(uintptr(num), common.KCFNumberSInt64Type, uintptr(unsafe.Add(unsafe.Pointer(&stat), off)))
}
i.cfRelease(uintptr(s))
i.corefoundation.CFRelease(uintptr(s))
}
return &stat, nil
}
func (i *ioCounters) cfStr(str string) unsafe.Pointer {
return i.cfStringCreateWithCString(common.KCFAllocatorDefault, str, common.KCFStringEncodingUTF8)
return i.corefoundation.CFStringCreateWithCString(common.KCFAllocatorDefault, str, common.KCFStringEncodingUTF8)
}

View File

@@ -292,7 +292,7 @@ func PartitionsWithContext(ctx context.Context, all bool) ([]PartitionStat, erro
}
// use mountinfo
ret, err = parseFieldsOnMountinfo(ctx, lines, all, fs, filename)
ret, err = parseFieldsOnMountinfo(ctx, lines, all, filename)
if err != nil {
return nil, fmt.Errorf("error parsing mountinfo file %s: %w", filename, err)
}
@@ -323,46 +323,77 @@ func parseFieldsOnMounts(lines []string, all bool, fs []string) []PartitionStat
return ret
}
func parseFieldsOnMountinfo(ctx context.Context, lines []string, all bool, fs []string, filename string) ([]PartitionStat, error) {
func parseFieldsOnMountinfo(ctx context.Context, lines []string, all bool, filename string) ([]PartitionStat, error) {
ret := make([]PartitionStat, 0, len(lines))
seenDevIDs := make(map[string]string)
for _, line := range lines {
// a line of 1/mountinfo has the following structure:
// 36 35 98:0 /mnt1 /mnt2 rw,noatime master:1 - ext3 /dev/root rw,errors=continue
// (1) (2) (3) (4) (5) (6) (7) (8) (9) (10) (11)
// See proc_pid_mountinfo(5) (proc(5) on EL)
// A line of <filename> (<procfs root>/<pid>/mountinfo) has the following structure:
// 36 35 98:0 /mnt1 /mnt2 rw,noatime master:1 - ext3 /dev/root rw,errors=continue
// (1) (2) (3) (4) (5) (6) (7) (8) (9) (10) (11)
// Documentation is unclear if (6) is optional/may not be present, so it is conditionally parsed if present.
// (7) is optional and may not be present, but this function does not currently use it.
// Documentation is unclear if (11) is optional or not but this function does not currently use it.
// split the mountinfo line by the separator hyphen
parts := strings.Split(line, " - ")
// split the mountinfo line by the separator hyphen (`(8)` above)
parts := strings.SplitN(line, " - ", 2)
if len(parts) != 2 {
return nil, fmt.Errorf("found invalid mountinfo line in file %s: %s ", filename, line)
return nil, fmt.Errorf("found invalid mountinfo line in file %s (bad parts len): %s ", filename, line)
}
fields := strings.Fields(parts[0])
if len(fields) < 5 { // field (7) is optional, field (6) may(?) be optional
return nil, fmt.Errorf("found invalid mountinfo line in file %s (bad fields(1) len): %s ", filename, line)
}
blockDeviceID := fields[2]
rootDir := fields[3]
mountPoint := fields[4]
mountOpts := strings.Split(fields[5], ",")
mountOpts := []string{}
if len(fields) >= 6 {
mountOpts = strings.Split(fields[5], ",")
}
fields = strings.Fields(parts[1])
if len(fields) < 2 {
return nil, fmt.Errorf("found invalid mountinfo line in file %s (bad fields(2) len): %s ", filename, line)
}
fsType := fields[0]
mntSrc := fields[1]
isBind := false
// Per fstab(5), the device can be any string for non-storage-backed filesystems.
if !all && !strings.HasPrefix(mntSrc, "/") {
continue
}
// Some virtual/non-storage filesystems do still have real sources (e.g. nsfs binds),
// but need to use the "root" field (field 4) instead of the "source" field (field 10).
// The "source" field is actually "*filesystem-specific" information".
device := rootDir
if strings.HasPrefix(mntSrc, "/") {
device = mntSrc
} else if rootDir == "/" {
device = "none"
}
if rootDir := fields[3]; rootDir != "" && rootDir != "/" {
if _, ok := seenDevIDs[blockDeviceID]; ok {
// Bind mount; set the underlying mount path as the device.
device = seenDevIDs[blockDeviceID]
isBind = true
mountOpts = append(mountOpts, "bind")
}
fields = strings.Fields(parts[1])
fstype := fields[0]
device := fields[1]
seenDevIDs[blockDeviceID] = mountPoint
if !all && isBind {
continue
}
d := PartitionStat{
Device: device,
Mountpoint: unescapeFstab(mountPoint),
Fstype: fstype,
Fstype: fsType,
Opts: mountOpts,
}
if !all {
if d.Device == "none" || !common.StringsHas(fs, d.Fstype) {
continue
}
}
if strings.HasPrefix(d.Device, "/dev/mapper/") {
devpath, err := filepath.EvalSymlinks(common.HostDevWithContext(ctx, strings.Replace(d.Device, "/dev", "", 1)))
if err == nil {

View File

@@ -8,7 +8,6 @@ import (
"context"
"errors"
"fmt"
"strings"
"syscall"
"unicode/utf16"
"unsafe"
@@ -239,8 +238,7 @@ func getLogicalDrives(ctx context.Context) ([]string, error) {
return nil, err // The call failed with an unexpected error
}
drivesString := windows.UTF16ToString(lpBuffer)
drives := strings.Split(drivesString, "\x00")
drives := split0(lpBuffer, int(bufferLen))
return drives, nil
}

View File

@@ -45,79 +45,91 @@ func BootTimeWithContext(ctx context.Context) (btime uint64, err error) {
return timeSince(ut), nil
}
// Parses result from uptime into minutes
// Some examples of uptime output that this command handles:
// 11:54AM up 13 mins, 1 user, load average: 2.78, 2.62, 1.79
// 12:41PM up 1 hr, 1 user, load average: 2.47, 2.85, 2.83
// 07:43PM up 5 hrs, 1 user, load average: 3.27, 2.91, 2.72
// 11:18:23 up 83 days, 18:29, 4 users, load average: 0.16, 0.03, 0.01
// 08:47PM up 2 days, 20 hrs, 1 user, load average: 2.47, 2.17, 2.17
// 01:16AM up 4 days, 29 mins, 1 user, load average: 2.29, 2.31, 2.21
// Uses ps to get the elapsed time for PID 1 in DAYS-HOURS:MINUTES:SECONDS format.
// Examples of ps -o etimes -p 1 output:
// 124-01:40:39 (with days)
// 15:03:02 (without days, hours only)
// 01:02 (just-rebooted systems, minutes and seconds)
func UptimeWithContext(ctx context.Context) (uint64, error) {
out, err := invoke.CommandWithContext(ctx, "uptime")
out, err := invoke.CommandWithContext(ctx, "ps", "-o", "etimes", "-p", "1")
if err != nil {
return 0, err
}
return parseUptime(string(out)), nil
}
func parseUptime(uptime string) uint64 {
ut := strings.Fields(uptime)
var days, hours, mins uint64
var err error
switch ut[3] {
case "day,", "days,":
days, err = strconv.ParseUint(ut[2], 10, 64)
if err != nil {
return 0
}
// day provided along with a single hour or hours
// ie: up 2 days, 20 hrs,
if ut[5] == "hr," || ut[5] == "hrs," {
hours, err = strconv.ParseUint(ut[4], 10, 64)
if err != nil {
return 0
}
}
// mins provided along with a single min or mins
// ie: up 4 days, 29 mins,
if ut[5] == "min," || ut[5] == "mins," {
mins, err = strconv.ParseUint(ut[4], 10, 64)
if err != nil {
return 0
}
}
// alternatively day provided with hh:mm
// ie: up 83 days, 18:29
if strings.Contains(ut[4], ":") {
hm := strings.Split(ut[4], ":")
hours, err = strconv.ParseUint(hm[0], 10, 64)
if err != nil {
return 0
}
mins, err = strconv.ParseUint(strings.Trim(hm[1], ","), 10, 64)
if err != nil {
return 0
}
}
case "hr,", "hrs,":
hours, err = strconv.ParseUint(ut[2], 10, 64)
if err != nil {
return 0
}
case "min,", "mins,":
mins, err = strconv.ParseUint(ut[2], 10, 64)
if err != nil {
return 0
}
lines := strings.Split(strings.TrimSpace(string(out)), "\n")
if len(lines) < 2 {
return 0, errors.New("ps output has fewer than 2 rows")
}
return (days * 24 * 60) + (hours * 60) + mins
// Extract the etimes value from the second row, trimming whitespace
etimes := strings.TrimSpace(lines[1])
return parseUptime(etimes), nil
}
// Parses etimes output from ps command into total minutes.
// Handles formats like:
// - "124-01:40:39" (DAYS-HOURS:MINUTES:SECONDS)
// - "15:03:02" (HOURS:MINUTES:SECONDS)
// - "01:02" (MINUTES:SECONDS, from just-rebooted systems)
func parseUptime(etimes string) uint64 {
var days, hours, mins, secs uint64
// Check if days component is present (contains a dash)
if strings.Contains(etimes, "-") {
parts := strings.Split(etimes, "-")
if len(parts) != 2 {
return 0
}
var err error
days, err = strconv.ParseUint(parts[0], 10, 64)
if err != nil {
return 0
}
// Parse the HH:MM:SS portion
etimes = parts[1]
}
// Parse time portions (either HH:MM:SS or MM:SS)
timeParts := strings.Split(etimes, ":")
switch len(timeParts) {
case 3:
// HH:MM:SS format
var err error
hours, err = strconv.ParseUint(timeParts[0], 10, 64)
if err != nil {
return 0
}
mins, err = strconv.ParseUint(timeParts[1], 10, 64)
if err != nil {
return 0
}
secs, err = strconv.ParseUint(timeParts[2], 10, 64)
if err != nil {
return 0
}
case 2:
// MM:SS format (just-rebooted systems)
var err error
mins, err = strconv.ParseUint(timeParts[0], 10, 64)
if err != nil {
return 0
}
secs, err = strconv.ParseUint(timeParts[1], 10, 64)
if err != nil {
return 0
}
default:
return 0
}
// Convert to total minutes
totalMinutes := (days * 24 * 60) + (hours * 60) + mins + (secs / 60)
return totalMinutes
}
// This is a weak implementation due to the limitations on retrieving this data in AIX

View File

@@ -9,90 +9,314 @@ import (
"unsafe"
"github.com/ebitengine/purego"
"golang.org/x/sys/unix"
)
func CallSyscall(mib []int32) ([]byte, uint64, error) {
miblen := uint64(len(mib))
// get required buffer size
length := uint64(0)
_, _, err := unix.Syscall6(
202, // unix.SYS___SYSCTL https://github.com/golang/sys/blob/76b94024e4b621e672466e8db3d7f084e7ddcad2/unix/zsysnum_darwin_amd64.go#L146
uintptr(unsafe.Pointer(&mib[0])),
uintptr(miblen),
0,
uintptr(unsafe.Pointer(&length)),
0,
0)
if err != 0 {
var b []byte
return b, length, err
}
if length == 0 {
var b []byte
return b, length, err
}
// get proc info itself
buf := make([]byte, length)
_, _, err = unix.Syscall6(
202, // unix.SYS___SYSCTL https://github.com/golang/sys/blob/76b94024e4b621e672466e8db3d7f084e7ddcad2/unix/zsysnum_darwin_amd64.go#L146
uintptr(unsafe.Pointer(&mib[0])),
uintptr(miblen),
uintptr(unsafe.Pointer(&buf[0])),
uintptr(unsafe.Pointer(&length)),
0,
0)
if err != 0 {
return buf, length, err
}
return buf, length, nil
}
// Library represents a dynamic library loaded by purego.
type Library struct {
addr uintptr
path string
close func()
type library struct {
handle uintptr
fnMap map[string]any
}
// library paths
const (
IOKit = "/System/Library/Frameworks/IOKit.framework/IOKit"
CoreFoundation = "/System/Library/Frameworks/CoreFoundation.framework/CoreFoundation"
System = "/usr/lib/libSystem.B.dylib"
IOKitLibPath = "/System/Library/Frameworks/IOKit.framework/IOKit"
CoreFoundationLibPath = "/System/Library/Frameworks/CoreFoundation.framework/CoreFoundation"
SystemLibPath = "/usr/lib/libSystem.B.dylib"
)
func NewLibrary(path string) (*Library, error) {
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,
return &library{
handle: lib,
fnMap: make(map[string]any),
}, nil
}
func (lib *Library) Dlsym(symbol string) (uintptr, error) {
return purego.Dlsym(lib.addr, symbol)
func (lib *library) Dlsym(symbol string) (uintptr, error) {
return purego.Dlsym(lib.handle, symbol)
}
func GetFunc[T any](lib *Library, symbol string) T {
var fptr T
purego.RegisterLibFunc(&fptr, lib.addr, symbol)
return fptr
func getFunc[T any](lib *library, symbol string) T {
var dlfun *dlFunc[T]
if f, ok := lib.fnMap[symbol].(*dlFunc[T]); ok {
dlfun = f
} else {
dlfun = newDlfunc[T](symbol)
dlfun.init(lib.handle)
lib.fnMap[symbol] = dlfun
}
return dlfun.fn
}
func (lib *Library) Close() {
lib.close()
func (lib *library) Close() {
purego.Dlclose(lib.handle)
}
type dlFunc[T any] struct {
sym string
fn T
}
func (d *dlFunc[T]) init(handle uintptr) {
purego.RegisterLibFunc(&d.fn, handle, d.sym)
}
func newDlfunc[T any](sym string) *dlFunc[T] {
return &dlFunc[T]{sym: sym}
}
type CoreFoundationLib struct {
*library
}
func NewCoreFoundationLib() (*CoreFoundationLib, error) {
library, err := newLibrary(CoreFoundationLibPath)
if err != nil {
return nil, err
}
return &CoreFoundationLib{library}, nil
}
func (c *CoreFoundationLib) CFGetTypeID(cf uintptr) int32 {
fn := getFunc[CFGetTypeIDFunc](c.library, "CFGetTypeID")
return fn(cf)
}
func (c *CoreFoundationLib) CFNumberCreate(allocator uintptr, theType int32, valuePtr uintptr) unsafe.Pointer {
fn := getFunc[CFNumberCreateFunc](c.library, "CFNumberCreate")
return fn(allocator, theType, valuePtr)
}
func (c *CoreFoundationLib) CFNumberGetValue(num uintptr, theType int32, valuePtr uintptr) bool {
fn := getFunc[CFNumberGetValueFunc](c.library, "CFNumberGetValue")
return fn(num, theType, valuePtr)
}
func (c *CoreFoundationLib) CFDictionaryCreate(allocator uintptr, keys, values *unsafe.Pointer, numValues int32,
keyCallBacks, valueCallBacks uintptr,
) unsafe.Pointer {
fn := getFunc[CFDictionaryCreateFunc](c.library, "CFDictionaryCreate")
return fn(allocator, keys, values, numValues, keyCallBacks, valueCallBacks)
}
func (c *CoreFoundationLib) CFDictionaryAddValue(theDict, key, value uintptr) {
fn := getFunc[CFDictionaryAddValueFunc](c.library, "CFDictionaryAddValue")
fn(theDict, key, value)
}
func (c *CoreFoundationLib) CFDictionaryGetValue(theDict, key uintptr) unsafe.Pointer {
fn := getFunc[CFDictionaryGetValueFunc](c.library, "CFDictionaryGetValue")
return fn(theDict, key)
}
func (c *CoreFoundationLib) CFArrayGetCount(theArray uintptr) int32 {
fn := getFunc[CFArrayGetCountFunc](c.library, "CFArrayGetCount")
return fn(theArray)
}
func (c *CoreFoundationLib) CFArrayGetValueAtIndex(theArray uintptr, index int32) unsafe.Pointer {
fn := getFunc[CFArrayGetValueAtIndexFunc](c.library, "CFArrayGetValueAtIndex")
return fn(theArray, index)
}
func (c *CoreFoundationLib) CFStringCreateMutable(alloc uintptr, maxLength int32) unsafe.Pointer {
fn := getFunc[CFStringCreateMutableFunc](c.library, "CFStringCreateMutable")
return fn(alloc, maxLength)
}
func (c *CoreFoundationLib) CFStringGetLength(theString uintptr) int32 {
fn := getFunc[CFStringGetLengthFunc](c.library, "CFStringGetLength")
return fn(theString)
}
func (c *CoreFoundationLib) CFStringGetCString(theString uintptr, buffer CStr, bufferSize int32, encoding uint32) {
fn := getFunc[CFStringGetCStringFunc](c.library, "CFStringGetCString")
fn(theString, buffer, bufferSize, encoding)
}
func (c *CoreFoundationLib) CFStringCreateWithCString(alloc uintptr, cStr string, encoding uint32) unsafe.Pointer {
fn := getFunc[CFStringCreateWithCStringFunc](c.library, "CFStringCreateWithCString")
return fn(alloc, cStr, encoding)
}
func (c *CoreFoundationLib) CFDataGetLength(theData uintptr) int32 {
fn := getFunc[CFDataGetLengthFunc](c.library, "CFDataGetLength")
return fn(theData)
}
func (c *CoreFoundationLib) CFDataGetBytePtr(theData uintptr) unsafe.Pointer {
fn := getFunc[CFDataGetBytePtrFunc](c.library, "CFDataGetBytePtr")
return fn(theData)
}
func (c *CoreFoundationLib) CFRelease(cf uintptr) {
fn := getFunc[CFReleaseFunc](c.library, "CFRelease")
fn(cf)
}
type IOKitLib struct {
*library
}
func NewIOKitLib() (*IOKitLib, error) {
library, err := newLibrary(IOKitLibPath)
if err != nil {
return nil, err
}
return &IOKitLib{library}, nil
}
func (l *IOKitLib) IOServiceGetMatchingService(mainPort uint32, matching uintptr) uint32 {
fn := getFunc[IOServiceGetMatchingServiceFunc](l.library, "IOServiceGetMatchingService")
return fn(mainPort, matching)
}
func (l *IOKitLib) IOServiceGetMatchingServices(mainPort uint32, matching uintptr, existing *uint32) int {
fn := getFunc[IOServiceGetMatchingServicesFunc](l.library, "IOServiceGetMatchingServices")
return fn(mainPort, matching, existing)
}
func (l *IOKitLib) IOServiceMatching(name string) unsafe.Pointer {
fn := getFunc[IOServiceMatchingFunc](l.library, "IOServiceMatching")
return fn(name)
}
func (l *IOKitLib) IOServiceOpen(service, owningTask, connType uint32, connect *uint32) int {
fn := getFunc[IOServiceOpenFunc](l.library, "IOServiceOpen")
return fn(service, owningTask, connType, connect)
}
func (l *IOKitLib) IOServiceClose(connect uint32) int {
fn := getFunc[IOServiceCloseFunc](l.library, "IOServiceClose")
return fn(connect)
}
func (l *IOKitLib) IOIteratorNext(iterator uint32) uint32 {
fn := getFunc[IOIteratorNextFunc](l.library, "IOIteratorNext")
return fn(iterator)
}
func (l *IOKitLib) IORegistryEntryGetName(entry uint32, name CStr) int {
fn := getFunc[IORegistryEntryGetNameFunc](l.library, "IORegistryEntryGetName")
return fn(entry, name)
}
func (l *IOKitLib) IORegistryEntryGetParentEntry(entry uint32, plane string, parent *uint32) int {
fn := getFunc[IORegistryEntryGetParentEntryFunc](l.library, "IORegistryEntryGetParentEntry")
return fn(entry, plane, parent)
}
func (l *IOKitLib) IORegistryEntryCreateCFProperty(entry uint32, key, allocator uintptr, options uint32) unsafe.Pointer {
fn := getFunc[IORegistryEntryCreateCFPropertyFunc](l.library, "IORegistryEntryCreateCFProperty")
return fn(entry, key, allocator, options)
}
func (l *IOKitLib) IORegistryEntryCreateCFProperties(entry uint32, properties unsafe.Pointer, allocator uintptr, options uint32) int {
fn := getFunc[IORegistryEntryCreateCFPropertiesFunc](l.library, "IORegistryEntryCreateCFProperties")
return fn(entry, properties, allocator, options)
}
func (l *IOKitLib) IOObjectConformsTo(object uint32, className string) bool {
fn := getFunc[IOObjectConformsToFunc](l.library, "IOObjectConformsTo")
return fn(object, className)
}
func (l *IOKitLib) IOObjectRelease(object uint32) int {
fn := getFunc[IOObjectReleaseFunc](l.library, "IOObjectRelease")
return fn(object)
}
func (l *IOKitLib) IOConnectCallStructMethod(connection, selector uint32, inputStruct, inputStructCnt, outputStruct uintptr, outputStructCnt *uintptr) int {
fn := getFunc[IOConnectCallStructMethodFunc](l.library, "IOConnectCallStructMethod")
return fn(connection, selector, inputStruct, inputStructCnt, outputStruct, outputStructCnt)
}
func (l *IOKitLib) IOHIDEventSystemClientCreate(allocator uintptr) unsafe.Pointer {
fn := getFunc[IOHIDEventSystemClientCreateFunc](l.library, "IOHIDEventSystemClientCreate")
return fn(allocator)
}
func (l *IOKitLib) IOHIDEventSystemClientSetMatching(client, match uintptr) int {
fn := getFunc[IOHIDEventSystemClientSetMatchingFunc](l.library, "IOHIDEventSystemClientSetMatching")
return fn(client, match)
}
func (l *IOKitLib) IOHIDServiceClientCopyEvent(service uintptr, eventType int64, options int32, timeout int64) unsafe.Pointer {
fn := getFunc[IOHIDServiceClientCopyEventFunc](l.library, "IOHIDServiceClientCopyEvent")
return fn(service, eventType, options, timeout)
}
func (l *IOKitLib) IOHIDServiceClientCopyProperty(service, property uintptr) unsafe.Pointer {
fn := getFunc[IOHIDServiceClientCopyPropertyFunc](l.library, "IOHIDServiceClientCopyProperty")
return fn(service, property)
}
func (l *IOKitLib) IOHIDEventGetFloatValue(event uintptr, field int32) float64 {
fn := getFunc[IOHIDEventGetFloatValueFunc](l.library, "IOHIDEventGetFloatValue")
return fn(event, field)
}
func (l *IOKitLib) IOHIDEventSystemClientCopyServices(client uintptr) unsafe.Pointer {
fn := getFunc[IOHIDEventSystemClientCopyServicesFunc](l.library, "IOHIDEventSystemClientCopyServices")
return fn(client)
}
type SystemLib struct {
*library
}
func NewSystemLib() (*SystemLib, error) {
library, err := newLibrary(SystemLibPath)
if err != nil {
return nil, err
}
return &SystemLib{library}, nil
}
func (s *SystemLib) HostProcessorInfo(host uint32, flavor int32, outProcessorCount *uint32, outProcessorInfo uintptr,
outProcessorInfoCnt *uint32,
) int {
fn := getFunc[HostProcessorInfoFunc](s.library, "host_processor_info")
return fn(host, flavor, outProcessorCount, outProcessorInfo, outProcessorInfoCnt)
}
func (s *SystemLib) HostStatistics(host uint32, flavor int32, hostInfoOut uintptr, hostInfoOutCnt *uint32) int {
fn := getFunc[HostStatisticsFunc](s.library, "host_statistics")
return fn(host, flavor, hostInfoOut, hostInfoOutCnt)
}
func (s *SystemLib) MachHostSelf() uint32 {
fn := getFunc[MachHostSelfFunc](s.library, "mach_host_self")
return fn()
}
func (s *SystemLib) MachTaskSelf() uint32 {
fn := getFunc[MachTaskSelfFunc](s.library, "mach_task_self")
return fn()
}
func (s *SystemLib) MachTimeBaseInfo(info uintptr) int {
fn := getFunc[MachTimeBaseInfoFunc](s.library, "mach_timebase_info")
return fn(info)
}
func (s *SystemLib) VMDeallocate(targetTask uint32, vmAddress, vmSize uintptr) int {
fn := getFunc[VMDeallocateFunc](s.library, "vm_deallocate")
return fn(targetTask, vmAddress, vmSize)
}
func (s *SystemLib) ProcPidPath(pid int32, buffer uintptr, bufferSize uint32) int32 {
fn := getFunc[ProcPidPathFunc](s.library, "proc_pidpath")
return fn(pid, buffer, bufferSize)
}
func (s *SystemLib) ProcPidInfo(pid, flavor int32, arg uint64, buffer uintptr, bufferSize int32) int32 {
fn := getFunc[ProcPidInfoFunc](s.library, "proc_pidinfo")
return fn(pid, flavor, arg, buffer, bufferSize)
}
// status codes
@@ -100,7 +324,7 @@ const (
KERN_SUCCESS = 0
)
// IOKit functions and symbols.
// IOKit types and constants.
type (
IOServiceGetMatchingServiceFunc func(mainPort uint32, matching uintptr) uint32
IOServiceGetMatchingServicesFunc func(mainPort uint32, matching uintptr, existing *uint32) int
@@ -125,29 +349,6 @@ type (
IOHIDEventSystemClientCopyServicesFunc func(client uintptr) unsafe.Pointer
)
const (
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"
IOHIDServiceClientCopyEventSym = "IOHIDServiceClientCopyEvent"
IOHIDServiceClientCopyPropertySym = "IOHIDServiceClientCopyProperty"
IOHIDEventGetFloatValueSym = "IOHIDEventGetFloatValue"
IOHIDEventSystemClientCopyServicesSym = "IOHIDEventSystemClientCopyServices"
)
const (
KIOMainPortDefault = 0
@@ -161,7 +362,7 @@ const (
KIOServicePlane = "IOService"
)
// CoreFoundation functions and symbols.
// CoreFoundation types and constants.
type (
CFGetTypeIDFunc func(cf uintptr) int32
CFNumberCreateFunc func(allocator uintptr, theType int32, valuePtr uintptr) unsafe.Pointer
@@ -181,24 +382,6 @@ type (
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
@@ -206,7 +389,7 @@ const (
KCFAllocatorDefault = 0
)
// Kernel functions and symbols.
// libSystem types and constants.
type MachTimeBaseInfo struct {
Numer uint32
Denom uint32
@@ -232,17 +415,12 @@ const (
)
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
@@ -256,6 +434,7 @@ const (
const (
MAXPATHLEN = 1024
PROC_PIDLISTFDS = 1
PROC_PIDPATHINFO_MAXSIZE = 4 * MAXPATHLEN
PROC_PIDTASKINFO = 4
PROC_PIDVNODEPATHINFO = 9
@@ -263,9 +442,8 @@ const (
// SMC represents a SMC instance.
type SMC struct {
lib *Library
conn uint32
callStruct IOConnectCallStructMethodFunc
lib *IOKitLib
conn uint32
}
const ioServiceSMC = "AppleSMC"
@@ -287,47 +465,39 @@ const (
KSMCKeyNotFound = 132
)
func NewSMC(ioKit *Library) (*SMC, error) {
if ioKit.path != IOKit {
return nil, errors.New("library is not IOKit")
func NewSMC() (*SMC, error) {
iokit, err := NewIOKitLib()
if err != nil {
return nil, err
}
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)))
service := iokit.IOServiceGetMatchingService(0, uintptr(iokit.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 {
machTaskSelf := getFunc[MachTaskSelfFunc](iokit.library, "mach_task_self")
if result := iokit.IOServiceOpen(service, machTaskSelf(), 0, &conn); result != 0 {
return nil, errors.New("ERROR: IOServiceOpen failed")
}
ioObjectRelease(service)
iokit.IOObjectRelease(service)
return &SMC{
lib: ioKit,
conn: conn,
callStruct: ioConnectCallStructMethod,
lib: iokit,
conn: conn,
}, nil
}
func (s *SMC) CallStruct(selector uint32, inputStruct, inputStructCnt, outputStruct uintptr, outputStructCnt *uintptr) int {
return s.callStruct(s.conn, selector, inputStruct, inputStructCnt, outputStruct, outputStructCnt)
return s.lib.IOConnectCallStructMethod(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 {
if result := s.lib.IOServiceClose(s.conn); result != 0 {
return errors.New("ERROR: IOServiceClose failed")
}
s.lib.Close()
return nil
}

View File

@@ -14,6 +14,7 @@ type ExVirtualMemory struct {
ActiveAnon uint64 `json:"activeanon"`
InactiveAnon uint64 `json:"inactiveanon"`
Unevictable uint64 `json:"unevictable"`
Percpu uint64 `json:"percpu"`
}
func (v ExVirtualMemory) String() string {

View File

@@ -48,10 +48,11 @@ type VirtualMemoryStat struct {
Laundry uint64 `json:"laundry"`
// Linux specific numbers
// https://docs.redhat.com/en/documentation/red_hat_enterprise_linux/6/html/deployment_guide/s2-proc-meminfo
// https://blogs.oracle.com/linux/understanding-linux-kernel-memory-statistics
// https://www.kernel.org/doc/Documentation/filesystems/proc.txt
// https://www.kernel.org/doc/Documentation/vm/overcommit-accounting
// https://www.kernel.org/doc/Documentation/vm/transhuge.txt
//
Buffers uint64 `json:"buffers"`
Cached uint64 `json:"cached"`
WriteBack uint64 `json:"writeBack"`

View File

@@ -85,26 +85,23 @@ func VirtualMemory() (*VirtualMemoryStat, error) {
}
func VirtualMemoryWithContext(_ context.Context) (*VirtualMemoryStat, error) {
machLib, err := common.NewLibrary(common.System)
sys, err := common.NewSystemLib()
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)
defer sys.Close()
count := uint32(common.HOST_VM_INFO_COUNT)
var vmstat vmStatisticsData
status := hostStatistics(machHostSelf(), common.HOST_VM_INFO,
status := sys.HostStatistics(sys.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")
pageSizeAddr, _ := sys.Dlsym("vm_kernel_page_size")
pageSize := **(**uint64)(unsafe.Pointer(&pageSizeAddr))
total, err := getHwMemsize()
if err != nil {

View File

@@ -138,6 +138,12 @@ func fillFromMeminfoWithContext(ctx context.Context) (*VirtualMemoryStat, *ExVir
return ret, retEx, err
}
retEx.Unevictable = t * 1024
case "Percpu":
t, err := strconv.ParseUint(value, 10, 64)
if err != nil {
return ret, retEx, err
}
retEx.Percpu = t * 1024
case "Writeback":
t, err := strconv.ParseUint(value, 10, 64)
if err != nil {

View File

@@ -36,10 +36,6 @@ func (*Process) NumCtxSwitchesWithContext(_ context.Context) (*NumCtxSwitchesSta
return nil, common.ErrNotImplementedError
}
func (*Process) NumFDsWithContext(_ context.Context) (int32, error) {
return 0, common.ErrNotImplementedError
}
func (*Process) CPUAffinityWithContext(_ context.Context) ([]int32, error) {
return nil, common.ErrNotImplementedError
}

View File

@@ -280,31 +280,21 @@ func callPsWithContext(ctx context.Context, arg string, pid int32, threadOption,
}
type dlFuncs struct {
lib *common.Library
procPidPath common.ProcPidPathFunc
procPidInfo common.ProcPidInfoFunc
machTimeBaseInfo common.MachTimeBaseInfoFunc
lib *common.SystemLib
}
func loadProcFuncs() (*dlFuncs, error) {
lib, err := common.NewLibrary(common.System)
lib, err := common.NewSystemLib()
if err != nil {
return nil, err
}
return &dlFuncs{
lib: lib,
procPidPath: common.GetFunc[common.ProcPidPathFunc](lib, common.ProcPidPathSym),
procPidInfo: common.GetFunc[common.ProcPidInfoFunc](lib, common.ProcPidInfoSym),
machTimeBaseInfo: common.GetFunc[common.MachTimeBaseInfoFunc](lib, common.MachTimeBaseInfoSym),
}, nil
return &dlFuncs{lib}, err
}
func (f *dlFuncs) getTimeScaleToNanoSeconds() float64 {
var timeBaseInfo common.MachTimeBaseInfo
f.machTimeBaseInfo(uintptr(unsafe.Pointer(&timeBaseInfo)))
f.lib.MachTimeBaseInfo(uintptr(unsafe.Pointer(&timeBaseInfo)))
return float64(timeBaseInfo.Numer) / float64(timeBaseInfo.Denom)
}
@@ -321,7 +311,7 @@ func (p *Process) ExeWithContext(_ context.Context) (string, error) {
defer funcs.Close()
buf := common.NewCStr(common.PROC_PIDPATHINFO_MAXSIZE)
ret := funcs.procPidPath(p.Pid, buf.Addr(), common.PROC_PIDPATHINFO_MAXSIZE)
ret := funcs.lib.ProcPidPath(p.Pid, buf.Addr(), common.PROC_PIDPATHINFO_MAXSIZE)
if ret <= 0 {
return "", fmt.Errorf("unknown error: proc_pidpath returned %d", ret)
@@ -330,13 +320,6 @@ func (p *Process) ExeWithContext(_ context.Context) (string, error) {
return buf.GoString(), 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
@@ -355,7 +338,7 @@ func (p *Process) CwdWithContext(_ context.Context) (string, error) {
var vpi vnodePathInfo
const vpiSize = int32(unsafe.Sizeof(vpi))
ret := funcs.procPidInfo(p.Pid, common.PROC_PIDVNODEPATHINFO, 0, uintptr(unsafe.Pointer(&vpi)), vpiSize)
ret := funcs.lib.ProcPidInfo(p.Pid, common.PROC_PIDVNODEPATHINFO, 0, uintptr(unsafe.Pointer(&vpi)), vpiSize)
errno, _ := funcs.lib.Dlsym("errno")
err = *(**unix.Errno)(unsafe.Pointer(&errno))
if errors.Is(err, unix.EPERM) {
@@ -369,11 +352,11 @@ func (p *Process) CwdWithContext(_ context.Context) (string, error) {
if ret != vpiSize {
return "", fmt.Errorf("too few bytes; expected %d, got %d", vpiSize, ret)
}
return common.GoString(&vpi.vipPath[0]), nil
return common.GoString((*byte)(unsafe.Pointer(&vpi.Cdir.Path[0]))), nil
}
func procArgs(pid int32) ([]byte, int, error) {
procargs, _, err := common.CallSyscall([]int32{common.CTL_KERN, common.KERN_PROCARGS2, pid})
procargs, err := unix.SysctlRaw("kern.procargs2", int(pid))
if err != nil {
return nil, 0, err
}
@@ -447,7 +430,7 @@ func (p *Process) NumThreadsWithContext(_ context.Context) (int32, error) {
defer funcs.Close()
var ti ProcTaskInfo
funcs.procPidInfo(p.Pid, common.PROC_PIDTASKINFO, 0, uintptr(unsafe.Pointer(&ti)), int32(unsafe.Sizeof(ti)))
funcs.lib.ProcPidInfo(p.Pid, common.PROC_PIDTASKINFO, 0, uintptr(unsafe.Pointer(&ti)), int32(unsafe.Sizeof(ti)))
return int32(ti.Threadnum), nil
}
@@ -460,7 +443,7 @@ func (p *Process) TimesWithContext(_ context.Context) (*cpu.TimesStat, error) {
defer funcs.Close()
var ti ProcTaskInfo
funcs.procPidInfo(p.Pid, common.PROC_PIDTASKINFO, 0, uintptr(unsafe.Pointer(&ti)), int32(unsafe.Sizeof(ti)))
funcs.lib.ProcPidInfo(p.Pid, common.PROC_PIDTASKINFO, 0, uintptr(unsafe.Pointer(&ti)), int32(unsafe.Sizeof(ti)))
timescaleToNanoSeconds := funcs.getTimeScaleToNanoSeconds()
ret := &cpu.TimesStat{
@@ -479,7 +462,7 @@ func (p *Process) MemoryInfoWithContext(_ context.Context) (*MemoryInfoStat, err
defer funcs.Close()
var ti ProcTaskInfo
funcs.procPidInfo(p.Pid, common.PROC_PIDTASKINFO, 0, uintptr(unsafe.Pointer(&ti)), int32(unsafe.Sizeof(ti)))
funcs.lib.ProcPidInfo(p.Pid, common.PROC_PIDTASKINFO, 0, uintptr(unsafe.Pointer(&ti)), int32(unsafe.Sizeof(ti)))
ret := &MemoryInfoStat{
RSS: uint64(ti.Resident_size),
@@ -488,3 +471,54 @@ func (p *Process) MemoryInfoWithContext(_ context.Context) (*MemoryInfoStat, err
}
return ret, nil
}
// procFDInfo represents a file descriptor entry from sys/proc_info.h
type procFDInfo struct {
ProcFd int32
ProcFdtype uint32
}
// NumFDsWithContext returns the number of file descriptors used by the process.
// It uses proc_pidinfo with PROC_PIDLISTFDS to query the kernel for the count
// of open file descriptors. The method makes a single syscall and calculates
// the count from the buffer size returned by the kernel.
func (p *Process) NumFDsWithContext(_ context.Context) (int32, error) {
funcs, err := loadProcFuncs()
if err != nil {
return 0, err
}
defer funcs.Close()
// First call: get required buffer size
bufferSize := funcs.lib.ProcPidInfo(
p.Pid,
common.PROC_PIDLISTFDS,
0,
0, // NULL buffer
0, // 0 size
)
if bufferSize <= 0 {
return 0, fmt.Errorf("unknown error: proc_pidinfo returned %d", bufferSize)
}
// Allocate buffer of the required size
const sizeofProcFDInfo = int32(unsafe.Sizeof(procFDInfo{}))
numEntries := bufferSize / sizeofProcFDInfo
buf := make([]procFDInfo, numEntries)
// Second call: get actual data
ret := funcs.lib.ProcPidInfo(
p.Pid,
common.PROC_PIDLISTFDS,
0,
uintptr(unsafe.Pointer(&buf[0])), // Real buffer
bufferSize, // Size from first call
)
if ret <= 0 {
return 0, fmt.Errorf("unknown error: proc_pidinfo returned %d", ret)
}
// Calculate actual number of FDs returned
numFDs := ret / sizeofProcFDInfo
return numFDs, nil
}

View File

@@ -233,6 +233,51 @@ type ProcTaskInfo struct {
Priority int32
}
type vinfoStat struct {
Dev uint32
Mode uint16
Nlink uint16
Ino uint64
Uid uint32
Gid uint32
Atime int64
Atimensec int64
Mtime int64
Mtimensec int64
Ctime int64
Ctimensec int64
Birthtime int64
Birthtimensec int64
Size int64
Blocks int64
Blksize int32
Flags uint32
Gen uint32
Rdev uint32
Qspare [2]int64
}
type fsid struct {
Val [2]int32
}
type vnodeInfo struct {
Stat vinfoStat
Type int32
Pad int32
Fsid fsid
}
type vnodeInfoPath struct {
Vi vnodeInfo
Path [1024]int8
}
type vnodePathInfo struct {
Cdir vnodeInfoPath
Rdir vnodeInfoPath
}
type AuditinfoAddr struct {
Auid uint32
Mask AuMask

View File

@@ -211,6 +211,51 @@ type ProcTaskInfo struct {
Priority int32
}
type vinfoStat struct {
Dev uint32
Mode uint16
Nlink uint16
Ino uint64
Uid uint32
Gid uint32
Atime int64
Atimensec int64
Mtime int64
Mtimensec int64
Ctime int64
Ctimensec int64
Birthtime int64
Birthtimensec int64
Size int64
Blocks int64
Blksize int32
Flags uint32
Gen uint32
Rdev uint32
Qspare [2]int64
}
type fsid struct {
Val [2]int32
}
type vnodeInfo struct {
Stat vinfoStat
Type int32
Pad int32
Fsid fsid
}
type vnodeInfoPath struct {
Vi vnodeInfo
Path [1024]int8
}
type vnodePathInfo struct {
Cdir vnodeInfoPath
Rdir vnodeInfoPath
}
type AuditinfoAddr struct {
Auid uint32
Mask AuMask

View File

@@ -344,6 +344,10 @@ func ProcessesWithContext(ctx context.Context) ([]*Process, error) {
return results, nil
}
func (*Process) NumFDsWithContext(_ context.Context) (int32, error) {
return 0, common.ErrNotImplementedError
}
func (p *Process) getKProc() (*KinfoProc, error) {
mib := []int32{CTLKern, KernProc, KernProcPID, p.Pid}

View File

@@ -342,6 +342,10 @@ func ProcessesWithContext(ctx context.Context) ([]*Process, error) {
return results, nil
}
func (*Process) NumFDsWithContext(_ context.Context) (int32, error) {
return 0, common.ErrNotImplementedError
}
func (p *Process) getKProc() (*KinfoProc, error) {
buf, length, err := callKernProcSyscall(KernProcPID, p.Pid)
if err != nil {

2
vendor/modules.txt vendored
View File

@@ -281,7 +281,7 @@ github.com/prometheus/prometheus/model/value
github.com/prometheus/statsd_exporter/pkg/level
github.com/prometheus/statsd_exporter/pkg/mapper
github.com/prometheus/statsd_exporter/pkg/mapper/fsm
# github.com/shirou/gopsutil/v4 v4.25.11
# github.com/shirou/gopsutil/v4 v4.25.12
## explicit; go 1.24.0
github.com/shirou/gopsutil/v4/common
github.com/shirou/gopsutil/v4/cpu