Skip to content

Commit

Permalink
Fix TUI display issues in non-UTF-8 locales
Browse files Browse the repository at this point in the history
This commit addresses the issue namhyung#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: namhyung#1870

Signed-off-by: Gabriel Kim <[email protected]>
  • Loading branch information
GabrielKimm committed Sep 29, 2024
1 parent 4afc58f commit 96c38f3
Showing 1 changed file with 62 additions and 70 deletions.
132 changes: 62 additions & 70 deletions cmds/tui.c
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/wait.h>
#include <unistd.h>

Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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[] = {
Expand All @@ -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())
Expand Down Expand Up @@ -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");
Expand All @@ -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,
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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) {
Expand All @@ -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);
}
}

Expand All @@ -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)
Expand Down Expand Up @@ -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);
}
Expand Down Expand Up @@ -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++;
Expand Down Expand Up @@ -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(" ");
Expand Down Expand Up @@ -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;
}

Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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"
Expand Down

0 comments on commit 96c38f3

Please sign in to comment.