From 08d876ba2a6c71eb3e78b09e74904471be2eb36c Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 22 Sep 2026 16:07:01 +0000 Subject: [PATCH 1/2] linux/GPU: add per-process GPU memory column (GPU_MEMORY) btop shows per-process GPU memory usage, which htop lacked. htop already tracks per-process GPU engine time/utilization (GPU_TIME, GPU_PERCENT) by parsing the generic DRM fdinfo stats (drm-engine-*), so extend the same fdinfo parser to also sum drm-resident-/drm-memory- (the deprecated amdgpu-only alias for resident) values, deduplicated per client the same way engine time already is, and expose the result as a new GPU_MEMORY column. Assisted-by: Claude Sonnet 5 Co-Authored-By: Claude Sonnet 5 Claude-Session: https://claude.ai/code/session_016Tgh778fwKS3MFYmr7wd3M --- linux/GPU.c | 41 +++++++++++++++++++++++++++++++++++++++++ linux/LinuxProcess.c | 4 ++++ linux/LinuxProcess.h | 2 ++ linux/ProcessField.h | 1 + 4 files changed, 48 insertions(+) diff --git a/linux/GPU.c b/linux/GPU.c index ce9dee891..042bf9ec1 100644 --- a/linux/GPU.c +++ b/linux/GPU.c @@ -83,6 +83,7 @@ void GPU_readProcessData(LinuxProcessTable* lpt, LinuxProcess* lp, openat_arg_t DIR* fdinfoDir = NULL; ClientInfo* parsed_ids = NULL; unsigned long long int new_gpu_time = 0; + unsigned long long int new_gpu_memory = 0; /* check only if active in last check or last scan was more than 5s ago */ if (lp->gpu_activityMs != 0 && host->monotonicMs - lp->gpu_activityMs < 5000) { @@ -193,6 +194,45 @@ void GPU_readProcessData(LinuxProcessTable* lpt, LinuxProcess* lp, openat_arg_t update_machine_gpu(lpt, value, engineStart, delim - engineStart); } } + } else if ((line[0] == 'm' && String_startsWith(line, "memory-")) || + (line[0] == 'r' && String_startsWith(line, "resident-"))) { + /* + * "drm-resident-" is the current key for backing-store size; + * "drm-memory-" is its deprecated alias (amdgpu only). A given + * driver emits only one of the two per region, so summing both is safe. + */ + if (sstate == SECST_DUPLICATE) + continue; + + const char* delim = strchr(line, ':'); + if (!delim) + continue; + + char* endptr; + errno = 0; + unsigned long long int value = strtoull(delim + 1, &endptr, 10); + while (*endptr == ' ') + endptr++; + + unsigned long long int bytesValue; + if (errno != 0) + continue; + else if (String_startsWith(endptr, "KiB")) + bytesValue = value * 1024ULL; + else if (String_startsWith(endptr, "MiB")) + bytesValue = value * 1024ULL * 1024ULL; + else + continue; + + if (sstate == SECST_UNKNOWN) { + if (client_id != INVALID_CLIENT_ID && !is_duplicate_client(parsed_ids, client_id, pdev)) + sstate = SECST_NEW; + else + sstate = SECST_DUPLICATE; + } + + if (sstate == SECST_NEW) + new_gpu_memory += bytesValue; } } /* finished parsing lines */ @@ -228,6 +268,7 @@ void GPU_readProcessData(LinuxProcessTable* lpt, LinuxProcess* lp, openat_arg_t out: lp->gpu_time = new_gpu_time; + lp->gpu_memory = new_gpu_memory; while (parsed_ids) { ClientInfo* next = parsed_ids->next; diff --git a/linux/LinuxProcess.c b/linux/LinuxProcess.c index 930ef1ff9..a440bca04 100644 --- a/linux/LinuxProcess.c +++ b/linux/LinuxProcess.c @@ -107,6 +107,7 @@ const ProcessFieldData Process_fields[LAST_PROCESSFIELD] = { #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, }, + [GPU_MEMORY] = { .name = "GPU_MEMORY", .title = "GPU_MEM ", .description = "GPU memory used by the process", .flags = PROCESS_FLAG_LINUX_GPU, .defaultSortDesc = true, }, }; Process* LinuxProcess_new(const Machine* host) { @@ -239,6 +240,7 @@ static void LinuxProcess_rowWriteField(const Row* super, RichString* str, Proces 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 GPU_MEMORY: Row_printBytes(str, lp->gpu_memory, coloring); return; case M_DRS: Row_printBytes(str, lp->m_drs * lhost->pageSize, coloring); return; case M_LRS: if (lp->m_lrs) { @@ -449,6 +451,8 @@ static int LinuxProcess_compareByKey(const Process* v1, const Process* v2, Proce } case GPU_TIME: return SPACESHIP_NUMBER(p1->gpu_time, p2->gpu_time); + case GPU_MEMORY: + return SPACESHIP_NUMBER(p1->gpu_memory, p2->gpu_memory); case ISCONTAINER: return SPACESHIP_NUMBER(v1->isRunningInContainer, v2->isRunningInContainer); default: diff --git a/linux/LinuxProcess.h b/linux/LinuxProcess.h index 0a25bc70b..3fbf92484 100644 --- a/linux/LinuxProcess.h +++ b/linux/LinuxProcess.h @@ -104,6 +104,8 @@ typedef struct LinuxProcess_ { unsigned long long int gpu_time; /* GPU utilization in percent */ float gpu_percent; + /* GPU memory used by the process, in bytes */ + unsigned long long int gpu_memory; /* Activity of GPU: 0 if active, otherwise time of last scan in milliseconds */ uint64_t gpu_activityMs; diff --git a/linux/ProcessField.h b/linux/ProcessField.h index 390a495c6..a5b9a0efb 100644 --- a/linux/ProcessField.h +++ b/linux/ProcessField.h @@ -49,6 +49,7 @@ in the source distribution for its full text. GPU_PERCENT = 133, \ ISCONTAINER = 134, \ M_EPSS = 135, \ + GPU_MEMORY = 136, \ // End of list From 7825cd52e18aa0dad1265f9cfa982d76cdba2ee1 Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 23 Sep 2026 12:18:21 +0000 Subject: [PATCH 2/2] linux/GPU: harden drm memory value parsing Address upstream review feedback on the GPU_MEMORY column (PR #2121): - CodeRabbit: a value above ULLONG_MAX / 1024 (KiB) or ULLONG_MAX / 1048576 (MiB) could wrap bytesValue during multiplication; check for overflow before multiplying instead. - CodeRabbit: String_startsWith(endptr, "KiB") accepted malformed suffixes like "KiBjunk"; require an exact unit match via String_eq() instead. - BenBE: the parser only recognized KiB/MiB and failed for GiB/TiB values, which drivers reporting large VRAM sizes can emit; add those units. The unit dispatch is pulled out into parse_drm_memory_value() so the overflow check and exact-match comparisons don't turn the call site into a multi-statement if/else-if chain. Assisted-by: Claude Sonnet 5 Co-Authored-By: Claude Sonnet 5 Claude-Session: https://claude.ai/code/session_016Tgh778fwKS3MFYmr7wd3M --- linux/GPU.c | 52 +++++++++++++++++++++++++++++++++++++++------------- 1 file changed, 39 insertions(+), 13 deletions(-) diff --git a/linux/GPU.c b/linux/GPU.c index 042bf9ec1..fcacf4a83 100644 --- a/linux/GPU.c +++ b/linux/GPU.c @@ -13,8 +13,10 @@ in the source distribution for its full text. #include #include #include +#include #include +#include "XUtils.h" #include "linux/Compat.h" #include "linux/LinuxMachine.h" @@ -45,6 +47,42 @@ static bool is_duplicate_client(const ClientInfo* parsed, ClientID id, const cha return false; } +/* + * Parses the value of a "drm-resident-" / "drm-memory-" fdinfo + * line (the text right after the ':') into a byte count. Rejects malformed or + * unrecognized unit suffixes and multiplications that would overflow. + */ +static bool parse_drm_memory_value(const char* text, unsigned long long int* outBytes) { + char* endptr; + errno = 0; + unsigned long long int value = strtoull(text, &endptr, 10); + if (errno != 0) + return false; + + while (*endptr == ' ') + endptr++; + + unsigned long long int multiplier; + if (*endptr == '\0') + multiplier = 1; + else if (String_eq(endptr, "KiB")) + multiplier = 1024ULL; + else if (String_eq(endptr, "MiB")) + multiplier = 1024ULL * 1024ULL; + else if (String_eq(endptr, "GiB")) + multiplier = 1024ULL * 1024ULL * 1024ULL; + else if (String_eq(endptr, "TiB")) + multiplier = 1024ULL * 1024ULL * 1024ULL * 1024ULL; + else + return false; + + if (multiplier > 1 && value > ULLONG_MAX / multiplier) + return false; + + *outBytes = value * multiplier; + return true; +} + static void update_machine_gpu(LinuxProcessTable* lpt, unsigned long long int time, const char* engine, size_t engine_len) { Machine* host = lpt->super.super.host; LinuxMachine* lhost = (LinuxMachine*) host; @@ -208,20 +246,8 @@ void GPU_readProcessData(LinuxProcessTable* lpt, LinuxProcess* lp, openat_arg_t if (!delim) continue; - char* endptr; - errno = 0; - unsigned long long int value = strtoull(delim + 1, &endptr, 10); - while (*endptr == ' ') - endptr++; - unsigned long long int bytesValue; - if (errno != 0) - continue; - else if (String_startsWith(endptr, "KiB")) - bytesValue = value * 1024ULL; - else if (String_startsWith(endptr, "MiB")) - bytesValue = value * 1024ULL * 1024ULL; - else + if (!parse_drm_memory_value(delim + 1, &bytesValue)) continue; if (sstate == SECST_UNKNOWN) {