From 09adcfd4e2710871774ecdcee42a076a4986be85 Mon Sep 17 00:00:00 2001 From: Srinivas Kandagatla Date: Fri, 3 Jul 2026 06:27:22 +0100 Subject: [PATCH 1/3] nvtop: qualcomm: add Qualcomm NPU driver Add support for monitoring the Qualcomm NPU (Neural Processing Unit) via the qcnpuperf library. The driver reports Q6 utilization and clock speed, HVX and HMX utilization, temperature, and system memory usage. The DSP session is opened with qcom_dsp_open() in get_device_handles() and stored as an opaque qcom_dsp_ctx pointer in the per-device struct. qcom_dsp_get_arch_version() is used to form the device name as "Qualcomm Hexagon vXX NPU" where XX is the Hexagon ISA version. NSP thermal zones are discovered at startup by scanning /sys/class/thermal/thermal_zone*/type for entries whose type starts with "nsp". The hottest zone is reported as the NPU temperature on each refresh, avoiding hardcoded zone numbers that differ across SoCs. System memory (from /proc/meminfo) is reported as device memory since the Hexagon DSP shares the host DRAM. The driver is built as part of MSM_SUPPORT and depends on qcnpuperf (found via pkg-config). Signed-off-by: Srinivas Kandagatla --- include/nvtop/extract_gpuinfo_common.h | 4 + src/CMakeLists.txt | 13 ++ src/extract_npuinfo_qualcomm.c | 223 +++++++++++++++++++++++++ 3 files changed, 240 insertions(+) create mode 100644 src/extract_npuinfo_qualcomm.c diff --git a/include/nvtop/extract_gpuinfo_common.h b/include/nvtop/extract_gpuinfo_common.h index 9e4d1c9d..6ffcdd01 100644 --- a/include/nvtop/extract_gpuinfo_common.h +++ b/include/nvtop/extract_gpuinfo_common.h @@ -91,6 +91,8 @@ enum gpuinfo_dynamic_info_valid { gpuinfo_mem_clock_speed_max_valid, gpuinfo_gpu_util_rate_valid, gpuinfo_mem_util_rate_valid, + gpuinfo_hvx_util_rate_valid, + gpuinfo_hmx_util_rate_valid, gpuinfo_encoder_rate_valid, gpuinfo_decoder_rate_valid, gpuinfo_total_memory_valid, @@ -117,6 +119,8 @@ struct gpuinfo_dynamic_info { unsigned int mem_clock_speed_max; // Maximum clock speed in MHz unsigned int gpu_util_rate; // GPU utilization rate in % unsigned int mem_util_rate; // MEM utilization rate in % + unsigned int hvx_util_rate; // Qualcomm NPU HVX utilization rate in % + unsigned int hmx_util_rate; // Qualcomm NPU HMX utilization rate in % unsigned int effective_load_rate; // Effective load rate in % unsigned int encoder_rate; // Encoder utilization rate in % unsigned int decoder_rate; // Decoder utilization rate in % diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index b485cb40..8a0f1be3 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -99,6 +99,19 @@ endif() if (MSM_SUPPORT) target_sources(nvtop PRIVATE extract_gpuinfo_msm.c) target_sources(nvtop PRIVATE extract_gpuinfo_msm_utils.c) + + pkg_check_modules(NPUPERF REQUIRED qcnpuperf) + + target_include_directories(nvtop + PRIVATE + ${NPUPERF_INCLUDE_DIRS} + ) + + target_link_libraries(nvtop + PRIVATE + ${NPUPERF_LIBRARIES} + ) + target_sources(nvtop PRIVATE extract_npuinfo_qualcomm.c) endif() if(INTEL_SUPPORT) diff --git a/src/extract_npuinfo_qualcomm.c b/src/extract_npuinfo_qualcomm.c new file mode 100644 index 00000000..4697ccd1 --- /dev/null +++ b/src/extract_npuinfo_qualcomm.c @@ -0,0 +1,223 @@ +#include "nvtop/extract_gpuinfo_common.h" +#include "nvtop/get_process_info.h" +#include "nvtop/time.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define MAX_NSP_THERMAL_ZONES 8 + +struct gpu_info_qcom_npu { + struct gpu_info base; + struct qcom_dsp_ctx *ctx; + char nsp_thermal_paths[MAX_NSP_THERMAL_ZONES][64]; + int nsp_thermal_count; +}; + +static struct gpu_info_qcom_npu *qcom_npu_info = NULL; +extern struct gpu_vendor gpu_vendor_qcom_npu; + +static bool gpuinfo_qcom_npu_init(void) { + return true; +} + +static void gpuinfo_qcom_npu_shutdown(void) { + if (qcom_npu_info) { + for (unsigned i = 0; i < qcom_npu_info->base.processes_count; i++) { + free(qcom_npu_info->base.processes[i].cmdline); + free(qcom_npu_info->base.processes[i].user_name); + } + free(qcom_npu_info->base.processes); + if (qcom_npu_info->ctx) + qcom_dsp_close(qcom_npu_info->ctx); + } + free(qcom_npu_info); + qcom_npu_info = NULL; +} + +static const char *gpuinfo_qcom_npu_last_error_string(void) { + return "QCOM-NPU error"; +} + +static void add_qcom_npu_chip(struct list_head *devices, unsigned *count) { + struct gpu_info_qcom_npu *this_npu = &qcom_npu_info[*count]; + + this_npu->base.vendor = &gpu_vendor_qcom_npu; + snprintf(this_npu->base.pdev, PDEV_LEN, "QCOM-NPU%d", *count); + list_add_tail(&this_npu->base.list, devices); + this_npu->base.processes_count = 0; + this_npu->base.processes = NULL; + this_npu->base.processes_array_size = 0; + *count += 1; +} + +static void find_nsp_thermal_zones(struct gpu_info_qcom_npu *npu) { + DIR *dir = opendir("/sys/class/thermal"); + struct dirent *ent; + + npu->nsp_thermal_count = 0; + if (!dir) + return; + + while ((ent = readdir(dir)) != NULL) { + if (npu->nsp_thermal_count >= MAX_NSP_THERMAL_ZONES) + break; + if (strncmp(ent->d_name, "thermal_zone", 12) != 0) + continue; + + char type_path[80]; + snprintf(type_path, sizeof(type_path), "/sys/class/thermal/%.54s/type", ent->d_name); + + char type[32] = {0}; + FILE *fp = fopen(type_path, "r"); + if (!fp) + continue; + fgets(type, sizeof(type), fp); + fclose(fp); + type[strcspn(type, "\n")] = '\0'; + + if (strncmp(type, "nsp", 3) != 0) + continue; + + snprintf(npu->nsp_thermal_paths[npu->nsp_thermal_count], + sizeof(npu->nsp_thermal_paths[0]), + "/sys/class/thermal/%.38s/temp", ent->d_name); + npu->nsp_thermal_count++; + } + closedir(dir); +} + +static bool gpuinfo_qcom_npu_get_device_handles(struct list_head *devices_list, unsigned *count) { + *count = 0; + qcom_npu_info = calloc(1, sizeof(struct gpu_info_qcom_npu)); + if (!qcom_npu_info) + return false; + + qcom_npu_info->ctx = qcom_dsp_open(DSP_NPU0); + if (!qcom_npu_info->ctx) { + free(qcom_npu_info); + qcom_npu_info = NULL; + return false; + } + + add_qcom_npu_chip(devices_list, count); + find_nsp_thermal_zones(qcom_npu_info); + + return true; +} + +static void gpuinfo_qcom_npu_populate_static_info(struct gpu_info *_gpu_info) { + struct gpu_info_qcom_npu *gpu_info = container_of(_gpu_info, struct gpu_info_qcom_npu, base); + struct gpuinfo_static_info *static_info = &gpu_info->base.static_info; + unsigned int arch_ver; + + static_info->integrated_graphics = true; + static_info->encode_decode_shared = false; + RESET_ALL(static_info->valid); + + arch_ver = qcom_dsp_prof_get_q6_arch_version(gpu_info->ctx); + if (arch_ver) { + snprintf(static_info->device_name, sizeof(static_info->device_name), + "Qualcomm Hexagon v%x NPU", arch_ver); + SET_VALID(gpuinfo_device_name_valid, static_info->valid); + } +} + +static int read_int_from_file(const char *path) { + int value = 0; + FILE *fp = fopen(path, "r"); + if (fp) { + fscanf(fp, "%d", &value); + fclose(fp); + } + return value; +} + +static int set_gpuinfo_dynamic_memory(struct gpuinfo_dynamic_info *dynamic_info) { + FILE *fp = fopen("/proc/meminfo", "r"); + unsigned long mem_total = 0, mem_available = 0; + char line[256]; + + if (!fp) + return -1; + + while (fgets(line, sizeof(line), fp)) { + if (sscanf(line, "MemTotal: %lu kB", &mem_total) == 1) { + mem_total *= 1024; + SET_GPUINFO_DYNAMIC(dynamic_info, total_memory, mem_total); + } else if (sscanf(line, "MemAvailable: %lu kB", &mem_available) == 1) { + mem_available *= 1024; + SET_GPUINFO_DYNAMIC(dynamic_info, free_memory, mem_available); + } + } + fclose(fp); + + if (mem_total > 0 && mem_available > 0) { + SET_GPUINFO_DYNAMIC(dynamic_info, used_memory, mem_total - mem_available); + SET_GPUINFO_DYNAMIC(dynamic_info, mem_util_rate, + (dynamic_info->total_memory - dynamic_info->free_memory) * 100 / dynamic_info->total_memory); + } + return 0; +} + +static void gpuinfo_qcom_npu_refresh_dynamic_info(struct gpu_info *_gpu_info) { + struct gpu_info_qcom_npu *gpu_info = container_of(_gpu_info, struct gpu_info_qcom_npu, base); + struct gpuinfo_dynamic_info *dynamic_info = &gpu_info->base.dynamic_info; + struct sysmon_query_prof_data *data; + int no_metrics = 0; + + data = qcom_dsp_get_prof_data(gpu_info->ctx, &no_metrics); + if (!data || no_metrics <= 0) { + fprintf(stderr, "qcom_dsp_get_prof_data failed\n"); + return; + } + + float q6_util = qcom_dsp_prof_get_q6_utilization(data); + float hvx_util = qcom_dsp_prof_get_hvx_utilization(data); + float hmx_util = qcom_dsp_prof_get_hmx_utilization(data); + SET_GPUINFO_DYNAMIC(dynamic_info, gpu_util_rate, (unsigned int)q6_util); + SET_GPUINFO_DYNAMIC(dynamic_info, gpu_clock_speed, qcom_dsp_prof_get_q6_clock(data) / 1000); + SET_GPUINFO_DYNAMIC(dynamic_info, gpu_clock_speed_max, qcom_dsp_prof_get_q6_clock(data) / 1000); + SET_GPUINFO_DYNAMIC(dynamic_info, hvx_util_rate, (unsigned int)hvx_util); + SET_GPUINFO_DYNAMIC(dynamic_info, hmx_util_rate, (unsigned int)hmx_util); + SET_VALID(gpuinfo_hvx_util_rate_valid, dynamic_info->valid); + SET_VALID(gpuinfo_hmx_util_rate_valid, dynamic_info->valid); + + int max_temp = 0; + for (int i = 0; i < gpu_info->nsp_thermal_count; i++) { + int t = read_int_from_file(gpu_info->nsp_thermal_paths[i]); + if (t > max_temp) + max_temp = t; + } + if (gpu_info->nsp_thermal_count > 0) + SET_GPUINFO_DYNAMIC(dynamic_info, gpu_temp, max_temp / 1000); + + set_gpuinfo_dynamic_memory(dynamic_info); +} + +static void gpuinfo_qcom_npu_get_running_processes(struct gpu_info *_gpu_info) { + (void)_gpu_info; +} + +struct gpu_vendor gpu_vendor_qcom_npu = { + .init = gpuinfo_qcom_npu_init, + .shutdown = gpuinfo_qcom_npu_shutdown, + .last_error_string = gpuinfo_qcom_npu_last_error_string, + .get_device_handles = gpuinfo_qcom_npu_get_device_handles, + .populate_static_info = gpuinfo_qcom_npu_populate_static_info, + .refresh_dynamic_info = gpuinfo_qcom_npu_refresh_dynamic_info, + .refresh_running_processes = gpuinfo_qcom_npu_get_running_processes, + .name = "QCOM-NPU", +}; + +__attribute__((constructor)) static void init_extract_gpuinfo_qcom_npu(void) { + register_gpu_vendor(&gpu_vendor_qcom_npu); +} From 9ba357b150cf29fe6e828fce58a56cf71237b99a Mon Sep 17 00:00:00 2001 From: Srinivas Kandagatla Date: Sun, 28 Jun 2026 22:38:43 +0100 Subject: [PATCH 2/3] interface: use per-vendor unit name for compute unit labels Add a unit_name field to struct gpu_vendor so drivers can declare what kind of compute unit they represent (e.g. "NPU"). The interface uses this label in utilization meters, clock display, plot legends, and the integrated-device label, falling back to "GPU" when unset so existing drivers are unaffected. Pass the device list into populate_plot_data_from_ring_buffer() and draw_plots() so each plot legend can be prefixed with the correct unit name rather than the hardcoded "GPU" string. The Qualcomm NPU driver sets unit_name = "NPU", so its utilization bar, clock field, and plots now read "NPU" rather than "GPU". Signed-off-by: Srinivas Kandagatla --- include/nvtop/extract_gpuinfo_common.h | 1 + src/extract_npuinfo_qualcomm.c | 1 + src/interface.c | 55 ++++++++++++++++---------- 3 files changed, 37 insertions(+), 20 deletions(-) diff --git a/include/nvtop/extract_gpuinfo_common.h b/include/nvtop/extract_gpuinfo_common.h index 6ffcdd01..6fdc2117 100644 --- a/include/nvtop/extract_gpuinfo_common.h +++ b/include/nvtop/extract_gpuinfo_common.h @@ -212,6 +212,7 @@ struct gpu_vendor { void (*refresh_running_processes)(struct gpu_info *gpu_info); char *name; + const char *unit_name; // Short label for the compute unit (e.g. "GPU", "NPU"); NULL defaults to "GPU" }; #define PDEV_LEN 16 diff --git a/src/extract_npuinfo_qualcomm.c b/src/extract_npuinfo_qualcomm.c index 4697ccd1..6e7b118f 100644 --- a/src/extract_npuinfo_qualcomm.c +++ b/src/extract_npuinfo_qualcomm.c @@ -216,6 +216,7 @@ struct gpu_vendor gpu_vendor_qcom_npu = { .refresh_dynamic_info = gpuinfo_qcom_npu_refresh_dynamic_info, .refresh_running_processes = gpuinfo_qcom_npu_get_running_processes, .name = "QCOM-NPU", + .unit_name = "NPU", }; __attribute__((constructor)) static void init_extract_gpuinfo_qcom_npu(void) { diff --git a/src/interface.c b/src/interface.c index ae231991..25b6c8a9 100644 --- a/src/interface.c +++ b/src/interface.c @@ -42,6 +42,8 @@ #include #include +#define DEVICE_UNIT_NAME(dev) ((dev)->vendor->unit_name ? (dev)->vendor->unit_name : "GPU") + static unsigned int sizeof_device_field[device_field_count] = { [device_name] = 11, [device_fan_speed] = 11, [device_temperature] = 10, [device_power] = 15, [device_clock] = 11, [device_mem_clock] = 12, [device_pcie] = 46, [device_shadercores] = 7, @@ -657,6 +659,16 @@ static void encode_decode_show_select(struct device_window *dev, bool encode_val } } +static struct gpu_info *get_device_by_id(struct list_head *devices, unsigned id) { + struct gpu_info *device; + unsigned i = 0; + list_for_each_entry(device, devices, list) { + if (i++ == id) + return device; + } + return NULL; +} + static void draw_devices(struct list_head *devices, struct nvtop_interface *interface) { struct gpu_info *device; unsigned dev_id = 0; @@ -722,15 +734,15 @@ static void draw_devices(struct list_head *devices, struct nvtop_interface *inte if (GPUINFO_DYNAMIC_FIELD_VALID(&device->dynamic_info, effective_load_rate)) { snprintf(buff, 1024, "%u%%(eff %u%%)", device->dynamic_info.gpu_util_rate, device->dynamic_info.effective_load_rate); - draw_percentage_meter_with_yellow_highlight(gpu_util_win, "GPU", device->dynamic_info.gpu_util_rate, + draw_percentage_meter_with_yellow_highlight(gpu_util_win, DEVICE_UNIT_NAME(device), device->dynamic_info.gpu_util_rate, device->dynamic_info.effective_load_rate, buff); } else { snprintf(buff, 1024, "%u%%", device->dynamic_info.gpu_util_rate); - draw_percentage_meter(gpu_util_win, "GPU", device->dynamic_info.gpu_util_rate, buff); + draw_percentage_meter(gpu_util_win, DEVICE_UNIT_NAME(device), device->dynamic_info.gpu_util_rate, buff); } } else { snprintf(buff, 1024, "N/A"); - draw_percentage_meter(gpu_util_win, "GPU", 0, buff); + draw_percentage_meter(gpu_util_win, DEVICE_UNIT_NAME(device), 0, buff); } if (GPUINFO_DYNAMIC_FIELD_VALID(&device->dynamic_info, total_memory) && @@ -797,9 +809,9 @@ static void draw_devices(struct list_head *devices, struct nvtop_interface *inte // GPU CLOCK werase(dev->gpu_clock_info); if (GPUINFO_DYNAMIC_FIELD_VALID(&device->dynamic_info, gpu_clock_speed)) - mvwprintw(dev->gpu_clock_info, 0, 0, "GPU %uMHz", device->dynamic_info.gpu_clock_speed); + mvwprintw(dev->gpu_clock_info, 0, 0, "%.3s %uMHz", DEVICE_UNIT_NAME(device), device->dynamic_info.gpu_clock_speed); else - mvwprintw(dev->gpu_clock_info, 0, 0, "GPU N/A MHz"); + mvwprintw(dev->gpu_clock_info, 0, 0, "%.3s N/A MHz", DEVICE_UNIT_NAME(device)); mvwchgat(dev->gpu_clock_info, 0, 0, 3, 0, cyan_color, NULL); wnoutrefresh(dev->gpu_clock_info); @@ -834,7 +846,7 @@ static void draw_devices(struct list_head *devices, struct nvtop_interface *inte werase(dev->pcie_info); if (device->static_info.integrated_graphics) { wcolor_set(dev->pcie_info, cyan_color, NULL); - mvwprintw(dev->pcie_info, 0, 0, "Integrated GPU"); + mvwprintw(dev->pcie_info, 0, 0, "Integrated %s", DEVICE_UNIT_NAME(device)); } else { wcolor_set(dev->pcie_info, cyan_color, NULL); mvwprintw(dev->pcie_info, 0, 0, "PCIe "); @@ -1748,7 +1760,8 @@ void save_current_data_to_ring(struct list_head *devices, struct nvtop_interface } } -static unsigned populate_plot_data_from_ring_buffer(const struct nvtop_interface *interface, +static unsigned populate_plot_data_from_ring_buffer(struct list_head *devices, + const struct nvtop_interface *interface, struct plot_window *plot_win, unsigned size_data_buff, double data[size_data_buff], char plot_legend[MAX_LINES_PER_PLOT][PLOT_MAX_LEGEND_SIZE]) { @@ -1770,40 +1783,42 @@ static unsigned populate_plot_data_from_ring_buffer(const struct nvtop_interface for (unsigned i = 0; i < plot_win->num_devices_to_plot; ++i) { unsigned dev_id = plot_win->devices_ids[i]; plot_info_to_draw to_draw = interface->options.gpu_specific_opts[dev_id].to_draw; + struct gpu_info *device = get_device_by_id(devices, dev_id); + const char *unit = device ? DEVICE_UNIT_NAME(device) : "GPU"; unsigned data_ring_index = 0; for (enum plot_information info = plot_gpu_rate; info < plot_information_count; ++info) { if (plot_isset_draw_info(info, to_draw)) { // Populate the legend switch (info) { case plot_gpu_rate: - snprintf(plot_legend[in_processing], PLOT_MAX_LEGEND_SIZE, "GPU%u %%", dev_id); + snprintf(plot_legend[in_processing], PLOT_MAX_LEGEND_SIZE, "%s%u %%", unit, dev_id); break; case plot_gpu_mem_rate: - snprintf(plot_legend[in_processing], PLOT_MAX_LEGEND_SIZE, "GPU%u mem%%", dev_id); + snprintf(plot_legend[in_processing], PLOT_MAX_LEGEND_SIZE, "%s%u mem%%", unit, dev_id); break; case plot_encoder_rate: - snprintf(plot_legend[in_processing], PLOT_MAX_LEGEND_SIZE, "GPU%u encode%%", dev_id); + snprintf(plot_legend[in_processing], PLOT_MAX_LEGEND_SIZE, "%s%u encode%%", unit, dev_id); break; case plot_decoder_rate: - snprintf(plot_legend[in_processing], PLOT_MAX_LEGEND_SIZE, "GPU%u decode%%", dev_id); + snprintf(plot_legend[in_processing], PLOT_MAX_LEGEND_SIZE, "%s%u decode%%", unit, dev_id); break; case plot_gpu_temperature: - snprintf(plot_legend[in_processing], PLOT_MAX_LEGEND_SIZE, "GPU%u temp(c)", dev_id); + snprintf(plot_legend[in_processing], PLOT_MAX_LEGEND_SIZE, "%s%u temp(c)", unit, dev_id); break; case plot_gpu_power_draw_rate: - snprintf(plot_legend[in_processing], PLOT_MAX_LEGEND_SIZE, "GPU%u power%%", dev_id); + snprintf(plot_legend[in_processing], PLOT_MAX_LEGEND_SIZE, "%s%u power%%", unit, dev_id); break; case plot_fan_speed: - snprintf(plot_legend[in_processing], PLOT_MAX_LEGEND_SIZE, "GPU%u fan%%", dev_id); + snprintf(plot_legend[in_processing], PLOT_MAX_LEGEND_SIZE, "%s%u fan%%", unit, dev_id); break; case plot_gpu_clock_rate: - snprintf(plot_legend[in_processing], PLOT_MAX_LEGEND_SIZE, "GPU%u clock%%", dev_id); + snprintf(plot_legend[in_processing], PLOT_MAX_LEGEND_SIZE, "%s%u clock%%", unit, dev_id); break; case plot_gpu_mem_clock_rate: - snprintf(plot_legend[in_processing], PLOT_MAX_LEGEND_SIZE, "GPU%u mem clock%%", dev_id); + snprintf(plot_legend[in_processing], PLOT_MAX_LEGEND_SIZE, "%s%u mem clock%%", unit, dev_id); break; case plot_effective_load_rate: - snprintf(plot_legend[in_processing], PLOT_MAX_LEGEND_SIZE, "GPU%u eff. load%%", dev_id); + snprintf(plot_legend[in_processing], PLOT_MAX_LEGEND_SIZE, "%s%u eff. load%%", unit, dev_id); break; case plot_information_count: break; @@ -1829,14 +1844,14 @@ static unsigned populate_plot_data_from_ring_buffer(const struct nvtop_interface return total_to_draw; } -static void draw_plots(struct nvtop_interface *interface) { +static void draw_plots(struct list_head *devices, struct nvtop_interface *interface) { for (unsigned plot_id = 0; plot_id < interface->num_plots; ++plot_id) { werase(interface->plots[plot_id].plot_window); char plot_legend[MAX_LINES_PER_PLOT][PLOT_MAX_LEGEND_SIZE]; unsigned num_lines = - populate_plot_data_from_ring_buffer(interface, &interface->plots[plot_id], interface->plots[plot_id].num_data, + populate_plot_data_from_ring_buffer(devices, interface, &interface->plots[plot_id], interface->plots[plot_id].num_data, interface->plots[plot_id].data, plot_legend); nvtop_line_plot(interface->plots[plot_id].plot_window, interface->plots[plot_id].num_data, @@ -1850,7 +1865,7 @@ void draw_gpu_info_ncurses(unsigned devices_count, struct list_head *devices, st draw_devices(devices, interface); if (!interface->setup_win.visible) { - draw_plots(interface); + draw_plots(devices, interface); draw_processes(devices, interface); } else { draw_setup_window(devices_count, devices, interface); From ebc8504a56dda418eb5710ada1ba90676232690e Mon Sep 17 00:00:00 2001 From: Srinivas Kandagatla Date: Sun, 28 Jun 2026 22:39:43 +0100 Subject: [PATCH 3/3] interface: add HVX and HMX utilization plots Add plot_hvx_util_rate and plot_hmx_util_rate to the plot information enum and wire up data collection in save_current_data_to_ring() and legend labels in populate_plot_data_from_ring_buffer(). Introduce plot_npu_default_draw_info() which extends the standard default with HVX and HMX plots. Devices whose vendor sets unit_name (i.e. NPU-like devices) get HVX and HMX in their default plot at startup; regular GPU devices are unaffected. Signed-off-by: Srinivas Kandagatla --- include/nvtop/interface_common.h | 2 ++ include/nvtop/interface_options.h | 4 ++++ src/interface.c | 16 ++++++++++++++++ src/nvtop.c | 8 ++++++-- 4 files changed, 28 insertions(+), 2 deletions(-) diff --git a/include/nvtop/interface_common.h b/include/nvtop/interface_common.h index 306298a7..8938ab5f 100644 --- a/include/nvtop/interface_common.h +++ b/include/nvtop/interface_common.h @@ -33,6 +33,8 @@ enum plot_information { plot_gpu_clock_rate, plot_gpu_mem_clock_rate, plot_effective_load_rate, + plot_hvx_util_rate, + plot_hmx_util_rate, plot_information_count }; diff --git a/include/nvtop/interface_options.h b/include/nvtop/interface_options.h index 53324aee..187bff62 100644 --- a/include/nvtop/interface_options.h +++ b/include/nvtop/interface_options.h @@ -80,6 +80,10 @@ inline plot_info_to_draw plot_remove_draw_info(enum plot_information reset_info, inline plot_info_to_draw plot_default_draw_info(void) { return (1 << plot_gpu_rate) | (1 << plot_gpu_mem_rate); } +static inline plot_info_to_draw plot_npu_default_draw_info(void) { + return plot_default_draw_info() | (1 << plot_hvx_util_rate) | (1 << plot_hmx_util_rate); +} + void alloc_interface_options_internals(char *config_file_location, unsigned num_devices, struct list_head *devices, nvtop_interface_option *options); diff --git a/src/interface.c b/src/interface.c index 25b6c8a9..46675d93 100644 --- a/src/interface.c +++ b/src/interface.c @@ -1748,6 +1748,16 @@ void save_current_data_to_ring(struct list_head *devices, struct nvtop_interface data_val = device->dynamic_info.effective_load_rate; } break; + case plot_hvx_util_rate: + if (GPUINFO_DYNAMIC_FIELD_VALID(&device->dynamic_info, hvx_util_rate)) { + data_val = device->dynamic_info.hvx_util_rate; + } + break; + case plot_hmx_util_rate: + if (GPUINFO_DYNAMIC_FIELD_VALID(&device->dynamic_info, hmx_util_rate)) { + data_val = device->dynamic_info.hmx_util_rate; + } + break; case plot_information_count: break; } @@ -1820,6 +1830,12 @@ static unsigned populate_plot_data_from_ring_buffer(struct list_head *devices, case plot_effective_load_rate: snprintf(plot_legend[in_processing], PLOT_MAX_LEGEND_SIZE, "%s%u eff. load%%", unit, dev_id); break; + case plot_hvx_util_rate: + snprintf(plot_legend[in_processing], PLOT_MAX_LEGEND_SIZE, "HVX %%"); + break; + case plot_hmx_util_rate: + snprintf(plot_legend[in_processing], PLOT_MAX_LEGEND_SIZE, "HMX %%"); + break; case plot_information_count: break; } diff --git a/src/nvtop.c b/src/nvtop.c index 61302d29..bf1f0979 100644 --- a/src/nvtop.c +++ b/src/nvtop.c @@ -272,14 +272,18 @@ int main(int argc, char **argv) { nvtop_interface_option allDevicesOptions; alloc_interface_options_internals(custom_config_file_path, allDevCount, &monitoredGpus, &allDevicesOptions); load_interface_options_from_config_file(allDevCount, &allDevicesOptions); - for (unsigned i = 0; i < allDevCount; ++i) { + struct gpu_info *dev; + unsigned i = 0; + list_for_each_entry(dev, &monitoredGpus, list) { // Nothing specified in the file if (!plot_isset_draw_info(plot_information_count, allDevicesOptions.gpu_specific_opts[i].to_draw)) { - allDevicesOptions.gpu_specific_opts[i].to_draw = plot_default_draw_info(); + allDevicesOptions.gpu_specific_opts[i].to_draw = + dev->vendor->unit_name ? plot_npu_default_draw_info() : plot_default_draw_info(); } else { allDevicesOptions.gpu_specific_opts[i].to_draw = plot_remove_draw_info(plot_information_count, allDevicesOptions.gpu_specific_opts[i].to_draw); } + i++; } if (!process_is_field_displayed(process_field_count, allDevicesOptions.process_fields_displayed)) { allDevicesOptions.process_fields_displayed = process_default_displayed_field();