Merge pull request #1541 from gneitzke/feature/apple-silicon-gpu-support

feat: Add Apple Silicon GPU support via IOReport API
This commit is contained in:
Jakob P. Liljenberg
2026-02-15 20:44:18 +01:00
committed by GitHub
7 changed files with 568 additions and 4 deletions

View File

@@ -198,11 +198,22 @@ if(BTOP_STATIC)
target_link_options(libbtop PUBLIC -static LINKER:--fatal-warnings)
endif()
# Enable GPU support on Apple Silicon
if(APPLE AND BTOP_GPU)
target_compile_definitions(libbtop PUBLIC GPU_SUPPORT)
endif()
# Other platform dependent flags
if(APPLE)
target_link_libraries(libbtop
$<LINK_LIBRARY:FRAMEWORK,CoreFoundation> $<LINK_LIBRARY:FRAMEWORK,IOKit>
)
if(BTOP_GPU)
find_library(IOREPORT_LIB IOReport)
if(IOREPORT_LIB)
target_link_libraries(libbtop ${IOREPORT_LIB})
endif()
endif()
elseif(CMAKE_SYSTEM_NAME STREQUAL "FreeBSD" OR CMAKE_SYSTEM_NAME STREQUAL "MidnightBSD")
# Avoid version mismatch for libstdc++ when a specific version of GCC is installed and not the
# default one since all use the default ones RPATH

View File

@@ -43,6 +43,9 @@ ifeq ($(PLATFORM_LC)$(ARCH),linuxx86_64)
INTEL_GPU_SUPPORT := true
endif
endif
ifeq ($(PLATFORM_LC),macos)
GPU_SUPPORT := true
endif
ifneq ($(GPU_SUPPORT),true)
GPU_SUPPORT := false
endif
@@ -115,7 +118,7 @@ else ifeq ($(PLATFORM_LC),$(filter $(PLATFORM_LC),freebsd midnightbsd))
else ifeq ($(PLATFORM_LC),macos)
PLATFORM_DIR := osx
THREADS := $(shell sysctl -n hw.ncpu || echo 1)
override ADDFLAGS += -framework IOKit -framework CoreFoundation -Wno-format-truncation
override ADDFLAGS += -framework IOKit -framework CoreFoundation -lIOReport -Wno-format-truncation
SU_GROUP := wheel
else ifeq ($(PLATFORM_LC),openbsd)
PLATFORM_DIR := openbsd

View File

