Skip to content

Commit

Permalink
update disk & cpu & process
Browse files Browse the repository at this point in the history
  • Loading branch information
uubulb committed Sep 20, 2024
1 parent 701a74b commit 9e6efdb
Show file tree
Hide file tree
Showing 26 changed files with 721 additions and 736 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -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.
Expand Down
15 changes: 8 additions & 7 deletions common/env.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
15 changes: 4 additions & 11 deletions cpu/cpu_darwin.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ import (
"strings"
"unsafe"

"github.com/shoenig/go-m1cpu"
"github.com/tklauser/go-sysconf"
"golang.org/x/sys/unix"

Expand Down Expand Up @@ -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
}
Expand Down Expand Up @@ -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
Expand Down
80 changes: 80 additions & 0 deletions cpu/cpu_darwin_arm64.go
Original file line number Diff line number Diff line change
@@ -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
}
13 changes: 13 additions & 0 deletions cpu/cpu_darwin_fallback.go
Original file line number Diff line number Diff line change
@@ -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 changes: 2 additions & 3 deletions cpu/cpu_darwin_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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")
}

Expand Down
200 changes: 200 additions & 0 deletions disk/disk_darwin.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ package disk

import (
"context"
"fmt"
"unsafe"

"golang.org/x/sys/unix"

Expand Down Expand Up @@ -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)
}
Loading

0 comments on commit 9e6efdb

Please sign in to comment.