aboutsummaryrefslogtreecommitdiffstats
path: root/tools/perf/util/newt.c
diff options
context:
space:
mode:
Diffstat (limited to 'tools/perf/util/newt.c')
-rw-r--r--tools/perf/util/newt.c965
1 files changed, 11 insertions, 954 deletions
diff --git a/tools/perf/util/newt.c b/tools/perf/util/newt.c
index b596926afa2b..6bccdaa159aa 100644
--- a/tools/perf/util/newt.c
+++ b/tools/perf/util/newt.c
@@ -1,24 +1,19 @@
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> 1#include <newt.h>
2#include <signal.h>
3#include <stdio.h>
4#include <stdbool.h>
5#include <string.h>
9#include <sys/ttydefaults.h> 6#include <sys/ttydefaults.h>
10 7
11#include "cache.h" 8#include "cache.h"
12#include "hist.h" 9#include "debug.h"
13#include "pstack.h"
14#include "session.h"
15#include "sort.h"
16#include "symbol.h"
17#include "ui/browser.h" 10#include "ui/browser.h"
18#include "ui/helpline.h" 11#include "ui/helpline.h"
19#include "ui/browsers/map.h"
20 12
21newtComponent newt_form__new(void); 13newtComponent newt_form__new(void);
14int popup_menu(int argc, char * const argv[]);
15int ui__help_window(const char *text);
16bool dialog_yesno(const char *msg);
22 17
23char browser__last_msg[1024]; 18char browser__last_msg[1024];
24 19
@@ -57,7 +52,7 @@ newtComponent newt_form__new(void)
57 return self; 52 return self;
58} 53}
59 54
60static int popup_menu(int argc, char * const argv[]) 55int popup_menu(int argc, char * const argv[])
61{ 56{
62 struct newtExitStruct es; 57 struct newtExitStruct es;
63 int i, rc = -1, max_len = 5; 58 int i, rc = -1, max_len = 5;
@@ -91,7 +86,7 @@ out_destroy_form:
91 return rc; 86 return rc;
92} 87}
93 88
94static int ui__help_window(const char *text) 89int ui__help_window(const char *text)
95{ 90{
96 struct newtExitStruct es; 91 struct newtExitStruct es;
97 newtComponent tb, form = newt_form__new(); 92 newtComponent tb, form = newt_form__new();
@@ -133,316 +128,13 @@ out_destroy_form:
133 return rc; 128 return rc;
134} 129}
135 130
136static bool dialog_yesno(const char *msg) 131bool dialog_yesno(const char *msg)
137{ 132{
138 /* newtWinChoice should really be accepting const char pointers... */ 133 /* newtWinChoice should really be accepting const char pointers... */
139 char yes[] = "Yes", no[] = "No"; 134 char yes[] = "Yes", no[] = "No";
140 return newtWinChoice(NULL, yes, no, (char *)msg) == 1; 135 return newtWinChoice(NULL, yes, no, (char *)msg) == 1;
141} 136}
142 137
143static 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
153struct hist_browser {
154 struct ui_browser b;
155 struct hists *hists;
156 struct hist_entry *he_selection;
157 struct map_symbol *selection;
158};
159
160static void hist_browser__reset(struct hist_browser *self);
161static int hist_browser__run(struct hist_browser *self, const char *title,
162 struct newtExitStruct *es);
163static unsigned int hist_browser__refresh(struct ui_browser *self);
164static void ui_browser__hists_seek(struct ui_browser *self,
165 off_t offset, int whence);
166
167static 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
180static void hist_browser__delete(struct hist_browser *self)
181{
182 newtFormDestroy(self->b.form);
183 newtPopWindow();
184 free(self);
185}
186
187static struct hist_entry *hist_browser__selected_entry(struct hist_browser *self)
188{
189 return self->he_selection;
190}
191
192static struct thread *hist_browser__selected_thread(struct hist_browser *self)
193{
194 return self->he_selection->thread;
195}
196
197static 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
214int 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 '?':
279do_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;
352do_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) {
369zoom_dso:
370 if (dso_filter) {
371 pstack__remove(fstack, &dso_filter);
372zoom_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) {
388zoom_thread:
389 if (thread_filter) {
390 pstack__remove(fstack, &thread_filter);
391zoom_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 }
407out_free_stack:
408 pstack__delete(fstack);
409out:
410 hist_browser__delete(browser);
411 return key;
412}
413
414int 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
446static void newt_suspend(void *d __used) 138static void newt_suspend(void *d __used)
447{ 139{
448 newtSuspend(); 140 newtSuspend();
@@ -476,638 +168,3 @@ void exit_browser(bool wait_for_ok)
476 newtFinished(); 168 newtFinished();
477 } 169 }
478} 170}
479
480static 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
487static 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
494static char tree__folded_sign(bool unfolded)
495{
496 return unfolded ? '-' : '+';
497}
498
499static char map_symbol__folded(const struct map_symbol *self)
500{
501 return self->has_children ? tree__folded_sign(self->unfolded) : ' ';
502}
503
504static char hist_entry__folded(const struct hist_entry *self)
505{
506 return map_symbol__folded(&self->ms);
507}
508
509static char callchain_list__folded(const struct callchain_list *self)
510{
511 return map_symbol__folded(&self->ms);
512}
513
514static 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
525static 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;
604do_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 }
619out:
620 return row - first_row;
621}
622
623static 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);
675out:
676 return row - first_row;
677}
678
679static 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
701static 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
761static 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
784static 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
807static 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
817static 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
827static 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
835static 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
848static 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
861static 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 */
903do_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
977static 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
1002static 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
1019static 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
1032static 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
1054static 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}