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