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));