247 lines
8.3 KiB
Go
247 lines
8.3 KiB
Go
//go:build darwin
|
|
|
|
package gpu
|
|
|
|
import (
|
|
"fmt"
|
|
"unsafe"
|
|
|
|
"github.com/ebitengine/purego"
|
|
|
|
"github.com/nezhahq/agent/pkg/util"
|
|
)
|
|
|
|
type (
|
|
CFStringEncoding = uint32
|
|
CFIndex = int32
|
|
CFTypeID = int32
|
|
CFNumberType = CFIndex
|
|
CFTypeRef = unsafe.Pointer
|
|
CFStringRef = unsafe.Pointer
|
|
CFDictionaryRef = unsafe.Pointer
|
|
|
|
machPort = uint32
|
|
ioIterator = uint32
|
|
ioObject = uint32
|
|
ioRegistryEntry = uint32
|
|
ioService = uint32
|
|
IOOptionBits = uint32
|
|
)
|
|
|
|
type (
|
|
CFStringCreateWithCStringFunc = func(alloc uintptr, cStr string, encoding CFStringEncoding) CFStringRef
|
|
CFGetTypeIDFunc = func(cf uintptr) CFTypeID
|
|
CFStringGetTypeIDFunc = func() CFTypeID
|
|
CFStringGetLengthFunc = func(theString uintptr) int32
|
|
CFStringGetCStringFunc = func(cfStr uintptr, buffer *byte, size CFIndex, encoding CFStringEncoding) bool
|
|
CFDictionaryGetTypeIDFunc = func() CFTypeID
|
|
CFDictionaryGetValueFunc = func(dict, key uintptr) unsafe.Pointer
|
|
CFDataGetTypeIDFunc = func() CFTypeID
|
|
CFDataGetBytePtrFunc = func(theData uintptr) unsafe.Pointer
|
|
CFDataGetLengthFunc = func(theData uintptr) CFIndex
|
|
CFNumberGetValueFunc = func(number uintptr, theType CFNumberType, valuePtr uintptr) bool
|
|
CFReleaseFunc = func(cf uintptr)
|
|
|
|
IOServiceGetMatchingServicesFunc = func(mainPort machPort, matching uintptr, existing *ioIterator) ioService
|
|
IOIteratorNextFunc = func(iterator ioIterator) ioObject
|
|
IOServiceMatchingFunc = func(name string) CFDictionaryRef
|
|
IORegistryEntrySearchCFPropertyFunc = func(entry ioRegistryEntry, plane string, key, allocator uintptr, options IOOptionBits) CFTypeRef
|
|
IOObjectReleaseFunc = func(object ioObject) int
|
|
)
|
|
|
|
const (
|
|
KERN_SUCCESS = 0
|
|
MACH_PORT_NULL = 0
|
|
IOSERVICE_GPU = "IOAccelerator"
|
|
IOSERVICE_PCI = "IOPCIDevice"
|
|
|
|
kIOServicePlane = "IOService"
|
|
kIORegistryIterateRecursively = 1
|
|
kCFStringEncodingUTF8 = 0x08000100
|
|
kCFNumberIntType = 9
|
|
)
|
|
|
|
var (
|
|
kCFAllocatorDefault uintptr = 0
|
|
kIOMainPortDefault machPort = 0
|
|
)
|
|
|
|
var (
|
|
coreFoundation, _ = purego.Dlopen("/System/Library/Frameworks/CoreFoundation.framework/CoreFoundation", purego.RTLD_LAZY|purego.RTLD_GLOBAL)
|
|
ioKit, _ = purego.Dlopen("/System/Library/Frameworks/IOKit.framework/IOKit", purego.RTLD_LAZY|purego.RTLD_GLOBAL)
|
|
)
|
|
|
|
var (
|
|
CFStringCreateWithCString CFStringCreateWithCStringFunc
|
|
CFGetTypeID CFGetTypeIDFunc
|
|
CFStringGetTypeID CFStringGetTypeIDFunc
|
|
CFStringGetLength CFStringGetLengthFunc
|
|
CFStringGetCString CFStringGetCStringFunc
|
|
CFDictionaryGetTypeID CFDictionaryGetTypeIDFunc
|
|
CFDictionaryGetValue CFDictionaryGetValueFunc
|
|
CFDataGetTypeID CFDataGetTypeIDFunc
|
|
CFDataGetBytePtr CFDataGetBytePtrFunc
|
|
CFDataGetLength CFDataGetLengthFunc
|
|
CFNumberGetValue CFNumberGetValueFunc
|
|
CFRelease CFReleaseFunc
|
|
|
|
IOServiceGetMatchingServices IOServiceGetMatchingServicesFunc
|
|
IOIteratorNext IOIteratorNextFunc
|
|
IOServiceMatching IOServiceMatchingFunc
|
|
IORegistryEntrySearchCFProperty IORegistryEntrySearchCFPropertyFunc
|
|
IOObjectRelease IOObjectReleaseFunc
|
|
)
|
|
|
|
var validVendors = []string{
|
|
"AMD", "Intel", "NVIDIA", "Apple",
|
|
}
|
|
|
|
func init() {
|
|
purego.RegisterLibFunc(&CFStringCreateWithCString, coreFoundation, "CFStringCreateWithCString")
|
|
purego.RegisterLibFunc(&CFGetTypeID, coreFoundation, "CFGetTypeID")
|
|
purego.RegisterLibFunc(&CFStringGetTypeID, coreFoundation, "CFStringGetTypeID")
|
|
purego.RegisterLibFunc(&CFStringGetLength, coreFoundation, "CFStringGetLength")
|
|
purego.RegisterLibFunc(&CFStringGetCString, coreFoundation, "CFStringGetCString")
|
|
purego.RegisterLibFunc(&CFDictionaryGetTypeID, coreFoundation, "CFDictionaryGetTypeID")
|
|
purego.RegisterLibFunc(&CFDictionaryGetValue, coreFoundation, "CFDictionaryGetValue")
|
|
purego.RegisterLibFunc(&CFDataGetTypeID, coreFoundation, "CFDataGetTypeID")
|
|
purego.RegisterLibFunc(&CFDataGetBytePtr, coreFoundation, "CFDataGetBytePtr")
|
|
purego.RegisterLibFunc(&CFDataGetLength, coreFoundation, "CFDataGetLength")
|
|
purego.RegisterLibFunc(&CFNumberGetValue, coreFoundation, "CFNumberGetValue")
|
|
purego.RegisterLibFunc(&CFRelease, coreFoundation, "CFRelease")
|
|
|
|
purego.RegisterLibFunc(&IOServiceGetMatchingServices, ioKit, "IOServiceGetMatchingServices")
|
|
purego.RegisterLibFunc(&IOIteratorNext, ioKit, "IOIteratorNext")
|
|
purego.RegisterLibFunc(&IOServiceMatching, ioKit, "IOServiceMatching")
|
|
purego.RegisterLibFunc(&IORegistryEntrySearchCFProperty, ioKit, "IORegistryEntrySearchCFProperty")
|
|
purego.RegisterLibFunc(&IOObjectRelease, ioKit, "IOObjectRelease")
|
|
}
|
|
|
|
func GetGPUModel() ([]string, error) {
|
|
models, err := findDevices("model")
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return util.RemoveDuplicate(models), nil
|
|
}
|
|
|
|
func GetGPUStat() ([]float64, error) {
|
|
usage, err := findUtilization("PerformanceStatistics", "Device Utilization %")
|
|
return []float64{float64(usage)}, err
|
|
}
|
|
|
|
func findDevices(key string) ([]string, error) {
|
|
var iterator ioIterator
|
|
var results []string
|
|
done := false
|
|
|
|
iv := IOServiceGetMatchingServices(kIOMainPortDefault, uintptr(IOServiceMatching(IOSERVICE_GPU)), &iterator)
|
|
if iv != KERN_SUCCESS {
|
|
return nil, fmt.Errorf("error retrieving GPU entry")
|
|
}
|
|
|
|
var service ioObject
|
|
index := 0
|
|
|
|
for {
|
|
service = IOIteratorNext(iterator)
|
|
if service == MACH_PORT_NULL {
|
|
break
|
|
}
|
|
|
|
cfStr := CFStringCreateWithCString(kCFAllocatorDefault, key, kCFStringEncodingUTF8)
|
|
result, _, _ := findProperties(service, uintptr(cfStr), 0)
|
|
IOObjectRelease(service)
|
|
|
|
if util.ContainsStr(validVendors, result) {
|
|
results = append(results, result)
|
|
index++
|
|
} else if key == "model" && !done {
|
|
IOObjectRelease(iterator)
|
|
iv = IOServiceGetMatchingServices(kIOMainPortDefault, uintptr(IOServiceMatching(IOSERVICE_PCI)), &iterator)
|
|
if iv != KERN_SUCCESS {
|
|
return nil, fmt.Errorf("error retrieving GPU entry")
|
|
}
|
|
done = true
|
|
}
|
|
}
|
|
|
|
IOObjectRelease(iterator)
|
|
return results, nil
|
|
}
|
|
|
|
func findUtilization(key, dictKey string) (int, error) {
|
|
var iterator ioIterator
|
|
var err error
|
|
result := 0
|
|
|
|
iv := IOServiceGetMatchingServices(kIOMainPortDefault, uintptr(IOServiceMatching(IOSERVICE_GPU)), &iterator)
|
|
if iv != KERN_SUCCESS {
|
|
return 0, fmt.Errorf("error retrieving GPU entry")
|
|
}
|
|
|
|
// Only retrieving the utilization of a single GPU here
|
|
var service ioObject
|
|
for {
|
|
service = IOIteratorNext(iterator)
|
|
if service == MACH_PORT_NULL {
|
|
break
|
|
}
|
|
|
|
cfStr := CFStringCreateWithCString(kCFAllocatorDefault, key, CFStringEncoding(kCFStringEncodingUTF8))
|
|
cfDictStr := CFStringCreateWithCString(kCFAllocatorDefault, dictKey, CFStringEncoding(kCFStringEncodingUTF8))
|
|
|
|
_, result, err = findProperties(service, uintptr(cfStr), uintptr(cfDictStr))
|
|
|
|
CFRelease(uintptr(cfStr))
|
|
CFRelease(uintptr(cfDictStr))
|
|
|
|
if err != nil {
|
|
IOObjectRelease(service)
|
|
continue
|
|
} else if result != 0 {
|
|
break
|
|
}
|
|
}
|
|
|
|
IOObjectRelease(service)
|
|
IOObjectRelease(iterator)
|
|
|
|
return result, err
|
|
}
|
|
|
|
func findProperties(service ioRegistryEntry, key, dictKey uintptr) (string, int, error) {
|
|
properties := IORegistryEntrySearchCFProperty(service, kIOServicePlane, key, kCFAllocatorDefault, kIORegistryIterateRecursively)
|
|
ptrValue := uintptr(properties)
|
|
if properties != nil {
|
|
switch CFGetTypeID(ptrValue) {
|
|
// model
|
|
case CFStringGetTypeID():
|
|
length := CFStringGetLength(ptrValue) + 1 // null terminator
|
|
buf := make([]byte, length-1)
|
|
CFStringGetCString(ptrValue, &buf[0], length, uint32(kCFStringEncodingUTF8))
|
|
CFRelease(ptrValue)
|
|
return string(buf), 0, nil
|
|
case CFDataGetTypeID():
|
|
length := CFDataGetLength(ptrValue)
|
|
bin := unsafe.String((*byte)(CFDataGetBytePtr(ptrValue)), length)
|
|
CFRelease(ptrValue)
|
|
return bin, 0, nil
|
|
// PerformanceStatistics
|
|
case CFDictionaryGetTypeID():
|
|
cfValue := CFDictionaryGetValue(ptrValue, dictKey)
|
|
if cfValue != nil {
|
|
var value int
|
|
if CFNumberGetValue(uintptr(cfValue), kCFNumberIntType, uintptr(unsafe.Pointer(&value))) {
|
|
return "", value, nil
|
|
} else {
|
|
return "", 0, fmt.Errorf("failed to exec CFNumberGetValue")
|
|
}
|
|
} else {
|
|
return "", 0, fmt.Errorf("failed to exec CFDictionaryGetValue")
|
|
}
|
|
}
|
|
}
|
|
return "", 0, fmt.Errorf("failed to exec IORegistryEntrySearchCFProperty")
|
|
}
|