diff --git a/nvme-print-stdout-top.c b/nvme-print-stdout-top.c index b74dda81f3..2f9148ddc2 100644 --- a/nvme-print-stdout-top.c +++ b/nvme-print-stdout-top.c @@ -1069,7 +1069,7 @@ static void stdout_top_print_subsys_topology_footer( struct dashboard_ctx *db_ctx, FILE *stream) { fprintf(stream, "\n--------------------------------------\n"); - fprintf(stream, "[ESC to go back to the previous screen, q to quit]\n"); + fprintf(stream, "[up/down keys to navigate, ESC to go back to the previous screen, q to quit]\n"); dashboard_set_footer_rows(db_ctx, 3); @@ -1162,6 +1162,57 @@ static int stdout_top_find_subsys_by_name(libnvme_subsystem_t *subsys_arr, return -1; } +static int handle_event_page_down(struct dashboard_ctx *db_ctx) +{ + int data_start, data_rows, frame_rows; + int scroll_down, scroll = 0; + + data_start = dashboard_get_data_start(db_ctx); + data_rows = dashboard_get_data_rows(db_ctx); + frame_rows = dashboard_get_frame_data_rows(db_ctx); + + /* + * Scroll down max up to one page frame from current data_start index. + * While scrolling down, ensure we don't move past the max available + * data rows. If we are already on the last page frame or there's no + * data if move past the current page frame then ignore the key press. + */ + scroll_down = data_start + frame_rows; + + if (scroll_down < data_rows) { + dashboard_set_data_start(db_ctx, scroll_down); + scroll = 1; + } + + return scroll; +} + +static int handle_event_page_up(struct dashboard_ctx *db_ctx) +{ + int data_start, frame_rows, off; + int scroll_up, scroll = 0; + + data_start = dashboard_get_data_start(db_ctx); + frame_rows = dashboard_get_frame_data_rows(db_ctx); + + /* + * Scroll back up max up to one page frame. Determine the num of data + * rows we can scroll back up based on current data start index and max + * num of rows which could be drawn in one page frame. If we're already + * on the first page frame and hence we can't scroll back then ignore + * the key press. + */ + off = min(data_start, frame_rows); + scroll_up = data_start - off; + + if (scroll_up != data_start) { + dashboard_set_data_start(db_ctx, scroll_up); + scroll = 1; + } + + return scroll; +} + /* * Draws subsys topology screen of susbystem @s * Returns: 0 if ESC key is pressed or needs to draw subsystem selection screen @@ -1257,7 +1308,22 @@ static int stdout_top_draw_subsys_topology_screen(struct dashboard_ctx *db_ctx, * topology screen. */ scroll = 0; - } /* else unknown event, ignore */ + } else if (event == EVENT_TYPE_KEY_PAGE_DOWN) { + + scroll = handle_event_page_down(db_ctx); + if (scroll) + goto draw; + + goto wait_for_event; + + } else if (event == EVENT_TYPE_KEY_PAGE_UP) { + + scroll = handle_event_page_up(db_ctx); + if (scroll) + goto draw; + + goto wait_for_event; + } /* else ignore */ } return ret; @@ -1404,7 +1470,7 @@ static int stdout_top_draw_subsys_screen(struct dashboard_ctx *db_ctx, table_print_stream(stream, t); fprintf(stream, "\n--------------------------------------\n"); - fprintf(stream, "[up/down arrow keys to navigate, Enter to view, q to quit]\n"); + fprintf(stream, "[up/down keys to navigate, Enter to view, q to quit]\n"); /* * Footer rows are calculated manually. @@ -1475,20 +1541,43 @@ void stdout_top(int refresh_interval) s = subsys_arr[subsys_idx]; quit = stdout_top_draw_subsys_topology_screen(db_ctx, stream, s); - scroll = 0; if (quit) break; - fallthrough; + + scroll = 0; + free(subsys_arr); + subsys_arr = NULL; + libnvme_free_global_ctx(ctx); + + ctx = stdout_top_rescan_topology(); + if (!ctx) { + quit = 1; + break; + } + + /* + * The topology may have changed while the subsystem + * dashboard was active. Restart from the first + * subsystem instead of trying to restore the previous + * screen position. + */ + subsys_idx = 0; + subsys_arr = stdout_top_build_subsys_arr(ctx, + &num_subsys); + if (!subsys_arr) + quit = 1; + + break; case EVENT_TYPE_NVME_UEVENT: { __cleanup_free char *subsys_name = NULL; - libnvme_subsystem_t s; + libnvme_subsystem_t s = subsys_arr[subsys_idx]; - s = subsys_arr[subsys_idx]; subsys_name = strdup(libnvme_subsystem_get_name(s)); if (!subsys_name) { quit = 1; break; } + free(subsys_arr); subsys_arr = NULL; libnvme_free_global_ctx(ctx); @@ -1511,6 +1600,7 @@ void stdout_top(int refresh_interval) if (subsys_idx < 0) subsys_idx = 0; + scroll = 0; break; } case EVENT_TYPE_KEY_DOWN: @@ -1582,6 +1672,22 @@ void stdout_top(int refresh_interval) */ scroll = 0; break; + case EVENT_TYPE_KEY_PAGE_DOWN: + scroll = handle_event_page_down(db_ctx); + if (scroll) { + subsys_idx = dashboard_get_data_start(db_ctx); + goto draw; + } + + goto wait_for_event; + case EVENT_TYPE_KEY_PAGE_UP: + scroll = handle_event_page_up(db_ctx); + if (scroll) { + subsys_idx = dashboard_get_data_start(db_ctx); + goto draw; + } + + goto wait_for_event; default: /* unknown event, ignore */ continue; } diff --git a/util/dashboard.c b/util/dashboard.c index 728afbd5e0..e6cecf12c5 100644 --- a/util/dashboard.c +++ b/util/dashboard.c @@ -382,10 +382,18 @@ static int wait_for_event(struct dashboard_ctx *db_ctx, interval_nsec = 0; } } + + /* + * While parsing an escape sequence, only wait for the remaining escape + * bytes and thus exclude montioring kobject uevent and sigwinch. + * Deferring uevents and SIGWINCH avoids treating non-key events as + * escape-sequence bytes. + */ while (1) { FD_ZERO(&set); FD_SET(term_fd, &set); - FD_SET(uevent_fd, &set); + if (!esc_seq) + FD_SET(uevent_fd, &set); again: ts.tv_sec = interval_sec; ts.tv_nsec = interval_nsec; @@ -399,7 +407,7 @@ static int wait_for_event(struct dashboard_ctx *db_ctx, clock_gettime(CLOCK_MONOTONIC, &t0); ret = pselect(max_fd + 1, &set, NULL, NULL, &ts, - &db_ctx->orig_set); + esc_seq ? NULL : &db_ctx->orig_set); if (ret < 0) { if (errno == EINTR) { /* Interrupted, signal is received ? */ @@ -463,6 +471,16 @@ static int wait_for_event(struct dashboard_ctx *db_ctx, if (errno == EINTR) continue; + /* + * We may have missed some netlink + * uevents from kernel. This is not a + * fatal error and we may synthesize it + * as an NVME kobject change event and + * force a topology rescan. + */ + if (errno == ENOBUFS) + return EVENT_TYPE_NVME_UEVENT; + nvme_show_perror("read from uevent fd"); return n; } @@ -509,6 +527,61 @@ static int wait_for_event(struct dashboard_ctx *db_ctx, } } +static enum event_type wait_for_esc_seq(struct dashboard_ctx *db_ctx) +{ + unsigned char c; + enum event_type event; + + event = wait_for_event(db_ctx, &c, 1); + switch (event) { + case EVENT_TYPE_ERROR: /* fall through */ + case EVENT_TYPE_KEY_QUIT: + return event; + case EVENT_TYPE_TIMEOUT: + return EVENT_TYPE_KEY_ESC; + default: + if (c == 91) { /* '[' key */ + event = wait_for_event(db_ctx, &c, 1); + switch (event) { + case EVENT_TYPE_ERROR: /* fall through */ + case EVENT_TYPE_KEY_QUIT: /* fall through */ + return event; + case EVENT_TYPE_TIMEOUT: + /* ignore */ + break; + default: + if (c == 65) + return EVENT_TYPE_KEY_UP; + else if (c == 66) + return EVENT_TYPE_KEY_DOWN; + else if (c == 53 || c == 54) { + char prev = c; + + event = wait_for_event(db_ctx, &c, 1); + switch (event) { + case EVENT_TYPE_ERROR: /* fall through */ + case EVENT_TYPE_KEY_QUIT: + return event; + case EVENT_TYPE_TIMEOUT: + /* ignore */ + break; + default: + if (c == 126 && prev == 53) + return EVENT_TYPE_KEY_PAGE_UP; + else if (c == 126 && prev == 54) + return EVENT_TYPE_KEY_PAGE_DOWN; + /* else ignore */ + break; + } + } + /* else ignore */ + break; + } + } + } + return EVENT_TYPE_IGNORE; +} + enum event_type dashboard_wait_for_event(struct dashboard_ctx *db_ctx) { int event; @@ -529,33 +602,9 @@ enum event_type dashboard_wait_for_event(struct dashboard_ctx *db_ctx) default: if (c == 27) { /* 'ESC' key */ /* read escape sequence */ - event = wait_for_event(db_ctx, &c, 1); - switch (event) { - case EVENT_TYPE_ERROR: /* fall through */ - case EVENT_TYPE_KEY_QUIT: + event = wait_for_esc_seq(db_ctx); + if (event != EVENT_TYPE_IGNORE) return event; - case EVENT_TYPE_TIMEOUT: - return EVENT_TYPE_KEY_ESC; - default: - if (c == 91) { /* '[' key */ - event = wait_for_event(db_ctx, &c, 1); - switch (event) { - case EVENT_TYPE_ERROR: /* fall through */ - case EVENT_TYPE_KEY_QUIT: - return event; - case EVENT_TYPE_TIMEOUT: - break; - default: - if (c == 65) - return EVENT_TYPE_KEY_UP; - else if (c == 66) - return EVENT_TYPE_KEY_DOWN; - /* else ignore */ - break; - } - } /* else ignore */ - break; - } } else if (c == '\n' || c == '\r') { return EVENT_TYPE_KEY_RETURN; } else if (c == 'q') { diff --git a/util/dashboard.h b/util/dashboard.h index 177f7004d1..400eefb9e2 100644 --- a/util/dashboard.h +++ b/util/dashboard.h @@ -6,18 +6,22 @@ struct dashboard_ctx; enum event_type { - EVENT_TYPE_ERROR = -1, /* error waiting for event */ - EVENT_TYPE_TIMEOUT, /* timed out waiting for event */ - - EVENT_TYPE_KEY_PRESS, /* key pressed event */ - EVENT_TYPE_KEY_ESC, /* ESC key is pressed*/ - EVENT_TYPE_KEY_UP, /* UP arrow key is pressed */ - EVENT_TYPE_KEY_DOWN, /* DOWN arrow key is pressed */ - EVENT_TYPE_KEY_RETURN, /* Return/Enter key is pressed */ - EVENT_TYPE_KEY_QUIT, /* q is pressed */ - - EVENT_TYPE_NVME_UEVENT, /* kobject uevent received; rescan topology */ - EVENT_TYPE_SIGWINCH, /* SIGWINCH received */ + EVENT_TYPE_ERROR = -1, /* error waiting for event */ + EVENT_TYPE_TIMEOUT, /* timed out waiting for event */ + + EVENT_TYPE_KEY_PRESS, /* key pressed event */ + EVENT_TYPE_KEY_ESC, /* ESC key is pressed*/ + EVENT_TYPE_KEY_UP, /* UP arrow key is pressed */ + EVENT_TYPE_KEY_DOWN, /* DOWN arrow key is pressed */ + EVENT_TYPE_KEY_PAGE_UP, /* Page UP key is pressed*/ + EVENT_TYPE_KEY_PAGE_DOWN, /* Page DOWN key is pressed */ + EVENT_TYPE_KEY_RETURN, /* Return/Enter key is pressed */ + EVENT_TYPE_KEY_QUIT, /* q is pressed */ + + EVENT_TYPE_NVME_UEVENT, /* kobject uevent received; rescan topology */ + EVENT_TYPE_SIGWINCH, /* SIGWINCH received */ + + EVENT_TYPE_IGNORE, /* ignore event */ }; int dashboard_get_interval(struct dashboard_ctx *db_ctx);