diff --git a/include/nvtop/extract_gpuinfo_common.h b/include/nvtop/extract_gpuinfo_common.h index 9e4d1c9..6fdc211 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 % @@ -208,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/include/nvtop/interface_common.h b/include/nvtop/interface_common.h index 306298a..8938ab5 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 53324ae..187bff6 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/CMakeLists.txt b/src/CMakeLists.txt index b485cb4..8a0f1be 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 0000000..6e7b118 --- /dev/null +++ b/src/extract_npuinfo_qualcomm.c @@ -0,0 +1,224 @@ +#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", + .unit_name = "NPU", +}; + +__attribute__((constructor)) static void init_extract_gpuinfo_qcom_npu(void) { + register_gpu_vendor(&gpu_vendor_qcom_npu); +} diff --git a/src/interface.c b/src/interface.c index ae23199..46675d9 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 "); @@ -1736,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; } @@ -1748,7 +1770,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 +1793,48 @@ 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_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; @@ -1829,14 +1860,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 +1881,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); diff --git a/src/nvtop.c b/src/nvtop.c index 61302d2..bf1f097 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();