update disk & cpu & process

pull/1702/head
uubulb 6 months ago
parent 701a74be41
commit 9e6efdb991

@ -1,6 +1,6 @@
# gopsutil: psutil for golang # 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 This is a port of psutil (https://github.com/giampaolo/psutil). The
challenge is porting all psutil functions on some architectures. challenge is porting all psutil functions on some architectures.

@ -12,13 +12,14 @@ type EnvKeyType string
var EnvKey = EnvKeyType("env") var EnvKey = EnvKeyType("env")
const ( const (
HostProcEnvKey EnvKeyType = "HOST_PROC" HostProcEnvKey EnvKeyType = "HOST_PROC"
HostSysEnvKey EnvKeyType = "HOST_SYS" HostSysEnvKey EnvKeyType = "HOST_SYS"
HostEtcEnvKey EnvKeyType = "HOST_ETC" HostEtcEnvKey EnvKeyType = "HOST_ETC"
HostVarEnvKey EnvKeyType = "HOST_VAR" HostVarEnvKey EnvKeyType = "HOST_VAR"
HostRunEnvKey EnvKeyType = "HOST_RUN" HostRunEnvKey EnvKeyType = "HOST_RUN"
HostDevEnvKey EnvKeyType = "HOST_DEV" HostDevEnvKey EnvKeyType = "HOST_DEV"
HostRootEnvKey EnvKeyType = "HOST_ROOT" HostRootEnvKey EnvKeyType = "HOST_ROOT"
HostProcMountinfo EnvKeyType = "HOST_PROC_MOUNTINFO"
) )
type EnvMap map[EnvKeyType]string type EnvMap map[EnvKeyType]string

@ -10,7 +10,6 @@ import (
"strings" "strings"
"unsafe" "unsafe"
"github.com/shoenig/go-m1cpu"
"github.com/tklauser/go-sysconf" "github.com/tklauser/go-sysconf"
"golang.org/x/sys/unix" "golang.org/x/sys/unix"
@ -61,7 +60,7 @@ func Times(percpu bool) ([]TimesStat, error) {
} }
func TimesWithContext(ctx context.Context, 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 { if err != nil {
return nil, err return nil, err
} }
@ -114,15 +113,9 @@ func InfoWithContext(ctx context.Context) ([]InfoStat, error) {
c.CacheSize = int32(cacheSize) c.CacheSize = int32(cacheSize)
c.VendorID, _ = unix.Sysctl("machdep.cpu.vendor") c.VendorID, _ = unix.Sysctl("machdep.cpu.vendor")
if m1cpu.IsAppleSilicon() { v, err := getFrequency()
c.Mhz = float64(m1cpu.PCoreHz() / 1_000_000) if err == nil {
} else { c.Mhz = v
// 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
}
} }
return append(ret, c), nil return append(ret, c), nil

@ -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
}

@ -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
}

@ -5,13 +5,12 @@ package cpu
import ( import (
"os" "os"
"runtime"
"testing" "testing"
"github.com/shoenig/go-m1cpu"
) )
func TestInfo_AppleSilicon(t *testing.T) { func TestInfo_AppleSilicon(t *testing.T) {
if !m1cpu.IsAppleSilicon() { if runtime.GOARCH != "arm64" {
t.Skip("wrong cpu type") t.Skip("wrong cpu type")
} }

@ -5,6 +5,8 @@ package disk
import ( import (
"context" "context"
"fmt"
"unsafe"
"golang.org/x/sys/unix" "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) { func LabelWithContext(ctx context.Context, name string) (string, error) {
return "", common.ErrNotImplementedError 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)
}

@ -1,45 +0,0 @@
// SPDX-License-Identifier: BSD-3-Clause
//go:build darwin && cgo && !ios
package disk
/*
#cgo LDFLAGS: -framework CoreFoundation -framework IOKit
#include <stdint.h>
#include <CoreFoundation/CoreFoundation.h>
#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
}

@ -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
}

@ -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 <stdint.h>
#include <CoreFoundation/CoreFoundation.h>
#include "iostat_darwin.h"
#define IOKIT 1 /* to get io_name_t in device_types.h */
#include <IOKit/IOKitLib.h>
#include <IOKit/storage/IOBlockStorageDriver.h>
#include <IOKit/storage/IOMedia.h>
#include <IOKit/IOBSD.h>
#include <mach/mach_host.h>
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;
}

@ -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);

