From f8c5bdd86478a852c8d3967630dd81fdd70fa030 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20G=C3=B6ttsche?= Date: Tue, 29 Aug 2023 13:03:31 +0200 Subject: Linux: add GPU meter and process column Based on the DRM client usage stats[1] add statistics for GPU time spend and percentage utilization. [1]: https://www.kernel.org/doc/html/latest/gpu/drm-usage-stats.html --- CRT.c | 30 ++++++ CRT.h | 5 + Makefile.am | 4 + linux/GPU.c | 241 ++++++++++++++++++++++++++++++++++++++++++++++ linux/GPU.h | 17 ++++ linux/GPUMeter.c | 179 ++++++++++++++++++++++++++++++++++ linux/GPUMeter.h | 19 ++++ linux/LinuxMachine.c | 10 ++ linux/LinuxMachine.h | 9 ++ linux/LinuxProcess.c | 13 +++ linux/LinuxProcess.h | 6 ++ linux/LinuxProcessTable.c | 25 ++++- linux/Platform.c | 2 + linux/ProcessField.h | 2 + 14 files changed, 560 insertions(+), 2 deletions(-) create mode 100644 linux/GPU.c create mode 100644 linux/GPU.h create mode 100644 linux/GPUMeter.c create mode 100644 linux/GPUMeter.h diff --git a/CRT.c b/CRT.c index 7912b203..2db8ad69 100644 --- a/CRT.c +++ b/CRT.c @@ -199,6 +199,11 @@ static int CRT_colorSchemes[LAST_COLORSCHEME][LAST_COLORELEMENT] = { [CPU_SOFTIRQ] = ColorPair(Magenta, Black), [CPU_STEAL] = ColorPair(Cyan, Black), [CPU_GUEST] = ColorPair(Cyan, Black), + [GPU_ENGINE_1] = ColorPair(Green, Black), + [GPU_ENGINE_2] = ColorPair(Yellow, Black), + [GPU_ENGINE_3] = ColorPair(Red, Black), + [GPU_ENGINE_4] = A_BOLD | ColorPair(Blue, Black), + [GPU_RESIDUE] = ColorPair(Magenta, Black), [PANEL_EDIT] = ColorPair(White, Blue), [SCREENS_OTH_BORDER] = ColorPair(Blue, Blue), [SCREENS_OTH_TEXT] = ColorPair(Black, Blue), @@ -312,6 +317,11 @@ static int CRT_colorSchemes[LAST_COLORSCHEME][LAST_COLORELEMENT] = { [CPU_SOFTIRQ] = A_BOLD, [CPU_STEAL] = A_DIM, [CPU_GUEST] = A_DIM, + [GPU_ENGINE_1] = A_BOLD, + [GPU_ENGINE_2] = A_NORMAL, + [GPU_ENGINE_3] = A_REVERSE | A_BOLD, + [GPU_ENGINE_4] = A_REVERSE, + [GPU_RESIDUE] = A_BOLD, [PANEL_EDIT] = A_BOLD, [SCREENS_OTH_BORDER] = A_DIM, [SCREENS_OTH_TEXT] = A_DIM, @@ -425,6 +435,11 @@ static int CRT_colorSchemes[LAST_COLORSCHEME][LAST_COLORELEMENT] = { [CPU_SOFTIRQ] = ColorPair(Blue, White), [CPU_STEAL] = ColorPair(Cyan, White), [CPU_GUEST] = ColorPair(Cyan, White), + [GPU_ENGINE_1] = ColorPair(Green, White), + [GPU_ENGINE_2] = ColorPair(Yellow, White), + [GPU_ENGINE_3] = ColorPair(Red, White), + [GPU_ENGINE_4] = ColorPair(Blue, White), + [GPU_RESIDUE] = ColorPair(Magenta, White), [PANEL_EDIT] = ColorPair(White, Blue), [SCREENS_OTH_BORDER] = A_BOLD | ColorPair(Black, White), [SCREENS_OTH_TEXT] = A_BOLD | ColorPair(Black, White), @@ -538,6 +553,11 @@ static int CRT_colorSchemes[LAST_COLORSCHEME][LAST_COLORELEMENT] = { [CPU_SOFTIRQ] = ColorPair(Blue, Black), [CPU_STEAL] = ColorPair(Black, Black), [CPU_GUEST] = ColorPair(Black, Black), + [GPU_ENGINE_1] = ColorPair(Green, Black), + [GPU_ENGINE_2] = ColorPair(Yellow, Black), + [GPU_ENGINE_3] = ColorPair(Red, Black), + [GPU_ENGINE_4] = ColorPair(Blue, Black), + [GPU_RESIDUE] = ColorPair(Magenta, Black), [PANEL_EDIT] = ColorPair(White, Blue), [SCREENS_OTH_BORDER] = ColorPair(Blue, Black), [SCREENS_OTH_TEXT] = ColorPair(Blue, Black), @@ -651,6 +671,11 @@ static int CRT_colorSchemes[LAST_COLORSCHEME][LAST_COLORELEMENT] = { [CPU_SOFTIRQ] = ColorPair(Black, Blue), [CPU_STEAL] = ColorPair(White, Blue), [CPU_GUEST] = ColorPair(White, Blue), + [GPU_ENGINE_1] = A_BOLD | ColorPair(Green, Blue), + [GPU_ENGINE_2] = A_BOLD | ColorPair(Yellow, Blue), + [GPU_ENGINE_3] = A_BOLD | ColorPair(Red, Blue), + [GPU_ENGINE_4] = A_BOLD | ColorPair(White, Blue), + [GPU_RESIDUE] = A_BOLD | ColorPair(Magenta, Blue), [PANEL_EDIT] = ColorPair(White, Blue), [SCREENS_OTH_BORDER] = A_BOLD | ColorPair(Yellow, Blue), [SCREENS_OTH_TEXT] = ColorPair(Cyan, Blue), @@ -762,6 +787,11 @@ static int CRT_colorSchemes[LAST_COLORSCHEME][LAST_COLORELEMENT] = { [CPU_SOFTIRQ] = ColorPair(Blue, Black), [CPU_STEAL] = ColorPair(Cyan, Black), [CPU_GUEST] = ColorPair(Cyan, Black), + [GPU_ENGINE_1] = ColorPair(Green, Black), + [GPU_ENGINE_2] = ColorPair(Yellow, Black), + [GPU_ENGINE_3] = ColorPair(Red, Black), + [GPU_ENGINE_4] = ColorPair(Blue, Black), + [GPU_RESIDUE] = ColorPair(Magenta, Black), [PANEL_EDIT] = ColorPair(White, Cyan), [SCREENS_OTH_BORDER] = ColorPair(White, Black), [SCREENS_OTH_TEXT] = ColorPair(Cyan, Black), diff --git a/CRT.h b/CRT.h index 8740e096..eb3df1d7 100644 --- a/CRT.h +++ b/CRT.h @@ -121,6 +121,11 @@ typedef enum ColorElements_ { CPU_SOFTIRQ, CPU_STEAL, CPU_GUEST, + GPU_ENGINE_1, + GPU_ENGINE_2, + GPU_ENGINE_3, + GPU_ENGINE_4, + GPU_RESIDUE, PANEL_EDIT, SCREENS_OTH_BORDER, SCREENS_OTH_TEXT, diff --git a/Makefile.am b/Makefile.am index ed92afac..0d95e370 100644 --- a/Makefile.am +++ b/Makefile.am @@ -172,6 +172,8 @@ linux_platform_headers = \ generic/hostname.h \ generic/uname.h \ linux/CGroupUtils.h \ + linux/GPU.h \ + linux/GPUMeter.h \ linux/HugePageMeter.h \ linux/IOPriority.h \ linux/IOPriorityPanel.h \ @@ -196,6 +198,8 @@ linux_platform_sources = \ generic/hostname.c \ generic/uname.c \ linux/CGroupUtils.c \ + linux/GPU.c \ + linux/GPUMeter.c \ linux/HugePageMeter.c \ linux/IOPriorityPanel.c \ linux/LibSensors.c \ diff --git a/linux/GPU.c b/linux/GPU.c new file mode 100644 index 00000000..7b6ef886 --- /dev/null +++ b/linux/GPU.c @@ -0,0 +1,241 @@ +/* +htop - GPU.c +(C) 2023 htop dev team +Released under the GNU GPLv2+, see the COPYING file +in the source distribution for its full text. +*/ + +#include "config.h" // IWYU pragma: keep + +#include "linux/GPU.h" + +#include +#include +#include +#include +#include + +#include "XUtils.h" + +#include "linux/LinuxMachine.h" + + +typedef unsigned long long int ClientID; +#define INVALID_CLIENT_ID ((ClientID)-1) + + +typedef struct ClientInfo_ { + char* pdev; + ClientID id; + struct ClientInfo_* next; +} ClientInfo; + +enum section_state { + SECST_UNKNOWN, + SECST_DUPLICATE, + SECST_NEW, +}; + +static bool is_duplicate_client(const ClientInfo* parsed, ClientID id, const char* pdev) { + for (; parsed; parsed = parsed->next) { + if (id == parsed->id && String_eq_nullable(pdev, parsed->pdev)) { + return true; + } + } + + return false; +} + +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; + GPUEngineData** engineData = &lhost->gpuEngineData; + + while (*engineData) { + if (strncmp((*engineData)->key, engine, engine_len) == 0 && (*engineData)->key[engine_len] == '\0') + break; + + engineData = &((*engineData)->next); + } + + if (!*engineData) { + GPUEngineData* newData = xMalloc(sizeof(*newData)); + *newData = (GPUEngineData) { + .prevTime = 0, + .curTime = 0, + .key = xStrndup(engine, engine_len), + .next = NULL, + }; + + *engineData = newData; + } + + (*engineData)->curTime += time; + lhost->curGpuTime += time; +} + +/* + * Documentation reference: + * https://www.kernel.org/doc/html/latest/gpu/drm-usage-stats.html + */ +void GPU_readProcessData(LinuxProcessTable* lpt, LinuxProcess* lp, openat_arg_t procFd) { + int fdinfoFd = -1; + DIR* fdinfoDir = NULL; + ClientInfo* parsed_ids = NULL; + unsigned long long int new_gpu_time = 0; + + fdinfoFd = Compat_openat(procFd, "fdinfo", O_RDONLY | O_NOFOLLOW | O_DIRECTORY | O_CLOEXEC); + if (fdinfoFd == -1) + goto out; + + fdinfoDir = fdopendir(fdinfoFd); + if (!fdinfoDir) + goto out; + fdinfoFd = -1; + +#ifndef HAVE_OPENAT + char fdinfoPathBuf[32]; + xSnprintf(fdinfoPathBuf, sizeof(fdinfoPathBuf), PROCDIR "/%u/fdinfo", Process_getPid(&lp->super)); +#endif + + while (true) { + char* pdev = NULL; + ClientID client_id = INVALID_CLIENT_ID; + enum section_state sstate = SECST_UNKNOWN; + + const struct dirent* entry = readdir(fdinfoDir); + if (!entry) + break; + const char* ename = entry->d_name; + + if (String_eq(ename, ".") || + String_eq(ename, "..") || + String_eq(ename, "0") || + String_eq(ename, "1") || + String_eq(ename, "2")) + continue; + + char buffer[4096]; +#ifdef HAVE_OPENAT + ssize_t ret = xReadfileat(dirfd(fdinfoDir), ename, buffer, sizeof(buffer)); +#else + ssize_t ret = xReadfileat(fdinfoPathBuf, ename, buffer, sizeof(buffer)); +#endif + /* eventfd information can be huge */ + if (ret <= 0 || (size_t)ret >= sizeof(buffer) - 1) + continue; + + char* buf = buffer; + const char* line; + while ((line = strsep(&buf, "\n")) != NULL) { + if (!String_startsWith(line, "drm-")) + continue; + line += strlen("drm-"); + + if (line[0] == 'c' && String_startsWith(line, "client-id:")) { + if (sstate == SECST_NEW) { + assert(client_id != INVALID_CLIENT_ID); + + ClientInfo* new = xMalloc(sizeof(*new)); + *new = (ClientInfo) { + .id = client_id, + .pdev = pdev, + .next = parsed_ids, + }; + pdev = NULL; + + parsed_ids = new; + } + + sstate = SECST_UNKNOWN; + + char *endptr; + errno = 0; + client_id = strtoull(line + strlen("client-id:"), &endptr, 10); + if (errno || *endptr != '\0') + client_id = INVALID_CLIENT_ID; + } else if (line[0] == 'p' && String_startsWith(line, "pdev:")) { + const char* p = line + strlen("pdev:"); + + while (isspace((unsigned char)*p)) + p++; + + assert(!pdev || String_eq(pdev, p)); + if (!pdev) + pdev = xStrdup(p); + } else if (line[0] == 'e' && String_startsWith(line, "engine-")) { + if (sstate == SECST_DUPLICATE) + continue; + + const char* engineStart = line + strlen("engine-"); + + if (String_startsWith(engineStart, "capacity-")) + continue; + + const char* delim = strchr(line, ':'); + + char* endptr; + errno = 0; + unsigned long long int value = strtoull(delim + 1, &endptr, 10); + if (errno == 0 && String_startsWith(endptr, " ns")) { + 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_time += value; + update_machine_gpu(lpt, value, engineStart, delim - engineStart); + } + } + } + } /* finished parsing lines */ + + if (sstate == SECST_NEW) { + assert(client_id != INVALID_CLIENT_ID); + + ClientInfo* new = xMalloc(sizeof(*new)); + *new = (ClientInfo) { + .id = client_id, + .pdev = pdev, + .next = parsed_ids, + }; + pdev = NULL; + + parsed_ids = new; + } + + free(pdev); + } /* finished parsing fdinfo entries */ + + if (new_gpu_time > 0) { + const Machine* host = lp->super.super.host; + unsigned long long int gputimeDelta; + uint64_t monotonicTimeDelta; + + Row_updateFieldWidth(GPU_TIME, ceil(log10(new_gpu_time))); + + gputimeDelta = saturatingSub(new_gpu_time, lp->gpu_time); + monotonicTimeDelta = host->monotonicMs - host->prevMonotonicMs; + lp->gpu_percent = 100.0f * gputimeDelta / (1000 * 1000) / monotonicTimeDelta; + } else + lp->gpu_percent = 0.0f; + +out: + + lp->gpu_time = new_gpu_time; + + while (parsed_ids) { + ClientInfo* next = parsed_ids->next; + free(parsed_ids->pdev); + free(parsed_ids); + parsed_ids = next; + } + + if (fdinfoDir) + closedir(fdinfoDir); + if (fdinfoFd != -1) + close(fdinfoFd); +} diff --git a/linux/GPU.h b/linux/GPU.h new file mode 100644 index 00000000..b502d147 --- /dev/null +++ b/linux/GPU.h @@ -0,0 +1,17 @@ +#ifndef HEADER_GPU +#define HEADER_GPU +/* +htop - GPU.h +(C) 2023 htop dev team +Released under the GNU GPLv2+, see the COPYING file +in the source distribution for its full text. +*/ + +#include "Compat.h" +#include "linux/LinuxProcess.h" +#include "linux/LinuxProcessTable.h" + + +void GPU_readProcessData(LinuxProcessTable* lpl, LinuxProcess* lp, openat_arg_t procFd); + +#endif /* HEADER_GPU */ diff --git a/linux/GPUMeter.c b/linux/GPUMeter.c new file mode 100644 index 00000000..fbd32ea8 --- /dev/null +++ b/linux/GPUMeter.c @@ -0,0 +1,179 @@ +/* +htop - GPUMeter.c +(C) 2023 htop dev team +Released under the GNU GPLv2+, see the COPYING file +in the source distribution for its full text. +*/ + +#include "config.h" // IWYU pragma: keep + +#include "linux/GPUMeter.h" + +#include "CRT.h" +#include "RichString.h" +#include "linux/LinuxMachine.h" + + +static size_t activeMeters; + +bool GPUMeter_active(void) { + return activeMeters > 0; +} + +struct EngineData { + const char* key; /* owned by LinuxMachine */ + unsigned long long int timeDiff; +}; + +static struct EngineData GPUMeter_engineData[4]; +static unsigned long long int prevResidueTime, curResidueTime; +static double totalUsage; +static unsigned long long int totalGPUTimeDiff; + +static const int GPUMeter_attributes[] = { + GPU_ENGINE_1, + GPU_ENGINE_2, + GPU_ENGINE_3, + GPU_ENGINE_4, + GPU_RESIDUE, +}; + +static int humanTimeUnit(char* buffer, size_t size, unsigned long long int value) { + + if (value < 1000) + return xSnprintf(buffer, size, "%3lluns", value); + + if (value < 10000) + return xSnprintf(buffer, size, "%1llu.%1lluus", value / 1000, (value % 1000) / 100); + + value /= 1000; + + if (value < 1000) + return xSnprintf(buffer, size, "%3lluus", value); + + if (value < 10000) + return xSnprintf(buffer, size, "%1llu.%1llums", value / 1000, (value % 1000) / 100); + + value /= 1000; + + if (value < 1000) + return xSnprintf(buffer, size, "%3llums", value); + + if (value < 10000) + return xSnprintf(buffer, size, "%1llu.%1llus", value / 1000, (value % 1000) / 100); + + value /= 1000; + + if (value < 600) + return xSnprintf(buffer, size, "%3llus", value); + + value /= 60; + + if (value < 600) + return xSnprintf(buffer, size, "%3llum", value); + + value /= 60; + + if (value < 96) + return xSnprintf(buffer, size, "%3lluh", value); + + value /= 24; + + return xSnprintf(buffer, size, "%3llud", value); +} + +static void GPUMeter_updateValues(Meter* this) { + const Machine* host = this->host; + const LinuxMachine* lhost = (const LinuxMachine*) host; + const GPUEngineData* gpuEngineData; + char* buffer = this->txtBuffer; + size_t size = sizeof(this->txtBuffer); + int written; + unsigned int i; + uint64_t monotonictimeDelta; + + assert(ARRAYSIZE(GPUMeter_engineData) + 1 == ARRAYSIZE(GPUMeter_attributes)); + + totalGPUTimeDiff = saturatingSub(lhost->curGpuTime, lhost->prevGpuTime); + monotonictimeDelta = host->monotonicMs - host->prevMonotonicMs; + + prevResidueTime = curResidueTime; + curResidueTime = lhost->curGpuTime; + + for (gpuEngineData = lhost->gpuEngineData, i = 0; gpuEngineData && i < ARRAYSIZE(GPUMeter_engineData); gpuEngineData = gpuEngineData->next, i++) { + GPUMeter_engineData[i].key = gpuEngineData->key; + GPUMeter_engineData[i].timeDiff = saturatingSub(gpuEngineData->curTime, gpuEngineData->prevTime); + + curResidueTime = saturatingSub(curResidueTime, gpuEngineData->curTime); + + this->values[i] = 100.0 * GPUMeter_engineData[i].timeDiff / (1000 * 1000) / monotonictimeDelta; + } + + this->values[ARRAYSIZE(GPUMeter_engineData)] = 100.0 * saturatingSub(curResidueTime, prevResidueTime) / (1000 * 1000) / monotonictimeDelta; + + totalUsage = 100.0 * totalGPUTimeDiff / (1000 * 1000) / monotonictimeDelta; + written = snprintf(buffer, size, "%.1f", totalUsage); + METER_BUFFER_CHECK(buffer, size, written); + + METER_BUFFER_APPEND_CHR(buffer, size, '%'); +} + +static void GPUMeter_display(const Object* cast, RichString* out) { + char buffer[50]; + int written; + const Meter* this = (const Meter*)cast; + unsigned int i; + + RichString_writeAscii(out, CRT_colors[METER_TEXT], ":"); + written = xSnprintf(buffer, sizeof(buffer), "%4.1f", totalUsage); + RichString_appendnAscii(out, CRT_colors[METER_VALUE], buffer, written); + RichString_appendnAscii(out, CRT_colors[METER_TEXT], "%(", 2); + written = humanTimeUnit(buffer, sizeof(buffer), totalGPUTimeDiff); + RichString_appendnAscii(out, CRT_colors[METER_VALUE], buffer, written); + RichString_appendnAscii(out, CRT_colors[METER_TEXT], ")", 1); + + for (i = 0; i < ARRAYSIZE(GPUMeter_engineData); i++) { + if (!GPUMeter_engineData[i].key) + break; + + RichString_appendnAscii(out, CRT_colors[METER_TEXT], " ", 1); + RichString_appendAscii(out, CRT_colors[METER_TEXT], GPUMeter_engineData[i].key); + RichString_appendnAscii(out, CRT_colors[METER_TEXT], ":", 1); + if (isNonnegative(this->values[i])) + written = xSnprintf(buffer, sizeof(buffer), "%4.1f", this->values[i]); + else + written = xSnprintf(buffer, sizeof(buffer), " N/A"); + RichString_appendnAscii(out, CRT_colors[METER_VALUE], buffer, written); + RichString_appendnAscii(out, CRT_colors[METER_TEXT], "%(", 2); + written = humanTimeUnit(buffer, sizeof(buffer), GPUMeter_engineData[i].timeDiff); + RichString_appendnAscii(out, CRT_colors[METER_VALUE], buffer, written); + RichString_appendnAscii(out, CRT_colors[METER_TEXT], ")", 1); + } +} + +static void GPUMeter_init(Meter* this ATTR_UNUSED) { + activeMeters++; +} + +static void GPUMeter_done(Meter* this ATTR_UNUSED) { + assert(activeMeters > 0); + activeMeters--; +} + +const MeterClass GPUMeter_class = { + .super = { + .extends = Class(Meter), + .delete = Meter_delete, + .display = GPUMeter_display, + }, + .init = GPUMeter_init, + .done = GPUMeter_done, + .updateValues = GPUMeter_updateValues, + .defaultMode = BAR_METERMODE, + .maxItems = ARRAYSIZE(GPUMeter_engineData) + 1, + .total = 100.0, + .attributes = GPUMeter_attributes, + .name = "GPU", + .uiName = "GPU usage", + .caption = "GPU" +}; diff --git a/linux/GPUMeter.h b/linux/GPUMeter.h new file mode 100644 index 00000000..a770ec77 --- /dev/null +++ b/linux/GPUMeter.h @@ -0,0 +1,19 @@ +#ifndef HEADER_GPUMeter +#define HEADER_GPUMeter +/* +htop - GPUMeter.h +(C) 2023 htop dev team +Released under the GNU GPLv2+, see the COPYING file +in the source distribution for its full text. +*/ + +#include + +#include "Meter.h" + + +extern const MeterClass GPUMeter_class; + +bool GPUMeter_active(void); + +#endif /* HEADER_GPUMeter */ diff --git a/linux/LinuxMachine.c b/linux/LinuxMachine.c index ff2b605a..626ca97f 100644 --- a/linux/LinuxMachine.c +++ b/linux/LinuxMachine.c @@ -681,7 +681,17 @@ Machine* Machine_new(UsersTable* usersTable, uid_t userId) { void Machine_delete(Machine* super) { LinuxMachine* this = (LinuxMachine*) super; + GPUEngineData* gpuEngineData = this->gpuEngineData; + Machine_done(super); + + while (gpuEngineData) { + GPUEngineData* next = gpuEngineData->next; + free(gpuEngineData->key); + free(gpuEngineData); + gpuEngineData = next; + } + free(this->cpuData); free(this); } diff --git a/linux/LinuxMachine.h b/linux/LinuxMachine.h index 309b4850..398d1d77 100644 --- a/linux/LinuxMachine.h +++ b/linux/LinuxMachine.h @@ -53,6 +53,12 @@ typedef struct CPUData_ { bool online; } CPUData; +typedef struct GPUEngineData_ { + unsigned long long int prevTime, curTime; /* absolute GPU time in nano seconds */ + char* key; /* engine name */ + struct GPUEngineData_* next; +} GPUEngineData; + typedef struct LinuxMachine_ { Machine super; @@ -73,6 +79,9 @@ typedef struct LinuxMachine_ { memory_t availableMem; + unsigned long long int prevGpuTime, curGpuTime; /* total absolute GPU time in nano seconds */ + GPUEngineData* gpuEngineData; + ZfsArcStats zfs; ZramStats zram; ZswapStats zswap; diff --git a/linux/LinuxProcess.c b/linux/LinuxProcess.c index 2b42d8c8..5d145fff 100644 --- a/linux/LinuxProcess.c +++ b/linux/LinuxProcess.c @@ -109,6 +109,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 in nano seconds", .flags = PROCESS_FLAG_LINUX_GPU, .defaultSortDesc = true, .autoWidth = true, .autoTitleRightAlign = 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) { @@ -239,6 +241,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_printTime(str, lp->gpu_time / 1000 / 1000 / 10 /* nano to centi seconds */, coloring); return; case M_DRS: Row_printBytes(str, lp->m_drs * lhost->pageSize, coloring); return; case M_LRS: if (lp->m_lrs) { @@ -422,6 +426,15 @@ 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: { + 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/linux/LinuxProcess.h b/linux/LinuxProcess.h index 388e50d3..cc515f5f 100644 --- a/linux/LinuxProcess.h +++ b/linux/LinuxProcess.h @@ -29,6 +29,7 @@ in the source distribution for its full text. #define PROCESS_FLAG_LINUX_LRS_FIX 0x00010000 #define PROCESS_FLAG_LINUX_DELAYACCT 0x00040000 #define PROCESS_FLAG_LINUX_AUTOGROUP 0x00080000 +#define PROCESS_FLAG_LINUX_GPU 0x00100000 typedef struct LinuxProcess_ { Process super; @@ -106,6 +107,11 @@ typedef struct LinuxProcess_ { char* secattr; unsigned long long int last_mlrs_calctime; + /* Total GPU time used in nano seconds */ + unsigned long long int gpu_time; + /* GPU utilization in percent */ + float gpu_percent; + /* Autogroup scheduling (CFS) information */ long int autogroup_id; int autogroup_nice; diff --git a/linux/LinuxProcessTable.c b/linux/LinuxProcessTable.c index 82cfdab3..84308423 100644 --- a/linux/LinuxProcessTable.c +++ b/linux/LinuxProcessTable.c @@ -49,6 +49,8 @@ in the source distribution for its full text. #include "UsersTable.h" #include "XUtils.h" #include "linux/CGroupUtils.h" +#include "linux/GPU.h" +#include "linux/GPUMeter.h" #include "linux/LinuxMachine.h" #include "linux/LinuxProcess.h" #include "linux/Platform.h" // needed for GNU/hurd to get PATH_MAX // IWYU pragma: keep @@ -1617,6 +1619,14 @@ static bool LinuxProcessTable_recurseProcTree(LinuxProcessTable* this, openat_ar } #endif + if (ss->flags & PROCESS_FLAG_LINUX_GPU || GPUMeter_active()) { + if (parent) { + lp->gpu_time = ((const LinuxProcess*)parent)->gpu_time; + } else { + GPU_readProcessData(this, lp, procFd); + } + } + if (!proc->cmdline && statCommand[0] && (proc->state == ZOMBIE || Process_isKernelThread(proc) || settings->showThreadNames)) { Process_updateCmdline(proc, statCommand, 0, strlen(statCommand)); @@ -1674,9 +1684,9 @@ errorReadingProcess: void ProcessTable_goThroughEntries(ProcessTable* super) { LinuxProcessTable* this = (LinuxProcessTable*) super; - const Machine* host = super->super.host; + Machine* host = super->super.host; const Settings* settings = host->settings; - const LinuxMachine* lhost = (const LinuxMachine*) host; + LinuxMachine* lhost = (LinuxMachine*) host; if (settings->ss->flags & PROCESS_FLAG_LINUX_AUTOGROUP) { // Refer to sched(7) 'autogroup feature' section @@ -1688,6 +1698,17 @@ void ProcessTable_goThroughEntries(ProcessTable* super) { this->haveAutogroup = false; } + /* Shift GPU values */ + { + lhost->prevGpuTime = lhost->curGpuTime; + lhost->curGpuTime = 0; + + for (GPUEngineData* engine = lhost->gpuEngineData; engine; engine = engine->next) { + engine->prevTime = engine->curTime; + engine->curTime = 0; + } + } + /* PROCDIR is an absolute path */ assert(PROCDIR[0] == '/'); #ifdef HAVE_OPENAT diff --git a/linux/Platform.c b/linux/Platform.c index af81a694..7708a573 100644 --- a/linux/Platform.c +++ b/linux/Platform.c @@ -51,6 +51,7 @@ in the source distribution for its full text. #include "TasksMeter.h" #include "UptimeMeter.h" #include "XUtils.h" +#include "linux/GPUMeter.h" #include "linux/IOPriority.h" #include "linux/IOPriorityPanel.h" #include "linux/LinuxMachine.h" @@ -252,6 +253,7 @@ const MeterClass* const Platform_meterTypes[] = { &SystemdMeter_class, &SystemdUserMeter_class, &FileDescriptorMeter_class, + &GPUMeter_class, NULL }; diff --git a/linux/ProcessField.h b/linux/ProcessField.h index 581a982e..735423b9 100644 --- a/linux/ProcessField.h +++ b/linux/ProcessField.h @@ -48,6 +48,8 @@ in the source distribution for its full text. CCGROUP = 129, \ CONTAINER = 130, \ M_PRIV = 131, \ + GPU_TIME = 132, \ + GPU_PERCENT = 133, \ // End of list -- cgit v1.2.3