htop/linux/GPU.c

247 lines
6.7 KiB
C

/*
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) {
const Machine* host = lp->super.super.host;
int fdinfoFd = -1;
DIR* fdinfoDir = NULL;
ClientInfo* parsed_ids = NULL;
unsigned long long int new_gpu_time = 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) {
lp->gpu_percent = 0.0F;
return;
}
lp->gpu_activityMs = host->monotonicMs;
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 (ename[0] == '.' && (ename[1] == '\0' || (ename[1] == '.' && ename[2] == '\0')))
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) {
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;
lp->gpu_activityMs = 0;
} 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);
}