feat(cpu, mem, sensors)(darwin): cgo-free implementations

pull/1702/head
uubulb 6 months ago
parent 211eb8ccb4
commit 701a74be41

@ -5,12 +5,16 @@ 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 +27,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 +61,17 @@ func Times(percpu bool) ([]TimesStat, error) {
}
func TimesWithContext(ctx context.Context, percpu bool) ([]TimesStat, error) {
lib, err := common.NewLibrary(common.Kernel)
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
@ -115,3 +143,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
}

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

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

@ -3,6 +3,7 @@ module github.com/shirou/gopsutil/v4
go 1.18
require (
github.com/ebitengine/purego v0.7.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

@ -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.7.1 h1:6/55d26lG3o9VCZX8lping+bZcmShseiqlh2bnUDiPA=
github.com/ebitengine/purego v0.7.1/go.mod h1:ah1In8AOtksoNK6yk5z1HTJeUkC1Ez4Wk2idgGslMwQ=
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=

@ -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,218 @@ 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"
Kernel = "/usr/lib/system/libsystem_kernel.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
IOServiceMatchingFunc func(name string) unsafe.Pointer
IOServiceOpenFunc func(service, owningTask, connType uint32, connect *uint32) int
IOServiceCloseFunc func(connect uint32) int
IOObjectReleaseFunc func(object uint32) int
IOConnectCallStructMethodFunc func(connection, selector uint32, inputStruct, inputStructCnt, outputStruct uintptr, outputStructCnt *uintptr) int
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"
IOServiceMatchingSym = "IOServiceMatching"
IOServiceOpenSym = "IOServiceOpen"
IOServiceCloseSym = "IOServiceClose"
IOObjectReleaseSym = "IOObjectRelease"
IOConnectCallStructMethodSym = "IOConnectCallStructMethod"
IOHIDEventSystemClientCreateSym = "IOHIDEventSystemClientCreate"
IOHIDEventSystemClientSetMatchingSym = "IOHIDEventSystemClientSetMatching"
IOHIDServiceClientCopyEventSym = "IOHIDServiceClientCopyEvent"
IOHIDServiceClientCopyPropertySym = "IOHIDServiceClientCopyProperty"
IOHIDEventGetFloatValueSym = "IOHIDEventGetFloatValue"
IOHIDEventSystemClientCopyServicesSym = "IOHIDEventSystemClientCopyServices"
)
const (
KIOHIDEventTypeTemperature = 15
)
// CoreFoundation functions and symbols.
type (
CFNumberCreateFunc func(allocator uintptr, theType int32, valuePtr uintptr) unsafe.Pointer
CFDictionaryCreateFunc func(allocator uintptr, keys, values *unsafe.Pointer, numValues int32,
keyCallBacks, valueCallBacks 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
CFReleaseFunc func(cf uintptr)
)
const (
CFNumberCreateSym = "CFNumberCreate"
CFDictionaryCreateSym = "CFDictionaryCreate"
CFArrayGetCountSym = "CFArrayGetCount"
CFArrayGetValueAtIndexSym = "CFArrayGetValueAtIndex"
CFStringCreateMutableSym = "CFStringCreateMutable"
CFStringGetLengthSym = "CFStringGetLength"
CFStringGetCStringSym = "CFStringGetCString"
CFStringCreateWithCStringSym = "CFStringCreateWithCString"
CFReleaseSym = "CFRelease"
)
const (
KCFStringEncodingUTF8 = 0x08000100
KCFNumberIntType = 9
KCFAllocatorDefault = 0
)
// Kernel functions and symbols.
type (
HostProcessorInfoFunc func(host uint32, flavor int, outProcessorCount *uint32, outProcessorInfo uintptr,
outProcessorInfoCnt *uint32) int
HostStatisticsFunc func(host uint32, flavor int, hostInfoOut uintptr, hostInfoOutCnt *uint32) int
MachHostSelfFunc func() uint32
MachTaskSelfFunc func() uint32
VMDeallocateFunc func(targetTask uint32, vmAddress, vmSize uintptr) int
)
const (
HostProcessorInfoSym = "host_processor_info"
HostStatisticsSym = "host_statistics"
MachHostSelfSym = "mach_host_self"
MachTaskSelfSym = "mach_task_self"
VMDeallocateSym = "vm_deallocate"
)
const (
HOST_VM_INFO = 2
HOST_CPU_LOAD_INFO = 3
HOST_VM_INFO_COUNT = 0xf
)
// 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
}

@ -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.Kernel)
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
}

@ -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…
Cancel
Save