@ -7,11 +7,10 @@ require (
github.com/google/go-cmp v0.6.0 github.com/google/go-cmp v0.6.0
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c 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/stretchr/testify v1.9.0
github.com/tklauser/go-sysconf v0.3.12 github.com/tklauser/go-sysconf v0.3.12
github.com/yusufpapurcu/wmi v1.2.4 github.com/yusufpapurcu/wmi v1.2.4
golang.org/x/sys v0.24.0 golang.org/x/sys v0.25.0
) )
require ( require (

@ -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/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 h1:ncq/mPwQF4JjgDlrVEn3C11VoGHZN7m8qihwgMEtzYw=
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE= 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 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/tklauser/go-sysconf v0.3.12 h1:0QaGUFOdQaIVdPgfITYzaTegZvdCjmYO52cSFAEVmqU= 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.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.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.11.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.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34=
golang.org/x/sys v0.24.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= 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= 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 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=

@ -78,7 +78,7 @@ type Library struct {
const ( const (
IOKit = "/System/Library/Frameworks/IOKit.framework/IOKit" IOKit = "/System/Library/Frameworks/IOKit.framework/IOKit"
CoreFoundation = "/System/Library/Frameworks/CoreFoundation.framework/CoreFoundation" 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) { func NewLibrary(path string) (*Library, error) {
@ -119,12 +119,19 @@ const (
// IOKit functions and symbols. // IOKit functions and symbols.
type ( type (
IOServiceGetMatchingServiceFunc func(mainPort uint32, matching uintptr) uint32 IOServiceGetMatchingServiceFunc func(mainPort uint32, matching uintptr) uint32
IOServiceMatchingFunc func(name string) unsafe.Pointer IOServiceGetMatchingServicesFunc func(mainPort uint32, matching uintptr, existing *uint32) int
IOServiceOpenFunc func(service, owningTask, connType uint32, connect *uint32) int IOServiceMatchingFunc func(name string) unsafe.Pointer
IOServiceCloseFunc func(connect uint32) int IOServiceOpenFunc func(service, owningTask, connType uint32, connect *uint32) int
IOObjectReleaseFunc func(object uint32) int IOServiceCloseFunc func(connect uint32) int
IOConnectCallStructMethodFunc func(connection, selector uint32, inputStruct, inputStructCnt, outputStruct uintptr, outputStructCnt *uintptr) 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 IOHIDEventSystemClientCreateFunc func(allocator uintptr) unsafe.Pointer
IOHIDEventSystemClientSetMatchingFunc func(client, match uintptr) int IOHIDEventSystemClientSetMatchingFunc func(client, match uintptr) int
@ -136,12 +143,19 @@ type (
) )
const ( const (
IOServiceGetMatchingServiceSym = "IOServiceGetMatchingService" IOServiceGetMatchingServiceSym = "IOServiceGetMatchingService"
IOServiceMatchingSym = "IOServiceMatching" IOServiceGetMatchingServicesSym = "IOServiceGetMatchingServices"
IOServiceOpenSym = "IOServiceOpen" IOServiceMatchingSym = "IOServiceMatching"
IOServiceCloseSym = "IOServiceClose" IOServiceOpenSym = "IOServiceOpen"
IOObjectReleaseSym = "IOObjectRelease" IOServiceCloseSym = "IOServiceClose"
IOConnectCallStructMethodSym = "IOConnectCallStructMethod" IOIteratorNextSym = "IOIteratorNext"
IORegistryEntryGetNameSym = "IORegistryEntryGetName"
IORegistryEntryGetParentEntrySym = "IORegistryEntryGetParentEntry"
IORegistryEntryCreateCFPropertySym = "IORegistryEntryCreateCFProperty"
IORegistryEntryCreateCFPropertiesSym = "IORegistryEntryCreateCFProperties"
IOObjectConformsToSym = "IOObjectConformsTo"
IOObjectReleaseSym = "IOObjectRelease"
IOConnectCallStructMethodSym = "IOConnectCallStructMethod"
IOHIDEventSystemClientCreateSym = "IOHIDEventSystemClientCreate" IOHIDEventSystemClientCreateSym = "IOHIDEventSystemClientCreate"
IOHIDEventSystemClientSetMatchingSym = "IOHIDEventSystemClientSetMatching" IOHIDEventSystemClientSetMatchingSym = "IOHIDEventSystemClientSetMatching"
@ -152,49 +166,77 @@ const (
) )
const ( const (
KIOMainPortDefault = 0
KIOHIDEventTypeTemperature = 15 KIOHIDEventTypeTemperature = 15
KNilOptions = 0
)
const (
KIOMediaWholeKey = "Media"
KIOServicePlane = "IOService"
) )
// CoreFoundation functions and symbols. // CoreFoundation functions and symbols.
type ( type (
CFGetTypeIDFunc func(cf uintptr) int32
CFNumberCreateFunc func(allocator uintptr, theType int32, valuePtr uintptr) unsafe.Pointer 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, CFDictionaryCreateFunc func(allocator uintptr, keys, values *unsafe.Pointer, numValues int32,
keyCallBacks, valueCallBacks uintptr) unsafe.Pointer keyCallBacks, valueCallBacks uintptr) unsafe.Pointer
CFDictionaryAddValueFunc func(theDict, key, value uintptr)
CFDictionaryGetValueFunc func(theDict, key uintptr) unsafe.Pointer
CFArrayGetCountFunc func(theArray uintptr) int32 CFArrayGetCountFunc func(theArray uintptr) int32
CFArrayGetValueAtIndexFunc func(theArray uintptr, index int32) unsafe.Pointer CFArrayGetValueAtIndexFunc func(theArray uintptr, index int32) unsafe.Pointer
CFStringCreateMutableFunc func(alloc uintptr, maxLength int32) unsafe.Pointer CFStringCreateMutableFunc func(alloc uintptr, maxLength int32) unsafe.Pointer
CFStringGetLengthFunc func(theString uintptr) int32 CFStringGetLengthFunc func(theString uintptr) int32
CFStringGetCStringFunc func(theString uintptr, buffer *byte, bufferSize int32, encoding uint32) CFStringGetCStringFunc func(theString uintptr, buffer *byte, bufferSize int32, encoding uint32)
CFStringCreateWithCStringFunc func(alloc uintptr, cStr string, encoding uint32) unsafe.Pointer 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) CFReleaseFunc func(cf uintptr)
) )
const ( const (
CFGetTypeIDSym = "CFGetTypeID"
CFNumberCreateSym = "CFNumberCreate" CFNumberCreateSym = "CFNumberCreate"
CFNumberGetValueSym = "CFNumberGetValue"
CFDictionaryCreateSym = "CFDictionaryCreate" CFDictionaryCreateSym = "CFDictionaryCreate"
CFDictionaryAddValueSym = "CFDictionaryAddValue"
CFDictionaryGetValueSym = "CFDictionaryGetValue"
CFArrayGetCountSym = "CFArrayGetCount" CFArrayGetCountSym = "CFArrayGetCount"
CFArrayGetValueAtIndexSym = "CFArrayGetValueAtIndex" CFArrayGetValueAtIndexSym = "CFArrayGetValueAtIndex"
CFStringCreateMutableSym = "CFStringCreateMutable" CFStringCreateMutableSym = "CFStringCreateMutable"
CFStringGetLengthSym = "CFStringGetLength" CFStringGetLengthSym = "CFStringGetLength"
CFStringGetCStringSym = "CFStringGetCString" CFStringGetCStringSym = "CFStringGetCString"
CFStringCreateWithCStringSym = "CFStringCreateWithCString" CFStringCreateWithCStringSym = "CFStringCreateWithCString"
CFDataGetLengthSym = "CFDataGetLength"
CFDataGetBytePtrSym = "CFDataGetBytePtr"
CFReleaseSym = "CFRelease" CFReleaseSym = "CFRelease"
) )
const ( const (
KCFStringEncodingUTF8 = 0x08000100 KCFStringEncodingUTF8 = 0x08000100
KCFNumberSInt64Type = 4
KCFNumberIntType = 9 KCFNumberIntType = 9
KCFAllocatorDefault = 0 KCFAllocatorDefault = 0
) )
// Kernel functions and symbols. // Kernel functions and symbols.
type MachTimeBaseInfo struct {
Numer uint32
Denom uint32
}
type ( type (
HostProcessorInfoFunc func(host uint32, flavor int, outProcessorCount *uint32, outProcessorInfo uintptr, HostProcessorInfoFunc func(host uint32, flavor int32, outProcessorCount *uint32, outProcessorInfo uintptr,
outProcessorInfoCnt *uint32) int outProcessorInfoCnt *uint32) int
HostStatisticsFunc func(host uint32, flavor int, hostInfoOut uintptr, hostInfoOutCnt *uint32) int HostStatisticsFunc func(host uint32, flavor int32, hostInfoOut uintptr, hostInfoOutCnt *uint32) int
MachHostSelfFunc func() uint32 MachHostSelfFunc func() uint32
MachTaskSelfFunc func() uint32 MachTaskSelfFunc func() uint32
VMDeallocateFunc func(targetTask uint32, vmAddress, vmSize uintptr) int MachTimeBaseInfoFunc func(info uintptr) int
VMDeallocateFunc func(targetTask uint32, vmAddress, vmSize uintptr) int
) )
const ( const (
@ -202,16 +244,40 @@ const (
HostStatisticsSym = "host_statistics" HostStatisticsSym = "host_statistics"
MachHostSelfSym = "mach_host_self" MachHostSelfSym = "mach_host_self"
MachTaskSelfSym = "mach_task_self" MachTaskSelfSym = "mach_task_self"
MachTimeBaseInfoSym = "mach_timebase_info"
VMDeallocateSym = "vm_deallocate" VMDeallocateSym = "vm_deallocate"
) )
const ( const (
CTL_KERN = 1
KERN_ARGMAX = 8
KERN_PROCARGS2 = 49
HOST_VM_INFO = 2 HOST_VM_INFO = 2
HOST_CPU_LOAD_INFO = 3 HOST_CPU_LOAD_INFO = 3
HOST_VM_INFO_COUNT = 0xf 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. // SMC represents a SMC instance.
type SMC struct { type SMC struct {
lib *Library lib *Library
@ -281,3 +347,18 @@ func (s *SMC) Close() error {
} }
return nil 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))
}

@ -40,23 +40,3 @@ func CallLsofWithContext(ctx context.Context, invoke Invoker, pid int32, args ..
} }
return ret, nil 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
}

@ -85,7 +85,7 @@ func VirtualMemory() (*VirtualMemoryStat, error) {
} }
func VirtualMemoryWithContext(ctx context.Context) (*VirtualMemoryStat, error) { func VirtualMemoryWithContext(ctx context.Context) (*VirtualMemoryStat, error) {
machLib, err := common.NewLibrary(common.Kernel) machLib, err := common.NewLibrary(common.System)
if err != nil { if err != nil {
return nil, err return nil, err
} }

@ -18,7 +18,7 @@ import (
var ( var (
invoke common.Invoker = common.Invoke{} 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") ErrorProcessNotRunning = errors.New("process does not exist")
ErrorNotPermitted = errors.New("operation not permitted") ErrorNotPermitted = errors.New("operation not permitted")
) )

@ -4,15 +4,20 @@
package process package process
import ( import (
"bytes"
"context" "context"
"encoding/binary"
"fmt" "fmt"
"path/filepath" "path/filepath"
"runtime"
"sort"
"strconv" "strconv"
"strings" "strings"
"unsafe"
"github.com/tklauser/go-sysconf"
"golang.org/x/sys/unix" "golang.org/x/sys/unix"
"github.com/shirou/gopsutil/v4/cpu"
"github.com/shirou/gopsutil/v4/internal/common" "github.com/shirou/gopsutil/v4/internal/common"
"github.com/shirou/gopsutil/v4/net" "github.com/shirou/gopsutil/v4/net"
) )
@ -27,16 +32,6 @@ const (
KernProcPathname = 12 // path to executable 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 { type _Ctype_struct___0 struct {
Pad uint64 Pad uint64
} }
@ -186,65 +181,22 @@ func (p *Process) IOCountersWithContext(ctx context.Context) (*IOCountersStat, e
return nil, common.ErrNotImplementedError 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) { func (p *Process) ChildrenWithContext(ctx context.Context) ([]*Process, error) {
pids, err := common.CallPgrepWithContext(ctx, invoke, p.Pid) procs, err := ProcessesWithContext(ctx)
if err != nil { if err != nil {
return nil, err return nil, nil
} }
ret := make([]*Process, 0, len(pids)) ret := make([]*Process, 0, len(procs))
for _, pid := range pids { for _, proc := range procs {
np, err := NewProcessWithContext(ctx, pid) ppid, err := proc.PpidWithContext(ctx)
if err != nil { 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 return ret, nil
} }
@ -323,3 +275,206 @@ func callPsWithContext(ctx context.Context, arg string, pid int32, threadOption
return ret, nil 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
}

@ -212,6 +212,27 @@ type Posix_cred struct {
type Label 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 { type AuditinfoAddr struct {
Auid uint32 Auid uint32
Mask AuMask Mask AuMask

@ -190,6 +190,27 @@ type Posix_cred struct{}
type Label 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 { type AuditinfoAddr struct {
Auid uint32 Auid uint32
Mask AuMask Mask AuMask

@ -1,222 +0,0 @@
// SPDX-License-Identifier: BSD-3-Clause
//go:build darwin && cgo
package process
// #include <stdlib.h>
// #include <libproc.h>
// #include <string.h>
// #include <sys/errno.h>
// #include <sys/proc_info.h>
// #include <sys/sysctl.h>
// #include <mach/mach_time.h>
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
}

@ -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
}

@ -8,6 +8,7 @@ import (
"context" "context"
"errors" "errors"
"path/filepath" "path/filepath"
"sort"
"strconv" "strconv"
"strings" "strings"
@ -269,18 +270,21 @@ func (p *Process) MemoryInfoWithContext(ctx context.Context) (*MemoryInfoStat, e
} }
func (p *Process) ChildrenWithContext(ctx context.Context) ([]*Process, error) { func (p *Process) ChildrenWithContext(ctx context.Context) ([]*Process, error) {
pids, err := common.CallPgrepWithContext(ctx, invoke, p.Pid) procs, err := ProcessesWithContext(ctx)
if err != nil { if err != nil {
return nil, err return nil, nil
} }
ret := make([]*Process, 0, len(pids)) ret := make([]*Process, 0, len(procs))
for _, pid := range pids { for _, proc := range procs {
np, err := NewProcessWithContext(ctx, pid) ppid, err := proc.PpidWithContext(ctx)
if err != nil { 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 return ret, nil
} }

@ -12,6 +12,7 @@ import (
"math" "math"
"os" "os"
"path/filepath" "path/filepath"
"sort"
"strconv" "strconv"
"strings" "strings"
@ -338,21 +339,34 @@ func (p *Process) PageFaultsWithContext(ctx context.Context) (*PageFaultsStat, e
} }
func (p *Process) ChildrenWithContext(ctx context.Context) ([]*Process, error) { 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 { if err != nil {
return nil, err return nil, err
} }
if len(pids) == 0 { ret := make([]*Process, 0, len(statFiles))
return nil, ErrorNoChildren for _, statFile := range statFiles {
} statContents, err := os.ReadFile(statFile)
ret := make([]*Process, 0, len(pids))
for _, pid := range pids {
np, err := NewProcessWithContext(ctx, pid)
if err != nil { 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 return ret, nil
} }

@ -11,6 +11,7 @@ import (
"fmt" "fmt"
"io" "io"
"path/filepath" "path/filepath"
"sort"
"strconv" "strconv"
"strings" "strings"
"unsafe" "unsafe"
@ -286,18 +287,21 @@ func (p *Process) MemoryInfoWithContext(ctx context.Context) (*MemoryInfoStat, e
} }
func (p *Process) ChildrenWithContext(ctx context.Context) ([]*Process, error) { func (p *Process) ChildrenWithContext(ctx context.Context) ([]*Process, error) {
pids, err := common.CallPgrepWithContext(ctx, invoke, p.Pid) procs, err := ProcessesWithContext(ctx)
if err != nil { if err != nil {
return nil, err return nil, nil
} }
ret := make([]*Process, 0, len(pids)) ret := make([]*Process, 0, len(procs))
for _, pid := range pids { for _, proc := range procs {
np, err := NewProcessWithContext(ctx, pid) ppid, err := proc.PpidWithContext(ctx)
if err != nil { 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 return ret, nil
} }

@ -53,6 +53,7 @@ package process
#include <sys/sysctl.h> #include <sys/sysctl.h>
#include <sys/ucred.h> #include <sys/ucred.h>
#include <sys/proc.h> #include <sys/proc.h>
#include <sys/proc_info.h>
#include <sys/time.h> #include <sys/time.h>
#include <sys/_types/_timeval.h> #include <sys/_types/_timeval.h>
#include <sys/appleapiopts.h> #include <sys/appleapiopts.h>
@ -154,6 +155,8 @@ type Posix_cred C.struct_posix_cred
type Label C.struct_label type Label C.struct_label
type ProcTaskInfo C.struct_proc_taskinfo
type ( type (
AuditinfoAddr C.struct_auditinfo_addr AuditinfoAddr C.struct_auditinfo_addr
AuMask C.struct_au_mask AuMask C.struct_au_mask

Loading…
Cancel
Save