From fae7d7f65d8ee84fd02b8c19a9ce0ebc15a463be Mon Sep 17 00:00:00 2001 From: Maxime Schmitt Date: Mon, 17 May 2021 23:14:36 +0200 Subject: [PATCH] Update interface codebase The interface layout selection has been updated to support the interface option. The interface option stores which data that needs to be plotted for each devices. The plot drawing code now supports the reverse plotting option. The interface uses the ring buffer, the updated version of the layout computation and plots the data specified by the interface options. --- include/nvtop/interface_internal_common.h | 4 +- include/nvtop/interface_layout_selection.h | 25 +- include/nvtop/interface_options.h | 34 +- include/nvtop/interface_setup_win.h | 2 +- include/nvtop/plot.h | 5 +- src/interface.c | 336 ++++++++--------- src/interface_layout_selection.c | 417 +++++++++++++++------ src/interface_setup_win.c | 12 +- src/plot.c | 15 +- 9 files changed, 504 insertions(+), 346 deletions(-) diff --git a/include/nvtop/interface_internal_common.h b/include/nvtop/interface_internal_common.h index 7e2a54c..71498ea 100644 --- a/include/nvtop/interface_internal_common.h +++ b/include/nvtop/interface_internal_common.h @@ -88,12 +88,12 @@ struct process_window { }; struct plot_window { - enum plot_type type; size_t num_data; double *data; WINDOW *win; WINDOW *plot_window; - size_t gpu_ids[2]; + unsigned num_devices_to_plot; + unsigned devices_ids[4]; }; enum setup_window_section { diff --git a/include/nvtop/interface_layout_selection.h b/include/nvtop/interface_layout_selection.h index 9e19242..fb5041c 100644 --- a/include/nvtop/interface_layout_selection.h +++ b/include/nvtop/interface_layout_selection.h @@ -1,29 +1,26 @@ #ifndef INTERFACE_LAYOUT_SELECTION_H__ #define INTERFACE_LAYOUT_SELECTION_H__ +#include "nvtop/interface_common.h" +#include "nvtop/interface_options.h" + #include -struct layout_selection; - -enum plot_type { - plot_gpu_max, - plot_gpu_solo, - plot_gpu_duo, -}; - struct window_position { unsigned posX, posY, sizeX, sizeY; }; -char *layout_as_string(struct layout_selection *layout); +// Should be fine +#define MAX_CHARTS 30 void compute_sizes_from_layout( - bool show_graphs, bool show_header, bool show_process, unsigned num_devices, - unsigned num_info_per_device, unsigned device_header_rows, + unsigned devices_count, unsigned device_header_rows, unsigned device_header_cols, unsigned rows, unsigned cols, - struct window_position *device_position, - struct window_position *process_position, unsigned *num_plots, - struct window_position **plot_positions, enum plot_type *plot_types, + const plot_info_to_draw to_draw[devices_count], + struct window_position device_positions[devices_count], unsigned *num_plots, + struct window_position plot_positions[MAX_CHARTS], + unsigned map_device_to_plot[devices_count], + struct window_position *process_position, struct window_position *setup_position); #endif // INTERFACE_LAYOUT_SELECTION_H__ diff --git a/include/nvtop/interface_options.h b/include/nvtop/interface_options.h index 5d01ed6..0f3ea4c 100644 --- a/include/nvtop/interface_options.h +++ b/include/nvtop/interface_options.h @@ -41,12 +41,29 @@ typedef struct nvtop_interface_option_struct { enum process_field sort_processes_by; // Specify the field used to order the processes bool sort_descending_order; // Sort in descenging order - int update_interval; // Interval between interface update in milliseconds + int update_interval; // Interval between interface update in milliseconds } nvtop_interface_option; +inline bool plot_isset_draw_info(enum plot_information check_info, + plot_info_to_draw to_draw) { + return to_draw & (1 << check_info); +} + +inline unsigned plot_count_draw_info(plot_info_to_draw to_draw) { + unsigned count = 0; + for (enum plot_information i = plot_gpu_rate; i < plot_information_count; + ++i) { + count += plot_isset_draw_info(i, to_draw); + } + return count; +} + inline plot_info_to_draw plot_add_draw_info(enum plot_information set_info, plot_info_to_draw to_draw) { - return to_draw | (1 << set_info); + if (plot_count_draw_info(to_draw) < 4) + return to_draw | (1 << set_info); + else + return to_draw; } inline plot_info_to_draw plot_remove_draw_info(enum plot_information reset_info, @@ -58,19 +75,6 @@ inline plot_info_to_draw plot_default_draw_info(void) { return (1 << plot_gpu_rate) | (1 << plot_gpu_mem_rate); } -inline bool plot_isset_draw_info(enum plot_information check_info, - plot_info_to_draw to_draw) { - return to_draw & (1 << check_info); -} - -inline unsigned plot_count_draw_info(plot_info_to_draw to_draw) { - unsigned count = 0; - for (enum plot_information i = plot_gpu_rate; i < plot_information_count; ++i) { - count += plot_isset_draw_info(i, to_draw) ? 1 : 0; - } - return count; -} - void alloc_interface_options_internals(char *config_file_location, unsigned num_devices, nvtop_interface_option *options); diff --git a/include/nvtop/interface_setup_win.h b/include/nvtop/interface_setup_win.h index 4eca314..9f61389 100644 --- a/include/nvtop/interface_setup_win.h +++ b/include/nvtop/interface_setup_win.h @@ -40,4 +40,4 @@ void draw_setup_window(unsigned devices_count, gpu_info *devices, void handle_setup_win_keypress(int keyId, struct nvtop_interface *interface); -#endif // INTERFACE_SETUP_WIN_H__ \ No newline at end of file +#endif // INTERFACE_SETUP_WIN_H__ diff --git a/include/nvtop/plot.h b/include/nvtop/plot.h index 75d389d..7c41144 100644 --- a/include/nvtop/plot.h +++ b/include/nvtop/plot.h @@ -23,11 +23,14 @@ #define __PLOT_H_ #include +#include #include +#define PLOT_MAX_LEGEND_SIZE 35 + void nvtop_line_plot(WINDOW *win, size_t num_data, const double *data, double min, double max, unsigned num_plots, - const char *legend[]); + bool legend_left, char legend[4][PLOT_MAX_LEGEND_SIZE]); void nvtop_bar_plot(WINDOW *win, size_t num_data, const double *data, double min, double max); diff --git a/src/interface.c b/src/interface.c index 9bfa395..8fe0ed6 100644 --- a/src/interface.c +++ b/src/interface.c @@ -217,56 +217,35 @@ static void initialize_gpu_mem_plot(struct plot_window *plot, unsigned cols = position->sizeX; cols -= 5; rows -= 2; - cols = cols / 2 * 2; plot->plot_window = newwin(rows, cols, position->posY + 1, position->posX + 4); draw_rectangle(plot->win, 3, 0, cols + 2, rows + 2); - mvwprintw(plot->win, 1 + rows * 3 / 4, 0, "25%%"); - mvwprintw(plot->win, 1 + rows / 4, 0, "75%%"); - mvwprintw(plot->win, 1 + rows / 2, 0, "50%%"); + mvwprintw(plot->win, 1 + rows * 3 / 4, 0, " 25"); + mvwprintw(plot->win, 1 + rows / 4, 0, " 75"); + mvwprintw(plot->win, 1 + rows / 2, 0, " 50"); mvwprintw(plot->win, 1, 0, "100"); - mvwprintw(plot->win, rows, 0, " 0%%"); + mvwprintw(plot->win, rows, 0, " 0"); plot->data = calloc(cols, sizeof(*plot->data)); plot->num_data = cols; } -static void alloc_plot_window(struct nvtop_interface *interface, +static void alloc_plot_window(unsigned devices_count, struct window_position *plot_positions, - enum plot_type plot_type) { - if (!plot_positions) { + unsigned map_device_to_plot[devices_count], + struct nvtop_interface *interface) { + if (!interface->num_plots) { interface->plots = NULL; return; } interface->plots = malloc(interface->num_plots * sizeof(*interface->plots)); - unsigned num_device_to_attribute; - unsigned max_device_per_plot; - switch (plot_type) { - case plot_gpu_solo: - num_device_to_attribute = interface->devices_count; - max_device_per_plot = 1; - break; - case plot_gpu_duo: - num_device_to_attribute = interface->devices_count; - max_device_per_plot = 2; - break; - case plot_gpu_max: - num_device_to_attribute = 1; - max_device_per_plot = 1; - break; - } - unsigned current_gpu_id = 0; for (size_t i = 0; i < interface->num_plots; ++i) { - interface->plots[i].gpu_ids[0] = current_gpu_id++; - if (num_device_to_attribute > 1 && max_device_per_plot > 1) { - interface->plots[i].type = plot_gpu_duo; - num_device_to_attribute -= 2; - interface->plots[i].gpu_ids[1] = current_gpu_id++; - } else { - if (plot_type == plot_gpu_max) - interface->plots[i].type = plot_gpu_max; - else - interface->plots[i].type = plot_gpu_solo; - num_device_to_attribute -= 1; + interface->plots[i].num_devices_to_plot = 0; + for (unsigned dev_id = 0; dev_id < devices_count; ++dev_id) { + if (map_device_to_plot[dev_id] == i) { + interface->plots[i] + .devices_ids[interface->plots[i].num_devices_to_plot] = dev_id; + interface->plots[i].num_devices_to_plot++; + } } interface->plots[i].win = newwin(plot_positions[i].sizeY, plot_positions[i].sizeX, @@ -292,18 +271,18 @@ static void initialize_all_windows(struct nvtop_interface *dwin) { unsigned int devices_count = dwin->devices_count; struct window_position device_positions[devices_count]; + unsigned map_device_to_plot[devices_count]; struct window_position process_position; - struct window_position *plot_positions = NULL; + struct window_position plot_positions[MAX_CHARTS]; struct window_position setup_position; - enum plot_type plot_type; - compute_sizes_from_layout(true, true, true, devices_count, 2, 3, - device_length(), rows - 1, cols, device_positions, - &process_position, &dwin->num_plots, - &plot_positions, &plot_type, &setup_position); + compute_sizes_from_layout(devices_count, 3, device_length(), rows - 1, cols, + dwin->options.device_information_drawn, + device_positions, &dwin->num_plots, plot_positions, + map_device_to_plot, &process_position, + &setup_position); - alloc_plot_window(dwin, plot_positions, plot_type); - free(plot_positions); + alloc_plot_window(devices_count, plot_positions, map_device_to_plot, dwin); for (unsigned int i = 0; i < devices_count; ++i) { alloc_device_window(device_positions[i].posY, device_positions[i].posX, @@ -1326,148 +1305,151 @@ static void draw_option_selection(struct nvtop_interface *interface) { wnoutrefresh(win); } -void update_interface_retained_data(unsigned devices_count, gpu_info *devices, - struct nvtop_interface *interface) { - for (unsigned i = 0; i < devices_count; ++i) { - ((unsigned(*)[interface->past_data.size_data_buffer])interface->past_data - .gpu_util)[i][interface->past_data.num_collected_data % - interface->past_data.size_data_buffer] = - devices[i].dynamic_info.gpu_util_rate; - ((unsigned(*)[interface->past_data.size_data_buffer])interface->past_data - .mem_util)[i][interface->past_data.num_collected_data % - interface->past_data.size_data_buffer] = - 100 * devices[i].dynamic_info.used_memory / - devices[i].dynamic_info.total_memory; - } - interface->past_data.num_collected_data++; -} - -static inline double *address_recent_left_old_right(size_t buffer_size, - double buffer[buffer_size], - size_t col_size, size_t col, - size_t offset_in_column) { - return &buffer[col * col_size + offset_in_column]; -} - -static inline double *address_old_left_recent_right(size_t buffer_size, - double buffer[buffer_size], - size_t col_size, size_t col, - size_t offset_in_column) { - return &buffer[buffer_size - (col + 1) * col_size + offset_in_column]; -} - -typedef double *(*plot_buffer_access_fn)(size_t, double *, size_t, size_t, - size_t); - -static void populate_plot_data_gpu_mem(struct nvtop_interface *interface, - struct plot_window *plot) { - plot_buffer_access_fn access_fn = interface->options.plot_left_to_right - ? address_recent_left_old_right - : address_old_left_recent_right; - memset(plot->data, 0, plot->num_data * sizeof(double)); - unsigned num_cols = plot->type == plot_gpu_duo ? 4 : 2; - unsigned upper_bound = - plot->num_data / num_cols > interface->past_data.num_collected_data - ? interface->past_data.num_collected_data - : plot->num_data / num_cols; - for (unsigned i = 0; i < upper_bound; ++i) { - unsigned(*gpudata)[interface->past_data.size_data_buffer] = - (unsigned(*)[interface->past_data.size_data_buffer]) - interface->past_data.gpu_util; - unsigned(*memdata)[interface->past_data.size_data_buffer] = - (unsigned(*)[interface->past_data.size_data_buffer]) - interface->past_data.mem_util; - *access_fn(plot->num_data, plot->data, num_cols, i, 0) = - gpudata[plot->gpu_ids[0]] - [(interface->past_data.size_data_buffer + - interface->past_data.num_collected_data - i - 1) % - interface->past_data.size_data_buffer]; - *access_fn(plot->num_data, plot->data, num_cols, i, 1) = - memdata[plot->gpu_ids[0]] - [(interface->past_data.size_data_buffer + - interface->past_data.num_collected_data - i - 1) % - interface->past_data.size_data_buffer]; - if (plot->type == plot_gpu_duo) { - *access_fn(plot->num_data, plot->data, num_cols, i, 2) = - gpudata[plot->gpu_ids[1]] - [(interface->past_data.size_data_buffer + - interface->past_data.num_collected_data - i - 1) % - interface->past_data.size_data_buffer]; - *access_fn(plot->num_data, plot->data, num_cols, i, 3) = - memdata[plot->gpu_ids[1]] - [(interface->past_data.size_data_buffer + - interface->past_data.num_collected_data - i - 1) % - interface->past_data.size_data_buffer]; +void save_current_data_to_ring(unsigned devices_count, gpu_info *devices, + struct nvtop_interface *interface) { + for (unsigned dev_id = 0; dev_id < devices_count; ++dev_id) { + unsigned data_index = 0; + for (enum plot_information info = plot_gpu_rate; + info < plot_information_count; ++info) { + if (plot_isset_draw_info( + info, interface->options.device_information_drawn[dev_id])) { + unsigned data_val = 0; + switch (info) { + case plot_gpu_rate: + if (IS_VALID(gpuinfo_gpu_util_rate_valid, + devices[dev_id].dynamic_info.valid)) + data_val = devices[dev_id].dynamic_info.gpu_util_rate; + break; + case plot_gpu_mem_rate: + if (IS_VALID(gpuinfo_mem_util_rate_valid, + devices[dev_id].dynamic_info.valid)) + data_val = devices[dev_id].dynamic_info.mem_util_rate; + break; + case plot_encoder_rate: + if (IS_VALID(gpuinfo_encoder_rate_valid, + devices[dev_id].dynamic_info.valid)) + data_val = devices[dev_id].dynamic_info.encoder_rate; + break; + case plot_decoder_rate: + if (IS_VALID(gpuinfo_decoder_rate_valid, + devices[dev_id].dynamic_info.valid)) + data_val = devices[dev_id].dynamic_info.decoder_rate; + break; + case plot_gpu_temperature: + if (IS_VALID(gpuinfo_gpu_temp_valid, + devices[dev_id].dynamic_info.valid)) { + data_val = devices[dev_id].dynamic_info.gpu_temp; + if (data_val > 100) + data_val = 100u; + } + break; + case plot_information_count: + break; + } + interface_ring_buffer_push(&interface->saved_data_ring, dev_id, + data_index, data_val); + data_index++; + } } } } -static void populate_plot_data_max_gpu_mem(struct nvtop_interface *interface, - struct plot_window *plot) { - // Populate data - memset(plot->data, 0, plot->num_data * sizeof(double)); - unsigned upper_bound = - plot->num_data / 2 > interface->past_data.num_collected_data - ? interface->past_data.num_collected_data - : plot->num_data / 2; - for (unsigned k = 0; k < interface->devices_count; ++k) { - for (unsigned i = 0; i < upper_bound; ++i) { - unsigned(*gpudata)[interface->past_data.size_data_buffer] = - (unsigned(*)[interface->past_data.size_data_buffer]) - interface->past_data.gpu_util; - unsigned(*memdata)[interface->past_data.size_data_buffer] = - (unsigned(*)[interface->past_data.size_data_buffer]) - interface->past_data.mem_util; - plot->data[i * 2] = - max(plot->data[i * 2], - gpudata[k][(interface->past_data.size_data_buffer + - interface->past_data.num_collected_data - i - 1) % - interface->past_data.size_data_buffer]); - plot->data[i * 2 + 1] = - max(plot->data[i * 2 + 1], - memdata[k][(interface->past_data.size_data_buffer + - interface->past_data.num_collected_data - i - 1) % - interface->past_data.size_data_buffer]); + +static unsigned populate_plot_data_from_ring_buffer( + const struct nvtop_interface *interface, struct plot_window *plot_win, + unsigned size_data_buff, double data[size_data_buff], + char plot_legend[4][PLOT_MAX_LEGEND_SIZE]) { + + memset(data, 0, size_data_buff * sizeof(double)); + unsigned total_to_draw = 0; + 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.device_information_drawn[dev_id]; + total_to_draw += plot_count_draw_info(to_draw); + } + + assert(size_data_buff % total_to_draw == 0); + unsigned max_data_to_copy = size_data_buff / total_to_draw; + double(*data_split)[total_to_draw] = (double(*)[total_to_draw])data; + + unsigned in_processing = 0; + 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.device_information_drawn[dev_id]; + 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 utilization (%%)", dev_id); + break; + case plot_gpu_mem_rate: + snprintf(plot_legend[in_processing], PLOT_MAX_LEGEND_SIZE, + "GPU %u memory (%%)", dev_id); + break; + case plot_encoder_rate: + snprintf(plot_legend[in_processing], PLOT_MAX_LEGEND_SIZE, + "GPU %u encoder (%%)", dev_id); + break; + case plot_decoder_rate: + snprintf(plot_legend[in_processing], PLOT_MAX_LEGEND_SIZE, + "GPU %u decoder (%%)", dev_id); + break; + case plot_gpu_temperature: + snprintf(plot_legend[in_processing], PLOT_MAX_LEGEND_SIZE, + "GPU %u temperature (celsius)", dev_id); + break; + case plot_information_count: + break; + } + // Copy the data + unsigned data_in_ring = interface_ring_buffer_data_stored( + &interface->saved_data_ring, dev_id, data_ring_index); + if (interface->options.plot_left_to_right) { + for (unsigned j = 0; j < data_in_ring && j < max_data_to_copy; ++j) { + data_split[j][in_processing] = interface_ring_buffer_get( + &interface->saved_data_ring, dev_id, data_ring_index, + data_in_ring - j - 1); + } + } else { + for (unsigned j = 0; j < data_in_ring && j < max_data_to_copy; ++j) { + data_split[max_data_to_copy - j - 1][in_processing] = + interface_ring_buffer_get(&interface->saved_data_ring, dev_id, + data_ring_index, + data_in_ring - j - 1); + } + } + data_ring_index++; + in_processing++; + } } } + return total_to_draw; } static void draw_plots(struct nvtop_interface *interface) { - for (unsigned i = 0; i < interface->num_plots; ++i) { - wnoutrefresh(interface->plots[i].win); - werase(interface->plots[i].plot_window); - switch (interface->plots[i].type) { - case plot_gpu_max: - populate_plot_data_max_gpu_mem(interface, &interface->plots[i]); - nvtop_line_plot(interface->plots[i].plot_window, - interface->plots[i].num_data, interface->plots[i].data, - 0., 100., 2, (const char *[2]){"MAX GPU", "MAX MEM"}); - break; - case plot_gpu_solo: { - populate_plot_data_gpu_mem(interface, &interface->plots[i]); - char gpuNum[8]; - snprintf(gpuNum, 8, "GPU %zu", interface->plots[i].gpu_ids[0]); - nvtop_line_plot(interface->plots[i].plot_window, - interface->plots[i].num_data, interface->plots[i].data, - 0., 100., 2, (const char *[2]){gpuNum, "MEM"}); - } break; - case plot_gpu_duo: { - populate_plot_data_gpu_mem(interface, &interface->plots[i]); - char gpuNum[2][8]; - char memNum[2][8]; - snprintf(gpuNum[0], 8, "GPU %zu", interface->plots[i].gpu_ids[0]); - snprintf(gpuNum[1], 8, "GPU %zu", interface->plots[i].gpu_ids[1]); - snprintf(memNum[0], 8, "MEM %zu", interface->plots[i].gpu_ids[0]); - snprintf(memNum[1], 8, "MEM %zu", interface->plots[i].gpu_ids[1]); - nvtop_line_plot( - interface->plots[i].plot_window, interface->plots[i].num_data, - interface->plots[i].data, 0., 100., 4, - (const char *[4]){gpuNum[0], memNum[0], gpuNum[1], memNum[1]}); - } break; - default: - break; - } - wnoutrefresh(interface->plots[i].plot_window); + for (unsigned plot_id = 0; plot_id < interface->num_plots; ++plot_id) { + wnoutrefresh(interface->plots[plot_id].win); + werase(interface->plots[plot_id].plot_window); + + char plot_legend[4][PLOT_MAX_LEGEND_SIZE]; + + unsigned num_lines = populate_plot_data_from_ring_buffer( + 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, + interface->plots[plot_id].data, 0., 100., num_lines, + !interface->options.plot_left_to_right, plot_legend); + + wnoutrefresh(interface->plots[plot_id].plot_window); } } diff --git a/src/interface_layout_selection.c b/src/interface_layout_selection.c index 55847e7..5c8ef5e 100644 --- a/src/interface_layout_selection.c +++ b/src/interface_layout_selection.c @@ -1,49 +1,262 @@ #include "nvtop/interface_layout_selection.h" #include "nvtop/interface.h" +#include "nvtop/interface_options.h" #include #include #include #include +#include #define max(a, b) ((a) > (b) ? (a) : (b)) #define min(a, b) ((a) < (b) ? (a) : (b)) -static void min_size_taken_by_process(unsigned rows, unsigned num_devices, - unsigned *rows_needed) { - *rows_needed = 1 + max(5, min(rows / 4, num_devices * 3)); +static unsigned min_rows_taken_by_process(unsigned rows, unsigned num_devices) { + return 1 + max(5, min(rows / 4, num_devices * 3)); } static const unsigned cols_needed_box_drawing = 5; -static void min_size_taken_by_plot(unsigned num_data_info_to_plot, - unsigned *rows_needed, - unsigned *cols_needed) { - *rows_needed = 7; - *cols_needed = cols_needed_box_drawing + 10 * num_data_info_to_plot; +static const unsigned min_plot_rows = 7; +static unsigned min_plot_cols(unsigned num_data_info_to_plot) { + return cols_needed_box_drawing + 10 * num_data_info_to_plot; +} + +// The merge works as follows: +// Try to merge consecutive devices pairs, i.e. (0,1),(1,2), ..., (n-1,n) +// And then every 2 separated devices pairs, i.e. (0,2),(1,3), ... ,(n-2,n) +// And then every p separated devices pairs, i.e. (0,p),(1,p), ... ,(n-p,n) +static bool who_to_merge(unsigned n_th_merge, unsigned devices_count, + const plot_info_to_draw to_draw[devices_count], + unsigned merge_ids[2]) { + unsigned valid_merge_encountered = 0; + unsigned merge1 = 0, merge2; + unsigned separation = 1; + + while (true) { + merge2 = merge1 + separation; + if (merge2 < devices_count) { + unsigned dev1_info_count = plot_count_draw_info(to_draw[merge1]); + unsigned dev2_info_count = plot_count_draw_info(to_draw[merge2]); + if (dev1_info_count > 0 && dev2_info_count > 0 && + dev1_info_count + dev2_info_count <= 4) { + if (valid_merge_encountered == n_th_merge) { + merge_ids[0] = merge1; + merge_ids[1] = merge2; + return true; + } + valid_merge_encountered++; + } + merge1++; + } else { + merge1 = 0; + separation++; + // Has exhausted all possibilities + if (separation >= devices_count) + return false; + } + } +} + +static bool move_plot_to_stack(unsigned stack_max_cols, unsigned plot_id, + unsigned destination_stack, unsigned plot_count, + unsigned stack_count, + unsigned num_info_per_plot[plot_count], + unsigned cols_allocated_in_stacks[stack_count], + unsigned plot_in_stack[plot_count]) { + if (plot_in_stack[plot_id] == destination_stack) + return false; + unsigned cols_after_merge = cols_allocated_in_stacks[destination_stack] + + min_plot_cols(num_info_per_plot[plot_id]); + if (cols_after_merge > stack_max_cols) { + return false; + } else { + cols_allocated_in_stacks[plot_in_stack[plot_id]] -= + min_plot_cols(num_info_per_plot[plot_id]); + cols_allocated_in_stacks[destination_stack] += num_info_per_plot[plot_id]; + plot_in_stack[plot_id] = destination_stack; + return true; + } +} + +static unsigned info_in_plot(unsigned plot_id, unsigned devices_count, + unsigned map_device_to_plot[devices_count], + const plot_info_to_draw to_draw[devices_count]) { + unsigned sum = 0; + for (unsigned dev_id = 0; dev_id < devices_count; ++dev_id) { + if (map_device_to_plot[dev_id] == plot_id) + sum += plot_count_draw_info(to_draw[dev_id]); + } + return sum; +} + +static unsigned cols_used_by_stack(unsigned stack_id, unsigned plot_count, + unsigned num_info_per_plot[plot_count], + unsigned plot_in_stack[plot_count]) { + unsigned sum = 0; + for (unsigned plot_id = 0; plot_id < plot_count; ++plot_id) { + if (plot_in_stack[plot_id] == stack_id) + sum += min_plot_cols(num_info_per_plot[plot_id]); + } + return sum; +} + +static unsigned +size_differences_between_stacks(unsigned plot_count, unsigned stack_count, + unsigned cols_allocated_in_stacks[plot_count]) { + unsigned sum = 0; + for (unsigned i = 0; i < stack_count; ++i) { + for (unsigned j = i + 1; j < stack_count; ++j) { + if (cols_allocated_in_stacks[i] > cols_allocated_in_stacks[j]) { + sum += cols_allocated_in_stacks[i] - cols_allocated_in_stacks[j]; + } else { + sum += cols_allocated_in_stacks[j] - cols_allocated_in_stacks[i]; + } + } + } + return sum; +} + +static void +preliminary_plot_positioning(unsigned rows_for_plots, unsigned plot_total_cols, + unsigned devices_count, + const plot_info_to_draw to_draw[devices_count], + unsigned map_device_to_plot[devices_count], + unsigned plot_in_stack[devices_count], + unsigned *num_plots, unsigned *plot_stack_count) { + + // Used to handle the merging process + unsigned num_info_per_devices[MAX_CHARTS]; + unsigned how_many_to_merge = 0; + + bool plot_anything = false; + for (unsigned i = 0; i < devices_count; ++i) { + num_info_per_devices[i] = plot_count_draw_info(to_draw[i]); + if (num_info_per_devices[i]) + plot_anything = true; + } + + // Get the most packed configuration possible with one chart per device if + // possible. + // If there is not enough place, merge the charts and retry. + unsigned num_plot_stacks = 0; + bool search_a_window_configuration = + plot_anything && rows_for_plots >= min_plot_rows; + while (search_a_window_configuration) { + search_a_window_configuration = false; + unsigned plot_id = 0; + num_plot_stacks = 1; + unsigned cols_used_in_stack = 0; + unsigned rows_left_to_allocate = rows_for_plots - min_plot_rows; + + for (unsigned i = 0; i < devices_count; ++i) { + unsigned num_info_for_this_plot = num_info_per_devices[i]; + if (num_info_for_this_plot == 0) + continue; + + unsigned cols_this_plot = min_plot_cols(num_info_for_this_plot); + // If there is enough horizontal space left, allocate side by side + if (plot_total_cols >= cols_this_plot + cols_used_in_stack) { + cols_used_in_stack += cols_this_plot; + plot_in_stack[plot_id] = num_plot_stacks - 1; + map_device_to_plot[i] = plot_id; + plot_id++; + } else { // Else allocate a new stack + if (rows_left_to_allocate >= min_plot_rows) { + rows_left_to_allocate -= min_plot_rows; + num_plot_stacks++; + cols_used_in_stack = 0; + } else { // Not enough space for a stack: retry and merge one more + unsigned to_merge[2]; + if (who_to_merge(how_many_to_merge, devices_count, to_draw, + to_merge)) { + num_info_per_devices[to_merge[0]] += + num_info_per_devices[to_merge[1]]; + num_info_per_devices[to_merge[1]] = 0; + how_many_to_merge++; + search_a_window_configuration = true; + } else { // No merge left + num_plot_stacks = 0; + } + break; + } + } + } + } + + // Compute the number of plots, the mapping and the size + *num_plots = 0; + *plot_stack_count = num_plot_stacks; + if (num_plot_stacks > 0) { + for (unsigned i = 0; i < devices_count; ++i) { + if (num_info_per_devices[i]) { + map_device_to_plot[i] = *num_plots; + *num_plots += 1; + } + } + // For the devices that were merged + for (unsigned i = 0; i < how_many_to_merge; ++i) { + unsigned to_merge[2]; + who_to_merge(i, devices_count, to_draw, to_merge); + map_device_to_plot[to_merge[1]] = map_device_to_plot[to_merge[0]]; + } + } +} + +static void balance_info_on_stacks_preserving_plot_order( + unsigned stack_max_cols, unsigned stack_count, unsigned plot_count, + unsigned num_info_per_plot[plot_count], + unsigned cols_allocated_in_stacks[stack_count], + unsigned plot_in_stack[plot_count]) { + if (stack_count > plot_count) { + stack_count = plot_count; + } + unsigned moving_plot_id = plot_count - 1; + while (moving_plot_id < plot_count) { + unsigned to_stack = plot_in_stack[moving_plot_id] + 1; + if (to_stack < stack_count) { + unsigned diff_sum_before = size_differences_between_stacks( + plot_count, stack_count, cols_allocated_in_stacks); + unsigned stack_before = plot_in_stack[moving_plot_id]; + if (move_plot_to_stack(stack_max_cols, moving_plot_id, to_stack, + plot_count, stack_count, num_info_per_plot, + cols_allocated_in_stacks, plot_in_stack)) { + unsigned diff_sum_after = size_differences_between_stacks( + plot_count, stack_count, cols_allocated_in_stacks); + if (diff_sum_after <= diff_sum_before) { + moving_plot_id = plot_count; + } else { + // Move back + move_plot_to_stack(stack_max_cols, moving_plot_id, stack_before, + plot_count, stack_count, num_info_per_plot, + cols_allocated_in_stacks, plot_in_stack); + } + } + } + moving_plot_id--; + } } void compute_sizes_from_layout( - bool show_graphs, bool show_header, bool show_process, unsigned num_devices, - unsigned num_info_per_device, unsigned device_header_rows, + unsigned devices_count, unsigned device_header_rows, unsigned device_header_cols, unsigned rows, unsigned cols, - struct window_position *device_positions, - struct window_position *process_position, unsigned *num_plots, - struct window_position **plot_positions, enum plot_type *plot_types, + const plot_info_to_draw to_draw[devices_count], + struct window_position device_positions[devices_count], unsigned *num_plots, + struct window_position plot_positions[MAX_CHARTS], + unsigned map_device_to_plot[devices_count], + struct window_position *process_position, struct window_position *setup_position) { unsigned min_rows_for_header = 0, header_stacks = 0, num_device_per_row = 0; - if (show_header) { - num_device_per_row = max(1, cols / device_header_cols); - header_stacks = num_devices / num_device_per_row + - ((num_devices % num_device_per_row) > 0); - if (num_devices % header_stacks == 0) - num_device_per_row = num_devices / header_stacks; - min_rows_for_header = header_stacks * device_header_rows; - } - unsigned min_rows_for_process = 0; - if (show_process) { - min_size_taken_by_process(rows, num_devices, &min_rows_for_process); - } + num_device_per_row = max(1, cols / device_header_cols); + header_stacks = devices_count / num_device_per_row + + ((devices_count % num_device_per_row) > 0); + if (devices_count % header_stacks == 0) + num_device_per_row = devices_count / header_stacks; + min_rows_for_header = header_stacks * device_header_rows; + + unsigned min_rows_for_process = + min_rows_taken_by_process(rows, devices_count); + // Not enough room for the header and process if (rows < min_rows_for_header + min_rows_for_process) { if (rows >= min_rows_for_header + 2) { // Shrink process @@ -55,83 +268,49 @@ void compute_sizes_from_layout( } unsigned rows_for_header = min_rows_for_header; unsigned rows_for_process = min_rows_for_process; - unsigned rows_left = rows - min_rows_for_header - min_rows_for_process; + unsigned rows_for_plots = rows - min_rows_for_header - min_rows_for_process; - unsigned min_plot_rows, min_plot_cols_solo, min_plot_cols_duo; - min_size_taken_by_plot(num_info_per_device, &min_plot_rows, - &min_plot_cols_solo); - min_size_taken_by_plot(num_info_per_device * 2, &min_plot_rows, - &min_plot_cols_duo); - - enum plot_type preferred_plot_type = plot_gpu_duo; - unsigned max_plot_per_row = cols / min_plot_cols_duo; - if (max_plot_per_row == 0) { - if (cols >= min_plot_cols_solo) { - max_plot_per_row = 1; - preferred_plot_type = plot_gpu_solo; - } else { - max_plot_per_row = 0; - } - } - unsigned num_borrow_line = 0; unsigned num_plot_stacks = 0; - if (max_plot_per_row > 0 && show_graphs) { - if (preferred_plot_type == plot_gpu_duo) { - num_plot_stacks = - (num_devices + (num_devices % 2)) / 2 / max_plot_per_row; - } else { - num_plot_stacks = num_devices / max_plot_per_row; - } - num_plot_stacks = max(num_plot_stacks, 1); - if (num_plot_stacks * min_plot_rows > rows_left) { - if (rows_left >= min_plot_rows) { - preferred_plot_type = plot_gpu_max; - num_plot_stacks = 1; - } else { - num_plot_stacks = 0; - } - } - if (num_plot_stacks > 0) { - switch (preferred_plot_type) { - case plot_gpu_duo: - *num_plots = (num_devices + (num_devices % 2)) / 2; - break; - case plot_gpu_solo: - *num_plots = num_devices; - break; - case plot_gpu_max: - *num_plots = 1; - break; - } - num_borrow_line = rows_left - num_plot_stacks * min_plot_rows; - } else { - goto no_plot; - } - } else { - no_plot: - num_borrow_line = rows_left; - *num_plots = 0; - } + unsigned plot_in_stack[MAX_CHARTS]; + preliminary_plot_positioning(rows_for_plots, cols, devices_count, to_draw, + map_device_to_plot, plot_in_stack, num_plots, + &num_plot_stacks); + // Transfer some lines to the header to separate the devices + unsigned transferable_lines = + rows_for_plots - num_plot_stacks * min_plot_rows; unsigned space_for_header = header_stacks == 0 ? 0 : header_stacks - 1; bool space_between_header_stack = false; - if (num_borrow_line >= space_for_header && show_header) { + if (transferable_lines >= space_for_header) { rows_for_header += space_for_header; - rows_left -= space_for_header; + rows_for_plots -= space_for_header; space_between_header_stack = true; } - if (*num_plots == 0 && show_process && rows_left > 0) { - rows_for_process += rows_left - 1; - } + + // Allocate additional plot stacks if there is enough vertical room if (num_plot_stacks > 0) { - // Allocate a new plot stack if there is enough vertical room while (num_plot_stacks < *num_plots && - rows_left / (num_plot_stacks + 1) >= 11 && - (num_plot_stacks + 1) * min_plot_rows <= rows_left) + rows_for_plots / (num_plot_stacks + 1) >= 11 && + (num_plot_stacks + 1) * min_plot_rows <= rows_for_plots) num_plot_stacks++; } - // Now compute the interface window positions + // Compute the cols used in each stacks to prepare balancing + unsigned num_info_per_plot[MAX_CHARTS]; + for (unsigned i = 0; i < *num_plots; ++i) { + num_info_per_plot[i] = + info_in_plot(i, devices_count, map_device_to_plot, to_draw); + } + unsigned cols_allocated_in_stacks[MAX_CHARTS]; + for (unsigned i = 0; i < num_plot_stacks; ++i) { + cols_allocated_in_stacks[i] = + cols_used_by_stack(i, *num_plots, num_info_per_plot, plot_in_stack); + } + + // Keep the plot order of apparition, but spread the plot on different stacks + balance_info_on_stacks_preserving_plot_order( + cols, num_plot_stacks, *num_plots, num_info_per_plot, + cols_allocated_in_stacks, plot_in_stack); // Device Information Header unsigned cols_header_left = cols - num_device_per_row * device_header_cols; @@ -147,7 +326,7 @@ void compute_sizes_from_layout( unsigned num_this_row = 0; unsigned headerPosX = space_before_header; unsigned headerPosY = 0; - for (unsigned i = 0; i < num_devices; ++i) { + for (unsigned i = 0; i < devices_count; ++i) { device_positions[i].posX = headerPosX; device_positions[i].posY = headerPosY; device_positions[i].sizeX = device_header_cols; @@ -163,46 +342,42 @@ void compute_sizes_from_layout( } unsigned rows_left_for_process = 0; - if (*num_plots == 0) { - *plot_positions = NULL; - } else { - *plot_positions = calloc(*num_plots, sizeof(**plot_positions)); - *plot_types = preferred_plot_type; - unsigned rows_per_stack = rows_left / num_plot_stacks; + if (*num_plots > 0) { + unsigned rows_per_stack = rows_for_plots / num_plot_stacks; if (rows_per_stack > 23) rows_per_stack = 23; - unsigned plot_per_row = *num_plots / num_plot_stacks; - unsigned stacks_with_extra_plot = *num_plots % num_plot_stacks; unsigned num_plot_done = 0; unsigned currentPosX = 0, currentPosY = rows_for_header; - for (unsigned i = 0; i < num_plot_stacks; ++i) { - unsigned plot_in_this_row = min(*num_plots - num_plot_done, plot_per_row); - if (stacks_with_extra_plot) { - plot_in_this_row++; - stacks_with_extra_plot--; + for (unsigned stack_id = 0; stack_id < num_plot_stacks; ++stack_id) { + unsigned plot_in_this_stack = 0; + for (unsigned j = 0; j < *num_plots; ++j) { + if (plot_in_stack[j] == stack_id) { + plot_in_this_stack++; + } } - unsigned cols_per_plot = cols / plot_in_this_row; - if (*plot_types == plot_gpu_duo) - cols_per_plot -= (cols_per_plot - cols_needed_box_drawing) % - (2 * num_info_per_device); - else - cols_per_plot -= - (cols_per_plot - cols_needed_box_drawing) % num_info_per_device; - unsigned extra_cols = cols - cols_per_plot * plot_in_this_row; - unsigned cols_between_plots = - extra_cols / (plot_in_this_row <= 1 ? 1 : plot_in_this_row - 1); - for (unsigned j = 0; j < plot_in_this_row; ++j) { - (*plot_positions)[num_plot_done].posX = currentPosX; - (*plot_positions)[num_plot_done].posY = currentPosY; - (*plot_positions)[num_plot_done].sizeX = cols_per_plot; - (*plot_positions)[num_plot_done].sizeY = rows_per_stack; - currentPosX += cols_per_plot + cols_between_plots; - num_plot_done++; + unsigned cols_per_plot = cols / plot_in_this_stack; + unsigned extra_cols_in_stack = 0; + for (unsigned j = 0; j < *num_plots; ++j) { + if (plot_in_stack[j] == stack_id) { + unsigned cols_for_this_plot = cols_per_plot; + cols_for_this_plot -= (cols_for_this_plot - cols_needed_box_drawing) % + num_info_per_plot[j]; + extra_cols_in_stack += cols_per_plot - cols_for_this_plot; + plot_positions[num_plot_done].posX = currentPosX; + plot_positions[num_plot_done].posY = currentPosY; + plot_positions[num_plot_done].sizeX = cols_for_this_plot; + plot_positions[num_plot_done].sizeY = rows_per_stack; + currentPosX += cols_for_this_plot + extra_cols_in_stack; + num_plot_done++; + } } currentPosY += rows_per_stack; currentPosX = 0; } - rows_left_for_process = rows_left - rows_per_stack * num_plot_stacks; + rows_left_for_process = rows_for_plots - rows_per_stack * num_plot_stacks; + } else { + // No plot displayed, allocate the leftover space to the processes + rows_for_process += rows_for_plots - 1; } process_position->posX = 0; diff --git a/src/interface_setup_win.c b/src/interface_setup_win.c index 7902870..7d98c72 100644 --- a/src/interface_setup_win.c +++ b/src/interface_setup_win.c @@ -20,6 +20,7 @@ */ #include "nvtop/interface_setup_win.h" +#include "nvtop/interface.h" #include "nvtop/interface_internal_common.h" #include "nvtop/interface_options.h" #include "nvtop/interface_ring_buffer.h" @@ -513,8 +514,8 @@ static const char *setup_window_shortcuts[] = {"Enter", "ESC", "Arrow keys", "+/-", "F12"}; static const char *setup_window_shortcut_description[] = { - "Toggle", "Exit", "Navigate Menu", - "Increment/Decrement Values", "Save Config"}; + "Toggle", "Exit", "Navigate Menu", "Increment/Decrement Values", + "Save Config"}; static void draw_setup_window_shortcuts(struct nvtop_interface *interface) { WINDOW *window = interface->process.option_window.option_selection_window; @@ -769,12 +770,7 @@ void handle_setup_win_keypress(int keyId, struct nvtop_interface *interface) { case KEY_F(2): case 27: interface->setup_win.visible = false; - for (unsigned i = 0; i < interface->num_plots; ++i) { - touchwin(interface->plots[i].win); - touchwin(interface->plots[i].plot_window); - werase(interface->process.option_window.option_selection_window); - touchwin(interface->process.process_win); - } + update_window_size_to_terminal_size(interface); break; case KEY_F(12): save_interface_options_to_config_file(interface->devices_count, diff --git a/src/plot.c b/src/plot.c index dcf6db4..c04d5a2 100644 --- a/src/plot.c +++ b/src/plot.c @@ -9,7 +9,7 @@ static inline int data_level(double rows, double data, double increment) { void nvtop_line_plot(WINDOW *win, size_t num_data, const double *data, double min, double max, unsigned num_plots, - const char *legend[]) { + bool legend_left, char legend[4][PLOT_MAX_LEGEND_SIZE]) { if (num_data == 0) return; int rows, cols; @@ -87,14 +87,15 @@ void nvtop_line_plot(WINDOW *win, size_t num_data, const double *data, int plot_y_position = 0; for (unsigned i = 0; i < num_plots && plot_y_position < rows; ++i) { if (legend[i]) { - size_t length = strlen(legend[i]); wattron(win, COLOR_PAIR(1 + i % 5)); - if (length < (size_t)cols) { - mvwprintw(win, plot_y_position, cols - length, "%s", legend[i]); + if (legend_left) { + mvwprintw(win, plot_y_position, 0, "%.*s", cols, legend[i]); } else { - wmove(win, plot_y_position, 0); - for (int j = 0; j < cols; ++j) { - waddch(win, legend[i][j]); + size_t length = strlen(legend[i]); + if (length <= (size_t)cols) { + mvwprintw(win, plot_y_position, cols - length, "%s", legend[i]); + } else { + mvwprintw(win, plot_y_position, 0, "%.*s", length - cols, legend[i]); } } wattroff(win, COLOR_PAIR(1 + i % 5));