From 3ec2aeeb01d0dba12a3bb010531da8ebee0fa5ae Mon Sep 17 00:00:00 2001 From: Gary Neitzke Date: Sat, 14 Feb 2026 11:44:17 -0800 Subject: [PATCH] feat: Add Apple Silicon GPU support via IOReport API Add GPU monitoring for Apple Silicon Macs using the IOReport framework and IOHIDEventSystem for temperature readings. Metrics supported: - GPU utilization (from GPU Performance States residency) - GPU power consumption (from Energy Model channel) - GPU temperature (from IOHIDEventSystem thermal sensors) - GPU clock speed (from DVFS frequency table + residency weighting) - Memory usage (unified memory architecture, reports system RAM) Implementation follows the existing Nvml/Rsmi/Intel backend pattern with init/shutdown/collect template functions in a new AppleSilicon namespace under Gpu. Build system changes: - Enable GPU_SUPPORT on macOS in both CMakeLists.txt and Makefile - Link IOReport library on macOS - Add 'apple' to shown_gpus vendor filter Closes #955 --- CMakeLists.txt | 5 + Makefile | 5 +- src/btop.cpp | 1 + src/btop_config.cpp | 4 +- src/btop_shared.hpp | 3 + src/osx/btop_collect.cpp | 519 +++++++++++++++++++++++++++++++++++++++ 6 files changed, 534 insertions(+), 3 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 8c5bdce3..f4122c97 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -198,6 +198,11 @@ 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 diff --git a/Makefile b/Makefile index d08a4ac7..0bb9cbd5 100644 --- a/Makefile +++ b/Makefile @@ -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 diff --git a/src/btop.cpp b/src/btop.cpp index ffc4e9e6..52033b82 100644 --- a/src/btop.cpp +++ b/src/btop.cpp @@ -231,6 +231,7 @@ void clean_quit(int sig) { #ifdef GPU_SUPPORT Gpu::Nvml::shutdown(); Gpu::Rsmi::shutdown(); + Gpu::AppleSilicon::shutdown(); #endif diff --git a/src/btop_config.cpp b/src/btop_config.cpp index 36555e3f..c9211771 100644 --- a/src/btop_config.cpp +++ b/src/btop_config.cpp @@ -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 stringsTmp; diff --git a/src/btop_shared.hpp b/src/btop_shared.hpp index eaa72ca0..26ccad1a 100644 --- a/src/btop_shared.hpp +++ b/src/btop_shared.hpp @@ -183,6 +183,9 @@ namespace Gpu { namespace Rsmi { extern bool shutdown(); } + namespace AppleSilicon { + extern bool shutdown(); + } //* Collect gpu stats and temperatures auto collect(bool no_update = false) -> vector&; diff --git a/src/osx/btop_collect.cpp b/src/osx/btop_collect.cpp index 197cec55..4856c5a5 100644 --- a/src/osx/btop_collect.cpp +++ b/src/osx/btop_collect.cpp @@ -68,6 +68,49 @@ tab-size = 4 #endif #include "smc.hpp" +#if defined(GPU_SUPPORT) +#include +#include + +//? 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 @@ -115,6 +158,457 @@ namespace Mem { double old_uptime; } +#if defined(GPU_SUPPORT) +namespace Gpu { + vector 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 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 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; + CFMutableDictionaryRef matchDict = IOServiceMatching("AppleARMIODevice"); + if (IOServiceGetMatchingServices(kIOMainPortDefault, matchDict, &iter) != kIOReturnSuccess) + return; + + io_object_t entry; + while ((entry = IOIteratorNext(iter)) != 0) { + char name[128]; + if (IORegistryEntryGetName(entry, name) == kIOReturnSuccess and string(name) == "pmgr") { + CFMutableDictionaryRef props = nullptr; + if (IORegistryEntryCreateCFProperties(entry, &props, kCFAllocatorDefault, 0) == kIOReturnSuccess and props) { + 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 + } + } + CFRelease(props); + } + } + IOObjectRelease(entry); + } + IOObjectRelease(iter); + } + + 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 + CFStringRef gpu_stats_group = CFStringCreateWithCString(kCFAllocatorDefault, "GPU Stats", kCFStringEncodingUTF8); + CFStringRef gpu_perf_subgroup = CFStringCreateWithCString(kCFAllocatorDefault, "GPU Performance States", kCFStringEncodingUTF8); + CFStringRef energy_group = CFStringCreateWithCString(kCFAllocatorDefault, "Energy Model", kCFStringEncodingUTF8); + + CFDictionaryRef gpu_chan = IOReportCopyChannelsInGroup(gpu_stats_group, gpu_perf_subgroup, 0, 0, 0); + CFDictionaryRef energy_chan = IOReportCopyChannelsInGroup(energy_group, nullptr, 0, 0, 0); + + CFRelease(gpu_stats_group); + CFRelease(gpu_perf_subgroup); + CFRelease(energy_group); + + if (not gpu_chan and not energy_chan) { + Logger::info("Apple Silicon GPU: No IOReport channels found, GPU monitoring unavailable"); + return false; + } + + //? Merge channels into a single subscription + if (gpu_chan and energy_chan) { + IOReportMergeChannels(gpu_chan, energy_chan, nullptr); + } + CFDictionaryRef base_chan = gpu_chan ? gpu_chan : energy_chan; + + auto size = CFDictionaryGetCount(base_chan); + ior_chan = CFDictionaryCreateMutableCopy(kCFAllocatorDefault, size, base_chan); + if (gpu_chan) CFRelease(gpu_chan); + if (energy_chan and energy_chan != base_chan) CFRelease(energy_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; + + CFNumberRef nums[2]; + CFStringRef keys[2]; + keys[0] = CFSTR("PrimaryUsagePage"); + keys[1] = CFSTR("PrimaryUsage"); + int page = kHIDPage_AppleVendor, usage = kHIDUsage_TemperatureSensor; + nums[0] = CFNumberCreate(nullptr, kCFNumberSInt32Type, &page); + nums[1] = CFNumberCreate(nullptr, kCFNumberSInt32Type, &usage); + CFDictionaryRef match = CFDictionaryCreate(nullptr, + (const void**)keys, (const void**)nums, 2, + &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); + CFRelease(nums[0]); + CFRelease(nums[1]); + + auto system = IOHIDEventSystemClientCreate(kCFAllocatorDefault); + if (not system) { CFRelease(match); return -1; } + IOHIDEventSystemClientSetMatching(system, match); + CFArrayRef services = IOHIDEventSystemClientCopyServices(system); + CFRelease(match); + + if (not services) { CFRelease(system); 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; + CFStringRef name = IOHIDServiceClientCopyProperty(sc, CFSTR("Product")); + if (not name) continue; + char buf[200]; + CFStringGetCString(name, buf, 200, kCFStringEncodingASCII); + string n(buf); + CFRelease(name); + //? "GPU MTR Temp Sensor" is the standard Apple Silicon GPU temp sensor name + if (n.find("GPU") != string::npos) { + auto event = IOHIDServiceClientCopyEvent(sc, kIOHIDEventTypeTemperature, 0, 0); + if (event) { + double temp = IOHIDEventGetFloatValue(event, kIOHIDEventTypeTemperature << 16); + if (temp > 0 and temp < 150) { + gpu_temp_sum += temp; + gpu_temp_count++; + } + CFRelease(event); + } + } + } + CFRelease(services); + CFRelease(system); + + if (gpu_temp_count > 0) + return static_cast(round(gpu_temp_sum / gpu_temp_count)); + #endif + return -1; + } + + template + 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; + + CFDictionaryRef delta = nullptr; + if (prev_sample) { + delta = IOReportCreateSamplesDelta(prev_sample, cur_sample, nullptr); + CFRelease(prev_sample); + } + prev_sample = cur_sample; + prev_sample_time = cur_time; + + if (not delta) return false; + + //? Parse delta samples + CFArrayRef channels = (CFArrayRef)CFDictionaryGetValue(delta, CFSTR("IOReportChannels")); + if (not channels) { CFRelease(delta); 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(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(res) * gpu_freqs[freq_idx]; + } + + if (total_residency > 0) { + double usage_ratio = static_cast(active_residency) / static_cast(total_residency); + gpu_utilization = clamp(static_cast(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(active_residency); + gpus_slice[0].gpu_clock_speed = static_cast(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(val); + double divisor = static_cast(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(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(round(static_cast(gpus_slice[0].pwr_usage) * 100.0 / static_cast(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(vm_stats.active_count) + + static_cast(vm_stats.inactive_count) + + static_cast(vm_stats.wire_count) + + static_cast(vm_stats.speculative_count) + + static_cast(vm_stats.compressor_page_count) + - static_cast(vm_stats.purgeable_count) + - static_cast(vm_stats.external_page_count)) * static_cast(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(round(static_cast(used) * 100.0 / static_cast(gpus_slice[0].mem_total))); + gpus_slice[0].gpu_percent.at("gpu-vram-totals").push_back(clamp(used_pct, 0ll, 100ll)); + } + } + } + + CFRelease(delta); + return true; + } + + //? Explicit template instantiations + template bool collect(gpu_info*); + template bool collect(gpu_info*); + } // namespace AppleSilicon + + //? Collect data from Apple Silicon GPU + auto collect(bool no_update) -> vector& { + 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(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 +682,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();