|
|
|
@@ -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
|
|
|
|
@@ -115,6 +158,457 @@ 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;
|
|
|
|
|
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<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;
|
|
|
|
|
|
|
|
|
|
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<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));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
CFRelease(delta);
|
|
|
|
|
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 +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();
|
|
|
|
|