summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorChristian Göttsche <cgzones@googlemail.com>2023-08-29 13:03:31 +0200
committercgzones <cgzones@googlemail.com>2024-03-27 19:49:23 +0100
commitf8c5bdd86478a852c8d3967630dd81fdd70fa030 (patch)
tree868c30988bd44ee8e8be487ad2dd22f23e3cc46b
parentec608672cea2b857a7b43832bfabae2bbe6c04da (diff)
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
-rw-r--r--CRT.c30
-rw-r--r--CRT.h5
-rw-r--r--Makefile.am4
-rw-r--r--linux/GPU.c241
-rw-r--r--linux/GPU.h17
-rw-r--r--linux/GPUMeter.c179
-rw-r--r--linux/GPUMeter.h19
-rw-r--r--linux/LinuxMachine.c10
-rw-r--r--linux/LinuxMachine.h9
-rw-r--r--linux/LinuxProcess.c13
-rw-r--r--linux/LinuxProcess.h6
-rw-r--r--linux/LinuxProcessTable.c25
-rw-r--r--linux/Platform.c2
-rw-r--r--linux/ProcessField.h2
14 files changed, 560 insertions, 2 deletions
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 <assert.h>
+#include <ctype.h>
+#include <dirent.h>
+#include <errno.h>
+#include <sys/types.h>
+
+#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 <stdbool.h>
+
+#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

© 2014-2024 Faster IT GmbH | imprint | privacy policy