Skip to content

Commit

Permalink
Linux: add GPU meter and process column
Browse files Browse the repository at this point in the history
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
  • Loading branch information
cgzones committed Aug 29, 2023
1 parent 9e0d310 commit 798d3f3
Show file tree
Hide file tree
Showing 14 changed files with 593 additions and 2 deletions.
30 changes: 30 additions & 0 deletions CRT.c
Original file line number Diff line number Diff line change
Expand Up @@ -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),
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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),
Expand Down Expand Up @@ -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),
Expand Down Expand Up @@ -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),
Expand Down Expand Up @@ -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),
Expand Down
5 changes: 5 additions & 0 deletions CRT.h
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,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,
Expand Down
4 changes: 4 additions & 0 deletions Makefile.am
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,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 \
Expand All @@ -187,6 +189,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 \
Expand Down
272 changes: 272 additions & 0 deletions linux/GPU.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,272 @@
/*
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 "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"


#define INVALID_CLIENT_ID ((unsigned long long int)-1)


struct client_id {
char* pdev;
unsigned long long int id;
struct client_id* next;
};

enum section_state {
SECST_UNKNOWN,
SECST_DUPLICATE,
SECST_NEW,
};

static bool null_str_eq(const char* s1, const char* s2) {
if (s1 == NULL && s2 == NULL)
return true;

if (s1 && s2)
return String_eq(s1, s2);

return false;
}

static bool is_duplicate_client(const struct client_id* parsed, unsigned long long int id, const char* pdev) {
for (; parsed; parsed = parsed->next) {
if (id == parsed->id && null_str_eq(pdev, parsed->pdev)) {
return true;
}
}

return false;
}

static void update_system(LinuxProcessList* lpl, unsigned long long int time, char* engine) {
Machine* host = lpl->super.host;
LinuxMachine* lhost = (LinuxMachine*) host;
GPUEngineData** engineData = &lhost->gpuEngineData;

while (*engineData) {
if (String_eq((*engineData)->key, engine))
break;

engineData = &((*engineData)->next);
}

if (!*engineData) {
GPUEngineData* newData;

newData = xMalloc(sizeof(*newData));
*newData = (GPUEngineData) {
.prevTime = 0,
.curTime = 0,
.key = engine,
.next = NULL,
};

*engineData = newData;
} else
free(engine);

(*engineData)->curTime += time;
lhost->curGpuTime += time;
}

/*
* Documentation reference:
* https://www.kernel.org/doc/html/latest/gpu/drm-usage-stats.html
*/
void GPU_readProcessData(LinuxProcessList* lpl, LinuxProcess* lp, openat_arg_t procFd) {
int fdinfoFd = -1;
DIR* fdinfoDir = NULL;
struct client_id* 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), "/proc/%u/fdinfo", lp->super.pid);
#endif

while (true) {
const struct dirent* entry;
const char* ename;
FILE* fp;
char* pdev = NULL;
unsigned long long int client_id = INVALID_CLIENT_ID;
enum section_state sstate = SECST_UNKNOWN;
char buf[256];

entry = readdir(fdinfoDir);
if (!entry)
break;
ename = entry->d_name;

if (String_eq(ename, ".") ||
String_eq(ename, "..") ||
String_eq(ename, "0") ||
String_eq(ename, "1") ||
String_eq(ename, "2"))
continue;

#ifdef HAVE_OPENAT
fp = fopenat(dirfd(fdinfoDir), ename, "r");
#else
fp = fopenat(fdinfoPathBuf, ename, "r");
#endif
if (!fp)
continue;

while (fgets(buf, sizeof(buf), fp)) {
if (!String_startsWith(buf, "drm-"))
continue;

if (String_startsWith(buf, "drm-client-id:")) {
char *endptr;

if (sstate == SECST_NEW) {
struct client_id* new;

assert(client_id != INVALID_CLIENT_ID);

new = xMalloc(sizeof(*new));
*new = (struct client_id) {
.id = client_id,
.pdev = pdev,
.next = parsed_ids,
};
pdev = NULL;

parsed_ids = new;
}

sstate = SECST_UNKNOWN;

errno = 0;
client_id = strtoull(buf + strlen("drm-client-id:"), &endptr, 10);
if (errno || *endptr != '\n')
client_id = INVALID_CLIENT_ID;
} else if (String_startsWith(buf, "drm-pdev:")) {
const char* p;
char* q;

p = buf + strlen("drm-pdev:");

while (isspace((unsigned char)*p))
p++;

q = strchr(p, '\n');
if (q)
*q = '\0';

assert(!pdev || String_eq(pdev, p));
if (!pdev)
pdev = xStrdup(p);
} else if (String_startsWith(buf, "drm-engine-")) {
const char* delim;
const char* engineStart;
char* endptr;
unsigned long long int value;

if (sstate == SECST_DUPLICATE)
continue;

if (String_startsWith(buf, "drm-engine-capacity-"))
continue;

delim = strchr(buf, ':');
engineStart = buf + strlen("drm-engine-");

errno = 0;
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_system(lpl, value, xStrndup(engineStart, delim - engineStart));
}
}
}
} /* finished parsing lines */

fclose(fp);

if (sstate == SECST_NEW) {
struct client_id* new;

assert(client_id != INVALID_CLIENT_ID);

new = xMalloc(sizeof(*new));
*new = (struct client_id) {
.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.host;
unsigned long long int gputimeDelta;
uint64_t monotonictimeDelta;

Process_updateFieldWidth(GPU_TIME, ceil(log10(new_gpu_time)));

gputimeDelta = saturatingSub(new_gpu_time, lp->gpu_time);
monotonictimeDelta = host->monotonicMs - host->prevMonotonicMs;
if (gputimeDelta == 0)
lp->gpu_percent = 0.0f;
else if (monotonictimeDelta == 0)
lp->gpu_percent = -1.0f;
else
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) {
struct client_id* next = parsed_ids->next;
free(parsed_ids->pdev);
free(parsed_ids);
parsed_ids = next;
}

if (fdinfoDir)
closedir(fdinfoDir);
if (fdinfoFd != -1)
close(fdinfoFd);
}
16 changes: 16 additions & 0 deletions linux/GPU.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
#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 "linux/LinuxProcess.h"
#include "linux/LinuxProcessList.h"


void GPU_readProcessData(LinuxProcessList* lpl, LinuxProcess* lp, openat_arg_t procFd);

#endif /* HEADER_GPU */
Loading

0 comments on commit 798d3f3

Please sign in to comment.