From 65bf012579ebd4e735b1faa298bffca26bf655c1 Mon Sep 17 00:00:00 2001 From: UUBulb <35923940+uubulb@users.noreply.github.com> Date: Fri, 19 Jul 2024 19:00:59 +0800 Subject: [PATCH] feat: add GPU information retrieval for darwin using cgo (#40) * feat: add GPU information retrieval for darwin using cgo * fix kIOMasterPortDefault thing * return 0 --- pkg/gpu/gpu_darwin.go | 2 +- pkg/gpu/gpu_darwin_cgo.go | 65 ++++++++++++++ pkg/gpu/stat/gpu_darwin.c | 144 ++++++++++++++++++++++++++++++++ pkg/gpu/stat/gpu_darwin.h | 15 ++++ pkg/gpu/stat/stat_darwin.go | 2 +- pkg/gpu/stat/stat_darwin_cgo.go | 25 ++++++ 6 files changed, 251 insertions(+), 2 deletions(-) create mode 100644 pkg/gpu/gpu_darwin_cgo.go create mode 100644 pkg/gpu/stat/gpu_darwin.c create mode 100644 pkg/gpu/stat/gpu_darwin.h create mode 100644 pkg/gpu/stat/stat_darwin_cgo.go diff --git a/pkg/gpu/gpu_darwin.go b/pkg/gpu/gpu_darwin.go index bebfa2f..2664f8d 100644 --- a/pkg/gpu/gpu_darwin.go +++ b/pkg/gpu/gpu_darwin.go @@ -1,4 +1,4 @@ -//go:build darwin +//go:build darwin && !cgo package gpu diff --git a/pkg/gpu/gpu_darwin_cgo.go b/pkg/gpu/gpu_darwin_cgo.go new file mode 100644 index 0000000..da134e8 --- /dev/null +++ b/pkg/gpu/gpu_darwin_cgo.go @@ -0,0 +1,65 @@ +//go:build darwin && cgo + +package gpu + +// #cgo LDFLAGS: -framework IOKit -framework CoreFoundation +// #include "stat/gpu_darwin.h" +import "C" +import ( + "errors" + "strings" + "unsafe" +) + +func GoStrings(argc C.int, argv **C.char) []string { + length := int(argc) + tmpslice := unsafe.Slice(argv, length) + gostrings := make([]string, length) + for i, s := range tmpslice { + gostrings[i] = C.GoString(s) + } + return gostrings +} + +func extractGPUInfo(key *C.char) ([]string, error) { + devices := C.find_devices(key) + if devices != nil { + defer C.free(unsafe.Pointer(devices)) + length := 0 + for { + device := *(**C.char)(unsafe.Pointer(uintptr(unsafe.Pointer(devices)) + uintptr(length)*unsafe.Sizeof(*devices))) + if device == nil { + break + } + length++ + } + gpu := GoStrings(C.int(length), devices) + return gpu, nil + } + return nil, errors.New("cannot find key") +} + +func GetGPUModel() ([]string, error) { + vendorNames := []string{ + "AMD", "Intel", "Nvidia", "Apple", + } + + key := C.CString("model") + defer C.free(unsafe.Pointer(key)) + + gi, err := extractGPUInfo(key) + if err != nil { + return nil, err + } + + var gpuModel []string + for _, model := range gi { + for _, vendor := range vendorNames { + if strings.Contains(model, vendor) { + gpuModel = append(gpuModel, model) + break + } + } + } + return gpuModel, nil +} diff --git a/pkg/gpu/stat/gpu_darwin.c b/pkg/gpu/stat/gpu_darwin.c new file mode 100644 index 0000000..18eba50 --- /dev/null +++ b/pkg/gpu/stat/gpu_darwin.c @@ -0,0 +1,144 @@ +#include "gpu_darwin.h" +#include +#include +#include +#include + +#define IOSERVICE_GPU "IOAccelerator" +#define IOSERVICE_PCI "IOPCIDevice" + +void *find_properties(io_registry_entry_t service, int depth, CFStringRef key, + CFStringRef dict_key) { + CFTypeRef properties = IORegistryEntrySearchCFProperty( + service, kIOServicePlane, key, kCFAllocatorDefault, + kIORegistryIterateRecursively); + + if (properties) { + if (CFGetTypeID(properties) == CFStringGetTypeID()) { + CFStringRef cfStr = (CFStringRef)properties; + char buffer[1024]; + CFStringGetCString(cfStr, buffer, sizeof(buffer), kCFStringEncodingUTF8); + CFRelease(properties); + return strdup(buffer); + } else if (CFGetTypeID(properties) == CFDictionaryGetTypeID()) { + CFDictionaryRef cfDict = (CFDictionaryRef)properties; + CFNumberRef cfValue = (CFNumberRef)CFDictionaryGetValue(cfDict, dict_key); + if (cfValue == NULL) { + return NULL; + } + int value; + if (!CFNumberGetValue(cfValue, kCFNumberIntType, &value)) { + return NULL; + } + return (void *)(intptr_t)value; + } + } + + return NULL; +} + +char **find_devices(char *key) { + io_service_t io_reg_err; + io_iterator_t iterator; + int capacity = 10; + + char **cards = malloc(capacity * sizeof(char *)); + if (!cards) { + fprintf(stderr, "Memory allocation failed\n"); + return NULL; + } + + io_reg_err = IOServiceGetMatchingServices( + kIOMainPortDefault, IOServiceMatching(IOSERVICE_GPU), &iterator); + if (io_reg_err != KERN_SUCCESS) { + printf("Error getting GPU entry\n"); + return NULL; + } + + io_object_t service; + int index = 0; + while ((service = IOIteratorNext(iterator)) != MACH_PORT_NULL) { + CFStringRef cfStr = CFStringCreateWithCString(kCFAllocatorDefault, key, + kCFStringEncodingUTF8); + char *result = find_properties(service, 0, cfStr, CFSTR("")); + CFRelease(cfStr); + IOObjectRelease(service); + + if (result != NULL) { + if (index >= capacity) { + capacity += 1; + char **new_cards = (char **)realloc(cards, capacity * sizeof(char *)); + if (!new_cards) { + fprintf(stderr, "Memory reallocation failed\n"); + for (int i = 0; i < index; i++) { + free(cards[i]); + } + free(cards); + free(result); + return NULL; + } + cards = new_cards; + } + cards[index] = result; + index++; + } + + if (result == NULL && strcmp(key, "model") == 0) { + IOObjectRelease(iterator); + + io_reg_err = IOServiceGetMatchingServices( + kIOMainPortDefault, IOServiceMatching(IOSERVICE_PCI), &iterator); + if (io_reg_err != KERN_SUCCESS) { + printf("Error getting PCI entry\n"); + return NULL; + } + } + } + IOObjectRelease(iterator); + + char **result_cards = (char **)realloc(cards, sizeof(char *) * (index + 1)); + if (!result_cards) { + fprintf(stderr, "Memory reallocation failed\n"); + for (int i = 0; i < index; i++) { + free(cards[i]); + } + free(cards); + return NULL; + } + result_cards[index] = NULL; + + return result_cards; +} + +int find_utilization(char *key, char *dict_key) { + void *result_ptr; + io_service_t io_reg_err; + io_iterator_t iterator; + + io_reg_err = IOServiceGetMatchingServices( + kIOMainPortDefault, IOServiceMatching(IOSERVICE_GPU), &iterator); + if (io_reg_err != KERN_SUCCESS) { + printf("Error getting GPU entry\n"); + return 0; + } + + io_object_t service = IOIteratorNext(iterator); + if (service != MACH_PORT_NULL) { + CFStringRef cfStr = CFStringCreateWithCString(kCFAllocatorDefault, key, + kCFStringEncodingUTF8); + CFStringRef cfDictStr = CFStringCreateWithCString( + kCFAllocatorDefault, dict_key, kCFStringEncodingUTF8); + result_ptr = find_properties(service, 0, cfStr, cfDictStr); + CFRelease(cfStr); + CFRelease(cfDictStr); + } + + IOObjectRelease(service); + IOObjectRelease(iterator); + + if (result_ptr == NULL) { + return 0; + } + + return (int)(intptr_t)result_ptr; +} \ No newline at end of file diff --git a/pkg/gpu/stat/gpu_darwin.h b/pkg/gpu/stat/gpu_darwin.h new file mode 100644 index 0000000..8dc6d0a --- /dev/null +++ b/pkg/gpu/stat/gpu_darwin.h @@ -0,0 +1,15 @@ +#ifndef __SMC_H__ +#define __SMC_H__ 1 + +#include +#include + +#if (defined __MAC_OS_X_VERSION_MIN_REQUIRED) && (__MAC_OS_X_VERSION_MIN_REQUIRED < 120000) +#define kIOMainPortDefault kIOMasterPortDefault +#endif + +void *find_properties(io_registry_entry_t, int, CFStringRef, CFStringRef); +char **find_devices(char *); +int find_utilization(char *, char *); + +#endif \ No newline at end of file diff --git a/pkg/gpu/stat/stat_darwin.go b/pkg/gpu/stat/stat_darwin.go index 5b50be9..a841f81 100644 --- a/pkg/gpu/stat/stat_darwin.go +++ b/pkg/gpu/stat/stat_darwin.go @@ -1,4 +1,4 @@ -//go:build darwin +//go:build darwin && !cgo package stat diff --git a/pkg/gpu/stat/stat_darwin_cgo.go b/pkg/gpu/stat/stat_darwin_cgo.go new file mode 100644 index 0000000..19f00a1 --- /dev/null +++ b/pkg/gpu/stat/stat_darwin_cgo.go @@ -0,0 +1,25 @@ +//go:build darwin && cgo + +package stat + +// #cgo LDFLAGS: -framework IOKit -framework CoreFoundation +// #include "gpu_darwin.h" +import "C" +import ( + "unsafe" +) + +func extractGPUStat(key *C.char, dict_key *C.char) (int, error) { + utilization := C.find_utilization(key, dict_key) + return int(utilization), nil +} + +func GetGPUStat() (float64, error) { + key := C.CString("PerformanceStatistics") + dict_key := C.CString("Device Utilization %") + defer C.free(unsafe.Pointer(key)) + defer C.free(unsafe.Pointer(dict_key)) + + gs, _ := extractGPUStat(key, dict_key) + return float64(gs), nil +}