OpenSolaris_b135/cmd/latencytop/common/display.c

Compare this file to the similar file:
Show the results in this format:

/*
 * 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 <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/time.h>
#include <dirent.h>
#include <curses.h>
#include <time.h>
#include <wchar.h>
#include <ctype.h>
#include <stdarg.h>
#include <signal.h>

#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 dimension */
static int	screen_width = 1, screen_height = 1;
/* Is display initialized, i.e. are window pointers set up. */
static int	display_initialized = FALSE;
/* Is initscr() called */
static int	curses_inited = FALSE;

/* To handle user key presses */
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;
/* Type of list being displayed */
static int current_list_type = LT_LIST_CAUSE;
static int show_help = FALSE;

/* Help functions that append/prepend a blank to the given 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);
	}
}

/* Convert the nanosecond value to a human readable string */
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);
}

/* Used in print_statistics below */
#define	WIDTH_REASON_STRING	36
#define	WIDTH_COUNT		12
#define	WIDTH_AVG		12
#define	WIDTH_MAX		12
#define	WIDTH_PCT		8
#define	BEGIN_COUNT		WIDTH_REASON_STRING
#define	BEGIN_AVG		(BEGIN_COUNT + WIDTH_COUNT)
#define	BEGIN_MAX		(BEGIN_AVG + WIDTH_AVG)
#define	BEGIN_PCT		(BEGIN_MAX + WIDTH_MAX)

/*
 * Print statistics in global/process pane. Called by print_sysglobal
 * print_process.
 *
 * Parameters:
 *		window - the global or process statistics window.
 *		begin_line - where to start printing.
 *		count - how many lines should be printed.
 *		list - a stat_list.
 */
static void
print_statistics(WINDOW * window, int begin_line, int nlines, 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 < nlines && lt_stat_list_has_item(list, i)) {

		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), "%llu", count);
		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_AVG,
		    "%s", get_time_string(
		    (double)lt_stat_list_get_sum(list, i) / count,
		    tmp, sizeof (tmp), WIDTH_AVG));

		(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 statistics in global pane.
 */
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", "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. Mode is combination of:
 *
 * 	"Process or Thread", and "1 or 2 or 3".
 */
static void
print_current_mode()
{
	char type;

	if (!display_initialized) {
		return;
	}

	switch (current_list_type) {
	case LT_LIST_CAUSE:
		type = '1';
		break;
	case LT_LIST_SPECIALS:
		type = '2';
		break;
	case LT_LIST_SOBJ:
		type = '3';
		break;
	default:
		type = '?';
		break;
	}

	(void) mvwprintw(process_window, 0, screen_width - 8, "View: %c%c",
	    type, thread_mode ? 'T' : 'P');
}

/*
 * Print process window bar when the list is empty.
 */
static void
print_empty_process_bar()
{
	char header[256];

	if (!display_initialized) {
		return;
	}

	(void) werase(process_window);
	(void) wattron(process_window, A_REVERSE);
	(void) snprintf(header, sizeof (header),
	    "No process/thread data is available");
	fill_space_right(header, screen_width, sizeof (header));
	(void) mvwprintw(process_window, 0, 0, "%s", header);

	print_current_mode();
	(void) wattroff(process_window, A_REVERSE);

	(void) wrefresh(process_window);
}

/*
 * Print per-process statistics in process pane.
 * This is called when mode of operation is process.
 */
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), %d threads",
	    lt_stat_proc_get_name(pid), pid, lt_stat_proc_get_nthreads(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, 48, "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);
}

/*
 * Display the list of processes that are tracked, in task bar.
 * This one is called when mode of operation is process.
 */
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);
}

/*
 * Display the list of processes that are tracked, in task bar.
 * This one is called when mode of operation is thread.
 */
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 length of thread's ID; use shorter process name
		 * in order to save space on the screen.
		 */
		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 per-thread statistics in process pane.
 * This is called when mode of operation is thread.
 */
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, 48, "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 display its own message.
 */
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 {
		/*
		 * Important messages are displayed at least every 2 cycles.
		 */
		next_hint = now + update_interval * 2;
	}

	(void) werase(hintbar);
	(void) mvwprintw(hintbar, 0, (screen_width - strlen(hint)) / 2,
	    "%s", hint);
	(void) wrefresh(hintbar);
}

/*
 * Create a PID list or a PID/TID list (if operation mode is thread) from
 * available statistics.
 */
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 previously selected PID */
		for (*list_index = 0; *list_index < *list_len &&
		    (*plist)[*list_index] != selected_pid;
		    ++*list_index) {
		}

		if (*list_index >= *list_len) {
			/*
			 * The previously 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 previously 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 previously selected pid/tid is gone.
			 * Select the first one.
			 */
			for (*list_index = 0;
			    *list_index < *list_len &&
			    (*plist)[*list_index] != selected_pid;
			    ++*list_index) {
			}
		}

		if (*list_index >= *list_len) {
			/*
			 * The previously selected pid is gone.
			 * Select the first one
			 */
			*list_index = 0;
		}
	}
}

/* Print help message when user presses 'h' hot key */
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",
	    "               Cause                    "
	    "Count      Average     Maximum   Percent");
	(void) wrefresh(captionbar);

	(void) wattrset(hintbar, COLOR_PAIR(LT_COLOR_HEADER));
	(void) wbkgd(hintbar, COLOR_PAIR(LT_COLOR_HEADER));
}

/*
 * Handle signal from terminal resize
 */
/* ARGSUSED */
static void
on_resize(int sig)
{
	lt_gpipe_break("r");
}

/*
 * Initialize display. Display 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 library */
	(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;
	}

	/* Set up all window panes */
	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 for display. It displays data on screen and handles hotkey
 * presses.
 *
 * Parameter :
 *		duration - returns after 'duration'
 *
 * The function also returns if user presses 'q', 'Ctrl+C' or 'r'.
 *
 * Return value:
 *		0 - main() exits
 *		1 - main() calls it 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 (need_refresh && !show_help) {
			if (list_len != 0) {
				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]);
				}
			} else {
				print_empty_process_bar();
			}
		}

		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 time for the next one.
			 */
			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;
				(void) read(gpipe, &ch, 1);
				k = ch; /* Need this for big-endianness */
			} else {
				k = getch();
			}

			/*
			 * Check if we need to update the hint line whenever we
			 * get a chance.
			 * NOTE: current implementation depends on
			 * g_config.lt_cfg_snap_interval, but it's OK because it
			 * doesn't have to be precise.
			 */
			print_hint(NULL);
			/*
			 * If help is on display right now, and a key press
			 * happens, we need to clear the help and continue.
			 */
			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.lt_cfg_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.lt_cfg_trace_syncobj) {
					current_list_type = LT_LIST_SOBJ;
					print_sysglobal();
				} else if (g_config.lt_cfg_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 refresh is needed */
				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);
}

/*
 * Clean up display.
 */
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 message when display error happens.
 */
/* 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) fprintf(stderr, "%s\n", tmp);
	} else if (!show_help) {
		print_hint(tmp);
	}

}