]> bbs.cooldavid.org Git - net-next-2.6.git/blob - tools/perf/util/newt.c
perf ui: Move map browser to util/ui/browsers/
[net-next-2.6.git] / tools / perf / util / newt.c
1 #define _GNU_SOURCE
2 #include <stdio.h>
3 #undef _GNU_SOURCE
4 #include "ui/libslang.h"
5 #include <signal.h>
6 #include <stdlib.h>
7 #include <elf.h>
8 #include <newt.h>
9 #include <sys/ttydefaults.h>
10
11 #include "cache.h"
12 #include "hist.h"
13 #include "pstack.h"
14 #include "session.h"
15 #include "sort.h"
16 #include "symbol.h"
17 #include "ui/browser.h"
18 #include "ui/helpline.h"
19 #include "ui/browsers/map.h"
20
21 newtComponent newt_form__new(void);
22
23 char browser__last_msg[1024];
24
25 int browser__show_help(const char *format, va_list ap)
26 {
27         int ret;
28         static int backlog;
29
30         ret = vsnprintf(browser__last_msg + backlog,
31                         sizeof(browser__last_msg) - backlog, format, ap);
32         backlog += ret;
33
34         if (browser__last_msg[backlog - 1] == '\n') {
35                 ui_helpline__puts(browser__last_msg);
36                 newtRefresh();
37                 backlog = 0;
38         }
39
40         return ret;
41 }
42
43 static void newt_form__set_exit_keys(newtComponent self)
44 {
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'));
50 }
51
52 newtComponent newt_form__new(void)
53 {
54         newtComponent self = newtForm(NULL, NULL, 0);
55         if (self)
56                 newt_form__set_exit_keys(self);
57         return self;
58 }
59
60 static int popup_menu(int argc, char * const argv[])
61 {
62         struct newtExitStruct es;
63         int i, rc = -1, max_len = 5;
64         newtComponent listbox, form = newt_form__new();
65
66         if (form == NULL)
67                 return -1;
68
69         listbox = newtListbox(0, 0, argc, NEWT_FLAG_RETURNEXIT);
70         if (listbox == NULL)
71                 goto out_destroy_form;
72
73         newtFormAddComponent(form, listbox);
74
75         for (i = 0; i < argc; ++i) {
76                 int len = strlen(argv[i]);
77                 if (len > max_len)
78                         max_len = len;
79                 if (newtListboxAddEntry(listbox, argv[i], (void *)(long)i))
80                         goto out_destroy_form;
81         }
82
83         newtCenteredWindow(max_len, argc, NULL);
84         newtFormRun(form, &es);
85         rc = newtListboxGetCurrent(listbox) - NULL;
86         if (es.reason == NEWT_EXIT_HOTKEY)
87                 rc = -1;
88         newtPopWindow();
89 out_destroy_form:
90         newtFormDestroy(form);
91         return rc;
92 }
93
94 static int ui__help_window(const char *text)
95 {
96         struct newtExitStruct es;
97         newtComponent tb, form = newt_form__new();
98         int rc = -1;
99         int max_len = 0, nr_lines = 0;
100         const char *t;
101
102         if (form == NULL)
103                 return -1;
104
105         t = text;
106         while (1) {
107                 const char *sep = strchr(t, '\n');
108                 int len;
109
110                 if (sep == NULL)
111                         sep = strchr(t, '\0');
112                 len = sep - t;
113                 if (max_len < len)
114                         max_len = len;
115                 ++nr_lines;
116                 if (*sep == '\0')
117                         break;
118                 t = sep + 1;
119         }
120
121         tb = newtTextbox(0, 0, max_len, nr_lines, 0);
122         if (tb == NULL)
123                 goto out_destroy_form;
124
125         newtTextboxSetText(tb, text);
126         newtFormAddComponent(form, tb);
127         newtCenteredWindow(max_len, nr_lines, NULL);
128         newtFormRun(form, &es);
129         newtPopWindow();
130         rc = 0;
131 out_destroy_form:
132         newtFormDestroy(form);
133         return rc;
134 }
135
136 static bool dialog_yesno(const char *msg)
137 {
138         /* newtWinChoice should really be accepting const char pointers... */
139         char yes[] = "Yes", no[] = "No";
140         return newtWinChoice(NULL, yes, no, (char *)msg) == 1;
141 }
142
143 static char *callchain_list__sym_name(struct callchain_list *self,
144                                       char *bf, size_t bfsize)
145 {
146         if (self->ms.sym)
147                 return self->ms.sym->name;
148
149         snprintf(bf, bfsize, "%#Lx", self->ip);
150         return bf;
151 }
152
153 struct hist_browser {
154         struct ui_browser   b;
155         struct hists        *hists;
156         struct hist_entry   *he_selection;
157         struct map_symbol   *selection;
158 };
159
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);
166
167 static struct hist_browser *hist_browser__new(struct hists *hists)
168 {
169         struct hist_browser *self = zalloc(sizeof(*self));
170
171         if (self) {
172                 self->hists = hists;
173                 self->b.refresh = hist_browser__refresh;
174                 self->b.seek = ui_browser__hists_seek;
175         }
176
177         return self;
178 }
179
180 static void hist_browser__delete(struct hist_browser *self)
181 {
182         newtFormDestroy(self->b.form);
183         newtPopWindow();
184         free(self);
185 }
186
187 static struct hist_entry *hist_browser__selected_entry(struct hist_browser *self)
188 {
189         return self->he_selection;
190 }
191
192 static struct thread *hist_browser__selected_thread(struct hist_browser *self)
193 {
194         return self->he_selection->thread;
195 }
196
197 static int hist_browser__title(char *bf, size_t size, const char *ev_name,
198                                const struct dso *dso, const struct thread *thread)
199 {
200         int printed = 0;
201
202         if (thread)
203                 printed += snprintf(bf + printed, size - printed,
204                                     "Thread: %s(%d)",
205                                     (thread->comm_set ?  thread->comm : ""),
206                                     thread->pid);
207         if (dso)
208                 printed += snprintf(bf + printed, size - printed,
209                                     "%sDSO: %s", thread ? " " : "",
210                                     dso->short_name);
211         return printed ?: snprintf(bf, size, "Event: %s", ev_name);
212 }
213
214 int hists__browse(struct hists *self, const char *helpline, const char *ev_name)
215 {
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;
221         char msg[160];
222         int key = -1;
223
224         if (browser == NULL)
225                 return -1;
226
227         fstack = pstack__new(2);
228         if (fstack == NULL)
229                 goto out;
230
231         ui_helpline__push(helpline);
232
233         hist_browser__title(msg, sizeof(msg), ev_name,
234                             dso_filter, thread_filter);
235
236         while (1) {
237                 const struct thread *thread;
238                 const struct dso *dso;
239                 char *options[16];
240                 int nr_options = 0, choice = 0, i,
241                     annotate = -2, zoom_dso = -2, zoom_thread = -2,
242                     browse_map = -2;
243
244                 if (hist_browser__run(browser, msg, &es))
245                         break;
246
247                 thread = hist_browser__selected_thread(browser);
248                 dso = browser->selection->map ? browser->selection->map->dso : NULL;
249
250                 if (es.reason == NEWT_EXIT_HOTKEY) {
251                         key = es.u.key;
252
253                         switch (key) {
254                         case NEWT_KEY_F1:
255                                 goto do_help;
256                         case NEWT_KEY_TAB:
257                         case NEWT_KEY_UNTAB:
258                                 /*
259                                  * Exit the browser, let hists__browser_tree
260                                  * go to the next or previous
261                                  */
262                                 goto out_free_stack;
263                         default:;
264                         }
265
266                         key = toupper(key);
267                         switch (key) {
268                         case 'A':
269                                 if (browser->selection->map == NULL &&
270                                     browser->selection->map->dso->annotate_warned)
271                                         continue;
272                                 goto do_annotate;
273                         case 'D':
274                                 goto zoom_dso;
275                         case 'T':
276                                 goto zoom_thread;
277                         case 'H':
278                         case '?':
279 do_help:
280                                 ui__help_window("->        Zoom into DSO/Threads & Annotate current symbol\n"
281                                                 "<-        Zoom out\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");
287                                 continue;
288                         default:;
289                         }
290                         if (is_exit_key(key)) {
291                                 if (key == NEWT_KEY_ESCAPE) {
292                                         if (dialog_yesno("Do you really want to exit?"))
293                                                 break;
294                                         else
295                                                 continue;
296                                 } else
297                                         break;
298                         }
299
300                         if (es.u.key == NEWT_KEY_LEFT) {
301                                 const void *top;
302
303                                 if (pstack__empty(fstack))
304                                         continue;
305                                 top = pstack__pop(fstack);
306                                 if (top == &dso_filter)
307                                         goto zoom_out_dso;
308                                 if (top == &thread_filter)
309                                         goto zoom_out_thread;
310                                 continue;
311                         }
312                 }
313
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++;
319
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 : ""),
324                              thread->pid) > 0)
325                         zoom_thread = nr_options++;
326
327                 if (dso != NULL &&
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++;
332
333                 if (browser->selection->map != NULL &&
334                     asprintf(&options[nr_options], "Browse map details") > 0)
335                         browse_map = nr_options++;
336
337                 options[nr_options++] = (char *)"Exit";
338
339                 choice = popup_menu(nr_options, options);
340
341                 for (i = 0; i < nr_options - 1; ++i)
342                         free(options[i]);
343
344                 if (choice == nr_options - 1)
345                         break;
346
347                 if (choice == -1)
348                         continue;
349
350                 if (choice == annotate) {
351                         struct hist_entry *he;
352 do_annotate:
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 "
357                                                  "kallsyms file");
358                                 continue;
359                         }
360
361                         he = hist_browser__selected_entry(browser);
362                         if (he == NULL)
363                                 continue;
364
365                         hist_entry__tui_annotate(he);
366                 } else if (choice == browse_map)
367                         map__browse(browser->selection->map);
368                 else if (choice == zoom_dso) {
369 zoom_dso:
370                         if (dso_filter) {
371                                 pstack__remove(fstack, &dso_filter);
372 zoom_out_dso:
373                                 ui_helpline__pop();
374                                 dso_filter = NULL;
375                         } else {
376                                 if (dso == NULL)
377                                         continue;
378                                 ui_helpline__fpush("To zoom out press <- or -> + \"Zoom out of %s DSO\"",
379                                                    dso->kernel ? "the Kernel" : dso->short_name);
380                                 dso_filter = dso;
381                                 pstack__push(fstack, &dso_filter);
382                         }
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) {
388 zoom_thread:
389                         if (thread_filter) {
390                                 pstack__remove(fstack, &thread_filter);
391 zoom_out_thread:
392                                 ui_helpline__pop();
393                                 thread_filter = NULL;
394                         } else {
395                                 ui_helpline__fpush("To zoom out press <- or -> + \"Zoom out of %s(%d) thread\"",
396                                                    thread->comm_set ? thread->comm : "",
397                                                    thread->pid);
398                                 thread_filter = thread;
399                                 pstack__push(fstack, &thread_filter);
400                         }
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);
405                 }
406         }
407 out_free_stack:
408         pstack__delete(fstack);
409 out:
410         hist_browser__delete(browser);
411         return key;
412 }
413
414 int hists__tui_browse_tree(struct rb_root *self, const char *help)
415 {
416         struct rb_node *first = rb_first(self), *nd = first, *next;
417         int key = 0;
418
419         while (nd) {
420                 struct hists *hists = rb_entry(nd, struct hists, rb_node);
421                 const char *ev_name = __event_name(hists->type, hists->config);
422
423                 key = hists__browse(hists, help, ev_name);
424
425                 if (is_exit_key(key))
426                         break;
427
428                 switch (key) {
429                 case NEWT_KEY_TAB:
430                         next = rb_next(nd);
431                         if (next)
432                                 nd = next;
433                         break;
434                 case NEWT_KEY_UNTAB:
435                         if (nd == first)
436                                 continue;
437                         nd = rb_prev(nd);
438                 default:
439                         break;
440                 }
441         }
442
443         return key;
444 }
445
446 static void newt_suspend(void *d __used)
447 {
448         newtSuspend();
449         raise(SIGTSTP);
450         newtResume();
451 }
452
453 void setup_browser(void)
454 {
455         if (!isatty(1) || !use_browser || dump_trace) {
456                 use_browser = 0;
457                 setup_pager();
458                 return;
459         }
460
461         use_browser = 1;
462         newtInit();
463         newtCls();
464         newtSetSuspendCallback(newt_suspend, NULL);
465         ui_helpline__puts(" ");
466         ui_browser__init();
467 }
468
469 void exit_browser(bool wait_for_ok)
470 {
471         if (use_browser > 0) {
472                 if (wait_for_ok) {
473                         char title[] = "Fatal Error", ok[] = "Ok";
474                         newtWinMessage(title, ok, browser__last_msg);
475                 }
476                 newtFinished();
477         }
478 }
479
480 static void hist_browser__refresh_dimensions(struct hist_browser *self)
481 {
482         /* 3 == +/- toggle symbol before actual hist_entry rendering */
483         self->b.width = 3 + (hists__sort_list_width(self->hists) +
484                              sizeof("[k]"));
485 }
486
487 static void hist_browser__reset(struct hist_browser *self)
488 {
489         self->b.nr_entries = self->hists->nr_entries;
490         hist_browser__refresh_dimensions(self);
491         ui_browser__reset_index(&self->b);
492 }
493
494 static char tree__folded_sign(bool unfolded)
495 {
496         return unfolded ? '-' : '+';
497 }
498
499 static char map_symbol__folded(const struct map_symbol *self)
500 {
501         return self->has_children ? tree__folded_sign(self->unfolded) : ' ';
502 }
503
504 static char hist_entry__folded(const struct hist_entry *self)
505 {
506         return map_symbol__folded(&self->ms);
507 }
508
509 static char callchain_list__folded(const struct callchain_list *self)
510 {
511         return map_symbol__folded(&self->ms);
512 }
513
514 static bool map_symbol__toggle_fold(struct map_symbol *self)
515 {
516         if (!self->has_children)
517                 return false;
518
519         self->unfolded = !self->unfolded;
520         return true;
521 }
522
523 #define LEVEL_OFFSET_STEP 3
524
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,
528                                                      unsigned short row,
529                                                      off_t *row_offset,
530                                                      bool *is_current_entry)
531 {
532         struct rb_node *node;
533         int first_row = row, width, offset = level * LEVEL_OFFSET_STEP;
534         u64 new_total, remaining;
535
536         if (callchain_param.mode == CHAIN_GRAPH_REL)
537                 new_total = chain_node->children_hit;
538         else
539                 new_total = total;
540
541         remaining = new_total;
542         node = rb_first(&chain_node->rb_root);
543         while (node) {
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 = ' ';
549                 int first = true;
550                 int extra_offset = 0;
551
552                 remaining -= cumul;
553
554                 list_for_each_entry(chain, &child->val, list) {
555                         char ipstr[BITS_PER_LONG / 4 + 1], *alloc_str;
556                         const char *str;
557                         int color;
558                         bool was_first = first;
559
560                         if (first) {
561                                 first = false;
562                                 chain->ms.has_children = chain->list.next != &child->val ||
563                                                          rb_first(&child->rb_root) != NULL;
564                         } else {
565                                 extra_offset = LEVEL_OFFSET_STEP;
566                                 chain->ms.has_children = chain->list.next == &child->val &&
567                                                          rb_first(&child->rb_root) != NULL;
568                         }
569
570                         folded_sign = callchain_list__folded(chain);
571                         if (*row_offset != 0) {
572                                 --*row_offset;
573                                 goto do_next;
574                         }
575
576                         alloc_str = NULL;
577                         str = callchain_list__sym_name(chain, ipstr, sizeof(ipstr));
578                         if (was_first) {
579                                 double percent = cumul * 100.0 / new_total;
580
581                                 if (asprintf(&alloc_str, "%2.2f%% %s", percent, str) < 0)
582                                         str = "Not enough memory!";
583                                 else
584                                         str = alloc_str;
585                         }
586
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;
593                         }
594
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);
600                         free(alloc_str);
601
602                         if (++row == self->b.height)
603                                 goto out;
604 do_next:
605                         if (folded_sign == '+')
606                                 break;
607                 }
608
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,
613                                                                          is_current_entry);
614                 }
615                 if (row == self->b.height)
616                         goto out;
617                 node = next;
618         }
619 out:
620         return row - first_row;
621 }
622
623 static int hist_browser__show_callchain_node(struct hist_browser *self,
624                                              struct callchain_node *node,
625                                              int level, unsigned short row,
626                                              off_t *row_offset,
627                                              bool *is_current_entry)
628 {
629         struct callchain_list *chain;
630         int first_row = row,
631              offset = level * LEVEL_OFFSET_STEP,
632              width = self->b.width - offset;
633         char folded_sign = ' ';
634
635         list_for_each_entry(chain, &node->val, list) {
636                 char ipstr[BITS_PER_LONG / 4 + 1], *s;
637                 int color;
638                 /*
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
642                  */
643                 chain->ms.has_children = rb_first(&node->rb_root) != NULL;
644                 folded_sign = callchain_list__folded(chain);
645
646                 if (*row_offset != 0) {
647                         --*row_offset;
648                         continue;
649                 }
650
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;
656                 }
657
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);
664
665                 if (++row == self->b.height)
666                         goto out;
667         }
668
669         if (folded_sign == '-')
670                 row += hist_browser__show_callchain_node_rb_tree(self, node,
671                                                                  self->hists->stats.total_period,
672                                                                  level + 1, row,
673                                                                  row_offset,
674                                                                  is_current_entry);
675 out:
676         return row - first_row;
677 }
678
679 static int hist_browser__show_callchain(struct hist_browser *self,
680                                         struct rb_root *chain,
681                                         int level, unsigned short row,
682                                         off_t *row_offset,
683                                         bool *is_current_entry)
684 {
685         struct rb_node *nd;
686         int first_row = row;
687
688         for (nd = rb_first(chain); nd; nd = rb_next(nd)) {
689                 struct callchain_node *node = rb_entry(nd, struct callchain_node, rb_node);
690
691                 row += hist_browser__show_callchain_node(self, node, level,
692                                                          row, row_offset,
693                                                          is_current_entry);
694                 if (row == self->b.height)
695                         break;
696         }
697
698         return row - first_row;
699 }
700
701 static int hist_browser__show_entry(struct hist_browser *self,
702                                     struct hist_entry *entry,
703                                     unsigned short row)
704 {
705         char s[256];
706         double percent;
707         int printed = 0;
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;
712
713         if (current_entry) {
714                 self->he_selection = entry;
715                 self->selection = &entry->ms;
716         }
717
718         if (symbol_conf.use_callchain) {
719                 entry->ms.has_children = !RB_EMPTY_ROOT(&entry->sorted_chain);
720                 folded_sign = hist_entry__folded(entry);
721         }
722
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;
727
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;
734                         else
735                                 color = HE_COLORSET_NORMAL;
736                 }
737
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);
742                         width -= 2;
743                 }
744                 slsmg_write_nstring(s, width);
745                 ++row;
746                 ++printed;
747         } else
748                 --row_offset;
749
750         if (folded_sign == '-' && row != self->b.height) {
751                 printed += hist_browser__show_callchain(self, &entry->sorted_chain,
752                                                         1, row, &row_offset,
753                                                         &current_entry);
754                 if (current_entry)
755                         self->he_selection = entry;
756         }
757
758         return printed;
759 }
760
761 static unsigned int hist_browser__refresh(struct ui_browser *self)
762 {
763         unsigned row = 0;
764         struct rb_node *nd;
765         struct hist_browser *hb = container_of(self, struct hist_browser, b);
766
767         if (self->top == NULL)
768                 self->top = rb_first(&hb->hists->entries);
769
770         for (nd = self->top; nd; nd = rb_next(nd)) {
771                 struct hist_entry *h = rb_entry(nd, struct hist_entry, rb_node);
772
773                 if (h->filtered)
774                         continue;
775
776                 row += hist_browser__show_entry(hb, h, row);
777                 if (row == self->height)
778                         break;
779         }
780
781         return row;
782 }
783
784 static void callchain_node__init_have_children_rb_tree(struct callchain_node *self)
785 {
786         struct rb_node *nd = rb_first(&self->rb_root);
787
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;
791                 int first = true;
792
793                 list_for_each_entry(chain, &child->val, list) {
794                         if (first) {
795                                 first = false;
796                                 chain->ms.has_children = chain->list.next != &child->val ||
797                                                          rb_first(&child->rb_root) != NULL;
798                         } else
799                                 chain->ms.has_children = chain->list.next == &child->val &&
800                                                          rb_first(&child->rb_root) != NULL;
801                 }
802
803                 callchain_node__init_have_children_rb_tree(child);
804         }
805 }
806
807 static void callchain_node__init_have_children(struct callchain_node *self)
808 {
809         struct callchain_list *chain;
810
811         list_for_each_entry(chain, &self->val, list)
812                 chain->ms.has_children = rb_first(&self->rb_root) != NULL;
813
814         callchain_node__init_have_children_rb_tree(self);
815 }
816
817 static void callchain__init_have_children(struct rb_root *self)
818 {
819         struct rb_node *nd;
820
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);
824         }
825 }
826
827 static void hist_entry__init_have_children(struct hist_entry *self)
828 {
829         if (!self->init_have_children) {
830                 callchain__init_have_children(&self->sorted_chain);
831                 self->init_have_children = true;
832         }
833 }
834
835 static struct rb_node *hists__filter_entries(struct rb_node *nd)
836 {
837         while (nd != NULL) {
838                 struct hist_entry *h = rb_entry(nd, struct hist_entry, rb_node);
839                 if (!h->filtered)
840                         return nd;
841
842                 nd = rb_next(nd);
843         }
844
845         return NULL;
846 }
847
848 static struct rb_node *hists__filter_prev_entries(struct rb_node *nd)
849 {
850         while (nd != NULL) {
851                 struct hist_entry *h = rb_entry(nd, struct hist_entry, rb_node);
852                 if (!h->filtered)
853                         return nd;
854
855                 nd = rb_prev(nd);
856         }
857
858         return NULL;
859 }
860
861 static void ui_browser__hists_seek(struct ui_browser *self,
862                                    off_t offset, int whence)
863 {
864         struct hist_entry *h;
865         struct rb_node *nd;
866         bool first = true;
867
868         switch (whence) {
869         case SEEK_SET:
870                 nd = hists__filter_entries(rb_first(self->entries));
871                 break;
872         case SEEK_CUR:
873                 nd = self->top;
874                 goto do_offset;
875         case SEEK_END:
876                 nd = hists__filter_prev_entries(rb_last(self->entries));
877                 first = false;
878                 break;
879         default:
880                 return;
881         }
882
883         /*
884          * Moves not relative to the first visible entry invalidates its
885          * row_offset:
886          */
887         h = rb_entry(self->top, struct hist_entry, rb_node);
888         h->row_offset = 0;
889
890         /*
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.
894          *
895          * This offset increments when we are going from top to bottom and
896          * decreases when we're going from bottom to top.
897          *
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.
902          */
903 do_offset:
904         if (offset > 0) {
905                 do {
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) {
910                                         offset -= remaining;
911                                         h->row_offset = 0;
912                                 } else {
913                                         h->row_offset += offset;
914                                         offset = 0;
915                                         self->top = nd;
916                                         break;
917                                 }
918                         }
919                         nd = hists__filter_entries(rb_next(nd));
920                         if (nd == NULL)
921                                 break;
922                         --offset;
923                         self->top = nd;
924                 } while (offset != 0);
925         } else if (offset < 0) {
926                 while (1) {
927                         h = rb_entry(nd, struct hist_entry, rb_node);
928                         if (h->ms.unfolded) {
929                                 if (first) {
930                                         if (-offset > h->row_offset) {
931                                                 offset += h->row_offset;
932                                                 h->row_offset = 0;
933                                         } else {
934                                                 h->row_offset += offset;
935                                                 offset = 0;
936                                                 self->top = nd;
937                                                 break;
938                                         }
939                                 } else {
940                                         if (-offset > h->nr_rows) {
941                                                 offset += h->nr_rows;
942                                                 h->row_offset = 0;
943                                         } else {
944                                                 h->row_offset = h->nr_rows + offset;
945                                                 offset = 0;
946                                                 self->top = nd;
947                                                 break;
948                                         }
949                                 }
950                         }
951
952                         nd = hists__filter_prev_entries(rb_prev(nd));
953                         if (nd == NULL)
954                                 break;
955                         ++offset;
956                         self->top = nd;
957                         if (offset == 0) {
958                                 /*
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.
962                                  */
963                                 h = rb_entry(nd, struct hist_entry, rb_node);
964                                 if (h->ms.unfolded)
965                                         h->row_offset = h->nr_rows;
966                                 break;
967                         }
968                         first = false;
969                 }
970         } else {
971                 self->top = nd;
972                 h = rb_entry(nd, struct hist_entry, rb_node);
973                 h->row_offset = 0;
974         }
975 }
976
977 static int callchain_node__count_rows_rb_tree(struct callchain_node *self)
978 {
979         int n = 0;
980         struct rb_node *nd;
981
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 */
986
987                 list_for_each_entry(chain, &child->val, list) {
988                         ++n;
989                         /* We need this because we may not have children */
990                         folded_sign = callchain_list__folded(chain);
991                         if (folded_sign == '+')
992                                 break;
993                 }
994
995                 if (folded_sign == '-') /* Have children and they're unfolded */
996                         n += callchain_node__count_rows_rb_tree(child);
997         }
998
999         return n;
1000 }
1001
1002 static int callchain_node__count_rows(struct callchain_node *node)
1003 {
1004         struct callchain_list *chain;
1005         bool unfolded = false;
1006         int n = 0;
1007
1008         list_for_each_entry(chain, &node->val, list) {
1009                 ++n;
1010                 unfolded = chain->ms.unfolded;
1011         }
1012
1013         if (unfolded)
1014                 n += callchain_node__count_rows_rb_tree(node);
1015
1016         return n;
1017 }
1018
1019 static int callchain__count_rows(struct rb_root *chain)
1020 {
1021         struct rb_node *nd;
1022         int n = 0;
1023
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);
1027         }
1028
1029         return n;
1030 }
1031
1032 static bool hist_browser__toggle_fold(struct hist_browser *self)
1033 {
1034         if (map_symbol__toggle_fold(self->selection)) {
1035                 struct hist_entry *he = self->he_selection;
1036
1037                 hist_entry__init_have_children(he);
1038                 self->hists->nr_entries -= he->nr_rows;
1039
1040                 if (he->ms.unfolded)
1041                         he->nr_rows = callchain__count_rows(&he->sorted_chain);
1042                 else
1043                         he->nr_rows = 0;
1044                 self->hists->nr_entries += he->nr_rows;
1045                 self->b.nr_entries = self->hists->nr_entries;
1046
1047                 return true;
1048         }
1049
1050         /* If it doesn't have children, no toggling performed */
1051         return false;
1052 }
1053
1054 static int hist_browser__run(struct hist_browser *self, const char *title,
1055                              struct newtExitStruct *es)
1056 {
1057         char str[256], unit;
1058         unsigned long nr_events = self->hists->stats.nr_events[PERF_RECORD_SAMPLE];
1059
1060         self->b.entries = &self->hists->entries;
1061         self->b.nr_entries = self->hists->nr_entries;
1062
1063         hist_browser__refresh_dimensions(self);
1064
1065         nr_events = convert_unit(nr_events, &unit);
1066         snprintf(str, sizeof(str), "Events: %lu%c                            ",
1067                  nr_events, unit);
1068         newtDrawRootText(0, 0, str);
1069
1070         if (ui_browser__show(&self->b, title) < 0)
1071                 return -1;
1072
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');
1079
1080         newtFormAddHotKey(self->b.form, NEWT_KEY_LEFT);
1081         newtFormAddHotKey(self->b.form, NEWT_KEY_RIGHT);
1082         newtFormAddHotKey(self->b.form, NEWT_KEY_ENTER);
1083
1084         while (1) {
1085                 ui_browser__run(&self->b, es);
1086
1087                 if (es->reason != NEWT_EXIT_HOTKEY)
1088                         break;
1089                 switch (es->u.key) {
1090                 case 'd': { /* Debug */
1091                         static int seq;
1092                         struct hist_entry *h = rb_entry(self->b.top,
1093                                                         struct hist_entry, rb_node);
1094                         ui_helpline__pop();
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,
1098                                            self->b.height,
1099                                            self->b.index,
1100                                            self->b.top_idx,
1101                                            h->row_offset, h->nr_rows);
1102                 }
1103                         continue;
1104                 case NEWT_KEY_ENTER:
1105                         if (hist_browser__toggle_fold(self))
1106                                 break;
1107                         /* fall thru */
1108                 default:
1109                         return 0;
1110                 }
1111         }
1112         return 0;
1113 }