/* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License (the "License"). * You may not use this file except in compliance with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright (c) 2008-2009, Intel Corporation. * All Rights Reserved. */ #include #include #include #include #include #include #include #include #include #include #include #include #include "latencytop.h" #define LT_WINDOW_X 80 #define LT_WINDOW_Y 24 #define LT_COLOR_DEFAULT 1 #define LT_COLOR_HEADER 2 /* Windows created by libcurses */ static WINDOW *titlebar = NULL; static WINDOW *captionbar = NULL; static WINDOW *sysglobal_window = NULL; static WINDOW *taskbar = NULL; static WINDOW *process_window = NULL; static WINDOW *hintbar = NULL; /* Screen dimention */ static int screen_width = 1, screen_height = 1; /* Is display initialized, i.e. window pointers set up. */ static int display_initialized = FALSE; /* Is initscr() called */ static int curses_inited = FALSE; /* Changed by user key press */ static pid_t selected_pid = INVALID_PID; static id_t selected_tid = INVALID_TID; static lt_sort_t sort_type = LT_SORT_TOTAL; static int thread_mode = FALSE; /* what kind of list are we showing now */ static int current_list_type = LT_LIST_CAUSE; static int show_help = FALSE; /* Help functions that append/prepend blank to the string */ #define fill_space_right(a, b, c) fill_space((a), (b), (c), TRUE) #define fill_space_left(a, b, c) fill_space((a), (b), (c), FALSE) static void fill_space(char *buffer, int len, int buffer_limit, int is_right) { int i = 0; int tofill; if (len >= buffer_limit) { len = buffer_limit - 1; } i = strlen(buffer); if (i >= len) { return; } tofill = len - i; if (is_right) { (void) memset(&buffer[i], ' ', tofill); buffer[len] = 0; } else { (void) memmove(&buffer[tofill], buffer, i+1); (void) memset(buffer, ' ', tofill); } } /* Formats a human readable string out of nanosecond value */ static const char * get_time_string(double nanoseconds, char *buffer, int len, int fill_width) { const double ONE_USEC = 1000.0; const double ONE_MSEC = 1000000.0; const double ONE_SEC = 1000000000.0; if (nanoseconds < (ONE_USEC - .5)) { (void) snprintf(buffer, len, "%3.1f nsec", nanoseconds); } else if (nanoseconds < (ONE_MSEC - .5 * ONE_USEC)) { (void) snprintf(buffer, len, "%3.1f usec", nanoseconds / ONE_USEC); } else if (nanoseconds < (ONE_SEC - .5 * ONE_MSEC)) { (void) snprintf(buffer, len, "%3.1f msec", nanoseconds / ONE_MSEC); } else if (nanoseconds < 999.5 * ONE_SEC) { (void) snprintf(buffer, len, "%3.1f sec", nanoseconds / ONE_SEC); } else { (void) snprintf(buffer, len, "%.0e sec", nanoseconds / ONE_SEC); } fill_space_left(buffer, fill_width, len); return (buffer); } /* * Print statistics in a window. * IN: window - the global or process statistics window. * begin_line - where to start printing. * count - how many lines should we print. * list - a stat_list. */ #define WIDTH_REASON_STRING 36 #define WIDTH_COUNT 12 #define WIDTH_SUM 12 #define WIDTH_MAX 12 #define WIDTH_PCT 8 #define BEGIN_COUNT WIDTH_REASON_STRING #define BEGIN_SUM (BEGIN_COUNT + WIDTH_COUNT) #define BEGIN_MAX (BEGIN_SUM + WIDTH_SUM) #define BEGIN_PCT (BEGIN_MAX + WIDTH_MAX) static void print_statistics(WINDOW * window, int begin_line, int count, void *list) { uint64_t total; int i = 0; if (!display_initialized) { return; } total = lt_stat_list_get_gtotal(list); if (total == 0) { return; } while (i < count && lt_stat_list_has_item(list, i)) { /* * We intentionally make tmp[] hold one character less * than WIDTH_REASON_STRING, so it will look nice on the * screen. */ char tmp[WIDTH_REASON_STRING]; const char *reason = lt_stat_list_get_reason(list, i); uint64_t count = lt_stat_list_get_count(list, i); if (count == 0) { continue; } (void) snprintf(tmp, sizeof (tmp), "%s", reason); (void) mvwprintw(window, i + begin_line, 0, "%s", tmp); (void) snprintf(tmp, sizeof (tmp), "%d", lt_stat_list_get_count(list, i)); fill_space_left(tmp, WIDTH_COUNT, sizeof (tmp)); (void) mvwprintw(window, i + begin_line, BEGIN_COUNT, "%s", tmp); (void) mvwprintw(window, i + begin_line, BEGIN_SUM, "%s", get_time_string( (double)lt_stat_list_get_sum(list, i) / count, tmp, sizeof (tmp), WIDTH_SUM)); (void) mvwprintw(window, i + begin_line, BEGIN_MAX, "%s", get_time_string( (double)lt_stat_list_get_max(list, i), tmp, sizeof (tmp), WIDTH_MAX)); if (LT_LIST_SPECIALS != current_list_type) { (void) snprintf(tmp, sizeof (tmp), "%.1f %%", (double)lt_stat_list_get_sum(list, i) / total * 100.0); } else { (void) snprintf(tmp, sizeof (tmp), "--- "); } fill_space_left(tmp, WIDTH_PCT, sizeof (tmp)); (void) mvwprintw(window, i + begin_line, BEGIN_PCT, "%s", tmp); i++; } } /* * Print global statistics. Calls print_statistics(). */ static void print_sysglobal(void) { void *list; char header[256]; if (!display_initialized) { return; } (void) werase(sysglobal_window); (void) wattron(sysglobal_window, A_REVERSE); (void) snprintf(header, sizeof (header), "%s", lt_text("System wide latencies")); fill_space_right(header, screen_width, sizeof (header)); (void) mvwprintw(sysglobal_window, 0, 0, "%s", header); (void) wattroff(sysglobal_window, A_REVERSE); list = lt_stat_list_create(current_list_type, LT_LEVEL_GLOBAL, 0, 0, 10, sort_type); print_statistics(sysglobal_window, 1, 10, list); lt_stat_list_free(list); (void) wrefresh(sysglobal_window); } /* * Prints current operation mode: process/thread, window 1/2/3. */ static void print_current_mode() { char type; if (!display_initialized) { return; } switch (current_list_type) { case LT_LIST_CAUSE: type = 'C'; break; case LT_LIST_SPECIALS: type = 'S'; break; case LT_LIST_SOBJ: type = 'L'; break; default: type = '?'; break; } (void) mvwprintw(process_window, 0, screen_width - 2, "%c%c", type, thread_mode ? 'T' : 'P'); } /* * Print per-process statistics. Calls print_statistics(). * This one is used in per-process mode. */ static void print_process(unsigned int pid) { void *list; char header[256]; char tmp[30]; if (!display_initialized) { return; } list = lt_stat_list_create(current_list_type, LT_LEVEL_PROCESS, pid, 0, 8, sort_type); (void) werase(process_window); (void) wattron(process_window, A_REVERSE); (void) snprintf(header, sizeof (header), "Process %s (%i) ", lt_stat_proc_get_name(pid), pid); fill_space_right(header, screen_width, sizeof (header)); (void) mvwprintw(process_window, 0, 0, "%s", header); if (current_list_type != LT_LIST_SPECIALS) { (void) mvwprintw(process_window, 0, 40, lt_text("Total: %s from %d threads"), get_time_string((double)lt_stat_list_get_gtotal(list), tmp, sizeof (tmp), 12), lt_stat_proc_get_nthreads(pid)); } print_current_mode(); (void) wattroff(process_window, A_REVERSE); print_statistics(process_window, 1, 8, list); lt_stat_list_free(list); (void) wrefresh(process_window); } /* * List all processes in task bar. * This one is used in per-process mode. */ static void print_taskbar_process(pid_t *pidlist, int pidlist_len, int pidlist_index) { const int ITEM_WIDTH = 8; int number_item; int i; int xpos = 0; if (!display_initialized) { return; } number_item = (screen_width / ITEM_WIDTH) - 1; i = pidlist_index - (pidlist_index % number_item); (void) werase(taskbar); if (i != 0) { (void) mvwprintw(taskbar, 0, xpos, "<-"); } xpos = ITEM_WIDTH / 2; while (xpos + ITEM_WIDTH <= screen_width && i < pidlist_len) { char str[ITEM_WIDTH+1]; int slen; const char *pname = lt_stat_proc_get_name(pidlist[i]); if (pname && pname[0]) { (void) snprintf(str, sizeof (str) - 1, "%s", pname); } else { (void) snprintf(str, sizeof (str) - 1, "<%d>", pidlist[i]); } slen = strlen(str); if (slen < ITEM_WIDTH) { (void) memset(&str[slen], ' ', ITEM_WIDTH - slen); } str[sizeof (str) - 1] = 0; if (i == pidlist_index) { (void) wattron(taskbar, A_REVERSE); } (void) mvwprintw(taskbar, 0, xpos, "%s", str); if (i == pidlist_index) { (void) wattroff(taskbar, A_REVERSE); } xpos += ITEM_WIDTH; i++; } if (i != pidlist_len) { (void) mvwprintw(taskbar, 0, screen_width - 2, "->"); } (void) wrefresh(taskbar); } /* * List all processes in task bar. * This one is used in per-thread mode. */ static void print_taskbar_thread(pid_t *pidlist, id_t *tidlist, int list_len, int list_index) { const int ITEM_WIDTH = 12; int number_item; int i; int xpos = 0; const char *pname = NULL; pid_t last_pid = INVALID_PID; if (!display_initialized) { return; } number_item = (screen_width - 8) / ITEM_WIDTH; i = list_index - (list_index % number_item); (void) werase(taskbar); if (i != 0) { (void) mvwprintw(taskbar, 0, xpos, "<-"); } xpos = 4; while (xpos + ITEM_WIDTH <= screen_width && i < list_len) { char str[ITEM_WIDTH+1]; int slen, tlen; if (pidlist[i] != last_pid) { pname = lt_stat_proc_get_name(pidlist[i]); last_pid = pidlist[i]; } /* * Calculate thread id length, leave enough space by print * shorter process name. */ tlen = snprintf(NULL, 0, "_%d", tidlist[i]); if (pname && pname[0]) { (void) snprintf(str, sizeof (str) - tlen - 1, "%s", pname); } else { (void) snprintf(str, sizeof (str) - tlen - 1, "<%d>", pidlist[i]); } slen = strlen(str); (void) snprintf(&str[slen], sizeof (str) - slen, "_%d", tidlist[i]); slen += tlen; if (slen < ITEM_WIDTH) { (void) memset(&str[slen], ' ', ITEM_WIDTH - slen); } str[sizeof (str) - 1] = 0; if (i == list_index) { (void) wattron(taskbar, A_REVERSE); } (void) mvwprintw(taskbar, 0, xpos, "%s", str); if (i == list_index) { (void) wattroff(taskbar, A_REVERSE); } xpos += ITEM_WIDTH; i++; } if (i != list_len) { (void) mvwprintw(taskbar, 0, screen_width - 2, "->"); } (void) wrefresh(taskbar); } /* * Print statistics. Calls print_statistics(). * This one is used in per-thread mode. */ static void print_thread(pid_t pid, id_t tid) { void *list; char header[256]; char tmp[30]; if (!display_initialized) { return; } list = lt_stat_list_create(current_list_type, LT_LEVEL_THREAD, pid, tid, 8, sort_type); (void) werase(process_window); (void) wattron(process_window, A_REVERSE); (void) snprintf(header, sizeof (header), "Process %s (%i), LWP %d", lt_stat_proc_get_name(pid), pid, tid); fill_space_right(header, screen_width, sizeof (header)); (void) mvwprintw(process_window, 0, 0, "%s", header); if (current_list_type != LT_LIST_SPECIALS) { (void) mvwprintw(process_window, 0, 40, lt_text("Total: %s"), get_time_string( (double)lt_stat_list_get_gtotal(list), tmp, sizeof (tmp), 12)); } print_current_mode(); (void) wattroff(process_window, A_REVERSE); print_statistics(process_window, 1, 8, list); lt_stat_list_free(list); (void) wrefresh(process_window); } /* * Update hint string at the bottom line. The message to print is stored in * hint. If hint is NULL, the function will pick a message from useful tips * and display it. */ static void print_hint(const char *hint) { const char *HINTS[] = { "Press '<' or '>' to switch between processes.", "Press 'q' to exit.", "Press 'r' to refresh immediately.", "Press 't' to toggle Process/Thread display mode.", "Press 'h' for help.", "Use 'c', 'a', 'm', 'p' to change sort criteria." "Use '1', '2', '3' to switch between windows." }; const uint64_t update_interval = 5000; /* 5 seconds */ static int index = 0; static uint64_t next_hint = 0; uint64_t now = lt_millisecond(); if (!display_initialized) { return; } if (hint == NULL) { if (now < next_hint) { return; } hint = HINTS[index]; index = (index + 1) % (sizeof (HINTS) / sizeof (HINTS[0])); next_hint = now + update_interval; } else { /* * To ensure important message * show at least 2 cycles. */ next_hint = now + update_interval * 2; } (void) werase(hintbar); (void) mvwprintw(hintbar, 0, (screen_width - strlen(hint)) / 2, "%s", lt_text(hint)); (void) wrefresh(hintbar); } /* * Get information from existing statistics, and create a PID list * or PID/TID list based on current display mode. */ static void get_plist(pid_t **plist, id_t **tlist, int *list_len, int *list_index) { if (!thread_mode) { /* Per-process mode */ *list_len = lt_stat_proc_list_create(plist, NULL); /* Search for previous selected PID */ for (*list_index = 0; *list_index < *list_len && (*plist)[*list_index] != selected_pid; ++*list_index) { } if (*list_index >= *list_len) { /* * The old selected pid is gone. * Select the first one */ *list_index = 0; } } else { /* Per-thread mode */ *list_len = lt_stat_proc_list_create(plist, tlist); /* Search for previous selected PID & TID */ for (*list_index = 0; *list_index < *list_len; ++*list_index) { if ((*plist)[*list_index] == selected_pid && (*tlist)[*list_index] == selected_tid) { break; } } if (*list_index >= *list_len) { /* * The old selected pid/tid is gone. * Select the first one in the pid */ for (*list_index = 0; *list_index < *list_len && (*plist)[*list_index] != selected_pid; ++*list_index) { } } if (*list_index >= *list_len) { /* * The old selected pid is gone. * Select the first one */ *list_index = 0; } } } static void print_help(void) { const char *HELP[] = { TITLE, COPYRIGHT, "", "These single-character commands are available:", "< - Move to previous process/thread.", "> - Move to next process/thread.", "q - Exit.", "r - Refresh.", "t - Toggle process/thread mode.", "c - Sort by count.", "a - Sort by average.", "m - Sort by maximum.", "p - Sort by percent.", "1 - Show list by causes.", "2 - Show list of special entries.", "3 - Show list by synchronization objects.", "h - Show this help.", "", "Press any key to continue..." }; int i; if (!display_initialized) { return; } for (i = 0; i < sizeof (HELP) / sizeof (HELP[0]); ++i) { (void) mvwprintw(stdscr, i, 0, "%s", HELP[i]); } (void) refresh(); } /* * Print title on screen */ static void print_title(void) { if (!display_initialized) { return; } (void) wattrset(titlebar, COLOR_PAIR(LT_COLOR_HEADER)); (void) wbkgd(titlebar, COLOR_PAIR(LT_COLOR_HEADER)); (void) werase(titlebar); (void) mvwprintw(titlebar, 0, (screen_width - strlen(TITLE)) / 2, "%s", TITLE); (void) wrefresh(titlebar); (void) werase(captionbar); (void) mvwprintw(captionbar, 0, 0, "%s", lt_text( " Cause " "Count Average Maximum Percent")); (void) wrefresh(captionbar); (void) wattrset(hintbar, COLOR_PAIR(LT_COLOR_HEADER)); (void) wbkgd(hintbar, COLOR_PAIR(LT_COLOR_HEADER)); } /* * Signal handler on terminal resize */ /* ARGSUSED */ static void on_resize(int sig) { lt_gpipe_break("r"); } /* * Initialize display part. Screen will be cleared when this function returns. */ void lt_display_init(void) { if (display_initialized) { return; } /* Window resize signal */ (void) signal(SIGWINCH, on_resize); /* Initialize curses lib. */ (void) initscr(); (void) start_color(); (void) keypad(stdscr, TRUE); (void) nonl(); (void) cbreak(); (void) noecho(); (void) curs_set(0); /* Set up color pairs */ (void) init_pair(LT_COLOR_DEFAULT, COLOR_WHITE, COLOR_BLACK); (void) init_pair(LT_COLOR_HEADER, COLOR_BLACK, COLOR_WHITE); curses_inited = TRUE; getmaxyx(stdscr, screen_height, screen_width); if (screen_width < LT_WINDOW_X || screen_height < LT_WINDOW_Y) { (void) mvwprintw(stdscr, 0, 0, "Terminal size is too small."); (void) mvwprintw(stdscr, 1, 0, "Please resize it to 80x24 or larger."); (void) mvwprintw(stdscr, 2, 0, "Press q to quit."); (void) refresh(); return; } /* Setup all windows on screen. */ titlebar = subwin(stdscr, 1, screen_width, 0, 0); captionbar = subwin(stdscr, 1, screen_width, 1, 0); sysglobal_window = subwin(stdscr, screen_height / 2 - 1, screen_width, 2, 0); process_window = subwin(stdscr, screen_height / 2 - 3, screen_width, screen_height / 2 + 1, 0); taskbar = subwin(stdscr, 1, screen_width, screen_height - 2, 0); hintbar = subwin(stdscr, 1, screen_width, screen_height - 1, 0); (void) werase(stdscr); (void) refresh(); display_initialized = TRUE; print_title(); } /* * The event loop. Display data on screen and handles key press. Will return * after "duration" seconds, unless exit or refresh hotkey is pressed. * Return 0 means main() should exit. 1 means to loop again. */ int lt_display_loop(int duration) { uint64_t start; int remaining; struct timeval timeout; fd_set read_fd; int need_refresh = TRUE; pid_t *plist = NULL; id_t *tlist = NULL; int list_len = 0; int list_index = 0; int retval = 1; int next_snap; int gpipe; start = lt_millisecond(); gpipe = lt_gpipe_readfd(); if (!show_help) { print_hint(NULL); print_sysglobal(); } get_plist(&plist, &tlist, &list_len, &list_index); for (;;) { if (list_len != 0 && need_refresh && !show_help) { if (!thread_mode) { print_taskbar_process(plist, list_len, list_index); print_process(plist[list_index]); } else { print_taskbar_thread(plist, tlist, list_len, list_index); print_thread(plist[list_index], tlist[list_index]); } } need_refresh = TRUE; /* Usually we need refresh. */ remaining = duration - (int)(lt_millisecond() - start); if (remaining <= 0) { break; } /* Embedded dtrace snap action here. */ next_snap = lt_dtrace_work(0); if (next_snap == 0) { /* * Just did a snap, check again to get time for * next shot. */ next_snap = lt_dtrace_work(0); } if (next_snap > 0 && remaining > next_snap) { remaining = next_snap; } timeout.tv_sec = remaining / 1000; timeout.tv_usec = (remaining % 1000) * 1000; FD_ZERO(&read_fd); FD_SET(0, &read_fd); FD_SET(gpipe, &read_fd); /* Wait for keyboard input, or signal from gpipe */ if (select(gpipe + 1, &read_fd, NULL, NULL, &timeout) > 0) { int k = 0; if (FD_ISSET(gpipe, &read_fd)) { /* data from pipe has priority */ char ch; /* Need this for big-endian */ (void) read(gpipe, &ch, 1); k = ch; } else { k = getch(); } /* * We check if we need to update hint line whenever we * get chance. * NOTE: current implementation depends on * g_config.snap_interval, but it's OK because it * doesn't have to be precise. */ print_hint(NULL); /* * If help is on, and a key press happens, * we need to clear the help and go on. */ if (show_help) { (void) werase(stdscr); (void) refresh(); print_title(); print_sysglobal(); show_help = FALSE; /* Drop this key and continue */ continue; } switch (k) { case 'Q': case 'q': retval = 0; goto quit; case 'R': case 'r': lt_display_deinit(); lt_display_init(); goto quit; case 'H': case 'h': show_help = TRUE; (void) werase(stdscr); (void) refresh(); print_help(); break; case ',': case '<': case KEY_LEFT: --list_index; if (list_index < 0) { list_index = 0; } break; case '.': case '>': case KEY_RIGHT: ++list_index; if (list_index >= list_len) { list_index = list_len - 1; } break; case 'a': case 'A': sort_type = LT_SORT_AVG; print_sysglobal(); break; case 'p': case 'P': sort_type = LT_SORT_TOTAL; print_sysglobal(); break; case 'm': case 'M': sort_type = LT_SORT_MAX; print_sysglobal(); break; case 'c': case 'C': sort_type = LT_SORT_COUNT; print_sysglobal(); break; case 't': case 'T': if (plist != NULL) { selected_pid = plist[list_index]; } selected_tid = INVALID_TID; thread_mode = !thread_mode; get_plist(&plist, &tlist, &list_len, &list_index); break; case '1': case '!': current_list_type = LT_LIST_CAUSE; print_sysglobal(); break; case '2': case '@': if (g_config.low_overhead_mode) { lt_display_error("Switching mode is " "not available for '-f low'."); } else { current_list_type = LT_LIST_SPECIALS; print_sysglobal(); } break; case '3': case '#': if (g_config.trace_syncobj) { current_list_type = LT_LIST_SOBJ; print_sysglobal(); } else if (g_config.low_overhead_mode) { lt_display_error("Switching mode is " "not available for '-f low'."); } else { lt_display_error("Tracing " "synchronization objects is " "disabled."); } break; default: /* Wake up for nothing, no need to refresh */ need_refresh = FALSE; break; } } else { need_refresh = FALSE; } } quit: if (plist != NULL) { selected_pid = plist[list_index]; } if (tlist != NULL) { selected_tid = tlist[list_index]; } lt_stat_proc_list_free(plist, tlist); return (retval); } /* * Close display part. */ void lt_display_deinit(void) { if (curses_inited) { (void) clear(); (void) refresh(); (void) endwin(); } titlebar = NULL; captionbar = NULL; sysglobal_window = NULL; taskbar = NULL; process_window = NULL; hintbar = NULL; screen_width = 1; screen_height = 1; display_initialized = FALSE; curses_inited = FALSE; } /* * Print error message. */ /* ARGSUSED */ void lt_display_error(const char *fmt, ...) { va_list vl; char tmp[81]; int l; va_start(vl, fmt); (void) vsnprintf(tmp, sizeof (tmp), fmt, vl); va_end(vl); l = strlen(tmp); while (l > 0 && (tmp[l - 1] == '\n' || tmp[l - 1] == '\r')) { tmp[l - 1] = 0; --l; } if (!display_initialized) { (void) printf("%s\n", tmp); } else if (!show_help) { print_hint(tmp); } }