4 #include "ui/libslang.h"
9 #include <sys/ttydefaults.h>
17 #include "ui/browser.h"
18 #include "ui/helpline.h"
19 #include "ui/browsers/map.h"
21 newtComponent newt_form__new(void);
23 char browser__last_msg[1024];
25 int browser__show_help(const char *format, va_list ap)
30 ret = vsnprintf(browser__last_msg + backlog,
31 sizeof(browser__last_msg) - backlog, format, ap);
34 if (browser__last_msg[backlog - 1] == '\n') {
35 ui_helpline__puts(browser__last_msg);
43 static void newt_form__set_exit_keys(newtComponent self)
45 newtFormAddHotKey(self, NEWT_KEY_LEFT);
46 newtFormAddHotKey(self, NEWT_KEY_ESCAPE);
47 newtFormAddHotKey(self, 'Q');
48 newtFormAddHotKey(self, 'q');
49 newtFormAddHotKey(self, CTRL('c'));
52 newtComponent newt_form__new(void)
54 newtComponent self = newtForm(NULL, NULL, 0);
56 newt_form__set_exit_keys(self);
60 static int popup_menu(int argc, char * const argv[])
62 struct newtExitStruct es;
63 int i, rc = -1, max_len = 5;
64 newtComponent listbox, form = newt_form__new();
69 listbox = newtListbox(0, 0, argc, NEWT_FLAG_RETURNEXIT);
71 goto out_destroy_form;
73 newtFormAddComponent(form, listbox);
75 for (i = 0; i < argc; ++i) {
76 int len = strlen(argv[i]);
79 if (newtListboxAddEntry(listbox, argv[i], (void *)(long)i))
80 goto out_destroy_form;
83 newtCenteredWindow(max_len, argc, NULL);
84 newtFormRun(form, &es);
85 rc = newtListboxGetCurrent(listbox) - NULL;
86 if (es.reason == NEWT_EXIT_HOTKEY)
90 newtFormDestroy(form);
94 static int ui__help_window(const char *text)
96 struct newtExitStruct es;
97 newtComponent tb, form = newt_form__new();
99 int max_len = 0, nr_lines = 0;
107 const char *sep = strchr(t, '\n');
111 sep = strchr(t, '\0');
121 tb = newtTextbox(0, 0, max_len, nr_lines, 0);
123 goto out_destroy_form;
125 newtTextboxSetText(tb, text);
126 newtFormAddComponent(form, tb);
127 newtCenteredWindow(max_len, nr_lines, NULL);
128 newtFormRun(form, &es);
132 newtFormDestroy(form);
136 static bool dialog_yesno(const char *msg)
138 /* newtWinChoice should really be accepting const char pointers... */
139 char yes[] = "Yes", no[] = "No";
140 return newtWinChoice(NULL, yes, no, (char *)msg) == 1;
143 static char *callchain_list__sym_name(struct callchain_list *self,
144 char *bf, size_t bfsize)
147 return self->ms.sym->name;
149 snprintf(bf, bfsize, "%#Lx", self->ip);
153 struct hist_browser {
156 struct hist_entry *he_selection;
157 struct map_symbol *selection;
160 static void hist_browser__reset(struct hist_browser *self);
161 static int hist_browser__run(struct hist_browser *self, const char *title,
162 struct newtExitStruct *es);
163 static unsigned int hist_browser__refresh(struct ui_browser *self);
164 static void ui_browser__hists_seek(struct ui_browser *self,
165 off_t offset, int whence);
167 static struct hist_browser *hist_browser__new(struct hists *hists)
169 struct hist_browser *self = zalloc(sizeof(*self));
173 self->b.refresh = hist_browser__refresh;
174 self->b.seek = ui_browser__hists_seek;
180 static void hist_browser__delete(struct hist_browser *self)
182 newtFormDestroy(self->b.form);
187 static struct hist_entry *hist_browser__selected_entry(struct hist_browser *self)
189 return self->he_selection;
192 static struct thread *hist_browser__selected_thread(struct hist_browser *self)
194 return self->he_selection->thread;
197 static int hist_browser__title(char *bf, size_t size, const char *ev_name,
198 const struct dso *dso, const struct thread *thread)
203 printed += snprintf(bf + printed, size - printed,
205 (thread->comm_set ? thread->comm : ""),
208 printed += snprintf(bf + printed, size - printed,
209 "%sDSO: %s", thread ? " " : "",
211 return printed ?: snprintf(bf, size, "Event: %s", ev_name);
214 int hists__browse(struct hists *self, const char *helpline, const char *ev_name)
216 struct hist_browser *browser = hist_browser__new(self);
217 struct pstack *fstack;
218 const struct thread *thread_filter = NULL;
219 const struct dso *dso_filter = NULL;
220 struct newtExitStruct es;
227 fstack = pstack__new(2);
231 ui_helpline__push(helpline);
233 hist_browser__title(msg, sizeof(msg), ev_name,
234 dso_filter, thread_filter);
237 const struct thread *thread;
238 const struct dso *dso;
240 int nr_options = 0, choice = 0, i,
241 annotate = -2, zoom_dso = -2, zoom_thread = -2,
244 if (hist_browser__run(browser, msg, &es))
247 thread = hist_browser__selected_thread(browser);
248 dso = browser->selection->map ? browser->selection->map->dso : NULL;
250 if (es.reason == NEWT_EXIT_HOTKEY) {
259 * Exit the browser, let hists__browser_tree
260 * go to the next or previous
269 if (browser->selection->map == NULL &&
270 browser->selection->map->dso->annotate_warned)
280 ui__help_window("-> Zoom into DSO/Threads & Annotate current symbol\n"
282 "a Annotate current symbol\n"
283 "h/?/F1 Show this window\n"
284 "d Zoom into current DSO\n"
285 "t Zoom into current Thread\n"
286 "q/CTRL+C Exit browser");
290 if (is_exit_key(key)) {
291 if (key == NEWT_KEY_ESCAPE) {
292 if (dialog_yesno("Do you really want to exit?"))
300 if (es.u.key == NEWT_KEY_LEFT) {
303 if (pstack__empty(fstack))
305 top = pstack__pop(fstack);
306 if (top == &dso_filter)
308 if (top == &thread_filter)
309 goto zoom_out_thread;
314 if (browser->selection->sym != NULL &&
315 !browser->selection->map->dso->annotate_warned &&
316 asprintf(&options[nr_options], "Annotate %s",
317 browser->selection->sym->name) > 0)
318 annotate = nr_options++;
320 if (thread != NULL &&
321 asprintf(&options[nr_options], "Zoom %s %s(%d) thread",
322 (thread_filter ? "out of" : "into"),
323 (thread->comm_set ? thread->comm : ""),
325 zoom_thread = nr_options++;
328 asprintf(&options[nr_options], "Zoom %s %s DSO",
329 (dso_filter ? "out of" : "into"),
330 (dso->kernel ? "the Kernel" : dso->short_name)) > 0)
331 zoom_dso = nr_options++;
333 if (browser->selection->map != NULL &&
334 asprintf(&options[nr_options], "Browse map details") > 0)
335 browse_map = nr_options++;
337 options[nr_options++] = (char *)"Exit";
339 choice = popup_menu(nr_options, options);
341 for (i = 0; i < nr_options - 1; ++i)
344 if (choice == nr_options - 1)
350 if (choice == annotate) {
351 struct hist_entry *he;
353 if (browser->selection->map->dso->origin == DSO__ORIG_KERNEL) {
354 browser->selection->map->dso->annotate_warned = 1;
355 ui_helpline__puts("No vmlinux file found, can't "
356 "annotate with just a "
361 he = hist_browser__selected_entry(browser);
365 hist_entry__tui_annotate(he);
366 } else if (choice == browse_map)
367 map__browse(browser->selection->map);
368 else if (choice == zoom_dso) {
371 pstack__remove(fstack, &dso_filter);
378 ui_helpline__fpush("To zoom out press <- or -> + \"Zoom out of %s DSO\"",
379 dso->kernel ? "the Kernel" : dso->short_name);
381 pstack__push(fstack, &dso_filter);
383 hists__filter_by_dso(self, dso_filter);
384 hist_browser__title(msg, sizeof(msg), ev_name,
385 dso_filter, thread_filter);
386 hist_browser__reset(browser);
387 } else if (choice == zoom_thread) {
390 pstack__remove(fstack, &thread_filter);
393 thread_filter = NULL;
395 ui_helpline__fpush("To zoom out press <- or -> + \"Zoom out of %s(%d) thread\"",
396 thread->comm_set ? thread->comm : "",
398 thread_filter = thread;
399 pstack__push(fstack, &thread_filter);
401 hists__filter_by_thread(self, thread_filter);
402 hist_browser__title(msg, sizeof(msg), ev_name,
403 dso_filter, thread_filter);
404 hist_browser__reset(browser);
408 pstack__delete(fstack);
410 hist_browser__delete(browser);
414 int hists__tui_browse_tree(struct rb_root *self, const char *help)
416 struct rb_node *first = rb_first(self), *nd = first, *next;
420 struct hists *hists = rb_entry(nd, struct hists, rb_node);
421 const char *ev_name = __event_name(hists->type, hists->config);
423 key = hists__browse(hists, help, ev_name);
425 if (is_exit_key(key))
446 static void newt_suspend(void *d __used)
453 void setup_browser(void)
455 if (!isatty(1) || !use_browser || dump_trace) {
464 newtSetSuspendCallback(newt_suspend, NULL);
465 ui_helpline__puts(" ");
469 void exit_browser(bool wait_for_ok)
471 if (use_browser > 0) {
473 char title[] = "Fatal Error", ok[] = "Ok";
474 newtWinMessage(title, ok, browser__last_msg);
480 static void hist_browser__refresh_dimensions(struct hist_browser *self)
482 /* 3 == +/- toggle symbol before actual hist_entry rendering */
483 self->b.width = 3 + (hists__sort_list_width(self->hists) +
487 static void hist_browser__reset(struct hist_browser *self)
489 self->b.nr_entries = self->hists->nr_entries;
490 hist_browser__refresh_dimensions(self);
491 ui_browser__reset_index(&self->b);
494 static char tree__folded_sign(bool unfolded)
496 return unfolded ? '-' : '+';
499 static char map_symbol__folded(const struct map_symbol *self)
501 return self->has_children ? tree__folded_sign(self->unfolded) : ' ';
504 static char hist_entry__folded(const struct hist_entry *self)
506 return map_symbol__folded(&self->ms);
509 static char callchain_list__folded(const struct callchain_list *self)
511 return map_symbol__folded(&self->ms);
514 static bool map_symbol__toggle_fold(struct map_symbol *self)
516 if (!self->has_children)
519 self->unfolded = !self->unfolded;
523 #define LEVEL_OFFSET_STEP 3
525 static int hist_browser__show_callchain_node_rb_tree(struct hist_browser *self,
526 struct callchain_node *chain_node,
527 u64 total, int level,
530 bool *is_current_entry)
532 struct rb_node *node;
533 int first_row = row, width, offset = level * LEVEL_OFFSET_STEP;
534 u64 new_total, remaining;
536 if (callchain_param.mode == CHAIN_GRAPH_REL)
537 new_total = chain_node->children_hit;
541 remaining = new_total;
542 node = rb_first(&chain_node->rb_root);
544 struct callchain_node *child = rb_entry(node, struct callchain_node, rb_node);
545 struct rb_node *next = rb_next(node);
546 u64 cumul = cumul_hits(child);
547 struct callchain_list *chain;
548 char folded_sign = ' ';
550 int extra_offset = 0;
554 list_for_each_entry(chain, &child->val, list) {
555 char ipstr[BITS_PER_LONG / 4 + 1], *alloc_str;
558 bool was_first = first;
562 chain->ms.has_children = chain->list.next != &child->val ||
563 rb_first(&child->rb_root) != NULL;
565 extra_offset = LEVEL_OFFSET_STEP;
566 chain->ms.has_children = chain->list.next == &child->val &&
567 rb_first(&child->rb_root) != NULL;
570 folded_sign = callchain_list__folded(chain);
571 if (*row_offset != 0) {
577 str = callchain_list__sym_name(chain, ipstr, sizeof(ipstr));
579 double percent = cumul * 100.0 / new_total;
581 if (asprintf(&alloc_str, "%2.2f%% %s", percent, str) < 0)
582 str = "Not enough memory!";
587 color = HE_COLORSET_NORMAL;
588 width = self->b.width - (offset + extra_offset + 2);
589 if (ui_browser__is_current_entry(&self->b, row)) {
590 self->selection = &chain->ms;
591 color = HE_COLORSET_SELECTED;
592 *is_current_entry = true;
595 SLsmg_set_color(color);
596 SLsmg_gotorc(self->b.y + row, self->b.x);
597 slsmg_write_nstring(" ", offset + extra_offset);
598 slsmg_printf("%c ", folded_sign);
599 slsmg_write_nstring(str, width);
602 if (++row == self->b.height)
605 if (folded_sign == '+')
609 if (folded_sign == '-') {
610 const int new_level = level + (extra_offset ? 2 : 1);
611 row += hist_browser__show_callchain_node_rb_tree(self, child, new_total,
612 new_level, row, row_offset,
615 if (row == self->b.height)
620 return row - first_row;
623 static int hist_browser__show_callchain_node(struct hist_browser *self,
624 struct callchain_node *node,
625 int level, unsigned short row,
627 bool *is_current_entry)
629 struct callchain_list *chain;
631 offset = level * LEVEL_OFFSET_STEP,
632 width = self->b.width - offset;
633 char folded_sign = ' ';
635 list_for_each_entry(chain, &node->val, list) {
636 char ipstr[BITS_PER_LONG / 4 + 1], *s;
639 * FIXME: This should be moved to somewhere else,
640 * probably when the callchain is created, so as not to
641 * traverse it all over again
643 chain->ms.has_children = rb_first(&node->rb_root) != NULL;
644 folded_sign = callchain_list__folded(chain);
646 if (*row_offset != 0) {
651 color = HE_COLORSET_NORMAL;
652 if (ui_browser__is_current_entry(&self->b, row)) {
653 self->selection = &chain->ms;
654 color = HE_COLORSET_SELECTED;
655 *is_current_entry = true;
658 s = callchain_list__sym_name(chain, ipstr, sizeof(ipstr));
659 SLsmg_gotorc(self->b.y + row, self->b.x);
660 SLsmg_set_color(color);
661 slsmg_write_nstring(" ", offset);
662 slsmg_printf("%c ", folded_sign);
663 slsmg_write_nstring(s, width - 2);
665 if (++row == self->b.height)
669 if (folded_sign == '-')
670 row += hist_browser__show_callchain_node_rb_tree(self, node,
671 self->hists->stats.total_period,
676 return row - first_row;
679 static int hist_browser__show_callchain(struct hist_browser *self,
680 struct rb_root *chain,
681 int level, unsigned short row,
683 bool *is_current_entry)
688 for (nd = rb_first(chain); nd; nd = rb_next(nd)) {
689 struct callchain_node *node = rb_entry(nd, struct callchain_node, rb_node);
691 row += hist_browser__show_callchain_node(self, node, level,
694 if (row == self->b.height)
698 return row - first_row;
701 static int hist_browser__show_entry(struct hist_browser *self,
702 struct hist_entry *entry,
708 int color, width = self->b.width;
709 char folded_sign = ' ';
710 bool current_entry = ui_browser__is_current_entry(&self->b, row);
711 off_t row_offset = entry->row_offset;
714 self->he_selection = entry;
715 self->selection = &entry->ms;
718 if (symbol_conf.use_callchain) {
719 entry->ms.has_children = !RB_EMPTY_ROOT(&entry->sorted_chain);
720 folded_sign = hist_entry__folded(entry);
723 if (row_offset == 0) {
724 hist_entry__snprintf(entry, s, sizeof(s), self->hists, NULL, false,
725 0, false, self->hists->stats.total_period);
726 percent = (entry->period * 100.0) / self->hists->stats.total_period;
728 color = HE_COLORSET_SELECTED;
729 if (!current_entry) {
730 if (percent >= MIN_RED)
731 color = HE_COLORSET_TOP;
732 else if (percent >= MIN_GREEN)
733 color = HE_COLORSET_MEDIUM;
735 color = HE_COLORSET_NORMAL;
738 SLsmg_set_color(color);
739 SLsmg_gotorc(self->b.y + row, self->b.x);
740 if (symbol_conf.use_callchain) {
741 slsmg_printf("%c ", folded_sign);
744 slsmg_write_nstring(s, width);
750 if (folded_sign == '-' && row != self->b.height) {
751 printed += hist_browser__show_callchain(self, &entry->sorted_chain,
755 self->he_selection = entry;
761 static unsigned int hist_browser__refresh(struct ui_browser *self)
765 struct hist_browser *hb = container_of(self, struct hist_browser, b);
767 if (self->top == NULL)
768 self->top = rb_first(&hb->hists->entries);
770 for (nd = self->top; nd; nd = rb_next(nd)) {
771 struct hist_entry *h = rb_entry(nd, struct hist_entry, rb_node);
776 row += hist_browser__show_entry(hb, h, row);
777 if (row == self->height)
784 static void callchain_node__init_have_children_rb_tree(struct callchain_node *self)
786 struct rb_node *nd = rb_first(&self->rb_root);
788 for (nd = rb_first(&self->rb_root); nd; nd = rb_next(nd)) {
789 struct callchain_node *child = rb_entry(nd, struct callchain_node, rb_node);
790 struct callchain_list *chain;
793 list_for_each_entry(chain, &child->val, list) {
796 chain->ms.has_children = chain->list.next != &child->val ||
797 rb_first(&child->rb_root) != NULL;
799 chain->ms.has_children = chain->list.next == &child->val &&
800 rb_first(&child->rb_root) != NULL;
803 callchain_node__init_have_children_rb_tree(child);
807 static void callchain_node__init_have_children(struct callchain_node *self)
809 struct callchain_list *chain;
811 list_for_each_entry(chain, &self->val, list)
812 chain->ms.has_children = rb_first(&self->rb_root) != NULL;
814 callchain_node__init_have_children_rb_tree(self);
817 static void callchain__init_have_children(struct rb_root *self)
821 for (nd = rb_first(self); nd; nd = rb_next(nd)) {
822 struct callchain_node *node = rb_entry(nd, struct callchain_node, rb_node);
823 callchain_node__init_have_children(node);
827 static void hist_entry__init_have_children(struct hist_entry *self)
829 if (!self->init_have_children) {
830 callchain__init_have_children(&self->sorted_chain);
831 self->init_have_children = true;
835 static struct rb_node *hists__filter_entries(struct rb_node *nd)
838 struct hist_entry *h = rb_entry(nd, struct hist_entry, rb_node);
848 static struct rb_node *hists__filter_prev_entries(struct rb_node *nd)
851 struct hist_entry *h = rb_entry(nd, struct hist_entry, rb_node);
861 static void ui_browser__hists_seek(struct ui_browser *self,
862 off_t offset, int whence)
864 struct hist_entry *h;
870 nd = hists__filter_entries(rb_first(self->entries));
876 nd = hists__filter_prev_entries(rb_last(self->entries));
884 * Moves not relative to the first visible entry invalidates its
887 h = rb_entry(self->top, struct hist_entry, rb_node);
891 * Here we have to check if nd is expanded (+), if it is we can't go
892 * the next top level hist_entry, instead we must compute an offset of
893 * what _not_ to show and not change the first visible entry.
895 * This offset increments when we are going from top to bottom and
896 * decreases when we're going from bottom to top.
898 * As we don't have backpointers to the top level in the callchains
899 * structure, we need to always print the whole hist_entry callchain,
900 * skipping the first ones that are before the first visible entry
901 * and stop when we printed enough lines to fill the screen.
906 h = rb_entry(nd, struct hist_entry, rb_node);
907 if (h->ms.unfolded) {
908 u16 remaining = h->nr_rows - h->row_offset;
909 if (offset > remaining) {
913 h->row_offset += offset;
919 nd = hists__filter_entries(rb_next(nd));
924 } while (offset != 0);
925 } else if (offset < 0) {
927 h = rb_entry(nd, struct hist_entry, rb_node);
928 if (h->ms.unfolded) {
930 if (-offset > h->row_offset) {
931 offset += h->row_offset;
934 h->row_offset += offset;
940 if (-offset > h->nr_rows) {
941 offset += h->nr_rows;
944 h->row_offset = h->nr_rows + offset;
952 nd = hists__filter_prev_entries(rb_prev(nd));
959 * Last unfiltered hist_entry, check if it is
960 * unfolded, if it is then we should have
961 * row_offset at its last entry.
963 h = rb_entry(nd, struct hist_entry, rb_node);
965 h->row_offset = h->nr_rows;
972 h = rb_entry(nd, struct hist_entry, rb_node);
977 static int callchain_node__count_rows_rb_tree(struct callchain_node *self)
982 for (nd = rb_first(&self->rb_root); nd; nd = rb_next(nd)) {
983 struct callchain_node *child = rb_entry(nd, struct callchain_node, rb_node);
984 struct callchain_list *chain;
985 char folded_sign = ' '; /* No children */
987 list_for_each_entry(chain, &child->val, list) {
989 /* We need this because we may not have children */
990 folded_sign = callchain_list__folded(chain);
991 if (folded_sign == '+')
995 if (folded_sign == '-') /* Have children and they're unfolded */
996 n += callchain_node__count_rows_rb_tree(child);
1002 static int callchain_node__count_rows(struct callchain_node *node)
1004 struct callchain_list *chain;
1005 bool unfolded = false;
1008 list_for_each_entry(chain, &node->val, list) {
1010 unfolded = chain->ms.unfolded;
1014 n += callchain_node__count_rows_rb_tree(node);
1019 static int callchain__count_rows(struct rb_root *chain)
1024 for (nd = rb_first(chain); nd; nd = rb_next(nd)) {
1025 struct callchain_node *node = rb_entry(nd, struct callchain_node, rb_node);
1026 n += callchain_node__count_rows(node);
1032 static bool hist_browser__toggle_fold(struct hist_browser *self)
1034 if (map_symbol__toggle_fold(self->selection)) {
1035 struct hist_entry *he = self->he_selection;
1037 hist_entry__init_have_children(he);
1038 self->hists->nr_entries -= he->nr_rows;
1040 if (he->ms.unfolded)
1041 he->nr_rows = callchain__count_rows(&he->sorted_chain);
1044 self->hists->nr_entries += he->nr_rows;
1045 self->b.nr_entries = self->hists->nr_entries;
1050 /* If it doesn't have children, no toggling performed */
1054 static int hist_browser__run(struct hist_browser *self, const char *title,
1055 struct newtExitStruct *es)
1057 char str[256], unit;
1058 unsigned long nr_events = self->hists->stats.nr_events[PERF_RECORD_SAMPLE];
1060 self->b.entries = &self->hists->entries;
1061 self->b.nr_entries = self->hists->nr_entries;
1063 hist_browser__refresh_dimensions(self);
1065 nr_events = convert_unit(nr_events, &unit);
1066 snprintf(str, sizeof(str), "Events: %lu%c ",
1068 newtDrawRootText(0, 0, str);
1070 if (ui_browser__show(&self->b, title) < 0)
1073 newtFormAddHotKey(self->b.form, 'A');
1074 newtFormAddHotKey(self->b.form, 'a');
1075 newtFormAddHotKey(self->b.form, '?');
1076 newtFormAddHotKey(self->b.form, 'h');
1077 newtFormAddHotKey(self->b.form, 'H');
1078 newtFormAddHotKey(self->b.form, 'd');
1080 newtFormAddHotKey(self->b.form, NEWT_KEY_LEFT);
1081 newtFormAddHotKey(self->b.form, NEWT_KEY_RIGHT);
1082 newtFormAddHotKey(self->b.form, NEWT_KEY_ENTER);
1085 ui_browser__run(&self->b, es);
1087 if (es->reason != NEWT_EXIT_HOTKEY)
1089 switch (es->u.key) {
1090 case 'd': { /* Debug */
1092 struct hist_entry *h = rb_entry(self->b.top,
1093 struct hist_entry, rb_node);
1095 ui_helpline__fpush("%d: nr_ent=(%d,%d), height=%d, idx=%d, fve: idx=%d, row_off=%d, nrows=%d",
1096 seq++, self->b.nr_entries,
1097 self->hists->nr_entries,
1101 h->row_offset, h->nr_rows);
1104 case NEWT_KEY_ENTER:
1105 if (hist_browser__toggle_fold(self))