feat: add GPU information retrieval for darwin using cgo (#40)

* feat: add GPU information retrieval for darwin using cgo

* fix kIOMasterPortDefault thing

* return 0
This commit is contained in:
UUBulb 2024-07-19 19:00:59 +08:00 committed by GitHub
parent f220134e5b
commit 65bf012579
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 251 additions and 2 deletions

View File

@ -1,4 +1,4 @@
//go:build darwin
//go:build darwin && !cgo
package gpu

65
pkg/gpu/gpu_darwin_cgo.go Normal file
View File

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

144
pkg/gpu/stat/gpu_darwin.c Normal file
View File

@ -0,0 +1,144 @@
#include "gpu_darwin.h"
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#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;
}

15
pkg/gpu/stat/gpu_darwin.h Normal file
View File

@ -0,0 +1,15 @@
#ifndef __SMC_H__
#define __SMC_H__ 1
#include <IOKit/IOKitLib.h>
#include <CoreFoundation/CoreFoundation.h>
#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

View File

@ -1,4 +1,4 @@
//go:build darwin
//go:build darwin && !cgo
package stat

View File

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