]> git.proxmox.com Git - mirror_ubuntu-bionic-kernel.git/blob - tools/perf/ui/browsers/hists.c
perf hists: Rename and move some functions
[mirror_ubuntu-bionic-kernel.git] / tools / perf / ui / browsers / hists.c
1 #include <stdio.h>
2 #include "../libslang.h"
3 #include <stdlib.h>
4 #include <string.h>
5 #include <newt.h>
6 #include <linux/rbtree.h>
7
8 #include "../../util/evsel.h"
9 #include "../../util/evlist.h"
10 #include "../../util/hist.h"
11 #include "../../util/pstack.h"
12 #include "../../util/sort.h"
13 #include "../../util/util.h"
14
15 #include "../browser.h"
16 #include "../helpline.h"
17 #include "../util.h"
18 #include "../ui.h"
19 #include "map.h"
20
21 struct hist_browser {
22 struct ui_browser b;
23 struct hists *hists;
24 struct hist_entry *he_selection;
25 struct map_symbol *selection;
26 int print_seq;
27 bool show_dso;
28 bool has_symbols;
29 };
30
31 static int hists__browser_title(struct hists *hists, char *bf, size_t size,
32 const char *ev_name);
33
34 static void hist_browser__refresh_dimensions(struct hist_browser *browser)
35 {
36 /* 3 == +/- toggle symbol before actual hist_entry rendering */
37 browser->b.width = 3 + (hists__sort_list_width(browser->hists) +
38 sizeof("[k]"));
39 }
40
41 static void hist_browser__reset(struct hist_browser *browser)
42 {
43 browser->b.nr_entries = browser->hists->nr_entries;
44 hist_browser__refresh_dimensions(browser);
45 ui_browser__reset_index(&browser->b);
46 }
47
48 static char tree__folded_sign(bool unfolded)
49 {
50 return unfolded ? '-' : '+';
51 }
52
53 static char map_symbol__folded(const struct map_symbol *ms)
54 {
55 return ms->has_children ? tree__folded_sign(ms->unfolded) : ' ';
56 }
57
58 static char hist_entry__folded(const struct hist_entry *he)
59 {
60 return map_symbol__folded(&he->ms);
61 }
62
63 static char callchain_list__folded(const struct callchain_list *cl)
64 {
65 return map_symbol__folded(&cl->ms);
66 }
67
68 static void map_symbol__set_folding(struct map_symbol *ms, bool unfold)
69 {
70 ms->unfolded = unfold ? ms->has_children : false;
71 }
72
73 static int callchain_node__count_rows_rb_tree(struct callchain_node *node)
74 {
75 int n = 0;
76 struct rb_node *nd;
77
78 for (nd = rb_first(&node->rb_root); nd; nd = rb_next(nd)) {
79 struct callchain_node *child = rb_entry(nd, struct callchain_node, rb_node);
80 struct callchain_list *chain;
81 char folded_sign = ' '; /* No children */
82
83 list_for_each_entry(chain, &child->val, list) {
84 ++n;
85 /* We need this because we may not have children */
86 folded_sign = callchain_list__folded(chain);
87 if (folded_sign == '+')
88 break;
89 }
90
91 if (folded_sign == '-') /* Have children and they're unfolded */
92 n += callchain_node__count_rows_rb_tree(child);
93 }
94
95 return n;
96 }
97
98 static int callchain_node__count_rows(struct callchain_node *node)
99 {
100 struct callchain_list *chain;
101 bool unfolded = false;
102 int n = 0;
103
104 list_for_each_entry(chain, &node->val, list) {
105 ++n;
106 unfolded = chain->ms.unfolded;
107 }
108
109 if (unfolded)
110 n += callchain_node__count_rows_rb_tree(node);
111
112 return n;
113 }
114
115 static int callchain__count_rows(struct rb_root *chain)
116 {
117 struct rb_node *nd;
118 int n = 0;
119
120 for (nd = rb_first(chain); nd; nd = rb_next(nd)) {
121 struct callchain_node *node = rb_entry(nd, struct callchain_node, rb_node);
122 n += callchain_node__count_rows(node);
123 }
124
125 return n;
126 }
127
128 static bool map_symbol__toggle_fold(struct map_symbol *ms)
129 {
130 if (!ms)
131 return false;
132
133 if (!ms->has_children)
134 return false;
135
136 ms->unfolded = !ms->unfolded;
137 return true;
138 }
139
140 static void callchain_node__init_have_children_rb_tree(struct callchain_node *node)
141 {
142 struct rb_node *nd = rb_first(&node->rb_root);
143
144 for (nd = rb_first(&node->rb_root); nd; nd = rb_next(nd)) {
145 struct callchain_node *child = rb_entry(nd, struct callchain_node, rb_node);
146 struct callchain_list *chain;
147 bool first = true;
148
149 list_for_each_entry(chain, &child->val, list) {
150 if (first) {
151 first = false;
152 chain->ms.has_children = chain->list.next != &child->val ||
153 !RB_EMPTY_ROOT(&child->rb_root);
154 } else
155 chain->ms.has_children = chain->list.next == &child->val &&
156 !RB_EMPTY_ROOT(&child->rb_root);
157 }
158
159 callchain_node__init_have_children_rb_tree(child);
160 }
161 }
162
163 static void callchain_node__init_have_children(struct callchain_node *node)
164 {
165 struct callchain_list *chain;
166
167 list_for_each_entry(chain, &node->val, list)
168 chain->ms.has_children = !RB_EMPTY_ROOT(&node->rb_root);
169
170 callchain_node__init_have_children_rb_tree(node);
171 }
172
173 static void callchain__init_have_children(struct rb_root *root)
174 {
175 struct rb_node *nd;
176
177 for (nd = rb_first(root); nd; nd = rb_next(nd)) {
178 struct callchain_node *node = rb_entry(nd, struct callchain_node, rb_node);
179 callchain_node__init_have_children(node);
180 }
181 }
182
183 static void hist_entry__init_have_children(struct hist_entry *he)
184 {
185 if (!he->init_have_children) {
186 he->ms.has_children = !RB_EMPTY_ROOT(&he->sorted_chain);
187 callchain__init_have_children(&he->sorted_chain);
188 he->init_have_children = true;
189 }
190 }
191
192 static bool hist_browser__toggle_fold(struct hist_browser *browser)
193 {
194 if (map_symbol__toggle_fold(browser->selection)) {
195 struct hist_entry *he = browser->he_selection;
196
197 hist_entry__init_have_children(he);
198 browser->hists->nr_entries -= he->nr_rows;
199
200 if (he->ms.unfolded)
201 he->nr_rows = callchain__count_rows(&he->sorted_chain);
202 else
203 he->nr_rows = 0;
204 browser->hists->nr_entries += he->nr_rows;
205 browser->b.nr_entries = browser->hists->nr_entries;
206
207 return true;
208 }
209
210 /* If it doesn't have children, no toggling performed */
211 return false;
212 }
213
214 static int callchain_node__set_folding_rb_tree(struct callchain_node *node, bool unfold)
215 {
216 int n = 0;
217 struct rb_node *nd;
218
219 for (nd = rb_first(&node->rb_root); nd; nd = rb_next(nd)) {
220 struct callchain_node *child = rb_entry(nd, struct callchain_node, rb_node);
221 struct callchain_list *chain;
222 bool has_children = false;
223
224 list_for_each_entry(chain, &child->val, list) {
225 ++n;
226 map_symbol__set_folding(&chain->ms, unfold);
227 has_children = chain->ms.has_children;
228 }
229
230 if (has_children)
231 n += callchain_node__set_folding_rb_tree(child, unfold);
232 }
233
234 return n;
235 }
236
237 static int callchain_node__set_folding(struct callchain_node *node, bool unfold)
238 {
239 struct callchain_list *chain;
240 bool has_children = false;
241 int n = 0;
242
243 list_for_each_entry(chain, &node->val, list) {
244 ++n;
245 map_symbol__set_folding(&chain->ms, unfold);
246 has_children = chain->ms.has_children;
247 }
248
249 if (has_children)
250 n += callchain_node__set_folding_rb_tree(node, unfold);
251
252 return n;
253 }
254
255 static int callchain__set_folding(struct rb_root *chain, bool unfold)
256 {
257 struct rb_node *nd;
258 int n = 0;
259
260 for (nd = rb_first(chain); nd; nd = rb_next(nd)) {
261 struct callchain_node *node = rb_entry(nd, struct callchain_node, rb_node);
262 n += callchain_node__set_folding(node, unfold);
263 }
264
265 return n;
266 }
267
268 static void hist_entry__set_folding(struct hist_entry *he, bool unfold)
269 {
270 hist_entry__init_have_children(he);
271 map_symbol__set_folding(&he->ms, unfold);
272
273 if (he->ms.has_children) {
274 int n = callchain__set_folding(&he->sorted_chain, unfold);
275 he->nr_rows = unfold ? n : 0;
276 } else
277 he->nr_rows = 0;
278 }
279
280 static void hists__set_folding(struct hists *hists, bool unfold)
281 {
282 struct rb_node *nd;
283
284 hists->nr_entries = 0;
285
286 for (nd = rb_first(&hists->entries); nd; nd = rb_next(nd)) {
287 struct hist_entry *he = rb_entry(nd, struct hist_entry, rb_node);
288 hist_entry__set_folding(he, unfold);
289 hists->nr_entries += 1 + he->nr_rows;
290 }
291 }
292
293 static void hist_browser__set_folding(struct hist_browser *browser, bool unfold)
294 {
295 hists__set_folding(browser->hists, unfold);
296 browser->b.nr_entries = browser->hists->nr_entries;
297 /* Go to the start, we may be way after valid entries after a collapse */
298 ui_browser__reset_index(&browser->b);
299 }
300
301 static void ui_browser__warn_lost_events(struct ui_browser *browser)
302 {
303 ui_browser__warning(browser, 4,
304 "Events are being lost, check IO/CPU overload!\n\n"
305 "You may want to run 'perf' using a RT scheduler policy:\n\n"
306 " perf top -r 80\n\n"
307 "Or reduce the sampling frequency.");
308 }
309
310 static int hist_browser__run(struct hist_browser *browser, const char *ev_name,
311 void(*timer)(void *arg), void *arg, int delay_secs)
312 {
313 int key;
314 char title[160];
315
316 browser->b.entries = &browser->hists->entries;
317 browser->b.nr_entries = browser->hists->nr_entries;
318
319 hist_browser__refresh_dimensions(browser);
320 hists__browser_title(browser->hists, title, sizeof(title), ev_name);
321
322 if (ui_browser__show(&browser->b, title,
323 "Press '?' for help on key bindings") < 0)
324 return -1;
325
326 while (1) {
327 key = ui_browser__run(&browser->b, delay_secs);
328
329 switch (key) {
330 case K_TIMER:
331 timer(arg);
332 ui_browser__update_nr_entries(&browser->b, browser->hists->nr_entries);
333
334 if (browser->hists->stats.nr_lost_warned !=
335 browser->hists->stats.nr_events[PERF_RECORD_LOST]) {
336 browser->hists->stats.nr_lost_warned =
337 browser->hists->stats.nr_events[PERF_RECORD_LOST];
338 ui_browser__warn_lost_events(&browser->b);
339 }
340
341 hists__browser_title(browser->hists, title, sizeof(title), ev_name);
342 ui_browser__show_title(&browser->b, title);
343 continue;
344 case 'D': { /* Debug */
345 static int seq;
346 struct hist_entry *h = rb_entry(browser->b.top,
347 struct hist_entry, rb_node);
348 ui_helpline__pop();
349 ui_helpline__fpush("%d: nr_ent=(%d,%d), height=%d, idx=%d, fve: idx=%d, row_off=%d, nrows=%d",
350 seq++, browser->b.nr_entries,
351 browser->hists->nr_entries,
352 browser->b.height,
353 browser->b.index,
354 browser->b.top_idx,
355 h->row_offset, h->nr_rows);
356 }
357 break;
358 case 'C':
359 /* Collapse the whole world. */
360 hist_browser__set_folding(browser, false);
361 break;
362 case 'E':
363 /* Expand the whole world. */
364 hist_browser__set_folding(browser, true);
365 break;
366 case K_ENTER:
367 if (hist_browser__toggle_fold(browser))
368 break;
369 /* fall thru */
370 default:
371 goto out;
372 }
373 }
374 out:
375 ui_browser__hide(&browser->b);
376 return key;
377 }
378
379 static char *callchain_list__sym_name(struct callchain_list *cl,
380 char *bf, size_t bfsize, bool show_dso)
381 {
382 int printed;
383
384 if (cl->ms.sym)
385 printed = scnprintf(bf, bfsize, "%s", cl->ms.sym->name);
386 else
387 printed = scnprintf(bf, bfsize, "%#" PRIx64, cl->ip);
388
389 if (show_dso)
390 scnprintf(bf + printed, bfsize - printed, " %s",
391 cl->ms.map ? cl->ms.map->dso->short_name : "unknown");
392
393 return bf;
394 }
395
396 #define LEVEL_OFFSET_STEP 3
397
398 static int hist_browser__show_callchain_node_rb_tree(struct hist_browser *browser,
399 struct callchain_node *chain_node,
400 u64 total, int level,
401 unsigned short row,
402 off_t *row_offset,
403 bool *is_current_entry)
404 {
405 struct rb_node *node;
406 int first_row = row, width, offset = level * LEVEL_OFFSET_STEP;
407 u64 new_total, remaining;
408
409 if (callchain_param.mode == CHAIN_GRAPH_REL)
410 new_total = chain_node->children_hit;
411 else
412 new_total = total;
413
414 remaining = new_total;
415 node = rb_first(&chain_node->rb_root);
416 while (node) {
417 struct callchain_node *child = rb_entry(node, struct callchain_node, rb_node);
418 struct rb_node *next = rb_next(node);
419 u64 cumul = callchain_cumul_hits(child);
420 struct callchain_list *chain;
421 char folded_sign = ' ';
422 int first = true;
423 int extra_offset = 0;
424
425 remaining -= cumul;
426
427 list_for_each_entry(chain, &child->val, list) {
428 char bf[1024], *alloc_str;
429 const char *str;
430 int color;
431 bool was_first = first;
432
433 if (first)
434 first = false;
435 else
436 extra_offset = LEVEL_OFFSET_STEP;
437
438 folded_sign = callchain_list__folded(chain);
439 if (*row_offset != 0) {
440 --*row_offset;
441 goto do_next;
442 }
443
444 alloc_str = NULL;
445 str = callchain_list__sym_name(chain, bf, sizeof(bf),
446 browser->show_dso);
447 if (was_first) {
448 double percent = cumul * 100.0 / new_total;
449
450 if (asprintf(&alloc_str, "%2.2f%% %s", percent, str) < 0)
451 str = "Not enough memory!";
452 else
453 str = alloc_str;
454 }
455
456 color = HE_COLORSET_NORMAL;
457 width = browser->b.width - (offset + extra_offset + 2);
458 if (ui_browser__is_current_entry(&browser->b, row)) {
459 browser->selection = &chain->ms;
460 color = HE_COLORSET_SELECTED;
461 *is_current_entry = true;
462 }
463
464 ui_browser__set_color(&browser->b, color);
465 ui_browser__gotorc(&browser->b, row, 0);
466 slsmg_write_nstring(" ", offset + extra_offset);
467 slsmg_printf("%c ", folded_sign);
468 slsmg_write_nstring(str, width);
469 free(alloc_str);
470
471 if (++row == browser->b.height)
472 goto out;
473 do_next:
474 if (folded_sign == '+')
475 break;
476 }
477
478 if (folded_sign == '-') {
479 const int new_level = level + (extra_offset ? 2 : 1);
480 row += hist_browser__show_callchain_node_rb_tree(browser, child, new_total,
481 new_level, row, row_offset,
482 is_current_entry);
483 }
484 if (row == browser->b.height)
485 goto out;
486 node = next;
487 }
488 out:
489 return row - first_row;
490 }
491
492 static int hist_browser__show_callchain_node(struct hist_browser *browser,
493 struct callchain_node *node,
494 int level, unsigned short row,
495 off_t *row_offset,
496 bool *is_current_entry)
497 {
498 struct callchain_list *chain;
499 int first_row = row,
500 offset = level * LEVEL_OFFSET_STEP,
501 width = browser->b.width - offset;
502 char folded_sign = ' ';
503
504 list_for_each_entry(chain, &node->val, list) {
505 char bf[1024], *s;
506 int color;
507
508 folded_sign = callchain_list__folded(chain);
509
510 if (*row_offset != 0) {
511 --*row_offset;
512 continue;
513 }
514
515 color = HE_COLORSET_NORMAL;
516 if (ui_browser__is_current_entry(&browser->b, row)) {
517 browser->selection = &chain->ms;
518 color = HE_COLORSET_SELECTED;
519 *is_current_entry = true;
520 }
521
522 s = callchain_list__sym_name(chain, bf, sizeof(bf),
523 browser->show_dso);
524 ui_browser__gotorc(&browser->b, row, 0);
525 ui_browser__set_color(&browser->b, color);
526 slsmg_write_nstring(" ", offset);
527 slsmg_printf("%c ", folded_sign);
528 slsmg_write_nstring(s, width - 2);
529
530 if (++row == browser->b.height)
531 goto out;
532 }
533
534 if (folded_sign == '-')
535 row += hist_browser__show_callchain_node_rb_tree(browser, node,
536 browser->hists->stats.total_period,
537 level + 1, row,
538 row_offset,
539 is_current_entry);
540 out:
541 return row - first_row;
542 }
543
544 static int hist_browser__show_callchain(struct hist_browser *browser,
545 struct rb_root *chain,
546 int level, unsigned short row,
547 off_t *row_offset,
548 bool *is_current_entry)
549 {
550 struct rb_node *nd;
551 int first_row = row;
552
553 for (nd = rb_first(chain); nd; nd = rb_next(nd)) {
554 struct callchain_node *node = rb_entry(nd, struct callchain_node, rb_node);
555
556 row += hist_browser__show_callchain_node(browser, node, level,
557 row, row_offset,
558 is_current_entry);
559 if (row == browser->b.height)
560 break;
561 }
562
563 return row - first_row;
564 }
565
566 static int hist_browser__show_entry(struct hist_browser *browser,
567 struct hist_entry *entry,
568 unsigned short row)
569 {
570 char s[256];
571 double percent;
572 int printed = 0;
573 int width = browser->b.width - 6; /* The percentage */
574 char folded_sign = ' ';
575 bool current_entry = ui_browser__is_current_entry(&browser->b, row);
576 off_t row_offset = entry->row_offset;
577
578 if (current_entry) {
579 browser->he_selection = entry;
580 browser->selection = &entry->ms;
581 }
582
583 if (symbol_conf.use_callchain) {
584 hist_entry__init_have_children(entry);
585 folded_sign = hist_entry__folded(entry);
586 }
587
588 if (row_offset == 0) {
589 hist_entry__sort_snprintf(entry, s, sizeof(s), browser->hists);
590 percent = (entry->period * 100.0) / browser->hists->stats.total_period;
591
592 ui_browser__set_percent_color(&browser->b, percent, current_entry);
593 ui_browser__gotorc(&browser->b, row, 0);
594 if (symbol_conf.use_callchain) {
595 slsmg_printf("%c ", folded_sign);
596 width -= 2;
597 }
598
599 slsmg_printf(" %5.2f%%", percent);
600
601 /* The scroll bar isn't being used */
602 if (!browser->b.navkeypressed)
603 width += 1;
604
605 if (!current_entry || !browser->b.navkeypressed)
606 ui_browser__set_color(&browser->b, HE_COLORSET_NORMAL);
607
608 if (symbol_conf.show_nr_samples) {
609 slsmg_printf(" %11u", entry->nr_events);
610 width -= 12;
611 }
612
613 if (symbol_conf.show_total_period) {
614 slsmg_printf(" %12" PRIu64, entry->period);
615 width -= 13;
616 }
617
618 slsmg_write_nstring(s, width);
619 ++row;
620 ++printed;
621 } else
622 --row_offset;
623
624 if (folded_sign == '-' && row != browser->b.height) {
625 printed += hist_browser__show_callchain(browser, &entry->sorted_chain,
626 1, row, &row_offset,
627 &current_entry);
628 if (current_entry)
629 browser->he_selection = entry;
630 }
631
632 return printed;
633 }
634
635 static void ui_browser__hists_init_top(struct ui_browser *browser)
636 {
637 if (browser->top == NULL) {
638 struct hist_browser *hb;
639
640 hb = container_of(browser, struct hist_browser, b);
641 browser->top = rb_first(&hb->hists->entries);
642 }
643 }
644
645 static unsigned int hist_browser__refresh(struct ui_browser *browser)
646 {
647 unsigned row = 0;
648 struct rb_node *nd;
649 struct hist_browser *hb = container_of(browser, struct hist_browser, b);
650
651 ui_browser__hists_init_top(browser);
652
653 for (nd = browser->top; nd; nd = rb_next(nd)) {
654 struct hist_entry *h = rb_entry(nd, struct hist_entry, rb_node);
655
656 if (h->filtered)
657 continue;
658
659 row += hist_browser__show_entry(hb, h, row);
660 if (row == browser->height)
661 break;
662 }
663
664 return row;
665 }
666
667 static struct rb_node *hists__filter_entries(struct rb_node *nd)
668 {
669 while (nd != NULL) {
670 struct hist_entry *h = rb_entry(nd, struct hist_entry, rb_node);
671 if (!h->filtered)
672 return nd;
673
674 nd = rb_next(nd);
675 }
676
677 return NULL;
678 }
679
680 static struct rb_node *hists__filter_prev_entries(struct rb_node *nd)
681 {
682 while (nd != NULL) {
683 struct hist_entry *h = rb_entry(nd, struct hist_entry, rb_node);
684 if (!h->filtered)
685 return nd;
686
687 nd = rb_prev(nd);
688 }
689
690 return NULL;
691 }
692
693 static void ui_browser__hists_seek(struct ui_browser *browser,
694 off_t offset, int whence)
695 {
696 struct hist_entry *h;
697 struct rb_node *nd;
698 bool first = true;
699
700 if (browser->nr_entries == 0)
701 return;
702
703 ui_browser__hists_init_top(browser);
704
705 switch (whence) {
706 case SEEK_SET:
707 nd = hists__filter_entries(rb_first(browser->entries));
708 break;
709 case SEEK_CUR:
710 nd = browser->top;
711 goto do_offset;
712 case SEEK_END:
713 nd = hists__filter_prev_entries(rb_last(browser->entries));
714 first = false;
715 break;
716 default:
717 return;
718 }
719
720 /*
721 * Moves not relative to the first visible entry invalidates its
722 * row_offset:
723 */
724 h = rb_entry(browser->top, struct hist_entry, rb_node);
725 h->row_offset = 0;
726
727 /*
728 * Here we have to check if nd is expanded (+), if it is we can't go
729 * the next top level hist_entry, instead we must compute an offset of
730 * what _not_ to show and not change the first visible entry.
731 *
732 * This offset increments when we are going from top to bottom and
733 * decreases when we're going from bottom to top.
734 *
735 * As we don't have backpointers to the top level in the callchains
736 * structure, we need to always print the whole hist_entry callchain,
737 * skipping the first ones that are before the first visible entry
738 * and stop when we printed enough lines to fill the screen.
739 */
740 do_offset:
741 if (offset > 0) {
742 do {
743 h = rb_entry(nd, struct hist_entry, rb_node);
744 if (h->ms.unfolded) {
745 u16 remaining = h->nr_rows - h->row_offset;
746 if (offset > remaining) {
747 offset -= remaining;
748 h->row_offset = 0;
749 } else {
750 h->row_offset += offset;
751 offset = 0;
752 browser->top = nd;
753 break;
754 }
755 }
756 nd = hists__filter_entries(rb_next(nd));
757 if (nd == NULL)
758 break;
759 --offset;
760 browser->top = nd;
761 } while (offset != 0);
762 } else if (offset < 0) {
763 while (1) {
764 h = rb_entry(nd, struct hist_entry, rb_node);
765 if (h->ms.unfolded) {
766 if (first) {
767 if (-offset > h->row_offset) {
768 offset += h->row_offset;
769 h->row_offset = 0;
770 } else {
771 h->row_offset += offset;
772 offset = 0;
773 browser->top = nd;
774 break;
775 }
776 } else {
777 if (-offset > h->nr_rows) {
778 offset += h->nr_rows;
779 h->row_offset = 0;
780 } else {
781 h->row_offset = h->nr_rows + offset;
782 offset = 0;
783 browser->top = nd;
784 break;
785 }
786 }
787 }
788
789 nd = hists__filter_prev_entries(rb_prev(nd));
790 if (nd == NULL)
791 break;
792 ++offset;
793 browser->top = nd;
794 if (offset == 0) {
795 /*
796 * Last unfiltered hist_entry, check if it is
797 * unfolded, if it is then we should have
798 * row_offset at its last entry.
799 */
800 h = rb_entry(nd, struct hist_entry, rb_node);
801 if (h->ms.unfolded)
802 h->row_offset = h->nr_rows;
803 break;
804 }
805 first = false;
806 }
807 } else {
808 browser->top = nd;
809 h = rb_entry(nd, struct hist_entry, rb_node);
810 h->row_offset = 0;
811 }
812 }
813
814 static int hist_browser__fprintf_callchain_node_rb_tree(struct hist_browser *browser,
815 struct callchain_node *chain_node,
816 u64 total, int level,
817 FILE *fp)
818 {
819 struct rb_node *node;
820 int offset = level * LEVEL_OFFSET_STEP;
821 u64 new_total, remaining;
822 int printed = 0;
823
824 if (callchain_param.mode == CHAIN_GRAPH_REL)
825 new_total = chain_node->children_hit;
826 else
827 new_total = total;
828
829 remaining = new_total;
830 node = rb_first(&chain_node->rb_root);
831 while (node) {
832 struct callchain_node *child = rb_entry(node, struct callchain_node, rb_node);
833 struct rb_node *next = rb_next(node);
834 u64 cumul = callchain_cumul_hits(child);
835 struct callchain_list *chain;
836 char folded_sign = ' ';
837 int first = true;
838 int extra_offset = 0;
839
840 remaining -= cumul;
841
842 list_for_each_entry(chain, &child->val, list) {
843 char bf[1024], *alloc_str;
844 const char *str;
845 bool was_first = first;
846
847 if (first)
848 first = false;
849 else
850 extra_offset = LEVEL_OFFSET_STEP;
851
852 folded_sign = callchain_list__folded(chain);
853
854 alloc_str = NULL;
855 str = callchain_list__sym_name(chain, bf, sizeof(bf),
856 browser->show_dso);
857 if (was_first) {
858 double percent = cumul * 100.0 / new_total;
859
860 if (asprintf(&alloc_str, "%2.2f%% %s", percent, str) < 0)
861 str = "Not enough memory!";
862 else
863 str = alloc_str;
864 }
865
866 printed += fprintf(fp, "%*s%c %s\n", offset + extra_offset, " ", folded_sign, str);
867 free(alloc_str);
868 if (folded_sign == '+')
869 break;
870 }
871
872 if (folded_sign == '-') {
873 const int new_level = level + (extra_offset ? 2 : 1);
874 printed += hist_browser__fprintf_callchain_node_rb_tree(browser, child, new_total,
875 new_level, fp);
876 }
877
878 node = next;
879 }
880
881 return printed;
882 }
883
884 static int hist_browser__fprintf_callchain_node(struct hist_browser *browser,
885 struct callchain_node *node,
886 int level, FILE *fp)
887 {
888 struct callchain_list *chain;
889 int offset = level * LEVEL_OFFSET_STEP;
890 char folded_sign = ' ';
891 int printed = 0;
892
893 list_for_each_entry(chain, &node->val, list) {
894 char bf[1024], *s;
895
896 folded_sign = callchain_list__folded(chain);
897 s = callchain_list__sym_name(chain, bf, sizeof(bf), browser->show_dso);
898 printed += fprintf(fp, "%*s%c %s\n", offset, " ", folded_sign, s);
899 }
900
901 if (folded_sign == '-')
902 printed += hist_browser__fprintf_callchain_node_rb_tree(browser, node,
903 browser->hists->stats.total_period,
904 level + 1, fp);
905 return printed;
906 }
907
908 static int hist_browser__fprintf_callchain(struct hist_browser *browser,
909 struct rb_root *chain, int level, FILE *fp)
910 {
911 struct rb_node *nd;
912 int printed = 0;
913
914 for (nd = rb_first(chain); nd; nd = rb_next(nd)) {
915 struct callchain_node *node = rb_entry(nd, struct callchain_node, rb_node);
916
917 printed += hist_browser__fprintf_callchain_node(browser, node, level, fp);
918 }
919
920 return printed;
921 }
922
923 static int hist_browser__fprintf_entry(struct hist_browser *browser,
924 struct hist_entry *he, FILE *fp)
925 {
926 char s[8192];
927 double percent;
928 int printed = 0;
929 char folded_sign = ' ';
930
931 if (symbol_conf.use_callchain)
932 folded_sign = hist_entry__folded(he);
933
934 hist_entry__sort_snprintf(he, s, sizeof(s), browser->hists);
935 percent = (he->period * 100.0) / browser->hists->stats.total_period;
936
937 if (symbol_conf.use_callchain)
938 printed += fprintf(fp, "%c ", folded_sign);
939
940 printed += fprintf(fp, " %5.2f%%", percent);
941
942 if (symbol_conf.show_nr_samples)
943 printed += fprintf(fp, " %11u", he->nr_events);
944
945 if (symbol_conf.show_total_period)
946 printed += fprintf(fp, " %12" PRIu64, he->period);
947
948 printed += fprintf(fp, "%s\n", rtrim(s));
949
950 if (folded_sign == '-')
951 printed += hist_browser__fprintf_callchain(browser, &he->sorted_chain, 1, fp);
952
953 return printed;
954 }
955
956 static int hist_browser__fprintf(struct hist_browser *browser, FILE *fp)
957 {
958 struct rb_node *nd = hists__filter_entries(rb_first(browser->b.entries));
959 int printed = 0;
960
961 while (nd) {
962 struct hist_entry *h = rb_entry(nd, struct hist_entry, rb_node);
963
964 printed += hist_browser__fprintf_entry(browser, h, fp);
965 nd = hists__filter_entries(rb_next(nd));
966 }
967
968 return printed;
969 }
970
971 static int hist_browser__dump(struct hist_browser *browser)
972 {
973 char filename[64];
974 FILE *fp;
975
976 while (1) {
977 scnprintf(filename, sizeof(filename), "perf.hist.%d", browser->print_seq);
978 if (access(filename, F_OK))
979 break;
980 /*
981 * XXX: Just an arbitrary lazy upper limit
982 */
983 if (++browser->print_seq == 8192) {
984 ui_helpline__fpush("Too many perf.hist.N files, nothing written!");
985 return -1;
986 }
987 }
988
989 fp = fopen(filename, "w");
990 if (fp == NULL) {
991 char bf[64];
992 const char *err = strerror_r(errno, bf, sizeof(bf));
993 ui_helpline__fpush("Couldn't write to %s: %s", filename, err);
994 return -1;
995 }
996
997 ++browser->print_seq;
998 hist_browser__fprintf(browser, fp);
999 fclose(fp);
1000 ui_helpline__fpush("%s written!", filename);
1001
1002 return 0;
1003 }
1004
1005 static struct hist_browser *hist_browser__new(struct hists *hists)
1006 {
1007 struct hist_browser *browser = zalloc(sizeof(*browser));
1008
1009 if (browser) {
1010 browser->hists = hists;
1011 browser->b.refresh = hist_browser__refresh;
1012 browser->b.seek = ui_browser__hists_seek;
1013 browser->b.use_navkeypressed = true;
1014 if (sort__branch_mode == 1)
1015 browser->has_symbols = sort_sym_from.list.next != NULL;
1016 else
1017 browser->has_symbols = sort_sym.list.next != NULL;
1018 }
1019
1020 return browser;
1021 }
1022
1023 static void hist_browser__delete(struct hist_browser *browser)
1024 {
1025 free(browser);
1026 }
1027
1028 static struct hist_entry *hist_browser__selected_entry(struct hist_browser *browser)
1029 {
1030 return browser->he_selection;
1031 }
1032
1033 static struct thread *hist_browser__selected_thread(struct hist_browser *browser)
1034 {
1035 return browser->he_selection->thread;
1036 }
1037
1038 static int hists__browser_title(struct hists *hists, char *bf, size_t size,
1039 const char *ev_name)
1040 {
1041 char unit;
1042 int printed;
1043 const struct dso *dso = hists->dso_filter;
1044 const struct thread *thread = hists->thread_filter;
1045 unsigned long nr_samples = hists->stats.nr_events[PERF_RECORD_SAMPLE];
1046 u64 nr_events = hists->stats.total_period;
1047
1048 nr_samples = convert_unit(nr_samples, &unit);
1049 printed = scnprintf(bf, size,
1050 "Samples: %lu%c of event '%s', Event count (approx.): %lu",
1051 nr_samples, unit, ev_name, nr_events);
1052
1053
1054 if (hists->uid_filter_str)
1055 printed += snprintf(bf + printed, size - printed,
1056 ", UID: %s", hists->uid_filter_str);
1057 if (thread)
1058 printed += scnprintf(bf + printed, size - printed,
1059 ", Thread: %s(%d)",
1060 (thread->comm_set ? thread->comm : ""),
1061 thread->pid);
1062 if (dso)
1063 printed += scnprintf(bf + printed, size - printed,
1064 ", DSO: %s", dso->short_name);
1065 return printed;
1066 }
1067
1068 static inline void free_popup_options(char **options, int n)
1069 {
1070 int i;
1071
1072 for (i = 0; i < n; ++i) {
1073 free(options[i]);
1074 options[i] = NULL;
1075 }
1076 }
1077
1078 static int perf_evsel__hists_browse(struct perf_evsel *evsel, int nr_events,
1079 const char *helpline, const char *ev_name,
1080 bool left_exits,
1081 void(*timer)(void *arg), void *arg,
1082 int delay_secs)
1083 {
1084 struct hists *hists = &evsel->hists;
1085 struct hist_browser *browser = hist_browser__new(hists);
1086 struct branch_info *bi;
1087 struct pstack *fstack;
1088 char *options[16];
1089 int nr_options = 0;
1090 int key = -1;
1091 char buf[64];
1092
1093 if (browser == NULL)
1094 return -1;
1095
1096 fstack = pstack__new(2);
1097 if (fstack == NULL)
1098 goto out;
1099
1100 ui_helpline__push(helpline);
1101
1102 memset(options, 0, sizeof(options));
1103
1104 while (1) {
1105 const struct thread *thread = NULL;
1106 const struct dso *dso = NULL;
1107 int choice = 0,
1108 annotate = -2, zoom_dso = -2, zoom_thread = -2,
1109 annotate_f = -2, annotate_t = -2, browse_map = -2;
1110
1111 nr_options = 0;
1112
1113 key = hist_browser__run(browser, ev_name, timer, arg, delay_secs);
1114
1115 if (browser->he_selection != NULL) {
1116 thread = hist_browser__selected_thread(browser);
1117 dso = browser->selection->map ? browser->selection->map->dso : NULL;
1118 }
1119 switch (key) {
1120 case K_TAB:
1121 case K_UNTAB:
1122 if (nr_events == 1)
1123 continue;
1124 /*
1125 * Exit the browser, let hists__browser_tree
1126 * go to the next or previous
1127 */
1128 goto out_free_stack;
1129 case 'a':
1130 if (!browser->has_symbols) {
1131 ui_browser__warning(&browser->b, delay_secs * 2,
1132 "Annotation is only available for symbolic views, "
1133 "include \"sym*\" in --sort to use it.");
1134 continue;
1135 }
1136
1137 if (browser->selection == NULL ||
1138 browser->selection->sym == NULL ||
1139 browser->selection->map->dso->annotate_warned)
1140 continue;
1141 goto do_annotate;
1142 case 'P':
1143 hist_browser__dump(browser);
1144 continue;
1145 case 'd':
1146 goto zoom_dso;
1147 case 'V':
1148 browser->show_dso = !browser->show_dso;
1149 continue;
1150 case 't':
1151 goto zoom_thread;
1152 case '/':
1153 if (ui_browser__input_window("Symbol to show",
1154 "Please enter the name of symbol you want to see",
1155 buf, "ENTER: OK, ESC: Cancel",
1156 delay_secs * 2) == K_ENTER) {
1157 hists->symbol_filter_str = *buf ? buf : NULL;
1158 hists__filter_by_symbol(hists);
1159 hist_browser__reset(browser);
1160 }
1161 continue;
1162 case K_F1:
1163 case 'h':
1164 case '?':
1165 ui_browser__help_window(&browser->b,
1166 "h/?/F1 Show this window\n"
1167 "UP/DOWN/PGUP\n"
1168 "PGDN/SPACE Navigate\n"
1169 "q/ESC/CTRL+C Exit browser\n\n"
1170 "For multiple event sessions:\n\n"
1171 "TAB/UNTAB Switch events\n\n"
1172 "For symbolic views (--sort has sym):\n\n"
1173 "-> Zoom into DSO/Threads & Annotate current symbol\n"
1174 "<- Zoom out\n"
1175 "a Annotate current symbol\n"
1176 "C Collapse all callchains\n"
1177 "E Expand all callchains\n"
1178 "d Zoom into current DSO\n"
1179 "t Zoom into current Thread\n"
1180 "P Print histograms to perf.hist.N\n"
1181 "V Verbose (DSO names in callchains, etc)\n"
1182 "/ Filter symbol by name");
1183 continue;
1184 case K_ENTER:
1185 case K_RIGHT:
1186 /* menu */
1187 break;
1188 case K_LEFT: {
1189 const void *top;
1190
1191 if (pstack__empty(fstack)) {
1192 /*
1193 * Go back to the perf_evsel_menu__run or other user
1194 */
1195 if (left_exits)
1196 goto out_free_stack;
1197 continue;
1198 }
1199 top = pstack__pop(fstack);
1200 if (top == &browser->hists->dso_filter)
1201 goto zoom_out_dso;
1202 if (top == &browser->hists->thread_filter)
1203 goto zoom_out_thread;
1204 continue;
1205 }
1206 case K_ESC:
1207 if (!left_exits &&
1208 !ui_browser__dialog_yesno(&browser->b,
1209 "Do you really want to exit?"))
1210 continue;
1211 /* Fall thru */
1212 case 'q':
1213 case CTRL('c'):
1214 goto out_free_stack;
1215 default:
1216 continue;
1217 }
1218
1219 if (!browser->has_symbols)
1220 goto add_exit_option;
1221
1222 if (sort__branch_mode == 1) {
1223 bi = browser->he_selection->branch_info;
1224 if (browser->selection != NULL &&
1225 bi &&
1226 bi->from.sym != NULL &&
1227 !bi->from.map->dso->annotate_warned &&
1228 asprintf(&options[nr_options], "Annotate %s",
1229 bi->from.sym->name) > 0)
1230 annotate_f = nr_options++;
1231
1232 if (browser->selection != NULL &&
1233 bi &&
1234 bi->to.sym != NULL &&
1235 !bi->to.map->dso->annotate_warned &&
1236 (bi->to.sym != bi->from.sym ||
1237 bi->to.map->dso != bi->from.map->dso) &&
1238 asprintf(&options[nr_options], "Annotate %s",
1239 bi->to.sym->name) > 0)
1240 annotate_t = nr_options++;
1241 } else {
1242
1243 if (browser->selection != NULL &&
1244 browser->selection->sym != NULL &&
1245 !browser->selection->map->dso->annotate_warned &&
1246 asprintf(&options[nr_options], "Annotate %s",
1247 browser->selection->sym->name) > 0)
1248 annotate = nr_options++;
1249 }
1250
1251 if (thread != NULL &&
1252 asprintf(&options[nr_options], "Zoom %s %s(%d) thread",
1253 (browser->hists->thread_filter ? "out of" : "into"),
1254 (thread->comm_set ? thread->comm : ""),
1255 thread->pid) > 0)
1256 zoom_thread = nr_options++;
1257
1258 if (dso != NULL &&
1259 asprintf(&options[nr_options], "Zoom %s %s DSO",
1260 (browser->hists->dso_filter ? "out of" : "into"),
1261 (dso->kernel ? "the Kernel" : dso->short_name)) > 0)
1262 zoom_dso = nr_options++;
1263
1264 if (browser->selection != NULL &&
1265 browser->selection->map != NULL &&
1266 asprintf(&options[nr_options], "Browse map details") > 0)
1267 browse_map = nr_options++;
1268 add_exit_option:
1269 options[nr_options++] = (char *)"Exit";
1270 retry_popup_menu:
1271 choice = ui__popup_menu(nr_options, options);
1272
1273 if (choice == nr_options - 1)
1274 break;
1275
1276 if (choice == -1) {
1277 free_popup_options(options, nr_options - 1);
1278 continue;
1279 }
1280
1281 if (choice == annotate || choice == annotate_t || choice == annotate_f) {
1282 struct hist_entry *he;
1283 int err;
1284 do_annotate:
1285 he = hist_browser__selected_entry(browser);
1286 if (he == NULL)
1287 continue;
1288
1289 /*
1290 * we stash the branch_info symbol + map into the
1291 * the ms so we don't have to rewrite all the annotation
1292 * code to use branch_info.
1293 * in branch mode, the ms struct is not used
1294 */
1295 if (choice == annotate_f) {
1296 he->ms.sym = he->branch_info->from.sym;
1297 he->ms.map = he->branch_info->from.map;
1298 } else if (choice == annotate_t) {
1299 he->ms.sym = he->branch_info->to.sym;
1300 he->ms.map = he->branch_info->to.map;
1301 }
1302
1303 /*
1304 * Don't let this be freed, say, by hists__decay_entry.
1305 */
1306 he->used = true;
1307 err = hist_entry__tui_annotate(he, evsel->idx,
1308 timer, arg, delay_secs);
1309 he->used = false;
1310 /*
1311 * offer option to annotate the other branch source or target
1312 * (if they exists) when returning from annotate
1313 */
1314 if ((err == 'q' || err == CTRL('c'))
1315 && annotate_t != -2 && annotate_f != -2)
1316 goto retry_popup_menu;
1317
1318 ui_browser__update_nr_entries(&browser->b, browser->hists->nr_entries);
1319 if (err)
1320 ui_browser__handle_resize(&browser->b);
1321
1322 } else if (choice == browse_map)
1323 map__browse(browser->selection->map);
1324 else if (choice == zoom_dso) {
1325 zoom_dso:
1326 if (browser->hists->dso_filter) {
1327 pstack__remove(fstack, &browser->hists->dso_filter);
1328 zoom_out_dso:
1329 ui_helpline__pop();
1330 browser->hists->dso_filter = NULL;
1331 sort_dso.elide = false;
1332 } else {
1333 if (dso == NULL)
1334 continue;
1335 ui_helpline__fpush("To zoom out press <- or -> + \"Zoom out of %s DSO\"",
1336 dso->kernel ? "the Kernel" : dso->short_name);
1337 browser->hists->dso_filter = dso;
1338 sort_dso.elide = true;
1339 pstack__push(fstack, &browser->hists->dso_filter);
1340 }
1341 hists__filter_by_dso(hists);
1342 hist_browser__reset(browser);
1343 } else if (choice == zoom_thread) {
1344 zoom_thread:
1345 if (browser->hists->thread_filter) {
1346 pstack__remove(fstack, &browser->hists->thread_filter);
1347 zoom_out_thread:
1348 ui_helpline__pop();
1349 browser->hists->thread_filter = NULL;
1350 sort_thread.elide = false;
1351 } else {
1352 ui_helpline__fpush("To zoom out press <- or -> + \"Zoom out of %s(%d) thread\"",
1353 thread->comm_set ? thread->comm : "",
1354 thread->pid);
1355 browser->hists->thread_filter = thread;
1356 sort_thread.elide = true;
1357 pstack__push(fstack, &browser->hists->thread_filter);
1358 }
1359 hists__filter_by_thread(hists);
1360 hist_browser__reset(browser);
1361 }
1362 }
1363 out_free_stack:
1364 pstack__delete(fstack);
1365 out:
1366 hist_browser__delete(browser);
1367 free_popup_options(options, nr_options - 1);
1368 return key;
1369 }
1370
1371 struct perf_evsel_menu {
1372 struct ui_browser b;
1373 struct perf_evsel *selection;
1374 bool lost_events, lost_events_warned;
1375 };
1376
1377 static void perf_evsel_menu__write(struct ui_browser *browser,
1378 void *entry, int row)
1379 {
1380 struct perf_evsel_menu *menu = container_of(browser,
1381 struct perf_evsel_menu, b);
1382 struct perf_evsel *evsel = list_entry(entry, struct perf_evsel, node);
1383 bool current_entry = ui_browser__is_current_entry(browser, row);
1384 unsigned long nr_events = evsel->hists.stats.nr_events[PERF_RECORD_SAMPLE];
1385 const char *ev_name = perf_evsel__name(evsel);
1386 char bf[256], unit;
1387 const char *warn = " ";
1388 size_t printed;
1389
1390 ui_browser__set_color(browser, current_entry ? HE_COLORSET_SELECTED :
1391 HE_COLORSET_NORMAL);
1392
1393 nr_events = convert_unit(nr_events, &unit);
1394 printed = scnprintf(bf, sizeof(bf), "%lu%c%s%s", nr_events,
1395 unit, unit == ' ' ? "" : " ", ev_name);
1396 slsmg_printf("%s", bf);
1397
1398 nr_events = evsel->hists.stats.nr_events[PERF_RECORD_LOST];
1399 if (nr_events != 0) {
1400 menu->lost_events = true;
1401 if (!current_entry)
1402 ui_browser__set_color(browser, HE_COLORSET_TOP);
1403 nr_events = convert_unit(nr_events, &unit);
1404 printed += scnprintf(bf, sizeof(bf), ": %ld%c%schunks LOST!",
1405 nr_events, unit, unit == ' ' ? "" : " ");
1406 warn = bf;
1407 }
1408
1409 slsmg_write_nstring(warn, browser->width - printed);
1410
1411 if (current_entry)
1412 menu->selection = evsel;
1413 }
1414
1415 static int perf_evsel_menu__run(struct perf_evsel_menu *menu,
1416 int nr_events, const char *help,
1417 void(*timer)(void *arg), void *arg, int delay_secs)
1418 {
1419 struct perf_evlist *evlist = menu->b.priv;
1420 struct perf_evsel *pos;
1421 const char *ev_name, *title = "Available samples";
1422 int key;
1423
1424 if (ui_browser__show(&menu->b, title,
1425 "ESC: exit, ENTER|->: Browse histograms") < 0)
1426 return -1;
1427
1428 while (1) {
1429 key = ui_browser__run(&menu->b, delay_secs);
1430
1431 switch (key) {
1432 case K_TIMER:
1433 timer(arg);
1434
1435 if (!menu->lost_events_warned && menu->lost_events) {
1436 ui_browser__warn_lost_events(&menu->b);
1437 menu->lost_events_warned = true;
1438 }
1439 continue;
1440 case K_RIGHT:
1441 case K_ENTER:
1442 if (!menu->selection)
1443 continue;
1444 pos = menu->selection;
1445 browse_hists:
1446 perf_evlist__set_selected(evlist, pos);
1447 /*
1448 * Give the calling tool a chance to populate the non
1449 * default evsel resorted hists tree.
1450 */
1451 if (timer)
1452 timer(arg);
1453 ev_name = perf_evsel__name(pos);
1454 key = perf_evsel__hists_browse(pos, nr_events, help,
1455 ev_name, true, timer,
1456 arg, delay_secs);
1457 ui_browser__show_title(&menu->b, title);
1458 switch (key) {
1459 case K_TAB:
1460 if (pos->node.next == &evlist->entries)
1461 pos = list_entry(evlist->entries.next, struct perf_evsel, node);
1462 else
1463 pos = list_entry(pos->node.next, struct perf_evsel, node);
1464 goto browse_hists;
1465 case K_UNTAB:
1466 if (pos->node.prev == &evlist->entries)
1467 pos = list_entry(evlist->entries.prev, struct perf_evsel, node);
1468 else
1469 pos = list_entry(pos->node.prev, struct perf_evsel, node);
1470 goto browse_hists;
1471 case K_ESC:
1472 if (!ui_browser__dialog_yesno(&menu->b,
1473 "Do you really want to exit?"))
1474 continue;
1475 /* Fall thru */
1476 case 'q':
1477 case CTRL('c'):
1478 goto out;
1479 default:
1480 continue;
1481 }
1482 case K_LEFT:
1483 continue;
1484 case K_ESC:
1485 if (!ui_browser__dialog_yesno(&menu->b,
1486 "Do you really want to exit?"))
1487 continue;
1488 /* Fall thru */
1489 case 'q':
1490 case CTRL('c'):
1491 goto out;
1492 default:
1493 continue;
1494 }
1495 }
1496
1497 out:
1498 ui_browser__hide(&menu->b);
1499 return key;
1500 }
1501
1502 static int __perf_evlist__tui_browse_hists(struct perf_evlist *evlist,
1503 const char *help,
1504 void(*timer)(void *arg), void *arg,
1505 int delay_secs)
1506 {
1507 struct perf_evsel *pos;
1508 struct perf_evsel_menu menu = {
1509 .b = {
1510 .entries = &evlist->entries,
1511 .refresh = ui_browser__list_head_refresh,
1512 .seek = ui_browser__list_head_seek,
1513 .write = perf_evsel_menu__write,
1514 .nr_entries = evlist->nr_entries,
1515 .priv = evlist,
1516 },
1517 };
1518
1519 ui_helpline__push("Press ESC to exit");
1520
1521 list_for_each_entry(pos, &evlist->entries, node) {
1522 const char *ev_name = perf_evsel__name(pos);
1523 size_t line_len = strlen(ev_name) + 7;
1524
1525 if (menu.b.width < line_len)
1526 menu.b.width = line_len;
1527 }
1528
1529 return perf_evsel_menu__run(&menu, evlist->nr_entries, help, timer,
1530 arg, delay_secs);
1531 }
1532
1533 int perf_evlist__tui_browse_hists(struct perf_evlist *evlist, const char *help,
1534 void(*timer)(void *arg), void *arg,
1535 int delay_secs)
1536 {
1537 if (evlist->nr_entries == 1) {
1538 struct perf_evsel *first = list_entry(evlist->entries.next,
1539 struct perf_evsel, node);
1540 const char *ev_name = perf_evsel__name(first);
1541 return perf_evsel__hists_browse(first, evlist->nr_entries, help,
1542 ev_name, false, timer, arg,
1543 delay_secs);
1544 }
1545
1546 return __perf_evlist__tui_browse_hists(evlist, help,
1547 timer, arg, delay_secs);
1548 }