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