]> Pileus Git - ~andy/linux/blob - tools/perf/util/newt.c
perf ui: Start breaking down newt.c into multiple files
[~andy/linux] / tools / perf / util / newt.c
1 #define _GNU_SOURCE
2 #include <stdio.h>
3 #undef _GNU_SOURCE
4 /*
5  * slang versions <= 2.0.6 have a "#if HAVE_LONG_LONG" that breaks
6  * the build if it isn't defined. Use the equivalent one that glibc
7  * has on features.h.
8  */
9 #include <features.h>
10 #ifndef HAVE_LONG_LONG
11 #define HAVE_LONG_LONG __GLIBC_HAVE_LONG_LONG
12 #endif
13 #include <slang.h>
14 #include <signal.h>
15 #include <stdlib.h>
16 #include <elf.h>
17 #include <newt.h>
18 #include <sys/ttydefaults.h>
19
20 #include "cache.h"
21 #include "hist.h"
22 #include "pstack.h"
23 #include "session.h"
24 #include "sort.h"
25 #include "symbol.h"
26 #include "ui/browser.h"
27
28 #if SLANG_VERSION < 20104
29 #define slsmg_printf(msg, args...) SLsmg_printf((char *)msg, ##args)
30 #define slsmg_write_nstring(msg, len) SLsmg_write_nstring((char *)msg, len)
31 #define sltt_set_color(obj, name, fg, bg) SLtt_set_color(obj,(char *)name,\
32                                                          (char *)fg, (char *)bg)
33 #else
34 #define slsmg_printf SLsmg_printf
35 #define slsmg_write_nstring SLsmg_write_nstring
36 #define sltt_set_color SLtt_set_color
37 #endif
38
39 newtComponent newt_form__new(void);
40
41 struct ui_progress {
42         newtComponent form, scale;
43 };
44
45 struct ui_progress *ui_progress__new(const char *title, u64 total)
46 {
47         struct ui_progress *self = malloc(sizeof(*self));
48
49         if (self != NULL) {
50                 int cols;
51
52                 if (use_browser <= 0)   
53                         return self;
54                 newtGetScreenSize(&cols, NULL);
55                 cols -= 4;
56                 newtCenteredWindow(cols, 1, title);
57                 self->form  = newtForm(NULL, NULL, 0);
58                 if (self->form == NULL)
59                         goto out_free_self;
60                 self->scale = newtScale(0, 0, cols, total);
61                 if (self->scale == NULL)
62                         goto out_free_form;
63                 newtFormAddComponent(self->form, self->scale);
64                 newtRefresh();
65         }
66
67         return self;
68
69 out_free_form:
70         newtFormDestroy(self->form);
71 out_free_self:
72         free(self);
73         return NULL;
74 }
75
76 void ui_progress__update(struct ui_progress *self, u64 curr)
77 {
78         /*
79          * FIXME: We should have a per UI backend way of showing progress,
80          * stdio will just show a percentage as NN%, etc.
81          */
82         if (use_browser <= 0)
83                 return;
84         newtScaleSet(self->scale, curr);
85         newtRefresh();
86 }
87
88 void ui_progress__delete(struct ui_progress *self)
89 {
90         if (use_browser > 0) {
91                 newtFormDestroy(self->form);
92                 newtPopWindow();
93         }
94         free(self);
95 }
96
97 static void ui_helpline__pop(void)
98 {
99         newtPopHelpLine();
100 }
101
102 static void ui_helpline__push(const char *msg)
103 {
104         newtPushHelpLine(msg);
105 }
106
107 static void ui_helpline__vpush(const char *fmt, va_list ap)
108 {
109         char *s;
110
111         if (vasprintf(&s, fmt, ap) < 0)
112                 vfprintf(stderr, fmt, ap);
113         else {
114                 ui_helpline__push(s);
115                 free(s);
116         }
117 }
118
119 static void ui_helpline__fpush(const char *fmt, ...)
120 {
121         va_list ap;
122
123         va_start(ap, fmt);
124         ui_helpline__vpush(fmt, ap);
125         va_end(ap);
126 }
127
128 static void ui_helpline__puts(const char *msg)
129 {
130         ui_helpline__pop();
131         ui_helpline__push(msg);
132 }
133
134 static int ui_entry__read(const char *title, char *bf, size_t size, int width)
135 {
136         struct newtExitStruct es;
137         newtComponent form, entry;
138         const char *result;
139         int err = -1;
140
141         newtCenteredWindow(width, 1, title);
142         form = newtForm(NULL, NULL, 0);
143         if (form == NULL)
144                 return -1;
145
146         entry = newtEntry(0, 0, "0x", width, &result, NEWT_FLAG_SCROLL);
147         if (entry == NULL)
148                 goto out_free_form;
149
150         newtFormAddComponent(form, entry);
151         newtFormAddHotKey(form, NEWT_KEY_ENTER);
152         newtFormAddHotKey(form, NEWT_KEY_ESCAPE);
153         newtFormAddHotKey(form, NEWT_KEY_LEFT);
154         newtFormAddHotKey(form, CTRL('c'));
155         newtFormRun(form, &es);
156
157         if (result != NULL) {
158                 strncpy(bf, result, size);
159                 err = 0;
160         }
161 out_free_form:
162         newtPopWindow();
163         newtFormDestroy(form);
164         return 0;
165 }
166
167 static char browser__last_msg[1024];
168
169 int browser__show_help(const char *format, va_list ap)
170 {
171         int ret;
172         static int backlog;
173
174         ret = vsnprintf(browser__last_msg + backlog,
175                         sizeof(browser__last_msg) - backlog, format, ap);
176         backlog += ret;
177
178         if (browser__last_msg[backlog - 1] == '\n') {
179                 ui_helpline__puts(browser__last_msg);
180                 newtRefresh();
181                 backlog = 0;
182         }
183
184         return ret;
185 }
186
187 static void newt_form__set_exit_keys(newtComponent self)
188 {
189         newtFormAddHotKey(self, NEWT_KEY_LEFT);
190         newtFormAddHotKey(self, NEWT_KEY_ESCAPE);
191         newtFormAddHotKey(self, 'Q');
192         newtFormAddHotKey(self, 'q');
193         newtFormAddHotKey(self, CTRL('c'));
194 }
195
196 newtComponent newt_form__new(void)
197 {
198         newtComponent self = newtForm(NULL, NULL, 0);
199         if (self)
200                 newt_form__set_exit_keys(self);
201         return self;
202 }
203
204 static int popup_menu(int argc, char * const argv[])
205 {
206         struct newtExitStruct es;
207         int i, rc = -1, max_len = 5;
208         newtComponent listbox, form = newt_form__new();
209
210         if (form == NULL)
211                 return -1;
212
213         listbox = newtListbox(0, 0, argc, NEWT_FLAG_RETURNEXIT);
214         if (listbox == NULL)
215                 goto out_destroy_form;
216
217         newtFormAddComponent(form, listbox);
218
219         for (i = 0; i < argc; ++i) {
220                 int len = strlen(argv[i]);
221                 if (len > max_len)
222                         max_len = len;
223                 if (newtListboxAddEntry(listbox, argv[i], (void *)(long)i))
224                         goto out_destroy_form;
225         }
226
227         newtCenteredWindow(max_len, argc, NULL);
228         newtFormRun(form, &es);
229         rc = newtListboxGetCurrent(listbox) - NULL;
230         if (es.reason == NEWT_EXIT_HOTKEY)
231                 rc = -1;
232         newtPopWindow();
233 out_destroy_form:
234         newtFormDestroy(form);
235         return rc;
236 }
237
238 static int ui__help_window(const char *text)
239 {
240         struct newtExitStruct es;
241         newtComponent tb, form = newt_form__new();
242         int rc = -1;
243         int max_len = 0, nr_lines = 0;
244         const char *t;
245
246         if (form == NULL)
247                 return -1;
248
249         t = text;
250         while (1) {
251                 const char *sep = strchr(t, '\n');
252                 int len;
253
254                 if (sep == NULL)
255                         sep = strchr(t, '\0');
256                 len = sep - t;
257                 if (max_len < len)
258                         max_len = len;
259                 ++nr_lines;
260                 if (*sep == '\0')
261                         break;
262                 t = sep + 1;
263         }
264
265         tb = newtTextbox(0, 0, max_len, nr_lines, 0);
266         if (tb == NULL)
267                 goto out_destroy_form;
268
269         newtTextboxSetText(tb, text);
270         newtFormAddComponent(form, tb);
271         newtCenteredWindow(max_len, nr_lines, NULL);
272         newtFormRun(form, &es);
273         newtPopWindow();
274         rc = 0;
275 out_destroy_form:
276         newtFormDestroy(form);
277         return rc;
278 }
279
280 static bool dialog_yesno(const char *msg)
281 {
282         /* newtWinChoice should really be accepting const char pointers... */
283         char yes[] = "Yes", no[] = "No";
284         return newtWinChoice(NULL, yes, no, (char *)msg) == 1;
285 }
286
287 static void ui__error_window(const char *fmt, ...)
288 {
289         va_list ap;
290
291         va_start(ap, fmt);
292         newtWinMessagev((char *)"Error", (char *)"Ok", (char *)fmt, ap);
293         va_end(ap);
294 }
295
296 static void annotate_browser__write(struct ui_browser *self, void *entry, int row)
297 {
298         struct objdump_line *ol = rb_entry(entry, struct objdump_line, node);
299         bool current_entry = ui_browser__is_current_entry(self, row);
300         int width = self->width;
301
302         if (ol->offset != -1) {
303                 struct hist_entry *he = self->priv;
304                 struct symbol *sym = he->ms.sym;
305                 int len = he->ms.sym->end - he->ms.sym->start;
306                 unsigned int hits = 0;
307                 double percent = 0.0;
308                 int color;
309                 struct sym_priv *priv = symbol__priv(sym);
310                 struct sym_ext *sym_ext = priv->ext;
311                 struct sym_hist *h = priv->hist;
312                 s64 offset = ol->offset;
313                 struct objdump_line *next = objdump__get_next_ip_line(self->entries, ol);
314
315                 while (offset < (s64)len &&
316                        (next == NULL || offset < next->offset)) {
317                         if (sym_ext) {
318                                 percent += sym_ext[offset].percent;
319                         } else
320                                 hits += h->ip[offset];
321
322                         ++offset;
323                 }
324
325                 if (sym_ext == NULL && h->sum)
326                         percent = 100.0 * hits / h->sum;
327
328                 color = ui_browser__percent_color(percent, current_entry);
329                 SLsmg_set_color(color);
330                 slsmg_printf(" %7.2f ", percent);
331                 if (!current_entry)
332                         SLsmg_set_color(HE_COLORSET_CODE);
333         } else {
334                 int color = ui_browser__percent_color(0, current_entry);
335                 SLsmg_set_color(color);
336                 slsmg_write_nstring(" ", 9);
337         }
338
339         SLsmg_write_char(':');
340         slsmg_write_nstring(" ", 8);
341         if (!*ol->line)
342                 slsmg_write_nstring(" ", width - 18);
343         else
344                 slsmg_write_nstring(ol->line, width - 18);
345 }
346
347 static char *callchain_list__sym_name(struct callchain_list *self,
348                                       char *bf, size_t bfsize)
349 {
350         if (self->ms.sym)
351                 return self->ms.sym->name;
352
353         snprintf(bf, bfsize, "%#Lx", self->ip);
354         return bf;
355 }
356
357 int hist_entry__tui_annotate(struct hist_entry *self)
358 {
359         struct newtExitStruct es;
360         struct objdump_line *pos, *n;
361         LIST_HEAD(head);
362         struct ui_browser browser = {
363                 .entries = &head,
364                 .refresh = ui_browser__list_head_refresh,
365                 .seek    = ui_browser__list_head_seek,
366                 .write   = annotate_browser__write,
367                 .priv    = self,
368         };
369         int ret;
370
371         if (self->ms.sym == NULL)
372                 return -1;
373
374         if (self->ms.map->dso->annotate_warned)
375                 return -1;
376
377         if (hist_entry__annotate(self, &head) < 0) {
378                 ui__error_window(browser__last_msg);
379                 return -1;
380         }
381
382         ui_helpline__push("Press <- or ESC to exit");
383
384         list_for_each_entry(pos, &head, node) {
385                 size_t line_len = strlen(pos->line);
386                 if (browser.width < line_len)
387                         browser.width = line_len;
388                 ++browser.nr_entries;
389         }
390
391         browser.width += 18; /* Percentage */
392         ui_browser__show(&browser, self->ms.sym->name);
393         newtFormAddHotKey(browser.form, ' ');
394         ret = ui_browser__run(&browser, &es);
395         newtFormDestroy(browser.form);
396         newtPopWindow();
397         list_for_each_entry_safe(pos, n, &head, node) {
398                 list_del(&pos->node);
399                 objdump_line__free(pos);
400         }
401         ui_helpline__pop();
402         return ret;
403 }
404
405 /* -------------------------------------------------------------------- */
406
407 struct map_browser {
408         struct ui_browser b;
409         struct map        *map;
410         u16               namelen;
411         u8                addrlen;
412 };
413
414 static void map_browser__write(struct ui_browser *self, void *nd, int row)
415 {
416         struct symbol *sym = rb_entry(nd, struct symbol, rb_node);
417         struct map_browser *mb = container_of(self, struct map_browser, b);
418         bool current_entry = ui_browser__is_current_entry(self, row);
419         int color = ui_browser__percent_color(0, current_entry);
420
421         SLsmg_set_color(color);
422         slsmg_printf("%*llx %*llx %c ",
423                      mb->addrlen, sym->start, mb->addrlen, sym->end,
424                      sym->binding == STB_GLOBAL ? 'g' :
425                      sym->binding == STB_LOCAL  ? 'l' : 'w');
426         slsmg_write_nstring(sym->name, mb->namelen);
427 }
428
429 /* FIXME uber-kludgy, see comment on cmd_report... */
430 static u32 *symbol__browser_index(struct symbol *self)
431 {
432         return ((void *)self) - sizeof(struct rb_node) - sizeof(u32);
433 }
434
435 static int map_browser__search(struct map_browser *self)
436 {
437         char target[512];
438         struct symbol *sym;
439         int err = ui_entry__read("Search by name/addr", target, sizeof(target), 40);
440
441         if (err)
442                 return err;
443
444         if (target[0] == '0' && tolower(target[1]) == 'x') {
445                 u64 addr = strtoull(target, NULL, 16);
446                 sym = map__find_symbol(self->map, addr, NULL);
447         } else
448                 sym = map__find_symbol_by_name(self->map, target, NULL);
449
450         if (sym != NULL) {
451                 u32 *idx = symbol__browser_index(sym);
452                         
453                 self->b.first_visible_entry = &sym->rb_node;
454                 self->b.index = self->b.first_visible_entry_idx = *idx;
455         } else
456                 ui_helpline__fpush("%s not found!", target);
457
458         return 0;
459 }
460
461 static int map_browser__run(struct map_browser *self, struct newtExitStruct *es)
462 {
463         if (ui_browser__show(&self->b, self->map->dso->long_name) < 0)
464                 return -1;
465
466         ui_helpline__fpush("Press <- or ESC to exit, %s / to search",
467                            verbose ? "" : "restart with -v to use");
468         newtFormAddHotKey(self->b.form, NEWT_KEY_LEFT);
469         newtFormAddHotKey(self->b.form, NEWT_KEY_ENTER);
470         if (verbose)
471                 newtFormAddHotKey(self->b.form, '/');
472
473         while (1) {
474                 ui_browser__run(&self->b, es);
475
476                 if (es->reason != NEWT_EXIT_HOTKEY)
477                         break;
478                 if (verbose && es->u.key == '/')
479                         map_browser__search(self);
480                 else
481                         break;
482         }
483
484         newtFormDestroy(self->b.form);
485         newtPopWindow();
486         ui_helpline__pop();
487         return 0;
488 }
489
490 static int map__browse(struct map *self)
491 {
492         struct map_browser mb = {
493                 .b = {
494                         .entries = &self->dso->symbols[self->type],
495                         .refresh = ui_browser__rb_tree_refresh,
496                         .seek    = ui_browser__rb_tree_seek,
497                         .write   = map_browser__write,
498                 },
499                 .map = self,
500         };
501         struct newtExitStruct es;
502         struct rb_node *nd;
503         char tmp[BITS_PER_LONG / 4];
504         u64 maxaddr = 0;
505
506         for (nd = rb_first(mb.b.entries); nd; nd = rb_next(nd)) {
507                 struct symbol *pos = rb_entry(nd, struct symbol, rb_node);
508
509                 if (mb.namelen < pos->namelen)
510                         mb.namelen = pos->namelen;
511                 if (maxaddr < pos->end)
512                         maxaddr = pos->end;
513                 if (verbose) {
514                         u32 *idx = symbol__browser_index(pos);
515                         *idx = mb.b.nr_entries;
516                 }
517                 ++mb.b.nr_entries;
518         }
519
520         mb.addrlen = snprintf(tmp, sizeof(tmp), "%llx", maxaddr);
521         mb.b.width += mb.addrlen * 2 + 4 + mb.namelen;
522         return map_browser__run(&mb, &es);
523 }
524
525 /* -------------------------------------------------------------------- */
526
527 struct hist_browser {
528         struct ui_browser   b;
529         struct hists        *hists;
530         struct hist_entry   *he_selection;
531         struct map_symbol   *selection;
532 };
533
534 static void hist_browser__reset(struct hist_browser *self);
535 static int hist_browser__run(struct hist_browser *self, const char *title,
536                              struct newtExitStruct *es);
537 static unsigned int hist_browser__refresh(struct ui_browser *self);
538 static void ui_browser__hists_seek(struct ui_browser *self,
539                                    off_t offset, int whence);
540
541 static struct hist_browser *hist_browser__new(struct hists *hists)
542 {
543         struct hist_browser *self = zalloc(sizeof(*self));
544
545         if (self) {
546                 self->hists = hists;
547                 self->b.refresh = hist_browser__refresh;
548                 self->b.seek = ui_browser__hists_seek;
549         }
550
551         return self;
552 }
553
554 static void hist_browser__delete(struct hist_browser *self)
555 {
556         newtFormDestroy(self->b.form);
557         newtPopWindow();
558         free(self);
559 }
560
561 static struct hist_entry *hist_browser__selected_entry(struct hist_browser *self)
562 {
563         return self->he_selection;
564 }
565
566 static struct thread *hist_browser__selected_thread(struct hist_browser *self)
567 {
568         return self->he_selection->thread;
569 }
570
571 static int hist_browser__title(char *bf, size_t size, const char *ev_name,
572                                const struct dso *dso, const struct thread *thread)
573 {
574         int printed = 0;
575
576         if (thread)
577                 printed += snprintf(bf + printed, size - printed,
578                                     "Thread: %s(%d)",
579                                     (thread->comm_set ?  thread->comm : ""),
580                                     thread->pid);
581         if (dso)
582                 printed += snprintf(bf + printed, size - printed,
583                                     "%sDSO: %s", thread ? " " : "",
584                                     dso->short_name);
585         return printed ?: snprintf(bf, size, "Event: %s", ev_name);
586 }
587
588 int hists__browse(struct hists *self, const char *helpline, const char *ev_name)
589 {
590         struct hist_browser *browser = hist_browser__new(self);
591         struct pstack *fstack;
592         const struct thread *thread_filter = NULL;
593         const struct dso *dso_filter = NULL;
594         struct newtExitStruct es;
595         char msg[160];
596         int key = -1;
597
598         if (browser == NULL)
599                 return -1;
600
601         fstack = pstack__new(2);
602         if (fstack == NULL)
603                 goto out;
604
605         ui_helpline__push(helpline);
606
607         hist_browser__title(msg, sizeof(msg), ev_name,
608                             dso_filter, thread_filter);
609
610         while (1) {
611                 const struct thread *thread;
612                 const struct dso *dso;
613                 char *options[16];
614                 int nr_options = 0, choice = 0, i,
615                     annotate = -2, zoom_dso = -2, zoom_thread = -2,
616                     browse_map = -2;
617
618                 if (hist_browser__run(browser, msg, &es))
619                         break;
620
621                 thread = hist_browser__selected_thread(browser);
622                 dso = browser->selection->map ? browser->selection->map->dso : NULL;
623
624                 if (es.reason == NEWT_EXIT_HOTKEY) {
625                         key = es.u.key;
626
627                         switch (key) {
628                         case NEWT_KEY_F1:
629                                 goto do_help;
630                         case NEWT_KEY_TAB:
631                         case NEWT_KEY_UNTAB:
632                                 /*
633                                  * Exit the browser, let hists__browser_tree
634                                  * go to the next or previous
635                                  */
636                                 goto out_free_stack;
637                         default:;
638                         }
639
640                         key = toupper(key);
641                         switch (key) {
642                         case 'A':
643                                 if (browser->selection->map == NULL &&
644                                     browser->selection->map->dso->annotate_warned)
645                                         continue;
646                                 goto do_annotate;
647                         case 'D':
648                                 goto zoom_dso;
649                         case 'T':
650                                 goto zoom_thread;
651                         case 'H':
652                         case '?':
653 do_help:
654                                 ui__help_window("->        Zoom into DSO/Threads & Annotate current symbol\n"
655                                                 "<-        Zoom out\n"
656                                                 "a         Annotate current symbol\n"
657                                                 "h/?/F1    Show this window\n"
658                                                 "d         Zoom into current DSO\n"
659                                                 "t         Zoom into current Thread\n"
660                                                 "q/CTRL+C  Exit browser");
661                                 continue;
662                         default:;
663                         }
664                         if (is_exit_key(key)) {
665                                 if (key == NEWT_KEY_ESCAPE) {
666                                         if (dialog_yesno("Do you really want to exit?"))
667                                                 break;
668                                         else
669                                                 continue;
670                                 } else
671                                         break;
672                         }
673
674                         if (es.u.key == NEWT_KEY_LEFT) {
675                                 const void *top;
676
677                                 if (pstack__empty(fstack))
678                                         continue;
679                                 top = pstack__pop(fstack);
680                                 if (top == &dso_filter)
681                                         goto zoom_out_dso;
682                                 if (top == &thread_filter)
683                                         goto zoom_out_thread;
684                                 continue;
685                         }
686                 }
687
688                 if (browser->selection->sym != NULL &&
689                     !browser->selection->map->dso->annotate_warned &&
690                     asprintf(&options[nr_options], "Annotate %s",
691                              browser->selection->sym->name) > 0)
692                         annotate = nr_options++;
693
694                 if (thread != NULL &&
695                     asprintf(&options[nr_options], "Zoom %s %s(%d) thread",
696                              (thread_filter ? "out of" : "into"),
697                              (thread->comm_set ? thread->comm : ""),
698                              thread->pid) > 0)
699                         zoom_thread = nr_options++;
700
701                 if (dso != NULL &&
702                     asprintf(&options[nr_options], "Zoom %s %s DSO",
703                              (dso_filter ? "out of" : "into"),
704                              (dso->kernel ? "the Kernel" : dso->short_name)) > 0)
705                         zoom_dso = nr_options++;
706
707                 if (browser->selection->map != NULL &&
708                     asprintf(&options[nr_options], "Browse map details") > 0)
709                         browse_map = nr_options++;
710
711                 options[nr_options++] = (char *)"Exit";
712
713                 choice = popup_menu(nr_options, options);
714
715                 for (i = 0; i < nr_options - 1; ++i)
716                         free(options[i]);
717
718                 if (choice == nr_options - 1)
719                         break;
720
721                 if (choice == -1)
722                         continue;
723
724                 if (choice == annotate) {
725                         struct hist_entry *he;
726 do_annotate:
727                         if (browser->selection->map->dso->origin == DSO__ORIG_KERNEL) {
728                                 browser->selection->map->dso->annotate_warned = 1;
729                                 ui_helpline__puts("No vmlinux file found, can't "
730                                                  "annotate with just a "
731                                                  "kallsyms file");
732                                 continue;
733                         }
734
735                         he = hist_browser__selected_entry(browser);
736                         if (he == NULL)
737                                 continue;
738
739                         hist_entry__tui_annotate(he);
740                 } else if (choice == browse_map)
741                         map__browse(browser->selection->map);
742                 else if (choice == zoom_dso) {
743 zoom_dso:
744                         if (dso_filter) {
745                                 pstack__remove(fstack, &dso_filter);
746 zoom_out_dso:
747                                 ui_helpline__pop();
748                                 dso_filter = NULL;
749                         } else {
750                                 if (dso == NULL)
751                                         continue;
752                                 ui_helpline__fpush("To zoom out press <- or -> + \"Zoom out of %s DSO\"",
753                                                    dso->kernel ? "the Kernel" : dso->short_name);
754                                 dso_filter = dso;
755                                 pstack__push(fstack, &dso_filter);
756                         }
757                         hists__filter_by_dso(self, dso_filter);
758                         hist_browser__title(msg, sizeof(msg), ev_name,
759                                             dso_filter, thread_filter);
760                         hist_browser__reset(browser);
761                 } else if (choice == zoom_thread) {
762 zoom_thread:
763                         if (thread_filter) {
764                                 pstack__remove(fstack, &thread_filter);
765 zoom_out_thread:
766                                 ui_helpline__pop();
767                                 thread_filter = NULL;
768                         } else {
769                                 ui_helpline__fpush("To zoom out press <- or -> + \"Zoom out of %s(%d) thread\"",
770                                                    thread->comm_set ? thread->comm : "",
771                                                    thread->pid);
772                                 thread_filter = thread;
773                                 pstack__push(fstack, &thread_filter);
774                         }
775                         hists__filter_by_thread(self, thread_filter);
776                         hist_browser__title(msg, sizeof(msg), ev_name,
777                                             dso_filter, thread_filter);
778                         hist_browser__reset(browser);
779                 }
780         }
781 out_free_stack:
782         pstack__delete(fstack);
783 out:
784         hist_browser__delete(browser);
785         return key;
786 }
787
788 int hists__tui_browse_tree(struct rb_root *self, const char *help)
789 {
790         struct rb_node *first = rb_first(self), *nd = first, *next;
791         int key = 0;
792
793         while (nd) {
794                 struct hists *hists = rb_entry(nd, struct hists, rb_node);
795                 const char *ev_name = __event_name(hists->type, hists->config);
796
797                 key = hists__browse(hists, help, ev_name);
798
799                 if (is_exit_key(key))
800                         break;
801
802                 switch (key) {
803                 case NEWT_KEY_TAB:
804                         next = rb_next(nd);
805                         if (next)
806                                 nd = next;
807                         break;
808                 case NEWT_KEY_UNTAB:
809                         if (nd == first)
810                                 continue;
811                         nd = rb_prev(nd);
812                 default:
813                         break;
814                 }
815         }
816
817         return key;
818 }
819
820 static void newt_suspend(void *d __used)
821 {
822         newtSuspend();
823         raise(SIGTSTP);
824         newtResume();
825 }
826
827 void setup_browser(void)
828 {
829         if (!isatty(1) || !use_browser || dump_trace) {
830                 use_browser = 0;
831                 setup_pager();
832                 return;
833         }
834
835         use_browser = 1;
836         newtInit();
837         newtCls();
838         newtSetSuspendCallback(newt_suspend, NULL);
839         ui_helpline__puts(" ");
840         ui_browser__init();
841 }
842
843 void exit_browser(bool wait_for_ok)
844 {
845         if (use_browser > 0) {
846                 if (wait_for_ok) {
847                         char title[] = "Fatal Error", ok[] = "Ok";
848                         newtWinMessage(title, ok, browser__last_msg);
849                 }
850                 newtFinished();
851         }
852 }
853
854 static void hist_browser__refresh_dimensions(struct hist_browser *self)
855 {
856         /* 3 == +/- toggle symbol before actual hist_entry rendering */
857         self->b.width = 3 + (hists__sort_list_width(self->hists) +
858                              sizeof("[k]"));
859 }
860
861 static void hist_browser__reset(struct hist_browser *self)
862 {
863         self->b.nr_entries = self->hists->nr_entries;
864         hist_browser__refresh_dimensions(self);
865         ui_browser__reset_index(&self->b);
866 }
867
868 static char tree__folded_sign(bool unfolded)
869 {
870         return unfolded ? '-' : '+';
871 }
872
873 static char map_symbol__folded(const struct map_symbol *self)
874 {
875         return self->has_children ? tree__folded_sign(self->unfolded) : ' ';
876 }
877
878 static char hist_entry__folded(const struct hist_entry *self)
879 {
880         return map_symbol__folded(&self->ms);
881 }
882
883 static char callchain_list__folded(const struct callchain_list *self)
884 {
885         return map_symbol__folded(&self->ms);
886 }
887
888 static bool map_symbol__toggle_fold(struct map_symbol *self)
889 {
890         if (!self->has_children)
891                 return false;
892
893         self->unfolded = !self->unfolded;
894         return true;
895 }
896
897 #define LEVEL_OFFSET_STEP 3
898
899 static int hist_browser__show_callchain_node_rb_tree(struct hist_browser *self,
900                                                      struct callchain_node *chain_node,
901                                                      u64 total, int level,
902                                                      unsigned short row,
903                                                      off_t *row_offset,
904                                                      bool *is_current_entry)
905 {
906         struct rb_node *node;
907         int first_row = row, width, offset = level * LEVEL_OFFSET_STEP;
908         u64 new_total, remaining;
909
910         if (callchain_param.mode == CHAIN_GRAPH_REL)
911                 new_total = chain_node->children_hit;
912         else
913                 new_total = total;
914
915         remaining = new_total;
916         node = rb_first(&chain_node->rb_root);
917         while (node) {
918                 struct callchain_node *child = rb_entry(node, struct callchain_node, rb_node);
919                 struct rb_node *next = rb_next(node);
920                 u64 cumul = cumul_hits(child);
921                 struct callchain_list *chain;
922                 char folded_sign = ' ';
923                 int first = true;
924                 int extra_offset = 0;
925
926                 remaining -= cumul;
927
928                 list_for_each_entry(chain, &child->val, list) {
929                         char ipstr[BITS_PER_LONG / 4 + 1], *alloc_str;
930                         const char *str;
931                         int color;
932                         bool was_first = first;
933
934                         if (first) {
935                                 first = false;
936                                 chain->ms.has_children = chain->list.next != &child->val ||
937                                                          rb_first(&child->rb_root) != NULL;
938                         } else {
939                                 extra_offset = LEVEL_OFFSET_STEP;
940                                 chain->ms.has_children = chain->list.next == &child->val &&
941                                                          rb_first(&child->rb_root) != NULL;
942                         }
943
944                         folded_sign = callchain_list__folded(chain);
945                         if (*row_offset != 0) {
946                                 --*row_offset;
947                                 goto do_next;
948                         }
949
950                         alloc_str = NULL;
951                         str = callchain_list__sym_name(chain, ipstr, sizeof(ipstr));
952                         if (was_first) {
953                                 double percent = cumul * 100.0 / new_total;
954
955                                 if (asprintf(&alloc_str, "%2.2f%% %s", percent, str) < 0)
956                                         str = "Not enough memory!";
957                                 else
958                                         str = alloc_str;
959                         }
960
961                         color = HE_COLORSET_NORMAL;
962                         width = self->b.width - (offset + extra_offset + 2);
963                         if (ui_browser__is_current_entry(&self->b, row)) {
964                                 self->selection = &chain->ms;
965                                 color = HE_COLORSET_SELECTED;
966                                 *is_current_entry = true;
967                         }
968
969                         SLsmg_set_color(color);
970                         SLsmg_gotorc(self->b.top + row, self->b.left);
971                         slsmg_write_nstring(" ", offset + extra_offset);
972                         slsmg_printf("%c ", folded_sign);
973                         slsmg_write_nstring(str, width);
974                         free(alloc_str);
975
976                         if (++row == self->b.height)
977                                 goto out;
978 do_next:
979                         if (folded_sign == '+')
980                                 break;
981                 }
982
983                 if (folded_sign == '-') {
984                         const int new_level = level + (extra_offset ? 2 : 1);
985                         row += hist_browser__show_callchain_node_rb_tree(self, child, new_total,
986                                                                          new_level, row, row_offset,
987                                                                          is_current_entry);
988                 }
989                 if (row == self->b.height)
990                         goto out;
991                 node = next;
992         }
993 out:
994         return row - first_row;
995 }
996
997 static int hist_browser__show_callchain_node(struct hist_browser *self,
998                                              struct callchain_node *node,
999                                              int level, unsigned short row,
1000                                              off_t *row_offset,
1001                                              bool *is_current_entry)
1002 {
1003         struct callchain_list *chain;
1004         int first_row = row,
1005              offset = level * LEVEL_OFFSET_STEP,
1006              width = self->b.width - offset;
1007         char folded_sign = ' ';
1008
1009         list_for_each_entry(chain, &node->val, list) {
1010                 char ipstr[BITS_PER_LONG / 4 + 1], *s;
1011                 int color;
1012                 /*
1013                  * FIXME: This should be moved to somewhere else,
1014                  * probably when the callchain is created, so as not to
1015                  * traverse it all over again
1016                  */
1017                 chain->ms.has_children = rb_first(&node->rb_root) != NULL;
1018                 folded_sign = callchain_list__folded(chain);
1019
1020                 if (*row_offset != 0) {
1021                         --*row_offset;
1022                         continue;
1023                 }
1024
1025                 color = HE_COLORSET_NORMAL;
1026                 if (ui_browser__is_current_entry(&self->b, row)) {
1027                         self->selection = &chain->ms;
1028                         color = HE_COLORSET_SELECTED;
1029                         *is_current_entry = true;
1030                 }
1031
1032                 s = callchain_list__sym_name(chain, ipstr, sizeof(ipstr));
1033                 SLsmg_gotorc(self->b.top + row, self->b.left);
1034                 SLsmg_set_color(color);
1035                 slsmg_write_nstring(" ", offset);
1036                 slsmg_printf("%c ", folded_sign);
1037                 slsmg_write_nstring(s, width - 2);
1038
1039                 if (++row == self->b.height)
1040                         goto out;
1041         }
1042
1043         if (folded_sign == '-')
1044                 row += hist_browser__show_callchain_node_rb_tree(self, node,
1045                                                                  self->hists->stats.total_period,
1046                                                                  level + 1, row,
1047                                                                  row_offset,
1048                                                                  is_current_entry);
1049 out:
1050         return row - first_row;
1051 }
1052
1053 static int hist_browser__show_callchain(struct hist_browser *self,
1054                                         struct rb_root *chain,
1055                                         int level, unsigned short row,
1056                                         off_t *row_offset,
1057                                         bool *is_current_entry)
1058 {
1059         struct rb_node *nd;
1060         int first_row = row;
1061
1062         for (nd = rb_first(chain); nd; nd = rb_next(nd)) {
1063                 struct callchain_node *node = rb_entry(nd, struct callchain_node, rb_node);
1064
1065                 row += hist_browser__show_callchain_node(self, node, level,
1066                                                          row, row_offset,
1067                                                          is_current_entry);
1068                 if (row == self->b.height)
1069                         break;
1070         }
1071
1072         return row - first_row;
1073 }
1074
1075 static int hist_browser__show_entry(struct hist_browser *self,
1076                                     struct hist_entry *entry,
1077                                     unsigned short row)
1078 {
1079         char s[256];
1080         double percent;
1081         int printed = 0;
1082         int color, width = self->b.width;
1083         char folded_sign = ' ';
1084         bool current_entry = ui_browser__is_current_entry(&self->b, row);
1085         off_t row_offset = entry->row_offset;
1086
1087         if (current_entry) {
1088                 self->he_selection = entry;
1089                 self->selection = &entry->ms;
1090         }
1091
1092         if (symbol_conf.use_callchain) {
1093                 entry->ms.has_children = !RB_EMPTY_ROOT(&entry->sorted_chain);
1094                 folded_sign = hist_entry__folded(entry);
1095         }
1096
1097         if (row_offset == 0) {
1098                 hist_entry__snprintf(entry, s, sizeof(s), self->hists, NULL, false,
1099                                      0, false, self->hists->stats.total_period);
1100                 percent = (entry->period * 100.0) / self->hists->stats.total_period;
1101
1102                 color = HE_COLORSET_SELECTED;
1103                 if (!current_entry) {
1104                         if (percent >= MIN_RED)
1105                                 color = HE_COLORSET_TOP;
1106                         else if (percent >= MIN_GREEN)
1107                                 color = HE_COLORSET_MEDIUM;
1108                         else
1109                                 color = HE_COLORSET_NORMAL;
1110                 }
1111
1112                 SLsmg_set_color(color);
1113                 SLsmg_gotorc(self->b.top + row, self->b.left);
1114                 if (symbol_conf.use_callchain) {
1115                         slsmg_printf("%c ", folded_sign);
1116                         width -= 2;
1117                 }
1118                 slsmg_write_nstring(s, width);
1119                 ++row;
1120                 ++printed;
1121         } else
1122                 --row_offset;
1123
1124         if (folded_sign == '-' && row != self->b.height) {
1125                 printed += hist_browser__show_callchain(self, &entry->sorted_chain,
1126                                                         1, row, &row_offset,
1127                                                         &current_entry);
1128                 if (current_entry)
1129                         self->he_selection = entry;
1130         }
1131
1132         return printed;
1133 }
1134
1135 static unsigned int hist_browser__refresh(struct ui_browser *self)
1136 {
1137         unsigned row = 0;
1138         struct rb_node *nd;
1139         struct hist_browser *hb = container_of(self, struct hist_browser, b);
1140
1141         if (self->first_visible_entry == NULL)
1142                 self->first_visible_entry = rb_first(&hb->hists->entries);
1143
1144         for (nd = self->first_visible_entry; nd; nd = rb_next(nd)) {
1145                 struct hist_entry *h = rb_entry(nd, struct hist_entry, rb_node);
1146
1147                 if (h->filtered)
1148                         continue;
1149
1150                 row += hist_browser__show_entry(hb, h, row);
1151                 if (row == self->height)
1152                         break;
1153         }
1154
1155         return row;
1156 }
1157
1158 static void callchain_node__init_have_children_rb_tree(struct callchain_node *self)
1159 {
1160         struct rb_node *nd = rb_first(&self->rb_root);
1161
1162         for (nd = rb_first(&self->rb_root); nd; nd = rb_next(nd)) {
1163                 struct callchain_node *child = rb_entry(nd, struct callchain_node, rb_node);
1164                 struct callchain_list *chain;
1165                 int first = true;
1166
1167                 list_for_each_entry(chain, &child->val, list) {
1168                         if (first) {
1169                                 first = false;
1170                                 chain->ms.has_children = chain->list.next != &child->val ||
1171                                                          rb_first(&child->rb_root) != NULL;
1172                         } else
1173                                 chain->ms.has_children = chain->list.next == &child->val &&
1174                                                          rb_first(&child->rb_root) != NULL;
1175                 }
1176
1177                 callchain_node__init_have_children_rb_tree(child);
1178         }
1179 }
1180
1181 static void callchain_node__init_have_children(struct callchain_node *self)
1182 {
1183         struct callchain_list *chain;
1184
1185         list_for_each_entry(chain, &self->val, list)
1186                 chain->ms.has_children = rb_first(&self->rb_root) != NULL;
1187
1188         callchain_node__init_have_children_rb_tree(self);
1189 }
1190
1191 static void callchain__init_have_children(struct rb_root *self)
1192 {
1193         struct rb_node *nd;
1194
1195         for (nd = rb_first(self); nd; nd = rb_next(nd)) {
1196                 struct callchain_node *node = rb_entry(nd, struct callchain_node, rb_node);
1197                 callchain_node__init_have_children(node);
1198         }
1199 }
1200
1201 static void hist_entry__init_have_children(struct hist_entry *self)
1202 {
1203         if (!self->init_have_children) {
1204                 callchain__init_have_children(&self->sorted_chain);
1205                 self->init_have_children = true;
1206         }
1207 }
1208
1209 static struct rb_node *hists__filter_entries(struct rb_node *nd)
1210 {
1211         while (nd != NULL) {
1212                 struct hist_entry *h = rb_entry(nd, struct hist_entry, rb_node);
1213                 if (!h->filtered)
1214                         return nd;
1215
1216                 nd = rb_next(nd);
1217         }
1218
1219         return NULL;
1220 }
1221
1222 static struct rb_node *hists__filter_prev_entries(struct rb_node *nd)
1223 {
1224         while (nd != NULL) {
1225                 struct hist_entry *h = rb_entry(nd, struct hist_entry, rb_node);
1226                 if (!h->filtered)
1227                         return nd;
1228
1229                 nd = rb_prev(nd);
1230         }
1231
1232         return NULL;
1233 }
1234
1235 static void ui_browser__hists_seek(struct ui_browser *self,
1236                                    off_t offset, int whence)
1237 {
1238         struct hist_entry *h;
1239         struct rb_node *nd;
1240         bool first = true;
1241
1242         switch (whence) {
1243         case SEEK_SET:
1244                 nd = hists__filter_entries(rb_first(self->entries));
1245                 break;
1246         case SEEK_CUR:
1247                 nd = self->first_visible_entry;
1248                 goto do_offset;
1249         case SEEK_END:
1250                 nd = hists__filter_prev_entries(rb_last(self->entries));
1251                 first = false;
1252                 break;
1253         default:
1254                 return;
1255         }
1256
1257         /*
1258          * Moves not relative to the first visible entry invalidates its
1259          * row_offset:
1260          */
1261         h = rb_entry(self->first_visible_entry, struct hist_entry, rb_node);
1262         h->row_offset = 0;
1263
1264         /*
1265          * Here we have to check if nd is expanded (+), if it is we can't go
1266          * the next top level hist_entry, instead we must compute an offset of
1267          * what _not_ to show and not change the first visible entry.
1268          *
1269          * This offset increments when we are going from top to bottom and
1270          * decreases when we're going from bottom to top.
1271          *
1272          * As we don't have backpointers to the top level in the callchains
1273          * structure, we need to always print the whole hist_entry callchain,
1274          * skipping the first ones that are before the first visible entry
1275          * and stop when we printed enough lines to fill the screen.
1276          */
1277 do_offset:
1278         if (offset > 0) {
1279                 do {
1280                         h = rb_entry(nd, struct hist_entry, rb_node);
1281                         if (h->ms.unfolded) {
1282                                 u16 remaining = h->nr_rows - h->row_offset;
1283                                 if (offset > remaining) {
1284                                         offset -= remaining;
1285                                         h->row_offset = 0;
1286                                 } else {
1287                                         h->row_offset += offset;
1288                                         offset = 0;
1289                                         self->first_visible_entry = nd;
1290                                         break;
1291                                 }
1292                         }
1293                         nd = hists__filter_entries(rb_next(nd));
1294                         if (nd == NULL)
1295                                 break;
1296                         --offset;
1297                         self->first_visible_entry = nd;
1298                 } while (offset != 0);
1299         } else if (offset < 0) {
1300                 while (1) {
1301                         h = rb_entry(nd, struct hist_entry, rb_node);
1302                         if (h->ms.unfolded) {
1303                                 if (first) {
1304                                         if (-offset > h->row_offset) {
1305                                                 offset += h->row_offset;
1306                                                 h->row_offset = 0;
1307                                         } else {
1308                                                 h->row_offset += offset;
1309                                                 offset = 0;
1310                                                 self->first_visible_entry = nd;
1311                                                 break;
1312                                         }
1313                                 } else {
1314                                         if (-offset > h->nr_rows) {
1315                                                 offset += h->nr_rows;
1316                                                 h->row_offset = 0;
1317                                         } else {
1318                                                 h->row_offset = h->nr_rows + offset;
1319                                                 offset = 0;
1320                                                 self->first_visible_entry = nd;
1321                                                 break;
1322                                         }
1323                                 }
1324                         }
1325
1326                         nd = hists__filter_prev_entries(rb_prev(nd));
1327                         if (nd == NULL)
1328                                 break;
1329                         ++offset;
1330                         self->first_visible_entry = nd;
1331                         if (offset == 0) {
1332                                 /*
1333                                  * Last unfiltered hist_entry, check if it is
1334                                  * unfolded, if it is then we should have
1335                                  * row_offset at its last entry.
1336                                  */
1337                                 h = rb_entry(nd, struct hist_entry, rb_node);
1338                                 if (h->ms.unfolded)
1339                                         h->row_offset = h->nr_rows;
1340                                 break;
1341                         }
1342                         first = false;
1343                 }
1344         } else {
1345                 self->first_visible_entry = nd;
1346                 h = rb_entry(nd, struct hist_entry, rb_node);
1347                 h->row_offset = 0;
1348         }
1349 }
1350
1351 static int callchain_node__count_rows_rb_tree(struct callchain_node *self)
1352 {
1353         int n = 0;
1354         struct rb_node *nd;
1355
1356         for (nd = rb_first(&self->rb_root); nd; nd = rb_next(nd)) {
1357                 struct callchain_node *child = rb_entry(nd, struct callchain_node, rb_node);
1358                 struct callchain_list *chain;
1359                 char folded_sign = ' '; /* No children */
1360
1361                 list_for_each_entry(chain, &child->val, list) {
1362                         ++n;
1363                         /* We need this because we may not have children */
1364                         folded_sign = callchain_list__folded(chain);
1365                         if (folded_sign == '+')
1366                                 break;
1367                 }
1368
1369                 if (folded_sign == '-') /* Have children and they're unfolded */
1370                         n += callchain_node__count_rows_rb_tree(child);
1371         }
1372
1373         return n;
1374 }
1375
1376 static int callchain_node__count_rows(struct callchain_node *node)
1377 {
1378         struct callchain_list *chain;
1379         bool unfolded = false;
1380         int n = 0;
1381
1382         list_for_each_entry(chain, &node->val, list) {
1383                 ++n;
1384                 unfolded = chain->ms.unfolded;
1385         }
1386
1387         if (unfolded)
1388                 n += callchain_node__count_rows_rb_tree(node);
1389
1390         return n;
1391 }
1392
1393 static int callchain__count_rows(struct rb_root *chain)
1394 {
1395         struct rb_node *nd;
1396         int n = 0;
1397
1398         for (nd = rb_first(chain); nd; nd = rb_next(nd)) {
1399                 struct callchain_node *node = rb_entry(nd, struct callchain_node, rb_node);
1400                 n += callchain_node__count_rows(node);
1401         }
1402
1403         return n;
1404 }
1405
1406 static bool hist_browser__toggle_fold(struct hist_browser *self)
1407 {
1408         if (map_symbol__toggle_fold(self->selection)) {
1409                 struct hist_entry *he = self->he_selection;
1410
1411                 hist_entry__init_have_children(he);
1412                 self->hists->nr_entries -= he->nr_rows;
1413
1414                 if (he->ms.unfolded)
1415                         he->nr_rows = callchain__count_rows(&he->sorted_chain);
1416                 else
1417                         he->nr_rows = 0;
1418                 self->hists->nr_entries += he->nr_rows;
1419                 self->b.nr_entries = self->hists->nr_entries;
1420
1421                 return true;
1422         }
1423
1424         /* If it doesn't have children, no toggling performed */
1425         return false;
1426 }
1427
1428 static int hist_browser__run(struct hist_browser *self, const char *title,
1429                              struct newtExitStruct *es)
1430 {
1431         char str[256], unit;
1432         unsigned long nr_events = self->hists->stats.nr_events[PERF_RECORD_SAMPLE];
1433
1434         self->b.entries = &self->hists->entries;
1435         self->b.nr_entries = self->hists->nr_entries;
1436
1437         hist_browser__refresh_dimensions(self);
1438
1439         nr_events = convert_unit(nr_events, &unit);
1440         snprintf(str, sizeof(str), "Events: %lu%c                            ",
1441                  nr_events, unit);
1442         newtDrawRootText(0, 0, str);
1443
1444         if (ui_browser__show(&self->b, title) < 0)
1445                 return -1;
1446
1447         newtFormAddHotKey(self->b.form, 'A');
1448         newtFormAddHotKey(self->b.form, 'a');
1449         newtFormAddHotKey(self->b.form, '?');
1450         newtFormAddHotKey(self->b.form, 'h');
1451         newtFormAddHotKey(self->b.form, 'H');
1452         newtFormAddHotKey(self->b.form, 'd');
1453
1454         newtFormAddHotKey(self->b.form, NEWT_KEY_LEFT);
1455         newtFormAddHotKey(self->b.form, NEWT_KEY_RIGHT);
1456         newtFormAddHotKey(self->b.form, NEWT_KEY_ENTER);
1457
1458         while (1) {
1459                 ui_browser__run(&self->b, es);
1460
1461                 if (es->reason != NEWT_EXIT_HOTKEY)
1462                         break;
1463                 switch (es->u.key) {
1464                 case 'd': { /* Debug */
1465                         static int seq;
1466                         struct hist_entry *h = rb_entry(self->b.first_visible_entry,
1467                                                         struct hist_entry, rb_node);
1468                         ui_helpline__pop();
1469                         ui_helpline__fpush("%d: nr_ent=(%d,%d), height=%d, idx=%d, fve: idx=%d, row_off=%d, nrows=%d",
1470                                            seq++, self->b.nr_entries,
1471                                            self->hists->nr_entries,
1472                                            self->b.height,
1473                                            self->b.index,
1474                                            self->b.first_visible_entry_idx,
1475                                            h->row_offset, h->nr_rows);
1476                 }
1477                         continue;
1478                 case NEWT_KEY_ENTER:
1479                         if (hist_browser__toggle_fold(self))
1480                                 break;
1481                         /* fall thru */
1482                 default:
1483                         return 0;
1484                 }
1485         }
1486         return 0;
1487 }