From 83c3a9e9db462fe6b60e7fb9006d71e15da9182a Mon Sep 17 00:00:00 2001 From: shatyuka Date: Thu, 8 Jan 2026 16:04:57 +0800 Subject: [PATCH 1/6] Darwin: Add GPU monitoring support --- darwin/DarwinProcess.c | 34 ++++++++++++++ darwin/DarwinProcess.h | 8 ++++ darwin/DarwinProcessTable.c | 13 +++++ darwin/Platform.c | 94 +++++++++++++++++++++++++++++++++---- darwin/Platform.h | 2 + darwin/ProcessField.h | 2 + linux/LinuxProcess.c | 12 ++--- 7 files changed, 151 insertions(+), 14 deletions(-) diff --git a/darwin/DarwinProcess.c b/darwin/DarwinProcess.c index 9763c74c9..4a9bba858 100644 --- a/darwin/DarwinProcess.c +++ b/darwin/DarwinProcess.c @@ -52,6 +52,8 @@ const ProcessFieldData Process_fields[LAST_PROCESSFIELD] = { [PROC_EXE] = { .name = "EXE", .title = "EXE ", .description = "Basename of exe of the process from /proc/[pid]/exe", .flags = 0, }, [CWD] = { .name = "CWD", .title = "CWD ", .description = "The current working directory of the process", .flags = PROCESS_FLAG_CWD, }, [TRANSLATED] = { .name = "TRANSLATED", .title = "T ", .description = "Translation info (T translated, N native)", .flags = 0, }, + [TIME_GPU] = { .name = "TIME_GPU", .title = "GPU TIME+", .description = "Total GPU time", .flags = PROCESS_FLAG_GPU, .defaultSortDesc = true, }, + [PERCENT_GPU] = { .name = "PERCENT_GPU", .title = " GPU% ", .description = "Percentage of the GPU time the process used in the last sampling", .flags = PROCESS_FLAG_GPU, .defaultSortDesc = true, }, }; Process* DarwinProcess_new(const Machine* host) { @@ -77,7 +79,9 @@ void Process_delete(Object* cast) { static void DarwinProcess_rowWriteField(const Row* super, RichString* str, ProcessField field) { const DarwinProcess* dp = (const DarwinProcess*) super; + const Machine* host = (const Machine*) super->host; + bool coloring = host->settings->highlightMegabytes; char buffer[256]; buffer[255] = '\0'; int attr = CRT_colors[DEFAULT_COLOR]; size_t n = sizeof(buffer) - 1; @@ -85,6 +89,8 @@ static void DarwinProcess_rowWriteField(const Row* super, RichString* str, Proce switch (field) { // add Platform-specific fields here case TRANSLATED: xSnprintf(buffer, n, "%c ", dp->translated ? 'T' : 'N'); break; + case PERCENT_GPU: Row_printPercentage(dp->gpu_percent, buffer, n, 5, &attr); break; + case TIME_GPU: Row_printNanoseconds(str, dp->gpu_time, coloring); return; default: Process_writeField(&dp->super, str, field); return; @@ -101,6 +107,15 @@ static int DarwinProcess_compareByKey(const Process* v1, const Process* v2, Proc // add Platform-specific fields here case TRANSLATED: return SPACESHIP_NUMBER(p1->translated, p2->translated); + case PERCENT_GPU: { + int r = compareRealNumbers(p1->gpu_percent, p2->gpu_percent); + if (r) + return r; + + return SPACESHIP_NUMBER(p1->gpu_time, p2->gpu_time); + } + case TIME_GPU: + return SPACESHIP_NUMBER(p1->gpu_time, p2->gpu_time); default: return Process_compareByKey_Base(v1, v2, key); } @@ -521,6 +536,25 @@ void DarwinProcess_scanThreads(DarwinProcess* dp, DarwinProcessTable* dpt) { mach_port_deallocate(mach_task_self(), task); } +void DarwinProcess_setFromGPUProcesses(DarwinProcess* dp, Hashtable* gps) { + if (gps) { + unsigned long long* data = Hashtable_get(gps, (ht_key_t) dp->super.super.id); + unsigned long long new_gpu_time = data ? *data : 0; + if (new_gpu_time > 0) { + unsigned long long gputimeDelta = saturatingSub(new_gpu_time, dp->gpu_time); + const Machine* host = dp->super.super.host; + uint64_t monotonicTimeDelta = host->monotonicMs - host->prevMonotonicMs; + dp->gpu_percent = 100.0F * gputimeDelta / (1000 * 1000) / monotonicTimeDelta; + } else { + dp->gpu_percent = 0.0F; + } + dp->gpu_time = new_gpu_time; + } else { + dp->gpu_time = 0; + dp->gpu_percent = 0.0F; + } +} + const ProcessClass DarwinProcess_class = { .super = { diff --git a/darwin/DarwinProcess.h b/darwin/DarwinProcess.h index a5d66b490..c1602e6cc 100644 --- a/darwin/DarwinProcess.h +++ b/darwin/DarwinProcess.h @@ -14,6 +14,7 @@ in the source distribution for its full text. #define PROCESS_FLAG_TTY 0x00000100 +#define PROCESS_FLAG_GPU 0x00000200 typedef struct DarwinProcess_ { Process super; @@ -22,6 +23,11 @@ typedef struct DarwinProcess_ { uint64_t stime; bool taskAccess; bool translated; + + /* Total GPU time used in nano seconds */ + unsigned long long int gpu_time; + /* GPU utilization in percent */ + float gpu_percent; } DarwinProcess; extern const ProcessClass DarwinProcess_class; @@ -43,4 +49,6 @@ void DarwinProcess_setFromLibprocPidinfo(DarwinProcess* proc, DarwinProcessTable */ void DarwinProcess_scanThreads(DarwinProcess* dp, DarwinProcessTable* dpt); +void DarwinProcess_setFromGPUProcesses(DarwinProcess* dp, Hashtable* gps); + #endif diff --git a/darwin/DarwinProcessTable.c b/darwin/DarwinProcessTable.c index 9a2f6d5d0..b6c1f07e8 100644 --- a/darwin/DarwinProcessTable.c +++ b/darwin/DarwinProcessTable.c @@ -72,11 +72,14 @@ void ProcessTable_delete(Object* cast) { void ProcessTable_goThroughEntries(ProcessTable* super) { const Machine* host = super->super.host; const DarwinMachine* dhost = (const DarwinMachine*) host; + const Settings* settings = host->settings; + const ScreenSettings* ss = settings->ss; DarwinProcessTable* dpt = (DarwinProcessTable*) super; bool preExisting = true; struct kinfo_proc* ps; size_t count; DarwinProcess* proc; + Hashtable* gps = NULL; /* Get the time difference */ dpt->global_diff = 0; @@ -97,6 +100,10 @@ void ProcessTable_goThroughEntries(ProcessTable* super) { */ ps = ProcessTable_getKInfoProcs(&count); + if (ss->flags & PROCESS_FLAG_GPU) { + gps = Platform_getGPUProcesses(host); + } + for (size_t i = 0; i < count; ++i) { proc = (DarwinProcess*)ProcessTable_getProcess(super, ps[i].kp_proc.p_pid, &preExisting, DarwinProcess_new); @@ -122,6 +129,8 @@ void ProcessTable_goThroughEntries(ProcessTable* super) { DarwinProcess_scanThreads(proc, dpt); } + DarwinProcess_setFromGPUProcesses(proc, gps); + super->totalTasks += 1; if (!preExisting) { @@ -129,5 +138,9 @@ void ProcessTable_goThroughEntries(ProcessTable* super) { } } + if (gps) { + Hashtable_delete(gps); + } + free(ps); } diff --git a/darwin/Platform.c b/darwin/Platform.c index 72d262590..d4177da51 100644 --- a/darwin/Platform.c +++ b/darwin/Platform.c @@ -365,14 +365,9 @@ void Platform_setGPUValues(Meter* mtr, double* totalUsage, unsigned long long* t return; } - CFMutableDictionaryRef properties = NULL; - kern_return_t ret = IORegistryEntryCreateCFProperties(dhost->GPUService, &properties, kCFAllocatorDefault, kNilOptions); - if (ret != KERN_SUCCESS || !properties) - return; - - CFDictionaryRef perfStats = CFDictionaryGetValue(properties, CFSTR("PerformanceStatistics")); + CFDictionaryRef perfStats = IORegistryEntryCreateCFProperty(dhost->GPUService, CFSTR("PerformanceStatistics"), kCFAllocatorDefault, kNilOptions); if (!perfStats) - goto cleanup; + return; assert(CFGetTypeID(perfStats) == CFDictionaryGetTypeID()); @@ -387,7 +382,7 @@ void Platform_setGPUValues(Meter* mtr, double* totalUsage, unsigned long long* t prevMonotonicMs = host->monotonicMs; cleanup: - CFRelease(properties); + CFRelease(perfStats); mtr->values[0] = *totalUsage; } @@ -798,3 +793,86 @@ static void Platform_getOSRelease(char* buffer, size_t bufferLen) { const char* Platform_getRelease(void) { return Generic_unameRelease(Platform_getOSRelease); } + +Hashtable* Platform_getGPUProcesses(const Machine* host) { + const DarwinMachine* dhost = (const DarwinMachine*)host; + if (!dhost->GPUService) { + return NULL; + } + + Hashtable* gps = Hashtable_new(64, true); + + io_iterator_t iterator; + if (IORegistryEntryGetChildIterator(dhost->GPUService, kIOServicePlane, &iterator) == KERN_SUCCESS) { + io_registry_entry_t entry; + while ((entry = IOIteratorNext(iterator))) { + io_struct_inband_t buffer; + uint32_t size = sizeof(buffer); + if (IORegistryEntryGetProperty(entry, "IOUserClientCreator", buffer, &size) != KERN_SUCCESS) + goto cleanup; + + pid_t pid; + if (sscanf(buffer, "pid %d", &pid) != 1) + goto cleanup; + + unsigned long long gpuTime = 0; + unsigned long long* data = Hashtable_get(gps, (ht_key_t) pid); + if (data) + gpuTime = *data; + + if (IOObjectConformsTo(entry, "IOGPUDeviceUserClient")) { + CFArrayRef appUsage = IORegistryEntryCreateCFProperty(entry, CFSTR("AppUsage"), kCFAllocatorDefault, kNilOptions); + if (!appUsage) + goto cleanup; + + assert(CFGetTypeID(appUsage) == CFArrayGetTypeID()); + + // Sum up the GPU time for all command queues for this client + size_t count = CFArrayGetCount(appUsage); + for (size_t i = 0; i < count; ++i) { + CFDictionaryRef appUsageEntry = CFArrayGetValueAtIndex(appUsage, i); + assert(CFGetTypeID(appUsageEntry) == CFDictionaryGetTypeID()); + + CFNumberRef accumulatedGPUTimeRef = CFDictionaryGetValue(appUsageEntry, CFSTR("accumulatedGPUTime")); + if (!accumulatedGPUTimeRef) { + CFRelease(appUsage); + goto cleanup; + } + + unsigned long long accumulatedGPUTime = 0; + CFNumberGetValue(accumulatedGPUTimeRef, kCFNumberLongLongType, &accumulatedGPUTime); + + gpuTime += accumulatedGPUTime; + } + + CFRelease(appUsage); + } else if (IOObjectConformsTo(entry, "IOAccelSubmitter2")) { + CFNumberRef accumulatedGPUTimeRef = IORegistryEntryCreateCFProperty(entry, CFSTR("accumulatedGPUTime"), kCFAllocatorDefault, kNilOptions); + if (!accumulatedGPUTimeRef) + goto cleanup; + + unsigned long long accumulatedGPUTime = 0; + CFNumberGetValue(accumulatedGPUTimeRef, kCFNumberLongLongType, &accumulatedGPUTime); + CFRelease(accumulatedGPUTimeRef); + + gpuTime += accumulatedGPUTime; + } else { + goto cleanup; + } + + if (gpuTime > 0) { + if (!data) { + data = xCalloc(1, sizeof(gpuTime)); + Hashtable_put(gps, (ht_key_t) pid, data); + } + *data = gpuTime; + } + + cleanup: + IOObjectRelease(entry); + } + IOObjectRelease(iterator); + } + + return gps; +} diff --git a/darwin/Platform.h b/darwin/Platform.h index bfe67e0a0..2577878f6 100644 --- a/darwin/Platform.h +++ b/darwin/Platform.h @@ -86,6 +86,8 @@ static inline void Platform_getHostname(char* buffer, size_t size) { const char* Platform_getRelease(void); +Hashtable* Platform_getGPUProcesses(const Machine* host); + static inline const char* Platform_getFailedState(void) { return NULL; } diff --git a/darwin/ProcessField.h b/darwin/ProcessField.h index 05fbc3ba6..e791f8dcb 100644 --- a/darwin/ProcessField.h +++ b/darwin/ProcessField.h @@ -10,6 +10,8 @@ in the source distribution for its full text. #define PLATFORM_PROCESS_FIELDS \ TRANSLATED = 100, \ + TIME_GPU = 101, \ + PERCENT_GPU = 102, \ \ DUMMY_BUMP_FIELD = CWD, \ // End of list diff --git a/linux/LinuxProcess.c b/linux/LinuxProcess.c index 3f917d870..d820519ad 100644 --- a/linux/LinuxProcess.c +++ b/linux/LinuxProcess.c @@ -110,8 +110,8 @@ const ProcessFieldData Process_fields[LAST_PROCESSFIELD] = { #ifdef SCHEDULER_SUPPORT [SCHEDULERPOLICY] = { .name = "SCHEDULERPOLICY", .title = "SCHED ", .description = "Current scheduling policy of the process", .flags = PROCESS_FLAG_SCHEDPOL, }, #endif - [GPU_TIME] = { .name = "GPU_TIME", .title = "GPU_TIME ", .description = "Total GPU time", .flags = PROCESS_FLAG_LINUX_GPU, .defaultSortDesc = true, }, - [GPU_PERCENT] = { .name = "GPU_PERCENT", .title = " GPU% ", .description = "Percentage of the GPU time the process used in the last sampling", .flags = PROCESS_FLAG_LINUX_GPU, .defaultSortDesc = true, }, + [TIME_GPU] = { .name = "TIME_GPU", .title = "GPU TIME+", .description = "Total GPU time", .flags = PROCESS_FLAG_LINUX_GPU, .defaultSortDesc = true, }, + [PERCENT_GPU] = { .name = "PERCENT_GPU", .title = " GPU% ", .description = "Percentage of the GPU time the process used in the last sampling", .flags = PROCESS_FLAG_LINUX_GPU, .defaultSortDesc = true, }, }; Process* LinuxProcess_new(const Machine* host) { @@ -245,8 +245,8 @@ static void LinuxProcess_rowWriteField(const Row* super, RichString* str, Proces switch (field) { case CMINFLT: Row_printCount(str, lp->cminflt, coloring); return; case CMAJFLT: Row_printCount(str, lp->cmajflt, coloring); return; - case GPU_PERCENT: Row_printPercentage(lp->gpu_percent, buffer, n, 5, &attr); break; - case GPU_TIME: Row_printNanoseconds(str, lp->gpu_time, coloring); return; + case PERCENT_GPU: Row_printPercentage(lp->gpu_percent, buffer, n, 5, &attr); break; + case TIME_GPU: Row_printNanoseconds(str, lp->gpu_time, coloring); return; case M_DRS: Row_printBytes(str, lp->m_drs * lhost->pageSize, coloring); return; case M_LRS: if (lp->m_lrs) { @@ -455,14 +455,14 @@ static int LinuxProcess_compareByKey(const Process* v1, const Process* v2, Proce return SPACESHIP_NUMBER(p1->autogroup_id, p2->autogroup_id); case AUTOGROUP_NICE: return SPACESHIP_NUMBER(p1->autogroup_nice, p2->autogroup_nice); - case GPU_PERCENT: { + case PERCENT_GPU: { int r = compareRealNumbers(p1->gpu_percent, p2->gpu_percent); if (r) return r; return SPACESHIP_NUMBER(p1->gpu_time, p2->gpu_time); } - case GPU_TIME: + case TIME_GPU: return SPACESHIP_NUMBER(p1->gpu_time, p2->gpu_time); case ISCONTAINER: return SPACESHIP_NUMBER(v1->isRunningInContainer, v2->isRunningInContainer); From 9327a96b8b7cb87a2990f88980de666e3f890024 Mon Sep 17 00:00:00 2001 From: shatyuka Date: Thu, 8 Jan 2026 19:57:52 +0800 Subject: [PATCH 2/6] Revert field name change --- darwin/DarwinProcess.c | 4 ++-- linux/LinuxProcess.c | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/darwin/DarwinProcess.c b/darwin/DarwinProcess.c index 4a9bba858..41a53778b 100644 --- a/darwin/DarwinProcess.c +++ b/darwin/DarwinProcess.c @@ -52,8 +52,8 @@ const ProcessFieldData Process_fields[LAST_PROCESSFIELD] = { [PROC_EXE] = { .name = "EXE", .title = "EXE ", .description = "Basename of exe of the process from /proc/[pid]/exe", .flags = 0, }, [CWD] = { .name = "CWD", .title = "CWD ", .description = "The current working directory of the process", .flags = PROCESS_FLAG_CWD, }, [TRANSLATED] = { .name = "TRANSLATED", .title = "T ", .description = "Translation info (T translated, N native)", .flags = 0, }, - [TIME_GPU] = { .name = "TIME_GPU", .title = "GPU TIME+", .description = "Total GPU time", .flags = PROCESS_FLAG_GPU, .defaultSortDesc = true, }, - [PERCENT_GPU] = { .name = "PERCENT_GPU", .title = " GPU% ", .description = "Percentage of the GPU time the process used in the last sampling", .flags = PROCESS_FLAG_GPU, .defaultSortDesc = true, }, + [TIME_GPU] = { .name = "GPU_TIME", .title = "GPU TIME+", .description = "Total GPU time", .flags = PROCESS_FLAG_GPU, .defaultSortDesc = true, }, + [PERCENT_GPU] = { .name = "GPU_PERCENT", .title = " GPU% ", .description = "Percentage of the GPU time the process used in the last sampling", .flags = PROCESS_FLAG_GPU, .defaultSortDesc = true, }, }; Process* DarwinProcess_new(const Machine* host) { diff --git a/linux/LinuxProcess.c b/linux/LinuxProcess.c index d820519ad..c8c917b1d 100644 --- a/linux/LinuxProcess.c +++ b/linux/LinuxProcess.c @@ -110,8 +110,8 @@ const ProcessFieldData Process_fields[LAST_PROCESSFIELD] = { #ifdef SCHEDULER_SUPPORT [SCHEDULERPOLICY] = { .name = "SCHEDULERPOLICY", .title = "SCHED ", .description = "Current scheduling policy of the process", .flags = PROCESS_FLAG_SCHEDPOL, }, #endif - [TIME_GPU] = { .name = "TIME_GPU", .title = "GPU TIME+", .description = "Total GPU time", .flags = PROCESS_FLAG_LINUX_GPU, .defaultSortDesc = true, }, - [PERCENT_GPU] = { .name = "PERCENT_GPU", .title = " GPU% ", .description = "Percentage of the GPU time the process used in the last sampling", .flags = PROCESS_FLAG_LINUX_GPU, .defaultSortDesc = true, }, + [TIME_GPU] = { .name = "GPU_TIME", .title = "GPU TIME+", .description = "Total GPU time", .flags = PROCESS_FLAG_LINUX_GPU, .defaultSortDesc = true, }, + [PERCENT_GPU] = { .name = "GPU_PERCENT", .title = " GPU% ", .description = "Percentage of the GPU time the process used in the last sampling", .flags = PROCESS_FLAG_LINUX_GPU, .defaultSortDesc = true, }, }; Process* LinuxProcess_new(const Machine* host) { From 937a7d7b47b7142d098c20be917ec05b2bab706e Mon Sep 17 00:00:00 2001 From: shatyuka Date: Fri, 9 Jan 2026 00:45:32 +0800 Subject: [PATCH 3/6] Revert PerformanceStatistics property code simplify --- darwin/Platform.c | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/darwin/Platform.c b/darwin/Platform.c index d4177da51..2c6cff5db 100644 --- a/darwin/Platform.c +++ b/darwin/Platform.c @@ -365,10 +365,15 @@ void Platform_setGPUValues(Meter* mtr, double* totalUsage, unsigned long long* t return; } - CFDictionaryRef perfStats = IORegistryEntryCreateCFProperty(dhost->GPUService, CFSTR("PerformanceStatistics"), kCFAllocatorDefault, kNilOptions); - if (!perfStats) + CFMutableDictionaryRef properties = NULL; + kern_return_t ret = IORegistryEntryCreateCFProperties(dhost->GPUService, &properties, kCFAllocatorDefault, kNilOptions); + if (ret != KERN_SUCCESS || !properties) return; + CFDictionaryRef perfStats = CFDictionaryGetValue(properties, CFSTR("PerformanceStatistics")); + if (!perfStats) + goto cleanup; + assert(CFGetTypeID(perfStats) == CFDictionaryGetTypeID()); CFNumberRef deviceUtil = CFDictionaryGetValue(perfStats, CFSTR("Device Utilization %")); @@ -382,7 +387,7 @@ void Platform_setGPUValues(Meter* mtr, double* totalUsage, unsigned long long* t prevMonotonicMs = host->monotonicMs; cleanup: - CFRelease(perfStats); + CFRelease(properties); mtr->values[0] = *totalUsage; } From 1ccd9b7bf39fca1f467e49aa47ea176cad6ea48a Mon Sep 17 00:00:00 2001 From: shatyuka Date: Sat, 10 Jan 2026 16:36:13 +0800 Subject: [PATCH 4/6] Update code per review comments --- darwin/DarwinProcess.c | 21 +----- darwin/DarwinProcess.h | 6 +- darwin/DarwinProcessTable.c | 13 ++-- darwin/Platform.c | 134 ++++++++++++++++++++---------------- darwin/Platform.h | 2 +- linux/LinuxProcess.c | 2 +- 6 files changed, 84 insertions(+), 94 deletions(-) diff --git a/darwin/DarwinProcess.c b/darwin/DarwinProcess.c index 41a53778b..cee8b08ea 100644 --- a/darwin/DarwinProcess.c +++ b/darwin/DarwinProcess.c @@ -52,7 +52,7 @@ const ProcessFieldData Process_fields[LAST_PROCESSFIELD] = { [PROC_EXE] = { .name = "EXE", .title = "EXE ", .description = "Basename of exe of the process from /proc/[pid]/exe", .flags = 0, }, [CWD] = { .name = "CWD", .title = "CWD ", .description = "The current working directory of the process", .flags = PROCESS_FLAG_CWD, }, [TRANSLATED] = { .name = "TRANSLATED", .title = "T ", .description = "Translation info (T translated, N native)", .flags = 0, }, - [TIME_GPU] = { .name = "GPU_TIME", .title = "GPU TIME+", .description = "Total GPU time", .flags = PROCESS_FLAG_GPU, .defaultSortDesc = true, }, + [TIME_GPU] = { .name = "GPU_TIME", .title = "GPU TIME", .description = "Total GPU time", .flags = PROCESS_FLAG_GPU, .defaultSortDesc = true, }, [PERCENT_GPU] = { .name = "GPU_PERCENT", .title = " GPU% ", .description = "Percentage of the GPU time the process used in the last sampling", .flags = PROCESS_FLAG_GPU, .defaultSortDesc = true, }, }; @@ -536,25 +536,6 @@ void DarwinProcess_scanThreads(DarwinProcess* dp, DarwinProcessTable* dpt) { mach_port_deallocate(mach_task_self(), task); } -void DarwinProcess_setFromGPUProcesses(DarwinProcess* dp, Hashtable* gps) { - if (gps) { - unsigned long long* data = Hashtable_get(gps, (ht_key_t) dp->super.super.id); - unsigned long long new_gpu_time = data ? *data : 0; - if (new_gpu_time > 0) { - unsigned long long gputimeDelta = saturatingSub(new_gpu_time, dp->gpu_time); - const Machine* host = dp->super.super.host; - uint64_t monotonicTimeDelta = host->monotonicMs - host->prevMonotonicMs; - dp->gpu_percent = 100.0F * gputimeDelta / (1000 * 1000) / monotonicTimeDelta; - } else { - dp->gpu_percent = 0.0F; - } - dp->gpu_time = new_gpu_time; - } else { - dp->gpu_time = 0; - dp->gpu_percent = 0.0F; - } -} - const ProcessClass DarwinProcess_class = { .super = { diff --git a/darwin/DarwinProcess.h b/darwin/DarwinProcess.h index c1602e6cc..e7cc2b858 100644 --- a/darwin/DarwinProcess.h +++ b/darwin/DarwinProcess.h @@ -25,9 +25,11 @@ typedef struct DarwinProcess_ { bool translated; /* Total GPU time used in nano seconds */ - unsigned long long int gpu_time; + uint64_t gpu_time; /* GPU utilization in percent */ float gpu_percent; + /* Got GPU time info from last scan */ + bool gpu_time_updated; } DarwinProcess; extern const ProcessClass DarwinProcess_class; @@ -49,6 +51,4 @@ void DarwinProcess_setFromLibprocPidinfo(DarwinProcess* proc, DarwinProcessTable */ void DarwinProcess_scanThreads(DarwinProcess* dp, DarwinProcessTable* dpt); -void DarwinProcess_setFromGPUProcesses(DarwinProcess* dp, Hashtable* gps); - #endif diff --git a/darwin/DarwinProcessTable.c b/darwin/DarwinProcessTable.c index b6c1f07e8..f85b50317 100644 --- a/darwin/DarwinProcessTable.c +++ b/darwin/DarwinProcessTable.c @@ -79,7 +79,6 @@ void ProcessTable_goThroughEntries(ProcessTable* super) { struct kinfo_proc* ps; size_t count; DarwinProcess* proc; - Hashtable* gps = NULL; /* Get the time difference */ dpt->global_diff = 0; @@ -100,10 +99,6 @@ void ProcessTable_goThroughEntries(ProcessTable* super) { */ ps = ProcessTable_getKInfoProcs(&count); - if (ss->flags & PROCESS_FLAG_GPU) { - gps = Platform_getGPUProcesses(host); - } - for (size_t i = 0; i < count; ++i) { proc = (DarwinProcess*)ProcessTable_getProcess(super, ps[i].kp_proc.p_pid, &preExisting, DarwinProcess_new); @@ -129,7 +124,8 @@ void ProcessTable_goThroughEntries(ProcessTable* super) { DarwinProcess_scanThreads(proc, dpt); } - DarwinProcess_setFromGPUProcesses(proc, gps); + // Reset GPU time updated flag + proc->gpu_time_updated = false; super->totalTasks += 1; @@ -138,8 +134,9 @@ void ProcessTable_goThroughEntries(ProcessTable* super) { } } - if (gps) { - Hashtable_delete(gps); + // Get GPU usage info for processes if requested + if (ss->flags & PROCESS_FLAG_GPU) { + Platform_setGPUProcesses(dpt); } free(ps); diff --git a/darwin/Platform.c b/darwin/Platform.c index 2c6cff5db..64f1659a8 100644 --- a/darwin/Platform.c +++ b/darwin/Platform.c @@ -799,85 +799,97 @@ const char* Platform_getRelease(void) { return Generic_unameRelease(Platform_getOSRelease); } -Hashtable* Platform_getGPUProcesses(const Machine* host) { - const DarwinMachine* dhost = (const DarwinMachine*)host; - if (!dhost->GPUService) { - return NULL; - } +void Platform_setGPUProcesses(DarwinProcessTable* dpt) { + const Machine* host = dpt->super.super.host; + const DarwinMachine* dhost = (const DarwinMachine*) host; - Hashtable* gps = Hashtable_new(64, true); + if (!dhost->GPUService) + return; io_iterator_t iterator; - if (IORegistryEntryGetChildIterator(dhost->GPUService, kIOServicePlane, &iterator) == KERN_SUCCESS) { - io_registry_entry_t entry; - while ((entry = IOIteratorNext(iterator))) { - io_struct_inband_t buffer; - uint32_t size = sizeof(buffer); - if (IORegistryEntryGetProperty(entry, "IOUserClientCreator", buffer, &size) != KERN_SUCCESS) - goto cleanup; + if (IORegistryEntryGetChildIterator(dhost->GPUService, kIOServicePlane, &iterator) != KERN_SUCCESS) + return; - pid_t pid; - if (sscanf(buffer, "pid %d", &pid) != 1) + io_registry_entry_t entry; + while ((entry = IOIteratorNext(iterator))) { + io_struct_inband_t buffer; + uint32_t size = sizeof(buffer); + if (IORegistryEntryGetProperty(entry, "IOUserClientCreator", buffer, &size) != KERN_SUCCESS) + goto cleanup; + + pid_t pid; + if (sscanf(buffer, "pid %d", &pid) != 1) + goto cleanup; + + uint64_t gpuTime = 0; + const Table* table = &dpt->super.super; + DarwinProcess* dp = (DarwinProcess*) Hashtable_get(table->table, pid); + if (!dp) + goto cleanup; + + if (IOObjectConformsTo(entry, "IOGPUDeviceUserClient")) { + CFArrayRef appUsage = IORegistryEntryCreateCFProperty(entry, CFSTR("AppUsage"), kCFAllocatorDefault, kNilOptions); + if (!appUsage) goto cleanup; - unsigned long long gpuTime = 0; - unsigned long long* data = Hashtable_get(gps, (ht_key_t) pid); - if (data) - gpuTime = *data; + assert(CFGetTypeID(appUsage) == CFArrayGetTypeID()); - if (IOObjectConformsTo(entry, "IOGPUDeviceUserClient")) { - CFArrayRef appUsage = IORegistryEntryCreateCFProperty(entry, CFSTR("AppUsage"), kCFAllocatorDefault, kNilOptions); - if (!appUsage) - goto cleanup; + // Sum up the GPU time for all command queues for this client + size_t count = CFArrayGetCount(appUsage); + for (size_t i = 0; i < count; ++i) { + CFDictionaryRef appUsageEntry = CFArrayGetValueAtIndex(appUsage, i); + assert(CFGetTypeID(appUsageEntry) == CFDictionaryGetTypeID()); - assert(CFGetTypeID(appUsage) == CFArrayGetTypeID()); + CFNumberRef accumulatedGPUTimeRef = CFDictionaryGetValue(appUsageEntry, CFSTR("accumulatedGPUTime")); + if (!accumulatedGPUTimeRef) { + CFRelease(appUsage); + goto cleanup; + } - // Sum up the GPU time for all command queues for this client - size_t count = CFArrayGetCount(appUsage); - for (size_t i = 0; i < count; ++i) { - CFDictionaryRef appUsageEntry = CFArrayGetValueAtIndex(appUsage, i); - assert(CFGetTypeID(appUsageEntry) == CFDictionaryGetTypeID()); + uint64_t accumulatedGPUTime = 0; + CFNumberGetValue(accumulatedGPUTimeRef, kCFNumberLongLongType, &accumulatedGPUTime); - CFNumberRef accumulatedGPUTimeRef = CFDictionaryGetValue(appUsageEntry, CFSTR("accumulatedGPUTime")); - if (!accumulatedGPUTimeRef) { - CFRelease(appUsage); - goto cleanup; - } + gpuTime += accumulatedGPUTime; + } - unsigned long long accumulatedGPUTime = 0; - CFNumberGetValue(accumulatedGPUTimeRef, kCFNumberLongLongType, &accumulatedGPUTime); + CFRelease(appUsage); + } else if (IOObjectConformsTo(entry, "IOAccelSubmitter2")) { + CFNumberRef accumulatedGPUTimeRef = IORegistryEntryCreateCFProperty(entry, CFSTR("accumulatedGPUTime"), kCFAllocatorDefault, kNilOptions); + if (!accumulatedGPUTimeRef) + goto cleanup; - gpuTime += accumulatedGPUTime; - } + uint64_t accumulatedGPUTime = 0; + CFNumberGetValue(accumulatedGPUTimeRef, kCFNumberLongLongType, &accumulatedGPUTime); - CFRelease(appUsage); - } else if (IOObjectConformsTo(entry, "IOAccelSubmitter2")) { - CFNumberRef accumulatedGPUTimeRef = IORegistryEntryCreateCFProperty(entry, CFSTR("accumulatedGPUTime"), kCFAllocatorDefault, kNilOptions); - if (!accumulatedGPUTimeRef) - goto cleanup; + gpuTime += accumulatedGPUTime; - unsigned long long accumulatedGPUTime = 0; - CFNumberGetValue(accumulatedGPUTimeRef, kCFNumberLongLongType, &accumulatedGPUTime); - CFRelease(accumulatedGPUTimeRef); + CFRelease(accumulatedGPUTimeRef); + } else { + goto cleanup; + } - gpuTime += accumulatedGPUTime; - } else { - goto cleanup; - } + if (gpuTime > 0) { + uint64_t gputimeDelta = saturatingSub(gpuTime, dp->gpu_time); + uint64_t monotonicTimeDelta = host->monotonicMs - host->prevMonotonicMs; + dp->gpu_percent = 100.0F * gputimeDelta / (1000 * 1000) / monotonicTimeDelta; + dp->gpu_time = gpuTime; + dp->gpu_time_updated = true; + } - if (gpuTime > 0) { - if (!data) { - data = xCalloc(1, sizeof(gpuTime)); - Hashtable_put(gps, (ht_key_t) pid, data); - } - *data = gpuTime; - } +cleanup: + IOObjectRelease(entry); + } - cleanup: - IOObjectRelease(entry); + // Clear GPU time and percent for processes not found in the GPU process list + const Vector* rows = dpt->super.super.rows; + int table_size = Vector_size(rows); + for (int i = 0; i < table_size; ++i) { + DarwinProcess* dp = (DarwinProcess*) Vector_get(rows, i); + if (!dp->gpu_time_updated) { + dp->gpu_time = 0; + dp->gpu_percent = 0.0F; } - IOObjectRelease(iterator); } - return gps; + IOObjectRelease(iterator); } diff --git a/darwin/Platform.h b/darwin/Platform.h index 2577878f6..087799973 100644 --- a/darwin/Platform.h +++ b/darwin/Platform.h @@ -86,7 +86,7 @@ static inline void Platform_getHostname(char* buffer, size_t size) { const char* Platform_getRelease(void); -Hashtable* Platform_getGPUProcesses(const Machine* host); +void Platform_setGPUProcesses(DarwinProcessTable* dpt); static inline const char* Platform_getFailedState(void) { return NULL; diff --git a/linux/LinuxProcess.c b/linux/LinuxProcess.c index c8c917b1d..93a841a65 100644 --- a/linux/LinuxProcess.c +++ b/linux/LinuxProcess.c @@ -110,7 +110,7 @@ const ProcessFieldData Process_fields[LAST_PROCESSFIELD] = { #ifdef SCHEDULER_SUPPORT [SCHEDULERPOLICY] = { .name = "SCHEDULERPOLICY", .title = "SCHED ", .description = "Current scheduling policy of the process", .flags = PROCESS_FLAG_SCHEDPOL, }, #endif - [TIME_GPU] = { .name = "GPU_TIME", .title = "GPU TIME+", .description = "Total GPU time", .flags = PROCESS_FLAG_LINUX_GPU, .defaultSortDesc = true, }, + [TIME_GPU] = { .name = "GPU_TIME", .title = "GPU TIME", .description = "Total GPU time", .flags = PROCESS_FLAG_LINUX_GPU, .defaultSortDesc = true, }, [PERCENT_GPU] = { .name = "GPU_PERCENT", .title = " GPU% ", .description = "Percentage of the GPU time the process used in the last sampling", .flags = PROCESS_FLAG_LINUX_GPU, .defaultSortDesc = true, }, }; From 3ac5de90739809c52c426b3cd202a82197eff9f9 Mon Sep 17 00:00:00 2001 From: shatyuka Date: Sun, 11 Jan 2026 13:28:22 +0800 Subject: [PATCH 5/6] Update code per review comments --- darwin/DarwinProcess.c | 12 ++++++------ darwin/Platform.c | 12 +++++------- darwin/ProcessField.h | 4 ++-- linux/LinuxProcess.c | 12 ++++++------ 4 files changed, 19 insertions(+), 21 deletions(-) diff --git a/darwin/DarwinProcess.c b/darwin/DarwinProcess.c index cee8b08ea..2eb739071 100644 --- a/darwin/DarwinProcess.c +++ b/darwin/DarwinProcess.c @@ -52,8 +52,8 @@ const ProcessFieldData Process_fields[LAST_PROCESSFIELD] = { [PROC_EXE] = { .name = "EXE", .title = "EXE ", .description = "Basename of exe of the process from /proc/[pid]/exe", .flags = 0, }, [CWD] = { .name = "CWD", .title = "CWD ", .description = "The current working directory of the process", .flags = PROCESS_FLAG_CWD, }, [TRANSLATED] = { .name = "TRANSLATED", .title = "T ", .description = "Translation info (T translated, N native)", .flags = 0, }, - [TIME_GPU] = { .name = "GPU_TIME", .title = "GPU TIME", .description = "Total GPU time", .flags = PROCESS_FLAG_GPU, .defaultSortDesc = true, }, - [PERCENT_GPU] = { .name = "GPU_PERCENT", .title = " GPU% ", .description = "Percentage of the GPU time the process used in the last sampling", .flags = PROCESS_FLAG_GPU, .defaultSortDesc = true, }, + [GPU_TIME] = { .name = "GPU_TIME", .title = "GPU TIME", .description = "Total GPU time", .flags = PROCESS_FLAG_GPU, .defaultSortDesc = true, }, + [GPU_PERCENT] = { .name = "GPU_PERCENT", .title = " GPU% ", .description = "Percentage of the GPU time the process used in the last sampling", .flags = PROCESS_FLAG_GPU, .defaultSortDesc = true, }, }; Process* DarwinProcess_new(const Machine* host) { @@ -89,8 +89,8 @@ static void DarwinProcess_rowWriteField(const Row* super, RichString* str, Proce switch (field) { // add Platform-specific fields here case TRANSLATED: xSnprintf(buffer, n, "%c ", dp->translated ? 'T' : 'N'); break; - case PERCENT_GPU: Row_printPercentage(dp->gpu_percent, buffer, n, 5, &attr); break; - case TIME_GPU: Row_printNanoseconds(str, dp->gpu_time, coloring); return; + case GPU_PERCENT: Row_printPercentage(dp->gpu_percent, buffer, n, 5, &attr); break; + case GPU_TIME: Row_printNanoseconds(str, dp->gpu_time, coloring); return; default: Process_writeField(&dp->super, str, field); return; @@ -107,14 +107,14 @@ static int DarwinProcess_compareByKey(const Process* v1, const Process* v2, Proc // add Platform-specific fields here case TRANSLATED: return SPACESHIP_NUMBER(p1->translated, p2->translated); - case PERCENT_GPU: { + case GPU_PERCENT: { int r = compareRealNumbers(p1->gpu_percent, p2->gpu_percent); if (r) return r; return SPACESHIP_NUMBER(p1->gpu_time, p2->gpu_time); } - case TIME_GPU: + case GPU_TIME: return SPACESHIP_NUMBER(p1->gpu_time, p2->gpu_time); default: return Process_compareByKey_Base(v1, v2, key); diff --git a/darwin/Platform.c b/darwin/Platform.c index 64f1659a8..8bb78eacc 100644 --- a/darwin/Platform.c +++ b/darwin/Platform.c @@ -799,6 +799,7 @@ const char* Platform_getRelease(void) { return Generic_unameRelease(Platform_getOSRelease); } +// GPU I/O registry dump: https://archive.org/download/ioreg-gpu/ioreg-gpu.log void Platform_setGPUProcesses(DarwinProcessTable* dpt) { const Machine* host = dpt->super.super.host; const DarwinMachine* dhost = (const DarwinMachine*) host; @@ -817,13 +818,12 @@ void Platform_setGPUProcesses(DarwinProcessTable* dpt) { if (IORegistryEntryGetProperty(entry, "IOUserClientCreator", buffer, &size) != KERN_SUCCESS) goto cleanup; - pid_t pid; + int pid; if (sscanf(buffer, "pid %d", &pid) != 1) goto cleanup; uint64_t gpuTime = 0; - const Table* table = &dpt->super.super; - DarwinProcess* dp = (DarwinProcess*) Hashtable_get(table->table, pid); + DarwinProcess* dp = (DarwinProcess*) ProcessTable_findProcess(&dpt->super, (pid_t) pid); if (!dp) goto cleanup; @@ -841,10 +841,8 @@ void Platform_setGPUProcesses(DarwinProcessTable* dpt) { assert(CFGetTypeID(appUsageEntry) == CFDictionaryGetTypeID()); CFNumberRef accumulatedGPUTimeRef = CFDictionaryGetValue(appUsageEntry, CFSTR("accumulatedGPUTime")); - if (!accumulatedGPUTimeRef) { - CFRelease(appUsage); - goto cleanup; - } + if (!accumulatedGPUTimeRef) + continue; uint64_t accumulatedGPUTime = 0; CFNumberGetValue(accumulatedGPUTimeRef, kCFNumberLongLongType, &accumulatedGPUTime); diff --git a/darwin/ProcessField.h b/darwin/ProcessField.h index e791f8dcb..702001f58 100644 --- a/darwin/ProcessField.h +++ b/darwin/ProcessField.h @@ -10,8 +10,8 @@ in the source distribution for its full text. #define PLATFORM_PROCESS_FIELDS \ TRANSLATED = 100, \ - TIME_GPU = 101, \ - PERCENT_GPU = 102, \ + GPU_TIME = 101, \ + GPU_PERCENT = 102, \ \ DUMMY_BUMP_FIELD = CWD, \ // End of list diff --git a/linux/LinuxProcess.c b/linux/LinuxProcess.c index 93a841a65..0e5c49d51 100644 --- a/linux/LinuxProcess.c +++ b/linux/LinuxProcess.c @@ -110,8 +110,8 @@ const ProcessFieldData Process_fields[LAST_PROCESSFIELD] = { #ifdef SCHEDULER_SUPPORT [SCHEDULERPOLICY] = { .name = "SCHEDULERPOLICY", .title = "SCHED ", .description = "Current scheduling policy of the process", .flags = PROCESS_FLAG_SCHEDPOL, }, #endif - [TIME_GPU] = { .name = "GPU_TIME", .title = "GPU TIME", .description = "Total GPU time", .flags = PROCESS_FLAG_LINUX_GPU, .defaultSortDesc = true, }, - [PERCENT_GPU] = { .name = "GPU_PERCENT", .title = " GPU% ", .description = "Percentage of the GPU time the process used in the last sampling", .flags = PROCESS_FLAG_LINUX_GPU, .defaultSortDesc = true, }, + [GPU_TIME] = { .name = "GPU_TIME", .title = "GPU TIME", .description = "Total GPU time", .flags = PROCESS_FLAG_LINUX_GPU, .defaultSortDesc = true, }, + [GPU_PERCENT] = { .name = "GPU_PERCENT", .title = " GPU% ", .description = "Percentage of the GPU time the process used in the last sampling", .flags = PROCESS_FLAG_LINUX_GPU, .defaultSortDesc = true, }, }; Process* LinuxProcess_new(const Machine* host) { @@ -245,8 +245,8 @@ static void LinuxProcess_rowWriteField(const Row* super, RichString* str, Proces switch (field) { case CMINFLT: Row_printCount(str, lp->cminflt, coloring); return; case CMAJFLT: Row_printCount(str, lp->cmajflt, coloring); return; - case PERCENT_GPU: Row_printPercentage(lp->gpu_percent, buffer, n, 5, &attr); break; - case TIME_GPU: Row_printNanoseconds(str, lp->gpu_time, coloring); return; + case GPU_PERCENT: Row_printPercentage(lp->gpu_percent, buffer, n, 5, &attr); break; + case GPU_TIME: Row_printNanoseconds(str, lp->gpu_time, coloring); return; case M_DRS: Row_printBytes(str, lp->m_drs * lhost->pageSize, coloring); return; case M_LRS: if (lp->m_lrs) { @@ -455,14 +455,14 @@ static int LinuxProcess_compareByKey(const Process* v1, const Process* v2, Proce return SPACESHIP_NUMBER(p1->autogroup_id, p2->autogroup_id); case AUTOGROUP_NICE: return SPACESHIP_NUMBER(p1->autogroup_nice, p2->autogroup_nice); - case PERCENT_GPU: { + case GPU_PERCENT: { int r = compareRealNumbers(p1->gpu_percent, p2->gpu_percent); if (r) return r; return SPACESHIP_NUMBER(p1->gpu_time, p2->gpu_time); } - case TIME_GPU: + case GPU_TIME: return SPACESHIP_NUMBER(p1->gpu_time, p2->gpu_time); case ISCONTAINER: return SPACESHIP_NUMBER(v1->isRunningInContainer, v2->isRunningInContainer); From 6055adbfc80452b9e8c526fb4ca2165606be88b2 Mon Sep 17 00:00:00 2001 From: shatyuka Date: Sun, 11 Jan 2026 14:56:20 +0800 Subject: [PATCH 6/6] Fix accumulation of GPU time --- darwin/DarwinProcess.h | 4 ++-- darwin/DarwinProcessTable.c | 8 ++++++-- darwin/Platform.c | 20 +++++--------------- 3 files changed, 13 insertions(+), 19 deletions(-) diff --git a/darwin/DarwinProcess.h b/darwin/DarwinProcess.h index e7cc2b858..1197c3156 100644 --- a/darwin/DarwinProcess.h +++ b/darwin/DarwinProcess.h @@ -26,10 +26,10 @@ typedef struct DarwinProcess_ { /* Total GPU time used in nano seconds */ uint64_t gpu_time; + /* Total GPU time used in nano seconds in the last scan */ + uint64_t gpu_time_last; /* GPU utilization in percent */ float gpu_percent; - /* Got GPU time info from last scan */ - bool gpu_time_updated; } DarwinProcess; extern const ProcessClass DarwinProcess_class; diff --git a/darwin/DarwinProcessTable.c b/darwin/DarwinProcessTable.c index f85b50317..58a49cb59 100644 --- a/darwin/DarwinProcessTable.c +++ b/darwin/DarwinProcessTable.c @@ -124,8 +124,12 @@ void ProcessTable_goThroughEntries(ProcessTable* super) { DarwinProcess_scanThreads(proc, dpt); } - // Reset GPU time updated flag - proc->gpu_time_updated = false; + // Reset GPU time/percent + if (ss->flags & PROCESS_FLAG_GPU) { + proc->gpu_time_last = proc->gpu_time; + proc->gpu_time = 0; + proc->gpu_percent = 0.0F; + } super->totalTasks += 1; diff --git a/darwin/Platform.c b/darwin/Platform.c index 8bb78eacc..b50823a54 100644 --- a/darwin/Platform.c +++ b/darwin/Platform.c @@ -866,28 +866,18 @@ void Platform_setGPUProcesses(DarwinProcessTable* dpt) { goto cleanup; } - if (gpuTime > 0) { - uint64_t gputimeDelta = saturatingSub(gpuTime, dp->gpu_time); + dp->gpu_time += gpuTime; + + // Only calculate GPU percent if we have a previous sample, and the GPU time has increased + if (dp->gpu_time > dp->gpu_time_last && dp->gpu_time_last > 0) { + uint64_t gputimeDelta = saturatingSub(dp->gpu_time, dp->gpu_time_last); uint64_t monotonicTimeDelta = host->monotonicMs - host->prevMonotonicMs; dp->gpu_percent = 100.0F * gputimeDelta / (1000 * 1000) / monotonicTimeDelta; - dp->gpu_time = gpuTime; - dp->gpu_time_updated = true; } cleanup: IOObjectRelease(entry); } - // Clear GPU time and percent for processes not found in the GPU process list - const Vector* rows = dpt->super.super.rows; - int table_size = Vector_size(rows); - for (int i = 0; i < table_size; ++i) { - DarwinProcess* dp = (DarwinProcess*) Vector_get(rows, i); - if (!dp->gpu_time_updated) { - dp->gpu_time = 0; - dp->gpu_percent = 0.0F; - } - } - IOObjectRelease(iterator); }