diff options
Diffstat (limited to 'src/spdk/app/spdk_top/spdk_top.c')
-rw-r--r-- | src/spdk/app/spdk_top/spdk_top.c | 1982 |
1 files changed, 1982 insertions, 0 deletions
diff --git a/src/spdk/app/spdk_top/spdk_top.c b/src/spdk/app/spdk_top/spdk_top.c new file mode 100644 index 000000000..8531cd32a --- /dev/null +++ b/src/spdk/app/spdk_top/spdk_top.c @@ -0,0 +1,1982 @@ +/*- + * BSD LICENSE + * + * Copyright (c) Intel Corporation. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Intel Corporation nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "spdk/stdinc.h" +#include "spdk/jsonrpc.h" +#include "spdk/rpc.h" +#include "spdk/event.h" +#include "spdk/util.h" +#include "spdk/env.h" + +#if defined __has_include +#if __has_include(<ncurses/panel.h>) +#include <ncurses/ncurses.h> +#include <ncurses/panel.h> +#include <ncurses/menu.h> +#else +#include <ncurses.h> +#include <panel.h> +#include <menu.h> +#endif +#else +#include <ncurses.h> +#include <panel.h> +#include <menu.h> +#endif + +#define RPC_MAX_THREADS 1024 +#define RPC_MAX_POLLERS 1024 +#define RPC_MAX_CORES 255 +#define MAX_THREAD_NAME 128 +#define MAX_POLLER_NAME 128 +#define MAX_THREADS 4096 +#define RR_MAX_VALUE 255 + +#define MAX_STRING_LEN 12289 /* 3x 4k monitors + 1 */ +#define TAB_WIN_HEIGHT 3 +#define TAB_WIN_LOCATION_ROW 1 +#define TABS_SPACING 2 +#define TABS_LOCATION_ROW 4 +#define TABS_LOCATION_COL 0 +#define TABS_DATA_START_ROW 3 +#define TABS_DATA_START_COL 2 +#define TABS_COL_COUNT 10 +#define MENU_WIN_HEIGHT 3 +#define MENU_WIN_SPACING 4 +#define MENU_WIN_LOCATION_COL 0 +#define RR_WIN_WIDTH 32 +#define RR_WIN_HEIGHT 5 +#define MAX_THREAD_NAME_LEN 26 +#define MAX_THREAD_COUNT_STR_LEN 14 +#define MAX_POLLER_NAME_LEN 36 +#define MAX_POLLER_COUNT_STR_LEN 16 +#define MAX_POLLER_TYPE_STR_LEN 8 +#define MAX_CORE_MASK_STR_LEN 16 +#define MAX_CORE_STR_LEN 6 +#define MAX_TIME_STR_LEN 10 +#define MAX_PERIOD_STR_LEN 12 +#define WINDOW_HEADER 12 +#define FROM_HEX 16 + +enum tabs { + THREADS_TAB, + POLLERS_TAB, + CORES_TAB, + NUMBER_OF_TABS, +}; + +enum spdk_poller_type { + SPDK_ACTIVE_POLLER, + SPDK_TIMED_POLLER, + SPDK_PAUSED_POLLER, + SPDK_POLLER_TYPES_COUNT, +}; + +struct col_desc { + const char *name; + uint8_t name_len; + uint8_t max_data_string; + bool disabled; +}; + +struct run_counter_history { + char *poller_name; + uint64_t thread_id; + uint64_t last_run_counter; + TAILQ_ENTRY(run_counter_history) link; +}; + +struct core_info { + uint32_t core; + char core_mask[MAX_CORE_MASK_STR_LEN]; + uint64_t threads_count; + uint64_t pollers_count; + uint64_t idle; + uint64_t last_idle; + uint64_t busy; + uint64_t last_busy; +}; + +uint8_t g_sleep_time = 1; +struct rpc_thread_info *g_thread_info[MAX_THREADS]; +const char *poller_type_str[SPDK_POLLER_TYPES_COUNT] = {"Active", "Timed", "Paused"}; +const char *g_tab_title[NUMBER_OF_TABS] = {"[1] THREADS", "[2] POLLERS", "[3] CORES"}; +struct spdk_jsonrpc_client *g_rpc_client; +static TAILQ_HEAD(, run_counter_history) g_run_counter_history = TAILQ_HEAD_INITIALIZER( + g_run_counter_history); +struct core_info g_cores_history[RPC_MAX_CORES]; +WINDOW *g_menu_win, *g_tab_win[NUMBER_OF_TABS], *g_tabs[NUMBER_OF_TABS]; +PANEL *g_panels[NUMBER_OF_TABS]; +uint16_t g_max_row, g_max_col; +uint16_t g_data_win_size, g_max_data_rows; +uint32_t g_last_threads_count, g_last_pollers_count, g_last_cores_count; +uint8_t g_current_sort_col[NUMBER_OF_TABS] = {0, 0, 0}; +static struct col_desc g_col_desc[NUMBER_OF_TABS][TABS_COL_COUNT] = { + { {.name = "Thread name", .max_data_string = MAX_THREAD_NAME_LEN}, + {.name = "Core", .max_data_string = MAX_CORE_STR_LEN}, + {.name = "Active pollers", .max_data_string = MAX_POLLER_COUNT_STR_LEN}, + {.name = "Timed pollers", .max_data_string = MAX_POLLER_COUNT_STR_LEN}, + {.name = "Paused pollers", .max_data_string = MAX_POLLER_COUNT_STR_LEN}, + {.name = "Idle [us]", .max_data_string = MAX_TIME_STR_LEN}, + {.name = "Busy [us]", .max_data_string = MAX_TIME_STR_LEN}, + {.name = (char *)NULL} + }, + { {.name = "Poller name", .max_data_string = MAX_POLLER_NAME_LEN}, + {.name = "Type", .max_data_string = MAX_POLLER_TYPE_STR_LEN}, + {.name = "On thread", .max_data_string = MAX_THREAD_NAME_LEN}, + {.name = "Run count", .max_data_string = MAX_TIME_STR_LEN}, + {.name = "Period [us]", .max_data_string = MAX_PERIOD_STR_LEN}, + {.name = (char *)NULL} + }, + { {.name = "Core", .max_data_string = MAX_CORE_STR_LEN}, + {.name = "Thread count", .max_data_string = MAX_THREAD_COUNT_STR_LEN}, + {.name = "Poller count", .max_data_string = MAX_POLLER_COUNT_STR_LEN}, + {.name = "Idle [us]", .max_data_string = MAX_TIME_STR_LEN}, + {.name = "Busy [us]", .max_data_string = MAX_TIME_STR_LEN}, + {.name = (char *)NULL} + } +}; + +struct rpc_thread_info { + char *name; + uint64_t id; + uint32_t core_num; + char *cpumask; + uint64_t busy; + uint64_t last_busy; + uint64_t idle; + uint64_t last_idle; + uint64_t active_pollers_count; + uint64_t timed_pollers_count; + uint64_t paused_pollers_count; +}; + +struct rpc_threads { + uint64_t threads_count; + struct rpc_thread_info thread_info[RPC_MAX_THREADS]; +}; + +struct rpc_threads_stats { + uint64_t tick_rate; + struct rpc_threads threads; +}; + +struct rpc_poller_info { + char *name; + char *state; + uint64_t run_count; + uint64_t busy_count; + uint64_t period_ticks; + enum spdk_poller_type type; + char thread_name[MAX_THREAD_NAME]; + uint64_t thread_id; +}; + +struct rpc_pollers { + uint64_t pollers_count; + struct rpc_poller_info pollers[RPC_MAX_POLLERS]; +}; + +struct rpc_poller_thread_info { + char *name; + uint64_t id; + struct rpc_pollers active_pollers; + struct rpc_pollers timed_pollers; + struct rpc_pollers paused_pollers; +}; + +struct rpc_pollers_threads { + uint64_t threads_count; + struct rpc_poller_thread_info threads[RPC_MAX_THREADS]; +}; + +struct rpc_pollers_stats { + uint64_t tick_rate; + struct rpc_pollers_threads pollers_threads; +}; + +struct rpc_core_thread_info { + char *name; + uint64_t id; + char *cpumask; + uint64_t elapsed; +}; + +struct rpc_core_threads { + uint64_t threads_count; + struct rpc_core_thread_info thread[RPC_MAX_THREADS]; +}; + +struct rpc_core_info { + uint32_t lcore; + uint64_t busy; + uint64_t idle; + struct rpc_core_threads threads; +}; + +struct rpc_cores { + uint64_t cores_count; + struct rpc_core_info core[RPC_MAX_CORES]; +}; + +struct rpc_cores_stats { + uint64_t tick_rate; + struct rpc_cores cores; +}; + +struct rpc_threads_stats g_threads_stats; +struct rpc_pollers_stats g_pollers_stats; +struct rpc_cores_stats g_cores_stats; + +static void +init_str_len(void) +{ + int i, j; + + for (i = 0; i < NUMBER_OF_TABS; i++) { + for (j = 0; g_col_desc[i][j].name != NULL; j++) { + g_col_desc[i][j].name_len = strlen(g_col_desc[i][j].name); + } + } +} + +static void +free_rpc_threads_stats(struct rpc_threads_stats *req) +{ + uint64_t i; + + for (i = 0; i < req->threads.threads_count; i++) { + free(req->threads.thread_info[i].name); + req->threads.thread_info[i].name = NULL; + free(req->threads.thread_info[i].cpumask); + req->threads.thread_info[i].cpumask = NULL; + } +} + +static const struct spdk_json_object_decoder rpc_thread_info_decoders[] = { + {"name", offsetof(struct rpc_thread_info, name), spdk_json_decode_string}, + {"id", offsetof(struct rpc_thread_info, id), spdk_json_decode_uint64}, + {"cpumask", offsetof(struct rpc_thread_info, cpumask), spdk_json_decode_string}, + {"busy", offsetof(struct rpc_thread_info, busy), spdk_json_decode_uint64}, + {"idle", offsetof(struct rpc_thread_info, idle), spdk_json_decode_uint64}, + {"active_pollers_count", offsetof(struct rpc_thread_info, active_pollers_count), spdk_json_decode_uint64}, + {"timed_pollers_count", offsetof(struct rpc_thread_info, timed_pollers_count), spdk_json_decode_uint64}, + {"paused_pollers_count", offsetof(struct rpc_thread_info, paused_pollers_count), spdk_json_decode_uint64}, +}; + +static int +rpc_decode_threads_object(const struct spdk_json_val *val, void *out) +{ + struct rpc_thread_info *info = out; + + return spdk_json_decode_object(val, rpc_thread_info_decoders, + SPDK_COUNTOF(rpc_thread_info_decoders), info); +} + +static int +rpc_decode_threads_array(const struct spdk_json_val *val, void *out) +{ + struct rpc_threads *threads = out; + + return spdk_json_decode_array(val, rpc_decode_threads_object, threads->thread_info, RPC_MAX_THREADS, + &threads->threads_count, sizeof(struct rpc_thread_info)); +} + +static const struct spdk_json_object_decoder rpc_threads_stats_decoders[] = { + {"tick_rate", offsetof(struct rpc_threads_stats, tick_rate), spdk_json_decode_uint64}, + {"threads", offsetof(struct rpc_threads_stats, threads), rpc_decode_threads_array}, +}; + +static void +free_rpc_poller(struct rpc_poller_info *poller) +{ + free(poller->name); + poller->name = NULL; + free(poller->state); + poller->state = NULL; +} + +static void +free_rpc_pollers_stats(struct rpc_pollers_stats *req) +{ + struct rpc_poller_thread_info *thread; + uint64_t i, j; + + for (i = 0; i < req->pollers_threads.threads_count; i++) { + thread = &req->pollers_threads.threads[i]; + + for (j = 0; j < thread->active_pollers.pollers_count; j++) { + free_rpc_poller(&thread->active_pollers.pollers[j]); + } + + for (j = 0; j < thread->timed_pollers.pollers_count; j++) { + free_rpc_poller(&thread->timed_pollers.pollers[j]); + } + + for (j = 0; j < thread->paused_pollers.pollers_count; j++) { + free_rpc_poller(&thread->paused_pollers.pollers[j]); + } + + free(thread->name); + thread->name = NULL; + } +} + +static void +free_rpc_cores_stats(struct rpc_cores_stats *req) +{ + struct rpc_core_info *core; + struct rpc_core_thread_info *thread; + uint64_t i, j; + + for (i = 0; i < req->cores.cores_count; i++) { + core = &req->cores.core[i]; + + for (j = 0; j < core->threads.threads_count; j++) { + thread = &core->threads.thread[j]; + + free(thread->name); + free(thread->cpumask); + } + } +} + +static const struct spdk_json_object_decoder rpc_pollers_decoders[] = { + {"name", offsetof(struct rpc_poller_info, name), spdk_json_decode_string}, + {"state", offsetof(struct rpc_poller_info, state), spdk_json_decode_string}, + {"run_count", offsetof(struct rpc_poller_info, run_count), spdk_json_decode_uint64}, + {"busy_count", offsetof(struct rpc_poller_info, busy_count), spdk_json_decode_uint64}, + {"period_ticks", offsetof(struct rpc_poller_info, period_ticks), spdk_json_decode_uint64, true}, +}; + +static int +rpc_decode_pollers_object(const struct spdk_json_val *val, void *out) +{ + struct rpc_poller_info *info = out; + + return spdk_json_decode_object(val, rpc_pollers_decoders, SPDK_COUNTOF(rpc_pollers_decoders), info); +} + +static int +rpc_decode_pollers_array(const struct spdk_json_val *val, void *out) +{ + struct rpc_pollers *pollers = out; + + return spdk_json_decode_array(val, rpc_decode_pollers_object, pollers->pollers, RPC_MAX_THREADS, + &pollers->pollers_count, sizeof(struct rpc_poller_info)); +} + +static const struct spdk_json_object_decoder rpc_pollers_threads_decoders[] = { + {"name", offsetof(struct rpc_poller_thread_info, name), spdk_json_decode_string}, + {"id", offsetof(struct rpc_poller_thread_info, id), spdk_json_decode_uint64}, + {"active_pollers", offsetof(struct rpc_poller_thread_info, active_pollers), rpc_decode_pollers_array}, + {"timed_pollers", offsetof(struct rpc_poller_thread_info, timed_pollers), rpc_decode_pollers_array}, + {"paused_pollers", offsetof(struct rpc_poller_thread_info, paused_pollers), rpc_decode_pollers_array}, +}; + +static int +rpc_decode_pollers_threads_object(const struct spdk_json_val *val, void *out) +{ + struct rpc_poller_thread_info *info = out; + + return spdk_json_decode_object(val, rpc_pollers_threads_decoders, + SPDK_COUNTOF(rpc_pollers_threads_decoders), info); +} + +static int +rpc_decode_pollers_threads_array(const struct spdk_json_val *val, void *out) +{ + struct rpc_pollers_threads *pollers_threads = out; + + return spdk_json_decode_array(val, rpc_decode_pollers_threads_object, pollers_threads->threads, + RPC_MAX_THREADS, &pollers_threads->threads_count, sizeof(struct rpc_poller_thread_info)); +} + +static const struct spdk_json_object_decoder rpc_pollers_stats_decoders[] = { + {"tick_rate", offsetof(struct rpc_pollers_stats, tick_rate), spdk_json_decode_uint64}, + {"threads", offsetof(struct rpc_pollers_stats, pollers_threads), rpc_decode_pollers_threads_array}, +}; + +static const struct spdk_json_object_decoder rpc_core_thread_info_decoders[] = { + {"name", offsetof(struct rpc_core_thread_info, name), spdk_json_decode_string}, + {"id", offsetof(struct rpc_core_thread_info, id), spdk_json_decode_uint64}, + {"cpumask", offsetof(struct rpc_core_thread_info, cpumask), spdk_json_decode_string}, + {"elapsed", offsetof(struct rpc_core_thread_info, elapsed), spdk_json_decode_uint64}, +}; + +static int +rpc_decode_core_threads_object(const struct spdk_json_val *val, void *out) +{ + struct rpc_core_thread_info *info = out; + + return spdk_json_decode_object(val, rpc_core_thread_info_decoders, + SPDK_COUNTOF(rpc_core_thread_info_decoders), info); +} + +static int +rpc_decode_cores_lw_threads(const struct spdk_json_val *val, void *out) +{ + struct rpc_core_threads *threads = out; + + return spdk_json_decode_array(val, rpc_decode_core_threads_object, threads->thread, RPC_MAX_THREADS, + &threads->threads_count, sizeof(struct rpc_core_thread_info)); +} + +static const struct spdk_json_object_decoder rpc_core_info_decoders[] = { + {"lcore", offsetof(struct rpc_core_info, lcore), spdk_json_decode_uint32}, + {"busy", offsetof(struct rpc_core_info, busy), spdk_json_decode_uint64}, + {"idle", offsetof(struct rpc_core_info, idle), spdk_json_decode_uint64}, + {"lw_threads", offsetof(struct rpc_core_info, threads), rpc_decode_cores_lw_threads}, +}; + +static int +rpc_decode_core_object(const struct spdk_json_val *val, void *out) +{ + struct rpc_core_info *info = out; + + return spdk_json_decode_object(val, rpc_core_info_decoders, + SPDK_COUNTOF(rpc_core_info_decoders), info); +} + +static int +rpc_decode_cores_array(const struct spdk_json_val *val, void *out) +{ + struct rpc_cores *cores = out; + + return spdk_json_decode_array(val, rpc_decode_core_object, cores->core, + RPC_MAX_THREADS, &cores->cores_count, sizeof(struct rpc_core_info)); +} + +static const struct spdk_json_object_decoder rpc_cores_stats_decoders[] = { + {"tick_rate", offsetof(struct rpc_cores_stats, tick_rate), spdk_json_decode_uint64}, + {"reactors", offsetof(struct rpc_cores_stats, cores), rpc_decode_cores_array}, +}; + + +static int +rpc_send_req(char *rpc_name, struct spdk_jsonrpc_client_response **resp) +{ + struct spdk_jsonrpc_client_response *json_resp = NULL; + struct spdk_json_write_ctx *w; + struct spdk_jsonrpc_client_request *request; + int rc; + + request = spdk_jsonrpc_client_create_request(); + if (request == NULL) { + return -ENOMEM; + } + + w = spdk_jsonrpc_begin_request(request, 1, rpc_name); + spdk_jsonrpc_end_request(request, w); + spdk_jsonrpc_client_send_request(g_rpc_client, request); + + do { + rc = spdk_jsonrpc_client_poll(g_rpc_client, 1); + } while (rc == 0 || rc == -ENOTCONN); + + if (rc <= 0) { + return -1; + } + + json_resp = spdk_jsonrpc_client_get_response(g_rpc_client); + if (json_resp == NULL) { + return -1; + } + + /* Check for error response */ + if (json_resp->error != NULL) { + return -1; + } + + assert(json_resp->result); + + *resp = json_resp; + + return 0; +} + +static int +get_data(void) +{ + struct spdk_jsonrpc_client_response *json_resp = NULL; + struct rpc_thread_info *thread_info; + struct rpc_core_info *core_info; + uint64_t i, j; + int rc = 0; + + rc = rpc_send_req("thread_get_stats", &json_resp); + if (rc) { + goto end; + } + + /* Decode json */ + if (spdk_json_decode_object(json_resp->result, rpc_threads_stats_decoders, + SPDK_COUNTOF(rpc_threads_stats_decoders), &g_threads_stats)) { + rc = -EINVAL; + goto end; + } + + spdk_jsonrpc_client_free_response(json_resp); + + for (i = 0; i < g_threads_stats.threads.threads_count; i++) { + thread_info = &g_threads_stats.threads.thread_info[i]; + g_thread_info[thread_info->id] = thread_info; + } + + rc = rpc_send_req("thread_get_pollers", &json_resp); + if (rc) { + goto end; + } + + /* Decode json */ + memset(&g_pollers_stats, 0, sizeof(g_pollers_stats)); + if (spdk_json_decode_object(json_resp->result, rpc_pollers_stats_decoders, + SPDK_COUNTOF(rpc_pollers_stats_decoders), &g_pollers_stats)) { + rc = -EINVAL; + goto end; + } + + spdk_jsonrpc_client_free_response(json_resp); + + rc = rpc_send_req("framework_get_reactors", &json_resp); + if (rc) { + goto end; + } + + /* Decode json */ + memset(&g_cores_stats, 0, sizeof(g_cores_stats)); + if (spdk_json_decode_object(json_resp->result, rpc_cores_stats_decoders, + SPDK_COUNTOF(rpc_cores_stats_decoders), &g_cores_stats)) { + rc = -EINVAL; + goto end; + } + + for (i = 0; i < g_cores_stats.cores.cores_count; i++) { + core_info = &g_cores_stats.cores.core[i]; + + for (j = 0; j < core_info->threads.threads_count; j++) { + g_thread_info[core_info->threads.thread[j].id]->core_num = core_info->lcore; + } + } + +end: + spdk_jsonrpc_client_free_response(json_resp); + return rc; +} + +static void +free_data(void) +{ + free_rpc_threads_stats(&g_threads_stats); + free_rpc_pollers_stats(&g_pollers_stats); + free_rpc_cores_stats(&g_cores_stats); +} + +enum str_alignment { + ALIGN_LEFT, + ALIGN_RIGHT, +}; + +static void +print_max_len(WINDOW *win, int row, uint16_t col, uint16_t max_len, enum str_alignment alignment, + const char *string) +{ + const char dots[] = "..."; + int DOTS_STR_LEN = sizeof(dots) / sizeof(dots[0]); + char tmp_str[MAX_STRING_LEN]; + int len, max_col, max_str, cmp_len; + int max_row; + + len = strlen(string); + getmaxyx(win, max_row, max_col); + + if (row > max_row) { + /* We are in a process of resizing and this may happen */ + return; + } + + if (max_len != 0 && col + max_len < max_col) { + max_col = col + max_len; + } + + max_str = max_col - col; + + if (max_str <= DOTS_STR_LEN + 1) { + /* No space to print anything, but we have to let a user know about it */ + mvwprintw(win, row, max_col - DOTS_STR_LEN - 1, "..."); + refresh(); + wrefresh(win); + return; + } + + if (max_len) { + if (alignment == ALIGN_LEFT) { + snprintf(tmp_str, max_str, "%s%*c", string, max_len - len - 1, ' '); + } else { + snprintf(tmp_str, max_str, "%*c%s", max_len - len - 1, ' ', string); + } + cmp_len = max_len - 1; + } else { + snprintf(tmp_str, max_str, "%s", string); + cmp_len = len; + } + + if (col + cmp_len > max_col - 1) { + snprintf(&tmp_str[max_str - DOTS_STR_LEN - 2], DOTS_STR_LEN, "%s", dots); + } + + mvwprintw(win, row, col, tmp_str); + + refresh(); + wrefresh(win); +} + + +static void +draw_menu_win(void) +{ + wbkgd(g_menu_win, COLOR_PAIR(2)); + box(g_menu_win, 0, 0); + print_max_len(g_menu_win, 1, 1, 0, ALIGN_LEFT, + " [q] Quit | [1-3] TAB selection | [PgUp] Previous page | [PgDown] Next page | [c] Columns | [s] Sorting | [r] Refresh rate"); +} + +static void +draw_tab_win(enum tabs tab) +{ + uint16_t col; + uint8_t white_spaces = TABS_SPACING * NUMBER_OF_TABS; + + wbkgd(g_tab_win[tab], COLOR_PAIR(2)); + box(g_tab_win[tab], 0, 0); + + col = ((g_max_col - white_spaces) / NUMBER_OF_TABS / 2) - (strlen(g_tab_title[tab]) / 2) - + TABS_SPACING; + print_max_len(g_tab_win[tab], 1, col, 0, ALIGN_LEFT, g_tab_title[tab]); +} + +static void +draw_tabs(enum tabs tab_index, uint8_t sort_col) +{ + struct col_desc *col_desc = g_col_desc[tab_index]; + WINDOW *tab = g_tabs[tab_index]; + int i, j; + uint16_t offset, draw_offset; + + for (i = 0; col_desc[i].name != NULL; i++) { + if (col_desc[i].disabled) { + continue; + } + + offset = 1; + for (j = i; j != 0; j--) { + if (!col_desc[j - 1].disabled) { + offset += col_desc[j - 1].max_data_string; + offset += col_desc[j - 1].name_len % 2 + 1; + } + } + + draw_offset = offset + (col_desc[i].max_data_string / 2) - (col_desc[i].name_len / 2); + + if (i == sort_col) { + wattron(tab, COLOR_PAIR(3)); + print_max_len(tab, 1, draw_offset, 0, ALIGN_LEFT, col_desc[i].name); + wattroff(tab, COLOR_PAIR(3)); + } else { + print_max_len(tab, 1, draw_offset, 0, ALIGN_LEFT, col_desc[i].name); + } + + if (offset != 1) { + print_max_len(tab, 1, offset - 1, 0, ALIGN_LEFT, "|"); + } + } + + print_max_len(tab, 2, 1, 0, ALIGN_LEFT, ""); /* Move to next line */ + whline(tab, ACS_HLINE, MAX_STRING_LEN); + box(tab, 0, 0); + wrefresh(tab); +} + +static void +resize_interface(enum tabs tab) +{ + int i; + + clear(); + wclear(g_menu_win); + mvwin(g_menu_win, g_max_row - MENU_WIN_SPACING, MENU_WIN_LOCATION_COL); + wresize(g_menu_win, MENU_WIN_HEIGHT, g_max_col); + draw_menu_win(); + + for (i = 0; i < NUMBER_OF_TABS; i++) { + wclear(g_tabs[i]); + wresize(g_tabs[i], g_max_row - MENU_WIN_HEIGHT - TAB_WIN_HEIGHT - 2, g_max_col); + mvwin(g_tabs[i], TABS_LOCATION_ROW, TABS_LOCATION_COL); + draw_tabs(i, g_current_sort_col[i]); + } + + draw_tabs(tab, g_current_sort_col[tab]); + + for (i = 0; i < NUMBER_OF_TABS; i++) { + wclear(g_tab_win[i]); + wresize(g_tab_win[i], TAB_WIN_HEIGHT, + (g_max_col - (TABS_SPACING * NUMBER_OF_TABS)) / NUMBER_OF_TABS); + mvwin(g_tab_win[i], TAB_WIN_LOCATION_ROW, 1 + (g_max_col / NUMBER_OF_TABS) * i); + draw_tab_win(i); + } + + update_panels(); + doupdate(); +} + +static void +switch_tab(enum tabs tab) +{ + top_panel(g_panels[tab]); + update_panels(); + doupdate(); +} + +static void +get_time_str(uint64_t ticks, char *time_str) +{ + uint64_t time; + + time = ticks * SPDK_SEC_TO_USEC / g_cores_stats.tick_rate; + snprintf(time_str, MAX_TIME_STR_LEN, "%" PRIu64, time); +} + +static int +sort_threads(const void *p1, const void *p2) +{ + const struct rpc_thread_info *thread_info1 = *(struct rpc_thread_info **)p1; + const struct rpc_thread_info *thread_info2 = *(struct rpc_thread_info **)p2; + uint64_t count1, count2; + + switch (g_current_sort_col[THREADS_TAB]) { + case 0: /* Sort by name */ + return strcmp(thread_info1->name, thread_info2->name); + case 1: /* Sort by core */ + count2 = thread_info1->core_num; + count1 = thread_info2->core_num; + break; + case 2: /* Sort by active pollers number */ + count1 = thread_info1->active_pollers_count; + count2 = thread_info2->active_pollers_count; + break; + case 3: /* Sort by timed pollers number */ + count1 = thread_info1->timed_pollers_count; + count2 = thread_info2->timed_pollers_count; + break; + case 4: /* Sort by paused pollers number */ + count1 = thread_info1->paused_pollers_count; + count2 = thread_info2->paused_pollers_count; + break; + case 5: /* Sort by idle time */ + count1 = thread_info1->idle - thread_info1->last_idle; + count2 = thread_info2->idle - thread_info2->last_idle; + break; + case 6: /* Sort by busy time */ + count1 = thread_info1->busy - thread_info1->last_busy; + count2 = thread_info2->busy - thread_info2->last_busy; + break; + default: + return 0; + } + + if (count2 > count1) { + return 1; + } else if (count2 < count1) { + return -1; + } else { + return 0; + } +} + +static uint8_t +refresh_threads_tab(uint8_t current_page) +{ + struct col_desc *col_desc = g_col_desc[THREADS_TAB]; + uint64_t i, threads_count; + uint16_t j; + uint16_t col; + uint8_t max_pages, item_index; + static uint8_t last_page = 0; + char pollers_number[MAX_POLLER_COUNT_STR_LEN], idle_time[MAX_TIME_STR_LEN], + busy_time[MAX_TIME_STR_LEN], core_str[MAX_CORE_MASK_STR_LEN]; + struct rpc_thread_info *thread_info[g_threads_stats.threads.threads_count]; + + threads_count = g_threads_stats.threads.threads_count; + + /* Clear screen if number of threads changed */ + if (g_last_threads_count != threads_count) { + for (i = TABS_DATA_START_ROW; i < g_data_win_size; i++) { + for (j = 1; j < (uint64_t)g_max_col - 1; j++) { + mvwprintw(g_tabs[THREADS_TAB], i, j, " "); + } + } + + g_last_threads_count = threads_count; + } + + /* Thread IDs starts from '1', so we have to take this into account when copying. + * TODO: In future we can have gaps in ID list, so we will need to change the way we + * handle copying threads list below */ + memcpy(thread_info, &g_thread_info[1], sizeof(struct rpc_thread_info *) * threads_count); + + if (last_page != current_page) { + for (i = 0; i < threads_count; i++) { + /* Thread IDs start from 1, so we have to do i + 1 */ + g_threads_stats.threads.thread_info[i].last_idle = g_thread_info[i + 1]->idle; + g_threads_stats.threads.thread_info[i].last_busy = g_thread_info[i + 1]->busy; + } + + last_page = current_page; + } + + max_pages = (threads_count + g_max_data_rows - 1) / g_max_data_rows; + + qsort(thread_info, threads_count, sizeof(thread_info[0]), sort_threads); + + for (i = current_page * g_max_data_rows; + i < spdk_min(threads_count, (uint64_t)((current_page + 1) * g_max_data_rows)); + i++) { + item_index = i - (current_page * g_max_data_rows); + + col = TABS_DATA_START_COL; + + if (!col_desc[0].disabled) { + print_max_len(g_tabs[THREADS_TAB], TABS_DATA_START_ROW + item_index, col, + col_desc[0].max_data_string, ALIGN_LEFT, thread_info[i]->name); + col += col_desc[0].max_data_string; + } + + if (!col_desc[1].disabled) { + snprintf(core_str, MAX_CORE_STR_LEN, "%d", thread_info[i]->core_num); + print_max_len(g_tabs[THREADS_TAB], TABS_DATA_START_ROW + item_index, + col, col_desc[1].max_data_string, ALIGN_RIGHT, core_str); + col += col_desc[1].max_data_string + 2; + } + + if (!col_desc[2].disabled) { + snprintf(pollers_number, MAX_POLLER_COUNT_STR_LEN, "%ld", thread_info[i]->active_pollers_count); + print_max_len(g_tabs[THREADS_TAB], TABS_DATA_START_ROW + item_index, + col + (col_desc[2].name_len / 2), col_desc[2].max_data_string, ALIGN_LEFT, pollers_number); + col += col_desc[2].max_data_string + 2; + } + + if (!col_desc[3].disabled) { + snprintf(pollers_number, MAX_POLLER_COUNT_STR_LEN, "%ld", thread_info[i]->timed_pollers_count); + print_max_len(g_tabs[THREADS_TAB], TABS_DATA_START_ROW + item_index, + col + (col_desc[3].name_len / 2), col_desc[3].max_data_string, ALIGN_LEFT, pollers_number); + col += col_desc[3].max_data_string + 1; + } + + if (!col_desc[4].disabled) { + snprintf(pollers_number, MAX_POLLER_COUNT_STR_LEN, "%ld", thread_info[i]->paused_pollers_count); + print_max_len(g_tabs[THREADS_TAB], TABS_DATA_START_ROW + item_index, + col + (col_desc[4].name_len / 2), col_desc[4].max_data_string, ALIGN_LEFT, pollers_number); + col += col_desc[4].max_data_string + 2; + } + + if (!col_desc[5].disabled) { + get_time_str(thread_info[i]->idle - thread_info[i]->last_idle, idle_time); + print_max_len(g_tabs[THREADS_TAB], TABS_DATA_START_ROW + item_index, col, + col_desc[5].max_data_string, ALIGN_RIGHT, idle_time); + col += col_desc[5].max_data_string; + thread_info[i]->last_idle = thread_info[i]->idle; + } + + if (!col_desc[6].disabled) { + get_time_str(thread_info[i]->busy - thread_info[i]->last_busy, busy_time); + print_max_len(g_tabs[THREADS_TAB], TABS_DATA_START_ROW + item_index, col, + col_desc[6].max_data_string, ALIGN_RIGHT, busy_time); + thread_info[i]->last_busy = thread_info[i]->busy; + } + } + + return max_pages; +} + +static uint64_t * +get_last_run_counter(const char *poller_name, uint64_t thread_id) +{ + struct run_counter_history *history; + + TAILQ_FOREACH(history, &g_run_counter_history, link) { + if (!strcmp(history->poller_name, poller_name) && history->thread_id == thread_id) { + return &history->last_run_counter; + } + } + + return NULL; +} + +static void +store_last_run_counter(const char *poller_name, uint64_t thread_id, uint64_t last_run_counter) +{ + struct run_counter_history *history; + + TAILQ_FOREACH(history, &g_run_counter_history, link) { + if (!strcmp(history->poller_name, poller_name) && history->thread_id == thread_id) { + history->last_run_counter = last_run_counter; + return; + } + } + + history = calloc(1, sizeof(*history)); + if (history == NULL) { + fprintf(stderr, "Unable to allocate a history object in store_last_run_counter.\n"); + return; + } + history->poller_name = strdup(poller_name); + history->thread_id = thread_id; + history->last_run_counter = last_run_counter; + + TAILQ_INSERT_TAIL(&g_run_counter_history, history, link); +} + +enum sort_type { + BY_NAME, + USE_GLOBAL, +}; + +static int +#ifdef __FreeBSD__ +sort_pollers(void *arg, const void *p1, const void *p2) +#else +sort_pollers(const void *p1, const void *p2, void *arg) +#endif +{ + const struct rpc_poller_info *poller1 = *(struct rpc_poller_info **)p1; + const struct rpc_poller_info *poller2 = *(struct rpc_poller_info **)p2; + enum sort_type sorting = *(enum sort_type *)arg; + uint64_t count1, count2; + uint64_t *last_run_counter; + + if (sorting == BY_NAME) { + /* Sorting by name requested explicitly */ + return strcmp(poller1->name, poller2->name); + } else { + /* Use globaly set sorting */ + switch (g_current_sort_col[POLLERS_TAB]) { + case 0: /* Sort by name */ + return strcmp(poller1->name, poller2->name); + case 1: /* Sort by type */ + return poller1->type - poller2->type; + case 2: /* Sort by thread */ + return strcmp(poller1->thread_name, poller2->thread_name); + case 3: /* Sort by run counter */ + last_run_counter = get_last_run_counter(poller1->name, poller1->thread_id); + assert(last_run_counter != NULL); + count1 = poller1->run_count - *last_run_counter; + last_run_counter = get_last_run_counter(poller2->name, poller2->thread_id); + assert(last_run_counter != NULL); + count2 = poller2->run_count - *last_run_counter; + break; + case 4: /* Sort by period */ + count1 = poller1->period_ticks; + count2 = poller2->period_ticks; + break; + default: + return 0; + } + } + + if (count2 > count1) { + return 1; + } else if (count2 < count1) { + return -1; + } else { + return 0; + } +} + +static void +copy_pollers(struct rpc_pollers *pollers, uint64_t pollers_count, enum spdk_poller_type type, + struct rpc_poller_thread_info *thread, uint64_t *current_count, bool reset_last_counter, + struct rpc_poller_info **pollers_info) +{ + uint64_t *last_run_counter; + uint64_t i; + + for (i = 0; i < pollers_count; i++) { + if (reset_last_counter) { + last_run_counter = get_last_run_counter(pollers->pollers[i].name, thread->id); + if (last_run_counter == NULL) { + store_last_run_counter(pollers->pollers[i].name, thread->id, pollers->pollers[i].run_count); + last_run_counter = get_last_run_counter(pollers->pollers[i].name, thread->id); + } + + assert(last_run_counter != NULL); + *last_run_counter = pollers->pollers[i].run_count; + } + pollers_info[*current_count] = &pollers->pollers[i]; + snprintf(pollers_info[*current_count]->thread_name, MAX_POLLER_NAME - 1, "%s", thread->name); + pollers_info[*current_count]->thread_id = thread->id; + pollers_info[(*current_count)++]->type = type; + } +} + +static uint8_t +refresh_pollers_tab(uint8_t current_page) +{ + struct col_desc *col_desc = g_col_desc[POLLERS_TAB]; + struct rpc_poller_thread_info *thread; + uint64_t *last_run_counter; + uint64_t i, count = 0; + uint16_t col, j; + uint8_t max_pages, item_index; + /* Init g_last_page with value != 0 to force store_last_run_counter() call in copy_pollers() + * so that initial values for run_counter are stored in g_run_counter_history */ + static uint8_t g_last_page = 0xF; + enum sort_type sorting; + char run_count[MAX_TIME_STR_LEN], period_ticks[MAX_PERIOD_STR_LEN]; + struct rpc_poller_info *pollers[RPC_MAX_POLLERS]; + bool reset_last_counter = false; + + for (i = 0; i < g_pollers_stats.pollers_threads.threads_count; i++) { + thread = &g_pollers_stats.pollers_threads.threads[i]; + if (g_last_page != current_page) { + reset_last_counter = true; + } + + copy_pollers(&thread->active_pollers, thread->active_pollers.pollers_count, SPDK_ACTIVE_POLLER, + thread, &count, reset_last_counter, pollers); + copy_pollers(&thread->timed_pollers, thread->timed_pollers.pollers_count, SPDK_TIMED_POLLER, thread, + &count, reset_last_counter, pollers); + copy_pollers(&thread->paused_pollers, thread->paused_pollers.pollers_count, SPDK_PAUSED_POLLER, + thread, &count, reset_last_counter, pollers); + } + + if (g_last_page != current_page) { + g_last_page = current_page; + } + + max_pages = (count + g_max_data_rows - 1) / g_max_data_rows; + + /* Clear screen if number of pollers changed */ + if (g_last_pollers_count != count) { + for (i = TABS_DATA_START_ROW; i < g_data_win_size; i++) { + for (j = 1; j < (uint64_t)g_max_col - 1; j++) { + mvwprintw(g_tabs[POLLERS_TAB], i, j, " "); + } + } + + g_last_pollers_count = count; + + /* We need to run store_last_run_counter() again, so the easiest way is to call this function + * again with changed g_last_page value */ + g_last_page = 0xF; + refresh_pollers_tab(current_page); + return max_pages; + } + + /* Timed pollers can switch their possition on a list because of how they work. + * Let's sort them by name first so that they won't switch on data refresh */ + sorting = BY_NAME; + qsort_r(pollers, count, sizeof(pollers[0]), sort_pollers, (void *)&sorting); + sorting = USE_GLOBAL; + qsort_r(pollers, count, sizeof(pollers[0]), sort_pollers, (void *)&sorting); + + /* Display info */ + for (i = current_page * g_max_data_rows; + i < spdk_min(count, (uint64_t)((current_page + 1) * g_max_data_rows)); + i++) { + item_index = i - (current_page * g_max_data_rows); + + col = TABS_DATA_START_COL; + + if (!col_desc[0].disabled) { + print_max_len(g_tabs[POLLERS_TAB], TABS_DATA_START_ROW + item_index, col + 1, + col_desc[0].max_data_string, ALIGN_LEFT, pollers[i]->name); + col += col_desc[0].max_data_string + 2; + } + + if (!col_desc[1].disabled) { + print_max_len(g_tabs[POLLERS_TAB], TABS_DATA_START_ROW + item_index, col, + col_desc[1].max_data_string, ALIGN_LEFT, poller_type_str[pollers[i]->type]); + col += col_desc[1].max_data_string + 2; + } + + if (!col_desc[2].disabled) { + print_max_len(g_tabs[POLLERS_TAB], TABS_DATA_START_ROW + item_index, col, + col_desc[2].max_data_string, ALIGN_LEFT, pollers[i]->thread_name); + col += col_desc[2].max_data_string + 1; + } + + if (!col_desc[3].disabled) { + last_run_counter = get_last_run_counter(pollers[i]->name, pollers[i]->thread_id); + assert(last_run_counter != NULL); + + snprintf(run_count, MAX_TIME_STR_LEN, "%" PRIu64, pollers[i]->run_count - *last_run_counter); + print_max_len(g_tabs[POLLERS_TAB], TABS_DATA_START_ROW + item_index, col, + col_desc[3].max_data_string, ALIGN_RIGHT, run_count); + col += col_desc[3].max_data_string; + + store_last_run_counter(pollers[i]->name, pollers[i]->thread_id, pollers[i]->run_count); + } + + if (!col_desc[4].disabled) { + if (pollers[i]->period_ticks != 0) { + get_time_str(pollers[i]->period_ticks, period_ticks); + print_max_len(g_tabs[POLLERS_TAB], TABS_DATA_START_ROW + item_index, col, + col_desc[4].max_data_string, ALIGN_RIGHT, period_ticks); + } + } + } + + return max_pages; +} + +static int +sort_cores(const void *p1, const void *p2) +{ + const struct core_info core_info1 = *(struct core_info *)p1; + const struct core_info core_info2 = *(struct core_info *)p2; + uint64_t count1, count2; + + switch (g_current_sort_col[CORES_TAB]) { + case 0: /* Sort by core */ + count1 = core_info2.core; + count2 = core_info1.core; + break; + case 1: /* Sort by threads number */ + count1 = core_info1.threads_count; + count2 = core_info2.threads_count; + break; + case 2: /* Sort by pollers number */ + count1 = core_info1.pollers_count; + count2 = core_info2.pollers_count; + break; + case 3: /* Sort by idle time */ + count2 = g_cores_history[core_info1.core].last_idle - core_info1.idle; + count1 = g_cores_history[core_info2.core].last_idle - core_info2.idle; + break; + case 4: /* Sort by busy time */ + count2 = g_cores_history[core_info1.core].last_busy - core_info1.busy; + count1 = g_cores_history[core_info2.core].last_busy - core_info2.busy; + break; + default: + return 0; + } + + if (count2 > count1) { + return 1; + } else if (count2 < count1) { + return -1; + } else { + return 0; + } +} + +static void +store_core_last_stats(uint32_t core, uint64_t idle, uint64_t busy) +{ + g_cores_history[core].last_idle = idle; + g_cores_history[core].last_busy = busy; +} + +static void +get_core_last_stats(uint32_t core, uint64_t *idle, uint64_t *busy) +{ + *idle = g_cores_history[core].last_idle; + *busy = g_cores_history[core].last_busy; +} + +static uint8_t +refresh_cores_tab(uint8_t current_page) +{ + struct col_desc *col_desc = g_col_desc[CORES_TAB]; + uint64_t i, j; + uint16_t offset, count = 0; + uint8_t max_pages, item_index; + static uint8_t last_page = 0; + char core[MAX_CORE_STR_LEN], threads_number[MAX_THREAD_COUNT_STR_LEN], + pollers_number[MAX_POLLER_COUNT_STR_LEN], idle_time[MAX_TIME_STR_LEN], busy_time[MAX_TIME_STR_LEN]; + struct core_info cores[RPC_MAX_CORES]; + struct spdk_cpuset tmp_cpumask = {}; + bool found = false; + + for (i = 0; i < g_threads_stats.threads.threads_count; i++) { + if (i == 0) { + snprintf(cores[0].core_mask, MAX_CORE_MASK_STR_LEN, "%s", + g_threads_stats.threads.thread_info[0].cpumask); + cores[0].threads_count = 1; + cores[0].pollers_count = g_threads_stats.threads.thread_info[0].active_pollers_count + + g_threads_stats.threads.thread_info[0].timed_pollers_count + + g_threads_stats.threads.thread_info[0].paused_pollers_count; + count++; + continue; + } + for (j = 0; j < count; j++) { + if (!strcmp(cores[j].core_mask, g_threads_stats.threads.thread_info[i].cpumask)) { + found = true; + break; + } + } + + if (found) { + cores[j].threads_count++; + cores[j].pollers_count += g_threads_stats.threads.thread_info[i].active_pollers_count + + g_threads_stats.threads.thread_info[i].timed_pollers_count + + g_threads_stats.threads.thread_info[i].paused_pollers_count; + found = false; + } else { + snprintf(cores[count].core_mask, MAX_CORE_MASK_STR_LEN, "%s", + g_threads_stats.threads.thread_info[i].cpumask); + cores[count].threads_count = 1; + cores[count].pollers_count = g_threads_stats.threads.thread_info[i].active_pollers_count + + g_threads_stats.threads.thread_info[i].timed_pollers_count + + g_threads_stats.threads.thread_info[i].paused_pollers_count; + count++; + } + } + + assert(g_cores_stats.cores.cores_count == count); + + for (i = 0; i < count; i++) { + for (j = 0; j < count; j++) { + spdk_cpuset_zero(&tmp_cpumask); + spdk_cpuset_set_cpu(&tmp_cpumask, g_cores_stats.cores.core[j].lcore, true); + if (!strcmp(cores[i].core_mask, spdk_cpuset_fmt(&tmp_cpumask))) { + cores[i].core = g_cores_stats.cores.core[j].lcore; + cores[i].busy = g_cores_stats.cores.core[j].busy; + cores[i].idle = g_cores_stats.cores.core[j].idle; + if (last_page != current_page) { + store_core_last_stats(cores[i].core, cores[i].idle, cores[i].busy); + } + } + } + } + + if (last_page != current_page) { + last_page = current_page; + } + + max_pages = (count + g_max_row - WINDOW_HEADER - 1) / (g_max_row - WINDOW_HEADER); + + qsort(&cores, count, sizeof(cores[0]), sort_cores); + + for (i = current_page * g_max_data_rows; + i < spdk_min(count, (uint64_t)((current_page + 1) * g_max_data_rows)); + i++) { + item_index = i - (current_page * g_max_data_rows); + + snprintf(threads_number, MAX_THREAD_COUNT_STR_LEN, "%ld", cores[i].threads_count); + snprintf(pollers_number, MAX_POLLER_COUNT_STR_LEN, "%ld", cores[i].pollers_count); + get_core_last_stats(cores[i].core, &cores[i].last_idle, &cores[i].last_busy); + + offset = 1; + + if (!col_desc[0].disabled) { + snprintf(core, MAX_CORE_STR_LEN, "%d", cores[i].core); + print_max_len(g_tabs[CORES_TAB], TABS_DATA_START_ROW + item_index, offset, + col_desc[0].max_data_string, ALIGN_RIGHT, core); + offset += col_desc[0].max_data_string + 2; + } + + if (!col_desc[1].disabled) { + print_max_len(g_tabs[CORES_TAB], TABS_DATA_START_ROW + item_index, + offset + (col_desc[1].name_len / 2), col_desc[1].max_data_string, ALIGN_LEFT, threads_number); + offset += col_desc[1].max_data_string + 2; + } + + if (!col_desc[2].disabled) { + print_max_len(g_tabs[CORES_TAB], TABS_DATA_START_ROW + item_index, + offset + (col_desc[2].name_len / 2), col_desc[2].max_data_string, ALIGN_LEFT, pollers_number); + offset += col_desc[2].max_data_string; + } + + if (!col_desc[3].disabled) { + get_time_str(cores[i].idle - cores[i].last_idle, idle_time); + print_max_len(g_tabs[CORES_TAB], TABS_DATA_START_ROW + item_index, offset, + col_desc[3].max_data_string, ALIGN_RIGHT, idle_time); + offset += col_desc[3].max_data_string + 2; + } + + if (!col_desc[4].disabled) { + get_time_str(cores[i].busy - cores[i].last_busy, busy_time); + print_max_len(g_tabs[CORES_TAB], TABS_DATA_START_ROW + item_index, offset, + col_desc[4].max_data_string, ALIGN_RIGHT, busy_time); + } + + store_core_last_stats(cores[i].core, cores[i].idle, cores[i].busy); + } + + return max_pages; +} + +static uint8_t +refresh_tab(enum tabs tab, uint8_t current_page) +{ + uint8_t (*refresh_function[NUMBER_OF_TABS])(uint8_t current_page) = {refresh_threads_tab, refresh_pollers_tab, refresh_cores_tab}; + int color_pair[NUMBER_OF_TABS] = {COLOR_PAIR(2), COLOR_PAIR(2), COLOR_PAIR(2)}; + int i; + uint8_t max_pages = 0; + + color_pair[tab] = COLOR_PAIR(1); + + for (i = 0; i < NUMBER_OF_TABS; i++) { + wbkgd(g_tab_win[i], color_pair[i]); + } + + max_pages = (*refresh_function[tab])(current_page); + refresh(); + + for (i = 0; i < NUMBER_OF_TABS; i++) { + wrefresh(g_tab_win[i]); + } + + return max_pages; +} + +static void +print_in_middle(WINDOW *win, int starty, int startx, int width, char *string, chtype color) +{ + int length, temp; + + length = strlen(string); + temp = (width - length) / 2; + wattron(win, color); + mvwprintw(win, starty, startx + temp, "%s", string); + wattroff(win, color); + refresh(); +} + +static void +apply_filters(enum tabs tab) +{ + wclear(g_tabs[tab]); + draw_tabs(tab, g_current_sort_col[tab]); +} + +static ITEM ** +draw_filtering_menu(uint8_t position, WINDOW *filter_win, uint8_t tab, MENU **my_menu) +{ + const int ADDITIONAL_ELEMENTS = 3; + const int ROW_PADDING = 6; + const int WINDOW_START_X = 1; + const int WINDOW_START_Y = 3; + const int WINDOW_COLUMNS = 2; + struct col_desc *col_desc = g_col_desc[tab]; + ITEM **my_items; + MENU *menu; + int i, elements; + uint8_t len = 0; + + for (i = 0; col_desc[i].name != NULL; ++i) { + len = spdk_max(col_desc[i].name_len, len); + } + + elements = i; + + my_items = (ITEM **)calloc(elements * WINDOW_COLUMNS + ADDITIONAL_ELEMENTS, sizeof(ITEM *)); + if (my_items == NULL) { + fprintf(stderr, "Unable to allocate an item list in draw_filtering_menu.\n"); + return NULL; + } + + for (i = 0; i < elements * 2; i++) { + my_items[i] = new_item(col_desc[i / WINDOW_COLUMNS].name, NULL); + i++; + my_items[i] = new_item(col_desc[i / WINDOW_COLUMNS].disabled ? "[ ]" : "[*]", NULL); + } + + my_items[i] = new_item(" CLOSE", NULL); + set_item_userptr(my_items[i], apply_filters); + + menu = new_menu((ITEM **)my_items); + + menu_opts_off(menu, O_SHOWDESC); + set_menu_format(menu, elements + 1, WINDOW_COLUMNS); + + set_menu_win(menu, filter_win); + set_menu_sub(menu, derwin(filter_win, elements + 1, len + ROW_PADDING, WINDOW_START_Y, + WINDOW_START_X)); + + *my_menu = menu; + + post_menu(menu); + refresh(); + wrefresh(filter_win); + + for (i = 0; i < position / WINDOW_COLUMNS; i++) { + menu_driver(menu, REQ_DOWN_ITEM); + } + + return my_items; +} + +static void +delete_filtering_menu(MENU *my_menu, ITEM **my_items, uint8_t elements) +{ + int i; + + unpost_menu(my_menu); + free_menu(my_menu); + for (i = 0; i < elements * 2 + 2; ++i) { + free_item(my_items[i]); + } + free(my_items); +} + +static ITEM ** +refresh_filtering_menu(MENU **my_menu, WINDOW *filter_win, uint8_t tab, ITEM **my_items, + uint8_t elements, uint8_t position) +{ + delete_filtering_menu(*my_menu, my_items, elements); + return draw_filtering_menu(position, filter_win, tab, my_menu); +} + +static void +filter_columns(uint8_t tab) +{ + const int WINDOW_HEADER_LEN = 5; + const int WINDOW_BORDER_LEN = 8; + const int WINDOW_HEADER_END_LINE = 2; + const int WINDOW_COLUMNS = 2; + struct col_desc *col_desc = g_col_desc[tab]; + PANEL *filter_panel; + WINDOW *filter_win; + ITEM **my_items; + MENU *my_menu = NULL; + int i, c, elements; + bool stop_loop = false; + ITEM *cur; + void (*p)(enum tabs tab); + uint8_t current_index, len = 0; + + for (i = 0; col_desc[i].name != NULL; ++i) { + len = spdk_max(col_desc[i].name_len, len); + } + + elements = i; + + filter_win = newwin(elements + WINDOW_HEADER_LEN, len + WINDOW_BORDER_LEN, + (g_max_row - elements - 1) / 2, (g_max_col - len) / 2); + keypad(filter_win, TRUE); + filter_panel = new_panel(filter_win); + + top_panel(filter_panel); + update_panels(); + doupdate(); + + box(filter_win, 0, 0); + + print_in_middle(filter_win, 1, 0, len + WINDOW_BORDER_LEN, "Filtering", COLOR_PAIR(3)); + mvwaddch(filter_win, WINDOW_HEADER_END_LINE, 0, ACS_LTEE); + mvwhline(filter_win, WINDOW_HEADER_END_LINE, 1, ACS_HLINE, len + WINDOW_BORDER_LEN - 2); + mvwaddch(filter_win, WINDOW_HEADER_END_LINE, len + WINDOW_BORDER_LEN - 1, ACS_RTEE); + + my_items = draw_filtering_menu(0, filter_win, tab, &my_menu); + if (my_items == NULL || my_menu == NULL) { + goto fail; + } + + while (!stop_loop) { + c = wgetch(filter_win); + + switch (c) { + case KEY_DOWN: + menu_driver(my_menu, REQ_DOWN_ITEM); + break; + case KEY_UP: + menu_driver(my_menu, REQ_UP_ITEM); + break; + case 27: /* ESC */ + case 'q': + stop_loop = true; + break; + case ' ': /* Space */ + cur = current_item(my_menu); + current_index = item_index(cur) / WINDOW_COLUMNS; + col_desc[current_index].disabled = !col_desc[current_index].disabled; + my_items = refresh_filtering_menu(&my_menu, filter_win, tab, my_items, elements, + item_index(cur) + 1); + if (my_items == NULL || my_menu == NULL) { + goto fail; + } + break; + case 10: /* Enter */ + cur = current_item(my_menu); + current_index = item_index(cur) / WINDOW_COLUMNS; + if (current_index == elements) { + stop_loop = true; + p = item_userptr(cur); + p(tab); + } else { + col_desc[current_index].disabled = !col_desc[current_index].disabled; + my_items = refresh_filtering_menu(&my_menu, filter_win, tab, my_items, elements, + item_index(cur) + 1); + if (my_items == NULL || my_menu == NULL) { + goto fail; + } + } + break; + } + wrefresh(filter_win); + } + + delete_filtering_menu(my_menu, my_items, elements); + + del_panel(filter_panel); + delwin(filter_win); + + wclear(g_menu_win); + draw_menu_win(); + return; + +fail: + fprintf(stderr, "Unable to filter the columns due to allocation failure.\n"); + assert(false); +} + +static void +sort_type(enum tabs tab, int item_index) +{ + g_current_sort_col[tab] = item_index; + wclear(g_tabs[tab]); + draw_tabs(tab, g_current_sort_col[tab]); +} + +static void +change_sorting(uint8_t tab) +{ + const int WINDOW_HEADER_LEN = 4; + const int WINDOW_BORDER_LEN = 3; + const int WINDOW_START_X = 1; + const int WINDOW_START_Y = 3; + const int WINDOW_HEADER_END_LINE = 2; + PANEL *sort_panel; + WINDOW *sort_win; + ITEM **my_items; + MENU *my_menu; + int i, c, elements; + bool stop_loop = false; + ITEM *cur; + void (*p)(enum tabs tab, int item_index); + uint8_t len = 0; + + for (i = 0; g_col_desc[tab][i].name != NULL; ++i) { + len = spdk_max(len, g_col_desc[tab][i].name_len); + } + + elements = i; + + my_items = (ITEM **)calloc(elements + 1, sizeof(ITEM *)); + if (my_items == NULL) { + fprintf(stderr, "Unable to allocate an item list in change_sorting.\n"); + return; + } + + for (i = 0; i < elements; ++i) { + my_items[i] = new_item(g_col_desc[tab][i].name, NULL); + set_item_userptr(my_items[i], sort_type); + } + + my_menu = new_menu((ITEM **)my_items); + + menu_opts_off(my_menu, O_SHOWDESC); + + sort_win = newwin(elements + WINDOW_HEADER_LEN, len + WINDOW_BORDER_LEN, (g_max_row - elements) / 2, + (g_max_col - len) / 2); + keypad(sort_win, TRUE); + sort_panel = new_panel(sort_win); + + top_panel(sort_panel); + update_panels(); + doupdate(); + + set_menu_win(my_menu, sort_win); + set_menu_sub(my_menu, derwin(sort_win, elements, len + 1, WINDOW_START_Y, WINDOW_START_X)); + box(sort_win, 0, 0); + + print_in_middle(sort_win, 1, 0, len + WINDOW_BORDER_LEN, "Sorting", COLOR_PAIR(3)); + mvwaddch(sort_win, WINDOW_HEADER_END_LINE, 0, ACS_LTEE); + mvwhline(sort_win, WINDOW_HEADER_END_LINE, 1, ACS_HLINE, len + 1); + mvwaddch(sort_win, WINDOW_HEADER_END_LINE, len + WINDOW_BORDER_LEN - 1, ACS_RTEE); + + post_menu(my_menu); + refresh(); + wrefresh(sort_win); + + while (!stop_loop) { + c = wgetch(sort_win); + + switch (c) { + case KEY_DOWN: + menu_driver(my_menu, REQ_DOWN_ITEM); + break; + case KEY_UP: + menu_driver(my_menu, REQ_UP_ITEM); + break; + case 27: /* ESC */ + stop_loop = true; + break; + case 10: /* Enter */ + stop_loop = true; + cur = current_item(my_menu); + p = item_userptr(cur); + p(tab, item_index(cur)); + break; + } + wrefresh(sort_win); + } + + unpost_menu(my_menu); + free_menu(my_menu); + + for (i = 0; i < elements; ++i) { + free_item(my_items[i]); + } + + free(my_items); + + del_panel(sort_panel); + delwin(sort_win); + + wclear(g_menu_win); + draw_menu_win(); +} + +static void +change_refresh_rate(void) +{ + const int WINDOW_HEADER_END_LINE = 2; + PANEL *refresh_panel; + WINDOW *refresh_win; + int c; + bool stop_loop = false; + uint32_t rr_tmp, refresh_rate = 0; + char refresh_rate_str[MAX_STRING_LEN]; + + refresh_win = newwin(RR_WIN_HEIGHT, RR_WIN_WIDTH, (g_max_row - RR_WIN_HEIGHT - 1) / 2, + (g_max_col - RR_WIN_WIDTH) / 2); + keypad(refresh_win, TRUE); + refresh_panel = new_panel(refresh_win); + + top_panel(refresh_panel); + update_panels(); + doupdate(); + + box(refresh_win, 0, 0); + + print_in_middle(refresh_win, 1, 0, RR_WIN_WIDTH + 1, "Enter refresh rate value [s]", COLOR_PAIR(3)); + mvwaddch(refresh_win, WINDOW_HEADER_END_LINE, 0, ACS_LTEE); + mvwhline(refresh_win, WINDOW_HEADER_END_LINE, 1, ACS_HLINE, RR_WIN_WIDTH - 2); + mvwaddch(refresh_win, WINDOW_HEADER_END_LINE, RR_WIN_WIDTH, ACS_RTEE); + mvwprintw(refresh_win, WINDOW_HEADER_END_LINE + 1, (RR_WIN_WIDTH - 1) / 2, "%d", refresh_rate); + + refresh(); + wrefresh(refresh_win); + + while (!stop_loop) { + c = wgetch(refresh_win); + + switch (c) { + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + rr_tmp = refresh_rate * 10 + c - '0'; + + if (rr_tmp <= RR_MAX_VALUE) { + refresh_rate = rr_tmp; + snprintf(refresh_rate_str, MAX_STRING_LEN - 1, "%d", refresh_rate); + mvwprintw(refresh_win, WINDOW_HEADER_END_LINE + 1, + (RR_WIN_WIDTH - 1 - strlen(refresh_rate_str)) / 2, "%d", refresh_rate); + refresh(); + wrefresh(refresh_win); + } + break; + case KEY_BACKSPACE: + case 127: + case '\b': + refresh_rate = refresh_rate / 10; + snprintf(refresh_rate_str, MAX_STRING_LEN - 1, "%d", refresh_rate); + mvwprintw(refresh_win, WINDOW_HEADER_END_LINE + 1, + (RR_WIN_WIDTH - 1 - strlen(refresh_rate_str) - 2) / 2, " "); + mvwprintw(refresh_win, WINDOW_HEADER_END_LINE + 1, + (RR_WIN_WIDTH - 1 - strlen(refresh_rate_str)) / 2, "%d", refresh_rate); + refresh(); + wrefresh(refresh_win); + break; + case 27: /* ESC */ + case 'q': + stop_loop = true; + break; + case 10: /* Enter */ + g_sleep_time = refresh_rate; + stop_loop = true; + break; + } + wrefresh(refresh_win); + } + + del_panel(refresh_panel); + delwin(refresh_win); +} + +static void +free_resources(void) +{ + struct run_counter_history *history, *tmp; + + TAILQ_FOREACH_SAFE(history, &g_run_counter_history, link, tmp) { + TAILQ_REMOVE(&g_run_counter_history, history, link); + free(history->poller_name); + free(history); + } +} + +static void +show_stats(void) +{ + const int CURRENT_PAGE_STR_LEN = 50; + const char *refresh_error = "ERROR occurred while getting data"; + long int time_last, time_dif; + struct timespec time_now; + int c, rc; + int max_row, max_col; + uint8_t active_tab = THREADS_TAB; + uint8_t current_page = 0; + uint8_t max_pages = 1; + char current_page_str[CURRENT_PAGE_STR_LEN]; + bool force_refresh = true; + + clock_gettime(CLOCK_REALTIME, &time_now); + time_last = time_now.tv_sec; + + switch_tab(THREADS_TAB); + + while (1) { + /* Check if interface has to be resized (terminal size changed) */ + getmaxyx(stdscr, max_row, max_col); + + if (max_row != g_max_row || max_col != g_max_col) { + g_max_row = max_row; + g_max_col = max_col; + g_data_win_size = g_max_row - MENU_WIN_HEIGHT - TAB_WIN_HEIGHT - TABS_DATA_START_ROW; + g_max_data_rows = g_max_row - WINDOW_HEADER; + resize_interface(active_tab); + } + + c = getch(); + if (c == 'q') { + free_resources(); + break; + } + + force_refresh = true; + + switch (c) { + case '1': + case '2': + case '3': + active_tab = c - '1'; + current_page = 0; + switch_tab(active_tab); + break; + case '\t': + if (active_tab < NUMBER_OF_TABS - 1) { + active_tab++; + } else { + active_tab = THREADS_TAB; + } + current_page = 0; + switch_tab(active_tab); + break; + case 's': + change_sorting(active_tab); + break; + case 'c': + filter_columns(active_tab); + break; + case 'r': + change_refresh_rate(); + break; + case 54: /* PgDown */ + if (current_page + 1 < max_pages) { + current_page++; + } + wclear(g_tabs[active_tab]); + draw_tabs(active_tab, g_current_sort_col[active_tab]); + break; + case 53: /* PgUp */ + if (current_page > 0) { + current_page--; + } + wclear(g_tabs[active_tab]); + draw_tabs(active_tab, g_current_sort_col[active_tab]); + break; + default: + force_refresh = false; + break; + } + + clock_gettime(CLOCK_REALTIME, &time_now); + time_dif = time_now.tv_sec - time_last; + if (time_dif < 0) { + time_dif = g_sleep_time; + } + + if (time_dif >= g_sleep_time || force_refresh) { + time_last = time_now.tv_sec; + rc = get_data(); + if (rc) { + mvprintw(g_max_row - 1, g_max_col - strlen(refresh_error) - 2, refresh_error); + } + + max_pages = refresh_tab(active_tab, current_page); + + snprintf(current_page_str, CURRENT_PAGE_STR_LEN - 1, "Page: %d/%d", current_page + 1, max_pages); + mvprintw(g_max_row - 1, 1, current_page_str); + + free_data(); + + refresh(); + } + } +} + +static void +draw_interface(void) +{ + int i; + + getmaxyx(stdscr, g_max_row, g_max_col); + g_data_win_size = g_max_row - MENU_WIN_HEIGHT - TAB_WIN_HEIGHT - TABS_DATA_START_ROW; + g_max_data_rows = g_max_row - WINDOW_HEADER; + + g_menu_win = newwin(MENU_WIN_HEIGHT, g_max_col, g_max_row - MENU_WIN_HEIGHT - 1, + MENU_WIN_LOCATION_COL); + draw_menu_win(); + + for (i = 0; i < NUMBER_OF_TABS; i++) { + g_tab_win[i] = newwin(TAB_WIN_HEIGHT, g_max_col / NUMBER_OF_TABS - TABS_SPACING, + TAB_WIN_LOCATION_ROW, g_max_col / NUMBER_OF_TABS * i + 1); + draw_tab_win(i); + + g_tabs[i] = newwin(g_max_row - MENU_WIN_HEIGHT - TAB_WIN_HEIGHT - 2, g_max_col, TABS_LOCATION_ROW, + TABS_LOCATION_COL); + draw_tabs(i, g_current_sort_col[i]); + g_panels[i] = new_panel(g_tabs[i]); + } + + update_panels(); + doupdate(); +} + +static void finish(int sig) +{ + /* End ncurses mode */ + endwin(); + spdk_jsonrpc_client_close(g_rpc_client); + exit(0); +} + +static void +setup_ncurses(void) +{ + clear(); + noecho(); + timeout(1); + curs_set(0); + start_color(); + init_pair(1, COLOR_BLACK, COLOR_GREEN); + init_pair(2, COLOR_BLACK, COLOR_WHITE); + init_pair(3, COLOR_YELLOW, COLOR_BLACK); + init_pair(4, COLOR_BLACK, COLOR_YELLOW); + + if (has_colors() == FALSE) { + endwin(); + printf("Your terminal does not support color\n"); + exit(1); + } + + /* Handle signals to exit gracfully cleaning up ncurses */ + (void) signal(SIGINT, finish); + (void) signal(SIGPIPE, finish); + (void) signal(SIGABRT, finish); +} + +static void +usage(const char *program_name) +{ + printf("%s [options]", program_name); + printf("\n"); + printf("options:\n"); + printf(" -r <path> RPC listen address (default: /var/tmp/spdk.sock\n"); + printf(" -h show this usage\n"); +} + +int main(int argc, char **argv) +{ + int op; + char *socket = SPDK_DEFAULT_RPC_ADDR; + + while ((op = getopt(argc, argv, "r:h")) != -1) { + switch (op) { + case 'r': + socket = optarg; + break; + case 'H': + default: + usage(argv[0]); + return 1; + } + } + + g_rpc_client = spdk_jsonrpc_client_connect(socket, AF_UNIX); + if (!g_rpc_client) { + fprintf(stderr, "spdk_jsonrpc_client_connect() failed: %d\n", errno); + return 1; + } + + initscr(); + init_str_len(); + setup_ncurses(); + draw_interface(); + show_stats(); + + finish(0); + + return (0); +} |