aboutsummaryrefslogtreecommitdiffstats
path: root/tools
diff options
context:
space:
mode:
authorArnaldo Carvalho de Melo <acme@redhat.com>2010-08-10 14:49:07 -0400
committerArnaldo Carvalho de Melo <acme@redhat.com>2010-08-10 15:11:08 -0400
commitd1b4f2491c3341c61c752049f73ba12553f978d8 (patch)
tree98bb44444cc2eff8680dd72b09c3cc8b059f1b54 /tools
parent696b97a5d2de9e2b22699300835e675dfffe8592 (diff)
perf ui: Move hists browser to util/ui/browsers/
LKML-Reference: <new-submission> Signed-off-by: Arnaldo Carvalho de Melo <acme@redhat.com>
Diffstat (limited to 'tools')
-rw-r--r--tools/perf/Makefile4
-rw-r--r--tools/perf/util/newt.c965
-rw-r--r--tools/perf/util/pstack.h2
-rw-r--r--tools/perf/util/ui/browsers/hists.c950
4 files changed, 967 insertions, 954 deletions
diff --git a/tools/perf/Makefile b/tools/perf/Makefile
index d118020014ff..62e4d6f0dc85 100644
--- a/tools/perf/Makefile
+++ b/tools/perf/Makefile
@@ -570,6 +570,7 @@ else
570 LIB_OBJS += $(OUTPUT)util/newt.o 570 LIB_OBJS += $(OUTPUT)util/newt.o
571 LIB_OBJS += $(OUTPUT)util/ui/browser.o 571 LIB_OBJS += $(OUTPUT)util/ui/browser.o
572 LIB_OBJS += $(OUTPUT)util/ui/browsers/annotate.o 572 LIB_OBJS += $(OUTPUT)util/ui/browsers/annotate.o
573 LIB_OBJS += $(OUTPUT)util/ui/browsers/hists.o
573 LIB_OBJS += $(OUTPUT)util/ui/browsers/map.o 574 LIB_OBJS += $(OUTPUT)util/ui/browsers/map.o
574 LIB_OBJS += $(OUTPUT)util/ui/helpline.o 575 LIB_OBJS += $(OUTPUT)util/ui/helpline.o
575 LIB_OBJS += $(OUTPUT)util/ui/progress.o 576 LIB_OBJS += $(OUTPUT)util/ui/progress.o
@@ -984,6 +985,9 @@ $(OUTPUT)util/ui/browser.o: util/ui/browser.c $(OUTPUT)PERF-CFLAGS
984$(OUTPUT)util/ui/browsers/annotate.o: util/ui/browsers/annotate.c $(OUTPUT)PERF-CFLAGS 985$(OUTPUT)util/ui/browsers/annotate.o: util/ui/browsers/annotate.c $(OUTPUT)PERF-CFLAGS
985 $(QUIET_CC)$(CC) -o $@ -c $(ALL_CFLAGS) -DENABLE_SLFUTURE_CONST $< 986 $(QUIET_CC)$(CC) -o $@ -c $(ALL_CFLAGS) -DENABLE_SLFUTURE_CONST $<
986 987
988$(OUTPUT)util/ui/browsers/hists.o: util/ui/browsers/hists.c $(OUTPUT)PERF-CFLAGS
989 $(QUIET_CC)$(CC) -o $@ -c $(ALL_CFLAGS) -DENABLE_SLFUTURE_CONST $<
990
987$(OUTPUT)util/ui/browsers/map.o: util/ui/browsers/map.c $(OUTPUT)PERF-CFLAGS 991$(OUTPUT)util/ui/browsers/map.o: util/ui/browsers/map.c $(OUTPUT)PERF-CFLAGS
988 $(QUIET_CC)$(CC) -o $@ -c $(ALL_CFLAGS) -DENABLE_SLFUTURE_CONST $< 992 $(QUIET_CC)$(CC) -o $@ -c $(ALL_CFLAGS) -DENABLE_SLFUTURE_CONST $<
989 993
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}
diff --git a/tools/perf/util/pstack.h b/tools/perf/util/pstack.h
index 5ad07023504b..4cedea59f518 100644
--- a/tools/perf/util/pstack.h
+++ b/tools/perf/util/pstack.h
@@ -1,6 +1,8 @@
1#ifndef _PERF_PSTACK_ 1#ifndef _PERF_PSTACK_
2#define _PERF_PSTACK_ 2#define _PERF_PSTACK_
3 3
4#include <stdbool.h>
5
4struct pstack; 6struct pstack;
5struct pstack *pstack__new(unsigned short max_nr_entries); 7struct pstack *pstack__new(unsigned short max_nr_entries);
6void pstack__delete(struct pstack *self); 8void pstack__delete(struct pstack *self);
diff --git a/tools/perf/util/ui/browsers/hists.c b/tools/perf/util/ui/browsers/hists.c
new file mode 100644
index 000000000000..9d32a4149ab3
--- /dev/null
+++ b/tools/perf/util/ui/browsers/hists.c
@@ -0,0 +1,950 @@
1#define _GNU_SOURCE
2#include <stdio.h>
3#undef _GNU_SOURCE
4#include "../libslang.h"
5#include <stdlib.h>
6#include <string.h>
7#include <newt.h>
8#include <linux/rbtree.h>
9
10#include "../../hist.h"
11#include "../../pstack.h"
12#include "../../sort.h"
13#include "../../util.h"
14
15#include "../browser.h"
16#include "../helpline.h"
17#include "../util.h"
18#include "map.h"
19
20int ui__help_window(const char *text);
21bool dialog_yesno(const char *msg);
22int popup_menu(int argc, char * const argv[]);
23
24struct hist_browser {
25 struct ui_browser b;
26 struct hists *hists;
27 struct hist_entry *he_selection;
28 struct map_symbol *selection;
29};
30
31static void hist_browser__refresh_dimensions(struct hist_browser *self)
32{
33 /* 3 == +/- toggle symbol before actual hist_entry rendering */
34 self->b.width = 3 + (hists__sort_list_width(self->hists) +
35 sizeof("[k]"));
36}
37
38static void hist_browser__reset(struct hist_browser *self)
39{
40 self->b.nr_entries = self->hists->nr_entries;
41 hist_browser__refresh_dimensions(self);
42 ui_browser__reset_index(&self->b);
43}
44
45static char tree__folded_sign(bool unfolded)
46{
47 return unfolded ? '-' : '+';
48}
49
50static char map_symbol__folded(const struct map_symbol *self)
51{
52 return self->has_children ? tree__folded_sign(self->unfolded) : ' ';
53}
54
55static char hist_entry__folded(const struct hist_entry *self)
56{
57 return map_symbol__folded(&self->ms);
58}
59
60static char callchain_list__folded(const struct callchain_list *self)
61{
62 return map_symbol__folded(&self->ms);
63}
64
65static int callchain_node__count_rows_rb_tree(struct callchain_node *self)
66{
67 int n = 0;
68 struct rb_node *nd;
69
70 for (nd = rb_first(&self->rb_root); nd; nd = rb_next(nd)) {
71 struct callchain_node *child = rb_entry(nd, struct callchain_node, rb_node);
72 struct callchain_list *chain;
73 char folded_sign = ' '; /* No children */
74
75 list_for_each_entry(chain, &child->val, list) {
76 ++n;
77 /* We need this because we may not have children */
78 folded_sign = callchain_list__folded(chain);
79 if (folded_sign == '+')
80 break;
81 }
82
83 if (folded_sign == '-') /* Have children and they're unfolded */
84 n += callchain_node__count_rows_rb_tree(child);
85 }
86
87 return n;
88}
89
90static int callchain_node__count_rows(struct callchain_node *node)
91{
92 struct callchain_list *chain;
93 bool unfolded = false;
94 int n = 0;
95
96 list_for_each_entry(chain, &node->val, list) {
97 ++n;
98 unfolded = chain->ms.unfolded;
99 }
100
101 if (unfolded)
102 n += callchain_node__count_rows_rb_tree(node);
103
104 return n;
105}
106
107static int callchain__count_rows(struct rb_root *chain)
108{
109 struct rb_node *nd;
110 int n = 0;
111
112 for (nd = rb_first(chain); nd; nd = rb_next(nd)) {
113 struct callchain_node *node = rb_entry(nd, struct callchain_node, rb_node);
114 n += callchain_node__count_rows(node);
115 }
116
117 return n;
118}
119
120static bool map_symbol__toggle_fold(struct map_symbol *self)
121{
122 if (!self->has_children)
123 return false;
124
125 self->unfolded = !self->unfolded;
126 return true;
127}
128
129static void callchain_node__init_have_children_rb_tree(struct callchain_node *self)
130{
131 struct rb_node *nd = rb_first(&self->rb_root);
132
133 for (nd = rb_first(&self->rb_root); nd; nd = rb_next(nd)) {
134 struct callchain_node *child = rb_entry(nd, struct callchain_node, rb_node);
135 struct callchain_list *chain;
136 int first = true;
137
138 list_for_each_entry(chain, &child->val, list) {
139 if (first) {
140 first = false;
141 chain->ms.has_children = chain->list.next != &child->val ||
142 rb_first(&child->rb_root) != NULL;
143 } else
144 chain->ms.has_children = chain->list.next == &child->val &&
145 rb_first(&child->rb_root) != NULL;
146 }
147
148 callchain_node__init_have_children_rb_tree(child);
149 }
150}
151
152static void callchain_node__init_have_children(struct callchain_node *self)
153{
154 struct callchain_list *chain;
155
156 list_for_each_entry(chain, &self->val, list)
157 chain->ms.has_children = rb_first(&self->rb_root) != NULL;
158
159 callchain_node__init_have_children_rb_tree(self);
160}
161
162static void callchain__init_have_children(struct rb_root *self)
163{
164 struct rb_node *nd;
165
166 for (nd = rb_first(self); nd; nd = rb_next(nd)) {
167 struct callchain_node *node = rb_entry(nd, struct callchain_node, rb_node);
168 callchain_node__init_have_children(node);
169 }
170}
171
172static void hist_entry__init_have_children(struct hist_entry *self)
173{
174 if (!self->init_have_children) {
175 callchain__init_have_children(&self->sorted_chain);
176 self->init_have_children = true;
177 }
178}
179
180static bool hist_browser__toggle_fold(struct hist_browser *self)
181{
182 if (map_symbol__toggle_fold(self->selection)) {
183 struct hist_entry *he = self->he_selection;
184
185 hist_entry__init_have_children(he);
186 self->hists->nr_entries -= he->nr_rows;
187
188 if (he->ms.unfolded)
189 he->nr_rows = callchain__count_rows(&he->sorted_chain);
190 else
191 he->nr_rows = 0;
192 self->hists->nr_entries += he->nr_rows;
193 self->b.nr_entries = self->hists->nr_entries;
194
195 return true;
196 }
197
198 /* If it doesn't have children, no toggling performed */
199 return false;
200}
201
202static int hist_browser__run(struct hist_browser *self, const char *title,
203 struct newtExitStruct *es)
204{
205 char str[256], unit;
206 unsigned long nr_events = self->hists->stats.nr_events[PERF_RECORD_SAMPLE];
207
208 self->b.entries = &self->hists->entries;
209 self->b.nr_entries = self->hists->nr_entries;
210
211 hist_browser__refresh_dimensions(self);
212
213 nr_events = convert_unit(nr_events, &unit);
214 snprintf(str, sizeof(str), "Events: %lu%c ",
215 nr_events, unit);
216 newtDrawRootText(0, 0, str);
217
218 if (ui_browser__show(&self->b, title) < 0)
219 return -1;
220
221 newtFormAddHotKey(self->b.form, 'A');
222 newtFormAddHotKey(self->b.form, 'a');
223 newtFormAddHotKey(self->b.form, '?');
224 newtFormAddHotKey(self->b.form, 'h');
225 newtFormAddHotKey(self->b.form, 'H');
226 newtFormAddHotKey(self->b.form, 'd');
227
228 newtFormAddHotKey(self->b.form, NEWT_KEY_LEFT);
229 newtFormAddHotKey(self->b.form, NEWT_KEY_RIGHT);
230 newtFormAddHotKey(self->b.form, NEWT_KEY_ENTER);
231
232 while (1) {
233 ui_browser__run(&self->b, es);
234
235 if (es->reason != NEWT_EXIT_HOTKEY)
236 break;
237 switch (es->u.key) {
238 case 'd': { /* Debug */
239 static int seq;
240 struct hist_entry *h = rb_entry(self->b.top,
241 struct hist_entry, rb_node);
242 ui_helpline__pop();
243 ui_helpline__fpush("%d: nr_ent=(%d,%d), height=%d, idx=%d, fve: idx=%d, row_off=%d, nrows=%d",
244 seq++, self->b.nr_entries,
245 self->hists->nr_entries,
246 self->b.height,
247 self->b.index,
248 self->b.top_idx,
249 h->row_offset, h->nr_rows);
250 }
251 continue;
252 case NEWT_KEY_ENTER:
253 if (hist_browser__toggle_fold(self))
254 break;
255 /* fall thru */
256 default:
257 return 0;
258 }
259 }
260 return 0;
261}
262
263static char *callchain_list__sym_name(struct callchain_list *self,
264 char *bf, size_t bfsize)
265{
266 if (self->ms.sym)
267 return self->ms.sym->name;
268
269 snprintf(bf, bfsize, "%#Lx", self->ip);
270 return bf;
271}
272
273#define LEVEL_OFFSET_STEP 3
274
275static int hist_browser__show_callchain_node_rb_tree(struct hist_browser *self,
276 struct callchain_node *chain_node,
277 u64 total, int level,
278 unsigned short row,
279 off_t *row_offset,
280 bool *is_current_entry)
281{
282 struct rb_node *node;
283 int first_row = row, width, offset = level * LEVEL_OFFSET_STEP;
284 u64 new_total, remaining;
285
286 if (callchain_param.mode == CHAIN_GRAPH_REL)
287 new_total = chain_node->children_hit;
288 else
289 new_total = total;
290
291 remaining = new_total;
292 node = rb_first(&chain_node->rb_root);
293 while (node) {
294 struct callchain_node *child = rb_entry(node, struct callchain_node, rb_node);
295 struct rb_node *next = rb_next(node);
296 u64 cumul = cumul_hits(child);
297 struct callchain_list *chain;
298 char folded_sign = ' ';
299 int first = true;
300 int extra_offset = 0;
301
302 remaining -= cumul;
303
304 list_for_each_entry(chain, &child->val, list) {
305 char ipstr[BITS_PER_LONG / 4 + 1], *alloc_str;
306 const char *str;
307 int color;
308 bool was_first = first;
309
310 if (first) {
311 first = false;
312 chain->ms.has_children = chain->list.next != &child->val ||
313 rb_first(&child->rb_root) != NULL;
314 } else {
315 extra_offset = LEVEL_OFFSET_STEP;
316 chain->ms.has_children = chain->list.next == &child->val &&
317 rb_first(&child->rb_root) != NULL;
318 }
319
320 folded_sign = callchain_list__folded(chain);
321 if (*row_offset != 0) {
322 --*row_offset;
323 goto do_next;
324 }
325
326 alloc_str = NULL;
327 str = callchain_list__sym_name(chain, ipstr, sizeof(ipstr));
328 if (was_first) {
329 double percent = cumul * 100.0 / new_total;
330
331 if (asprintf(&alloc_str, "%2.2f%% %s", percent, str) < 0)
332 str = "Not enough memory!";
333 else
334 str = alloc_str;
335 }
336
337 color = HE_COLORSET_NORMAL;
338 width = self->b.width - (offset + extra_offset + 2);
339 if (ui_browser__is_current_entry(&self->b, row)) {
340 self->selection = &chain->ms;
341 color = HE_COLORSET_SELECTED;
342 *is_current_entry = true;
343 }
344
345 SLsmg_set_color(color);
346 SLsmg_gotorc(self->b.y + row, self->b.x);
347 slsmg_write_nstring(" ", offset + extra_offset);
348 slsmg_printf("%c ", folded_sign);
349 slsmg_write_nstring(str, width);
350 free(alloc_str);
351
352 if (++row == self->b.height)
353 goto out;
354do_next:
355 if (folded_sign == '+')
356 break;
357 }
358
359 if (folded_sign == '-') {
360 const int new_level = level + (extra_offset ? 2 : 1);
361 row += hist_browser__show_callchain_node_rb_tree(self, child, new_total,
362 new_level, row, row_offset,
363 is_current_entry);
364 }
365 if (row == self->b.height)
366 goto out;
367 node = next;
368 }
369out:
370 return row - first_row;
371}
372
373static int hist_browser__show_callchain_node(struct hist_browser *self,
374 struct callchain_node *node,
375 int level, unsigned short row,
376 off_t *row_offset,
377 bool *is_current_entry)
378{
379 struct callchain_list *chain;
380 int first_row = row,
381 offset = level * LEVEL_OFFSET_STEP,
382 width = self->b.width - offset;
383 char folded_sign = ' ';
384
385 list_for_each_entry(chain, &node->val, list) {
386 char ipstr[BITS_PER_LONG / 4 + 1], *s;
387 int color;
388 /*
389 * FIXME: This should be moved to somewhere else,
390 * probably when the callchain is created, so as not to
391 * traverse it all over again
392 */
393 chain->ms.has_children = rb_first(&node->rb_root) != NULL;
394 folded_sign = callchain_list__folded(chain);
395
396 if (*row_offset != 0) {
397 --*row_offset;
398 continue;
399 }
400
401 color = HE_COLORSET_NORMAL;
402 if (ui_browser__is_current_entry(&self->b, row)) {
403 self->selection = &chain->ms;
404 color = HE_COLORSET_SELECTED;
405 *is_current_entry = true;
406 }
407
408 s = callchain_list__sym_name(chain, ipstr, sizeof(ipstr));
409 SLsmg_gotorc(self->b.y + row, self->b.x);
410 SLsmg_set_color(color);
411 slsmg_write_nstring(" ", offset);
412 slsmg_printf("%c ", folded_sign);
413 slsmg_write_nstring(s, width - 2);
414
415 if (++row == self->b.height)
416 goto out;
417 }
418
419 if (folded_sign == '-')
420 row += hist_browser__show_callchain_node_rb_tree(self, node,
421 self->hists->stats.total_period,
422 level + 1, row,
423 row_offset,
424 is_current_entry);
425out:
426 return row - first_row;
427}
428
429static int hist_browser__show_callchain(struct hist_browser *self,
430 struct rb_root *chain,
431 int level, unsigned short row,
432 off_t *row_offset,
433 bool *is_current_entry)
434{
435 struct rb_node *nd;
436 int first_row = row;
437
438 for (nd = rb_first(chain); nd; nd = rb_next(nd)) {
439 struct callchain_node *node = rb_entry(nd, struct callchain_node, rb_node);
440
441 row += hist_browser__show_callchain_node(self, node, level,
442 row, row_offset,
443 is_current_entry);
444 if (row == self->b.height)
445 break;
446 }
447
448 return row - first_row;
449}
450
451static int hist_browser__show_entry(struct hist_browser *self,
452 struct hist_entry *entry,
453 unsigned short row)
454{
455 char s[256];
456 double percent;
457 int printed = 0;
458 int color, width = self->b.width;
459 char folded_sign = ' ';
460 bool current_entry = ui_browser__is_current_entry(&self->b, row);
461 off_t row_offset = entry->row_offset;
462
463 if (current_entry) {
464 self->he_selection = entry;
465 self->selection = &entry->ms;
466 }
467
468 if (symbol_conf.use_callchain) {
469 entry->ms.has_children = !RB_EMPTY_ROOT(&entry->sorted_chain);
470 folded_sign = hist_entry__folded(entry);
471 }
472
473 if (row_offset == 0) {
474 hist_entry__snprintf(entry, s, sizeof(s), self->hists, NULL, false,
475 0, false, self->hists->stats.total_period);
476 percent = (entry->period * 100.0) / self->hists->stats.total_period;
477
478 color = HE_COLORSET_SELECTED;
479 if (!current_entry) {
480 if (percent >= MIN_RED)
481 color = HE_COLORSET_TOP;
482 else if (percent >= MIN_GREEN)
483 color = HE_COLORSET_MEDIUM;
484 else
485 color = HE_COLORSET_NORMAL;
486 }
487
488 SLsmg_set_color(color);
489 SLsmg_gotorc(self->b.y + row, self->b.x);
490 if (symbol_conf.use_callchain) {
491 slsmg_printf("%c ", folded_sign);
492 width -= 2;
493 }
494 slsmg_write_nstring(s, width);
495 ++row;
496 ++printed;
497 } else
498 --row_offset;
499
500 if (folded_sign == '-' && row != self->b.height) {
501 printed += hist_browser__show_callchain(self, &entry->sorted_chain,
502 1, row, &row_offset,
503 &current_entry);
504 if (current_entry)
505 self->he_selection = entry;
506 }
507
508 return printed;
509}
510
511static unsigned int hist_browser__refresh(struct ui_browser *self)
512{
513 unsigned row = 0;
514 struct rb_node *nd;
515 struct hist_browser *hb = container_of(self, struct hist_browser, b);
516
517 if (self->top == NULL)
518 self->top = rb_first(&hb->hists->entries);
519
520 for (nd = self->top; nd; nd = rb_next(nd)) {
521 struct hist_entry *h = rb_entry(nd, struct hist_entry, rb_node);
522
523 if (h->filtered)
524 continue;
525
526 row += hist_browser__show_entry(hb, h, row);
527 if (row == self->height)
528 break;
529 }
530
531 return row;
532}
533
534static struct rb_node *hists__filter_entries(struct rb_node *nd)
535{
536 while (nd != NULL) {
537 struct hist_entry *h = rb_entry(nd, struct hist_entry, rb_node);
538 if (!h->filtered)
539 return nd;
540
541 nd = rb_next(nd);
542 }
543
544 return NULL;
545}
546
547static struct rb_node *hists__filter_prev_entries(struct rb_node *nd)
548{
549 while (nd != NULL) {
550 struct hist_entry *h = rb_entry(nd, struct hist_entry, rb_node);
551 if (!h->filtered)
552 return nd;
553
554 nd = rb_prev(nd);
555 }
556
557 return NULL;
558}
559
560static void ui_browser__hists_seek(struct ui_browser *self,
561 off_t offset, int whence)
562{
563 struct hist_entry *h;
564 struct rb_node *nd;
565 bool first = true;
566
567 switch (whence) {
568 case SEEK_SET:
569 nd = hists__filter_entries(rb_first(self->entries));
570 break;
571 case SEEK_CUR:
572 nd = self->top;
573 goto do_offset;
574 case SEEK_END:
575 nd = hists__filter_prev_entries(rb_last(self->entries));
576 first = false;
577 break;
578 default:
579 return;
580 }
581
582 /*
583 * Moves not relative to the first visible entry invalidates its
584 * row_offset:
585 */
586 h = rb_entry(self->top, struct hist_entry, rb_node);
587 h->row_offset = 0;
588
589 /*
590 * Here we have to check if nd is expanded (+), if it is we can't go
591 * the next top level hist_entry, instead we must compute an offset of
592 * what _not_ to show and not change the first visible entry.
593 *
594 * This offset increments when we are going from top to bottom and
595 * decreases when we're going from bottom to top.
596 *
597 * As we don't have backpointers to the top level in the callchains
598 * structure, we need to always print the whole hist_entry callchain,
599 * skipping the first ones that are before the first visible entry
600 * and stop when we printed enough lines to fill the screen.
601 */
602do_offset:
603 if (offset > 0) {
604 do {
605 h = rb_entry(nd, struct hist_entry, rb_node);
606 if (h->ms.unfolded) {
607 u16 remaining = h->nr_rows - h->row_offset;
608 if (offset > remaining) {
609 offset -= remaining;
610 h->row_offset = 0;
611 } else {
612 h->row_offset += offset;
613 offset = 0;
614 self->top = nd;
615 break;
616 }
617 }
618 nd = hists__filter_entries(rb_next(nd));
619 if (nd == NULL)
620 break;
621 --offset;
622 self->top = nd;
623 } while (offset != 0);
624 } else if (offset < 0) {
625 while (1) {
626 h = rb_entry(nd, struct hist_entry, rb_node);
627 if (h->ms.unfolded) {
628 if (first) {
629 if (-offset > h->row_offset) {
630 offset += h->row_offset;
631 h->row_offset = 0;
632 } else {
633 h->row_offset += offset;
634 offset = 0;
635 self->top = nd;
636 break;
637 }
638 } else {
639 if (-offset > h->nr_rows) {
640 offset += h->nr_rows;
641 h->row_offset = 0;
642 } else {
643 h->row_offset = h->nr_rows + offset;
644 offset = 0;
645 self->top = nd;
646 break;
647 }
648 }
649 }
650
651 nd = hists__filter_prev_entries(rb_prev(nd));
652 if (nd == NULL)
653 break;
654 ++offset;
655 self->top = nd;
656 if (offset == 0) {
657 /*
658 * Last unfiltered hist_entry, check if it is
659 * unfolded, if it is then we should have
660 * row_offset at its last entry.
661 */
662 h = rb_entry(nd, struct hist_entry, rb_node);
663 if (h->ms.unfolded)
664 h->row_offset = h->nr_rows;
665 break;
666 }
667 first = false;
668 }
669 } else {
670 self->top = nd;
671 h = rb_entry(nd, struct hist_entry, rb_node);
672 h->row_offset = 0;
673 }
674}
675
676static struct hist_browser *hist_browser__new(struct hists *hists)
677{
678 struct hist_browser *self = zalloc(sizeof(*self));
679
680 if (self) {
681 self->hists = hists;
682 self->b.refresh = hist_browser__refresh;
683 self->b.seek = ui_browser__hists_seek;
684 }
685
686 return self;
687}
688
689static void hist_browser__delete(struct hist_browser *self)
690{
691 newtFormDestroy(self->b.form);
692 newtPopWindow();
693 free(self);
694}
695
696static struct hist_entry *hist_browser__selected_entry(struct hist_browser *self)
697{
698 return self->he_selection;
699}
700
701static struct thread *hist_browser__selected_thread(struct hist_browser *self)
702{
703 return self->he_selection->thread;
704}
705
706static int hist_browser__title(char *bf, size_t size, const char *ev_name,
707 const struct dso *dso, const struct thread *thread)
708{
709 int printed = 0;
710
711 if (thread)
712 printed += snprintf(bf + printed, size - printed,
713 "Thread: %s(%d)",
714 (thread->comm_set ? thread->comm : ""),
715 thread->pid);
716 if (dso)
717 printed += snprintf(bf + printed, size - printed,
718 "%sDSO: %s", thread ? " " : "",
719 dso->short_name);
720 return printed ?: snprintf(bf, size, "Event: %s", ev_name);
721}
722
723int hists__browse(struct hists *self, const char *helpline, const char *ev_name)
724{
725 struct hist_browser *browser = hist_browser__new(self);
726 struct pstack *fstack;
727 const struct thread *thread_filter = NULL;
728 const struct dso *dso_filter = NULL;
729 struct newtExitStruct es;
730 char msg[160];
731 int key = -1;
732
733 if (browser == NULL)
734 return -1;
735
736 fstack = pstack__new(2);
737 if (fstack == NULL)
738 goto out;
739
740 ui_helpline__push(helpline);
741
742 hist_browser__title(msg, sizeof(msg), ev_name,
743 dso_filter, thread_filter);
744
745 while (1) {
746 const struct thread *thread;
747 const struct dso *dso;
748 char *options[16];
749 int nr_options = 0, choice = 0, i,
750 annotate = -2, zoom_dso = -2, zoom_thread = -2,
751 browse_map = -2;
752
753 if (hist_browser__run(browser, msg, &es))
754 break;
755
756 thread = hist_browser__selected_thread(browser);
757 dso = browser->selection->map ? browser->selection->map->dso : NULL;
758
759 if (es.reason == NEWT_EXIT_HOTKEY) {
760 key = es.u.key;
761
762 switch (key) {
763 case NEWT_KEY_F1:
764 goto do_help;
765 case NEWT_KEY_TAB:
766 case NEWT_KEY_UNTAB:
767 /*
768 * Exit the browser, let hists__browser_tree
769 * go to the next or previous
770 */
771 goto out_free_stack;
772 default:;
773 }
774
775 key = toupper(key);
776 switch (key) {
777 case 'A':
778 if (browser->selection->map == NULL &&
779 browser->selection->map->dso->annotate_warned)
780 continue;
781 goto do_annotate;
782 case 'D':
783 goto zoom_dso;
784 case 'T':
785 goto zoom_thread;
786 case 'H':
787 case '?':
788do_help:
789 ui__help_window("-> Zoom into DSO/Threads & Annotate current symbol\n"
790 "<- Zoom out\n"
791 "a Annotate current symbol\n"
792 "h/?/F1 Show this window\n"
793 "d Zoom into current DSO\n"
794 "t Zoom into current Thread\n"
795 "q/CTRL+C Exit browser");
796 continue;
797 default:;
798 }
799 if (is_exit_key(key)) {
800 if (key == NEWT_KEY_ESCAPE &&
801 !dialog_yesno("Do you really want to exit?"))
802 continue;
803 break;
804 }
805
806 if (es.u.key == NEWT_KEY_LEFT) {
807 const void *top;
808
809 if (pstack__empty(fstack))
810 continue;
811 top = pstack__pop(fstack);
812 if (top == &dso_filter)
813 goto zoom_out_dso;
814 if (top == &thread_filter)
815 goto zoom_out_thread;
816 continue;
817 }
818 }
819
820 if (browser->selection->sym != NULL &&
821 !browser->selection->map->dso->annotate_warned &&
822 asprintf(&options[nr_options], "Annotate %s",
823 browser->selection->sym->name) > 0)
824 annotate = nr_options++;
825
826 if (thread != NULL &&
827 asprintf(&options[nr_options], "Zoom %s %s(%d) thread",
828 (thread_filter ? "out of" : "into"),
829 (thread->comm_set ? thread->comm : ""),
830 thread->pid) > 0)
831 zoom_thread = nr_options++;
832
833 if (dso != NULL &&
834 asprintf(&options[nr_options], "Zoom %s %s DSO",
835 (dso_filter ? "out of" : "into"),
836 (dso->kernel ? "the Kernel" : dso->short_name)) > 0)
837 zoom_dso = nr_options++;
838
839 if (browser->selection->map != NULL &&
840 asprintf(&options[nr_options], "Browse map details") > 0)
841 browse_map = nr_options++;
842
843 options[nr_options++] = (char *)"Exit";
844
845 choice = popup_menu(nr_options, options);
846
847 for (i = 0; i < nr_options - 1; ++i)
848 free(options[i]);
849
850 if (choice == nr_options - 1)
851 break;
852
853 if (choice == -1)
854 continue;
855
856 if (choice == annotate) {
857 struct hist_entry *he;
858do_annotate:
859 if (browser->selection->map->dso->origin == DSO__ORIG_KERNEL) {
860 browser->selection->map->dso->annotate_warned = 1;
861 ui_helpline__puts("No vmlinux file found, can't "
862 "annotate with just a "
863 "kallsyms file");
864 continue;
865 }
866
867 he = hist_browser__selected_entry(browser);
868 if (he == NULL)
869 continue;
870
871 hist_entry__tui_annotate(he);
872 } else if (choice == browse_map)
873 map__browse(browser->selection->map);
874 else if (choice == zoom_dso) {
875zoom_dso:
876 if (dso_filter) {
877 pstack__remove(fstack, &dso_filter);
878zoom_out_dso:
879 ui_helpline__pop();
880 dso_filter = NULL;
881 } else {
882 if (dso == NULL)
883 continue;
884 ui_helpline__fpush("To zoom out press <- or -> + \"Zoom out of %s DSO\"",
885 dso->kernel ? "the Kernel" : dso->short_name);
886 dso_filter = dso;
887 pstack__push(fstack, &dso_filter);
888 }
889 hists__filter_by_dso(self, dso_filter);
890 hist_browser__title(msg, sizeof(msg), ev_name,
891 dso_filter, thread_filter);
892 hist_browser__reset(browser);
893 } else if (choice == zoom_thread) {
894zoom_thread:
895 if (thread_filter) {
896 pstack__remove(fstack, &thread_filter);
897zoom_out_thread:
898 ui_helpline__pop();
899 thread_filter = NULL;
900 } else {
901 ui_helpline__fpush("To zoom out press <- or -> + \"Zoom out of %s(%d) thread\"",
902 thread->comm_set ? thread->comm : "",
903 thread->pid);
904 thread_filter = thread;
905 pstack__push(fstack, &thread_filter);
906 }
907 hists__filter_by_thread(self, thread_filter);
908 hist_browser__title(msg, sizeof(msg), ev_name,
909 dso_filter, thread_filter);
910 hist_browser__reset(browser);
911 }
912 }
913out_free_stack:
914 pstack__delete(fstack);
915out:
916 hist_browser__delete(browser);
917 return key;
918}
919
920int hists__tui_browse_tree(struct rb_root *self, const char *help)
921{
922 struct rb_node *first = rb_first(self), *nd = first, *next;
923 int key = 0;
924
925 while (nd) {
926 struct hists *hists = rb_entry(nd, struct hists, rb_node);
927 const char *ev_name = __event_name(hists->type, hists->config);
928
929 key = hists__browse(hists, help, ev_name);
930
931 if (is_exit_key(key))
932 break;
933
934 switch (key) {
935 case NEWT_KEY_TAB:
936 next = rb_next(nd);
937 if (next)
938 nd = next;
939 break;
940 case NEWT_KEY_UNTAB:
941 if (nd == first)
942 continue;
943 nd = rb_prev(nd);
944 default:
945 break;
946 }
947 }
948
949 return key;
950}