/* Copyright (c) 2016, 2019, Oracle and/or its affiliates. All rights reserved. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License, version 2.0, as published by the Free Software Foundation. This program is also distributed with certain software (including but not limited to OpenSSL) that is licensed under separate terms, as designated in a particular file or component or in included license documentation. The authors of MySQL hereby grant you an additional permission to link the program and your derivative works with the separately licensed software that they have included with MySQL. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License, version 2.0, for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #define _XOPEN_SOURCE_EXTENDED #include #include #include #include #ifdef HAVE_NCURSESW_CURSES_H #include #elif defined HAVE_NCURSESW_H #include #elif defined HAVE_NCURSES_CURSES_H #include #elif HAVE_NCURSES_H #include #else #include #endif #include #include #include "my_getopt.h" #include "mysql/service_mysql_alloc.h" #include #include "../../../client/client_priv.h" #include "welcome_copyright_notice.h" /* ORACLE_WELCOME_COPYRIGHT_NOTICE */ #include "my_default.h" #include "map_helpers.h" #define BLUE_COLOR 1 #define GREEN_COLOR 2 #define RED_COLOR 3 #define YELLOW_COLOR 4 #define BLACK_COLOR 5 #define DEFAULT_COLOR 6 struct thread_result_type { unsigned int thr_no; char thr_name[32]; unsigned int OS_user; unsigned int OS_system; unsigned int OS_idle; unsigned int thread_exec; unsigned int thread_send; unsigned int thread_buffer_full; unsigned int thread_sleeping; unsigned int elapsed_time; }; typedef struct thread_result_type THREAD_RESULT; static THREAD_RESULT *thread_result = NULL; static unsigned int ndb_threads = 0; static MYSQL *con = NULL; unsigned int opt_port_number = 0; char *opt_host = (char*)"localhost"; char *opt_user = (char*)"root"; char *opt_password = 0; char *opt_socket = 0; bool tty_password = 0; char *db_name = (char *)"ndbinfo"; unsigned int opt_node_id = 0; unsigned int opt_sleep_time = 0; bool opt_measured_load = 0; bool opt_os_load = 0; bool opt_color = 0; bool opt_text = 0; bool opt_graph = 0; bool opt_sort = 0; bool opt_help = 0; static char percentage_sign = '%'; const char *load_default_groups[] = {"ndb_top", "client", 0}; void handle_error() { printf("%s\n\r", mysql_error(con)); } void cleanup(bool in_screen) { if (in_screen) { endwin(); } if (con != NULL) { mysql_close(con); } if (thread_result) { free(thread_result); } if (opt_password) { my_free(opt_password); } } int connect_mysql() { const mysql_protocol_type connect_protocol = opt_socket != 0 ? MYSQL_PROTOCOL_SOCKET : MYSQL_PROTOCOL_TCP; mysql_options(con, MYSQL_OPT_PROTOCOL, (void*)&connect_protocol); MYSQL *loc = mysql_real_connect(con, opt_host, opt_user, opt_password, db_name, opt_port_number, opt_socket, 0); return loc == NULL ? 1 : 0; } int query_mysql() { char buf[512]; char *query_str = &buf[0]; snprintf( buf, sizeof(buf), "SELECT cs.thr_no, ts.thread_name, cs.OS_user, cs.OS_system, cs.OS_idle," " cs.thread_exec, cs.thread_send, cs.thread_buffer_full, cs.thread_sleeping," " cs.elapsed_time FROM cpustat as cs, threads as ts WHERE" " cs.node_id = %u AND" " cs.thr_no = ts.thr_no AND" " cs.node_id = ts.node_id", opt_node_id); int res; if ((res = mysql_query(con, query_str))) return res; MYSQL_RES *result = mysql_store_result(con); if (result == NULL) return 2; int num_fields = mysql_num_fields(result); if (num_fields != 10) { return 3; } uint64_t num_rows = mysql_num_rows(result); if (thread_result != NULL) { free(thread_result); thread_result = NULL; } if (num_rows == 0) { return 4; } THREAD_RESULT *tr_array = (THREAD_RESULT*)malloc(sizeof(THREAD_RESULT) * num_rows); assert(tr_array != NULL); thread_result = tr_array; ndb_threads = 0; MYSQL_ROW row; while ((row = mysql_fetch_row(result))) { THREAD_RESULT *tr = &thread_result[ndb_threads]; unsigned long *lengths; lengths = mysql_fetch_lengths(result); assert(row[0]); sscanf(row[0], "%u", &tr->thr_no); assert(row[1]); sscanf(row[1], "%31s", tr->thr_name); assert(row[2]); sscanf(row[2], "%u", &tr->OS_user); assert(row[3]); sscanf(row[3], "%u", &tr->OS_system); assert(row[4]); sscanf(row[4], "%u", &tr->OS_idle); assert(row[5]); sscanf(row[5], "%u", &tr->thread_exec); assert(row[6]); sscanf(row[6], "%u", &tr->thread_send); assert(row[7]); sscanf(row[7], "%u", &tr->thread_buffer_full); assert(row[8]); sscanf(row[8], "%u", &tr->thread_sleeping); assert(row[9]); sscanf(row[9], "%u", &tr->elapsed_time); ndb_threads++; } mysql_free_result(result); assert((uint64_t)ndb_threads == num_rows); return 0; } static char* tombs(const wchar_t* wc, const char* c) { char* p; size_t n = wcstombs(NULL, wc, 0); if (n == (size_t)-1) { p = strdup(c); } else { p = (char*)malloc(n+1); wcstombs(p, wc, n+1); } return p; } int print_black_block() { static char *mbs=tombs(L"\u2588", "#"); return addstr(mbs); } int print_dark_shade() { static char *mbs=tombs(L"\u2593", "@"); return addstr(mbs); } int print_medium_shade() { static char *mbs=tombs(L"\u2592", "X"); return addstr(mbs); } int print_light_shade() { static char *mbs=tombs(L"\u2591", "o"); return addstr(mbs); } int print_space() { return addstr(" "); } static volatile sig_atomic_t g_resize_window = 0; void resize_window(int dummy) { g_resize_window = 1; } static struct my_option my_long_options[] = { {"host", 'h', "Hostname of MySQL Server", (uchar**) &opt_host, (uchar**) &opt_host, 0, GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, {"port", 'P', "Port of MySQL Server", &opt_port_number, &opt_port_number, 0, GET_UINT, REQUIRED_ARG, 3306, 0, 0, 0, 0, 0}, {"socket", 'S', "The socket file to use for connection.", &opt_socket, &opt_socket, 0, GET_STR_ALLOC, REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, {"user", 'u', "Username to log into MySQL Server", (uchar**) &opt_user, (uchar**) &opt_user, 0, GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, {"password", 'p', "Password to log into MySQL Server (default is NULL)", 0, 0, 0, GET_PASSWORD, OPT_ARG, 0, 0, 0, 0, 0, 0}, {"node_id", 'n', "Node id of data node to watch", &opt_node_id, &opt_node_id, 0, GET_UINT, REQUIRED_ARG, 1, 0, 0, 0, 0, 0}, {"sleep_time", 's', "Sleep time between each refresh of statistics", &opt_sleep_time, &opt_sleep_time, 0, GET_UINT, REQUIRED_ARG, 1, 0, 0, 0, 0, 0}, {"measured_load", 'm', "Show measured load by thread", &opt_measured_load, &opt_measured_load, 0, GET_BOOL, NO_ARG, 0, 0, 0, 0, 0, 0}, {"os_load", 'o', "Show load measured by OS", &opt_os_load, &opt_os_load, 0, GET_BOOL, NO_ARG, 1, 0, 0, 0, 0, 0}, {"color", 'c', "Use color in ASCII graphs", &opt_color, &opt_color, 0, GET_BOOL, NO_ARG, 1, 0, 0, 0, 0, 0}, {"text", 't', "Use text to represent data", &opt_text, &opt_text, 0, GET_BOOL, NO_ARG, 0, 0, 0, 0, 0, 0}, {"graph", 'g', "Use ASCII graphs to represent data", &opt_graph, &opt_graph, 0, GET_BOOL, NO_ARG, 1, 0, 0, 0, 0, 0}, {"sort", 'r', "Sort threads after highest measured usage", &opt_sort, &opt_sort, 0, GET_BOOL, NO_ARG, 1, 0, 0, 0, 0, 0}, {"help", '?', "Print usage", &opt_help, &opt_help, 0, GET_BOOL, NO_ARG, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, GET_NO_ARG, NO_ARG, 0, 0, 0, 0, 0, 0} }; static void short_usage_sub() { printf("Usage: %s [OPTIONS]\n", my_progname); } #define NDB_TOP_VERSION "1.0" static void print_version(void) { printf("%s Ver %s Distrib %s, for %s (%s)\n", my_progname, NDB_TOP_VERSION, MYSQL_SERVER_VERSION, SYSTEM_TYPE,MACHINE_TYPE); } /* print_version */ static void usage(void) { print_version(); puts(ORACLE_WELCOME_COPYRIGHT_NOTICE("2017")); puts("ndb_top"); puts(""); puts("ndb_top uses the ndbinfo table cpustat to view CPU stats of NDB threads"); puts(""); puts("Each thread can be represented by two rows, the first one shows the OS stats,"); puts("the second row shows the measured stats in the thread (also affected by"); puts("the OS descheduling the thread."); puts(""); puts("The graph display shows OS user time as filled blue boxes, OS system time as"); puts("shady green boxes and idle time as space, for measured load we use filled"); puts("blue boxes for execution time, yellow shady boxes for send time and red"); puts("filled boxes for time spent in send buffer full waits and space for idle."); puts(""); puts("The percentage shown in graph display is the sum of all non-idle percentages."); puts("The text display shows the same information as the graph display but in text"); puts("representation. It is possible to use text and graph at the same time."); puts(""); puts("The sorted view is based on the maximum of the measured load and the load"); puts("reported by the OS."); puts(""); puts("The view will adjust itself to the height and width of the terminal window."); puts("The minimum width required is 76 characters wide."); puts(""); puts("By default it shows the CPU usage in node 1."); puts("Quit program by using Ctrl-C."); puts(""); short_usage_sub(); my_print_help(my_long_options); print_defaults(MYSQL_CONFIG_NAME,load_default_groups); my_print_variables(my_long_options); } /* usage */ static bool get_one_option(int optid, const struct my_option *opt MY_ATTRIBUTE((unused)), char *argument) { switch (optid) { case 'p': { if (argument == disabled_my_option) { argument = (char*)""; } if (argument) { char *start=argument; my_free(opt_password); opt_password=my_strdup(PSI_NOT_INSTRUMENTED, argument,MYF(MY_FAE)); while (*argument) *argument++= 'x'; /* Destroy argument */ if (*start) start[1]=0; /* Cut length of argument */ tty_password= 0; } else tty_password=1; break; } case '?': { usage(); exit(0); break; } case 'V': { print_version(); exit(0); } case 'P': case 'S': case 'n': case 'u': case 'h': case 's': case 'm': case 'o': case 'c': case 't': case 'g': case 'r': { break; } default: { usage(); exit(0); break; } } return FALSE; } #define SORT_ORDER_ENTRIES 128 static void init_sort_order(unsigned int *sort_order, THREAD_RESULT *tr, unsigned int num_threads) { unsigned int measured_load[SORT_ORDER_ENTRIES]; if (!opt_sort) { for (unsigned int i = 0; i < num_threads; i++) { sort_order[i] = i; } return; } for (unsigned int i = 0; i < num_threads; i++) { unsigned int meas_load = tr[i].thread_exec + tr[i].thread_send + tr[i].thread_buffer_full; unsigned int os_load = tr[i].OS_user + tr[i].OS_system; unsigned int load = meas_load; if (os_load > meas_load) { load = os_load; } measured_load[i] = load; sort_order[i] = i; } for (unsigned int i = 0; i < num_threads; i++) { unsigned int max_val = measured_load[i]; unsigned int max_index = i; for (unsigned int j = i + 1; j < num_threads; j++) { if (measured_load[j] > max_val) { max_val = measured_load[j]; max_index = j; } } measured_load[max_index] = measured_load[i]; measured_load[i] = max_val; unsigned int save = sort_order[i]; sort_order[i] = sort_order[max_index]; sort_order[max_index] = save; } return; } int main(int argc, char **argv) { int ret; int exit_code = 0; bool use_color = false; bool in_screen = false; WINDOW *win; unsigned int sort_order[SORT_ORDER_ENTRIES]; MY_INIT("ndb_top"); MEM_ROOT alloc{PSI_NOT_INSTRUMENTED, 512}; my_load_defaults(MYSQL_CONFIG_NAME, load_default_groups, &argc, &argv, &alloc, NULL); ret = handle_options(&argc, &argv, my_long_options, get_one_option); if (ret != 0) { printf("Wrong options\n"); exit_code = 1; goto exit_handling; } if (tty_password) { opt_password = get_tty_password(NullS); } if (opt_os_load == 0 && opt_measured_load == 0) { usage(); printf("\n\rError message:\n\rAt least one load need to be shown\n\r"); exit_code = 1; goto exit_handling; } if (opt_text == 0 && opt_graph == 0) { usage(); printf("\n\rError message:\n\rAt least one of text and graph is needed\n\r"); exit_code = 1; goto exit_handling; } con = mysql_init(NULL); if (con == NULL) { usage(); printf("\n\rError message:\n\rmysql_init failed\n"); exit_code = 1; goto exit_handling; } if (connect_mysql() != 0) { usage(); printf("\n\rError message:\n\r"); printf("Connect to ndbinfo database in MySQL Server failed\n"); handle_error(); exit_code = 1; goto exit_handling; } signal(SIGWINCH, resize_window); setlocale(LC_ALL,""); win = initscr(); use_default_colors(); if (opt_color && has_colors()) { int ret_code = start_color(); if (ret_code != ERR) { use_color = true; init_pair(BLUE_COLOR, COLOR_BLUE, -1); init_pair(GREEN_COLOR, COLOR_GREEN, -1); init_pair(RED_COLOR, COLOR_RED, -1); init_pair(YELLOW_COLOR, COLOR_YELLOW, -1); init_pair(BLACK_COLOR, COLOR_BLACK, -1); init_pair(DEFAULT_COLOR, -1, -1); } } while (1) { int res; if ((res = query_mysql() != 0)) { refresh(); endwin(); usage(); switch (res) { case 1: printf("\n\rFailed in mysql_query, empty result set, check node_id\n\r"); break; case 2: printf("\n\rFailed in mysql_store_results:\n\r"); break; case 3: printf("\n\rFailed in mysql_num_fields:\n\r"); break; case 4: printf("\n\rFailed in mysql_num_rows:\n\r"); break; default: if (res == CR_SERVER_LOST) printf("\n\rFailed in mysql_query: Server lost\n\r"); else if (res == CR_SERVER_GONE_ERROR) printf("\n\rFailed in mysql_query: Server gone\n\r"); else if (res == CR_UNKNOWN_ERROR) printf("\n\rFailed in mysql_query: MySQL unknown error\n\r"); else if (res == CR_COMMANDS_OUT_OF_SYNC) printf("\n\rFailed in mysql_query: Commands out of sync\n\r"); else printf("\n\rFailed in mysql_query: Error code not documented\n\r"); break; } printf("\n\rError message:\n\rFailed to query MySQL\n\r"); handle_error(); exit_code = 1; goto exit_handling; } int width = 0; int height = 0; getmaxyx(win, height, width); if (width < 76) { endwin(); printf("Width of screen is %d, smaller than 76, height is %d," " no use in proceeding\n", width, height); handle_error(); exit_code = 1; goto exit_handling; } in_screen = true; move(0,0); int lines_used = 0; unsigned int total_dots = width - 33; init_sort_order(&sort_order[0], &thread_result[0], ndb_threads); for (unsigned int k = 0; k < ndb_threads; k++) { THREAD_RESULT *tr = &thread_result[sort_order[k]]; if (opt_os_load) { unsigned int blue_dots = (tr->OS_user * total_dots) / 100; unsigned int green_dots = (tr->OS_system * total_dots) / 100; assert(total_dots >= (blue_dots + green_dots)); unsigned int white_dots = total_dots - (blue_dots + green_dots); unsigned int percentage = tr->OS_user + tr->OS_system; if (opt_text) { if (lines_used++ >= height) { break; } printw("%4s thr_no %2u OS view [", tr->thr_name, tr->thr_no); unsigned int idle; if ((tr->OS_user + tr->OS_system) > 100) { idle = 0; } else { idle = (100 - (tr->OS_user + tr->OS_system)); } printw("user: %3u%c, system: %3u%c, idle: %3u%c] %3u%c\n\r", tr->OS_user, percentage_sign, tr->OS_system, percentage_sign, idle, percentage_sign, (tr->OS_user + tr->OS_system), percentage_sign); } if (opt_graph) { if (lines_used++ >= height) { break; } printw("%4s thr_no %2u OS view [", tr->thr_name, tr->thr_no); if (use_color) attron(COLOR_PAIR(BLUE_COLOR)); for (unsigned int j = 0; j < blue_dots; j++) print_black_block(); if (use_color) attron(COLOR_PAIR(GREEN_COLOR)); for (unsigned int j = 0; j < green_dots; j++) print_medium_shade(); if (use_color) attron(COLOR_PAIR(BLACK_COLOR)); for (unsigned int j = 0; j < white_dots; j++) print_space(); if (use_color) attron(COLOR_PAIR(DEFAULT_COLOR)); printw("] %3u%c\n\r", percentage, percentage_sign); } } if (opt_measured_load) { unsigned int blue_dots = (tr->thread_exec * total_dots) / 100; unsigned int yellow_dots = (tr->thread_send * total_dots) / 100; unsigned int red_dots = (tr->thread_buffer_full * total_dots) / 100; assert(total_dots >= (blue_dots + yellow_dots + red_dots)); unsigned int white_dots = total_dots - (blue_dots + yellow_dots + red_dots); unsigned int percentage = tr->thread_exec + tr->thread_send + tr->thread_buffer_full; if (opt_text) { if (lines_used++ >= height) { break; } printw("%4s thr_no %2u user view [", tr->thr_name, tr->thr_no); unsigned int idle; if ((tr->thread_exec + tr->thread_send + tr->thread_buffer_full) > 100) { idle = 0; } else { idle = (100 - (tr->thread_exec + tr->thread_send + tr->thread_buffer_full)); } printw("exec: %3u%c, send: %3u%c, full: %3u%c idle: %3u%c] %3u%c\n\r", tr->thread_exec, percentage_sign, tr->thread_send, percentage_sign, tr->thread_buffer_full, percentage_sign, idle, percentage_sign, (tr->thread_exec + tr->thread_send + tr->thread_buffer_full), percentage_sign); } if (opt_graph) { if (lines_used++ >= height) { break; } printw("%4s thr_no %2u user view [", tr->thr_name, tr->thr_no); if (use_color) attron(COLOR_PAIR(BLUE_COLOR)); for (unsigned int j = 0; j < blue_dots; j++) print_black_block(); if (use_color) attron(COLOR_PAIR(YELLOW_COLOR)); for (unsigned int j = 0; j < yellow_dots; j++) print_dark_shade(); if (use_color) attron(COLOR_PAIR(RED_COLOR)); for (unsigned int j = 0; j < red_dots; j++) print_medium_shade(); if (use_color) attron(COLOR_PAIR(DEFAULT_COLOR)); for (unsigned int j = 0; j < white_dots; j++) print_space(); printw("] %3u%c\n\r", percentage, percentage_sign); } } } if (g_resize_window) { endwin(); refresh(); g_resize_window = 0; } wrefresh(win); sleep(opt_sleep_time); if (g_resize_window) { endwin(); refresh(); g_resize_window = 0; } in_screen = false; } endwin(); exit_handling: cleanup(in_screen); exit(exit_code); }