update disk & cpu & process

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

@ -1,6 +1,6 @@
# gopsutil: psutil for golang
[![Test](https://github.com/shirou/gopsutil/actions/workflows/test.yml/badge.svg)](https://github.com/shirou/gopsutil/actions/workflows/test.yml) [![Coverage Status](https://coveralls.io/repos/github/shirou/gopsutil/badge.svg?branch=master)](https://coveralls.io/github/shirou/gopsutil?branch=master) [![Go Reference](https://pkg.go.dev/badge/github.com/shirou/gopsutil/v4.svg)](https://pkg.go.dev/github.com/shirou/gopsutil/v4) [![Go Documentation](https://godocs.io/github.com/shirou/gopsutil/v4?status.svg)](https://godocs.io/github.com/shirou/gopsutil/v4) [![Calendar Versioning](https://img.shields.io/badge/calver-vMAJOR.YY.MM-22bfda.svg)](https://calver.org/)
[![Test](https://github.com/shirou/gopsutil/actions/workflows/test.yml/badge.svg)](https://github.com/shirou/gopsutil/actions/workflows/test.yml) [![Go Reference](https://pkg.go.dev/badge/github.com/shirou/gopsutil/v4.svg)](https://pkg.go.dev/github.com/shirou/gopsutil/v4) [![Calendar Versioning](https://img.shields.io/badge/calver-vMAJOR.YY.MM-22bfda.svg)](https://calver.org/)
This is a port of psutil (https://github.com/giampaolo/psutil). The
challenge is porting all psutil functions on some architectures.

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

@ -10,7 +10,6 @@ import (
"strings"
"unsafe"
"github.com/shoenig/go-m1cpu"
"github.com/tklauser/go-sysconf"
"golang.org/x/sys/unix"
@ -61,7 +60,7 @@ func Times(percpu bool) ([]TimesStat, error) {
}
func TimesWithContext(ctx context.Context, percpu bool) ([]TimesStat, error) {
lib, err := common.NewLibrary(common.Kernel)
lib, err := common.NewLibrary(common.System)
if err != nil {
return nil, err
}
@ -114,15 +113,9 @@ func InfoWithContext(ctx context.Context) ([]InfoStat, error) {
c.CacheSize = int32(cacheSize)
c.VendorID, _ = unix.Sysctl("machdep.cpu.vendor")
if m1cpu.IsAppleSilicon() {
c.Mhz = float64(m1cpu.PCoreHz() / 1_000_000)
} else {
// Use the rated frequency of the CPU. This is a static value and does not
// account for low power or Turbo Boost modes.
cpuFrequency, err := unix.SysctlUint64("hw.cpufrequency")
if err == nil {
c.Mhz = float64(cpuFrequency) / 1000000.0
}
v, err := getFrequency()
if err == nil {
c.Mhz = v
}
return append(ret, c), nil

@ -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 (
"os"
"runtime"
"testing"
"github.com/shoenig/go-m1cpu"
)
func TestInfo_AppleSilicon(t *testing.T) {
if !m1cpu.IsAppleSilicon() {
if runtime.GOARCH != "arm64" {
t.Skip("wrong cpu type")
}

@ -5,6 +5,8 @@ package disk
import (
"context"
"fmt"
"unsafe"
"golang.org/x/sys/unix"
@ -92,3 +94,201 @@ func SerialNumberWithContext(ctx context.Context, name string) (string, error) {
func LabelWithContext(ctx context.Context, name string) (string, error) {
return "", common.ErrNotImplementedError
}
func IOCountersWithContext(ctx context.Context, names ...string) (map[string]IOCountersStat, error) {
ioKit, err := common.NewLibrary(common.IOKit)
if err != nil {
return nil, err
}
defer ioKit.Close()
coreFoundation, err := common.NewLibrary(common.CoreFoundation)
if err != nil {
return nil, err
}
defer coreFoundation.Close()
ioServiceMatching := common.GetFunc[common.IOServiceMatchingFunc](ioKit, common.IOServiceMatchingSym)
ioServiceGetMatchingServices := common.GetFunc[common.IOServiceGetMatchingServicesFunc](ioKit, common.IOServiceGetMatchingServicesSym)
ioIteratorNext := common.GetFunc[common.IOIteratorNextFunc](ioKit, common.IOIteratorNextSym)
ioObjectRelease := common.GetFunc[common.IOObjectReleaseFunc](ioKit, common.IOObjectReleaseSym)
cfDictionaryAddValue := common.GetFunc[common.CFDictionaryAddValueFunc](coreFoundation, common.CFDictionaryAddValueSym)
cfStringCreateWithCString := common.GetFunc[common.CFStringCreateWithCStringFunc](coreFoundation, common.CFStringCreateWithCStringSym)
cfRelease := common.GetFunc[common.CFReleaseFunc](coreFoundation, common.CFReleaseSym)
kCFBooleanTruePtr, _ := coreFoundation.Dlsym("kCFBooleanTrue")
match := ioServiceMatching("IOMedia")
key := cfStringCreateWithCString(common.KCFAllocatorDefault, common.KIOMediaWholeKey, common.KCFStringEncodingUTF8)
defer cfRelease(uintptr(key))
var drives uint32
kCFBooleanTrue := **(**uintptr)(unsafe.Pointer(&kCFBooleanTruePtr))
cfDictionaryAddValue(uintptr(match), uintptr(key), kCFBooleanTrue)
if status := ioServiceGetMatchingServices(common.KIOMainPortDefault, uintptr(match), &drives); status != common.KERN_SUCCESS {
return nil, fmt.Errorf("IOServiceGetMatchingServices error=%d", status)
}
defer ioObjectRelease(drives)
ic := &ioCounters{
ioKit: ioKit,
coreFoundation: coreFoundation,
ioRegistryEntryCreateCFProperties: common.GetFunc[common.IORegistryEntryCreateCFPropertiesFunc](ioKit, common.IORegistryEntryCreateCFPropertiesSym),
ioObjectRelease: ioObjectRelease,
cfStringCreateWithCString: cfStringCreateWithCString,
cfDictionaryGetValue: common.GetFunc[common.CFDictionaryGetValueFunc](coreFoundation, common.CFDictionaryGetValueSym),
cfNumberGetValue: common.GetFunc[common.CFNumberGetValueFunc](coreFoundation, common.CFNumberGetValueSym),
cfRelease: cfRelease,
}
stats := make([]IOCountersStat, 0, 16)
for {
d := ioIteratorNext(drives)
if !(d > 0) {
break
}
stat, err := ic.getDriveStat(d)
if err != nil {
return nil, err
}
if stat != nil {
stats = append(stats, *stat)
}
ioObjectRelease(d)
}
ret := make(map[string]IOCountersStat, 0)
for i := 0; i < len(stats); i++ {
if len(names) > 0 && !common.StringsHas(names, stats[i].Name) {
continue
}
stats[i].ReadTime = stats[i].ReadTime / 1000 / 1000 // note: read/write time are in ns, but we want ms.
stats[i].WriteTime = stats[i].WriteTime / 1000 / 1000
stats[i].IoTime = stats[i].ReadTime + stats[i].WriteTime
ret[stats[i].Name] = stats[i]
}
return ret, nil
}
const (
kIOBSDNameKey = "BSD Name"
kIOMediaSizeKey = "Size"
kIOMediaPreferredBlockSizeKey = "Preferred Block Size"
kIOBlockStorageDriverStatisticsKey = "Statistics"
kIOBlockStorageDriverStatisticsBytesReadKey = "Bytes (Read)"
kIOBlockStorageDriverStatisticsBytesWrittenKey = "Bytes (Write)"
kIOBlockStorageDriverStatisticsReadsKey = "Operations (Read)"
kIOBlockStorageDriverStatisticsWritesKey = "Operations (Write)"
kIOBlockStorageDriverStatisticsTotalReadTimeKey = "Total Time (Read)"
kIOBlockStorageDriverStatisticsTotalWriteTimeKey = "Total Time (Write)"
)
type ioCounters struct {
ioKit *common.Library
coreFoundation *common.Library
ioRegistryEntryCreateCFProperties common.IORegistryEntryCreateCFPropertiesFunc
ioObjectRelease common.IOObjectReleaseFunc
cfStringCreateWithCString common.CFStringCreateWithCStringFunc
cfDictionaryGetValue common.CFDictionaryGetValueFunc
cfNumberGetValue common.CFNumberGetValueFunc
cfRelease common.CFReleaseFunc
}
func (i *ioCounters) getDriveStat(d uint32) (*IOCountersStat, error) {
ioRegistryEntryGetParentEntry := common.GetFunc[common.IORegistryEntryGetParentEntryFunc](i.ioKit, common.IORegistryEntryGetParentEntrySym)
ioObjectConformsTo := common.GetFunc[common.IOObjectConformsToFunc](i.ioKit, common.IOObjectConformsToSym)
cfStringGetLength := common.GetFunc[common.CFStringGetLengthFunc](i.coreFoundation, common.CFStringGetLengthSym)
cfStringGetCString := common.GetFunc[common.CFStringGetCStringFunc](i.coreFoundation, common.CFStringGetCStringSym)
var parent uint32
if status := ioRegistryEntryGetParentEntry(d, common.KIOServicePlane, &parent); status != common.KERN_SUCCESS {
return nil, fmt.Errorf("IORegistryEntryGetParentEntry error=%d", status)
}
defer i.ioObjectRelease(parent)
if !ioObjectConformsTo(parent, "IOBlockStorageDriver") {
//return nil, fmt.Errorf("ERROR: the object is not of the IOBlockStorageDriver class")
return nil, nil
}
var props unsafe.Pointer
if status := i.ioRegistryEntryCreateCFProperties(d, unsafe.Pointer(&props), common.KCFAllocatorDefault, common.KNilOptions); status != common.KERN_SUCCESS {
return nil, fmt.Errorf("IORegistryEntryCreateCFProperties error=%d", status)
}
defer i.cfRelease(uintptr(props))
key := i.cfStr(kIOBSDNameKey)
defer i.cfRelease(uintptr(key))
name := i.cfDictionaryGetValue(uintptr(props), uintptr(key))
length := cfStringGetLength(uintptr(name)) + 1
buf := make([]byte, length-1)
cfStringGetCString(uintptr(name), &buf[0], length, common.KCFStringEncodingUTF8)
stat, err := i.fillStat(parent)
if err != nil {
return nil, err
}
if stat != nil {
stat.Name = string(buf)
return stat, nil
}
return nil, nil
}
func (i *ioCounters) fillStat(d uint32) (*IOCountersStat, error) {
var props unsafe.Pointer
status := i.ioRegistryEntryCreateCFProperties(d, unsafe.Pointer(&props), common.KCFAllocatorDefault, common.KNilOptions)
if status != common.KERN_SUCCESS {
return nil, fmt.Errorf("IORegistryEntryCreateCFProperties error=%d", status)
}
if props == nil {
return nil, nil
}
defer i.cfRelease(uintptr(props))
key := i.cfStr(kIOBlockStorageDriverStatisticsKey)
defer i.cfRelease(uintptr(key))
v := i.cfDictionaryGetValue(uintptr(props), uintptr(key))
if v == nil {
return nil, fmt.Errorf("CFDictionaryGetValue failed")
}
var stat IOCountersStat
statstab := map[string]uintptr{
kIOBlockStorageDriverStatisticsBytesReadKey: unsafe.Offsetof(stat.ReadBytes),
kIOBlockStorageDriverStatisticsBytesWrittenKey: unsafe.Offsetof(stat.WriteBytes),
kIOBlockStorageDriverStatisticsReadsKey: unsafe.Offsetof(stat.ReadCount),
kIOBlockStorageDriverStatisticsWritesKey: unsafe.Offsetof(stat.WriteCount),
kIOBlockStorageDriverStatisticsTotalReadTimeKey: unsafe.Offsetof(stat.ReadTime),
kIOBlockStorageDriverStatisticsTotalWriteTimeKey: unsafe.Offsetof(stat.WriteTime),
}
for key, off := range statstab {
s := i.cfStr(key)
defer i.cfRelease(uintptr(s))
if num := i.cfDictionaryGetValue(uintptr(v), uintptr(s)); num != nil {
i.cfNumberGetValue(uintptr(num), common.KCFNumberSInt64Type, uintptr(unsafe.Pointer(uintptr(unsafe.Pointer(&stat))+off)))
}
}
return &stat, nil
}
func (i *ioCounters) cfStr(str string) unsafe.Pointer {
return i.cfStringCreateWithCString(common.KCFAllocatorDefault, str, common.KCFStringEncodingUTF8)
}

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

@ -13,9 +13,6 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c h1:ncq/mPwQF4JjgDlrVEn3C11VoGHZN7m8qihwgMEtzYw=
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE=
github.com/shoenig/go-m1cpu v0.1.6 h1:nxdKQNcEB6vzgA2E2bvzKIYRuNj7XNJ4S/aRSwKzFtM=
github.com/shoenig/go-m1cpu v0.1.6/go.mod h1:1JJMcUBvfNwpq05QDQVAnx3gUHr9IYF7GNg9SUEw2VQ=
github.com/shoenig/test v0.6.4 h1:kVTaSd7WLz5WZ2IaoM0RSzRsUD+m8wRR+5qvntpn4LU=
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/tklauser/go-sysconf v0.3.12 h1:0QaGUFOdQaIVdPgfITYzaTegZvdCjmYO52cSFAEVmqU=
@ -28,8 +25,8 @@ golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.24.0 h1:Twjiwq9dn6R1fQcyiK+wQyHWfaz/BJB+YIpzU/Cv3Xg=
golang.org/x/sys v0.24.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34=
golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=

@ -78,7 +78,7 @@ type Library struct {
const (
IOKit = "/System/Library/Frameworks/IOKit.framework/IOKit"
CoreFoundation = "/System/Library/Frameworks/CoreFoundation.framework/CoreFoundation"
Kernel = "/usr/lib/system/libsystem_kernel.dylib"
System = "/usr/lib/libSystem.B.dylib"
)
func NewLibrary(path string) (*Library, error) {
@ -119,12 +119,19 @@ const (
// IOKit functions and symbols.
type (
IOServiceGetMatchingServiceFunc func(mainPort uint32, matching uintptr) uint32
IOServiceMatchingFunc func(name string) unsafe.Pointer
IOServiceOpenFunc func(service, owningTask, connType uint32, connect *uint32) int
IOServiceCloseFunc func(connect uint32) int
IOObjectReleaseFunc func(object uint32) int
IOConnectCallStructMethodFunc func(connection, selector uint32, inputStruct, inputStructCnt, outputStruct uintptr, outputStructCnt *uintptr) int
IOServiceGetMatchingServiceFunc func(mainPort uint32, matching uintptr) uint32
IOServiceGetMatchingServicesFunc func(mainPort uint32, matching uintptr, existing *uint32) int
IOServiceMatchingFunc func(name string) unsafe.Pointer
IOServiceOpenFunc func(service, owningTask, connType uint32, connect *uint32) int
IOServiceCloseFunc func(connect uint32) int
IOIteratorNextFunc func(iterator uint32) uint32
IORegistryEntryGetNameFunc func(entry uint32, name *byte) int
IORegistryEntryGetParentEntryFunc func(entry uint32, plane string, parent *uint32) int
IORegistryEntryCreateCFPropertyFunc func(entry uint32, key, allocator uintptr, options uint32) unsafe.Pointer
IORegistryEntryCreateCFPropertiesFunc func(entry uint32, properties unsafe.Pointer, allocator uintptr, options uint32) int
IOObjectConformsToFunc func(object uint32, className string) bool
IOObjectReleaseFunc func(object uint32) int
IOConnectCallStructMethodFunc func(connection, selector uint32, inputStruct, inputStructCnt, outputStruct uintptr, outputStructCnt *uintptr) int
IOHIDEventSystemClientCreateFunc func(allocator uintptr) unsafe.Pointer
IOHIDEventSystemClientSetMatchingFunc func(client, match uintptr) int
@ -136,12 +143,19 @@ type (
)
const (
IOServiceGetMatchingServiceSym = "IOServiceGetMatchingService"
IOServiceMatchingSym = "IOServiceMatching"
IOServiceOpenSym = "IOServiceOpen"
IOServiceCloseSym = "IOServiceClose"
IOObjectReleaseSym = "IOObjectRelease"
IOConnectCallStructMethodSym = "IOConnectCallStructMethod"
IOServiceGetMatchingServiceSym = "IOServiceGetMatchingService"
IOServiceGetMatchingServicesSym = "IOServiceGetMatchingServices"
IOServiceMatchingSym = "IOServiceMatching"
IOServiceOpenSym = "IOServiceOpen"
IOServiceCloseSym = "IOServiceClose"
IOIteratorNextSym = "IOIteratorNext"
IORegistryEntryGetNameSym = "IORegistryEntryGetName"
IORegistryEntryGetParentEntrySym = "IORegistryEntryGetParentEntry"
IORegistryEntryCreateCFPropertySym = "IORegistryEntryCreateCFProperty"
IORegistryEntryCreateCFPropertiesSym = "IORegistryEntryCreateCFProperties"
IOObjectConformsToSym = "IOObjectConformsTo"
IOObjectReleaseSym = "IOObjectRelease"
IOConnectCallStructMethodSym = "IOConnectCallStructMethod"
IOHIDEventSystemClientCreateSym = "IOHIDEventSystemClientCreate"
IOHIDEventSystemClientSetMatchingSym = "IOHIDEventSystemClientSetMatching"
@ -152,49 +166,77 @@ const (
)
const (
KIOMainPortDefault = 0
KIOHIDEventTypeTemperature = 15
KNilOptions = 0
)
const (
KIOMediaWholeKey = "Media"
KIOServicePlane = "IOService"
)
// CoreFoundation functions and symbols.
type (
CFGetTypeIDFunc func(cf uintptr) int32
CFNumberCreateFunc func(allocator uintptr, theType int32, valuePtr uintptr) unsafe.Pointer
CFNumberGetValueFunc func(num uintptr, theType int32, valuePtr uintptr) bool
CFDictionaryCreateFunc func(allocator uintptr, keys, values *unsafe.Pointer, numValues int32,
keyCallBacks, valueCallBacks uintptr) unsafe.Pointer
CFDictionaryAddValueFunc func(theDict, key, value uintptr)
CFDictionaryGetValueFunc func(theDict, key uintptr) unsafe.Pointer
CFArrayGetCountFunc func(theArray uintptr) int32
CFArrayGetValueAtIndexFunc func(theArray uintptr, index int32) unsafe.Pointer
CFStringCreateMutableFunc func(alloc uintptr, maxLength int32) unsafe.Pointer
CFStringGetLengthFunc func(theString uintptr) int32
CFStringGetCStringFunc func(theString uintptr, buffer *byte, bufferSize int32, encoding uint32)
CFStringCreateWithCStringFunc func(alloc uintptr, cStr string, encoding uint32) unsafe.Pointer
CFDataGetLengthFunc func(theData uintptr) int32
CFDataGetBytePtrFunc func(theData uintptr) unsafe.Pointer
CFReleaseFunc func(cf uintptr)
)
const (
CFGetTypeIDSym = "CFGetTypeID"
CFNumberCreateSym = "CFNumberCreate"
CFNumberGetValueSym = "CFNumberGetValue"
CFDictionaryCreateSym = "CFDictionaryCreate"
CFDictionaryAddValueSym = "CFDictionaryAddValue"
CFDictionaryGetValueSym = "CFDictionaryGetValue"
CFArrayGetCountSym = "CFArrayGetCount"
CFArrayGetValueAtIndexSym = "CFArrayGetValueAtIndex"
CFStringCreateMutableSym = "CFStringCreateMutable"
CFStringGetLengthSym = "CFStringGetLength"
CFStringGetCStringSym = "CFStringGetCString"
CFStringCreateWithCStringSym = "CFStringCreateWithCString"
CFDataGetLengthSym = "CFDataGetLength"
CFDataGetBytePtrSym = "CFDataGetBytePtr"
CFReleaseSym = "CFRelease"
)
const (
KCFStringEncodingUTF8 = 0x08000100
KCFNumberSInt64Type = 4
KCFNumberIntType = 9
KCFAllocatorDefault = 0
)
// Kernel functions and symbols.
type MachTimeBaseInfo struct {
Numer uint32
Denom uint32
}
type (
HostProcessorInfoFunc func(host uint32, flavor int, outProcessorCount *uint32, outProcessorInfo uintptr,
HostProcessorInfoFunc func(host uint32, flavor int32, outProcessorCount *uint32, outProcessorInfo uintptr,
outProcessorInfoCnt *uint32) int
HostStatisticsFunc func(host uint32, flavor int, hostInfoOut uintptr, hostInfoOutCnt *uint32) int
MachHostSelfFunc func() uint32
MachTaskSelfFunc func() uint32
VMDeallocateFunc func(targetTask uint32, vmAddress, vmSize uintptr) int
HostStatisticsFunc func(host uint32, flavor int32, hostInfoOut uintptr, hostInfoOutCnt *uint32) int
MachHostSelfFunc func() uint32
MachTaskSelfFunc func() uint32
MachTimeBaseInfoFunc func(info uintptr) int
VMDeallocateFunc func(targetTask uint32, vmAddress, vmSize uintptr) int
)
const (
@ -202,16 +244,40 @@ const (
HostStatisticsSym = "host_statistics"
MachHostSelfSym = "mach_host_self"
MachTaskSelfSym = "mach_task_self"
MachTimeBaseInfoSym = "mach_timebase_info"
VMDeallocateSym = "vm_deallocate"
)
const (
CTL_KERN = 1
KERN_ARGMAX = 8
KERN_PROCARGS2 = 49
HOST_VM_INFO = 2
HOST_CPU_LOAD_INFO = 3
HOST_VM_INFO_COUNT = 0xf
)
// System functions and symbols.
type (
ProcPidPathFunc func(pid int32, buffer uintptr, bufferSize uint32) int32
ProcPidInfoFunc func(pid, flavor int32, arg uint64, buffer uintptr, bufferSize int32) int32
)
const (
SysctlSym = "sysctl"
ProcPidPathSym = "proc_pidpath"
ProcPidInfoSym = "proc_pidinfo"
)
const (
MAXPATHLEN = 1024
PROC_PIDPATHINFO_MAXSIZE = 4 * MAXPATHLEN
PROC_PIDTASKINFO = 4
PROC_PIDVNODEPATHINFO = 9
)
// SMC represents a SMC instance.
type SMC struct {
lib *Library
@ -281,3 +347,18 @@ func (s *SMC) Close() error {
}
return nil
}
// https://github.com/ebitengine/purego/blob/main/internal/strings/strings.go#L26
func GoString(cStr *byte) string {
if cStr == nil {
return ""
}
var length int
for {
if *(*byte)(unsafe.Add(unsafe.Pointer(cStr), uintptr(length))) == '\x00' {
break
}
length++
}
return string(unsafe.Slice(cStr, length))
}

@ -40,23 +40,3 @@ func CallLsofWithContext(ctx context.Context, invoke Invoker, pid int32, args ..
}
return ret, nil
}
func CallPgrepWithContext(ctx context.Context, invoke Invoker, pid int32) ([]int32, error) {
out, err := invoke.CommandWithContext(ctx, "pgrep", "-P", strconv.Itoa(int(pid)))
if err != nil {
return []int32{}, err
}
lines := strings.Split(string(out), "\n")
ret := make([]int32, 0, len(lines))
for _, l := range lines {
if len(l) == 0 {
continue
}
i, err := strconv.ParseInt(l, 10, 32)
if err != nil {
continue
}
ret = append(ret, int32(i))
}
return ret, nil
}

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

@ -18,7 +18,7 @@ import (
var (
invoke common.Invoker = common.Invoke{}
ErrorNoChildren = errors.New("process does not have children")
ErrorNoChildren = errors.New("process does not have children") // Deprecated: ErrorNoChildren is never returned by process.Children(), check its returned []*Process slice length instead
ErrorProcessNotRunning = errors.New("process does not exist")
ErrorNotPermitted = errors.New("operation not permitted")
)

@ -4,15 +4,20 @@
package process
import (
"bytes"
"context"
"encoding/binary"
"fmt"
"path/filepath"
"runtime"
"sort"
"strconv"
"strings"
"unsafe"
"github.com/tklauser/go-sysconf"
"golang.org/x/sys/unix"
"github.com/shirou/gopsutil/v4/cpu"
"github.com/shirou/gopsutil/v4/internal/common"
"github.com/shirou/gopsutil/v4/net"
)
@ -27,16 +32,6 @@ const (
KernProcPathname = 12 // path to executable
)
var clockTicks = 100 // default value
func init() {
clkTck, err := sysconf.Sysconf(sysconf.SC_CLK_TCK)
// ignore errors
if err == nil {
clockTicks = int(clkTck)
}
}
type _Ctype_struct___0 struct {
Pad uint64
}
@ -186,65 +181,22 @@ func (p *Process) IOCountersWithContext(ctx context.Context) (*IOCountersStat, e
return nil, common.ErrNotImplementedError
}
func convertCPUTimes(s string) (ret float64, err error) {
var t int
var _tmp string
if strings.Contains(s, ":") {
_t := strings.Split(s, ":")
switch len(_t) {
case 3:
hour, err := strconv.ParseInt(_t[0], 10, 32)
if err != nil {
return ret, err
}
t += int(hour) * 60 * 60 * clockTicks
mins, err := strconv.ParseInt(_t[1], 10, 32)
if err != nil {
return ret, err
}
t += int(mins) * 60 * clockTicks
_tmp = _t[2]
case 2:
mins, err := strconv.ParseInt(_t[0], 10, 32)
if err != nil {
return ret, err
}
t += int(mins) * 60 * clockTicks
_tmp = _t[1]
case 1, 0:
_tmp = s
default:
return ret, fmt.Errorf("wrong cpu time string")
}
} else {
_tmp = s
}
_t := strings.Split(_tmp, ".")
if err != nil {
return ret, err
}
h, err := strconv.ParseInt(_t[0], 10, 32)
t += int(h) * clockTicks
h, err = strconv.ParseInt(_t[1], 10, 32)
t += int(h)
return float64(t) / float64(clockTicks), nil
}
func (p *Process) ChildrenWithContext(ctx context.Context) ([]*Process, error) {
pids, err := common.CallPgrepWithContext(ctx, invoke, p.Pid)
procs, err := ProcessesWithContext(ctx)
if err != nil {
return nil, err
return nil, nil
}
ret := make([]*Process, 0, len(pids))
for _, pid := range pids {
np, err := NewProcessWithContext(ctx, pid)
ret := make([]*Process, 0, len(procs))
for _, proc := range procs {
ppid, err := proc.PpidWithContext(ctx)
if err != nil {
return nil, err
continue
}
if ppid == p.Pid {
ret = append(ret, proc)
}
ret = append(ret, np)
}
sort.Slice(ret, func(i, j int) bool { return ret[i].Pid < ret[j].Pid })
return ret, nil
}
@ -323,3 +275,206 @@ func callPsWithContext(ctx context.Context, arg string, pid int32, threadOption
return ret, nil
}
var (
procPidPath common.ProcPidPathFunc
procPidInfo common.ProcPidInfoFunc
machTimeBaseInfo common.MachTimeBaseInfoFunc
)
func registerFuncs() (*common.Library, error) {
lib, err := common.NewLibrary(common.System)
if err != nil {
return nil, err
}
procPidPath = common.GetFunc[common.ProcPidPathFunc](lib, common.ProcPidPathSym)
procPidInfo = common.GetFunc[common.ProcPidInfoFunc](lib, common.ProcPidInfoSym)
machTimeBaseInfo = common.GetFunc[common.MachTimeBaseInfoFunc](lib, common.MachTimeBaseInfoSym)
return lib, nil
}
func getTimeScaleToNanoSeconds() float64 {
var timeBaseInfo common.MachTimeBaseInfo
machTimeBaseInfo(uintptr(unsafe.Pointer(&timeBaseInfo)))
return float64(timeBaseInfo.Numer) / float64(timeBaseInfo.Denom)
}
func (p *Process) ExeWithContext(ctx context.Context) (string, error) {
lib, err := registerFuncs()
if err != nil {
return "", err
}
defer lib.Close()
buf := make([]byte, common.PROC_PIDPATHINFO_MAXSIZE)
ret := procPidPath(p.Pid, uintptr(unsafe.Pointer(&buf[0])), common.PROC_PIDPATHINFO_MAXSIZE)
if ret <= 0 {
return "", fmt.Errorf("unknown error: proc_pidpath returned %d", ret)
}
return common.GoString(&buf[0]), nil
}
// sys/proc_info.h
type vnodePathInfo struct {
_ [152]byte
vipPath [common.MAXPATHLEN]byte
_ [1176]byte
}
// CwdWithContext retrieves the Current Working Directory for the given process.
// It uses the proc_pidinfo from libproc and will only work for processes the
// EUID can access. Otherwise "operation not permitted" will be returned as the
// error.
// Note: This might also work for other *BSD OSs.
func (p *Process) CwdWithContext(ctx context.Context) (string, error) {
lib, err := registerFuncs()
if err != nil {
return "", err
}
defer lib.Close()
runtime.LockOSThread()
defer runtime.UnlockOSThread()
var vpi vnodePathInfo
const vpiSize = int32(unsafe.Sizeof(vpi))
ret := procPidInfo(p.Pid, common.PROC_PIDVNODEPATHINFO, 0, uintptr(unsafe.Pointer(&vpi)), vpiSize)
errno, _ := lib.Dlsym("errno")
err = *(**unix.Errno)(unsafe.Pointer(&errno))
if err == unix.EPERM {
return "", ErrorNotPermitted
}
if ret <= 0 {
return "", fmt.Errorf("unknown error: proc_pidinfo returned %d", ret)
}
if ret != vpiSize {
return "", fmt.Errorf("too few bytes; expected %d, got %d", vpiSize, ret)
}
return common.GoString(&vpi.vipPath[0]), nil
}
func procArgs(pid int32) ([]byte, int, error) {
procargs, _, err := common.CallSyscall([]int32{common.CTL_KERN, common.KERN_PROCARGS2, pid})
if err != nil {
return nil, 0, err
}
nargs := procargs[:4]
return procargs, int(binary.LittleEndian.Uint32(nargs)), nil
}
func (p *Process) CmdlineSliceWithContext(ctx context.Context) ([]string, error) {
return p.cmdlineSliceWithContext(ctx, true)
}
func (p *Process) cmdlineSliceWithContext(ctx context.Context, fallback bool) ([]string, error) {
pargs, nargs, err := procArgs(p.Pid)
if err != nil {
return nil, err
}
// The first bytes hold the nargs int, skip it.
args := bytes.Split((pargs)[unsafe.Sizeof(int(0)):], []byte{0})
var argStr string
// The first element is the actual binary/command path.
// command := args[0]
var argSlice []string
// var envSlice []string
// All other, non-zero elements are arguments. The first "nargs" elements
// are the arguments. Everything else in the slice is then the environment
// of the process.
for _, arg := range args[1:] {
argStr = string(arg[:])
if len(argStr) > 0 {
if nargs > 0 {
argSlice = append(argSlice, argStr)
nargs--
continue
}
break
// envSlice = append(envSlice, argStr)
}
}
return argSlice, err
}
// cmdNameWithContext returns the command name (including spaces) without any arguments
func (p *Process) cmdNameWithContext(ctx context.Context) (string, error) {
r, err := p.cmdlineSliceWithContext(ctx, false)
if err != nil {
return "", err
}
if len(r) == 0 {
return "", nil
}
return r[0], err
}
func (p *Process) CmdlineWithContext(ctx context.Context) (string, error) {
r, err := p.CmdlineSliceWithContext(ctx)
if err != nil {
return "", err
}
return strings.Join(r, " "), err
}
func (p *Process) NumThreadsWithContext(ctx context.Context) (int32, error) {
lib, err := registerFuncs()
if err != nil {
return 0, err
}
defer lib.Close()
var ti ProcTaskInfo
const tiSize = int32(unsafe.Sizeof(ti))
procPidInfo(p.Pid, common.PROC_PIDTASKINFO, 0, uintptr(unsafe.Pointer(&ti)), tiSize)
return int32(ti.Threadnum), nil
}
func (p *Process) TimesWithContext(ctx context.Context) (*cpu.TimesStat, error) {
lib, err := registerFuncs()
if err != nil {
return nil, err
}
defer lib.Close()
var ti ProcTaskInfo
const tiSize = int32(unsafe.Sizeof(ti))
procPidInfo(p.Pid, common.PROC_PIDTASKINFO, 0, uintptr(unsafe.Pointer(&ti)), tiSize)
timescaleToNanoSeconds := getTimeScaleToNanoSeconds()
ret := &cpu.TimesStat{
CPU: "cpu",
User: float64(ti.Total_user) * timescaleToNanoSeconds / 1e9,
System: float64(ti.Total_system) * timescaleToNanoSeconds / 1e9,
}
return ret, nil
}
func (p *Process) MemoryInfoWithContext(ctx context.Context) (*MemoryInfoStat, error) {
lib, err := registerFuncs()
if err != nil {
return nil, err
}
defer lib.Close()
var ti ProcTaskInfo
const tiSize = int32(unsafe.Sizeof(ti))
procPidInfo(p.Pid, common.PROC_PIDTASKINFO, 0, uintptr(unsafe.Pointer(&ti)), tiSize)
ret := &MemoryInfoStat{
RSS: uint64(ti.Resident_size),
VMS: uint64(ti.Virtual_size),
Swap: uint64(ti.Pageins),
}
return ret, nil
}

@ -212,6 +212,27 @@ type Posix_cred struct {
type Label struct{}
type ProcTaskInfo struct {
Virtual_size uint64
Resident_size uint64
Total_user uint64
Total_system uint64
Threads_user uint64
Threads_system uint64
Policy int32
Faults int32
Pageins int32
Cow_faults int32
Messages_sent int32
Messages_received int32
Syscalls_mach int32
Syscalls_unix int32
Csw int32
Threadnum int32
Numrunning int32
Priority int32
}
type AuditinfoAddr struct {
Auid uint32
Mask AuMask

@ -190,6 +190,27 @@ type Posix_cred struct{}
type Label struct{}
type ProcTaskInfo struct {
Virtual_size uint64
Resident_size uint64
Total_user uint64
Total_system uint64
Threads_user uint64
Threads_system uint64
Policy int32
Faults int32
Pageins int32
Cow_faults int32
Messages_sent int32
Messages_received int32
Syscalls_mach int32
Syscalls_unix int32
Csw int32
Threadnum int32
Numrunning int32
Priority int32
}
type AuditinfoAddr struct {
Auid uint32
Mask AuMask

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

@ -12,6 +12,7 @@ import (
"math"
"os"
"path/filepath"
"sort"
"strconv"
"strings"
@ -338,21 +339,34 @@ func (p *Process) PageFaultsWithContext(ctx context.Context) (*PageFaultsStat, e
}
func (p *Process) ChildrenWithContext(ctx context.Context) ([]*Process, error) {
pids, err := common.CallPgrepWithContext(ctx, invoke, p.Pid)
statFiles, err := filepath.Glob(common.HostProcWithContext(ctx, "[0-9]*/stat"))
if err != nil {
return nil, err
}
if len(pids) == 0 {
return nil, ErrorNoChildren
}
ret := make([]*Process, 0, len(pids))
for _, pid := range pids {
np, err := NewProcessWithContext(ctx, pid)
ret := make([]*Process, 0, len(statFiles))
for _, statFile := range statFiles {
statContents, err := os.ReadFile(statFile)
if err != nil {
return nil, err
continue
}
fields := splitProcStat(statContents)
pid, err := strconv.ParseInt(fields[1], 10, 32)
if err != nil {
continue
}
ppid, err := strconv.ParseInt(fields[4], 10, 32)
if err != nil {
continue
}
if int32(ppid) == p.Pid {
np, err := NewProcessWithContext(ctx, int32(pid))
if err != nil {
continue
}
ret = append(ret, np)
}
ret = append(ret, np)
}
sort.Slice(ret, func(i, j int) bool { return ret[i].Pid < ret[j].Pid })
return ret, nil
}

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

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

Loading…
Cancel
Save