diff --git a/darwin/DarwinProcess.c b/darwin/DarwinProcess.c index 9763c74c9..2eb739071 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, }, + [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) { @@ -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 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; @@ -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 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 GPU_TIME: + return SPACESHIP_NUMBER(p1->gpu_time, p2->gpu_time); default: return Process_compareByKey_Base(v1, v2, key); } diff --git a/darwin/DarwinProcess.h b/darwin/DarwinProcess.h index a5d66b490..1197c3156 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,13 @@ typedef struct DarwinProcess_ { uint64_t stime; bool taskAccess; bool translated; + + /* 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; } DarwinProcess; extern const ProcessClass DarwinProcess_class; diff --git a/darwin/DarwinProcessTable.c b/darwin/DarwinProcessTable.c index 9a2f6d5d0..58a49cb59 100644 --- a/darwin/DarwinProcessTable.c +++ b/darwin/DarwinProcessTable.c @@ -72,6 +72,8 @@ 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; @@ -122,6 +124,13 @@ void ProcessTable_goThroughEntries(ProcessTable* super) { DarwinProcess_scanThreads(proc, dpt); } + // 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; if (!preExisting) { @@ -129,5 +138,10 @@ void ProcessTable_goThroughEntries(ProcessTable* super) { } } + // 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 72d262590..b50823a54 100644 --- a/darwin/Platform.c +++ b/darwin/Platform.c @@ -798,3 +798,86 @@ static void Platform_getOSRelease(char* buffer, size_t bufferLen) { 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; + + if (!dhost->GPUService) + return; + + io_iterator_t iterator; + if (IORegistryEntryGetChildIterator(dhost->GPUService, kIOServicePlane, &iterator) != KERN_SUCCESS) + return; + + 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; + + int pid; + if (sscanf(buffer, "pid %d", &pid) != 1) + goto cleanup; + + uint64_t gpuTime = 0; + DarwinProcess* dp = (DarwinProcess*) ProcessTable_findProcess(&dpt->super, (pid_t) pid); + if (!dp) + goto cleanup; + + 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) + continue; + + uint64_t 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; + + uint64_t accumulatedGPUTime = 0; + CFNumberGetValue(accumulatedGPUTimeRef, kCFNumberLongLongType, &accumulatedGPUTime); + + gpuTime += accumulatedGPUTime; + + CFRelease(accumulatedGPUTimeRef); + } else { + goto cleanup; + } + + 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; + } + +cleanup: + IOObjectRelease(entry); + } + + IOObjectRelease(iterator); +} diff --git a/darwin/Platform.h b/darwin/Platform.h index bfe67e0a0..087799973 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); +void Platform_setGPUProcesses(DarwinProcessTable* dpt); + static inline const char* Platform_getFailedState(void) { return NULL; } diff --git a/darwin/ProcessField.h b/darwin/ProcessField.h index 05fbc3ba6..702001f58 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, \ + GPU_TIME = 101, \ + GPU_PERCENT = 102, \ \ DUMMY_BUMP_FIELD = CWD, \ // End of list diff --git a/linux/LinuxProcess.c b/linux/LinuxProcess.c index 3f917d870..0e5c49d51 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 - [GPU_TIME] = { .name = "GPU_TIME", .title = "GPU_TIME ", .description = "Total GPU time", .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, }, };