GPU.c 6.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246
  1. /*
  2. htop - GPU.c
  3. (C) 2023 htop dev team
  4. Released under the GNU GPLv2+, see the COPYING file
  5. in the source distribution for its full text.
  6. */
  7. #include "config.h" // IWYU pragma: keep
  8. #include "linux/GPU.h"
  9. #include <assert.h>
  10. #include <ctype.h>
  11. #include <dirent.h>
  12. #include <errno.h>
  13. #include <sys/types.h>
  14. #include "XUtils.h"
  15. #include "linux/LinuxMachine.h"
  16. typedef unsigned long long int ClientID;
  17. #define INVALID_CLIENT_ID ((ClientID)-1)
  18. typedef struct ClientInfo_ {
  19. char* pdev;
  20. ClientID id;
  21. struct ClientInfo_* next;
  22. } ClientInfo;
  23. enum section_state {
  24. SECST_UNKNOWN,
  25. SECST_DUPLICATE,
  26. SECST_NEW,
  27. };
  28. static bool is_duplicate_client(const ClientInfo* parsed, ClientID id, const char* pdev) {
  29. for (; parsed; parsed = parsed->next) {
  30. if (id == parsed->id && String_eq_nullable(pdev, parsed->pdev)) {
  31. return true;
  32. }
  33. }
  34. return false;
  35. }
  36. static void update_machine_gpu(LinuxProcessTable* lpt, unsigned long long int time, const char* engine, size_t engine_len) {
  37. Machine* host = lpt->super.super.host;
  38. LinuxMachine* lhost = (LinuxMachine*) host;
  39. GPUEngineData** engineData = &lhost->gpuEngineData;
  40. while (*engineData) {
  41. if (strncmp((*engineData)->key, engine, engine_len) == 0 && (*engineData)->key[engine_len] == '\0')
  42. break;
  43. engineData = &((*engineData)->next);
  44. }
  45. if (!*engineData) {
  46. GPUEngineData* newData = xMalloc(sizeof(*newData));
  47. *newData = (GPUEngineData) {
  48. .prevTime = 0,
  49. .curTime = 0,
  50. .key = xStrndup(engine, engine_len),
  51. .next = NULL,
  52. };
  53. *engineData = newData;
  54. }
  55. (*engineData)->curTime += time;
  56. lhost->curGpuTime += time;
  57. }
  58. /*
  59. * Documentation reference:
  60. * https://www.kernel.org/doc/html/latest/gpu/drm-usage-stats.html
  61. */
  62. void GPU_readProcessData(LinuxProcessTable* lpt, LinuxProcess* lp, openat_arg_t procFd) {
  63. const Machine* host = lp->super.super.host;
  64. int fdinfoFd = -1;
  65. DIR* fdinfoDir = NULL;
  66. ClientInfo* parsed_ids = NULL;
  67. unsigned long long int new_gpu_time = 0;
  68. /* check only if active in last check or last scan was more than 5s ago */
  69. if (lp->gpu_activityMs != 0 && host->monotonicMs - lp->gpu_activityMs < 5000) {
  70. lp->gpu_percent = 0.0F;
  71. return;
  72. }
  73. lp->gpu_activityMs = host->monotonicMs;
  74. fdinfoFd = Compat_openat(procFd, "fdinfo", O_RDONLY | O_NOFOLLOW | O_DIRECTORY | O_CLOEXEC);
  75. if (fdinfoFd == -1)
  76. goto out;
  77. fdinfoDir = fdopendir(fdinfoFd);
  78. if (!fdinfoDir)
  79. goto out;
  80. fdinfoFd = -1;
  81. #ifndef HAVE_OPENAT
  82. char fdinfoPathBuf[32];
  83. xSnprintf(fdinfoPathBuf, sizeof(fdinfoPathBuf), PROCDIR "/%u/fdinfo", Process_getPid(&lp->super));
  84. #endif
  85. while (true) {
  86. char* pdev = NULL;
  87. ClientID client_id = INVALID_CLIENT_ID;
  88. enum section_state sstate = SECST_UNKNOWN;
  89. const struct dirent* entry = readdir(fdinfoDir);
  90. if (!entry)
  91. break;
  92. const char* ename = entry->d_name;
  93. if (ename[0] == '.' && (ename[1] == '\0' || (ename[1] == '.' && ename[2] == '\0')))
  94. continue;
  95. char buffer[4096];
  96. #ifdef HAVE_OPENAT
  97. ssize_t ret = xReadfileat(dirfd(fdinfoDir), ename, buffer, sizeof(buffer));
  98. #else
  99. ssize_t ret = xReadfileat(fdinfoPathBuf, ename, buffer, sizeof(buffer));
  100. #endif
  101. /* eventfd information can be huge */
  102. if (ret <= 0 || (size_t)ret >= sizeof(buffer) - 1)
  103. continue;
  104. char* buf = buffer;
  105. const char* line;
  106. while ((line = strsep(&buf, "\n")) != NULL) {
  107. if (!String_startsWith(line, "drm-"))
  108. continue;
  109. line += strlen("drm-");
  110. if (line[0] == 'c' && String_startsWith(line, "client-id:")) {
  111. if (sstate == SECST_NEW) {
  112. assert(client_id != INVALID_CLIENT_ID);
  113. ClientInfo* new = xMalloc(sizeof(*new));
  114. *new = (ClientInfo) {
  115. .id = client_id,
  116. .pdev = pdev,
  117. .next = parsed_ids,
  118. };
  119. pdev = NULL;
  120. parsed_ids = new;
  121. }
  122. sstate = SECST_UNKNOWN;
  123. char *endptr;
  124. errno = 0;
  125. client_id = strtoull(line + strlen("client-id:"), &endptr, 10);
  126. if (errno || *endptr != '\0')
  127. client_id = INVALID_CLIENT_ID;
  128. } else if (line[0] == 'p' && String_startsWith(line, "pdev:")) {
  129. const char* p = line + strlen("pdev:");
  130. while (isspace((unsigned char)*p))
  131. p++;
  132. assert(!pdev || String_eq(pdev, p));
  133. if (!pdev)
  134. pdev = xStrdup(p);
  135. } else if (line[0] == 'e' && String_startsWith(line, "engine-")) {
  136. if (sstate == SECST_DUPLICATE)
  137. continue;
  138. const char* engineStart = line + strlen("engine-");
  139. if (String_startsWith(engineStart, "capacity-"))
  140. continue;
  141. const char* delim = strchr(line, ':');
  142. char* endptr;
  143. errno = 0;
  144. unsigned long long int value = strtoull(delim + 1, &endptr, 10);
  145. if (errno == 0 && String_startsWith(endptr, " ns")) {
  146. if (sstate == SECST_UNKNOWN) {
  147. if (client_id != INVALID_CLIENT_ID && !is_duplicate_client(parsed_ids, client_id, pdev))
  148. sstate = SECST_NEW;
  149. else
  150. sstate = SECST_DUPLICATE;
  151. }
  152. if (sstate == SECST_NEW) {
  153. new_gpu_time += value;
  154. update_machine_gpu(lpt, value, engineStart, delim - engineStart);
  155. }
  156. }
  157. }
  158. } /* finished parsing lines */
  159. if (sstate == SECST_NEW) {
  160. assert(client_id != INVALID_CLIENT_ID);
  161. ClientInfo* new = xMalloc(sizeof(*new));
  162. *new = (ClientInfo) {
  163. .id = client_id,
  164. .pdev = pdev,
  165. .next = parsed_ids,
  166. };
  167. pdev = NULL;
  168. parsed_ids = new;
  169. }
  170. free(pdev);
  171. } /* finished parsing fdinfo entries */
  172. if (new_gpu_time > 0) {
  173. unsigned long long int gputimeDelta;
  174. uint64_t monotonicTimeDelta;
  175. Row_updateFieldWidth(GPU_TIME, ceil(log10(new_gpu_time)));
  176. gputimeDelta = saturatingSub(new_gpu_time, lp->gpu_time);
  177. monotonicTimeDelta = host->monotonicMs - host->prevMonotonicMs;
  178. lp->gpu_percent = 100.0F * gputimeDelta / (1000 * 1000) / monotonicTimeDelta;
  179. lp->gpu_activityMs = 0;
  180. } else
  181. lp->gpu_percent = 0.0F;
  182. out:
  183. lp->gpu_time = new_gpu_time;
  184. while (parsed_ids) {
  185. ClientInfo* next = parsed_ids->next;
  186. free(parsed_ids->pdev);
  187. free(parsed_ids);
  188. parsed_ids = next;
  189. }
  190. if (fdinfoDir)
  191. closedir(fdinfoDir);
  192. if (fdinfoFd != -1)
  193. close(fdinfoFd);
  194. }