mirror of https://github.com/shirou/gopsutil
Merge pull request #1702 from uubulb/purego_darwin
feat(cpu, mem, sensors, disk, process)(darwin): cgo-free implementationspull/1715/head
commit
4140cda4ac
@ -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
|
|
||||||
}
|
|
@ -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
|
|
||||||
}
|
|
@ -1,45 +0,0 @@
|
|||||||
// SPDX-License-Identifier: BSD-3-Clause
|
|
||||||
//go:build darwin && cgo && !ios
|
|
||||||
|
|
||||||
package disk
|
|
||||||
|
|
||||||
/*
|
|
||||||
#cgo LDFLAGS: -framework CoreFoundation -framework IOKit
|
|
||||||
#include <stdint.h>
|
|
||||||
#include <CoreFoundation/CoreFoundation.h>
|
|
||||||
#include "iostat_darwin.h"
|
|
||||||
*/
|
|
||||||
import "C"
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
|
|
||||||
"github.com/shirou/gopsutil/v4/internal/common"
|
|
||||||
)
|
|
||||||
|
|
||||||
func IOCountersWithContext(ctx context.Context, names ...string) (map[string]IOCountersStat, error) {
|
|
||||||
var buf [C.NDRIVE]C.DriveStats
|
|
||||||
n, err := C.gopsutil_v4_readdrivestat(&buf[0], C.int(len(buf)))
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
ret := make(map[string]IOCountersStat, 0)
|
|
||||||
for i := 0; i < int(n); i++ {
|
|
||||||
d := IOCountersStat{
|
|
||||||
ReadBytes: uint64(buf[i].read),
|
|
||||||
WriteBytes: uint64(buf[i].written),
|
|
||||||
ReadCount: uint64(buf[i].nread),
|
|
||||||
WriteCount: uint64(buf[i].nwrite),
|
|
||||||
ReadTime: uint64(buf[i].readtime / 1000 / 1000), // note: read/write time are in ns, but we want ms.
|
|
||||||
WriteTime: uint64(buf[i].writetime / 1000 / 1000),
|
|
||||||
IoTime: uint64((buf[i].readtime + buf[i].writetime) / 1000 / 1000),
|
|
||||||
Name: C.GoString(&buf[i].name[0]),
|
|
||||||
}
|
|
||||||
if len(names) > 0 && !common.StringsHas(names, d.Name) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
ret[d.Name] = d
|
|
||||||
}
|
|
||||||
return ret, nil
|
|
||||||
}
|
|
@ -1,14 +0,0 @@
|
|||||||
// SPDX-License-Identifier: BSD-3-Clause
|
|
||||||
//go:build (darwin && !cgo) || ios
|
|
||||||
|
|
||||||
package disk
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
|
|
||||||
"github.com/shirou/gopsutil/v4/internal/common"
|
|
||||||
)
|
|
||||||
|
|
||||||
func IOCountersWithContext(ctx context.Context, names ...string) (map[string]IOCountersStat, error) {
|
|
||||||
return nil, common.ErrNotImplementedError
|
|
||||||
}
|
|
@ -1,131 +0,0 @@
|
|||||||
// SPDX-License-Identifier: BSD-3-Clause
|
|
||||||
// SPDX-FileCopyrightText: Copyright (c) 2017, kadota kyohei
|
|
||||||
// https://github.com/lufia/iostat/blob/9f7362b77ad333b26c01c99de52a11bdb650ded2/iostat_darwin.c
|
|
||||||
#include <stdint.h>
|
|
||||||
#include <CoreFoundation/CoreFoundation.h>
|
|
||||||
#include "iostat_darwin.h"
|
|
||||||
|
|
||||||
#define IOKIT 1 /* to get io_name_t in device_types.h */
|
|
||||||
|
|
||||||
#include <IOKit/IOKitLib.h>
|
|
||||||
#include <IOKit/storage/IOBlockStorageDriver.h>
|
|
||||||
#include <IOKit/storage/IOMedia.h>
|
|
||||||
#include <IOKit/IOBSD.h>
|
|
||||||
|
|
||||||
#include <mach/mach_host.h>
|
|
||||||
|
|
||||||
static int getdrivestat(io_registry_entry_t d, DriveStats *stat);
|
|
||||||
static int fillstat(io_registry_entry_t d, DriveStats *stat);
|
|
||||||
|
|
||||||
int
|
|
||||||
gopsutil_v4_readdrivestat(DriveStats a[], int n)
|
|
||||||
{
|
|
||||||
CFMutableDictionaryRef match;
|
|
||||||
io_iterator_t drives;
|
|
||||||
io_registry_entry_t d;
|
|
||||||
kern_return_t status;
|
|
||||||
int na, rv;
|
|
||||||
|
|
||||||
match = IOServiceMatching("IOMedia");
|
|
||||||
CFDictionaryAddValue(match, CFSTR(kIOMediaWholeKey), kCFBooleanTrue);
|
|
||||||
status = IOServiceGetMatchingServices(0, match, &drives);
|
|
||||||
if(status != KERN_SUCCESS)
|
|
||||||
return -1;
|
|
||||||
|
|
||||||
na = 0;
|
|
||||||
while(na < n && (d=IOIteratorNext(drives)) > 0){
|
|
||||||
rv = getdrivestat(d, &a[na]);
|
|
||||||
if(rv < 0)
|
|
||||||
return -1;
|
|
||||||
if(rv > 0)
|
|
||||||
na++;
|
|
||||||
IOObjectRelease(d);
|
|
||||||
}
|
|
||||||
IOObjectRelease(drives);
|
|
||||||
return na;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int
|
|
||||||
getdrivestat(io_registry_entry_t d, DriveStats *stat)
|
|
||||||
{
|
|
||||||
io_registry_entry_t parent;
|
|
||||||
kern_return_t status;
|
|
||||||
CFDictionaryRef props;
|
|
||||||
CFStringRef name;
|
|
||||||
CFNumberRef num;
|
|
||||||
int rv;
|
|
||||||
|
|
||||||
memset(stat, 0, sizeof *stat);
|
|
||||||
status = IORegistryEntryGetParentEntry(d, kIOServicePlane, &parent);
|
|
||||||
if(status != KERN_SUCCESS)
|
|
||||||
return -1;
|
|
||||||
if(!IOObjectConformsTo(parent, "IOBlockStorageDriver")){
|
|
||||||
IOObjectRelease(parent);
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
status = IORegistryEntryCreateCFProperties(d, (CFMutableDictionaryRef *)&props, kCFAllocatorDefault, kNilOptions);
|
|
||||||
if(status != KERN_SUCCESS){
|
|
||||||
IOObjectRelease(parent);
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
name = (CFStringRef)CFDictionaryGetValue(props, CFSTR(kIOBSDNameKey));
|
|
||||||
CFStringGetCString(name, stat->name, NAMELEN, CFStringGetSystemEncoding());
|
|
||||||
num = (CFNumberRef)CFDictionaryGetValue(props, CFSTR(kIOMediaSizeKey));
|
|
||||||
CFNumberGetValue(num, kCFNumberSInt64Type, &stat->size);
|
|
||||||
num = (CFNumberRef)CFDictionaryGetValue(props, CFSTR(kIOMediaPreferredBlockSizeKey));
|
|
||||||
CFNumberGetValue(num, kCFNumberSInt64Type, &stat->blocksize);
|
|
||||||
CFRelease(props);
|
|
||||||
|
|
||||||
rv = fillstat(parent, stat);
|
|
||||||
IOObjectRelease(parent);
|
|
||||||
if(rv < 0)
|
|
||||||
return -1;
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
static struct {
|
|
||||||
char *key;
|
|
||||||
size_t off;
|
|
||||||
} statstab[] = {
|
|
||||||
{kIOBlockStorageDriverStatisticsBytesReadKey, offsetof(DriveStats, read)},
|
|
||||||
{kIOBlockStorageDriverStatisticsBytesWrittenKey, offsetof(DriveStats, written)},
|
|
||||||
{kIOBlockStorageDriverStatisticsReadsKey, offsetof(DriveStats, nread)},
|
|
||||||
{kIOBlockStorageDriverStatisticsWritesKey, offsetof(DriveStats, nwrite)},
|
|
||||||
{kIOBlockStorageDriverStatisticsTotalReadTimeKey, offsetof(DriveStats, readtime)},
|
|
||||||
{kIOBlockStorageDriverStatisticsTotalWriteTimeKey, offsetof(DriveStats, writetime)},
|
|
||||||
{kIOBlockStorageDriverStatisticsLatentReadTimeKey, offsetof(DriveStats, readlat)},
|
|
||||||
{kIOBlockStorageDriverStatisticsLatentWriteTimeKey, offsetof(DriveStats, writelat)},
|
|
||||||
};
|
|
||||||
|
|
||||||
static int
|
|
||||||
fillstat(io_registry_entry_t d, DriveStats *stat)
|
|
||||||
{
|
|
||||||
CFDictionaryRef props, v;
|
|
||||||
CFNumberRef num;
|
|
||||||
kern_return_t status;
|
|
||||||
typeof(statstab[0]) *bp, *ep;
|
|
||||||
|
|
||||||
status = IORegistryEntryCreateCFProperties(d, (CFMutableDictionaryRef *)&props, kCFAllocatorDefault, kNilOptions);
|
|
||||||
if(status != KERN_SUCCESS)
|
|
||||||
return -1;
|
|
||||||
v = (CFDictionaryRef)CFDictionaryGetValue(props, CFSTR(kIOBlockStorageDriverStatisticsKey));
|
|
||||||
if(v == NULL){
|
|
||||||
CFRelease(props);
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
ep = &statstab[sizeof(statstab)/sizeof(statstab[0])];
|
|
||||||
for(bp = &statstab[0]; bp < ep; bp++){
|
|
||||||
CFStringRef s;
|
|
||||||
|
|
||||||
s = CFStringCreateWithCString(kCFAllocatorDefault, bp->key, CFStringGetSystemEncoding());
|
|
||||||
num = (CFNumberRef)CFDictionaryGetValue(v, s);
|
|
||||||
if(num)
|
|
||||||
CFNumberGetValue(num, kCFNumberSInt64Type, ((char*)stat)+bp->off);
|
|
||||||
CFRelease(s);
|
|
||||||
}
|
|
||||||
|
|
||||||
CFRelease(props);
|
|
||||||
return 0;
|
|
||||||
}
|
|
@ -1,34 +0,0 @@
|
|||||||
// SPDX-License-Identifier: BSD-3-Clause
|
|
||||||
// SPDX-FileCopyrightText: Copyright (c) 2017, kadota kyohei
|
|
||||||
// https://github.com/lufia/iostat/blob/9f7362b77ad333b26c01c99de52a11bdb650ded2/iostat_darwin.h
|
|
||||||
typedef struct DriveStats DriveStats;
|
|
||||||
typedef struct CPUStats CPUStats;
|
|
||||||
|
|
||||||
enum {
|
|
||||||
NDRIVE = 16,
|
|
||||||
NAMELEN = 31
|
|
||||||
};
|
|
||||||
|
|
||||||
struct DriveStats {
|
|
||||||
char name[NAMELEN+1];
|
|
||||||
int64_t size;
|
|
||||||
int64_t blocksize;
|
|
||||||
|
|
||||||
int64_t read;
|
|
||||||
int64_t written;
|
|
||||||
int64_t nread;
|
|
||||||
int64_t nwrite;
|
|
||||||
int64_t readtime;
|
|
||||||
int64_t writetime;
|
|
||||||
int64_t readlat;
|
|
||||||
int64_t writelat;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct CPUStats {
|
|
||||||
natural_t user;
|
|
||||||
natural_t nice;
|
|
||||||
natural_t sys;
|
|
||||||
natural_t idle;
|
|
||||||
};
|
|
||||||
|
|
||||||
extern int gopsutil_v4_readdrivestat(DriveStats a[], int n);
|
|
@ -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
|
|
||||||
}
|
|
@ -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
|
|
||||||
}
|
|
@ -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;
|
|
||||||
}
|
|
@ -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
|
||||||
|
}
|
@ -0,0 +1,186 @@
|
|||||||
|
// 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))
|
||||||
|
|
||||||
|
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))
|
||||||
|
|
||||||
|
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
|
|
||||||
}
|
|
@ -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…
Reference in New Issue