From 96c38f31873999612903ff133cd39018b06d0cdf Mon Sep 17 00:00:00 2001 From: 0xGabriel <0xGabriel.kim@gmail.com> Date: Mon, 30 Sep 2024 01:51:49 +0900 Subject: [PATCH] Fix TUI display issues in non-UTF-8 locales This commit addresses the issue #1870 where the TUI displays incorrect characters in non-UTF-8 locales. The fix includes: - Checking the current locale - Using ASCII characters for graph display in non-UTF-8 locales - Adjusting character widths accordingly Fixes: #1870 Signed-off-by: Gabriel Kim <0xGabriel.kim@gmail.com> --- cmds/tui.c | 132 +++++++++++++++++++++++++---------------------------- 1 file changed, 62 insertions(+), 70 deletions(-) diff --git a/cmds/tui.c b/cmds/tui.c index 7b02444ae..cad0063cb 100644 --- a/cmds/tui.c +++ b/cmds/tui.c @@ -8,6 +8,7 @@ #include #include #include +#include #include #include @@ -133,6 +134,8 @@ static const struct tui_window_ops report_ops; static const struct tui_window_ops info_ops; static const struct tui_window_ops session_ops; +static bool is_utf8_locale; + static void tui_window_move_down(struct tui_window *win); #define FIELD_SPACE 2 @@ -182,11 +185,11 @@ static const char *graph_field_names[NUM_GRAPH_FIELD] = { "ADDRESS", }; -#define NUM_REPORT_FIELD 12 +#define NUM_REPORT_FIELD 10 static const char *report_field_names[NUM_REPORT_FIELD] = { - "TOTAL TIME", "TOTAL AVG", "TOTAL MIN", "TOTAL MAX", "SELF TIME", "SELF AVG", - "SELF MIN", "SELF MAX", "CALL", "SIZE", "TOTAL STDV", "SELF STDV", + "TOTAL TIME", "TOTAL AVG", "TOTAL MIN", "TOTAL MAX", "SELF TIME", + "SELF AVG", "SELF MIN", "SELF MAX", "CALL", "SIZE", }; static const char *field_help[] = { @@ -204,14 +207,20 @@ enum tui_mode { }; static char *report_sort_key[] = { - OPT_SORT_KEYS, "total_avg", "total_min", "total_max", "self", "self_avg", - "self_min", "self_max", "call", "size", "total_stdv", "self_stdv", + OPT_SORT_KEYS, "total_avg", "total_min", "total_max", "self", + "self_avg", "self_min", "self_max", "call", "size", }; static char *selected_report_sort_key[NUM_REPORT_FIELD]; static int curr_sort_key = 0; +static void check_utf8_locale(void) +{ + const char *lang = getenv("LANG"); + is_utf8_locale = (lang != NULL && strstr(lang, ".UTF-8") != NULL); +} + static void init_colors(void) { if (!has_colors()) @@ -350,26 +359,20 @@ static void print_report_##_func(struct field_data *fd) { \ struct uftrace_report_node *node = fd->arg; \ uint64_t d = node->_field; \ + printw(" "); \ print_time(d); \ } \ -REPORT_FIELD_STRUCT(_id, _name, _func, _header, 10) - -#define REPORT_FIELD_PERCENTAGE(_id, _name, _field, _func, _header) \ -static void print_report_##_func(struct field_data *fd) \ -{ \ - struct uftrace_report_node *node = fd->arg; \ - printw("%9.2f%%", node->_field); \ -} \ -REPORT_FIELD_STRUCT(_id, _name, _func, _header, 10) +REPORT_FIELD_STRUCT(_id, _name, _func, _header, 11) #define REPORT_FIELD_UINT(_id, _name, _field, _func, _header) \ static void print_report_##_func(struct field_data *fd) \ { \ struct uftrace_report_node *node = fd->arg; \ uint64_t d = node->_field; \ + printw(" "); \ printw("%10"PRIu64 "", d); \ } \ -REPORT_FIELD_STRUCT(_id, _name, _func, _header, 10) +REPORT_FIELD_STRUCT(_id, _name, _func, _header, 11) REPORT_FIELD_TIME(REPORT_F_TOTAL_TIME, total, total.sum, total, "TOTAL TIME"); REPORT_FIELD_TIME(REPORT_F_TOTAL_TIME_AVG, total-avg, total.avg, total_avg, "TOTAL AVG"); @@ -381,16 +384,14 @@ REPORT_FIELD_TIME(REPORT_F_SELF_TIME_MIN, self-min, self.min, self_min, "SELF MI REPORT_FIELD_TIME(REPORT_F_SELF_TIME_MAX, self-max, self.max, self_max, "SELF MAX"); REPORT_FIELD_UINT(REPORT_F_CALL, call, call, call, "CALL"); REPORT_FIELD_UINT(REPORT_F_SIZE, size, size, size, "SIZE"); -REPORT_FIELD_PERCENTAGE(REPORT_F_TOTAL_TIME_STDV, total-stdv, total.stdv, total_stdv, "TOTAL STDV"); -REPORT_FIELD_PERCENTAGE(REPORT_F_SELF_TIME_STDV, self-stdv, self.stdv, self_stdv, "SELF STDV"); /* clang-format on */ static struct display_field *report_field_table[] = { - &report_field_total, &report_field_total_avg, &report_field_total_min, - &report_field_total_max, &report_field_self, &report_field_self_avg, - &report_field_self_min, &report_field_self_max, &report_field_call, - &report_field_size, &report_field_total_stdv, &report_field_self_stdv, + &report_field_total, &report_field_total_avg, &report_field_total_min, + &report_field_total_max, &report_field_self, &report_field_self_avg, + &report_field_self_min, &report_field_self_max, &report_field_call, + &report_field_size, }; static void setup_default_graph_field(struct list_head *fields, struct uftrace_opts *opts, @@ -740,7 +741,7 @@ static struct tui_graph *tui_graph_init(struct uftrace_opts *opts) list_for_each_entry(graph, &tui_graph_list, list) { /* top (root) is an artificial node, fill the info */ top = &graph->ug.root; - top->name = (char *)uftrace_basename(graph->ug.sess->exename); + top->name = basename(graph->ug.sess->exename); top->nr_calls = 1; list_for_each_entry(node, &graph->ug.root.head, list) { @@ -1260,6 +1261,18 @@ static void print_graph_indent(struct tui_graph *graph, struct tui_graph_node *n { int i; struct tui_graph_node *parent = (void *)node->n.parent; + const char *vertical_line, *corner, *branch; + + if (is_utf8_locale) { + vertical_line = "│"; + corner = "└"; + branch = "├"; + } + else { + vertical_line = "| "; + corner = "`-"; + branch = "+-"; + } for (i = 0; i < depth; i++) { if (width < 3) { @@ -1273,12 +1286,13 @@ static void print_graph_indent(struct tui_graph *graph, struct tui_graph_node *n continue; } - if (i < depth - 1 || single_child) - printw(" │"); + if (i < depth - 1 || single_child) { + printw(" %s", vertical_line); + } else if (is_last_child(parent, node)) - printw(" └"); + printw(" %s", corner); else - printw(" ├"); + printw(" %s", branch); } } @@ -1299,7 +1313,13 @@ static void win_display_graph(struct tui_window *win, void *node) return; } - fold_sign = curr->folded ? "▶" : "─"; + //fold_sign = curr->folded ? "▶" : "─"; + if (is_utf8_locale) { + fold_sign = curr->folded ? "▶" : "─"; + } + else { + fold_sign = curr->folded ? ">" : ""; + } parent = win_parent_graph(win, node); if (parent == NULL) @@ -1327,10 +1347,16 @@ static void win_display_graph(struct tui_window *win, void *node) w = COLS - width; width += snprintf(buf, sizeof(buf), "%s(%d) ", fold_sign, curr->n.nr_calls); - /* handle UTF-8 character length */ + /* handle character length */ if (strcmp(fold_sign, " ")) { - width -= 2; - w += 2; + if (is_utf8_locale) { + width -= 2; + w += 2; + } + else { + width -= 1; + w += 1; + } } printw("%.*s", w, buf); } @@ -1553,13 +1579,13 @@ static void win_header_report(struct tui_window *win, struct uftrace_data *handl buf = p = xmalloc(w + 1); list_for_each_entry(field, &report_output_fields, list) { - char header[field->length + 2]; + char header[field->length + 1]; header[0] = '\0'; if (i == curr_sort_key) strcpy(header, "*"); strcat(header, field->header); - c = snprintf(p, w, " %*s", field->length + 1, header); + c = snprintf(p, w, " %*s", field->length, header); p += c; w -= c; i++; @@ -1612,9 +1638,8 @@ static void win_display_report(struct tui_window *win, void *node) int w = 2; list_for_each_entry(field, &report_output_fields, list) { - printw(" "); field->print(&fd); - w += field->length + 2; + w += field->length + 1; } printw(" "); @@ -1880,7 +1905,7 @@ static void win_display_session(struct tui_window *win, void *node) curr_sess = partial_graph.ug.sess; get_current_graph(node, &count); print_buf(" %c %s #%d: %s", s == curr_sess ? 'G' : ' ', "call Graph for session", - count, uftrace_basename(s->exename)); + count, basename(s->exename)); break; } @@ -3029,6 +3054,8 @@ int command_tui(int argc, char *argv[], struct uftrace_opts *opts) struct uftrace_data handle; struct uftrace_task_reader *task; + check_utf8_locale(); + ret = open_data_file(opts, &handle); if (ret < 0) { pr_warn("cannot open record data: %s: %m\n", opts->dirname); @@ -3077,41 +3104,6 @@ int command_tui(int argc, char *argv[], struct uftrace_opts *opts) return 0; } -#ifdef UNIT_TEST -TEST_CASE(tui_command) -{ - struct uftrace_opts opts = { - .dirname = "tui-cmd-test", - .exename = read_exename(), - .max_stack = 10, - .depth = OPT_DEPTH_DEFAULT, - }; - struct uftrace_data handle; - struct uftrace_task_reader *task; - - TEST_EQ(prepare_test_data(&opts, &handle), 0); - - pr_dbg("construct data structure for TUI\n"); - tui_setup(&handle, &opts); - - while (read_rstack(&handle, &task) == 0) { - struct uftrace_record *rec = task->rstack; - - TEST_NE(fstack_check_opts(task, &opts), 0); - TEST_NE(fstack_check_filter(task), 0); - TEST_EQ(build_tui_node(task, rec, &opts), 0); - - fstack_check_filter_done(task); - } - add_remaining_node(&opts, &handle); - - tui_cleanup(); - - release_test_data(&opts, &handle); - return TEST_OK; -} -#endif /* UNIT_TEST */ - #else /* !HAVE_LIBNCURSES */ #include "uftrace.h"