root/usr/src/cmd/latencytop/display.c
/*
 * 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);
        }

}