mirror of
https://github.com/shirou/gopsutil.git
synced 2025-04-26 13:48:59 +08:00
Compare commits
49 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
62a52f910b | ||
![]() |
8e62971eb0 | ||
![]() |
aa9796d3d7 | ||
![]() |
6be0508aeb | ||
![]() |
137fd2acac | ||
![]() |
aa47e0fde0 | ||
![]() |
de435c3739 | ||
![]() |
9ae3f38658 | ||
![]() |
a19cedeb30 | ||
![]() |
c9da2cc0aa | ||
![]() |
44c71b62c0 | ||
![]() |
30457973b5 | ||
![]() |
7422a58e32 | ||
![]() |
c30c83bf4a | ||
![]() |
9fc4f64913 | ||
![]() |
68512056e8 | ||
![]() |
ec973203e9 | ||
![]() |
c37a5eba8c | ||
![]() |
9732546382 | ||
![]() |
10d9c04c2e | ||
![]() |
4cb0abd1d2 | ||
![]() |
3773f6fe43 | ||
![]() |
45e3591650 | ||
![]() |
3c43ac0060 | ||
![]() |
023f0cf765 | ||
![]() |
b048ca575d | ||
![]() |
3f241a0b08 | ||
![]() |
2e10d9f7d0 | ||
![]() |
112f4c00b7 | ||
![]() |
7ec134321c | ||
![]() |
4140cda4ac | ||
![]() |
3dc12249d3 | ||
![]() |
f56b53a155 | ||
![]() |
fdc3c05c92 | ||
![]() |
462736cb8b | ||
![]() |
86b68e8d4f | ||
![]() |
9e6efdb991 | ||
![]() |
cbc32afb65 | ||
![]() |
6c06ac987e | ||
![]() |
7802a18557 | ||
![]() |
e89f21d7dc | ||
![]() |
811185d3fe | ||
![]() |
76ccf0d220 | ||
![]() |
784157497b | ||
![]() |
97b1aaee94 | ||
![]() |
f824d50add | ||
![]() |
701a74be41 | ||
![]() |
211eb8ccb4 | ||
![]() |
050902aeaa |
4
.github/workflows/build_test.yml
vendored
4
.github/workflows/build_test.yml
vendored
@ -26,13 +26,13 @@ jobs:
|
||||
with:
|
||||
go-version: ${{ matrix.go-version }}
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
- id: cache-paths
|
||||
run: |
|
||||
echo "::set-output name=cache::$(go env GOCACHE)"
|
||||
echo "::set-output name=mod-cache::$(go env GOMODCACHE)"
|
||||
- name: Cache go modules
|
||||
uses: actions/cache@0c45773b623bea8c8e75f6c82b208c3cf94ea4f9 # v4.0.2
|
||||
uses: actions/cache@3624ceb22c1c5a301c8db4169662070a689d9ea8 # v4.1.1
|
||||
with:
|
||||
path: |
|
||||
${{ steps.cache-paths.outputs.cache }}
|
||||
|
4
.github/workflows/lint.yml
vendored
4
.github/workflows/lint.yml
vendored
@ -21,9 +21,9 @@ jobs:
|
||||
go-version: 1.17
|
||||
cache: false
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
- name: Setup golangci-lint
|
||||
uses: golangci/golangci-lint-action@aaa42aa0628b4ae2578232a66b541047968fac86 # v6.1.0
|
||||
uses: golangci/golangci-lint-action@971e284b6050e8a5849b72094c50ab08da042db8 # v6.1.1
|
||||
with:
|
||||
args: --verbose
|
||||
version: latest
|
||||
|
2
.github/workflows/release.yml
vendored
2
.github/workflows/release.yml
vendored
@ -10,6 +10,6 @@ jobs:
|
||||
release:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
- name: Release
|
||||
run: make release
|
||||
|
4
.github/workflows/sbom_generator.yml
vendored
4
.github/workflows/sbom_generator.yml
vendored
@ -13,13 +13,13 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
|
||||
- uses: advanced-security/sbom-generator-action@375dee8e6144d9fd0ec1f5667b4f6fb4faacefed # v0.0.1
|
||||
id: sbom
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ github.token }}
|
||||
- uses: actions/upload-artifact@834a144ee995460fba8ed112a2fc961b36a5ec5a # v4.3.6
|
||||
- uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3
|
||||
with:
|
||||
path: ${{steps.sbom.outputs.fileName }}
|
||||
name: "SBOM"
|
||||
|
2
.github/workflows/shellcheck.yml
vendored
2
.github/workflows/shellcheck.yml
vendored
@ -8,6 +8,6 @@ jobs:
|
||||
name: Shellcheck
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
- name: Run ShellCheck
|
||||
uses: ludeeus/action-shellcheck@00cae500b08a931fb5698e11e79bfbd38e612a38 # v2.0.0
|
||||
|
4
.github/workflows/test.yml
vendored
4
.github/workflows/test.yml
vendored
@ -27,13 +27,13 @@ jobs:
|
||||
with:
|
||||
go-version: ${{ matrix.go-version }}
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
- id: go-env
|
||||
run: |
|
||||
echo "::set-output name=cache::$(go env GOCACHE)"
|
||||
echo "::set-output name=mod-cache::$(go env GOMODCACHE)"
|
||||
- name: Cache go modules
|
||||
uses: actions/cache@0c45773b623bea8c8e75f6c82b208c3cf94ea4f9 # v4.0.2
|
||||
uses: actions/cache@3624ceb22c1c5a301c8db4169662070a689d9ea8 # v4.1.1
|
||||
with:
|
||||
path: |
|
||||
${{ steps.go-env.outputs.cache }}
|
||||
|
@ -1,6 +1,6 @@
|
||||
# gopsutil: psutil for golang
|
||||
|
||||
[](https://github.com/shirou/gopsutil/actions/workflows/test.yml) [](https://coveralls.io/github/shirou/gopsutil?branch=master) [](https://pkg.go.dev/github.com/shirou/gopsutil/v4) [](https://godocs.io/github.com/shirou/gopsutil/v4) [](https://calver.org/)
|
||||
[](https://github.com/shirou/gopsutil/actions/workflows/test.yml) [](https://pkg.go.dev/github.com/shirou/gopsutil/v4) [](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
|
||||
|
@ -5,12 +5,15 @@ package cpu
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
"unsafe"
|
||||
|
||||
"github.com/shoenig/go-m1cpu"
|
||||
"github.com/tklauser/go-sysconf"
|
||||
"golang.org/x/sys/unix"
|
||||
|
||||
"github.com/shirou/gopsutil/v4/internal/common"
|
||||
)
|
||||
|
||||
// sys/resource.h
|
||||
@ -23,6 +26,24 @@ const (
|
||||
cpUStates = 5
|
||||
)
|
||||
|
||||
// mach/machine.h
|
||||
const (
|
||||
cpuStateUser = 0
|
||||
cpuStateSystem = 1
|
||||
cpuStateIdle = 2
|
||||
cpuStateNice = 3
|
||||
cpuStateMax = 4
|
||||
)
|
||||
|
||||
// mach/processor_info.h
|
||||
const (
|
||||
processorCpuLoadInfo = 2
|
||||
)
|
||||
|
||||
type hostCpuLoadInfoData struct {
|
||||
cpuTicks [cpuStateMax]uint32
|
||||
}
|
||||
|
||||
// default value. from time.h
|
||||
var ClocksPerSec = float64(128)
|
||||
|
||||
@ -39,11 +60,17 @@ func Times(percpu bool) ([]TimesStat, error) {
|
||||
}
|
||||
|
||||
func TimesWithContext(ctx context.Context, percpu bool) ([]TimesStat, error) {
|
||||
lib, err := common.NewLibrary(common.System)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer lib.Close()
|
||||
|
||||
if percpu {
|
||||
return perCPUTimes()
|
||||
return perCPUTimes(lib)
|
||||
}
|
||||
|
||||
return allCPUTimes()
|
||||
return allCPUTimes(lib)
|
||||
}
|
||||
|
||||
// Returns only one CPUInfoStat on FreeBSD
|
||||
@ -86,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
|
||||
@ -115,3 +136,63 @@ func CountsWithContext(ctx context.Context, logical bool) (int, error) {
|
||||
|
||||
return int(count), nil
|
||||
}
|
||||
|
||||
func perCPUTimes(machLib *common.Library) ([]TimesStat, error) {
|
||||
machHostSelf := common.GetFunc[common.MachHostSelfFunc](machLib, common.MachHostSelfSym)
|
||||
machTaskSelf := common.GetFunc[common.MachTaskSelfFunc](machLib, common.MachTaskSelfSym)
|
||||
hostProcessorInfo := common.GetFunc[common.HostProcessorInfoFunc](machLib, common.HostProcessorInfoSym)
|
||||
vmDeallocate := common.GetFunc[common.VMDeallocateFunc](machLib, common.VMDeallocateSym)
|
||||
|
||||
var count, ncpu uint32
|
||||
var cpuload *hostCpuLoadInfoData
|
||||
|
||||
status := hostProcessorInfo(machHostSelf(), processorCpuLoadInfo, &ncpu, uintptr(unsafe.Pointer(&cpuload)), &count)
|
||||
|
||||
if status != common.KERN_SUCCESS {
|
||||
return nil, fmt.Errorf("host_processor_info error=%d", status)
|
||||
}
|
||||
|
||||
defer vmDeallocate(machTaskSelf(), uintptr(unsafe.Pointer(cpuload)), uintptr(ncpu))
|
||||
|
||||
ret := []TimesStat{}
|
||||
loads := unsafe.Slice(cpuload, ncpu)
|
||||
|
||||
for i := 0; i < int(ncpu); i++ {
|
||||
c := TimesStat{
|
||||
CPU: fmt.Sprintf("cpu%d", i),
|
||||
User: float64(loads[i].cpuTicks[cpuStateUser]) / ClocksPerSec,
|
||||
System: float64(loads[i].cpuTicks[cpuStateSystem]) / ClocksPerSec,
|
||||
Nice: float64(loads[i].cpuTicks[cpuStateNice]) / ClocksPerSec,
|
||||
Idle: float64(loads[i].cpuTicks[cpuStateIdle]) / ClocksPerSec,
|
||||
}
|
||||
|
||||
ret = append(ret, c)
|
||||
}
|
||||
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
func allCPUTimes(machLib *common.Library) ([]TimesStat, error) {
|
||||
machHostSelf := common.GetFunc[common.MachHostSelfFunc](machLib, common.MachHostSelfSym)
|
||||
hostStatistics := common.GetFunc[common.HostStatisticsFunc](machLib, common.HostStatisticsSym)
|
||||
|
||||
var cpuload hostCpuLoadInfoData
|
||||
count := uint32(cpuStateMax)
|
||||
|
||||
status := hostStatistics(machHostSelf(), common.HOST_CPU_LOAD_INFO,
|
||||
uintptr(unsafe.Pointer(&cpuload)), &count)
|
||||
|
||||
if status != common.KERN_SUCCESS {
|
||||
return nil, fmt.Errorf("host_statistics error=%d", status)
|
||||
}
|
||||
|
||||
c := TimesStat{
|
||||
CPU: "cpu-total",
|
||||
User: float64(cpuload.cpuTicks[cpuStateUser]) / ClocksPerSec,
|
||||
System: float64(cpuload.cpuTicks[cpuStateSystem]) / ClocksPerSec,
|
||||
Nice: float64(cpuload.cpuTicks[cpuStateNice]) / ClocksPerSec,
|
||||
Idle: float64(cpuload.cpuTicks[cpuStateIdle]) / ClocksPerSec,
|
||||
}
|
||||
|
||||
return []TimesStat{c}, nil
|
||||
}
|
||||
|
80
cpu/cpu_darwin_arm64.go
Normal file
80
cpu/cpu_darwin_arm64.go
Normal file
@ -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
|
||||
}
|
@ -1,111 +0,0 @@
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
//go:build darwin && cgo
|
||||
|
||||
package cpu
|
||||
|
||||
/*
|
||||
#include <stdlib.h>
|
||||
#include <sys/sysctl.h>
|
||||
#include <sys/mount.h>
|
||||
#include <mach/mach_init.h>
|
||||
#include <mach/mach_host.h>
|
||||
#include <mach/host_info.h>
|
||||
#include <TargetConditionals.h>
|
||||
#if TARGET_OS_MAC
|
||||
#include <libproc.h>
|
||||
#endif
|
||||
#include <mach/processor_info.h>
|
||||
#include <mach/vm_map.h>
|
||||
*/
|
||||
import "C"
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
// these CPU times for darwin is borrowed from influxdb/telegraf.
|
||||
|
||||
func perCPUTimes() ([]TimesStat, error) {
|
||||
var (
|
||||
count C.mach_msg_type_number_t
|
||||
cpuload *C.processor_cpu_load_info_data_t
|
||||
ncpu C.natural_t
|
||||
)
|
||||
|
||||
status := C.host_processor_info(C.host_t(C.mach_host_self()),
|
||||
C.PROCESSOR_CPU_LOAD_INFO,
|
||||
&ncpu,
|
||||
(*C.processor_info_array_t)(unsafe.Pointer(&cpuload)),
|
||||
&count)
|
||||
|
||||
if status != C.KERN_SUCCESS {
|
||||
return nil, fmt.Errorf("host_processor_info error=%d", status)
|
||||
}
|
||||
|
||||
// jump through some cgo casting hoops and ensure we properly free
|
||||
// the memory that cpuload points to
|
||||
target := C.vm_map_t(C.mach_task_self_)
|
||||
address := C.vm_address_t(uintptr(unsafe.Pointer(cpuload)))
|
||||
defer C.vm_deallocate(target, address, C.vm_size_t(ncpu))
|
||||
|
||||
// the body of struct processor_cpu_load_info
|
||||
// aka processor_cpu_load_info_data_t
|
||||
var cpu_ticks [C.CPU_STATE_MAX]uint32
|
||||
|
||||
// copy the cpuload array to a []byte buffer
|
||||
// where we can binary.Read the data
|
||||
size := int(ncpu) * binary.Size(cpu_ticks)
|
||||
buf := (*[1 << 30]byte)(unsafe.Pointer(cpuload))[:size:size]
|
||||
|
||||
bbuf := bytes.NewBuffer(buf)
|
||||
|
||||
var ret []TimesStat
|
||||
|
||||
for i := 0; i < int(ncpu); i++ {
|
||||
err := binary.Read(bbuf, binary.LittleEndian, &cpu_ticks)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
c := TimesStat{
|
||||
CPU: fmt.Sprintf("cpu%d", i),
|
||||
User: float64(cpu_ticks[C.CPU_STATE_USER]) / ClocksPerSec,
|
||||
System: float64(cpu_ticks[C.CPU_STATE_SYSTEM]) / ClocksPerSec,
|
||||
Nice: float64(cpu_ticks[C.CPU_STATE_NICE]) / ClocksPerSec,
|
||||
Idle: float64(cpu_ticks[C.CPU_STATE_IDLE]) / ClocksPerSec,
|
||||
}
|
||||
|
||||
ret = append(ret, c)
|
||||
}
|
||||
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
func allCPUTimes() ([]TimesStat, error) {
|
||||
var count C.mach_msg_type_number_t
|
||||
var cpuload C.host_cpu_load_info_data_t
|
||||
|
||||
count = C.HOST_CPU_LOAD_INFO_COUNT
|
||||
|
||||
status := C.host_statistics(C.host_t(C.mach_host_self()),
|
||||
C.HOST_CPU_LOAD_INFO,
|
||||
C.host_info_t(unsafe.Pointer(&cpuload)),
|
||||
&count)
|
||||
|
||||
if status != C.KERN_SUCCESS {
|
||||
return nil, fmt.Errorf("host_statistics error=%d", status)
|
||||
}
|
||||
|
||||
c := TimesStat{
|
||||
CPU: "cpu-total",
|
||||
User: float64(cpuload.cpu_ticks[C.CPU_STATE_USER]) / ClocksPerSec,
|
||||
System: float64(cpuload.cpu_ticks[C.CPU_STATE_SYSTEM]) / ClocksPerSec,
|
||||
Nice: float64(cpuload.cpu_ticks[C.CPU_STATE_NICE]) / ClocksPerSec,
|
||||
Idle: float64(cpuload.cpu_ticks[C.CPU_STATE_IDLE]) / ClocksPerSec,
|
||||
}
|
||||
|
||||
return []TimesStat{c}, nil
|
||||
}
|
13
cpu/cpu_darwin_fallback.go
Normal file
13
cpu/cpu_darwin_fallback.go
Normal file
@ -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
|
||||
}
|
@ -1,14 +0,0 @@
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
//go:build darwin && !cgo
|
||||
|
||||
package cpu
|
||||
|
||||
import "github.com/shirou/gopsutil/v4/internal/common"
|
||||
|
||||
func perCPUTimes() ([]TimesStat, error) {
|
||||
return []TimesStat{}, common.ErrNotImplementedError
|
||||
}
|
||||
|
||||
func allCPUTimes() ([]TimesStat, error) {
|
||||
return []TimesStat{}, common.ErrNotImplementedError
|
||||
}
|
@ -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")
|
||||
}
|
||||
|
||||
|
10
cpu/cpu_netbsd_arm.go
Normal file
10
cpu/cpu_netbsd_arm.go
Normal file
@ -0,0 +1,10 @@
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
package cpu
|
||||
|
||||
type cpuTimes struct {
|
||||
User uint32
|
||||
Nice uint32
|
||||
Sys uint32
|
||||
Intr uint32
|
||||
Idle uint32
|
||||
}
|
@ -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
|
||||
}
|
46
disk/disk_netbsd_arm.go
Normal file
46
disk/disk_netbsd_arm.go
Normal file
@ -0,0 +1,46 @@
|
||||
//go:build netbsd && arm
|
||||
// +build netbsd,arm
|
||||
|
||||
// Code generated by cmd/cgo -godefs; DO NOT EDIT.
|
||||
// cgo -godefs disk/types_netbsd.go
|
||||
|
||||
package disk
|
||||
|
||||
const (
|
||||
sizeOfStatvfs = 0xcc8
|
||||
)
|
||||
|
||||
type (
|
||||
Statvfs struct {
|
||||
Flag uint32
|
||||
Bsize uint32
|
||||
Frsize uint32
|
||||
Iosize uint32
|
||||
Blocks uint64
|
||||
Bfree uint64
|
||||
Bavail uint64
|
||||
Bresvd uint64
|
||||
Files uint64
|
||||
Ffree uint64
|
||||
Favail uint64
|
||||
Fresvd uint64
|
||||
Syncreads uint64
|
||||
Syncwrites uint64
|
||||
Asyncreads uint64
|
||||
Asyncwrites uint64
|
||||
Fsidx _Ctype_struct___0
|
||||
Fsid uint32
|
||||
Namemax uint32
|
||||
Owner uint32
|
||||
Pad_cgo_0 [4]byte
|
||||
Spare [4]uint64
|
||||
Fstypename [32]uint8
|
||||
Mntonname [1024]uint8
|
||||
Mntfromname [1024]uint8
|
||||
Mntfromlabel [1024]uint8
|
||||
}
|
||||
)
|
||||
|
||||
type _Ctype_struct___0 struct {
|
||||
FsidVal [2]int32
|
||||
}
|
@ -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);
|
4
go.mod
4
go.mod
@ -3,14 +3,14 @@ module github.com/shirou/gopsutil/v4
|
||||
go 1.18
|
||||
|
||||
require (
|
||||
github.com/ebitengine/purego v0.8.1
|
||||
github.com/google/go-cmp v0.6.0
|
||||
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0
|
||||
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c
|
||||
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.26.0
|
||||
)
|
||||
|
||||
require (
|
||||
|
9
go.sum
9
go.sum
@ -1,5 +1,7 @@
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/ebitengine/purego v0.8.1 h1:sdRKd6plj7KYW33EH5As6YKfe8m9zbN9JMrOjNVF/BE=
|
||||
github.com/ebitengine/purego v0.8.1/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ=
|
||||
github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY=
|
||||
github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
|
||||
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
@ -11,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=
|
||||
@ -26,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.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo=
|
||||
golang.org/x/sys v0.26.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=
|
||||
|
@ -315,6 +315,8 @@ func PlatformInformationWithContext(ctx context.Context) (platform string, famil
|
||||
family = "solus"
|
||||
case "neokylin":
|
||||
family = "neokylin"
|
||||
case "anolis":
|
||||
family = "anolis"
|
||||
}
|
||||
|
||||
return platform, family, version, nil
|
||||
|
@ -5,11 +5,13 @@ package common
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"strings"
|
||||
"unsafe"
|
||||
|
||||
"github.com/ebitengine/purego"
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
@ -64,3 +66,299 @@ func CallSyscall(mib []int32) ([]byte, uint64, error) {
|
||||
|
||||
return buf, length, nil
|
||||
}
|
||||
|
||||
// Library represents a dynamic library loaded by purego.
|
||||
type Library struct {
|
||||
addr uintptr
|
||||
path string
|
||||
close func()
|
||||
}
|
||||
|
||||
// library paths
|
||||
const (
|
||||
IOKit = "/System/Library/Frameworks/IOKit.framework/IOKit"
|
||||
CoreFoundation = "/System/Library/Frameworks/CoreFoundation.framework/CoreFoundation"
|
||||
System = "/usr/lib/libSystem.B.dylib"
|
||||
)
|
||||
|
||||
func NewLibrary(path string) (*Library, error) {
|
||||
lib, err := purego.Dlopen(path, purego.RTLD_LAZY|purego.RTLD_GLOBAL)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
closeFunc := func() {
|
||||
purego.Dlclose(lib)
|
||||
}
|
||||
|
||||
return &Library{
|
||||
addr: lib,
|
||||
path: path,
|
||||
close: closeFunc,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (lib *Library) Dlsym(symbol string) (uintptr, error) {
|
||||
return purego.Dlsym(lib.addr, symbol)
|
||||
}
|
||||
|
||||
func GetFunc[T any](lib *Library, symbol string) T {
|
||||
var fptr T
|
||||
purego.RegisterLibFunc(&fptr, lib.addr, symbol)
|
||||
return fptr
|
||||
}
|
||||
|
||||
func (lib *Library) Close() {
|
||||
lib.close()
|
||||
}
|
||||
|
||||
// status codes
|
||||
const (
|
||||
KERN_SUCCESS = 0
|
||||
)
|
||||
|
||||
// IOKit functions and symbols.
|
||||
type (
|
||||
IOServiceGetMatchingServiceFunc func(mainPort uint32, matching uintptr) uint32
|
||||
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
|
||||
IOHIDServiceClientCopyEventFunc func(service uintptr, eventType int64,
|
||||
options int32, timeout int64) unsafe.Pointer
|
||||
IOHIDServiceClientCopyPropertyFunc func(service, property uintptr) unsafe.Pointer
|
||||
IOHIDEventGetFloatValueFunc func(event uintptr, field int32) float64
|
||||
IOHIDEventSystemClientCopyServicesFunc func(client uintptr) unsafe.Pointer
|
||||
)
|
||||
|
||||
const (
|
||||
IOServiceGetMatchingServiceSym = "IOServiceGetMatchingService"
|
||||
IOServiceGetMatchingServicesSym = "IOServiceGetMatchingServices"
|
||||
IOServiceMatchingSym = "IOServiceMatching"
|
||||
IOServiceOpenSym = "IOServiceOpen"
|
||||
IOServiceCloseSym = "IOServiceClose"
|
||||
IOIteratorNextSym = "IOIteratorNext"
|
||||
IORegistryEntryGetNameSym = "IORegistryEntryGetName"
|
||||
IORegistryEntryGetParentEntrySym = "IORegistryEntryGetParentEntry"
|
||||
IORegistryEntryCreateCFPropertySym = "IORegistryEntryCreateCFProperty"
|
||||
IORegistryEntryCreateCFPropertiesSym = "IORegistryEntryCreateCFProperties"
|
||||
IOObjectConformsToSym = "IOObjectConformsTo"
|
||||
IOObjectReleaseSym = "IOObjectRelease"
|
||||
IOConnectCallStructMethodSym = "IOConnectCallStructMethod"
|
||||
|
||||
IOHIDEventSystemClientCreateSym = "IOHIDEventSystemClientCreate"
|
||||
IOHIDEventSystemClientSetMatchingSym = "IOHIDEventSystemClientSetMatching"
|
||||
IOHIDServiceClientCopyEventSym = "IOHIDServiceClientCopyEvent"
|
||||
IOHIDServiceClientCopyPropertySym = "IOHIDServiceClientCopyProperty"
|
||||
IOHIDEventGetFloatValueSym = "IOHIDEventGetFloatValue"
|
||||
IOHIDEventSystemClientCopyServicesSym = "IOHIDEventSystemClientCopyServices"
|
||||
)
|
||||
|
||||
const (
|
||||
KIOMainPortDefault = 0
|
||||
|
||||
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 int32, outProcessorCount *uint32, outProcessorInfo uintptr,
|
||||
outProcessorInfoCnt *uint32) 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 (
|
||||
HostProcessorInfoSym = "host_processor_info"
|
||||
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
|
||||
conn uint32
|
||||
callStruct IOConnectCallStructMethodFunc
|
||||
}
|
||||
|
||||
const ioServiceSMC = "AppleSMC"
|
||||
|
||||
const (
|
||||
KSMCUserClientOpen = 0
|
||||
KSMCUserClientClose = 1
|
||||
KSMCHandleYPCEvent = 2
|
||||
KSMCReadKey = 5
|
||||
KSMCWriteKey = 6
|
||||
KSMCGetKeyCount = 7
|
||||
KSMCGetKeyFromIndex = 8
|
||||
KSMCGetKeyInfo = 9
|
||||
)
|
||||
|
||||
const (
|
||||
KSMCSuccess = 0
|
||||
KSMCError = 1
|
||||
KSMCKeyNotFound = 132
|
||||
)
|
||||
|
||||
func NewSMC(ioKit *Library) (*SMC, error) {
|
||||
if ioKit.path != IOKit {
|
||||
return nil, fmt.Errorf("library is not IOKit")
|
||||
}
|
||||
|
||||
ioServiceGetMatchingService := GetFunc[IOServiceGetMatchingServiceFunc](ioKit, IOServiceGetMatchingServiceSym)
|
||||
ioServiceMatching := GetFunc[IOServiceMatchingFunc](ioKit, IOServiceMatchingSym)
|
||||
ioServiceOpen := GetFunc[IOServiceOpenFunc](ioKit, IOServiceOpenSym)
|
||||
ioObjectRelease := GetFunc[IOObjectReleaseFunc](ioKit, IOObjectReleaseSym)
|
||||
machTaskSelf := GetFunc[MachTaskSelfFunc](ioKit, MachTaskSelfSym)
|
||||
|
||||
ioConnectCallStructMethod := GetFunc[IOConnectCallStructMethodFunc](ioKit, IOConnectCallStructMethodSym)
|
||||
|
||||
service := ioServiceGetMatchingService(0, uintptr(ioServiceMatching(ioServiceSMC)))
|
||||
if service == 0 {
|
||||
return nil, fmt.Errorf("ERROR: %s NOT FOUND", ioServiceSMC)
|
||||
}
|
||||
|
||||
var conn uint32
|
||||
if result := ioServiceOpen(service, machTaskSelf(), 0, &conn); result != 0 {
|
||||
return nil, fmt.Errorf("ERROR: IOServiceOpen failed")
|
||||
}
|
||||
|
||||
ioObjectRelease(service)
|
||||
return &SMC{
|
||||
lib: ioKit,
|
||||
conn: conn,
|
||||
callStruct: ioConnectCallStructMethod,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *SMC) CallStruct(selector uint32, inputStruct, inputStructCnt, outputStruct uintptr, outputStructCnt *uintptr) int {
|
||||
return s.callStruct(s.conn, selector, inputStruct, inputStructCnt, outputStruct, outputStructCnt)
|
||||
}
|
||||
|
||||
func (s *SMC) Close() error {
|
||||
ioServiceClose := GetFunc[IOServiceCloseFunc](s.lib, IOServiceCloseSym)
|
||||
|
||||
if result := ioServiceClose(s.conn); result != 0 {
|
||||
return fmt.Errorf("ERROR: IOServiceClose failed")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
@ -11,7 +11,10 @@ import (
|
||||
|
||||
// ExVirtualMemory represents Windows specific information
|
||||
// https://learn.microsoft.com/en-us/windows/win32/api/sysinfoapi/ns-sysinfoapi-memorystatusex
|
||||
// https://learn.microsoft.com/en-us/windows/win32/api/psapi/ns-psapi-performance_information
|
||||
type ExVirtualMemory struct {
|
||||
CommitLimit uint64 `json:"commitLimit"`
|
||||
CommitTotal uint64 `json:"commitTotal"`
|
||||
VirtualTotal uint64 `json:"virtualTotal"`
|
||||
VirtualAvail uint64 `json:"virtualAvail"`
|
||||
}
|
||||
@ -30,7 +33,16 @@ func (e *ExWindows) VirtualMemory() (*ExVirtualMemory, error) {
|
||||
return nil, windows.GetLastError()
|
||||
}
|
||||
|
||||
var perfInfo performanceInformation
|
||||
perfInfo.cb = uint32(unsafe.Sizeof(perfInfo))
|
||||
perf, _, _ := procGetPerformanceInfo.Call(uintptr(unsafe.Pointer(&perfInfo)), uintptr(perfInfo.cb))
|
||||
if perf == 0 {
|
||||
return nil, windows.GetLastError()
|
||||
}
|
||||
|
||||
ret := &ExVirtualMemory{
|
||||
CommitLimit: perfInfo.commitLimit * perfInfo.pageSize,
|
||||
CommitTotal: perfInfo.commitTotal * perfInfo.pageSize,
|
||||
VirtualTotal: memInfo.ullTotalVirtual,
|
||||
VirtualAvail: memInfo.ullAvailVirtual,
|
||||
}
|
||||
|
@ -70,3 +70,61 @@ func SwapDevices() ([]*SwapDevice, error) {
|
||||
func SwapDevicesWithContext(ctx context.Context) ([]*SwapDevice, error) {
|
||||
return nil, common.ErrNotImplementedError
|
||||
}
|
||||
|
||||
type vmStatisticsData struct {
|
||||
freeCount uint32
|
||||
activeCount uint32
|
||||
inactiveCount uint32
|
||||
wireCount uint32
|
||||
_ [44]byte // Not used here
|
||||
}
|
||||
|
||||
// VirtualMemory returns VirtualmemoryStat.
|
||||
func VirtualMemory() (*VirtualMemoryStat, error) {
|
||||
return VirtualMemoryWithContext(context.Background())
|
||||
}
|
||||
|
||||
func VirtualMemoryWithContext(ctx context.Context) (*VirtualMemoryStat, error) {
|
||||
machLib, err := common.NewLibrary(common.System)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer machLib.Close()
|
||||
|
||||
hostStatistics := common.GetFunc[common.HostStatisticsFunc](machLib, common.HostStatisticsSym)
|
||||
machHostSelf := common.GetFunc[common.MachHostSelfFunc](machLib, common.MachHostSelfSym)
|
||||
|
||||
count := uint32(common.HOST_VM_INFO_COUNT)
|
||||
var vmstat vmStatisticsData
|
||||
|
||||
status := hostStatistics(machHostSelf(), common.HOST_VM_INFO,
|
||||
uintptr(unsafe.Pointer(&vmstat)), &count)
|
||||
|
||||
if status != common.KERN_SUCCESS {
|
||||
return nil, fmt.Errorf("host_statistics error=%d", status)
|
||||
}
|
||||
|
||||
pageSizeAddr, _ := machLib.Dlsym("vm_kernel_page_size")
|
||||
pageSize := **(**uint64)(unsafe.Pointer(&pageSizeAddr))
|
||||
total, err := getHwMemsize()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
totalCount := uint32(total / pageSize)
|
||||
|
||||
availableCount := vmstat.inactiveCount + vmstat.freeCount
|
||||
usedPercent := 100 * float64(totalCount-availableCount) / float64(totalCount)
|
||||
|
||||
usedCount := totalCount - availableCount
|
||||
|
||||
return &VirtualMemoryStat{
|
||||
Total: total,
|
||||
Available: pageSize * uint64(availableCount),
|
||||
Used: pageSize * uint64(usedCount),
|
||||
UsedPercent: usedPercent,
|
||||
Free: pageSize * uint64(vmstat.freeCount),
|
||||
Active: pageSize * uint64(vmstat.activeCount),
|
||||
Inactive: pageSize * uint64(vmstat.inactiveCount),
|
||||
Wired: pageSize * uint64(vmstat.wireCount),
|
||||
}, nil
|
||||
}
|
||||
|
@ -1,58 +0,0 @@
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
//go:build darwin && cgo
|
||||
|
||||
package mem
|
||||
|
||||
/*
|
||||
#include <mach/mach_host.h>
|
||||
#include <mach/vm_page_size.h>
|
||||
*/
|
||||
import "C"
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
// VirtualMemory returns VirtualmemoryStat.
|
||||
func VirtualMemory() (*VirtualMemoryStat, error) {
|
||||
return VirtualMemoryWithContext(context.Background())
|
||||
}
|
||||
|
||||
func VirtualMemoryWithContext(ctx context.Context) (*VirtualMemoryStat, error) {
|
||||
count := C.mach_msg_type_number_t(C.HOST_VM_INFO_COUNT)
|
||||
var vmstat C.vm_statistics_data_t
|
||||
|
||||
status := C.host_statistics(C.host_t(C.mach_host_self()),
|
||||
C.HOST_VM_INFO,
|
||||
C.host_info_t(unsafe.Pointer(&vmstat)),
|
||||
&count)
|
||||
|
||||
if status != C.KERN_SUCCESS {
|
||||
return nil, fmt.Errorf("host_statistics error=%d", status)
|
||||
}
|
||||
|
||||
pageSize := uint64(C.vm_kernel_page_size)
|
||||
total, err := getHwMemsize()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
totalCount := C.natural_t(total / pageSize)
|
||||
|
||||
availableCount := vmstat.inactive_count + vmstat.free_count
|
||||
usedPercent := 100 * float64(totalCount-availableCount) / float64(totalCount)
|
||||
|
||||
usedCount := totalCount - availableCount
|
||||
|
||||
return &VirtualMemoryStat{
|
||||
Total: total,
|
||||
Available: pageSize * uint64(availableCount),
|
||||
Used: pageSize * uint64(usedCount),
|
||||
UsedPercent: usedPercent,
|
||||
Free: pageSize * uint64(vmstat.free_count),
|
||||
Active: pageSize * uint64(vmstat.active_count),
|
||||
Inactive: pageSize * uint64(vmstat.inactive_count),
|
||||
Wired: pageSize * uint64(vmstat.wire_count),
|
||||
}, nil
|
||||
}
|
@ -1,89 +0,0 @@
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
//go:build darwin && !cgo
|
||||
|
||||
package mem
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
// Runs vm_stat and returns Free and inactive pages
|
||||
func getVMStat(vms *VirtualMemoryStat) error {
|
||||
out, err := invoke.Command("vm_stat")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return parseVMStat(string(out), vms)
|
||||
}
|
||||
|
||||
func parseVMStat(out string, vms *VirtualMemoryStat) error {
|
||||
var err error
|
||||
|
||||
lines := strings.Split(out, "\n")
|
||||
pagesize := uint64(unix.Getpagesize())
|
||||
for _, line := range lines {
|
||||
fields := strings.Split(line, ":")
|
||||
if len(fields) < 2 {
|
||||
continue
|
||||
}
|
||||
key := strings.TrimSpace(fields[0])
|
||||
value := strings.Trim(fields[1], " .")
|
||||
switch key {
|
||||
case "Pages free":
|
||||
free, e := strconv.ParseUint(value, 10, 64)
|
||||
if e != nil {
|
||||
err = e
|
||||
}
|
||||
vms.Free = free * pagesize
|
||||
case "Pages inactive":
|
||||
inactive, e := strconv.ParseUint(value, 10, 64)
|
||||
if e != nil {
|
||||
err = e
|
||||
}
|
||||
vms.Inactive = inactive * pagesize
|
||||
case "Pages active":
|
||||
active, e := strconv.ParseUint(value, 10, 64)
|
||||
if e != nil {
|
||||
err = e
|
||||
}
|
||||
vms.Active = active * pagesize
|
||||
case "Pages wired down":
|
||||
wired, e := strconv.ParseUint(value, 10, 64)
|
||||
if e != nil {
|
||||
err = e
|
||||
}
|
||||
vms.Wired = wired * pagesize
|
||||
}
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// VirtualMemory returns VirtualmemoryStat.
|
||||
func VirtualMemory() (*VirtualMemoryStat, error) {
|
||||
return VirtualMemoryWithContext(context.Background())
|
||||
}
|
||||
|
||||
func VirtualMemoryWithContext(ctx context.Context) (*VirtualMemoryStat, error) {
|
||||
ret := &VirtualMemoryStat{}
|
||||
|
||||
total, err := getHwMemsize()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = getVMStat(ret)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
ret.Available = ret.Free + ret.Inactive
|
||||
ret.Total = total
|
||||
|
||||
ret.Used = ret.Total - ret.Available
|
||||
ret.UsedPercent = 100 * float64(ret.Used) / float64(ret.Total)
|
||||
|
||||
return ret, nil
|
||||
}
|
@ -82,6 +82,8 @@ func SwapMemoryWithContext(ctx context.Context) (*SwapMemoryStat, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer common.PdhCloseQuery.Call(uintptr(counter.Query))
|
||||
|
||||
usedPercent, err := counter.GetValue()
|
||||
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
|
||||
}
|
||||
|
||||
@ -1082,8 +1096,7 @@ func (p *Process) fillFromTIDStatWithContext(ctx context.Context, tid int32) (ui
|
||||
if err != nil {
|
||||
return 0, 0, nil, 0, 0, 0, nil, err
|
||||
}
|
||||
ctime := (t / uint64(clockTicks)) + uint64(bootTime)
|
||||
createTime := int64(ctime * 1000)
|
||||
createTime := int64((t * 1000 / uint64(clockTicks)) + uint64(bootTime*1000))
|
||||
|
||||
rtpriority, err := strconv.ParseInt(fields[18], 10, 32)
|
||||
if err != 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
|
||||
}
|
||||
|
||||
|
@ -43,6 +43,7 @@ var (
|
||||
procGetPriorityClass = common.Modkernel32.NewProc("GetPriorityClass")
|
||||
procGetProcessIoCounters = common.Modkernel32.NewProc("GetProcessIoCounters")
|
||||
procGetNativeSystemInfo = common.Modkernel32.NewProc("GetNativeSystemInfo")
|
||||
procGetProcessHandleCount = common.Modkernel32.NewProc("GetProcessHandleCount")
|
||||
|
||||
processorArchitecture uint
|
||||
)
|
||||
@ -548,8 +549,21 @@ func (p *Process) NumCtxSwitchesWithContext(ctx context.Context) (*NumCtxSwitche
|
||||
return nil, common.ErrNotImplementedError
|
||||
}
|
||||
|
||||
// NumFDsWithContext returns the number of handles for a process on Windows,
|
||||
// not the number of file descriptors (FDs).
|
||||
func (p *Process) NumFDsWithContext(ctx context.Context) (int32, error) {
|
||||
return 0, common.ErrNotImplementedError
|
||||
handle, err := windows.OpenProcess(processQueryInformation, false, uint32(p.Pid))
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
defer windows.CloseHandle(handle)
|
||||
|
||||
var handleCount uint32
|
||||
ret, _, err := procGetProcessHandleCount.Call(uintptr(handle), uintptr(unsafe.Pointer(&handleCount)))
|
||||
if ret == 0 {
|
||||
return 0, err
|
||||
}
|
||||
return int32(handleCount), nil
|
||||
}
|
||||
|
||||
func (p *Process) NumThreadsWithContext(ctx context.Context) (int32, error) {
|
||||
|
@ -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
|
||||
|
@ -1,110 +0,0 @@
|
||||
// SPDX-FileCopyrightText: Copyright (c) 2016-2018, "freedom" Koan-Sin Tan
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
// https://github.com/freedomtan/sensors/blob/master/sensors/sensors.m
|
||||
#import <Foundation/Foundation.h>
|
||||
#import <IOKit/hidsystem/IOHIDEventSystemClient.h>
|
||||
#include <unistd.h>
|
||||
|
||||
typedef struct __IOHIDEvent *IOHIDEventRef;
|
||||
typedef struct __IOHIDServiceClient *IOHIDServiceClientRef;
|
||||
typedef double IOHIDFloat;
|
||||
|
||||
IOHIDEventSystemClientRef IOHIDEventSystemClientCreate(CFAllocatorRef allocator);
|
||||
|
||||
int IOHIDEventSystemClientSetMatching(IOHIDEventSystemClientRef client, CFDictionaryRef match);
|
||||
|
||||
IOHIDEventRef IOHIDServiceClientCopyEvent(IOHIDServiceClientRef, int64_t, int32_t, int64_t);
|
||||
|
||||
CFStringRef IOHIDServiceClientCopyProperty(IOHIDServiceClientRef service, CFStringRef property);
|
||||
|
||||
IOHIDFloat IOHIDEventGetFloatValue(IOHIDEventRef event, int32_t field);
|
||||
|
||||
NSDictionary *matching(int page, int usage) {
|
||||
NSDictionary *dict = @{
|
||||
@"PrimaryUsagePage" : [NSNumber numberWithInt:page],
|
||||
@"PrimaryUsage" : [NSNumber numberWithInt:usage],
|
||||
};
|
||||
|
||||
return dict;
|
||||
}
|
||||
|
||||
NSArray *getProductNames(NSDictionary *sensors) {
|
||||
IOHIDEventSystemClientRef system = IOHIDEventSystemClientCreate(kCFAllocatorDefault);
|
||||
|
||||
IOHIDEventSystemClientSetMatching(system, (__bridge CFDictionaryRef)sensors);
|
||||
NSArray *matchingsrvs = (__bridge NSArray *)IOHIDEventSystemClientCopyServices(system);
|
||||
|
||||
long count = [matchingsrvs count];
|
||||
NSMutableArray *array = [[NSMutableArray alloc] init];
|
||||
|
||||
for (int i = 0; i < count; i++) {
|
||||
IOHIDServiceClientRef sc = (IOHIDServiceClientRef)matchingsrvs[i];
|
||||
NSString *name = (NSString *)IOHIDServiceClientCopyProperty(sc, (__bridge CFStringRef)@"Product");
|
||||
|
||||
if (name) {
|
||||
[array addObject:name];
|
||||
} else {
|
||||
[array addObject:@"noname"];
|
||||
}
|
||||
}
|
||||
|
||||
return array;
|
||||
}
|
||||
|
||||
#define IOHIDEventFieldBase(type) (type << 16)
|
||||
#define kIOHIDEventTypeTemperature 15
|
||||
#define kIOHIDEventTypePower 25
|
||||
|
||||
NSArray *getThermalValues(NSDictionary *sensors) {
|
||||
IOHIDEventSystemClientRef system = IOHIDEventSystemClientCreate(kCFAllocatorDefault);
|
||||
|
||||
IOHIDEventSystemClientSetMatching(system, (__bridge CFDictionaryRef)sensors);
|
||||
NSArray *matchingsrvs = (__bridge NSArray *)IOHIDEventSystemClientCopyServices(system);
|
||||
|
||||
long count = [matchingsrvs count];
|
||||
NSMutableArray *array = [[NSMutableArray alloc] init];
|
||||
|
||||
for (int i = 0; i < count; i++) {
|
||||
IOHIDServiceClientRef sc = (IOHIDServiceClientRef)matchingsrvs[i];
|
||||
IOHIDEventRef event = IOHIDServiceClientCopyEvent(sc, kIOHIDEventTypeTemperature, 0, 0);
|
||||
|
||||
NSNumber *value;
|
||||
double temp = 0.0;
|
||||
|
||||
if (event != 0) {
|
||||
temp = IOHIDEventGetFloatValue(event, IOHIDEventFieldBase(kIOHIDEventTypeTemperature));
|
||||
}
|
||||
|
||||
value = [NSNumber numberWithDouble:temp];
|
||||
[array addObject:value];
|
||||
}
|
||||
|
||||
return array;
|
||||
}
|
||||
|
||||
NSString *dumpNamesValues(NSArray *kvsN, NSArray *kvsV) {
|
||||
NSMutableString *valueString = [[NSMutableString alloc] init];
|
||||
int count = [kvsN count];
|
||||
|
||||
for (int i = 0; i < count; i++) {
|
||||
NSString *output = [NSString stringWithFormat:@"%s:%lf\n", [kvsN[i] UTF8String], [kvsV[i] doubleValue]];
|
||||
[valueString appendString:output];
|
||||
}
|
||||
|
||||
return valueString;
|
||||
}
|
||||
|
||||
char *getThermals() {
|
||||
NSDictionary *thermalSensors = matching(0xff00, 5);
|
||||
NSArray *thermalNames = getProductNames(thermalSensors);
|
||||
NSArray *thermalValues = getThermalValues(thermalSensors);
|
||||
NSString *result = dumpNamesValues(thermalNames, thermalValues);
|
||||
char *finalStr = strdup([result UTF8String]);
|
||||
|
||||
CFRelease(thermalSensors);
|
||||
CFRelease(thermalNames);
|
||||
CFRelease(thermalValues);
|
||||
CFRelease(result);
|
||||
|
||||
return finalStr;
|
||||
}
|
181
sensors/sensors_darwin.go
Normal file
181
sensors/sensors_darwin.go
Normal file
@ -0,0 +1,181 @@
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
//go:build darwin && !arm64
|
||||
|
||||
package sensors
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"unsafe"
|
||||
|
||||
"github.com/shirou/gopsutil/v4/internal/common"
|
||||
)
|
||||
|
||||
func TemperaturesWithContext(ctx context.Context) ([]TemperatureStat, error) {
|
||||
ioKit, err := common.NewLibrary(common.IOKit)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer ioKit.Close()
|
||||
|
||||
smc, err := common.NewSMC(ioKit)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer smc.Close()
|
||||
|
||||
var temperatures []TemperatureStat
|
||||
for _, key := range temperatureKeys {
|
||||
temperatures = append(temperatures, TemperatureStat{
|
||||
SensorKey: key,
|
||||
Temperature: getTemperature(smc, key),
|
||||
})
|
||||
}
|
||||
|
||||
return temperatures, nil
|
||||
}
|
||||
|
||||
var temperatureKeys = []string{
|
||||
"TA0P", // AMBIENT_AIR_0
|
||||
"TA1P", // AMBIENT_AIR_1
|
||||
"TC0D", // CPU_0_DIODE
|
||||
"TC0H", // CPU_0_HEATSINK
|
||||
"TC0P", // CPU_0_PROXIMITY
|
||||
"TB0T", // ENCLOSURE_BASE_0
|
||||
"TB1T", // ENCLOSURE_BASE_1
|
||||
"TB2T", // ENCLOSURE_BASE_2
|
||||
"TB3T", // ENCLOSURE_BASE_3
|
||||
"TG0D", // GPU_0_DIODE
|
||||
"TG0H", // GPU_0_HEATSINK
|
||||
"TG0P", // GPU_0_PROXIMITY
|
||||
"TH0P", // HARD_DRIVE_BAY
|
||||
"TM0S", // MEMORY_SLOT_0
|
||||
"TM0P", // MEMORY_SLOTS_PROXIMITY
|
||||
"TN0H", // NORTHBRIDGE
|
||||
"TN0D", // NORTHBRIDGE_DIODE
|
||||
"TN0P", // NORTHBRIDGE_PROXIMITY
|
||||
"TI0P", // THUNDERBOLT_0
|
||||
"TI1P", // THUNDERBOLT_1
|
||||
"TW0P", // WIRELESS_MODULE
|
||||
}
|
||||
|
||||
type smcReturn struct {
|
||||
data [32]uint8
|
||||
dataType uint32
|
||||
dataSize uint32
|
||||
kSMC uint8
|
||||
}
|
||||
|
||||
type smcPLimitData struct {
|
||||
version uint16
|
||||
length uint16
|
||||
cpuPLimit uint32
|
||||
gpuPLimit uint32
|
||||
memPLimit uint32
|
||||
}
|
||||
|
||||
type smcKeyInfoData struct {
|
||||
dataSize uint32
|
||||
dataType uint32
|
||||
dataAttributes uint8
|
||||
}
|
||||
|
||||
type smcVersion struct {
|
||||
major byte
|
||||
minor byte
|
||||
build byte
|
||||
reserved byte
|
||||
release uint16
|
||||
}
|
||||
|
||||
type smcParamStruct struct {
|
||||
key uint32
|
||||
vers smcVersion
|
||||
plimitData smcPLimitData
|
||||
keyInfo smcKeyInfoData
|
||||
result uint8
|
||||
status uint8
|
||||
data8 uint8
|
||||
data32 uint32
|
||||
bytes [32]byte
|
||||
}
|
||||
|
||||
const (
|
||||
smcKeySize = 4
|
||||
dataTypeSp78 = "sp78"
|
||||
)
|
||||
|
||||
func getTemperature(smc *common.SMC, key string) float64 {
|
||||
result, err := readSMC(smc, key)
|
||||
if err != nil {
|
||||
return 0.0
|
||||
}
|
||||
|
||||
if result.dataSize == 2 && result.dataType == toUint32(dataTypeSp78) {
|
||||
return 0.0
|
||||
}
|
||||
|
||||
return float64(result.data[0])
|
||||
}
|
||||
|
||||
func readSMC(smc *common.SMC, key string) (*smcReturn, error) {
|
||||
input := new(smcParamStruct)
|
||||
resultSmc := new(smcReturn)
|
||||
|
||||
input.key = toUint32(key)
|
||||
input.data8 = common.KSMCGetKeyInfo
|
||||
|
||||
result, err := callSMC(smc, input)
|
||||
resultSmc.kSMC = result.result
|
||||
|
||||
if err != nil || result.result != common.KSMCSuccess {
|
||||
return resultSmc, fmt.Errorf("ERROR: IOConnectCallStructMethod failed")
|
||||
}
|
||||
|
||||
resultSmc.dataSize = uint32(result.keyInfo.dataSize)
|
||||
resultSmc.dataType = uint32(result.keyInfo.dataSize)
|
||||
|
||||
input.keyInfo.dataSize = result.keyInfo.dataSize
|
||||
input.data8 = common.KSMCReadKey
|
||||
|
||||
result, err = callSMC(smc, input)
|
||||
resultSmc.kSMC = result.result
|
||||
|
||||
if err != nil || result.result != common.KSMCSuccess {
|
||||
return resultSmc, err
|
||||
}
|
||||
|
||||
resultSmc.data = result.bytes
|
||||
return resultSmc, nil
|
||||
}
|
||||
|
||||
func callSMC(smc *common.SMC, input *smcParamStruct) (*smcParamStruct, error) {
|
||||
output := new(smcParamStruct)
|
||||
inputCnt := unsafe.Sizeof(*input)
|
||||
outputCnt := unsafe.Sizeof(*output)
|
||||
|
||||
result := smc.CallStruct(common.KSMCHandleYPCEvent,
|
||||
uintptr(unsafe.Pointer(input)), inputCnt, uintptr(unsafe.Pointer(output)), &outputCnt)
|
||||
|
||||
if result != 0 {
|
||||
return output, fmt.Errorf("ERROR: IOConnectCallStructMethod failed")
|
||||
}
|
||||
|
||||
return output, nil
|
||||
}
|
||||
|
||||
func toUint32(key string) uint32 {
|
||||
if len(key) != smcKeySize {
|
||||
return 0
|
||||
}
|
||||
|
||||
var ans uint32 = 0
|
||||
var shift uint32 = 24
|
||||
|
||||
for i := 0; i < smcKeySize; i++ {
|
||||
ans += uint32(key[i]) << shift
|
||||
shift -= 8
|
||||
}
|
||||
|
||||
return ans
|
||||
}
|
194
sensors/sensors_darwin_arm64.go
Normal file
194
sensors/sensors_darwin_arm64.go
Normal file
@ -0,0 +1,194 @@
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
//go:build darwin && arm64
|
||||
|
||||
package sensors
|
||||
|
||||
import (
|
||||
"context"
|
||||
"unsafe"
|
||||
|
||||
"github.com/shirou/gopsutil/v4/internal/common"
|
||||
)
|
||||
|
||||
func ReadTemperaturesArm() []TemperatureStat {
|
||||
temperatures, _ := TemperaturesWithContext(context.Background())
|
||||
return temperatures
|
||||
}
|
||||
|
||||
func TemperaturesWithContext(ctx context.Context) ([]TemperatureStat, error) {
|
||||
ioKit, err := common.NewLibrary(common.IOKit)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer ioKit.Close()
|
||||
|
||||
coreFoundation, err := common.NewLibrary(common.CoreFoundation)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer coreFoundation.Close()
|
||||
|
||||
ta := &temperatureArm{
|
||||
ioKit: ioKit,
|
||||
cf: coreFoundation,
|
||||
cfRelease: common.GetFunc[common.CFReleaseFunc](coreFoundation, common.CFReleaseSym),
|
||||
cfStringCreateWithCString: common.GetFunc[common.CFStringCreateWithCStringFunc](coreFoundation, common.CFStringCreateWithCStringSym),
|
||||
cfArrayGetCount: common.GetFunc[common.CFArrayGetCountFunc](coreFoundation, common.CFArrayGetCountSym),
|
||||
cfArrayGetValueAtIndex: common.GetFunc[common.CFArrayGetValueAtIndexFunc](coreFoundation, common.CFArrayGetValueAtIndexSym),
|
||||
ioHIDEventSystemClientCreate: common.GetFunc[common.IOHIDEventSystemClientCreateFunc](ioKit, common.IOHIDEventSystemClientCreateSym),
|
||||
ioHIDEventSystemClientSetMatching: common.GetFunc[common.IOHIDEventSystemClientSetMatchingFunc](ioKit, common.IOHIDEventSystemClientSetMatchingSym),
|
||||
ioHIDEventSystemClientCopyServices: common.GetFunc[common.IOHIDEventSystemClientCopyServicesFunc](ioKit, common.IOHIDEventSystemClientCopyServicesSym),
|
||||
}
|
||||
|
||||
ta.matching(0xff00, 5)
|
||||
thermalNames := ta.getProductNames()
|
||||
thermalValues := ta.getThermalValues()
|
||||
result := dumpNameValues(thermalNames, thermalValues)
|
||||
|
||||
ta.cfRelease(uintptr(ta.sensors))
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func dumpNameValues(kvsN []string, kvsV []float64) []TemperatureStat {
|
||||
count := len(kvsN)
|
||||
temperatureMap := make(map[string]TemperatureStat)
|
||||
|
||||
for i := 0; i < count; i++ {
|
||||
temperatureMap[kvsN[i]] = TemperatureStat{
|
||||
SensorKey: kvsN[i],
|
||||
Temperature: kvsV[i],
|
||||
}
|
||||
}
|
||||
|
||||
temperatures := make([]TemperatureStat, 0, len(temperatureMap))
|
||||
for _, stat := range temperatureMap {
|
||||
temperatures = append(temperatures, stat)
|
||||
}
|
||||
|
||||
return temperatures
|
||||
}
|
||||
|
||||
type temperatureArm struct {
|
||||
ioKit *common.Library
|
||||
cf *common.Library
|
||||
|
||||
cfRelease common.CFReleaseFunc
|
||||
cfStringCreateWithCString common.CFStringCreateWithCStringFunc
|
||||
cfArrayGetCount common.CFArrayGetCountFunc
|
||||
cfArrayGetValueAtIndex common.CFArrayGetValueAtIndexFunc
|
||||
|
||||
ioHIDEventSystemClientCreate common.IOHIDEventSystemClientCreateFunc
|
||||
ioHIDEventSystemClientSetMatching common.IOHIDEventSystemClientSetMatchingFunc
|
||||
ioHIDEventSystemClientCopyServices common.IOHIDEventSystemClientCopyServicesFunc
|
||||
|
||||
sensors unsafe.Pointer
|
||||
}
|
||||
|
||||
func (ta *temperatureArm) getProductNames() []string {
|
||||
ioHIDServiceClientCopyProperty := common.GetFunc[common.IOHIDServiceClientCopyPropertyFunc](ta.ioKit, common.IOHIDServiceClientCopyPropertySym)
|
||||
|
||||
cfStringGetLength := common.GetFunc[common.CFStringGetLengthFunc](ta.cf, common.CFStringGetLengthSym)
|
||||
cfStringGetCString := common.GetFunc[common.CFStringGetCStringFunc](ta.cf, common.CFStringGetCStringSym)
|
||||
|
||||
var names []string
|
||||
system := ta.ioHIDEventSystemClientCreate(common.KCFAllocatorDefault)
|
||||
|
||||
ta.ioHIDEventSystemClientSetMatching(uintptr(system), uintptr(ta.sensors))
|
||||
matchingsrvs := ta.ioHIDEventSystemClientCopyServices(uintptr(system))
|
||||
|
||||
if matchingsrvs == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
count := ta.cfArrayGetCount(uintptr(matchingsrvs))
|
||||
|
||||
var i int32
|
||||
str := ta.cfStr("Product")
|
||||
for i = 0; i < count; i++ {
|
||||
sc := ta.cfArrayGetValueAtIndex(uintptr(matchingsrvs), i)
|
||||
name := ioHIDServiceClientCopyProperty(uintptr(sc), uintptr(str))
|
||||
|
||||
if name != nil {
|
||||
length := cfStringGetLength(uintptr(name)) + 1 // null terminator
|
||||
buf := make([]byte, length-1)
|
||||
cfStringGetCString(uintptr(name), &buf[0], length, common.KCFStringEncodingUTF8)
|
||||
|
||||
names = append(names, string(buf))
|
||||
ta.cfRelease(uintptr(name))
|
||||
} else {
|
||||
names = append(names, "noname")
|
||||
}
|
||||
}
|
||||
|
||||
ta.cfRelease(uintptr(matchingsrvs))
|
||||
ta.cfRelease(uintptr(str))
|
||||
return names
|
||||
}
|
||||
|
||||
func (ta *temperatureArm) getThermalValues() []float64 {
|
||||
ioHIDServiceClientCopyEvent := common.GetFunc[common.IOHIDServiceClientCopyEventFunc](ta.ioKit, common.IOHIDServiceClientCopyEventSym)
|
||||
ioHIDEventGetFloatValue := common.GetFunc[common.IOHIDEventGetFloatValueFunc](ta.ioKit, common.IOHIDEventGetFloatValueSym)
|
||||
|
||||
system := ta.ioHIDEventSystemClientCreate(common.KCFAllocatorDefault)
|
||||
|
||||
ta.ioHIDEventSystemClientSetMatching(uintptr(system), uintptr(ta.sensors))
|
||||
matchingsrvs := ta.ioHIDEventSystemClientCopyServices(uintptr(system))
|
||||
|
||||
if matchingsrvs == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
count := ta.cfArrayGetCount(uintptr(matchingsrvs))
|
||||
|
||||
var values []float64
|
||||
var i int32
|
||||
for i = 0; i < count; i++ {
|
||||
sc := ta.cfArrayGetValueAtIndex(uintptr(matchingsrvs), i)
|
||||
event := ioHIDServiceClientCopyEvent(uintptr(sc), common.KIOHIDEventTypeTemperature, 0, 0)
|
||||
temp := 0.0
|
||||
|
||||
if event != nil {
|
||||
temp = ioHIDEventGetFloatValue(uintptr(event), ioHIDEventFieldBase(common.KIOHIDEventTypeTemperature))
|
||||
ta.cfRelease(uintptr(event))
|
||||
}
|
||||
|
||||
values = append(values, temp)
|
||||
}
|
||||
|
||||
ta.cfRelease(uintptr(matchingsrvs))
|
||||
return values
|
||||
}
|
||||
|
||||
func (ta *temperatureArm) matching(page, usage int) {
|
||||
cfNumberCreate := common.GetFunc[common.CFNumberCreateFunc](ta.cf, common.CFNumberCreateSym)
|
||||
cfDictionaryCreate := common.GetFunc[common.CFDictionaryCreateFunc](ta.cf, common.CFDictionaryCreateSym)
|
||||
|
||||
pageNum := cfNumberCreate(common.KCFAllocatorDefault, common.KCFNumberIntType, uintptr(unsafe.Pointer(&page)))
|
||||
usageNum := cfNumberCreate(common.KCFAllocatorDefault, common.KCFNumberIntType, uintptr(unsafe.Pointer(&usage)))
|
||||
|
||||
k1 := ta.cfStr("PrimaryUsagePage")
|
||||
k2 := ta.cfStr("PrimaryUsage")
|
||||
|
||||
keys := []unsafe.Pointer{k1, k2}
|
||||
values := []unsafe.Pointer{pageNum, usageNum}
|
||||
|
||||
kCFTypeDictionaryKeyCallBacks, _ := ta.cf.Dlsym("kCFTypeDictionaryKeyCallBacks")
|
||||
kCFTypeDictionaryValueCallBacks, _ := ta.cf.Dlsym("kCFTypeDictionaryValueCallBacks")
|
||||
|
||||
ta.sensors = cfDictionaryCreate(common.KCFAllocatorDefault, &keys[0], &values[0], 2,
|
||||
kCFTypeDictionaryKeyCallBacks,
|
||||
kCFTypeDictionaryValueCallBacks)
|
||||
|
||||
ta.cfRelease(uintptr(pageNum))
|
||||
ta.cfRelease(uintptr(usageNum))
|
||||
ta.cfRelease(uintptr(k1))
|
||||
ta.cfRelease(uintptr(k2))
|
||||
}
|
||||
|
||||
func (ta *temperatureArm) cfStr(str string) unsafe.Pointer {
|
||||
return ta.cfStringCreateWithCString(common.KCFAllocatorDefault, str, common.KCFStringEncodingUTF8)
|
||||
}
|
||||
|
||||
func ioHIDEventFieldBase(i int32) int32 {
|
||||
return i << 16
|
||||
}
|
@ -1,95 +0,0 @@
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
//go:build darwin && cgo
|
||||
|
||||
package sensors
|
||||
|
||||
// #cgo CFLAGS: -x objective-c
|
||||
// #cgo LDFLAGS: -framework Foundation -framework IOKit
|
||||
// #include "smc_darwin.h"
|
||||
// #include "darwin_arm_sensors.h"
|
||||
import "C"
|
||||
import (
|
||||
"bufio"
|
||||
"context"
|
||||
"math"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"strings"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
func ReadTemperaturesArm() []TemperatureStat {
|
||||
cStr := C.getThermals()
|
||||
defer C.free(unsafe.Pointer(cStr))
|
||||
|
||||
var stats []TemperatureStat
|
||||
goStr := C.GoString(cStr)
|
||||
scanner := bufio.NewScanner(strings.NewReader(goStr))
|
||||
for scanner.Scan() {
|
||||
split := strings.Split(scanner.Text(), ":")
|
||||
if len(split) != 2 {
|
||||
continue
|
||||
}
|
||||
|
||||
val, err := strconv.ParseFloat(split[1], 32)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
sensorKey := strings.Split(split[0], " ")[0]
|
||||
|
||||
val = math.Abs(val)
|
||||
|
||||
stats = append(stats, TemperatureStat{
|
||||
SensorKey: sensorKey,
|
||||
Temperature: float64(val),
|
||||
})
|
||||
}
|
||||
|
||||
return stats
|
||||
}
|
||||
|
||||
func TemperaturesWithContext(ctx context.Context) ([]TemperatureStat, error) {
|
||||
if runtime.GOARCH == "arm64" {
|
||||
return ReadTemperaturesArm(), nil
|
||||
}
|
||||
|
||||
temperatureKeys := []string{
|
||||
C.AMBIENT_AIR_0,
|
||||
C.AMBIENT_AIR_1,
|
||||
C.CPU_0_DIODE,
|
||||
C.CPU_0_HEATSINK,
|
||||
C.CPU_0_PROXIMITY,
|
||||
C.ENCLOSURE_BASE_0,
|
||||
C.ENCLOSURE_BASE_1,
|
||||
C.ENCLOSURE_BASE_2,
|
||||
C.ENCLOSURE_BASE_3,
|
||||
C.GPU_0_DIODE,
|
||||
C.GPU_0_HEATSINK,
|
||||
C.GPU_0_PROXIMITY,
|
||||
C.HARD_DRIVE_BAY,
|
||||
C.MEMORY_SLOT_0,
|
||||
C.MEMORY_SLOTS_PROXIMITY,
|
||||
C.NORTHBRIDGE,
|
||||
C.NORTHBRIDGE_DIODE,
|
||||
C.NORTHBRIDGE_PROXIMITY,
|
||||
C.THUNDERBOLT_0,
|
||||
C.THUNDERBOLT_1,
|
||||
C.WIRELESS_MODULE,
|
||||
}
|
||||
var temperatures []TemperatureStat
|
||||
|
||||
C.gopsutil_v4_open_smc()
|
||||
defer C.gopsutil_v4_close_smc()
|
||||
|
||||
for _, key := range temperatureKeys {
|
||||
ckey := C.CString(key)
|
||||
defer C.free(unsafe.Pointer(ckey))
|
||||
temperatures = append(temperatures, TemperatureStat{
|
||||
SensorKey: key,
|
||||
Temperature: float64(C.gopsutil_v4_get_temperature(ckey)),
|
||||
})
|
||||
}
|
||||
|
||||
return temperatures, nil
|
||||
}
|
@ -1,14 +0,0 @@
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
//go:build darwin && !cgo
|
||||
|
||||
package sensors
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/shirou/gopsutil/v4/internal/common"
|
||||
)
|
||||
|
||||
func TemperaturesWithContext(ctx context.Context) ([]TemperatureStat, error) {
|
||||
return []TemperatureStat{}, common.ErrNotImplementedError
|
||||
}
|
@ -24,7 +24,7 @@ func TemperaturesWithContext(ctx context.Context) ([]TemperatureStat, error) {
|
||||
|
||||
files, err := getTemperatureFiles(ctx)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get tempreteure files, %w", err)
|
||||
return nil, fmt.Errorf("failed to get temperature files, %w", err)
|
||||
}
|
||||
|
||||
if len(files) == 0 { // handle distributions without hwmon, like raspbian #391, parse legacy thermal_zone files
|
||||
|
@ -1,170 +0,0 @@
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include "smc_darwin.h"
|
||||
|
||||
#define IOSERVICE_SMC "AppleSMC"
|
||||
#define IOSERVICE_MODEL "IOPlatformExpertDevice"
|
||||
|
||||
#define DATA_TYPE_SP78 "sp78"
|
||||
|
||||
typedef enum {
|
||||
kSMCUserClientOpen = 0,
|
||||
kSMCUserClientClose = 1,
|
||||
kSMCHandleYPCEvent = 2,
|
||||
kSMCReadKey = 5,
|
||||
kSMCWriteKey = 6,
|
||||
kSMCGetKeyCount = 7,
|
||||
kSMCGetKeyFromIndex = 8,
|
||||
kSMCGetKeyInfo = 9,
|
||||
} selector_t;
|
||||
|
||||
typedef struct {
|
||||
unsigned char major;
|
||||
unsigned char minor;
|
||||
unsigned char build;
|
||||
unsigned char reserved;
|
||||
unsigned short release;
|
||||
} SMCVersion;
|
||||
|
||||
typedef struct {
|
||||
uint16_t version;
|
||||
uint16_t length;
|
||||
uint32_t cpuPLimit;
|
||||
uint32_t gpuPLimit;
|
||||
uint32_t memPLimit;
|
||||
} SMCPLimitData;
|
||||
|
||||
typedef struct {
|
||||
IOByteCount data_size;
|
||||
uint32_t data_type;
|
||||
uint8_t data_attributes;
|
||||
} SMCKeyInfoData;
|
||||
|
||||
typedef struct {
|
||||
uint32_t key;
|
||||
SMCVersion vers;
|
||||
SMCPLimitData p_limit_data;
|
||||
SMCKeyInfoData key_info;
|
||||
uint8_t result;
|
||||
uint8_t status;
|
||||
uint8_t data8;
|
||||
uint32_t data32;
|
||||
uint8_t bytes[32];
|
||||
} SMCParamStruct;
|
||||
|
||||
typedef enum {
|
||||
kSMCSuccess = 0,
|
||||
kSMCError = 1,
|
||||
kSMCKeyNotFound = 0x84,
|
||||
} kSMC_t;
|
||||
|
||||
typedef struct {
|
||||
uint8_t data[32];
|
||||
uint32_t data_type;
|
||||
uint32_t data_size;
|
||||
kSMC_t kSMC;
|
||||
} smc_return_t;
|
||||
|
||||
static const int SMC_KEY_SIZE = 4; // number of characters in an SMC key.
|
||||
static io_connect_t conn; // our connection to the SMC.
|
||||
|
||||
kern_return_t gopsutil_v4_open_smc(void) {
|
||||
kern_return_t result;
|
||||
io_service_t service;
|
||||
|
||||
service = IOServiceGetMatchingService(0, IOServiceMatching(IOSERVICE_SMC));
|
||||
if (service == 0) {
|
||||
// Note: IOServiceMatching documents 0 on failure
|
||||
printf("ERROR: %s NOT FOUND\n", IOSERVICE_SMC);
|
||||
return kIOReturnError;
|
||||
}
|
||||
|
||||
result = IOServiceOpen(service, mach_task_self(), 0, &conn);
|
||||
IOObjectRelease(service);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
kern_return_t gopsutil_v4_close_smc(void) { return IOServiceClose(conn); }
|
||||
|
||||
static uint32_t to_uint32(char *key) {
|
||||
uint32_t ans = 0;
|
||||
uint32_t shift = 24;
|
||||
|
||||
if (strlen(key) != SMC_KEY_SIZE) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
for (int i = 0; i < SMC_KEY_SIZE; i++) {
|
||||
ans += key[i] << shift;
|
||||
shift -= 8;
|
||||
}
|
||||
|
||||
return ans;
|
||||
}
|
||||
|
||||
static kern_return_t call_smc(SMCParamStruct *input, SMCParamStruct *output) {
|
||||
kern_return_t result;
|
||||
size_t input_cnt = sizeof(SMCParamStruct);
|
||||
size_t output_cnt = sizeof(SMCParamStruct);
|
||||
|
||||
result = IOConnectCallStructMethod(conn, kSMCHandleYPCEvent, input, input_cnt,
|
||||
output, &output_cnt);
|
||||
|
||||
if (result != kIOReturnSuccess) {
|
||||
result = err_get_code(result);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
static kern_return_t read_smc(char *key, smc_return_t *result_smc) {
|
||||
kern_return_t result;
|
||||
SMCParamStruct input;
|
||||
SMCParamStruct output;
|
||||
|
||||
memset(&input, 0, sizeof(SMCParamStruct));
|
||||
memset(&output, 0, sizeof(SMCParamStruct));
|
||||
memset(result_smc, 0, sizeof(smc_return_t));
|
||||
|
||||
input.key = to_uint32(key);
|
||||
input.data8 = kSMCGetKeyInfo;
|
||||
|
||||
result = call_smc(&input, &output);
|
||||
result_smc->kSMC = output.result;
|
||||
|
||||
if (result != kIOReturnSuccess || output.result != kSMCSuccess) {
|
||||
return result;
|
||||
}
|
||||
|
||||
result_smc->data_size = output.key_info.data_size;
|
||||
result_smc->data_type = output.key_info.data_type;
|
||||
|
||||
input.key_info.data_size = output.key_info.data_size;
|
||||
input.data8 = kSMCReadKey;
|
||||
|
||||
result = call_smc(&input, &output);
|
||||
result_smc->kSMC = output.result;
|
||||
|
||||
if (result != kIOReturnSuccess || output.result != kSMCSuccess) {
|
||||
return result;
|
||||
}
|
||||
|
||||
memcpy(result_smc->data, output.bytes, sizeof(output.bytes));
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
double gopsutil_v4_get_temperature(char *key) {
|
||||
kern_return_t result;
|
||||
smc_return_t result_smc;
|
||||
|
||||
result = read_smc(key, &result_smc);
|
||||
|
||||
if (!(result == kIOReturnSuccess) && result_smc.data_size == 2 &&
|
||||
result_smc.data_type == to_uint32(DATA_TYPE_SP78)) {
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
return (double)result_smc.data[0];
|
||||
}
|
@ -1,33 +0,0 @@
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
#ifndef __SMC_H__
|
||||
#define __SMC_H__ 1
|
||||
|
||||
#include <IOKit/IOKitLib.h>
|
||||
|
||||
#define AMBIENT_AIR_0 "TA0P"
|
||||
#define AMBIENT_AIR_1 "TA1P"
|
||||
#define CPU_0_DIODE "TC0D"
|
||||
#define CPU_0_HEATSINK "TC0H"
|
||||
#define CPU_0_PROXIMITY "TC0P"
|
||||
#define ENCLOSURE_BASE_0 "TB0T"
|
||||
#define ENCLOSURE_BASE_1 "TB1T"
|
||||
#define ENCLOSURE_BASE_2 "TB2T"
|
||||
#define ENCLOSURE_BASE_3 "TB3T"
|
||||
#define GPU_0_DIODE "TG0D"
|
||||
#define GPU_0_HEATSINK "TG0H"
|
||||
#define GPU_0_PROXIMITY "TG0P"
|
||||
#define HARD_DRIVE_BAY "TH0P"
|
||||
#define MEMORY_SLOT_0 "TM0S"
|
||||
#define MEMORY_SLOTS_PROXIMITY "TM0P"
|
||||
#define NORTHBRIDGE "TN0H"
|
||||
#define NORTHBRIDGE_DIODE "TN0D"
|
||||
#define NORTHBRIDGE_PROXIMITY "TN0P"
|
||||
#define THUNDERBOLT_0 "TI0P"
|
||||
#define THUNDERBOLT_1 "TI1P"
|
||||
#define WIRELESS_MODULE "TW0P"
|
||||
|
||||
kern_return_t gopsutil_v4_open_smc(void);
|
||||
kern_return_t gopsutil_v4_close_smc(void);
|
||||
double gopsutil_v4_get_temperature(char *);
|
||||
|
||||
#endif // __SMC_H__
|
Loading…
x
Reference in New Issue
Block a user