diff options
Diffstat (limited to 'tools/perf/ui/tui')
-rw-r--r-- | tools/perf/ui/tui/Build | 4 | ||||
-rw-r--r-- | tools/perf/ui/tui/helpline.c | 63 | ||||
-rw-r--r-- | tools/perf/ui/tui/progress.c | 87 | ||||
-rw-r--r-- | tools/perf/ui/tui/setup.c | 178 | ||||
-rw-r--r-- | tools/perf/ui/tui/tui.h | 7 | ||||
-rw-r--r-- | tools/perf/ui/tui/util.c | 274 |
6 files changed, 613 insertions, 0 deletions
diff --git a/tools/perf/ui/tui/Build b/tools/perf/ui/tui/Build new file mode 100644 index 000000000..f916df33a --- /dev/null +++ b/tools/perf/ui/tui/Build @@ -0,0 +1,4 @@ +perf-y += setup.o +perf-y += util.o +perf-y += helpline.o +perf-y += progress.o diff --git a/tools/perf/ui/tui/helpline.c b/tools/perf/ui/tui/helpline.c new file mode 100644 index 000000000..298d6af82 --- /dev/null +++ b/tools/perf/ui/tui/helpline.c @@ -0,0 +1,63 @@ +// SPDX-License-Identifier: GPL-2.0 +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <pthread.h> +#include <linux/kernel.h> +#include <linux/string.h> + +#include "../helpline.h" +#include "../ui.h" +#include "../libslang.h" + +char ui_helpline__last_msg[1024]; +bool tui_helpline__set; + +static void tui_helpline__pop(void) +{ +} + +static void tui_helpline__push(const char *msg) +{ + const size_t sz = sizeof(ui_helpline__current); + + SLsmg_gotorc(SLtt_Screen_Rows - 1, 0); + SLsmg_set_color(0); + SLsmg_write_nstring((char *)msg, SLtt_Screen_Cols); + SLsmg_refresh(); + strlcpy(ui_helpline__current, msg, sz); +} + +static int tui_helpline__show(const char *format, va_list ap) +{ + int ret; + static int backlog; + + pthread_mutex_lock(&ui__lock); + ret = vscnprintf(ui_helpline__last_msg + backlog, + sizeof(ui_helpline__last_msg) - backlog, format, ap); + backlog += ret; + + tui_helpline__set = true; + + if (ui_helpline__last_msg[backlog - 1] == '\n') { + ui_helpline__puts(ui_helpline__last_msg); + SLsmg_refresh(); + backlog = 0; + } + pthread_mutex_unlock(&ui__lock); + + return ret; +} + +struct ui_helpline tui_helpline_fns = { + .pop = tui_helpline__pop, + .push = tui_helpline__push, + .show = tui_helpline__show, +}; + +void ui_helpline__init(void) +{ + helpline_fns = &tui_helpline_fns; + ui_helpline__puts(" "); +} diff --git a/tools/perf/ui/tui/progress.c b/tools/perf/ui/tui/progress.c new file mode 100644 index 000000000..3d74af5a7 --- /dev/null +++ b/tools/perf/ui/tui/progress.c @@ -0,0 +1,87 @@ +// SPDX-License-Identifier: GPL-2.0 +#include <linux/kernel.h> +#include "../progress.h" +#include "../libslang.h" +#include "../ui.h" +#include "tui.h" +#include "units.h" +#include "../browser.h" + +static void __tui_progress__init(struct ui_progress *p) +{ + p->next = p->step = p->total / (SLtt_Screen_Cols - 2) ?: 1; +} + +static int get_title(struct ui_progress *p, char *buf, size_t size) +{ + char buf_cur[20]; + char buf_tot[20]; + int ret; + + ret = unit_number__scnprintf(buf_cur, sizeof(buf_cur), p->curr); + ret += unit_number__scnprintf(buf_tot, sizeof(buf_tot), p->total); + + return ret + scnprintf(buf, size, "%s [%s/%s]", + p->title, buf_cur, buf_tot); +} + +static void tui_progress__update(struct ui_progress *p) +{ + char buf[100], *title = (char *) p->title; + int bar, y; + /* + * FIXME: We should have a per UI backend way of showing progress, + * stdio will just show a percentage as NN%, etc. + */ + if (use_browser <= 0) + return; + + if (p->total == 0) + return; + + if (p->size) { + get_title(p, buf, sizeof(buf)); + title = buf; + } + + ui__refresh_dimensions(false); + pthread_mutex_lock(&ui__lock); + y = SLtt_Screen_Rows / 2 - 2; + SLsmg_set_color(0); + SLsmg_draw_box(y, 0, 3, SLtt_Screen_Cols); + SLsmg_gotorc(y++, 1); + SLsmg_write_string(title); + SLsmg_fill_region(y, 1, 1, SLtt_Screen_Cols - 2, ' '); + SLsmg_set_color(HE_COLORSET_SELECTED); + bar = ((SLtt_Screen_Cols - 2) * p->curr) / p->total; + SLsmg_fill_region(y, 1, 1, bar, ' '); + SLsmg_refresh(); + pthread_mutex_unlock(&ui__lock); +} + +static void tui_progress__finish(void) +{ + int y; + + if (use_browser <= 0) + return; + + ui__refresh_dimensions(false); + pthread_mutex_lock(&ui__lock); + y = SLtt_Screen_Rows / 2 - 2; + SLsmg_set_color(0); + SLsmg_fill_region(y, 0, 3, SLtt_Screen_Cols, ' '); + SLsmg_refresh(); + pthread_mutex_unlock(&ui__lock); +} + +static struct ui_progress_ops tui_progress__ops = { + .init = __tui_progress__init, + .update = tui_progress__update, + .finish = tui_progress__finish, +}; + +void tui_progress__init(void) +{ + ui_progress__ops = &tui_progress__ops; +} diff --git a/tools/perf/ui/tui/setup.c b/tools/perf/ui/tui/setup.c new file mode 100644 index 000000000..e9bfe856a --- /dev/null +++ b/tools/perf/ui/tui/setup.c @@ -0,0 +1,178 @@ +#include <errno.h> +#include <signal.h> +#include <stdbool.h> +#include <stdlib.h> +#include <unistd.h> +#include <linux/kernel.h> +#ifdef HAVE_BACKTRACE_SUPPORT +#include <execinfo.h> +#endif + +#include "../../util/debug.h" +#include "../../perf.h" +#include "../browser.h" +#include "../helpline.h" +#include "../ui.h" +#include "../util.h" +#include "../libslang.h" +#include "../keysyms.h" +#include "tui.h" + +static volatile int ui__need_resize; + +extern struct perf_error_ops perf_tui_eops; +extern bool tui_helpline__set; + +extern void hist_browser__init_hpp(void); + +void ui__refresh_dimensions(bool force) +{ + if (force || ui__need_resize) { + ui__need_resize = 0; + pthread_mutex_lock(&ui__lock); + SLtt_get_screen_size(); + SLsmg_reinit_smg(); + pthread_mutex_unlock(&ui__lock); + } +} + +static void ui__sigwinch(int sig __maybe_unused) +{ + ui__need_resize = 1; +} + +static void ui__setup_sigwinch(void) +{ + static bool done; + + if (done) + return; + + done = true; + pthread__unblock_sigwinch(); + signal(SIGWINCH, ui__sigwinch); +} + +int ui__getch(int delay_secs) +{ + struct timeval timeout, *ptimeout = delay_secs ? &timeout : NULL; + fd_set read_set; + int err, key; + + ui__setup_sigwinch(); + + FD_ZERO(&read_set); + FD_SET(0, &read_set); + + if (delay_secs) { + timeout.tv_sec = delay_secs; + timeout.tv_usec = 0; + } + + err = select(1, &read_set, NULL, NULL, ptimeout); + + if (err == 0) + return K_TIMER; + + if (err == -1) { + if (errno == EINTR) + return K_RESIZE; + return K_ERROR; + } + + key = SLang_getkey(); + if (key != K_ESC) + return key; + + FD_ZERO(&read_set); + FD_SET(0, &read_set); + timeout.tv_sec = 0; + timeout.tv_usec = 20; + err = select(1, &read_set, NULL, NULL, &timeout); + if (err == 0) + return K_ESC; + + SLang_ungetkey(key); + return SLkp_getkey(); +} + +#ifdef HAVE_BACKTRACE_SUPPORT +static void ui__signal_backtrace(int sig) +{ + void *stackdump[32]; + size_t size; + + ui__exit(false); + psignal(sig, "perf"); + + printf("-------- backtrace --------\n"); + size = backtrace(stackdump, ARRAY_SIZE(stackdump)); + backtrace_symbols_fd(stackdump, size, STDOUT_FILENO); + + exit(0); +} +#else +# define ui__signal_backtrace ui__signal +#endif + +static void ui__signal(int sig) +{ + ui__exit(false); + psignal(sig, "perf"); + exit(0); +} + +int ui__init(void) +{ + int err; + + SLutf8_enable(-1); + SLtt_get_terminfo(); + SLtt_get_screen_size(); + + err = SLsmg_init_smg(); + if (err < 0) + goto out; + err = SLang_init_tty(-1, 0, 0); + if (err < 0) + goto out; + + err = SLkp_init(); + if (err < 0) { + pr_err("TUI initialization failed.\n"); + goto out; + } + + SLkp_define_keysym((char *)"^(kB)", SL_KEY_UNTAB); + + signal(SIGSEGV, ui__signal_backtrace); + signal(SIGFPE, ui__signal_backtrace); + signal(SIGINT, ui__signal); + signal(SIGQUIT, ui__signal); + signal(SIGTERM, ui__signal); + + perf_error__register(&perf_tui_eops); + + ui_helpline__init(); + ui_browser__init(); + tui_progress__init(); + + hist_browser__init_hpp(); +out: + return err; +} + +void ui__exit(bool wait_for_ok) +{ + if (wait_for_ok && tui_helpline__set) + ui__question_window("Fatal Error", + ui_helpline__last_msg, + "Press any key...", 0); + + SLtt_set_cursor_visibility(1); + SLsmg_refresh(); + SLsmg_reset_smg(); + SLang_reset_tty(); + + perf_error__unregister(&perf_tui_eops); +} diff --git a/tools/perf/ui/tui/tui.h b/tools/perf/ui/tui/tui.h new file mode 100644 index 000000000..8de06f634 --- /dev/null +++ b/tools/perf/ui/tui/tui.h @@ -0,0 +1,7 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef _PERF_TUI_H_ +#define _PERF_TUI_H_ 1 + +void tui_progress__init(void); + +#endif /* _PERF_TUI_H_ */ diff --git a/tools/perf/ui/tui/util.c b/tools/perf/ui/tui/util.c new file mode 100644 index 000000000..0f562e2cb --- /dev/null +++ b/tools/perf/ui/tui/util.c @@ -0,0 +1,274 @@ +// SPDX-License-Identifier: GPL-2.0 +#include <signal.h> +#include <stdbool.h> +#include <string.h> +#include <stdlib.h> +#include <sys/ttydefaults.h> + +#include "../browser.h" +#include "../keysyms.h" +#include "../helpline.h" +#include "../ui.h" +#include "../util.h" +#include "../libslang.h" + +static void ui_browser__argv_write(struct ui_browser *browser, + void *entry, int row) +{ + char **arg = entry; + bool current_entry = ui_browser__is_current_entry(browser, row); + + ui_browser__set_color(browser, current_entry ? HE_COLORSET_SELECTED : + HE_COLORSET_NORMAL); + ui_browser__write_nstring(browser, *arg, browser->width); +} + +static int popup_menu__run(struct ui_browser *menu, int *keyp) +{ + int key; + + if (ui_browser__show(menu, " ", "ESC: exit, ENTER|->: Select option") < 0) + return -1; + + while (1) { + key = ui_browser__run(menu, 0); + + switch (key) { + case K_RIGHT: + case K_ENTER: + key = menu->index; + break; + case K_LEFT: + case K_ESC: + case 'q': + case CTRL('c'): + key = -1; + break; + default: + if (keyp) { + *keyp = key; + key = menu->nr_entries; + break; + } + continue; + } + + break; + } + + ui_browser__hide(menu); + return key; +} + +int ui__popup_menu(int argc, char * const argv[], int *keyp) +{ + struct ui_browser menu = { + .entries = (void *)argv, + .refresh = ui_browser__argv_refresh, + .seek = ui_browser__argv_seek, + .write = ui_browser__argv_write, + .nr_entries = argc, + }; + return popup_menu__run(&menu, keyp); +} + +int ui_browser__input_window(const char *title, const char *text, char *input, + const char *exit_msg, int delay_secs) +{ + int x, y, len, key; + int max_len = 60, nr_lines = 0; + static char buf[50]; + const char *t; + + t = text; + while (1) { + const char *sep = strchr(t, '\n'); + + if (sep == NULL) + sep = strchr(t, '\0'); + len = sep - t; + if (max_len < len) + max_len = len; + ++nr_lines; + if (*sep == '\0') + break; + t = sep + 1; + } + + pthread_mutex_lock(&ui__lock); + + max_len += 2; + nr_lines += 8; + y = SLtt_Screen_Rows / 2 - nr_lines / 2; + x = SLtt_Screen_Cols / 2 - max_len / 2; + + SLsmg_set_color(0); + SLsmg_draw_box(y, x++, nr_lines, max_len); + if (title) { + SLsmg_gotorc(y, x + 1); + SLsmg_write_string((char *)title); + } + SLsmg_gotorc(++y, x); + nr_lines -= 7; + max_len -= 2; + SLsmg_write_wrapped_string((unsigned char *)text, y, x, + nr_lines, max_len, 1); + y += nr_lines; + len = 5; + while (len--) { + SLsmg_gotorc(y + len - 1, x); + SLsmg_write_nstring((char *)" ", max_len); + } + SLsmg_draw_box(y++, x + 1, 3, max_len - 2); + + SLsmg_gotorc(y + 3, x); + SLsmg_write_nstring((char *)exit_msg, max_len); + SLsmg_refresh(); + + pthread_mutex_unlock(&ui__lock); + + x += 2; + len = 0; + key = ui__getch(delay_secs); + while (key != K_TIMER && key != K_ENTER && key != K_ESC) { + pthread_mutex_lock(&ui__lock); + + if (key == K_BKSPC) { + if (len == 0) { + pthread_mutex_unlock(&ui__lock); + goto next_key; + } + SLsmg_gotorc(y, x + --len); + SLsmg_write_char(' '); + } else { + buf[len] = key; + SLsmg_gotorc(y, x + len++); + SLsmg_write_char(key); + } + SLsmg_refresh(); + + pthread_mutex_unlock(&ui__lock); + + /* XXX more graceful overflow handling needed */ + if (len == sizeof(buf) - 1) { + ui_helpline__push("maximum size of symbol name reached!"); + key = K_ENTER; + break; + } +next_key: + key = ui__getch(delay_secs); + } + + buf[len] = '\0'; + strncpy(input, buf, len+1); + return key; +} + +void __ui__info_window(const char *title, const char *text, const char *exit_msg) +{ + int x, y; + int max_len = 0, nr_lines = 0; + const char *t; + + t = text; + while (1) { + const char *sep = strchr(t, '\n'); + int len; + + if (sep == NULL) + sep = strchr(t, '\0'); + len = sep - t; + if (max_len < len) + max_len = len; + ++nr_lines; + if (*sep == '\0') + break; + t = sep + 1; + } + + max_len += 2; + nr_lines += 2; + if (exit_msg) + nr_lines += 2; + y = SLtt_Screen_Rows / 2 - nr_lines / 2, + x = SLtt_Screen_Cols / 2 - max_len / 2; + + SLsmg_set_color(0); + SLsmg_draw_box(y, x++, nr_lines, max_len); + if (title) { + SLsmg_gotorc(y, x + 1); + SLsmg_write_string((char *)title); + } + SLsmg_gotorc(++y, x); + if (exit_msg) + nr_lines -= 2; + max_len -= 2; + SLsmg_write_wrapped_string((unsigned char *)text, y, x, + nr_lines, max_len, 1); + if (exit_msg) { + SLsmg_gotorc(y + nr_lines - 2, x); + SLsmg_write_nstring((char *)" ", max_len); + SLsmg_gotorc(y + nr_lines - 1, x); + SLsmg_write_nstring((char *)exit_msg, max_len); + } +} + +void ui__info_window(const char *title, const char *text) +{ + pthread_mutex_lock(&ui__lock); + __ui__info_window(title, text, NULL); + SLsmg_refresh(); + pthread_mutex_unlock(&ui__lock); +} + +int ui__question_window(const char *title, const char *text, + const char *exit_msg, int delay_secs) +{ + pthread_mutex_lock(&ui__lock); + __ui__info_window(title, text, exit_msg); + SLsmg_refresh(); + pthread_mutex_unlock(&ui__lock); + return ui__getch(delay_secs); +} + +int ui__help_window(const char *text) +{ + return ui__question_window("Help", text, "Press any key...", 0); +} + +int ui__dialog_yesno(const char *msg) +{ + return ui__question_window(NULL, msg, "Enter: Yes, ESC: No", 0); +} + +static int __ui__warning(const char *title, const char *format, va_list args) +{ + char *s; + + if (vasprintf(&s, format, args) > 0) { + int key; + + key = ui__question_window(title, s, "Press any key...", 0); + free(s); + return key; + } + + fprintf(stderr, "%s\n", title); + vfprintf(stderr, format, args); + return K_ESC; +} + +static int perf_tui__error(const char *format, va_list args) +{ + return __ui__warning("Error:", format, args); +} + +static int perf_tui__warning(const char *format, va_list args) +{ + return __ui__warning("Warning:", format, args); +} + +struct perf_error_ops perf_tui_eops = { + .error = perf_tui__error, + .warning = perf_tui__warning, +}; |