@@ -231,6 +231,9 @@ void clean_quit(int sig) {
#ifdef GPU_SUPPORT
Gpu::Nvml::shutdown();
Gpu::Rsmi::shutdown();
#ifdef __APPLE__
Gpu::AppleSilicon::shutdown();
#endif
#endif

View File

@@ -239,7 +239,7 @@ namespace Config {
{"rsmi_measure_pcie_speeds",
"#* Measure PCIe throughput on AMD cards, may impact performance on certain cards."},
{"gpu_mirror_graph", "#* Horizontally mirror the GPU graph."},
{"shown_gpus", "#* Set which GPU vendors to show. Available values are \"nvidia amd intel\""},
{"shown_gpus", "#* Set which GPU vendors to show. Available values are \"nvidia amd intel apple\""},
{"custom_gpu_name0", "#* Custom gpu0 model name, empty string to disable."},
{"custom_gpu_name1", "#* Custom gpu1 model name, empty string to disable."},
{"custom_gpu_name2", "#* Custom gpu2 model name, empty string to disable."},
@@ -288,7 +288,7 @@ namespace Config {
{"custom_gpu_name4", ""},
{"custom_gpu_name5", ""},
{"show_gpu_info", "Auto"},
{"shown_gpus", "nvidia amd intel"}
{"shown_gpus", "nvidia amd intel apple"}
#endif
};
std::unordered_map<std::string_view, string> stringsTmp;

View File

@@ -598,7 +598,8 @@ namespace Menu {
"Manually set which gpu vendors to show.",
"",
"Available values are",
"\"nvidia\", \"amd\", and \"intel\".",
"\"nvidia\", \"amd\", \"intel\",",
"and \"apple\".",
"Separate values with whitespace.",
"",
"A restart is required to apply changes."},

View File

@@ -183,6 +183,11 @@ namespace Gpu {
namespace Rsmi {
extern bool shutdown();
}
#ifdef __APPLE__
namespace AppleSilicon {
extern bool shutdown();
}
#endif
//* Collect gpu stats and temperatures
auto collect(bool no_update = false) -> vector<gpu_info>&;

View File

@@ -68,6 +68,49 @@ tab-size = 4
#endif
#include "smc.hpp"
#if defined(GPU_SUPPORT)
#include <dlfcn.h>
#include <mach/mach_time.h>
//? IOReport C function declarations for Apple Silicon GPU metrics
extern "C" {
typedef struct IOReportSubscription* IOReportSubscriptionRef;
CFDictionaryRef IOReportCopyChannelsInGroup(CFStringRef group, CFStringRef subgroup,
uint64_t a, uint64_t b, uint64_t c);
void IOReportMergeChannels(CFDictionaryRef a, CFDictionaryRef b, CFTypeRef cfnull);
IOReportSubscriptionRef IOReportCreateSubscription(void* a, CFMutableDictionaryRef b,
CFMutableDictionaryRef* c, uint64_t d, CFTypeRef cfnull);
CFDictionaryRef IOReportCreateSamples(IOReportSubscriptionRef sub,
CFMutableDictionaryRef chan, CFTypeRef cfnull);
CFDictionaryRef IOReportCreateSamplesDelta(CFDictionaryRef a, CFDictionaryRef b, CFTypeRef cfnull);
CFStringRef IOReportChannelGetGroup(CFDictionaryRef item);
CFStringRef IOReportChannelGetSubGroup(CFDictionaryRef item);
CFStringRef IOReportChannelGetChannelName(CFDictionaryRef item);
int64_t IOReportSimpleGetIntegerValue(CFDictionaryRef item, int32_t idx);
CFStringRef IOReportChannelGetUnitLabel(CFDictionaryRef item);
int32_t IOReportStateGetCount(CFDictionaryRef item);
CFStringRef IOReportStateGetNameForIndex(CFDictionaryRef item, int32_t idx);
int64_t IOReportStateGetResidency(CFDictionaryRef item, int32_t idx);
//? IOHIDEvent declarations for GPU temperature
typedef struct __IOHIDEvent* IOHIDEventRef;
typedef struct __IOHIDServiceClient* IOHIDServiceClientRef;
typedef struct __IOHIDEventSystemClient* IOHIDEventSystemClientRef;
#ifdef __LP64__
typedef double IOHIDFloat;
#else
typedef float IOHIDFloat;
#endif
IOHIDEventSystemClientRef IOHIDEventSystemClientCreate(CFAllocatorRef allocator);
int IOHIDEventSystemClientSetMatching(IOHIDEventSystemClientRef client, CFDictionaryRef match);
CFArrayRef IOHIDEventSystemClientCopyServices(IOHIDEventSystemClientRef client);
IOHIDEventRef IOHIDServiceClientCopyEvent(IOHIDServiceClientRef sc, int64_t type, int32_t a, int64_t b);
CFStringRef IOHIDServiceClientCopyProperty(IOHIDServiceClientRef service, CFStringRef property);
IOHIDFloat IOHIDEventGetFloatValue(IOHIDEventRef event, int32_t field);
}
#endif // GPU_SUPPORT
#if __MAC_OS_X_VERSION_MIN_REQUIRED < 120000
#define kIOMainPortDefault kIOMasterPortDefault
#endif
@@ -78,6 +121,43 @@ namespace fs = std::filesystem;
namespace rng = std::ranges;
using namespace Tools;
//? RAII wrapper for CoreFoundation types — releases via CFRelease() on destruction
template <typename T>
struct CFRef {
T ref;
CFRef() : ref(nullptr) {}
CFRef(T ref) : ref(ref) {}
~CFRef() { if (ref) CFRelease((CFTypeRef)ref); }
CFRef(const CFRef&) = delete;
CFRef& operator=(const CFRef&) = delete;
CFRef(CFRef&& other) noexcept : ref(other.ref) { other.ref = nullptr; }
CFRef& operator=(CFRef&& other) noexcept {
if (this != &other) { reset(); ref = other.ref; other.ref = nullptr; }
return *this;
}
operator T() const { return ref; }
T get() const { return ref; }
T* ptr() { return &ref; }
void reset(T new_ref = nullptr) {
if (ref) CFRelease((CFTypeRef)ref);
ref = new_ref;
}
T release() { T r = ref; ref = nullptr; return r; }
};
//? RAII wrapper for IOKit object types — releases via IOObjectRelease() on destruction
struct IORef {
io_object_t ref;
IORef() : ref(0) {}
IORef(io_object_t ref) : ref(ref) {}
~IORef() { if (ref) IOObjectRelease(ref); }
IORef(const IORef&) = delete;
IORef& operator=(const IORef&) = delete;
operator io_object_t() const { return ref; }
io_object_t get() const { return ref; }
io_object_t* ptr() { return &ref; }
};
//? --------------------------------------------------- FUNCTIONS -----------------------------------------------------
namespace Cpu {
@@ -115,6 +195,442 @@ namespace Mem {
double old_uptime;
}
#if defined(GPU_SUPPORT)
namespace Gpu {
vector<gpu_info> gpus;
//? Stub shutdown for backends not available on macOS
namespace Nvml { bool shutdown() { return false; } }
namespace Rsmi { bool shutdown() { return false; } }
//? Apple Silicon GPU data collection via IOReport
namespace AppleSilicon {
bool initialized = false;
unsigned int device_count = 0;
//? Forward declaration
template <bool is_init>
bool collect(gpu_info* gpus_slice);
//? IOReport subscription state
IOReportSubscriptionRef ior_sub = nullptr;
CFMutableDictionaryRef ior_chan = nullptr;
CFDictionaryRef prev_sample = nullptr;
uint64_t prev_sample_time = 0;
//? GPU frequency table from DVFS
vector<uint32_t> gpu_freqs;
static string cfstring_to_string(CFStringRef cfstr) {
if (not cfstr) return "";
char buf[256];
if (CFStringGetCString(cfstr, buf, sizeof(buf), kCFStringEncodingUTF8))
return string(buf);
return "";
}
static string get_chip_name() {
char buf[256];
size_t size = sizeof(buf);
if (sysctlbyname("machdep.cpu.brand_string", buf, &size, nullptr, 0) == 0)
return string(buf);
return "Apple Silicon GPU";
}
static uint64_t get_mach_time_ms() {
static mach_timebase_info_data_t timebase = {0, 0};
if (timebase.denom == 0) mach_timebase_info(&timebase);
return (mach_absolute_time() * timebase.numer / timebase.denom) / 1000000;
}
//? Read GPU DVFS frequency table from IORegistry pmgr node
static void get_gpu_freqs_from_pmgr() {
io_iterator_t iter_raw;
//? matchDict ownership is consumed by IOServiceGetMatchingServices
CFMutableDictionaryRef matchDict = IOServiceMatching("AppleARMIODevice");
if (IOServiceGetMatchingServices(kIOMainPortDefault, matchDict, &iter_raw) != kIOReturnSuccess)
return;
IORef iter(iter_raw);
io_object_t entry_raw;
while ((entry_raw = IOIteratorNext(iter)) != 0) {
IORef entry(entry_raw);
char name[128];
if (IORegistryEntryGetName(entry, name) == kIOReturnSuccess and string(name) == "pmgr") {
CFMutableDictionaryRef props_raw = nullptr;
if (IORegistryEntryCreateCFProperties(entry, &props_raw, kCFAllocatorDefault, 0) == kIOReturnSuccess and props_raw) {
CFRef<CFMutableDictionaryRef> props(props_raw);
CFDataRef dvfs_data = (CFDataRef)CFDictionaryGetValue(props, CFSTR("voltage-states9"));
if (dvfs_data) {
auto len = CFDataGetLength(dvfs_data);
auto ptr = CFDataGetBytePtr(dvfs_data);
//? Pairs of (freq, voltage), 4 bytes each
for (CFIndex i = 0; i + 7 < len; i += 8) {
uint32_t freq = 0;
memcpy(&freq, ptr + i, 4);
if (freq > 0) gpu_freqs.push_back(freq / (1000 * 1000)); // Hz -> MHz
}
}
}
}
}
}
bool init() {
if (initialized) return false;
//? Get GPU frequency table
get_gpu_freqs_from_pmgr();
//? Set up IOReport channels for GPU Stats and Energy Model
CFRef<CFStringRef> gpu_stats_group(CFStringCreateWithCString(kCFAllocatorDefault, "GPU Stats", kCFStringEncodingUTF8));
CFRef<CFStringRef> gpu_perf_subgroup(CFStringCreateWithCString(kCFAllocatorDefault, "GPU Performance States", kCFStringEncodingUTF8));
CFRef<CFStringRef> energy_group(CFStringCreateWithCString(kCFAllocatorDefault, "Energy Model", kCFStringEncodingUTF8));
CFRef<CFDictionaryRef> gpu_chan(IOReportCopyChannelsInGroup(gpu_stats_group, gpu_perf_subgroup, 0, 0, 0));
CFRef<CFDictionaryRef> energy_chan(IOReportCopyChannelsInGroup(energy_group, nullptr, 0, 0, 0));
if (not gpu_chan.get() and not energy_chan.get()) {
Logger::info("Apple Silicon GPU: No IOReport channels found, GPU monitoring unavailable");
return false;
}
//? Merge channels into a single subscription
if (gpu_chan.get() and energy_chan.get()) {
IOReportMergeChannels(gpu_chan, energy_chan, nullptr);
}
CFDictionaryRef base_chan = gpu_chan.get() ? gpu_chan.get() : energy_chan.get();
auto size = CFDictionaryGetCount(base_chan);
ior_chan = CFDictionaryCreateMutableCopy(kCFAllocatorDefault, size, base_chan);
//? Create IOReport subscription
CFMutableDictionaryRef sub_dict = nullptr;
ior_sub = IOReportCreateSubscription(nullptr, ior_chan, &sub_dict, 0, nullptr);
if (not ior_sub) {
Logger::warning("Apple Silicon GPU: Failed to create IOReport subscription");
CFRelease(ior_chan);
ior_chan = nullptr;
return false;
}
device_count = 1; //? Apple Silicon has one integrated GPU
gpus.resize(gpus.size() + device_count);
gpu_names.resize(gpu_names.size() + device_count);
initialized = true;
//? Take initial sample for delta computation
prev_sample = IOReportCreateSamples(ior_sub, ior_chan, nullptr);
prev_sample_time = get_mach_time_ms();
//? Run init collect to populate names and supported functions
collect<1>(gpus.data());
return true;
}
bool shutdown() {
if (not initialized) return false;
if (prev_sample) { CFRelease(prev_sample); prev_sample = nullptr; }
if (ior_chan) { CFRelease(ior_chan); ior_chan = nullptr; }
if (ior_sub) { CFRelease((CFTypeRef)ior_sub); ior_sub = nullptr; }
initialized = false;
return true;
}
//? Read GPU temperature via IOHIDEventSystem thermal sensors
static long long get_gpu_temp_iohid() {
#if __MAC_OS_X_VERSION_MIN_REQUIRED > 101504
constexpr int kHIDPage_AppleVendor = 0xff00;
constexpr int kHIDUsage_TemperatureSensor = 5;
constexpr int64_t kIOHIDEventTypeTemperature = 15;
CFStringRef keys[2] = { CFSTR("PrimaryUsagePage"), CFSTR("PrimaryUsage") };
int page = kHIDPage_AppleVendor, usage = kHIDUsage_TemperatureSensor;
CFRef<CFNumberRef> num0(CFNumberCreate(nullptr, kCFNumberSInt32Type, &page));
CFRef<CFNumberRef> num1(CFNumberCreate(nullptr, kCFNumberSInt32Type, &usage));
const void* values[] = { num0.get(), num1.get() };
CFRef<CFDictionaryRef> match(CFDictionaryCreate(nullptr,
(const void**)keys, values, 2,
&kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks));
CFRef<IOHIDEventSystemClientRef> system(IOHIDEventSystemClientCreate(kCFAllocatorDefault));
if (not system.get()) return -1;
IOHIDEventSystemClientSetMatching(system, match);
CFRef<CFArrayRef> services(IOHIDEventSystemClientCopyServices(system));
if (not services.get()) return -1;
double gpu_temp_sum = 0;
int gpu_temp_count = 0;
long count = CFArrayGetCount(services);
for (long i = 0; i < count; i++) {
auto sc = (IOHIDServiceClientRef)CFArrayGetValueAtIndex(services, i);
if (not sc) continue;
CFRef<CFStringRef> name(IOHIDServiceClientCopyProperty(sc, CFSTR("Product")));
if (not name.get()) continue;
char buf[200];
CFStringGetCString(name, buf, 200, kCFStringEncodingASCII);
string n(buf);
//? "GPU MTR Temp Sensor" is the standard Apple Silicon GPU temp sensor name
if (n.find("GPU") != string::npos) {
CFRef<IOHIDEventRef> event(IOHIDServiceClientCopyEvent(sc, kIOHIDEventTypeTemperature, 0, 0));
if (event.get()) {
double temp = IOHIDEventGetFloatValue(event, kIOHIDEventTypeTemperature << 16);
if (temp > 0 and temp < 150) {
gpu_temp_sum += temp;
gpu_temp_count++;
}
}
}
}
if (gpu_temp_count > 0)
return static_cast<long long>(round(gpu_temp_sum / gpu_temp_count));
#endif
return -1;
}
template <bool is_init>
bool collect(gpu_info* gpus_slice) {
if (not initialized) return false;
if constexpr (is_init) {
//? Device name
string chip = get_chip_name();
gpu_names[0] = chip + " GPU";
//? Power max (typical Apple Silicon GPU TDP ~15-20W)
gpus_slice[0].pwr_max_usage = 20000; // 20W in mW
gpu_pwr_total_max += gpus_slice[0].pwr_max_usage;
//? Temperature max
gpus_slice[0].temp_max = 110;
//? Memory total (unified memory architecture — GPU shares system RAM)
int64_t memsize = 0;
size_t size = sizeof(memsize);
if (sysctlbyname("hw.memsize", &memsize, &size, nullptr, 0) == 0)
gpus_slice[0].mem_total = memsize;
//? Supported functions
gpus_slice[0].supported_functions = {
.gpu_utilization = true,
.mem_utilization = true,
.gpu_clock = not gpu_freqs.empty(),
.mem_clock = false,
.pwr_usage = true,
.pwr_state = false,
.temp_info = true,
.mem_total = true,
.mem_used = true,
.pcie_txrx = false,
.encoder_utilization = false,
.decoder_utilization = false
};
}
//? Take new IOReport sample and compute delta
CFDictionaryRef cur_sample = IOReportCreateSamples(ior_sub, ior_chan, nullptr);
if (not cur_sample) return false;
uint64_t cur_time = get_mach_time_ms();
uint64_t dt = cur_time - prev_sample_time;
if (dt == 0) dt = 1;
CFRef<CFDictionaryRef> delta;
if (prev_sample) {
delta.reset(IOReportCreateSamplesDelta(prev_sample, cur_sample, nullptr));
CFRelease(prev_sample);
}
prev_sample = cur_sample;
prev_sample_time = cur_time;
if (not delta.get()) return false;
//? Parse delta samples
CFArrayRef channels = (CFArrayRef)CFDictionaryGetValue(delta, CFSTR("IOReportChannels"));
if (not channels) return false;
long long gpu_utilization = 0;
bool got_gpu_util = false;
double gpu_power_watts = 0;
bool got_gpu_power = false;
long chan_count = CFArrayGetCount(channels);
for (long i = 0; i < chan_count; i++) {
CFDictionaryRef item = (CFDictionaryRef)CFArrayGetValueAtIndex(channels, i);
if (not item) continue;
string group = cfstring_to_string(IOReportChannelGetGroup(item));
string subgroup = cfstring_to_string(IOReportChannelGetSubGroup(item));
string channel = cfstring_to_string(IOReportChannelGetChannelName(item));
//? GPU utilization from residency states
if (group == "GPU Stats" and subgroup == "GPU Performance States" and channel == "GPUPH") {
int32_t state_count = IOReportStateGetCount(item);
if (state_count <= 0) continue;
int64_t total_residency = 0;
int64_t active_residency = 0;
double weighted_freq = 0;
//? Find offset past IDLE/OFF/DOWN states
int offset = 0;
for (int32_t s = 0; s < state_count; s++) {
string name = cfstring_to_string(IOReportStateGetNameForIndex(item, s));
if (name == "IDLE" or name == "OFF" or name == "DOWN")
offset = s + 1;
total_residency += IOReportStateGetResidency(item, s);
}
int freq_count = static_cast<int>(gpu_freqs.size());
for (int32_t s = offset; s < state_count; s++) {
int64_t res = IOReportStateGetResidency(item, s);
active_residency += res;
int freq_idx = s - offset;
if (freq_idx < freq_count and active_residency > 0)
weighted_freq += static_cast<double>(res) * gpu_freqs[freq_idx];
}
if (total_residency > 0) {
double usage_ratio = static_cast<double>(active_residency) / static_cast<double>(total_residency);
gpu_utilization = clamp(static_cast<long long>(round(usage_ratio * 100.0)), 0ll, 100ll);
got_gpu_util = true;
//? Calculate average frequency
if (active_residency > 0 and not gpu_freqs.empty()) {
double avg_freq = weighted_freq / static_cast<double>(active_residency);
gpus_slice[0].gpu_clock_speed = static_cast<unsigned int>(round(avg_freq));
}
}
}
//? GPU power from Energy Model
if (group == "Energy Model" and channel == "GPU Energy") {
string unit = cfstring_to_string(IOReportChannelGetUnitLabel(item));
int64_t val = IOReportSimpleGetIntegerValue(item, 0);
double energy = static_cast<double>(val);
double divisor = static_cast<double>(dt) / 1000.0; // dt is in ms
if (unit.find("nJ") != string::npos) energy /= 1e9;
else if (unit.find("uJ") != string::npos or unit.find("\xc2\xb5J") != string::npos) energy /= 1e6;
else if (unit.find("mJ") != string::npos) energy /= 1e3;
//? energy is now in Joules
if (divisor > 0) {
gpu_power_watts = energy / divisor;
got_gpu_power = true;
}
}
}
//? Store GPU utilization
if (got_gpu_util) {
gpus_slice[0].gpu_percent.at("gpu-totals").push_back(gpu_utilization);
gpus_slice[0].mem_utilization_percent.push_back(gpu_utilization);
}
//? Store power usage (convert W to mW)
if (got_gpu_power) {
gpus_slice[0].pwr_usage = static_cast<long long>(round(gpu_power_watts * 1000.0));
if (gpus_slice[0].pwr_usage > gpus_slice[0].pwr_max_usage)
gpus_slice[0].pwr_max_usage = gpus_slice[0].pwr_usage;
gpus_slice[0].gpu_percent.at("gpu-pwr-totals").push_back(
clamp(static_cast<long long>(round(static_cast<double>(gpus_slice[0].pwr_usage) * 100.0 / static_cast<double>(gpus_slice[0].pwr_max_usage))), 0ll, 100ll));
}
//? GPU temperature
if (gpus_slice[0].supported_functions.temp_info and Config::getB("check_temp")) {
long long temp = get_gpu_temp_iohid();
if (temp > 0)
gpus_slice[0].temp.push_back(temp);
}
//? Memory usage (unified memory — report system memory usage)
if (gpus_slice[0].supported_functions.mem_total) {
vm_size_t page_size;
mach_port_t mach_port = mach_host_self();
vm_statistics64_data_t vm_stats;
mach_msg_type_number_t count = sizeof(vm_stats) / sizeof(natural_t);
host_page_size(mach_port, &page_size);
if (host_statistics64(mach_port, HOST_VM_INFO64, (host_info64_t)&vm_stats, &count) == KERN_SUCCESS) {
long long used = (static_cast<int64_t>(vm_stats.active_count)
+ static_cast<int64_t>(vm_stats.inactive_count)
+ static_cast<int64_t>(vm_stats.wire_count)
+ static_cast<int64_t>(vm_stats.speculative_count)
+ static_cast<int64_t>(vm_stats.compressor_page_count)
- static_cast<int64_t>(vm_stats.purgeable_count)
- static_cast<int64_t>(vm_stats.external_page_count)) * static_cast<int64_t>(page_size);
if (used < 0) used = 0;
gpus_slice[0].mem_used = used;
if (gpus_slice[0].mem_total > 0) {
auto used_pct = static_cast<long long>(round(static_cast<double>(used) * 100.0 / static_cast<double>(gpus_slice[0].mem_total)));
gpus_slice[0].gpu_percent.at("gpu-vram-totals").push_back(clamp(used_pct, 0ll, 100ll));
}
}
}
return true;
}
//? Explicit template instantiations
template bool collect<true>(gpu_info*);
template bool collect<false>(gpu_info*);
} // namespace AppleSilicon
//? Collect data from Apple Silicon GPU
auto collect(bool no_update) -> vector<gpu_info>& {
if (Runner::stopping or (no_update and not gpus.empty())) return gpus;
AppleSilicon::collect<0>(gpus.data());
//* Calculate averages
long long avg = 0;
long long mem_usage_total = 0;
long long mem_total = 0;
long long pwr_total = 0;
for (auto& gpu : gpus) {
if (gpu.supported_functions.gpu_utilization and not gpu.gpu_percent.at("gpu-totals").empty())
avg += gpu.gpu_percent.at("gpu-totals").back();
if (gpu.supported_functions.mem_used)
mem_usage_total += gpu.mem_used;
if (gpu.supported_functions.mem_total)
mem_total += gpu.mem_total;
if (gpu.supported_functions.pwr_usage)
pwr_total += gpu.pwr_usage;
//* Trim vectors if there are more values than needed for graphs
if (width != 0) {
while (cmp_greater(gpu.gpu_percent.at("gpu-totals").size(), width * 2)) gpu.gpu_percent.at("gpu-totals").pop_front();
while (cmp_greater(gpu.mem_utilization_percent.size(), width)) gpu.mem_utilization_percent.pop_front();
while (cmp_greater(gpu.gpu_percent.at("gpu-pwr-totals").size(), width)) gpu.gpu_percent.at("gpu-pwr-totals").pop_front();
while (cmp_greater(gpu.temp.size(), 18)) gpu.temp.pop_front();
while (cmp_greater(gpu.gpu_percent.at("gpu-vram-totals").size(), width/2)) gpu.gpu_percent.at("gpu-vram-totals").pop_front();
}
}
if (not gpus.empty()) {
shared_gpu_percent.at("gpu-average").push_back(avg / static_cast<long long>(gpus.size()));
if (mem_total != 0)
shared_gpu_percent.at("gpu-vram-total").push_back(mem_usage_total * 100 / mem_total);
if (gpu_pwr_total_max != 0)
shared_gpu_percent.at("gpu-pwr-total").push_back(pwr_total * 100 / gpu_pwr_total_max);
}
if (width != 0) {
while (cmp_greater(shared_gpu_percent.at("gpu-average").size(), width * 2)) shared_gpu_percent.at("gpu-average").pop_front();
while (cmp_greater(shared_gpu_percent.at("gpu-vram-total").size(), width)) shared_gpu_percent.at("gpu-vram-total").pop_front();
while (cmp_greater(shared_gpu_percent.at("gpu-pwr-total").size(), width)) shared_gpu_percent.at("gpu-pwr-total").pop_front();
}
return gpus;
}
} // namespace Gpu
#endif // GPU_SUPPORT
class MachProcessorInfo {
public:
processor_info_array_t info_array;
@@ -188,6 +704,31 @@ namespace Shared {
Cpu::got_sensors = Cpu::get_sensors();
Cpu::core_mapping = Cpu::get_core_mapping();
//? Init for namespace Gpu
#ifdef GPU_SUPPORT
auto shown_gpus = Config::getS("shown_gpus");
if (shown_gpus.contains("apple")) {
Gpu::AppleSilicon::init();
}
if (not Gpu::gpu_names.empty()) {
for (auto const& [key, _] : Gpu::gpus[0].gpu_percent)
Cpu::available_fields.push_back(key);
for (auto const& [key, _] : Gpu::shared_gpu_percent)
Cpu::available_fields.push_back(key);
using namespace Gpu;
count = gpus.size();
gpu_b_height_offsets.resize(gpus.size());
for (size_t i = 0; i < gpu_b_height_offsets.size(); ++i)
gpu_b_height_offsets[i] = gpus[i].supported_functions.gpu_utilization
+ gpus[i].supported_functions.pwr_usage
+ (gpus[i].supported_functions.encoder_utilization or gpus[i].supported_functions.decoder_utilization)
+ (gpus[i].supported_functions.mem_total or gpus[i].supported_functions.mem_used)
* (1 + 2*(gpus[i].supported_functions.mem_total and gpus[i].supported_functions.mem_used) + 2*gpus[i].supported_functions.mem_utilization);
}
#endif
//? Init for namespace Mem
Mem::old_uptime = system_uptime();
Mem::collect();