perf top: Add callgraph support
[deliverable/linux.git] / tools / perf / util / ui / browsers / hists.c
CommitLineData
d1b4f249
ACM
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
e248de33
ACM
10#include "../../evsel.h"
11#include "../../evlist.h"
d1b4f249
ACM
12#include "../../hist.h"
13#include "../../pstack.h"
14#include "../../sort.h"
15#include "../../util.h"
16
17#include "../browser.h"
18#include "../helpline.h"
19#include "../util.h"
20#include "map.h"
21
d1b4f249
ACM
22struct hist_browser {
23 struct ui_browser b;
24 struct hists *hists;
25 struct hist_entry *he_selection;
26 struct map_symbol *selection;
81cce8de
ACM
27 const struct thread *thread_filter;
28 const struct dso *dso_filter;
d1b4f249
ACM
29};
30
81cce8de
ACM
31static int hists__browser_title(struct hists *self, char *bf, size_t size,
32 const char *ev_name, const struct dso *dso,
33 const struct thread *thread);
34
d1b4f249
ACM
35static void hist_browser__refresh_dimensions(struct hist_browser *self)
36{
37 /* 3 == +/- toggle symbol before actual hist_entry rendering */
38 self->b.width = 3 + (hists__sort_list_width(self->hists) +
39 sizeof("[k]"));
40}
41
42static void hist_browser__reset(struct hist_browser *self)
43{
44 self->b.nr_entries = self->hists->nr_entries;
45 hist_browser__refresh_dimensions(self);
46 ui_browser__reset_index(&self->b);
47}
48
49static char tree__folded_sign(bool unfolded)
50{
51 return unfolded ? '-' : '+';
52}
53
54static char map_symbol__folded(const struct map_symbol *self)
55{
56 return self->has_children ? tree__folded_sign(self->unfolded) : ' ';
57}
58
59static char hist_entry__folded(const struct hist_entry *self)
60{
61 return map_symbol__folded(&self->ms);
62}
63
64static char callchain_list__folded(const struct callchain_list *self)
65{
66 return map_symbol__folded(&self->ms);
67}
68
3c916cc2
ACM
69static void map_symbol__set_folding(struct map_symbol *self, bool unfold)
70{
71 self->unfolded = unfold ? self->has_children : false;
72}
73
d1b4f249
ACM
74static int callchain_node__count_rows_rb_tree(struct callchain_node *self)
75{
76 int n = 0;
77 struct rb_node *nd;
78
79 for (nd = rb_first(&self->rb_root); nd; nd = rb_next(nd)) {
80 struct callchain_node *child = rb_entry(nd, struct callchain_node, rb_node);
81 struct callchain_list *chain;
82 char folded_sign = ' '; /* No children */
83
84 list_for_each_entry(chain, &child->val, list) {
85 ++n;
86 /* We need this because we may not have children */
87 folded_sign = callchain_list__folded(chain);
88 if (folded_sign == '+')
89 break;
90 }
91
92 if (folded_sign == '-') /* Have children and they're unfolded */
93 n += callchain_node__count_rows_rb_tree(child);
94 }
95
96 return n;
97}
98
99static int callchain_node__count_rows(struct callchain_node *node)
100{
101 struct callchain_list *chain;
102 bool unfolded = false;
103 int n = 0;
104
105 list_for_each_entry(chain, &node->val, list) {
106 ++n;
107 unfolded = chain->ms.unfolded;
108 }
109
110 if (unfolded)
111 n += callchain_node__count_rows_rb_tree(node);
112
113 return n;
114}
115
116static int callchain__count_rows(struct rb_root *chain)
117{
118 struct rb_node *nd;
119 int n = 0;
120
121 for (nd = rb_first(chain); nd; nd = rb_next(nd)) {
122 struct callchain_node *node = rb_entry(nd, struct callchain_node, rb_node);
123 n += callchain_node__count_rows(node);
124 }
125
126 return n;
127}
128
129static bool map_symbol__toggle_fold(struct map_symbol *self)
130{
131 if (!self->has_children)
132 return false;
133
134 self->unfolded = !self->unfolded;
135 return true;
136}
137
138static void callchain_node__init_have_children_rb_tree(struct callchain_node *self)
139{
140 struct rb_node *nd = rb_first(&self->rb_root);
141
142 for (nd = rb_first(&self->rb_root); nd; nd = rb_next(nd)) {
143 struct callchain_node *child = rb_entry(nd, struct callchain_node, rb_node);
144 struct callchain_list *chain;
293db47f 145 bool first = true;
d1b4f249
ACM
146
147 list_for_each_entry(chain, &child->val, list) {
148 if (first) {
149 first = false;
150 chain->ms.has_children = chain->list.next != &child->val ||
293db47f 151 !RB_EMPTY_ROOT(&child->rb_root);
d1b4f249
ACM
152 } else
153 chain->ms.has_children = chain->list.next == &child->val &&
293db47f 154 !RB_EMPTY_ROOT(&child->rb_root);
d1b4f249
ACM
155 }
156
157 callchain_node__init_have_children_rb_tree(child);
158 }
159}
160
161static void callchain_node__init_have_children(struct callchain_node *self)
162{
163 struct callchain_list *chain;
164
165 list_for_each_entry(chain, &self->val, list)
293db47f 166 chain->ms.has_children = !RB_EMPTY_ROOT(&self->rb_root);
d1b4f249
ACM
167
168 callchain_node__init_have_children_rb_tree(self);
169}
170
171static void callchain__init_have_children(struct rb_root *self)
172{
173 struct rb_node *nd;
174
175 for (nd = rb_first(self); nd; nd = rb_next(nd)) {
176 struct callchain_node *node = rb_entry(nd, struct callchain_node, rb_node);
177 callchain_node__init_have_children(node);
178 }
179}
180
181static void hist_entry__init_have_children(struct hist_entry *self)
182{
183 if (!self->init_have_children) {
18b308d7 184 self->ms.has_children = !RB_EMPTY_ROOT(&self->sorted_chain);
d1b4f249
ACM
185 callchain__init_have_children(&self->sorted_chain);
186 self->init_have_children = true;
187 }
188}
189
190static bool hist_browser__toggle_fold(struct hist_browser *self)
191{
192 if (map_symbol__toggle_fold(self->selection)) {
193 struct hist_entry *he = self->he_selection;
194
195 hist_entry__init_have_children(he);
196 self->hists->nr_entries -= he->nr_rows;
197
198 if (he->ms.unfolded)
199 he->nr_rows = callchain__count_rows(&he->sorted_chain);
200 else
201 he->nr_rows = 0;
202 self->hists->nr_entries += he->nr_rows;
203 self->b.nr_entries = self->hists->nr_entries;
204
205 return true;
206 }
207
208 /* If it doesn't have children, no toggling performed */
209 return false;
210}
211
3c916cc2
ACM
212static int callchain_node__set_folding_rb_tree(struct callchain_node *self, bool unfold)
213{
214 int n = 0;
215 struct rb_node *nd;
216
217 for (nd = rb_first(&self->rb_root); nd; nd = rb_next(nd)) {
218 struct callchain_node *child = rb_entry(nd, struct callchain_node, rb_node);
219 struct callchain_list *chain;
220 bool has_children = false;
221
222 list_for_each_entry(chain, &child->val, list) {
223 ++n;
224 map_symbol__set_folding(&chain->ms, unfold);
225 has_children = chain->ms.has_children;
226 }
227
228 if (has_children)
229 n += callchain_node__set_folding_rb_tree(child, unfold);
230 }
231
232 return n;
233}
234
235static int callchain_node__set_folding(struct callchain_node *node, bool unfold)
236{
237 struct callchain_list *chain;
238 bool has_children = false;
239 int n = 0;
240
241 list_for_each_entry(chain, &node->val, list) {
242 ++n;
243 map_symbol__set_folding(&chain->ms, unfold);
244 has_children = chain->ms.has_children;
245 }
246
247 if (has_children)
248 n += callchain_node__set_folding_rb_tree(node, unfold);
249
250 return n;
251}
252
253static int callchain__set_folding(struct rb_root *chain, bool unfold)
254{
255 struct rb_node *nd;
256 int n = 0;
257
258 for (nd = rb_first(chain); nd; nd = rb_next(nd)) {
259 struct callchain_node *node = rb_entry(nd, struct callchain_node, rb_node);
260 n += callchain_node__set_folding(node, unfold);
261 }
262
263 return n;
264}
265
266static void hist_entry__set_folding(struct hist_entry *self, bool unfold)
267{
268 hist_entry__init_have_children(self);
269 map_symbol__set_folding(&self->ms, unfold);
270
271 if (self->ms.has_children) {
272 int n = callchain__set_folding(&self->sorted_chain, unfold);
273 self->nr_rows = unfold ? n : 0;
274 } else
275 self->nr_rows = 0;
276}
277
278static void hists__set_folding(struct hists *self, bool unfold)
279{
280 struct rb_node *nd;
281
282 self->nr_entries = 0;
283
284 for (nd = rb_first(&self->entries); nd; nd = rb_next(nd)) {
285 struct hist_entry *he = rb_entry(nd, struct hist_entry, rb_node);
286 hist_entry__set_folding(he, unfold);
287 self->nr_entries += 1 + he->nr_rows;
288 }
289}
290
291static void hist_browser__set_folding(struct hist_browser *self, bool unfold)
292{
293 hists__set_folding(self->hists, unfold);
294 self->b.nr_entries = self->hists->nr_entries;
295 /* Go to the start, we may be way after valid entries after a collapse */
296 ui_browser__reset_index(&self->b);
297}
298
81cce8de
ACM
299static int hist_browser__run(struct hist_browser *self, const char *ev_name,
300 void(*timer)(void *arg), void *arg, int delay_secs)
d1b4f249 301{
b50e003d 302 int key;
81cce8de
ACM
303 int delay_msecs = delay_secs * 1000;
304 char title[160];
3c916cc2 305 int exit_keys[] = { 'a', '?', 'h', 'C', 'd', 'D', 'E', 't',
a03f35ce
ACM
306 NEWT_KEY_ENTER, NEWT_KEY_RIGHT, NEWT_KEY_LEFT,
307 NEWT_KEY_TAB, NEWT_KEY_UNTAB, 0, };
d1b4f249
ACM
308
309 self->b.entries = &self->hists->entries;
310 self->b.nr_entries = self->hists->nr_entries;
311
312 hist_browser__refresh_dimensions(self);
81cce8de
ACM
313 hists__browser_title(self->hists, title, sizeof(title), ev_name,
314 self->dso_filter, self->thread_filter);
d1b4f249 315
59e8fe32
ACM
316 if (ui_browser__show(&self->b, title,
317 "Press '?' for help on key bindings") < 0)
d1b4f249
ACM
318 return -1;
319
81cce8de
ACM
320 if (timer != NULL)
321 newtFormSetTimer(self->b.form, delay_msecs);
322
4c1c952e 323 ui_browser__add_exit_keys(&self->b, exit_keys);
d1b4f249
ACM
324
325 while (1) {
b50e003d 326 key = ui_browser__run(&self->b);
d1b4f249 327
b50e003d 328 switch (key) {
81cce8de
ACM
329 case -1:
330 /* FIXME we need to check if it was es.reason == NEWT_EXIT_TIMER */
331 timer(arg);
332 hists__browser_title(self->hists, title, sizeof(title),
333 ev_name, self->dso_filter,
334 self->thread_filter);
335 ui_browser__show_title(&self->b, title);
336 continue;
4694153c 337 case 'D': { /* Debug */
d1b4f249
ACM
338 static int seq;
339 struct hist_entry *h = rb_entry(self->b.top,
340 struct hist_entry, rb_node);
341 ui_helpline__pop();
342 ui_helpline__fpush("%d: nr_ent=(%d,%d), height=%d, idx=%d, fve: idx=%d, row_off=%d, nrows=%d",
343 seq++, self->b.nr_entries,
344 self->hists->nr_entries,
345 self->b.height,
346 self->b.index,
347 self->b.top_idx,
348 h->row_offset, h->nr_rows);
349 }
3c916cc2
ACM
350 break;
351 case 'C':
352 /* Collapse the whole world. */
353 hist_browser__set_folding(self, false);
354 break;
355 case 'E':
356 /* Expand the whole world. */
357 hist_browser__set_folding(self, true);
358 break;
d1b4f249
ACM
359 case NEWT_KEY_ENTER:
360 if (hist_browser__toggle_fold(self))
361 break;
362 /* fall thru */
363 default:
b50e003d 364 goto out;
d1b4f249
ACM
365 }
366 }
b50e003d 367out:
59e8fe32 368 ui_browser__hide(&self->b);
b50e003d 369 return key;
d1b4f249
ACM
370}
371
372static char *callchain_list__sym_name(struct callchain_list *self,
373 char *bf, size_t bfsize)
374{
375 if (self->ms.sym)
376 return self->ms.sym->name;
377
9486aa38 378 snprintf(bf, bfsize, "%#" PRIx64, self->ip);
d1b4f249
ACM
379 return bf;
380}
381
382#define LEVEL_OFFSET_STEP 3
383
384static int hist_browser__show_callchain_node_rb_tree(struct hist_browser *self,
385 struct callchain_node *chain_node,
386 u64 total, int level,
387 unsigned short row,
388 off_t *row_offset,
389 bool *is_current_entry)
390{
391 struct rb_node *node;
392 int first_row = row, width, offset = level * LEVEL_OFFSET_STEP;
393 u64 new_total, remaining;
394
395 if (callchain_param.mode == CHAIN_GRAPH_REL)
396 new_total = chain_node->children_hit;
397 else
398 new_total = total;
399
400 remaining = new_total;
401 node = rb_first(&chain_node->rb_root);
402 while (node) {
403 struct callchain_node *child = rb_entry(node, struct callchain_node, rb_node);
404 struct rb_node *next = rb_next(node);
f08c3154 405 u64 cumul = callchain_cumul_hits(child);
d1b4f249
ACM
406 struct callchain_list *chain;
407 char folded_sign = ' ';
408 int first = true;
409 int extra_offset = 0;
410
411 remaining -= cumul;
412
413 list_for_each_entry(chain, &child->val, list) {
414 char ipstr[BITS_PER_LONG / 4 + 1], *alloc_str;
415 const char *str;
416 int color;
417 bool was_first = first;
418
163caed9 419 if (first)
d1b4f249 420 first = false;
163caed9 421 else
d1b4f249 422 extra_offset = LEVEL_OFFSET_STEP;
d1b4f249
ACM
423
424 folded_sign = callchain_list__folded(chain);
425 if (*row_offset != 0) {
426 --*row_offset;
427 goto do_next;
428 }
429
430 alloc_str = NULL;
431 str = callchain_list__sym_name(chain, ipstr, sizeof(ipstr));
432 if (was_first) {
433 double percent = cumul * 100.0 / new_total;
434
435 if (asprintf(&alloc_str, "%2.2f%% %s", percent, str) < 0)
436 str = "Not enough memory!";
437 else
438 str = alloc_str;
439 }
440
441 color = HE_COLORSET_NORMAL;
442 width = self->b.width - (offset + extra_offset + 2);
443 if (ui_browser__is_current_entry(&self->b, row)) {
444 self->selection = &chain->ms;
445 color = HE_COLORSET_SELECTED;
446 *is_current_entry = true;
447 }
448
8f9bbc40
ACM
449 ui_browser__set_color(&self->b, color);
450 ui_browser__gotorc(&self->b, row, 0);
d1b4f249
ACM
451 slsmg_write_nstring(" ", offset + extra_offset);
452 slsmg_printf("%c ", folded_sign);
453 slsmg_write_nstring(str, width);
454 free(alloc_str);
455
456 if (++row == self->b.height)
457 goto out;
458do_next:
459 if (folded_sign == '+')
460 break;
461 }
462
463 if (folded_sign == '-') {
464 const int new_level = level + (extra_offset ? 2 : 1);
465 row += hist_browser__show_callchain_node_rb_tree(self, child, new_total,
466 new_level, row, row_offset,
467 is_current_entry);
468 }
469 if (row == self->b.height)
470 goto out;
471 node = next;
472 }
473out:
474 return row - first_row;
475}
476
477static int hist_browser__show_callchain_node(struct hist_browser *self,
478 struct callchain_node *node,
479 int level, unsigned short row,
480 off_t *row_offset,
481 bool *is_current_entry)
482{
483 struct callchain_list *chain;
484 int first_row = row,
485 offset = level * LEVEL_OFFSET_STEP,
486 width = self->b.width - offset;
487 char folded_sign = ' ';
488
489 list_for_each_entry(chain, &node->val, list) {
490 char ipstr[BITS_PER_LONG / 4 + 1], *s;
491 int color;
163caed9 492
d1b4f249
ACM
493 folded_sign = callchain_list__folded(chain);
494
495 if (*row_offset != 0) {
496 --*row_offset;
497 continue;
498 }
499
500 color = HE_COLORSET_NORMAL;
501 if (ui_browser__is_current_entry(&self->b, row)) {
502 self->selection = &chain->ms;
503 color = HE_COLORSET_SELECTED;
504 *is_current_entry = true;
505 }
506
507 s = callchain_list__sym_name(chain, ipstr, sizeof(ipstr));
8f9bbc40
ACM
508 ui_browser__gotorc(&self->b, row, 0);
509 ui_browser__set_color(&self->b, color);
d1b4f249
ACM
510 slsmg_write_nstring(" ", offset);
511 slsmg_printf("%c ", folded_sign);
512 slsmg_write_nstring(s, width - 2);
513
514 if (++row == self->b.height)
515 goto out;
516 }
517
518 if (folded_sign == '-')
519 row += hist_browser__show_callchain_node_rb_tree(self, node,
520 self->hists->stats.total_period,
521 level + 1, row,
522 row_offset,
523 is_current_entry);
524out:
525 return row - first_row;
526}
527
528static int hist_browser__show_callchain(struct hist_browser *self,
529 struct rb_root *chain,
530 int level, unsigned short row,
531 off_t *row_offset,
532 bool *is_current_entry)
533{
534 struct rb_node *nd;
535 int first_row = row;
536
537 for (nd = rb_first(chain); nd; nd = rb_next(nd)) {
538 struct callchain_node *node = rb_entry(nd, struct callchain_node, rb_node);
539
540 row += hist_browser__show_callchain_node(self, node, level,
541 row, row_offset,
542 is_current_entry);
543 if (row == self->b.height)
544 break;
545 }
546
547 return row - first_row;
548}
549
550static int hist_browser__show_entry(struct hist_browser *self,
551 struct hist_entry *entry,
552 unsigned short row)
553{
554 char s[256];
555 double percent;
556 int printed = 0;
557 int color, width = self->b.width;
558 char folded_sign = ' ';
559 bool current_entry = ui_browser__is_current_entry(&self->b, row);
560 off_t row_offset = entry->row_offset;
561
562 if (current_entry) {
563 self->he_selection = entry;
564 self->selection = &entry->ms;
565 }
566
567 if (symbol_conf.use_callchain) {
163caed9 568 hist_entry__init_have_children(entry);
d1b4f249
ACM
569 folded_sign = hist_entry__folded(entry);
570 }
571
572 if (row_offset == 0) {
573 hist_entry__snprintf(entry, s, sizeof(s), self->hists, NULL, false,
574 0, false, self->hists->stats.total_period);
575 percent = (entry->period * 100.0) / self->hists->stats.total_period;
576
577 color = HE_COLORSET_SELECTED;
578 if (!current_entry) {
579 if (percent >= MIN_RED)
580 color = HE_COLORSET_TOP;
581 else if (percent >= MIN_GREEN)
582 color = HE_COLORSET_MEDIUM;
583 else
584 color = HE_COLORSET_NORMAL;
585 }
586
8f9bbc40
ACM
587 ui_browser__set_color(&self->b, color);
588 ui_browser__gotorc(&self->b, row, 0);
d1b4f249
ACM
589 if (symbol_conf.use_callchain) {
590 slsmg_printf("%c ", folded_sign);
591 width -= 2;
592 }
593 slsmg_write_nstring(s, width);
594 ++row;
595 ++printed;
596 } else
597 --row_offset;
598
599 if (folded_sign == '-' && row != self->b.height) {
600 printed += hist_browser__show_callchain(self, &entry->sorted_chain,
601 1, row, &row_offset,
602 &current_entry);
603 if (current_entry)
604 self->he_selection = entry;
605 }
606
607 return printed;
608}
609
610static unsigned int hist_browser__refresh(struct ui_browser *self)
611{
612 unsigned row = 0;
613 struct rb_node *nd;
614 struct hist_browser *hb = container_of(self, struct hist_browser, b);
615
616 if (self->top == NULL)
617 self->top = rb_first(&hb->hists->entries);
618
619 for (nd = self->top; nd; nd = rb_next(nd)) {
620 struct hist_entry *h = rb_entry(nd, struct hist_entry, rb_node);
621
622 if (h->filtered)
623 continue;
624
625 row += hist_browser__show_entry(hb, h, row);
626 if (row == self->height)
627 break;
628 }
629
630 return row;
631}
632
633static struct rb_node *hists__filter_entries(struct rb_node *nd)
634{
635 while (nd != NULL) {
636 struct hist_entry *h = rb_entry(nd, struct hist_entry, rb_node);
637 if (!h->filtered)
638 return nd;
639
640 nd = rb_next(nd);
641 }
642
643 return NULL;
644}
645
646static struct rb_node *hists__filter_prev_entries(struct rb_node *nd)
647{
648 while (nd != NULL) {
649 struct hist_entry *h = rb_entry(nd, struct hist_entry, rb_node);
650 if (!h->filtered)
651 return nd;
652
653 nd = rb_prev(nd);
654 }
655
656 return NULL;
657}
658
659static void ui_browser__hists_seek(struct ui_browser *self,
660 off_t offset, int whence)
661{
662 struct hist_entry *h;
663 struct rb_node *nd;
664 bool first = true;
665
60098917
ACM
666 if (self->nr_entries == 0)
667 return;
668
d1b4f249
ACM
669 switch (whence) {
670 case SEEK_SET:
671 nd = hists__filter_entries(rb_first(self->entries));
672 break;
673 case SEEK_CUR:
674 nd = self->top;
675 goto do_offset;
676 case SEEK_END:
677 nd = hists__filter_prev_entries(rb_last(self->entries));
678 first = false;
679 break;
680 default:
681 return;
682 }
683
684 /*
685 * Moves not relative to the first visible entry invalidates its
686 * row_offset:
687 */
688 h = rb_entry(self->top, struct hist_entry, rb_node);
689 h->row_offset = 0;
690
691 /*
692 * Here we have to check if nd is expanded (+), if it is we can't go
693 * the next top level hist_entry, instead we must compute an offset of
694 * what _not_ to show and not change the first visible entry.
695 *
696 * This offset increments when we are going from top to bottom and
697 * decreases when we're going from bottom to top.
698 *
699 * As we don't have backpointers to the top level in the callchains
700 * structure, we need to always print the whole hist_entry callchain,
701 * skipping the first ones that are before the first visible entry
702 * and stop when we printed enough lines to fill the screen.
703 */
704do_offset:
705 if (offset > 0) {
706 do {
707 h = rb_entry(nd, struct hist_entry, rb_node);
708 if (h->ms.unfolded) {
709 u16 remaining = h->nr_rows - h->row_offset;
710 if (offset > remaining) {
711 offset -= remaining;
712 h->row_offset = 0;
713 } else {
714 h->row_offset += offset;
715 offset = 0;
716 self->top = nd;
717 break;
718 }
719 }
720 nd = hists__filter_entries(rb_next(nd));
721 if (nd == NULL)
722 break;
723 --offset;
724 self->top = nd;
725 } while (offset != 0);
726 } else if (offset < 0) {
727 while (1) {
728 h = rb_entry(nd, struct hist_entry, rb_node);
729 if (h->ms.unfolded) {
730 if (first) {
731 if (-offset > h->row_offset) {
732 offset += h->row_offset;
733 h->row_offset = 0;
734 } else {
735 h->row_offset += offset;
736 offset = 0;
737 self->top = nd;
738 break;
739 }
740 } else {
741 if (-offset > h->nr_rows) {
742 offset += h->nr_rows;
743 h->row_offset = 0;
744 } else {
745 h->row_offset = h->nr_rows + offset;
746 offset = 0;
747 self->top = nd;
748 break;
749 }
750 }
751 }
752
753 nd = hists__filter_prev_entries(rb_prev(nd));
754 if (nd == NULL)
755 break;
756 ++offset;
757 self->top = nd;
758 if (offset == 0) {
759 /*
760 * Last unfiltered hist_entry, check if it is
761 * unfolded, if it is then we should have
762 * row_offset at its last entry.
763 */
764 h = rb_entry(nd, struct hist_entry, rb_node);
765 if (h->ms.unfolded)
766 h->row_offset = h->nr_rows;
767 break;
768 }
769 first = false;
770 }
771 } else {
772 self->top = nd;
773 h = rb_entry(nd, struct hist_entry, rb_node);
774 h->row_offset = 0;
775 }
776}
777
778static struct hist_browser *hist_browser__new(struct hists *hists)
779{
780 struct hist_browser *self = zalloc(sizeof(*self));
781
782 if (self) {
783 self->hists = hists;
784 self->b.refresh = hist_browser__refresh;
785 self->b.seek = ui_browser__hists_seek;
786 }
787
788 return self;
789}
790
791static void hist_browser__delete(struct hist_browser *self)
792{
d1b4f249
ACM
793 free(self);
794}
795
796static struct hist_entry *hist_browser__selected_entry(struct hist_browser *self)
797{
798 return self->he_selection;
799}
800
801static struct thread *hist_browser__selected_thread(struct hist_browser *self)
802{
803 return self->he_selection->thread;
804}
805
469917ce
ACM
806static int hists__browser_title(struct hists *self, char *bf, size_t size,
807 const char *ev_name, const struct dso *dso,
808 const struct thread *thread)
d1b4f249 809{
469917ce
ACM
810 char unit;
811 int printed;
812 unsigned long nr_events = self->stats.nr_events[PERF_RECORD_SAMPLE];
813
814 nr_events = convert_unit(nr_events, &unit);
815 printed = snprintf(bf, size, "Events: %lu%c %s", nr_events, unit, ev_name);
d1b4f249
ACM
816
817 if (thread)
818 printed += snprintf(bf + printed, size - printed,
469917ce
ACM
819 ", Thread: %s(%d)",
820 (thread->comm_set ? thread->comm : ""),
d1b4f249
ACM
821 thread->pid);
822 if (dso)
823 printed += snprintf(bf + printed, size - printed,
469917ce
ACM
824 ", DSO: %s", dso->short_name);
825 return printed;
d1b4f249
ACM
826}
827
7f0030b2
ACM
828static int perf_evsel__hists_browse(struct perf_evsel *evsel,
829 const char *helpline, const char *ev_name,
81cce8de
ACM
830 bool left_exits,
831 void(*timer)(void *arg), void *arg,
832 int delay_secs)
d1b4f249 833{
7f0030b2 834 struct hists *self = &evsel->hists;
d1b4f249
ACM
835 struct hist_browser *browser = hist_browser__new(self);
836 struct pstack *fstack;
d1b4f249
ACM
837 int key = -1;
838
839 if (browser == NULL)
840 return -1;
841
842 fstack = pstack__new(2);
843 if (fstack == NULL)
844 goto out;
845
846 ui_helpline__push(helpline);
847
d1b4f249 848 while (1) {
60098917
ACM
849 const struct thread *thread = NULL;
850 const struct dso *dso = NULL;
d1b4f249
ACM
851 char *options[16];
852 int nr_options = 0, choice = 0, i,
853 annotate = -2, zoom_dso = -2, zoom_thread = -2,
854 browse_map = -2;
855
81cce8de 856 key = hist_browser__run(browser, ev_name, timer, arg, delay_secs);
d1b4f249 857
60098917
ACM
858 if (browser->he_selection != NULL) {
859 thread = hist_browser__selected_thread(browser);
860 dso = browser->selection->map ? browser->selection->map->dso : NULL;
861 }
d1b4f249 862
b50e003d 863 switch (key) {
b50e003d
ACM
864 case NEWT_KEY_TAB:
865 case NEWT_KEY_UNTAB:
866 /*
867 * Exit the browser, let hists__browser_tree
868 * go to the next or previous
869 */
870 goto out_free_stack;
871 case 'a':
60098917 872 if (browser->selection == NULL ||
db9a9cbc 873 browser->selection->sym == NULL ||
b50e003d 874 browser->selection->map->dso->annotate_warned)
d1b4f249 875 continue;
b50e003d
ACM
876 goto do_annotate;
877 case 'd':
878 goto zoom_dso;
879 case 't':
880 goto zoom_thread;
3c916cc2 881 case NEWT_KEY_F1:
b50e003d
ACM
882 case 'h':
883 case '?':
b50e003d
ACM
884 ui__help_window("-> Zoom into DSO/Threads & Annotate current symbol\n"
885 "<- Zoom out\n"
886 "a Annotate current symbol\n"
887 "h/?/F1 Show this window\n"
3c916cc2
ACM
888 "C Collapse all callchains\n"
889 "E Expand all callchains\n"
b50e003d
ACM
890 "d Zoom into current DSO\n"
891 "t Zoom into current Thread\n"
a03f35ce 892 "TAB/UNTAB Switch events\n"
b50e003d
ACM
893 "q/CTRL+C Exit browser");
894 continue;
895 case NEWT_KEY_ENTER:
896 case NEWT_KEY_RIGHT:
897 /* menu */
898 break;
899 case NEWT_KEY_LEFT: {
900 const void *top;
d1b4f249 901
7f0030b2
ACM
902 if (pstack__empty(fstack)) {
903 /*
904 * Go back to the perf_evsel_menu__run or other user
905 */
906 if (left_exits)
907 goto out_free_stack;
d1b4f249 908 continue;
7f0030b2 909 }
b50e003d 910 top = pstack__pop(fstack);
81cce8de 911 if (top == &browser->dso_filter)
b50e003d 912 goto zoom_out_dso;
81cce8de 913 if (top == &browser->thread_filter)
b50e003d
ACM
914 goto zoom_out_thread;
915 continue;
916 }
917 case NEWT_KEY_ESCAPE:
7f0030b2
ACM
918 if (!left_exits &&
919 !ui__dialog_yesno("Do you really want to exit?"))
b50e003d
ACM
920 continue;
921 /* Fall thru */
922 default:
923 goto out_free_stack;
d1b4f249
ACM
924 }
925
60098917
ACM
926 if (browser->selection != NULL &&
927 browser->selection->sym != NULL &&
d1b4f249
ACM
928 !browser->selection->map->dso->annotate_warned &&
929 asprintf(&options[nr_options], "Annotate %s",
930 browser->selection->sym->name) > 0)
931 annotate = nr_options++;
932
933 if (thread != NULL &&
934 asprintf(&options[nr_options], "Zoom %s %s(%d) thread",
81cce8de 935 (browser->thread_filter ? "out of" : "into"),
d1b4f249
ACM
936 (thread->comm_set ? thread->comm : ""),
937 thread->pid) > 0)
938 zoom_thread = nr_options++;
939
940 if (dso != NULL &&
941 asprintf(&options[nr_options], "Zoom %s %s DSO",
81cce8de 942 (browser->dso_filter ? "out of" : "into"),
d1b4f249
ACM
943 (dso->kernel ? "the Kernel" : dso->short_name)) > 0)
944 zoom_dso = nr_options++;
945
60098917
ACM
946 if (browser->selection != NULL &&
947 browser->selection->map != NULL &&
d1b4f249
ACM
948 asprintf(&options[nr_options], "Browse map details") > 0)
949 browse_map = nr_options++;
950
951 options[nr_options++] = (char *)"Exit";
952
1e6dd077 953 choice = ui__popup_menu(nr_options, options);
d1b4f249
ACM
954
955 for (i = 0; i < nr_options - 1; ++i)
956 free(options[i]);
957
958 if (choice == nr_options - 1)
959 break;
960
961 if (choice == -1)
962 continue;
963
964 if (choice == annotate) {
965 struct hist_entry *he;
966do_annotate:
d1b4f249
ACM
967 he = hist_browser__selected_entry(browser);
968 if (he == NULL)
969 continue;
970
81cce8de
ACM
971 hist_entry__tui_annotate(he, evsel->idx,
972 timer, arg, delay_secs);
d1b4f249
ACM
973 } else if (choice == browse_map)
974 map__browse(browser->selection->map);
975 else if (choice == zoom_dso) {
976zoom_dso:
81cce8de
ACM
977 if (browser->dso_filter) {
978 pstack__remove(fstack, &browser->dso_filter);
d1b4f249
ACM
979zoom_out_dso:
980 ui_helpline__pop();
81cce8de 981 browser->dso_filter = NULL;
d1b4f249
ACM
982 } else {
983 if (dso == NULL)
984 continue;
985 ui_helpline__fpush("To zoom out press <- or -> + \"Zoom out of %s DSO\"",
986 dso->kernel ? "the Kernel" : dso->short_name);
81cce8de
ACM
987 browser->dso_filter = dso;
988 pstack__push(fstack, &browser->dso_filter);
d1b4f249 989 }
81cce8de 990 hists__filter_by_dso(self, browser->dso_filter);
d1b4f249
ACM
991 hist_browser__reset(browser);
992 } else if (choice == zoom_thread) {
993zoom_thread:
81cce8de
ACM
994 if (browser->thread_filter) {
995 pstack__remove(fstack, &browser->thread_filter);
d1b4f249
ACM
996zoom_out_thread:
997 ui_helpline__pop();
81cce8de 998 browser->thread_filter = NULL;
d1b4f249
ACM
999 } else {
1000 ui_helpline__fpush("To zoom out press <- or -> + \"Zoom out of %s(%d) thread\"",
1001 thread->comm_set ? thread->comm : "",
1002 thread->pid);
81cce8de
ACM
1003 browser->thread_filter = thread;
1004 pstack__push(fstack, &browser->thread_filter);
d1b4f249 1005 }
81cce8de 1006 hists__filter_by_thread(self, browser->thread_filter);
d1b4f249
ACM
1007 hist_browser__reset(browser);
1008 }
1009 }
1010out_free_stack:
1011 pstack__delete(fstack);
1012out:
1013 hist_browser__delete(browser);
1014 return key;
1015}
1016
7f0030b2
ACM
1017struct perf_evsel_menu {
1018 struct ui_browser b;
1019 struct perf_evsel *selection;
1020};
1021
1022static void perf_evsel_menu__write(struct ui_browser *browser,
1023 void *entry, int row)
1024{
1025 struct perf_evsel_menu *menu = container_of(browser,
1026 struct perf_evsel_menu, b);
1027 struct perf_evsel *evsel = list_entry(entry, struct perf_evsel, node);
1028 bool current_entry = ui_browser__is_current_entry(browser, row);
1029 unsigned long nr_events = evsel->hists.stats.nr_events[PERF_RECORD_SAMPLE];
1030 const char *ev_name = event_name(evsel);
1031 char bf[256], unit;
1032
1033 ui_browser__set_color(browser, current_entry ? HE_COLORSET_SELECTED :
1034 HE_COLORSET_NORMAL);
1035
1036 nr_events = convert_unit(nr_events, &unit);
1037 snprintf(bf, sizeof(bf), "%lu%c%s%s", nr_events,
1038 unit, unit == ' ' ? "" : " ", ev_name);
1039 slsmg_write_nstring(bf, browser->width);
1040
1041 if (current_entry)
1042 menu->selection = evsel;
1043}
1044
81cce8de
ACM
1045static int perf_evsel_menu__run(struct perf_evsel_menu *menu, const char *help,
1046 void(*timer)(void *arg), void *arg, int delay_secs)
d1b4f249 1047{
7f0030b2 1048 int exit_keys[] = { NEWT_KEY_ENTER, NEWT_KEY_RIGHT, 0, };
81cce8de 1049 int delay_msecs = delay_secs * 1000;
7f0030b2 1050 struct perf_evlist *evlist = menu->b.priv;
e248de33 1051 struct perf_evsel *pos;
7f0030b2
ACM
1052 const char *ev_name, *title = "Available samples";
1053 int key;
d1b4f249 1054
7f0030b2
ACM
1055 if (ui_browser__show(&menu->b, title,
1056 "ESC: exit, ENTER|->: Browse histograms") < 0)
1057 return -1;
1058
81cce8de
ACM
1059 if (timer != NULL)
1060 newtFormSetTimer(menu->b.form, delay_msecs);
1061
7f0030b2
ACM
1062 ui_browser__add_exit_keys(&menu->b, exit_keys);
1063
1064 while (1) {
1065 key = ui_browser__run(&menu->b);
1066
1067 switch (key) {
81cce8de
ACM
1068 case -1:
1069 /* FIXME we need to check if it was es.reason == NEWT_EXIT_TIMER */
1070 timer(arg);
1071 continue;
7f0030b2
ACM
1072 case NEWT_KEY_RIGHT:
1073 case NEWT_KEY_ENTER:
1074 if (!menu->selection)
1075 continue;
1076 pos = menu->selection;
81cce8de 1077 perf_evlist__set_selected(evlist, pos);
7f0030b2
ACM
1078browse_hists:
1079 ev_name = event_name(pos);
81cce8de
ACM
1080 key = perf_evsel__hists_browse(pos, help, ev_name, true,
1081 timer, arg, delay_secs);
7f0030b2
ACM
1082 ui_browser__show_title(&menu->b, title);
1083 break;
1084 case NEWT_KEY_LEFT:
1085 continue;
1086 case NEWT_KEY_ESCAPE:
1087 if (!ui__dialog_yesno("Do you really want to exit?"))
1088 continue;
1089 /* Fall thru */
1090 default:
1091 goto out;
1092 }
d1b4f249 1093
d1b4f249
ACM
1094 switch (key) {
1095 case NEWT_KEY_TAB:
e248de33
ACM
1096 if (pos->node.next == &evlist->entries)
1097 pos = list_entry(evlist->entries.next, struct perf_evsel, node);
1098 else
1099 pos = list_entry(pos->node.next, struct perf_evsel, node);
7f0030b2 1100 goto browse_hists;
d1b4f249 1101 case NEWT_KEY_UNTAB:
e248de33
ACM
1102 if (pos->node.prev == &evlist->entries)
1103 pos = list_entry(evlist->entries.prev, struct perf_evsel, node);
1104 else
1105 pos = list_entry(pos->node.prev, struct perf_evsel, node);
7f0030b2
ACM
1106 goto browse_hists;
1107 case 'q':
1108 case CTRL('c'):
1109 goto out;
d1b4f249 1110 default:
7f0030b2 1111 break;
d1b4f249
ACM
1112 }
1113 }
1114
7f0030b2
ACM
1115out:
1116 ui_browser__hide(&menu->b);
1117 return key;
1118}
1119
1120static int __perf_evlist__tui_browse_hists(struct perf_evlist *evlist,
81cce8de
ACM
1121 const char *help,
1122 void(*timer)(void *arg), void *arg,
1123 int delay_secs)
7f0030b2
ACM
1124{
1125 struct perf_evsel *pos;
1126 struct perf_evsel_menu menu = {
1127 .b = {
1128 .entries = &evlist->entries,
1129 .refresh = ui_browser__list_head_refresh,
1130 .seek = ui_browser__list_head_seek,
1131 .write = perf_evsel_menu__write,
1132 .nr_entries = evlist->nr_entries,
1133 .priv = evlist,
1134 },
1135 };
1136
1137 ui_helpline__push("Press ESC to exit");
1138
1139 list_for_each_entry(pos, &evlist->entries, node) {
1140 const char *ev_name = event_name(pos);
1141 size_t line_len = strlen(ev_name) + 7;
1142
1143 if (menu.b.width < line_len)
1144 menu.b.width = line_len;
1145 /*
1146 * Cache the evsel name, tracepoints have a _high_ cost per
1147 * event_name() call.
1148 */
1149 if (pos->name == NULL)
1150 pos->name = strdup(ev_name);
1151 }
1152
81cce8de 1153 return perf_evsel_menu__run(&menu, help, timer, arg, delay_secs);
7f0030b2
ACM
1154}
1155
81cce8de
ACM
1156int perf_evlist__tui_browse_hists(struct perf_evlist *evlist, const char *help,
1157 void(*timer)(void *arg), void *arg,
1158 int delay_secs)
7f0030b2
ACM
1159{
1160
1161 if (evlist->nr_entries == 1) {
1162 struct perf_evsel *first = list_entry(evlist->entries.next,
1163 struct perf_evsel, node);
1164 const char *ev_name = event_name(first);
81cce8de
ACM
1165 return perf_evsel__hists_browse(first, help, ev_name, false,
1166 timer, arg, delay_secs);
7f0030b2
ACM
1167 }
1168
81cce8de
ACM
1169 return __perf_evlist__tui_browse_hists(evlist, help,
1170 timer, arg, delay_secs);
d1b4f249 1171}
This page took 0.123447 seconds and 5 git commands to generate.