diff --git a/CMakeLists.txt b/CMakeLists.txt index 9dadefa..821feb5 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -83,6 +83,7 @@ else() set(NVIDIA_SUPPORT_DEFAULT ON) set(AMDGPU_SUPPORT_DEFAULT ON) set(INTEL_SUPPORT_DEFAULT ON) + set(V3D_SUPPORT_DEFAULT ON) set(MSM_SUPPORT_DEFAULT ON) set(PANFROST_SUPPORT_DEFAULT ON) set(PANTHOR_SUPPORT_DEFAULT ON) @@ -97,6 +98,7 @@ option(APPLE_SUPPORT "Build support for Apple GPUs through Metal" ${APPLE_SUPPOR option(PANFROST_SUPPORT "Build support for Mali GPUs through panfrost driver" ${PANFROST_SUPPORT_DEFAULT}) option(PANTHOR_SUPPORT "Build support for Mali GPUs through panthor driver" ${PANTHOR_SUPPORT_DEFAULT}) option(ASCEND_SUPPORT "Build support for Ascend NPUs through Ascend DCMI" ${ASCEND_SUPPORT_DEFAULT}) +option(V3D_SUPPORT "Build support for Raspberrypi through v3d" ${V3D_SUPPORT_DEFAULT}) add_subdirectory(src) diff --git a/README.markdown b/README.markdown index 2acb2c7..4eccad0 100644 --- a/README.markdown +++ b/README.markdown @@ -9,8 +9,8 @@ accelerators. It can handle multiple GPUs and print information about them in a htop-familiar way. Currently supported vendors are AMD (Linux amdgpu driver), Apple (limited M1 & -M2 support), Huawei (Ascend), Intel (Linux i915 or Xe driver), NVIDIA (Linux -proprietary divers), Qualcomm Adreno (Linux MSM driver). +M2 support), Huawei (Ascend), Intel (Linux i915/Xe drivers), NVIDIA (Linux +proprietary divers), Qualcomm Adreno (Linux MSM driver), Broadcom VideoCore (Linux v3d driver). Because a picture is worth a thousand words: @@ -30,6 +30,7 @@ Table of Contents - [Adreno](#adreno) - [Apple](#apple) - [Ascend](#ascend) (only tested on 910B) + - [VideoCore](#videocore) - [Build](#build) - [Distribution Specific Installation Process](#distribution-specific-installation-process) - [Ubuntu / Debian](#ubuntu--debian) @@ -132,6 +133,14 @@ NVTOP supports Ascend (testing on Altas 800 (910B)) by DCMI API (version 6.0.0). Currently, the DCMI only supports limited APIs, missing PCIe generation, tx/rx throughput info, max power draw etc. +### VideoCore + +NVTOP supports VideoCore (testing on raspberrypi 4B). + +Supports GPU frequency, temperature, utilization, per-process utilization, GPU memory usage, and H264 decoding utilization. + +On non-raspberry pi os, you need to use the `linux-rpi` kernel, ensure the presence of the `/dev/vcio` device, and have access permissions to the `/sys/kernel/debug` directory. + Build ----- diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index fde4b27..169e112 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -100,6 +100,11 @@ if(INTEL_SUPPORT) target_sources(nvtop PRIVATE extract_gpuinfo_intel.c) endif() +if(V3D_SUPPORT) + target_sources(nvtop PRIVATE extract_gpuinfo_v3d.c) + target_sources(nvtop PRIVATE extract_gpuinfo_v3d_utils.c) +endif() + if(APPLE_SUPPORT) target_sources(nvtop PRIVATE extract_gpuinfo_apple.m) target_link_libraries(nvtop PRIVATE diff --git a/src/extract_gpuinfo_v3d.c b/src/extract_gpuinfo_v3d.c new file mode 100644 index 0000000..6fb879b --- /dev/null +++ b/src/extract_gpuinfo_v3d.c @@ -0,0 +1,270 @@ +/* + * + * Copyright (C) 2022 Hoream Xiao + * + * This file is part of Nvtop. + * + * Nvtop is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Nvtop is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with nvtop. If not, see . + * + */ + +#include "nvtop/device_discovery.h" +#include "nvtop/extract_gpuinfo_common.h" +#include "nvtop/extract_processinfo_fdinfo.h" +#include "nvtop/time.h" + +#include +#include +#include +#include + +int mbox_open(void); +void mbox_close(int mb); +void set_debug_files(int card_id); +void set_gpuinfo_from_vcio(struct gpuinfo_dynamic_info *dynamic_info, int mb); +void set_memory_gpuinfo(struct gpuinfo_dynamic_info *dynamic_info); +void set_usage_gpuinfo(struct gpuinfo_dynamic_info *dynamic_info); +void set_pid_usage_gpuinfo(struct gpu_process *process_info); +void set_init_max_memory(int mb); + +#define HASH_FIND_CLIENT(head, key_ptr, out_ptr) HASH_FIND(hh, head, key_ptr, sizeof(struct unique_cache_id), out_ptr) +#define HASH_ADD_CLIENT(head, in_ptr) HASH_ADD(hh, head, client_id, sizeof(struct unique_cache_id), in_ptr) + +#define SET_V3D_CACHE(cachePtr, field, value) SET_VALUE(cachePtr, field, value, v3d_cache_) +#define RESET_V3D_CACHE(cachePtr, field) INVALIDATE_VALUE(cachePtr, field, v3d_cache_) +#define V3D_CACHE_FIELD_VALID(cachePtr, field) VALUE_IS_VALID(cachePtr, field, v3d_cache_) + +enum v3d_process_info_cache_valid { v3d_cache_engine_render_valid = 0, v3d_cache_process_info_cache_valid_count }; + +struct __attribute__((__packed__)) unique_cache_id { + pid_t pid; +}; + +struct v3d_process_info_cache { + struct unique_cache_id client_id; + uint64_t engine_render; + nvtop_time last_measurement_tstamp; + unsigned char valid[(v3d_cache_process_info_cache_valid_count + CHAR_BIT - 1) / CHAR_BIT]; + UT_hash_handle hh; +}; + +struct gpu_info_v3d { + struct gpu_info base; + int mb; + int card_id; + + struct nvtop_device *card_device; + struct nvtop_device *driver_device; + struct v3d_process_info_cache *last_update_process_cache, *current_update_process_cache; // Cached processes info +}; + +static bool gpuinfo_v3d_init(void); +static void gpuinfo_v3d_shutdown(void); +static const char *gpuinfo_v3d_last_error_string(void); +static bool gpuinfo_v3d_get_device_handles(struct list_head *devices, unsigned *count); +static void gpuinfo_v3d_populate_static_info(struct gpu_info *_gpu_info); +static void gpuinfo_v3d_refresh_dynamic_info(struct gpu_info *_gpu_info); +static void gpuinfo_v3d_get_running_processes(struct gpu_info *_gpu_info); + +struct gpu_vendor gpu_vendor_v3d = { + .init = gpuinfo_v3d_init, + .shutdown = gpuinfo_v3d_shutdown, + .last_error_string = gpuinfo_v3d_last_error_string, + .get_device_handles = gpuinfo_v3d_get_device_handles, + .populate_static_info = gpuinfo_v3d_populate_static_info, + .refresh_dynamic_info = gpuinfo_v3d_refresh_dynamic_info, + .refresh_running_processes = gpuinfo_v3d_get_running_processes, + .name = "v3d", +}; + +unsigned v3d_gpu_count; +static struct gpu_info_v3d *gpu_infos; + +__attribute__((constructor)) static void init_extract_gpuinfo_v3d(void) { register_gpu_vendor(&gpu_vendor_v3d); } + +bool gpuinfo_v3d_init(void) { return true; } +void gpuinfo_v3d_shutdown(void) { + for (unsigned i = 0; i < v3d_gpu_count; ++i) { + struct gpu_info_v3d *current = &gpu_infos[i]; + nvtop_device_unref(current->card_device); + nvtop_device_unref(current->driver_device); + if (current->mb >= 0) + mbox_close(current->mb); + } +} + +const char *gpuinfo_v3d_last_error_string(void) { return "Err"; } + +static bool parse_drm_fdinfo_v3d(struct gpu_info *info, FILE *fdinfo_file, struct gpu_process *process_info) { + if (!fdinfo_file) + return false; + struct gpu_info_v3d *gpu_info = container_of(info, struct gpu_info_v3d, base); + struct unique_cache_id ucid = {.pid = process_info->pid}; + + struct v3d_process_info_cache *added_cache_entry; + HASH_FIND_CLIENT(gpu_info->current_update_process_cache, &ucid, added_cache_entry); + if (added_cache_entry) + return false; + + set_pid_usage_gpuinfo(process_info); + nvtop_time current_time; + nvtop_get_current_time(¤t_time); + + process_info->type |= gpu_process_graphical; + + struct v3d_process_info_cache *cache_entry; + HASH_FIND_CLIENT(gpu_info->last_update_process_cache, &ucid, cache_entry); + if (cache_entry) { + uint64_t time_elapsed = nvtop_difftime_u64(cache_entry->last_measurement_tstamp, current_time); + HASH_DEL(gpu_info->last_update_process_cache, cache_entry); + if (GPUINFO_PROCESS_FIELD_VALID(process_info, gfx_engine_used) && + V3D_CACHE_FIELD_VALID(cache_entry, engine_render) && + process_info->gfx_engine_used >= cache_entry->engine_render && + process_info->gfx_engine_used - cache_entry->engine_render <= time_elapsed) { + SET_GPUINFO_PROCESS( + process_info, gpu_usage, + busy_usage_from_time_usage_round(process_info->gfx_engine_used, cache_entry->engine_render, time_elapsed)); + } + } else { + cache_entry = calloc(1, sizeof(*cache_entry)); + if (!cache_entry) + goto parse_fdinfo_exit; + cache_entry->client_id.pid = process_info->pid; + } + + RESET_ALL(cache_entry->valid); + if (GPUINFO_PROCESS_FIELD_VALID(process_info, gfx_engine_used)) + SET_V3D_CACHE(cache_entry, engine_render, process_info->gfx_engine_used); + + cache_entry->last_measurement_tstamp = current_time; + HASH_ADD_CLIENT(gpu_info->current_update_process_cache, cache_entry); + +parse_fdinfo_exit: + return true; +} + +static void add_v3d_cards(struct nvtop_device *dev, const char *devname, struct list_head *devices, unsigned *count) { + struct nvtop_device *parent; + if (nvtop_device_get_parent(dev, &parent) < 0) + return; + + const char *driver; + nvtop_device_get_driver(parent, &driver); + if (strcmp(driver, "v3d")) + return; + + struct gpu_info_v3d *thisGPU = &gpu_infos[v3d_gpu_count++]; + thisGPU->base.vendor = &gpu_vendor_v3d; + thisGPU->card_device = nvtop_device_ref(dev); + thisGPU->driver_device = nvtop_device_ref(parent); + list_add_tail(&thisGPU->base.list, devices); + // Register a fdinfo callback for this GPU + processinfo_register_fdinfo_callback(parse_drm_fdinfo_v3d, &thisGPU->base); + thisGPU->mb = mbox_open(); + if (sscanf(devname, "/dev/dri/card%d", &thisGPU->card_id) != 1) + thisGPU->card_id = 0; + set_debug_files(thisGPU->card_id); + + (*count)++; +} + +bool gpuinfo_v3d_get_device_handles(struct list_head *devices_list, unsigned *count) { + *count = 0; + nvtop_device_enumerator *enumerator; + if (nvtop_enumerator_new(&enumerator) < 0) + return false; + + if (nvtop_device_enumerator_add_match_subsystem(enumerator, "drm", true) < 0) + return false; + + if (nvtop_device_enumerator_add_match_property(enumerator, "DEVNAME", "/dev/dri/*") < 0) + return false; + + unsigned num_devices = 0; + for (nvtop_device *device = nvtop_enumerator_get_device_first(enumerator); device; + device = nvtop_enumerator_get_device_next(enumerator)) { + num_devices++; + } + + gpu_infos = calloc(num_devices, sizeof(*gpu_infos)); + if (!gpu_infos) + return false; + + for (nvtop_device *device = nvtop_enumerator_get_device_first(enumerator); device; + device = nvtop_enumerator_get_device_next(enumerator)) { + num_devices++; + const char *devname; + if (nvtop_device_get_devname(device, &devname) < 0) + continue; + if (strstr(devname, "/dev/dri/card")) { + add_v3d_cards(device, devname, devices_list, count); + } + } + + nvtop_enumerator_unref(enumerator); + return true; +} + +void gpuinfo_v3d_populate_static_info(struct gpu_info *_gpu_info) { + struct gpu_info_v3d *gpu_info = container_of(_gpu_info, struct gpu_info_v3d, base); + struct gpuinfo_static_info *static_info = &gpu_info->base.static_info; + const char *dev_name = "VIDEO CORE"; + + static_info->integrated_graphics = true; + static_info->encode_decode_shared = false; + RESET_ALL(static_info->valid); + + snprintf(static_info->device_name, sizeof(static_info->device_name), "%s", dev_name); + SET_VALID(gpuinfo_device_name_valid, static_info->valid); + if (gpu_info->mb >= 0) + set_init_max_memory(gpu_info->mb); +} + +void gpuinfo_v3d_refresh_dynamic_info(struct gpu_info *_gpu_info) { + struct gpu_info_v3d *gpu_info = container_of(_gpu_info, struct gpu_info_v3d, base); + struct gpuinfo_dynamic_info *dynamic_info = &gpu_info->base.dynamic_info; + + RESET_ALL(dynamic_info->valid); + + nvtop_device *card_dev_copy; + const char *syspath; + nvtop_device_get_syspath(gpu_info->card_device, &syspath); + nvtop_device_new_from_syspath(&card_dev_copy, syspath); + + set_usage_gpuinfo(dynamic_info); + set_memory_gpuinfo(dynamic_info); + if (gpu_info->mb >= 0) + set_gpuinfo_from_vcio(dynamic_info, gpu_info->mb); + nvtop_device_unref(card_dev_copy); +} + +static void swap_process_cache_for_next_update(struct gpu_info_v3d *gpu_info) { + // Free old cache data and set the cache for the next update + if (gpu_info->last_update_process_cache) { + struct v3d_process_info_cache *cache_entry, *tmp; + HASH_ITER(hh, gpu_info->last_update_process_cache, cache_entry, tmp) { + HASH_DEL(gpu_info->last_update_process_cache, cache_entry); + free(cache_entry); + } + } + gpu_info->last_update_process_cache = gpu_info->current_update_process_cache; + gpu_info->current_update_process_cache = NULL; +} + +void gpuinfo_v3d_get_running_processes(struct gpu_info *_gpu_info) { + // For v3d, we register a fdinfo callback that will fill the gpu_process datastructure of the gpu_info structure + // for us. This avoids going through /proc multiple times per update for multiple GPUs. + struct gpu_info_v3d *gpu_info = container_of(_gpu_info, struct gpu_info_v3d, base); + swap_process_cache_for_next_update(gpu_info); +} diff --git a/src/extract_gpuinfo_v3d_utils.c b/src/extract_gpuinfo_v3d_utils.c new file mode 100644 index 0000000..0b0ec5e --- /dev/null +++ b/src/extract_gpuinfo_v3d_utils.c @@ -0,0 +1,295 @@ +/* + * + * Copyright (C) 2022 Hoream Xiao + * + * This file is part of Nvtop and adapted from the vcgencmd implementation. + * + * Nvtop is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Nvtop is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with nvtop. If not, see . + * + */ + +#include "nvtop/extract_gpuinfo_common.h" +#include +#include +#include +#include +#include +#include +#include + +/* + * use ioctl to send mbox property message + */ +#define DEVICE_FILE_NAME "/dev/vcio" +#define MAJOR_NUM 100 +#define IOCTL_MBOX_PROPERTY _IOWR(MAJOR_NUM, 0, char *) +#define MAX_STRING 1024 +#define GET_GENCMD_RESULT 0x00030080 +#define MAX_DECODER_FREQUENCE 550006336 + +int mbox_open(void); +void mbox_close(int mb); +void set_debug_files(int card_id); +void set_gpuinfo_from_vcio(struct gpuinfo_dynamic_info *dynamic_info, int mb); +void set_memory_gpuinfo(struct gpuinfo_dynamic_info *dynamic_info); +void set_usage_gpuinfo(struct gpuinfo_dynamic_info *dynamic_info); +void set_pid_usage_gpuinfo(struct gpu_process *process_info); +void set_init_max_memory(int mb); + +static uint64_t last_timestamp = 0; +static uint64_t last_runtime = 0; +static uint64_t max_gpu_memory_bytes = 128 << 20; + +static const char measure_temp[] = "measure_temp"; +static const char measure_clock_v3d[] = "measure_clock v3d"; +static const char measure_clock_h264[] = "measure_clock h264"; +static const char get_mem_gpu[] = "get_mem gpu"; + +static char gpu_usage_file[50]; +static char gpu_pid_usage_file[50]; +static char bo_stats_file[50]; + +void set_debug_files(int card_id) { + snprintf(gpu_usage_file, sizeof(gpu_usage_file), "/sys/kernel/debug/dri/%d/gpu_usage", card_id); + if (access(gpu_usage_file, F_OK)) + printf("%s is not available.\n", gpu_usage_file); + snprintf(gpu_pid_usage_file, sizeof(gpu_pid_usage_file), "/sys/kernel/debug/dri/%d/gpu_pid_usage", card_id); + if (access(gpu_pid_usage_file, F_OK)) + printf("%s is not available.\n", gpu_pid_usage_file); + snprintf(bo_stats_file, sizeof(bo_stats_file), "/sys/kernel/debug/dri/%d/bo_stats", card_id); + if (access(bo_stats_file, F_OK)) + printf("%s is not available.\n", bo_stats_file); +} + +static int mbox_property(int mb, void *buf) { + int ret_val = ioctl(mb, IOCTL_MBOX_PROPERTY, buf); + + if (ret_val < 0) { + printf("ioctl_set_msg failed:%d\n", ret_val); + } + return ret_val; +} + +int mbox_open(void) { + int mb; + + // open a char device file used for communicating with kernel mbox driver + mb = open(DEVICE_FILE_NAME, 0); + if (mb < 0) { + printf("Can't open device file: %s\n", DEVICE_FILE_NAME); + printf("Try creating a device file with: sudo mknod %s c %d 0\n", DEVICE_FILE_NAME, MAJOR_NUM); + } + return mb; +} + +void mbox_close(int mb) { close(mb); } + +static unsigned gencmd(int mb, const char *command, char *result, int result_len) { + int i = 0; + unsigned p[(MAX_STRING >> 2) + 7]; + int len = strlen(command); + // maximum length for command or response + if (len + 1 >= MAX_STRING) { + fprintf(stderr, "gencmd length too long : %d\n", len); + return -1; + } + p[i++] = 0; // size + p[i++] = 0x00000000; // process request + + p[i++] = GET_GENCMD_RESULT; // (the tag id) + p[i++] = MAX_STRING; // buffer_len + p[i++] = 0; // request_len (set to response length) + p[i++] = 0; // error response + + memcpy(p + i, command, len + 1); + i += MAX_STRING >> 2; + + p[i++] = 0x00000000; // end tag + p[0] = i * sizeof *p; // actual size + + mbox_property(mb, p); + result[0] = 0; + + size_t available_space = result_len - strlen(result) - 1; + strncat(result, (const char *)(p + 6), available_space); + + return p[5]; +} + +void set_init_max_memory(int mb) { + char result[MAX_STRING] = {}; + + int ret = gencmd(mb, get_mem_gpu, result, sizeof result); + if (!ret) { + if (sscanf(result, "gpu=%luM", &max_gpu_memory_bytes) == 1) { + max_gpu_memory_bytes <<= 20; + } + } +} + +static unsigned cal_percentage_usage(unsigned usage, unsigned all) { return (unsigned)(100.0 * usage / all + 0.5); } + +static void set_gpuinfo_decode(struct gpuinfo_dynamic_info *dynamic_info, int mb) { + unsigned int decode_usage = 0; + char result[MAX_STRING] = {}; + + int ret = gencmd(mb, measure_clock_h264, result, sizeof result); + if (!ret) { + if (sscanf(result, "frequency(28)=%u", &decode_usage) == 1) + // divide current frequency by max frequency; usage rate might not be accurate. + SET_GPUINFO_DYNAMIC(dynamic_info, decoder_rate, cal_percentage_usage(decode_usage, MAX_DECODER_FREQUENCE)); + } +} + +static void set_gpuinfo_temp(struct gpuinfo_dynamic_info *dynamic_info, int mb) { + float temperature = 0; + char result[MAX_STRING] = {}; + + int ret = gencmd(mb, measure_temp, result, sizeof result); + if (!ret) { + if (sscanf(result, "temp=%f'C", &temperature) == 1) { + SET_GPUINFO_DYNAMIC(dynamic_info, gpu_temp, (unsigned)temperature); + } + } +} + +static void set_gpuinfo_clock(struct gpuinfo_dynamic_info *dynamic_info, int mb) { + unsigned int clock = 0; + char result[MAX_STRING] = {}; + + int ret = gencmd(mb, measure_clock_v3d, result, sizeof result); + if (!ret) { + if (sscanf(result, "frequency(46)=%u", &clock) == 1) { + SET_GPUINFO_DYNAMIC(dynamic_info, gpu_clock_speed, clock >> 20); + } + } +} + +void set_gpuinfo_from_vcio(struct gpuinfo_dynamic_info *dynamic_info, int mb) { + set_gpuinfo_temp(dynamic_info, mb); + set_gpuinfo_clock(dynamic_info, mb); + set_gpuinfo_decode(dynamic_info, mb); +} + +void set_memory_gpuinfo(struct gpuinfo_dynamic_info *dynamic_info) { + FILE *fp = fopen(bo_stats_file, "rb"); + if (fp == NULL) { + return; + } + + char line[256]; + uint64_t allocated_bo_size_kb = 0; + + while (fgets(line, sizeof(line), fp)) { + if (sscanf(line, "allocated bo size (kb): %lu", &allocated_bo_size_kb) == 1) { + break; + } + } + + fclose(fp); + + uint64_t allocated_bo_size_bytes = allocated_bo_size_kb << 10; + + SET_GPUINFO_DYNAMIC(dynamic_info, used_memory, allocated_bo_size_bytes); + if (allocated_bo_size_bytes >= max_gpu_memory_bytes) + max_gpu_memory_bytes = allocated_bo_size_bytes; + SET_GPUINFO_DYNAMIC(dynamic_info, total_memory, max_gpu_memory_bytes); + SET_GPUINFO_DYNAMIC(dynamic_info, mem_util_rate, cal_percentage_usage(allocated_bo_size_bytes, max_gpu_memory_bytes)); +} + +void set_usage_gpuinfo(struct gpuinfo_dynamic_info *dynamic_info) { + FILE *fp = fopen(gpu_usage_file, "rb"); + + if (fp == NULL) + return; + + char *buf = NULL; + size_t res = 0; + unsigned jobs, active; + uint64_t timestamp, elapsed, runtime; + + while (getline(&buf, &res, fp) > 0) { + if (sscanf(buf, "timestamp;%lu;", ×tamp) == 1) { + elapsed = timestamp - last_timestamp; + last_timestamp = timestamp; + } else if (sscanf(strchr(buf, ';'), ";%u;%lu;%u;", &jobs, &runtime, &active) == 3) { + if (!strncmp(buf, "v3d_render", 10)) + break; + } + } + free(buf); + fclose(fp); + int usage = busy_usage_from_time_usage_round(runtime, last_runtime, elapsed); + last_runtime = runtime; + SET_GPUINFO_DYNAMIC(dynamic_info, gpu_util_rate, usage); + return; +} + +static pid_t get_tgid_from_tid(pid_t tid) { + char path[40]; + struct dirent *entry; + DIR *dp; + pid_t min_tid = INT_MAX; + + snprintf(path, sizeof(path), "/proc/%d/task/", tid); + + dp = opendir(path); + if (dp == NULL) { + return -1; + } + + while ((entry = readdir(dp)) != NULL) { + int current_tid = atoi(entry->d_name); + if (current_tid > 0 && current_tid < min_tid) { + min_tid = current_tid; + } + } + + closedir(dp); + + return min_tid; +} + +void set_pid_usage_gpuinfo(struct gpu_process *process_info) { + FILE *fp = fopen(gpu_pid_usage_file, "rb"); + if (fp == NULL) { + return; + } + + char *buf = NULL; + size_t res = 0; + unsigned jobs, active; + pid_t tid; + uint64_t runtime; + uint64_t timestamp; + + while (getline(&buf, &res, fp) > 0) { + if (sscanf(buf, "timestamp;%lu;", ×tamp) == 1) { + } else if (sscanf(strchr(buf, ';'), ";%u;%u;%lu;%u;", &tid, &jobs, &runtime, &active) == 4) { + if (!strncmp(buf, "v3d_render", 10)) { + if (get_tgid_from_tid(tid) == process_info->pid) { + SET_GPUINFO_PROCESS(process_info, gfx_engine_used, runtime); + free(buf); + fclose(fp); + return; + } + } + } + } + + SET_GPUINFO_PROCESS(process_info, gfx_engine_used, 0); + free(buf); + fclose(fp); + return; +}