1 // SPDX-License-Identifier: GPL-2.0
8 #include <linux/rbtree.h>
9 #include <sys/ttydefaults.h>
11 #include "../../util/evsel.h"
12 #include "../../util/evlist.h"
13 #include "../../util/hist.h"
14 #include "../../util/pstack.h"
15 #include "../../util/sort.h"
16 #include "../../util/util.h"
17 #include "../../util/top.h"
18 #include "../../util/thread.h"
19 #include "../../arch/common.h"
21 #include "../browsers/hists.h"
22 #include "../helpline.h"
31 #include "sane_ctype.h"
33 extern void hist_browser__init_hpp(void);
35 static int perf_evsel_browser_title(struct hist_browser
*browser
,
36 char *bf
, size_t size
);
37 static void hist_browser__update_nr_entries(struct hist_browser
*hb
);
39 static struct rb_node
*hists__filter_entries(struct rb_node
*nd
,
42 static bool hist_browser__has_filter(struct hist_browser
*hb
)
44 return hists__has_filter(hb
->hists
) || hb
->min_pcnt
|| symbol_conf
.has_filter
|| hb
->c2c_filter
;
47 static int hist_browser__get_folding(struct hist_browser
*browser
)
50 struct hists
*hists
= browser
->hists
;
51 int unfolded_rows
= 0;
53 for (nd
= rb_first(&hists
->entries
);
54 (nd
= hists__filter_entries(nd
, browser
->min_pcnt
)) != NULL
;
55 nd
= rb_hierarchy_next(nd
)) {
56 struct hist_entry
*he
=
57 rb_entry(nd
, struct hist_entry
, rb_node
);
59 if (he
->leaf
&& he
->unfolded
)
60 unfolded_rows
+= he
->nr_rows
;
65 static u32
hist_browser__nr_entries(struct hist_browser
*hb
)
69 if (symbol_conf
.report_hierarchy
)
70 nr_entries
= hb
->nr_hierarchy_entries
;
71 else if (hist_browser__has_filter(hb
))
72 nr_entries
= hb
->nr_non_filtered_entries
;
74 nr_entries
= hb
->hists
->nr_entries
;
76 hb
->nr_callchain_rows
= hist_browser__get_folding(hb
);
77 return nr_entries
+ hb
->nr_callchain_rows
;
80 static void hist_browser__update_rows(struct hist_browser
*hb
)
82 struct ui_browser
*browser
= &hb
->b
;
83 struct hists
*hists
= hb
->hists
;
84 struct perf_hpp_list
*hpp_list
= hists
->hpp_list
;
85 u16 header_offset
, index_row
;
87 header_offset
= hb
->show_headers
? hpp_list
->nr_header_lines
: 0;
88 browser
->rows
= browser
->height
- header_offset
;
90 * Verify if we were at the last line and that line isn't
91 * visibe because we now show the header line(s).
93 index_row
= browser
->index
- browser
->top_idx
;
94 if (index_row
>= browser
->rows
)
95 browser
->index
-= index_row
- browser
->rows
+ 1;
98 static void hist_browser__refresh_dimensions(struct ui_browser
*browser
)
100 struct hist_browser
*hb
= container_of(browser
, struct hist_browser
, b
);
102 /* 3 == +/- toggle symbol before actual hist_entry rendering */
103 browser
->width
= 3 + (hists__sort_list_width(hb
->hists
) + sizeof("[k]"));
105 * FIXME: Just keeping existing behaviour, but this really should be
106 * before updating browser->width, as it will invalidate the
107 * calculation above. Fix this and the fallout in another
110 ui_browser__refresh_dimensions(browser
);
111 hist_browser__update_rows(hb
);
114 static void hist_browser__gotorc(struct hist_browser
*browser
, int row
, int column
)
116 struct hists
*hists
= browser
->hists
;
117 struct perf_hpp_list
*hpp_list
= hists
->hpp_list
;
120 header_offset
= browser
->show_headers
? hpp_list
->nr_header_lines
: 0;
121 ui_browser__gotorc(&browser
->b
, row
+ header_offset
, column
);
124 static void hist_browser__reset(struct hist_browser
*browser
)
127 * The hists__remove_entry_filter() already folds non-filtered
128 * entries so we can assume it has 0 callchain rows.
130 browser
->nr_callchain_rows
= 0;
132 hist_browser__update_nr_entries(browser
);
133 browser
->b
.nr_entries
= hist_browser__nr_entries(browser
);
134 hist_browser__refresh_dimensions(&browser
->b
);
135 ui_browser__reset_index(&browser
->b
);
138 static char tree__folded_sign(bool unfolded
)
140 return unfolded
? '-' : '+';
143 static char hist_entry__folded(const struct hist_entry
*he
)
145 return he
->has_children
? tree__folded_sign(he
->unfolded
) : ' ';
148 static char callchain_list__folded(const struct callchain_list
*cl
)
150 return cl
->has_children
? tree__folded_sign(cl
->unfolded
) : ' ';
153 static void callchain_list__set_folding(struct callchain_list
*cl
, bool unfold
)
155 cl
->unfolded
= unfold
? cl
->has_children
: false;
158 static int callchain_node__count_rows_rb_tree(struct callchain_node
*node
)
163 for (nd
= rb_first(&node
->rb_root
); nd
; nd
= rb_next(nd
)) {
164 struct callchain_node
*child
= rb_entry(nd
, struct callchain_node
, rb_node
);
165 struct callchain_list
*chain
;
166 char folded_sign
= ' '; /* No children */
168 list_for_each_entry(chain
, &child
->val
, list
) {
171 /* We need this because we may not have children */
172 folded_sign
= callchain_list__folded(chain
);
173 if (folded_sign
== '+')
177 if (folded_sign
== '-') /* Have children and they're unfolded */
178 n
+= callchain_node__count_rows_rb_tree(child
);
184 static int callchain_node__count_flat_rows(struct callchain_node
*node
)
186 struct callchain_list
*chain
;
187 char folded_sign
= 0;
190 list_for_each_entry(chain
, &node
->parent_val
, list
) {
192 /* only check first chain list entry */
193 folded_sign
= callchain_list__folded(chain
);
194 if (folded_sign
== '+')
200 list_for_each_entry(chain
, &node
->val
, list
) {
202 /* node->parent_val list might be empty */
203 folded_sign
= callchain_list__folded(chain
);
204 if (folded_sign
== '+')
213 static int callchain_node__count_folded_rows(struct callchain_node
*node __maybe_unused
)
218 static int callchain_node__count_rows(struct callchain_node
*node
)
220 struct callchain_list
*chain
;
221 bool unfolded
= false;
224 if (callchain_param
.mode
== CHAIN_FLAT
)
225 return callchain_node__count_flat_rows(node
);
226 else if (callchain_param
.mode
== CHAIN_FOLDED
)
227 return callchain_node__count_folded_rows(node
);
229 list_for_each_entry(chain
, &node
->val
, list
) {
232 unfolded
= chain
->unfolded
;
236 n
+= callchain_node__count_rows_rb_tree(node
);
241 static int callchain__count_rows(struct rb_root
*chain
)
246 for (nd
= rb_first(chain
); nd
; nd
= rb_next(nd
)) {
247 struct callchain_node
*node
= rb_entry(nd
, struct callchain_node
, rb_node
);
248 n
+= callchain_node__count_rows(node
);
254 static int hierarchy_count_rows(struct hist_browser
*hb
, struct hist_entry
*he
,
255 bool include_children
)
258 struct rb_node
*node
;
259 struct hist_entry
*child
;
262 return callchain__count_rows(&he
->sorted_chain
);
264 if (he
->has_no_entry
)
267 node
= rb_first(&he
->hroot_out
);
271 child
= rb_entry(node
, struct hist_entry
, rb_node
);
272 percent
= hist_entry__get_percent_limit(child
);
274 if (!child
->filtered
&& percent
>= hb
->min_pcnt
) {
277 if (include_children
&& child
->unfolded
)
278 count
+= hierarchy_count_rows(hb
, child
, true);
281 node
= rb_next(node
);
286 static bool hist_entry__toggle_fold(struct hist_entry
*he
)
291 if (!he
->has_children
)
294 he
->unfolded
= !he
->unfolded
;
298 static bool callchain_list__toggle_fold(struct callchain_list
*cl
)
303 if (!cl
->has_children
)
306 cl
->unfolded
= !cl
->unfolded
;
310 static void callchain_node__init_have_children_rb_tree(struct callchain_node
*node
)
312 struct rb_node
*nd
= rb_first(&node
->rb_root
);
314 for (nd
= rb_first(&node
->rb_root
); nd
; nd
= rb_next(nd
)) {
315 struct callchain_node
*child
= rb_entry(nd
, struct callchain_node
, rb_node
);
316 struct callchain_list
*chain
;
319 list_for_each_entry(chain
, &child
->val
, list
) {
322 chain
->has_children
= chain
->list
.next
!= &child
->val
||
323 !RB_EMPTY_ROOT(&child
->rb_root
);
325 chain
->has_children
= chain
->list
.next
== &child
->val
&&
326 !RB_EMPTY_ROOT(&child
->rb_root
);
329 callchain_node__init_have_children_rb_tree(child
);
333 static void callchain_node__init_have_children(struct callchain_node
*node
,
336 struct callchain_list
*chain
;
338 chain
= list_entry(node
->val
.next
, struct callchain_list
, list
);
339 chain
->has_children
= has_sibling
;
341 if (!list_empty(&node
->val
)) {
342 chain
= list_entry(node
->val
.prev
, struct callchain_list
, list
);
343 chain
->has_children
= !RB_EMPTY_ROOT(&node
->rb_root
);
346 callchain_node__init_have_children_rb_tree(node
);
349 static void callchain__init_have_children(struct rb_root
*root
)
351 struct rb_node
*nd
= rb_first(root
);
352 bool has_sibling
= nd
&& rb_next(nd
);
354 for (nd
= rb_first(root
); nd
; nd
= rb_next(nd
)) {
355 struct callchain_node
*node
= rb_entry(nd
, struct callchain_node
, rb_node
);
356 callchain_node__init_have_children(node
, has_sibling
);
357 if (callchain_param
.mode
== CHAIN_FLAT
||
358 callchain_param
.mode
== CHAIN_FOLDED
)
359 callchain_node__make_parent_list(node
);
363 static void hist_entry__init_have_children(struct hist_entry
*he
)
365 if (he
->init_have_children
)
369 he
->has_children
= !RB_EMPTY_ROOT(&he
->sorted_chain
);
370 callchain__init_have_children(&he
->sorted_chain
);
372 he
->has_children
= !RB_EMPTY_ROOT(&he
->hroot_out
);
375 he
->init_have_children
= true;
378 static bool hist_browser__toggle_fold(struct hist_browser
*browser
)
380 struct hist_entry
*he
= browser
->he_selection
;
381 struct map_symbol
*ms
= browser
->selection
;
382 struct callchain_list
*cl
= container_of(ms
, struct callchain_list
, ms
);
389 has_children
= hist_entry__toggle_fold(he
);
391 has_children
= callchain_list__toggle_fold(cl
);
396 hist_entry__init_have_children(he
);
397 browser
->b
.nr_entries
-= he
->nr_rows
;
400 browser
->nr_callchain_rows
-= he
->nr_rows
;
402 browser
->nr_hierarchy_entries
-= he
->nr_rows
;
404 if (symbol_conf
.report_hierarchy
)
405 child_rows
= hierarchy_count_rows(browser
, he
, true);
409 he
->nr_rows
= callchain__count_rows(
412 he
->nr_rows
= hierarchy_count_rows(browser
, he
, false);
414 /* account grand children */
415 if (symbol_conf
.report_hierarchy
)
416 browser
->b
.nr_entries
+= child_rows
- he
->nr_rows
;
418 if (!he
->leaf
&& he
->nr_rows
== 0) {
419 he
->has_no_entry
= true;
423 if (symbol_conf
.report_hierarchy
)
424 browser
->b
.nr_entries
-= child_rows
- he
->nr_rows
;
426 if (he
->has_no_entry
)
427 he
->has_no_entry
= false;
432 browser
->b
.nr_entries
+= he
->nr_rows
;
435 browser
->nr_callchain_rows
+= he
->nr_rows
;
437 browser
->nr_hierarchy_entries
+= he
->nr_rows
;
442 /* If it doesn't have children, no toggling performed */
446 static int callchain_node__set_folding_rb_tree(struct callchain_node
*node
, bool unfold
)
451 for (nd
= rb_first(&node
->rb_root
); nd
; nd
= rb_next(nd
)) {
452 struct callchain_node
*child
= rb_entry(nd
, struct callchain_node
, rb_node
);
453 struct callchain_list
*chain
;
454 bool has_children
= false;
456 list_for_each_entry(chain
, &child
->val
, list
) {
458 callchain_list__set_folding(chain
, unfold
);
459 has_children
= chain
->has_children
;
463 n
+= callchain_node__set_folding_rb_tree(child
, unfold
);
469 static int callchain_node__set_folding(struct callchain_node
*node
, bool unfold
)
471 struct callchain_list
*chain
;
472 bool has_children
= false;
475 list_for_each_entry(chain
, &node
->val
, list
) {
477 callchain_list__set_folding(chain
, unfold
);
478 has_children
= chain
->has_children
;
482 n
+= callchain_node__set_folding_rb_tree(node
, unfold
);
487 static int callchain__set_folding(struct rb_root
*chain
, bool unfold
)
492 for (nd
= rb_first(chain
); nd
; nd
= rb_next(nd
)) {
493 struct callchain_node
*node
= rb_entry(nd
, struct callchain_node
, rb_node
);
494 n
+= callchain_node__set_folding(node
, unfold
);
500 static int hierarchy_set_folding(struct hist_browser
*hb
, struct hist_entry
*he
,
501 bool unfold __maybe_unused
)
505 struct hist_entry
*child
;
508 for (nd
= rb_first(&he
->hroot_out
); nd
; nd
= rb_next(nd
)) {
509 child
= rb_entry(nd
, struct hist_entry
, rb_node
);
510 percent
= hist_entry__get_percent_limit(child
);
511 if (!child
->filtered
&& percent
>= hb
->min_pcnt
)
518 static void __hist_entry__set_folding(struct hist_entry
*he
,
519 struct hist_browser
*hb
, bool unfold
)
521 hist_entry__init_have_children(he
);
522 he
->unfolded
= unfold
? he
->has_children
: false;
524 if (he
->has_children
) {
528 n
= callchain__set_folding(&he
->sorted_chain
, unfold
);
530 n
= hierarchy_set_folding(hb
, he
, unfold
);
532 he
->nr_rows
= unfold
? n
: 0;
537 static void hist_entry__set_folding(struct hist_entry
*he
,
538 struct hist_browser
*browser
, bool unfold
)
542 percent
= hist_entry__get_percent_limit(he
);
543 if (he
->filtered
|| percent
< browser
->min_pcnt
)
546 __hist_entry__set_folding(he
, browser
, unfold
);
548 if (!he
->depth
|| unfold
)
549 browser
->nr_hierarchy_entries
++;
551 browser
->nr_callchain_rows
+= he
->nr_rows
;
552 else if (unfold
&& !hist_entry__has_hierarchy_children(he
, browser
->min_pcnt
)) {
553 browser
->nr_hierarchy_entries
++;
554 he
->has_no_entry
= true;
557 he
->has_no_entry
= false;
561 __hist_browser__set_folding(struct hist_browser
*browser
, bool unfold
)
564 struct hist_entry
*he
;
566 nd
= rb_first(&browser
->hists
->entries
);
568 he
= rb_entry(nd
, struct hist_entry
, rb_node
);
570 /* set folding state even if it's currently folded */
571 nd
= __rb_hierarchy_next(nd
, HMD_FORCE_CHILD
);
573 hist_entry__set_folding(he
, browser
, unfold
);
577 static void hist_browser__set_folding(struct hist_browser
*browser
, bool unfold
)
579 browser
->nr_hierarchy_entries
= 0;
580 browser
->nr_callchain_rows
= 0;
581 __hist_browser__set_folding(browser
, unfold
);
583 browser
->b
.nr_entries
= hist_browser__nr_entries(browser
);
584 /* Go to the start, we may be way after valid entries after a collapse */
585 ui_browser__reset_index(&browser
->b
);
588 static void hist_browser__set_folding_selected(struct hist_browser
*browser
, bool unfold
)
590 if (!browser
->he_selection
)
593 hist_entry__set_folding(browser
->he_selection
, browser
, unfold
);
594 browser
->b
.nr_entries
= hist_browser__nr_entries(browser
);
597 static void ui_browser__warn_lost_events(struct ui_browser
*browser
)
599 ui_browser__warning(browser
, 4,
600 "Events are being lost, check IO/CPU overload!\n\n"
601 "You may want to run 'perf' using a RT scheduler policy:\n\n"
602 " perf top -r 80\n\n"
603 "Or reduce the sampling frequency.");
606 static int hist_browser__title(struct hist_browser
*browser
, char *bf
, size_t size
)
608 return browser
->title
? browser
->title(browser
, bf
, size
) : 0;
611 int hist_browser__run(struct hist_browser
*browser
, const char *help
,
612 bool warn_lost_event
)
616 struct hist_browser_timer
*hbt
= browser
->hbt
;
617 int delay_secs
= hbt
? hbt
->refresh
: 0;
619 browser
->b
.entries
= &browser
->hists
->entries
;
620 browser
->b
.nr_entries
= hist_browser__nr_entries(browser
);
622 hist_browser__title(browser
, title
, sizeof(title
));
624 if (ui_browser__show(&browser
->b
, title
, "%s", help
) < 0)
628 key
= ui_browser__run(&browser
->b
, delay_secs
);
633 hbt
->timer(hbt
->arg
);
635 if (hist_browser__has_filter(browser
) ||
636 symbol_conf
.report_hierarchy
)
637 hist_browser__update_nr_entries(browser
);
639 nr_entries
= hist_browser__nr_entries(browser
);
640 ui_browser__update_nr_entries(&browser
->b
, nr_entries
);
642 if (warn_lost_event
&&
643 (browser
->hists
->stats
.nr_lost_warned
!=
644 browser
->hists
->stats
.nr_events
[PERF_RECORD_LOST
])) {
645 browser
->hists
->stats
.nr_lost_warned
=
646 browser
->hists
->stats
.nr_events
[PERF_RECORD_LOST
];
647 ui_browser__warn_lost_events(&browser
->b
);
650 hist_browser__title(browser
, title
, sizeof(title
));
651 ui_browser__show_title(&browser
->b
, title
);
654 case 'D': { /* Debug */
656 struct hist_entry
*h
= rb_entry(browser
->b
.top
,
657 struct hist_entry
, rb_node
);
659 ui_helpline__fpush("%d: nr_ent=(%d,%d), rows=%d, idx=%d, fve: idx=%d, row_off=%d, nrows=%d",
660 seq
++, browser
->b
.nr_entries
,
661 browser
->hists
->nr_entries
,
665 h
->row_offset
, h
->nr_rows
);
669 /* Collapse the whole world. */
670 hist_browser__set_folding(browser
, false);
673 /* Collapse the selected entry. */
674 hist_browser__set_folding_selected(browser
, false);
677 /* Expand the whole world. */
678 hist_browser__set_folding(browser
, true);
681 /* Expand the selected entry. */
682 hist_browser__set_folding_selected(browser
, true);
685 browser
->show_headers
= !browser
->show_headers
;
686 hist_browser__update_rows(browser
);
689 if (hist_browser__toggle_fold(browser
))
697 ui_browser__hide(&browser
->b
);
701 struct callchain_print_arg
{
702 /* for hists browser */
704 bool is_current_entry
;
711 typedef void (*print_callchain_entry_fn
)(struct hist_browser
*browser
,
712 struct callchain_list
*chain
,
713 const char *str
, int offset
,
715 struct callchain_print_arg
*arg
);
717 static void hist_browser__show_callchain_entry(struct hist_browser
*browser
,
718 struct callchain_list
*chain
,
719 const char *str
, int offset
,
721 struct callchain_print_arg
*arg
)
724 char folded_sign
= callchain_list__folded(chain
);
725 bool show_annotated
= browser
->show_dso
&& chain
->ms
.sym
&& symbol__annotation(chain
->ms
.sym
)->src
;
727 color
= HE_COLORSET_NORMAL
;
728 width
= browser
->b
.width
- (offset
+ 2);
729 if (ui_browser__is_current_entry(&browser
->b
, row
)) {
730 browser
->selection
= &chain
->ms
;
731 color
= HE_COLORSET_SELECTED
;
732 arg
->is_current_entry
= true;
735 ui_browser__set_color(&browser
->b
, color
);
736 hist_browser__gotorc(browser
, row
, 0);
737 ui_browser__write_nstring(&browser
->b
, " ", offset
);
738 ui_browser__printf(&browser
->b
, "%c", folded_sign
);
739 ui_browser__write_graph(&browser
->b
, show_annotated
? SLSMG_RARROW_CHAR
: ' ');
740 ui_browser__write_nstring(&browser
->b
, str
, width
);
743 static void hist_browser__fprintf_callchain_entry(struct hist_browser
*b __maybe_unused
,
744 struct callchain_list
*chain
,
745 const char *str
, int offset
,
746 unsigned short row __maybe_unused
,
747 struct callchain_print_arg
*arg
)
749 char folded_sign
= callchain_list__folded(chain
);
751 arg
->printed
+= fprintf(arg
->fp
, "%*s%c %s\n", offset
, " ",
755 typedef bool (*check_output_full_fn
)(struct hist_browser
*browser
,
758 static bool hist_browser__check_output_full(struct hist_browser
*browser
,
761 return browser
->b
.rows
== row
;
764 static bool hist_browser__check_dump_full(struct hist_browser
*browser __maybe_unused
,
765 unsigned short row __maybe_unused
)
770 #define LEVEL_OFFSET_STEP 3
772 static int hist_browser__show_callchain_list(struct hist_browser
*browser
,
773 struct callchain_node
*node
,
774 struct callchain_list
*chain
,
775 unsigned short row
, u64 total
,
776 bool need_percent
, int offset
,
777 print_callchain_entry_fn print
,
778 struct callchain_print_arg
*arg
)
780 char bf
[1024], *alloc_str
;
781 char buf
[64], *alloc_str2
;
785 if (arg
->row_offset
!= 0) {
793 str
= callchain_list__sym_name(chain
, bf
, sizeof(bf
),
796 if (symbol_conf
.show_branchflag_count
) {
797 callchain_list_counts__printf_value(chain
, NULL
,
800 if (asprintf(&alloc_str2
, "%s%s", str
, buf
) < 0)
801 str
= "Not enough memory!";
807 callchain_node__scnprintf_value(node
, buf
, sizeof(buf
),
810 if (asprintf(&alloc_str
, "%s %s", buf
, str
) < 0)
811 str
= "Not enough memory!";
816 print(browser
, chain
, str
, offset
, row
, arg
);
823 static bool check_percent_display(struct rb_node
*node
, u64 parent_total
)
825 struct callchain_node
*child
;
833 child
= rb_entry(node
, struct callchain_node
, rb_node
);
834 return callchain_cumul_hits(child
) != parent_total
;
837 static int hist_browser__show_callchain_flat(struct hist_browser
*browser
,
838 struct rb_root
*root
,
839 unsigned short row
, u64 total
,
841 print_callchain_entry_fn print
,
842 struct callchain_print_arg
*arg
,
843 check_output_full_fn is_output_full
)
845 struct rb_node
*node
;
846 int first_row
= row
, offset
= LEVEL_OFFSET_STEP
;
849 node
= rb_first(root
);
850 need_percent
= check_percent_display(node
, parent_total
);
853 struct callchain_node
*child
= rb_entry(node
, struct callchain_node
, rb_node
);
854 struct rb_node
*next
= rb_next(node
);
855 struct callchain_list
*chain
;
856 char folded_sign
= ' ';
858 int extra_offset
= 0;
860 list_for_each_entry(chain
, &child
->parent_val
, list
) {
861 bool was_first
= first
;
865 else if (need_percent
)
866 extra_offset
= LEVEL_OFFSET_STEP
;
868 folded_sign
= callchain_list__folded(chain
);
870 row
+= hist_browser__show_callchain_list(browser
, child
,
872 was_first
&& need_percent
,
873 offset
+ extra_offset
,
876 if (is_output_full(browser
, row
))
879 if (folded_sign
== '+')
883 list_for_each_entry(chain
, &child
->val
, list
) {
884 bool was_first
= first
;
888 else if (need_percent
)
889 extra_offset
= LEVEL_OFFSET_STEP
;
891 folded_sign
= callchain_list__folded(chain
);
893 row
+= hist_browser__show_callchain_list(browser
, child
,
895 was_first
&& need_percent
,
896 offset
+ extra_offset
,
899 if (is_output_full(browser
, row
))
902 if (folded_sign
== '+')
907 if (is_output_full(browser
, row
))
912 return row
- first_row
;
915 static char *hist_browser__folded_callchain_str(struct hist_browser
*browser
,
916 struct callchain_list
*chain
,
917 char *value_str
, char *old_str
)
923 str
= callchain_list__sym_name(chain
, bf
, sizeof(bf
),
926 if (asprintf(&new, "%s%s%s", old_str
,
927 symbol_conf
.field_sep
?: ";", str
) < 0)
931 if (asprintf(&new, "%s %s", value_str
, str
) < 0)
934 if (asprintf(&new, "%s", str
) < 0)
941 static int hist_browser__show_callchain_folded(struct hist_browser
*browser
,
942 struct rb_root
*root
,
943 unsigned short row
, u64 total
,
945 print_callchain_entry_fn print
,
946 struct callchain_print_arg
*arg
,
947 check_output_full_fn is_output_full
)
949 struct rb_node
*node
;
950 int first_row
= row
, offset
= LEVEL_OFFSET_STEP
;
953 node
= rb_first(root
);
954 need_percent
= check_percent_display(node
, parent_total
);
957 struct callchain_node
*child
= rb_entry(node
, struct callchain_node
, rb_node
);
958 struct rb_node
*next
= rb_next(node
);
959 struct callchain_list
*chain
, *first_chain
= NULL
;
961 char *value_str
= NULL
, *value_str_alloc
= NULL
;
962 char *chain_str
= NULL
, *chain_str_alloc
= NULL
;
964 if (arg
->row_offset
!= 0) {
972 callchain_node__scnprintf_value(child
, buf
, sizeof(buf
), total
);
973 if (asprintf(&value_str
, "%s", buf
) < 0) {
974 value_str
= (char *)"<...>";
977 value_str_alloc
= value_str
;
980 list_for_each_entry(chain
, &child
->parent_val
, list
) {
981 chain_str
= hist_browser__folded_callchain_str(browser
,
982 chain
, value_str
, chain_str
);
988 if (chain_str
== NULL
) {
989 chain_str
= (char *)"Not enough memory!";
993 chain_str_alloc
= chain_str
;
996 list_for_each_entry(chain
, &child
->val
, list
) {
997 chain_str
= hist_browser__folded_callchain_str(browser
,
998 chain
, value_str
, chain_str
);
1001 first_chain
= chain
;
1004 if (chain_str
== NULL
) {
1005 chain_str
= (char *)"Not enough memory!";
1009 chain_str_alloc
= chain_str
;
1013 print(browser
, first_chain
, chain_str
, offset
, row
++, arg
);
1014 free(value_str_alloc
);
1015 free(chain_str_alloc
);
1018 if (is_output_full(browser
, row
))
1023 return row
- first_row
;
1026 static int hist_browser__show_callchain_graph(struct hist_browser
*browser
,
1027 struct rb_root
*root
, int level
,
1028 unsigned short row
, u64 total
,
1030 print_callchain_entry_fn print
,
1031 struct callchain_print_arg
*arg
,
1032 check_output_full_fn is_output_full
)
1034 struct rb_node
*node
;
1035 int first_row
= row
, offset
= level
* LEVEL_OFFSET_STEP
;
1037 u64 percent_total
= total
;
1039 if (callchain_param
.mode
== CHAIN_GRAPH_REL
)
1040 percent_total
= parent_total
;
1042 node
= rb_first(root
);
1043 need_percent
= check_percent_display(node
, parent_total
);
1046 struct callchain_node
*child
= rb_entry(node
, struct callchain_node
, rb_node
);
1047 struct rb_node
*next
= rb_next(node
);
1048 struct callchain_list
*chain
;
1049 char folded_sign
= ' ';
1051 int extra_offset
= 0;
1053 list_for_each_entry(chain
, &child
->val
, list
) {
1054 bool was_first
= first
;
1058 else if (need_percent
)
1059 extra_offset
= LEVEL_OFFSET_STEP
;
1061 folded_sign
= callchain_list__folded(chain
);
1063 row
+= hist_browser__show_callchain_list(browser
, child
,
1064 chain
, row
, percent_total
,
1065 was_first
&& need_percent
,
1066 offset
+ extra_offset
,
1069 if (is_output_full(browser
, row
))
1072 if (folded_sign
== '+')
1076 if (folded_sign
== '-') {
1077 const int new_level
= level
+ (extra_offset
? 2 : 1);
1079 row
+= hist_browser__show_callchain_graph(browser
, &child
->rb_root
,
1080 new_level
, row
, total
,
1081 child
->children_hit
,
1082 print
, arg
, is_output_full
);
1084 if (is_output_full(browser
, row
))
1089 return row
- first_row
;
1092 static int hist_browser__show_callchain(struct hist_browser
*browser
,
1093 struct hist_entry
*entry
, int level
,
1095 print_callchain_entry_fn print
,
1096 struct callchain_print_arg
*arg
,
1097 check_output_full_fn is_output_full
)
1099 u64 total
= hists__total_period(entry
->hists
);
1103 if (symbol_conf
.cumulate_callchain
)
1104 parent_total
= entry
->stat_acc
->period
;
1106 parent_total
= entry
->stat
.period
;
1108 if (callchain_param
.mode
== CHAIN_FLAT
) {
1109 printed
= hist_browser__show_callchain_flat(browser
,
1110 &entry
->sorted_chain
, row
,
1111 total
, parent_total
, print
, arg
,
1113 } else if (callchain_param
.mode
== CHAIN_FOLDED
) {
1114 printed
= hist_browser__show_callchain_folded(browser
,
1115 &entry
->sorted_chain
, row
,
1116 total
, parent_total
, print
, arg
,
1119 printed
= hist_browser__show_callchain_graph(browser
,
1120 &entry
->sorted_chain
, level
, row
,
1121 total
, parent_total
, print
, arg
,
1125 if (arg
->is_current_entry
)
1126 browser
->he_selection
= entry
;
1132 struct ui_browser
*b
;
1137 int __hpp__slsmg_color_printf(struct perf_hpp
*hpp
, const char *fmt
, ...)
1139 struct hpp_arg
*arg
= hpp
->ptr
;
1144 va_start(args
, fmt
);
1145 len
= va_arg(args
, int);
1146 percent
= va_arg(args
, double);
1149 ui_browser__set_percent_color(arg
->b
, percent
, arg
->current_entry
);
1151 ret
= scnprintf(hpp
->buf
, hpp
->size
, fmt
, len
, percent
);
1152 ui_browser__printf(arg
->b
, "%s", hpp
->buf
);
1157 #define __HPP_COLOR_PERCENT_FN(_type, _field) \
1158 static u64 __hpp_get_##_field(struct hist_entry *he) \
1160 return he->stat._field; \
1164 hist_browser__hpp_color_##_type(struct perf_hpp_fmt *fmt, \
1165 struct perf_hpp *hpp, \
1166 struct hist_entry *he) \
1168 return hpp__fmt(fmt, hpp, he, __hpp_get_##_field, " %*.2f%%", \
1169 __hpp__slsmg_color_printf, true); \
1172 #define __HPP_COLOR_ACC_PERCENT_FN(_type, _field) \
1173 static u64 __hpp_get_acc_##_field(struct hist_entry *he) \
1175 return he->stat_acc->_field; \
1179 hist_browser__hpp_color_##_type(struct perf_hpp_fmt *fmt, \
1180 struct perf_hpp *hpp, \
1181 struct hist_entry *he) \
1183 if (!symbol_conf.cumulate_callchain) { \
1184 struct hpp_arg *arg = hpp->ptr; \
1185 int len = fmt->user_len ?: fmt->len; \
1186 int ret = scnprintf(hpp->buf, hpp->size, \
1187 "%*s", len, "N/A"); \
1188 ui_browser__printf(arg->b, "%s", hpp->buf); \
1192 return hpp__fmt(fmt, hpp, he, __hpp_get_acc_##_field, \
1193 " %*.2f%%", __hpp__slsmg_color_printf, true); \
1196 __HPP_COLOR_PERCENT_FN(overhead
, period
)
1197 __HPP_COLOR_PERCENT_FN(overhead_sys
, period_sys
)
1198 __HPP_COLOR_PERCENT_FN(overhead_us
, period_us
)
1199 __HPP_COLOR_PERCENT_FN(overhead_guest_sys
, period_guest_sys
)
1200 __HPP_COLOR_PERCENT_FN(overhead_guest_us
, period_guest_us
)
1201 __HPP_COLOR_ACC_PERCENT_FN(overhead_acc
, period
)
1203 #undef __HPP_COLOR_PERCENT_FN
1204 #undef __HPP_COLOR_ACC_PERCENT_FN
1206 void hist_browser__init_hpp(void)
1208 perf_hpp__format
[PERF_HPP__OVERHEAD
].color
=
1209 hist_browser__hpp_color_overhead
;
1210 perf_hpp__format
[PERF_HPP__OVERHEAD_SYS
].color
=
1211 hist_browser__hpp_color_overhead_sys
;
1212 perf_hpp__format
[PERF_HPP__OVERHEAD_US
].color
=
1213 hist_browser__hpp_color_overhead_us
;
1214 perf_hpp__format
[PERF_HPP__OVERHEAD_GUEST_SYS
].color
=
1215 hist_browser__hpp_color_overhead_guest_sys
;
1216 perf_hpp__format
[PERF_HPP__OVERHEAD_GUEST_US
].color
=
1217 hist_browser__hpp_color_overhead_guest_us
;
1218 perf_hpp__format
[PERF_HPP__OVERHEAD_ACC
].color
=
1219 hist_browser__hpp_color_overhead_acc
;
1222 static int hist_browser__show_entry(struct hist_browser
*browser
,
1223 struct hist_entry
*entry
,
1227 int width
= browser
->b
.width
;
1228 char folded_sign
= ' ';
1229 bool current_entry
= ui_browser__is_current_entry(&browser
->b
, row
);
1230 off_t row_offset
= entry
->row_offset
;
1232 struct perf_hpp_fmt
*fmt
;
1234 if (current_entry
) {
1235 browser
->he_selection
= entry
;
1236 browser
->selection
= &entry
->ms
;
1239 if (symbol_conf
.use_callchain
) {
1240 hist_entry__init_have_children(entry
);
1241 folded_sign
= hist_entry__folded(entry
);
1244 if (row_offset
== 0) {
1245 struct hpp_arg arg
= {
1247 .folded_sign
= folded_sign
,
1248 .current_entry
= current_entry
,
1252 hist_browser__gotorc(browser
, row
, 0);
1254 hists__for_each_format(browser
->hists
, fmt
) {
1256 struct perf_hpp hpp
= {
1262 if (perf_hpp__should_skip(fmt
, entry
->hists
) ||
1263 column
++ < browser
->b
.horiz_scroll
)
1266 if (current_entry
&& browser
->b
.navkeypressed
) {
1267 ui_browser__set_color(&browser
->b
,
1268 HE_COLORSET_SELECTED
);
1270 ui_browser__set_color(&browser
->b
,
1271 HE_COLORSET_NORMAL
);
1275 if (symbol_conf
.use_callchain
) {
1276 ui_browser__printf(&browser
->b
, "%c ", folded_sign
);
1281 ui_browser__printf(&browser
->b
, " ");
1286 int ret
= fmt
->color(fmt
, &hpp
, entry
);
1287 hist_entry__snprintf_alignment(entry
, &hpp
, fmt
, ret
);
1289 * fmt->color() already used ui_browser to
1290 * print the non alignment bits, skip it (+ret):
1292 ui_browser__printf(&browser
->b
, "%s", s
+ ret
);
1294 hist_entry__snprintf_alignment(entry
, &hpp
, fmt
, fmt
->entry(fmt
, &hpp
, entry
));
1295 ui_browser__printf(&browser
->b
, "%s", s
);
1297 width
-= hpp
.buf
- s
;
1300 /* The scroll bar isn't being used */
1301 if (!browser
->b
.navkeypressed
)
1304 ui_browser__write_nstring(&browser
->b
, "", width
);
1311 if (folded_sign
== '-' && row
!= browser
->b
.rows
) {
1312 struct callchain_print_arg arg
= {
1313 .row_offset
= row_offset
,
1314 .is_current_entry
= current_entry
,
1317 printed
+= hist_browser__show_callchain(browser
,
1319 hist_browser__show_callchain_entry
,
1321 hist_browser__check_output_full
);
1327 static int hist_browser__show_hierarchy_entry(struct hist_browser
*browser
,
1328 struct hist_entry
*entry
,
1333 int width
= browser
->b
.width
;
1334 char folded_sign
= ' ';
1335 bool current_entry
= ui_browser__is_current_entry(&browser
->b
, row
);
1336 off_t row_offset
= entry
->row_offset
;
1338 struct perf_hpp_fmt
*fmt
;
1339 struct perf_hpp_list_node
*fmt_node
;
1340 struct hpp_arg arg
= {
1342 .current_entry
= current_entry
,
1345 int hierarchy_indent
= (entry
->hists
->nr_hpp_node
- 2) * HIERARCHY_INDENT
;
1347 if (current_entry
) {
1348 browser
->he_selection
= entry
;
1349 browser
->selection
= &entry
->ms
;
1352 hist_entry__init_have_children(entry
);
1353 folded_sign
= hist_entry__folded(entry
);
1354 arg
.folded_sign
= folded_sign
;
1356 if (entry
->leaf
&& row_offset
) {
1358 goto show_callchain
;
1361 hist_browser__gotorc(browser
, row
, 0);
1363 if (current_entry
&& browser
->b
.navkeypressed
)
1364 ui_browser__set_color(&browser
->b
, HE_COLORSET_SELECTED
);
1366 ui_browser__set_color(&browser
->b
, HE_COLORSET_NORMAL
);
1368 ui_browser__write_nstring(&browser
->b
, "", level
* HIERARCHY_INDENT
);
1369 width
-= level
* HIERARCHY_INDENT
;
1371 /* the first hpp_list_node is for overhead columns */
1372 fmt_node
= list_first_entry(&entry
->hists
->hpp_formats
,
1373 struct perf_hpp_list_node
, list
);
1374 perf_hpp_list__for_each_format(&fmt_node
->hpp
, fmt
) {
1376 struct perf_hpp hpp
= {
1382 if (perf_hpp__should_skip(fmt
, entry
->hists
) ||
1383 column
++ < browser
->b
.horiz_scroll
)
1386 if (current_entry
&& browser
->b
.navkeypressed
) {
1387 ui_browser__set_color(&browser
->b
,
1388 HE_COLORSET_SELECTED
);
1390 ui_browser__set_color(&browser
->b
,
1391 HE_COLORSET_NORMAL
);
1395 ui_browser__printf(&browser
->b
, "%c ", folded_sign
);
1399 ui_browser__printf(&browser
->b
, " ");
1404 int ret
= fmt
->color(fmt
, &hpp
, entry
);
1405 hist_entry__snprintf_alignment(entry
, &hpp
, fmt
, ret
);
1407 * fmt->color() already used ui_browser to
1408 * print the non alignment bits, skip it (+ret):
1410 ui_browser__printf(&browser
->b
, "%s", s
+ ret
);
1412 int ret
= fmt
->entry(fmt
, &hpp
, entry
);
1413 hist_entry__snprintf_alignment(entry
, &hpp
, fmt
, ret
);
1414 ui_browser__printf(&browser
->b
, "%s", s
);
1416 width
-= hpp
.buf
- s
;
1420 ui_browser__write_nstring(&browser
->b
, "", hierarchy_indent
);
1421 width
-= hierarchy_indent
;
1424 if (column
>= browser
->b
.horiz_scroll
) {
1426 struct perf_hpp hpp
= {
1432 if (current_entry
&& browser
->b
.navkeypressed
) {
1433 ui_browser__set_color(&browser
->b
,
1434 HE_COLORSET_SELECTED
);
1436 ui_browser__set_color(&browser
->b
,
1437 HE_COLORSET_NORMAL
);
1440 perf_hpp_list__for_each_format(entry
->hpp_list
, fmt
) {
1442 ui_browser__printf(&browser
->b
, "%c ", folded_sign
);
1445 ui_browser__write_nstring(&browser
->b
, "", 2);
1451 * No need to call hist_entry__snprintf_alignment()
1452 * since this fmt is always the last column in the
1456 width
-= fmt
->color(fmt
, &hpp
, entry
);
1460 width
-= fmt
->entry(fmt
, &hpp
, entry
);
1461 ui_browser__printf(&browser
->b
, "%s", ltrim(s
));
1463 while (isspace(s
[i
++]))
1469 /* The scroll bar isn't being used */
1470 if (!browser
->b
.navkeypressed
)
1473 ui_browser__write_nstring(&browser
->b
, "", width
);
1479 if (entry
->leaf
&& folded_sign
== '-' && row
!= browser
->b
.rows
) {
1480 struct callchain_print_arg carg
= {
1481 .row_offset
= row_offset
,
1484 printed
+= hist_browser__show_callchain(browser
, entry
,
1486 hist_browser__show_callchain_entry
, &carg
,
1487 hist_browser__check_output_full
);
1493 static int hist_browser__show_no_entry(struct hist_browser
*browser
,
1494 unsigned short row
, int level
)
1496 int width
= browser
->b
.width
;
1497 bool current_entry
= ui_browser__is_current_entry(&browser
->b
, row
);
1501 struct perf_hpp_fmt
*fmt
;
1502 struct perf_hpp_list_node
*fmt_node
;
1503 int indent
= browser
->hists
->nr_hpp_node
- 2;
1505 if (current_entry
) {
1506 browser
->he_selection
= NULL
;
1507 browser
->selection
= NULL
;
1510 hist_browser__gotorc(browser
, row
, 0);
1512 if (current_entry
&& browser
->b
.navkeypressed
)
1513 ui_browser__set_color(&browser
->b
, HE_COLORSET_SELECTED
);
1515 ui_browser__set_color(&browser
->b
, HE_COLORSET_NORMAL
);
1517 ui_browser__write_nstring(&browser
->b
, "", level
* HIERARCHY_INDENT
);
1518 width
-= level
* HIERARCHY_INDENT
;
1520 /* the first hpp_list_node is for overhead columns */
1521 fmt_node
= list_first_entry(&browser
->hists
->hpp_formats
,
1522 struct perf_hpp_list_node
, list
);
1523 perf_hpp_list__for_each_format(&fmt_node
->hpp
, fmt
) {
1524 if (perf_hpp__should_skip(fmt
, browser
->hists
) ||
1525 column
++ < browser
->b
.horiz_scroll
)
1528 ret
= fmt
->width(fmt
, NULL
, browser
->hists
);
1531 /* for folded sign */
1535 /* space between columns */
1539 ui_browser__write_nstring(&browser
->b
, "", ret
);
1543 ui_browser__write_nstring(&browser
->b
, "", indent
* HIERARCHY_INDENT
);
1544 width
-= indent
* HIERARCHY_INDENT
;
1546 if (column
>= browser
->b
.horiz_scroll
) {
1549 ret
= snprintf(buf
, sizeof(buf
), "no entry >= %.2f%%", browser
->min_pcnt
);
1550 ui_browser__printf(&browser
->b
, " %s", buf
);
1554 /* The scroll bar isn't being used */
1555 if (!browser
->b
.navkeypressed
)
1558 ui_browser__write_nstring(&browser
->b
, "", width
);
1562 static int advance_hpp_check(struct perf_hpp
*hpp
, int inc
)
1564 advance_hpp(hpp
, inc
);
1565 return hpp
->size
<= 0;
1569 hists_browser__scnprintf_headers(struct hist_browser
*browser
, char *buf
,
1570 size_t size
, int line
)
1572 struct hists
*hists
= browser
->hists
;
1573 struct perf_hpp dummy_hpp
= {
1577 struct perf_hpp_fmt
*fmt
;
1582 if (symbol_conf
.use_callchain
) {
1583 ret
= scnprintf(buf
, size
, " ");
1584 if (advance_hpp_check(&dummy_hpp
, ret
))
1588 hists__for_each_format(browser
->hists
, fmt
) {
1589 if (perf_hpp__should_skip(fmt
, hists
) || column
++ < browser
->b
.horiz_scroll
)
1592 ret
= fmt
->header(fmt
, &dummy_hpp
, hists
, line
, &span
);
1593 if (advance_hpp_check(&dummy_hpp
, ret
))
1599 ret
= scnprintf(dummy_hpp
.buf
, dummy_hpp
.size
, " ");
1600 if (advance_hpp_check(&dummy_hpp
, ret
))
1607 static int hists_browser__scnprintf_hierarchy_headers(struct hist_browser
*browser
, char *buf
, size_t size
)
1609 struct hists
*hists
= browser
->hists
;
1610 struct perf_hpp dummy_hpp
= {
1614 struct perf_hpp_fmt
*fmt
;
1615 struct perf_hpp_list_node
*fmt_node
;
1618 int indent
= hists
->nr_hpp_node
- 2;
1619 bool first_node
, first_col
;
1621 ret
= scnprintf(buf
, size
, " ");
1622 if (advance_hpp_check(&dummy_hpp
, ret
))
1626 /* the first hpp_list_node is for overhead columns */
1627 fmt_node
= list_first_entry(&hists
->hpp_formats
,
1628 struct perf_hpp_list_node
, list
);
1629 perf_hpp_list__for_each_format(&fmt_node
->hpp
, fmt
) {
1630 if (column
++ < browser
->b
.horiz_scroll
)
1633 ret
= fmt
->header(fmt
, &dummy_hpp
, hists
, 0, NULL
);
1634 if (advance_hpp_check(&dummy_hpp
, ret
))
1637 ret
= scnprintf(dummy_hpp
.buf
, dummy_hpp
.size
, " ");
1638 if (advance_hpp_check(&dummy_hpp
, ret
))
1645 ret
= scnprintf(dummy_hpp
.buf
, dummy_hpp
.size
, "%*s",
1646 indent
* HIERARCHY_INDENT
, "");
1647 if (advance_hpp_check(&dummy_hpp
, ret
))
1652 list_for_each_entry_continue(fmt_node
, &hists
->hpp_formats
, list
) {
1654 ret
= scnprintf(dummy_hpp
.buf
, dummy_hpp
.size
, " / ");
1655 if (advance_hpp_check(&dummy_hpp
, ret
))
1661 perf_hpp_list__for_each_format(&fmt_node
->hpp
, fmt
) {
1664 if (perf_hpp__should_skip(fmt
, hists
))
1668 ret
= scnprintf(dummy_hpp
.buf
, dummy_hpp
.size
, "+");
1669 if (advance_hpp_check(&dummy_hpp
, ret
))
1674 ret
= fmt
->header(fmt
, &dummy_hpp
, hists
, 0, NULL
);
1675 dummy_hpp
.buf
[ret
] = '\0';
1677 start
= trim(dummy_hpp
.buf
);
1678 ret
= strlen(start
);
1680 if (start
!= dummy_hpp
.buf
)
1681 memmove(dummy_hpp
.buf
, start
, ret
+ 1);
1683 if (advance_hpp_check(&dummy_hpp
, ret
))
1691 static void hists_browser__hierarchy_headers(struct hist_browser
*browser
)
1695 hists_browser__scnprintf_hierarchy_headers(browser
, headers
,
1698 ui_browser__gotorc(&browser
->b
, 0, 0);
1699 ui_browser__set_color(&browser
->b
, HE_COLORSET_ROOT
);
1700 ui_browser__write_nstring(&browser
->b
, headers
, browser
->b
.width
+ 1);
1703 static void hists_browser__headers(struct hist_browser
*browser
)
1705 struct hists
*hists
= browser
->hists
;
1706 struct perf_hpp_list
*hpp_list
= hists
->hpp_list
;
1710 for (line
= 0; line
< hpp_list
->nr_header_lines
; line
++) {
1713 hists_browser__scnprintf_headers(browser
, headers
,
1714 sizeof(headers
), line
);
1716 ui_browser__gotorc(&browser
->b
, line
, 0);
1717 ui_browser__set_color(&browser
->b
, HE_COLORSET_ROOT
);
1718 ui_browser__write_nstring(&browser
->b
, headers
, browser
->b
.width
+ 1);
1722 static void hist_browser__show_headers(struct hist_browser
*browser
)
1724 if (symbol_conf
.report_hierarchy
)
1725 hists_browser__hierarchy_headers(browser
);
1727 hists_browser__headers(browser
);
1730 static void ui_browser__hists_init_top(struct ui_browser
*browser
)
1732 if (browser
->top
== NULL
) {
1733 struct hist_browser
*hb
;
1735 hb
= container_of(browser
, struct hist_browser
, b
);
1736 browser
->top
= rb_first(&hb
->hists
->entries
);
1740 static unsigned int hist_browser__refresh(struct ui_browser
*browser
)
1743 u16 header_offset
= 0;
1745 struct hist_browser
*hb
= container_of(browser
, struct hist_browser
, b
);
1746 struct hists
*hists
= hb
->hists
;
1748 if (hb
->show_headers
) {
1749 struct perf_hpp_list
*hpp_list
= hists
->hpp_list
;
1751 hist_browser__show_headers(hb
);
1752 header_offset
= hpp_list
->nr_header_lines
;
1755 ui_browser__hists_init_top(browser
);
1756 hb
->he_selection
= NULL
;
1757 hb
->selection
= NULL
;
1759 for (nd
= browser
->top
; nd
; nd
= rb_hierarchy_next(nd
)) {
1760 struct hist_entry
*h
= rb_entry(nd
, struct hist_entry
, rb_node
);
1764 /* let it move to sibling */
1765 h
->unfolded
= false;
1769 percent
= hist_entry__get_percent_limit(h
);
1770 if (percent
< hb
->min_pcnt
)
1773 if (symbol_conf
.report_hierarchy
) {
1774 row
+= hist_browser__show_hierarchy_entry(hb
, h
, row
,
1776 if (row
== browser
->rows
)
1779 if (h
->has_no_entry
) {
1780 hist_browser__show_no_entry(hb
, row
, h
->depth
+ 1);
1784 row
+= hist_browser__show_entry(hb
, h
, row
);
1787 if (row
== browser
->rows
)
1791 return row
+ header_offset
;
1794 static struct rb_node
*hists__filter_entries(struct rb_node
*nd
,
1797 while (nd
!= NULL
) {
1798 struct hist_entry
*h
= rb_entry(nd
, struct hist_entry
, rb_node
);
1799 float percent
= hist_entry__get_percent_limit(h
);
1801 if (!h
->filtered
&& percent
>= min_pcnt
)
1805 * If it's filtered, its all children also were filtered.
1806 * So move to sibling node.
1811 nd
= rb_hierarchy_next(nd
);
1817 static struct rb_node
*hists__filter_prev_entries(struct rb_node
*nd
,
1820 while (nd
!= NULL
) {
1821 struct hist_entry
*h
= rb_entry(nd
, struct hist_entry
, rb_node
);
1822 float percent
= hist_entry__get_percent_limit(h
);
1824 if (!h
->filtered
&& percent
>= min_pcnt
)
1827 nd
= rb_hierarchy_prev(nd
);
1833 static void ui_browser__hists_seek(struct ui_browser
*browser
,
1834 off_t offset
, int whence
)
1836 struct hist_entry
*h
;
1839 struct hist_browser
*hb
;
1841 hb
= container_of(browser
, struct hist_browser
, b
);
1843 if (browser
->nr_entries
== 0)
1846 ui_browser__hists_init_top(browser
);
1850 nd
= hists__filter_entries(rb_first(browser
->entries
),
1857 nd
= rb_hierarchy_last(rb_last(browser
->entries
));
1858 nd
= hists__filter_prev_entries(nd
, hb
->min_pcnt
);
1866 * Moves not relative to the first visible entry invalidates its
1869 h
= rb_entry(browser
->top
, struct hist_entry
, rb_node
);
1873 * Here we have to check if nd is expanded (+), if it is we can't go
1874 * the next top level hist_entry, instead we must compute an offset of
1875 * what _not_ to show and not change the first visible entry.
1877 * This offset increments when we are going from top to bottom and
1878 * decreases when we're going from bottom to top.
1880 * As we don't have backpointers to the top level in the callchains
1881 * structure, we need to always print the whole hist_entry callchain,
1882 * skipping the first ones that are before the first visible entry
1883 * and stop when we printed enough lines to fill the screen.
1891 h
= rb_entry(nd
, struct hist_entry
, rb_node
);
1892 if (h
->unfolded
&& h
->leaf
) {
1893 u16 remaining
= h
->nr_rows
- h
->row_offset
;
1894 if (offset
> remaining
) {
1895 offset
-= remaining
;
1898 h
->row_offset
+= offset
;
1904 nd
= hists__filter_entries(rb_hierarchy_next(nd
),
1910 } while (offset
!= 0);
1911 } else if (offset
< 0) {
1913 h
= rb_entry(nd
, struct hist_entry
, rb_node
);
1914 if (h
->unfolded
&& h
->leaf
) {
1916 if (-offset
> h
->row_offset
) {
1917 offset
+= h
->row_offset
;
1920 h
->row_offset
+= offset
;
1926 if (-offset
> h
->nr_rows
) {
1927 offset
+= h
->nr_rows
;
1930 h
->row_offset
= h
->nr_rows
+ offset
;
1938 nd
= hists__filter_prev_entries(rb_hierarchy_prev(nd
),
1946 * Last unfiltered hist_entry, check if it is
1947 * unfolded, if it is then we should have
1948 * row_offset at its last entry.
1950 h
= rb_entry(nd
, struct hist_entry
, rb_node
);
1951 if (h
->unfolded
&& h
->leaf
)
1952 h
->row_offset
= h
->nr_rows
;
1959 h
= rb_entry(nd
, struct hist_entry
, rb_node
);
1964 static int hist_browser__fprintf_callchain(struct hist_browser
*browser
,
1965 struct hist_entry
*he
, FILE *fp
,
1968 struct callchain_print_arg arg
= {
1972 hist_browser__show_callchain(browser
, he
, level
, 0,
1973 hist_browser__fprintf_callchain_entry
, &arg
,
1974 hist_browser__check_dump_full
);
1978 static int hist_browser__fprintf_entry(struct hist_browser
*browser
,
1979 struct hist_entry
*he
, FILE *fp
)
1983 char folded_sign
= ' ';
1984 struct perf_hpp hpp
= {
1988 struct perf_hpp_fmt
*fmt
;
1992 if (symbol_conf
.use_callchain
) {
1993 folded_sign
= hist_entry__folded(he
);
1994 printed
+= fprintf(fp
, "%c ", folded_sign
);
1997 hists__for_each_format(browser
->hists
, fmt
) {
1998 if (perf_hpp__should_skip(fmt
, he
->hists
))
2002 ret
= scnprintf(hpp
.buf
, hpp
.size
, " ");
2003 advance_hpp(&hpp
, ret
);
2007 ret
= fmt
->entry(fmt
, &hpp
, he
);
2008 ret
= hist_entry__snprintf_alignment(he
, &hpp
, fmt
, ret
);
2009 advance_hpp(&hpp
, ret
);
2011 printed
+= fprintf(fp
, "%s\n", s
);
2013 if (folded_sign
== '-')
2014 printed
+= hist_browser__fprintf_callchain(browser
, he
, fp
, 1);
2020 static int hist_browser__fprintf_hierarchy_entry(struct hist_browser
*browser
,
2021 struct hist_entry
*he
,
2022 FILE *fp
, int level
)
2026 char folded_sign
= ' ';
2027 struct perf_hpp hpp
= {
2031 struct perf_hpp_fmt
*fmt
;
2032 struct perf_hpp_list_node
*fmt_node
;
2035 int hierarchy_indent
= (he
->hists
->nr_hpp_node
- 2) * HIERARCHY_INDENT
;
2037 printed
= fprintf(fp
, "%*s", level
* HIERARCHY_INDENT
, "");
2039 folded_sign
= hist_entry__folded(he
);
2040 printed
+= fprintf(fp
, "%c", folded_sign
);
2042 /* the first hpp_list_node is for overhead columns */
2043 fmt_node
= list_first_entry(&he
->hists
->hpp_formats
,
2044 struct perf_hpp_list_node
, list
);
2045 perf_hpp_list__for_each_format(&fmt_node
->hpp
, fmt
) {
2047 ret
= scnprintf(hpp
.buf
, hpp
.size
, " ");
2048 advance_hpp(&hpp
, ret
);
2052 ret
= fmt
->entry(fmt
, &hpp
, he
);
2053 advance_hpp(&hpp
, ret
);
2056 ret
= scnprintf(hpp
.buf
, hpp
.size
, "%*s", hierarchy_indent
, "");
2057 advance_hpp(&hpp
, ret
);
2059 perf_hpp_list__for_each_format(he
->hpp_list
, fmt
) {
2060 ret
= scnprintf(hpp
.buf
, hpp
.size
, " ");
2061 advance_hpp(&hpp
, ret
);
2063 ret
= fmt
->entry(fmt
, &hpp
, he
);
2064 advance_hpp(&hpp
, ret
);
2067 printed
+= fprintf(fp
, "%s\n", rtrim(s
));
2069 if (he
->leaf
&& folded_sign
== '-') {
2070 printed
+= hist_browser__fprintf_callchain(browser
, he
, fp
,
2077 static int hist_browser__fprintf(struct hist_browser
*browser
, FILE *fp
)
2079 struct rb_node
*nd
= hists__filter_entries(rb_first(browser
->b
.entries
),
2084 struct hist_entry
*h
= rb_entry(nd
, struct hist_entry
, rb_node
);
2086 if (symbol_conf
.report_hierarchy
) {
2087 printed
+= hist_browser__fprintf_hierarchy_entry(browser
,
2091 printed
+= hist_browser__fprintf_entry(browser
, h
, fp
);
2094 nd
= hists__filter_entries(rb_hierarchy_next(nd
),
2101 static int hist_browser__dump(struct hist_browser
*browser
)
2107 scnprintf(filename
, sizeof(filename
), "perf.hist.%d", browser
->print_seq
);
2108 if (access(filename
, F_OK
))
2111 * XXX: Just an arbitrary lazy upper limit
2113 if (++browser
->print_seq
== 8192) {
2114 ui_helpline__fpush("Too many perf.hist.N files, nothing written!");
2119 fp
= fopen(filename
, "w");
2122 const char *err
= str_error_r(errno
, bf
, sizeof(bf
));
2123 ui_helpline__fpush("Couldn't write to %s: %s", filename
, err
);
2127 ++browser
->print_seq
;
2128 hist_browser__fprintf(browser
, fp
);
2130 ui_helpline__fpush("%s written!", filename
);
2135 void hist_browser__init(struct hist_browser
*browser
,
2136 struct hists
*hists
)
2138 struct perf_hpp_fmt
*fmt
;
2140 browser
->hists
= hists
;
2141 browser
->b
.refresh
= hist_browser__refresh
;
2142 browser
->b
.refresh_dimensions
= hist_browser__refresh_dimensions
;
2143 browser
->b
.seek
= ui_browser__hists_seek
;
2144 browser
->b
.use_navkeypressed
= true;
2145 browser
->show_headers
= symbol_conf
.show_hist_headers
;
2147 if (symbol_conf
.report_hierarchy
) {
2148 struct perf_hpp_list_node
*fmt_node
;
2150 /* count overhead columns (in the first node) */
2151 fmt_node
= list_first_entry(&hists
->hpp_formats
,
2152 struct perf_hpp_list_node
, list
);
2153 perf_hpp_list__for_each_format(&fmt_node
->hpp
, fmt
)
2154 ++browser
->b
.columns
;
2156 /* add a single column for whole hierarchy sort keys*/
2157 ++browser
->b
.columns
;
2159 hists__for_each_format(hists
, fmt
)
2160 ++browser
->b
.columns
;
2163 hists__reset_column_width(hists
);
2166 struct hist_browser
*hist_browser__new(struct hists
*hists
)
2168 struct hist_browser
*browser
= zalloc(sizeof(*browser
));
2171 hist_browser__init(browser
, hists
);
2176 static struct hist_browser
*
2177 perf_evsel_browser__new(struct perf_evsel
*evsel
,
2178 struct hist_browser_timer
*hbt
,
2179 struct perf_env
*env
)
2181 struct hist_browser
*browser
= hist_browser__new(evsel__hists(evsel
));
2186 browser
->title
= perf_evsel_browser_title
;
2191 void hist_browser__delete(struct hist_browser
*browser
)
2196 static struct hist_entry
*hist_browser__selected_entry(struct hist_browser
*browser
)
2198 return browser
->he_selection
;
2201 static struct thread
*hist_browser__selected_thread(struct hist_browser
*browser
)
2203 return browser
->he_selection
->thread
;
2206 /* Check whether the browser is for 'top' or 'report' */
2207 static inline bool is_report_browser(void *timer
)
2209 return timer
== NULL
;
2212 static int perf_evsel_browser_title(struct hist_browser
*browser
,
2213 char *bf
, size_t size
)
2215 struct hist_browser_timer
*hbt
= browser
->hbt
;
2216 struct hists
*hists
= browser
->hists
;
2219 const struct dso
*dso
= hists
->dso_filter
;
2220 const struct thread
*thread
= hists
->thread_filter
;
2221 int socket_id
= hists
->socket_filter
;
2222 unsigned long nr_samples
= hists
->stats
.nr_events
[PERF_RECORD_SAMPLE
];
2223 u64 nr_events
= hists
->stats
.total_period
;
2224 struct perf_evsel
*evsel
= hists_to_evsel(hists
);
2225 const char *ev_name
= perf_evsel__name(evsel
);
2227 size_t buflen
= sizeof(buf
);
2228 char ref
[30] = " show reference callgraph, ";
2229 bool enable_ref
= false;
2231 if (symbol_conf
.filter_relative
) {
2232 nr_samples
= hists
->stats
.nr_non_filtered_samples
;
2233 nr_events
= hists
->stats
.total_non_filtered_period
;
2236 if (perf_evsel__is_group_event(evsel
)) {
2237 struct perf_evsel
*pos
;
2239 perf_evsel__group_desc(evsel
, buf
, buflen
);
2242 for_each_group_member(pos
, evsel
) {
2243 struct hists
*pos_hists
= evsel__hists(pos
);
2245 if (symbol_conf
.filter_relative
) {
2246 nr_samples
+= pos_hists
->stats
.nr_non_filtered_samples
;
2247 nr_events
+= pos_hists
->stats
.total_non_filtered_period
;
2249 nr_samples
+= pos_hists
->stats
.nr_events
[PERF_RECORD_SAMPLE
];
2250 nr_events
+= pos_hists
->stats
.total_period
;
2255 if (symbol_conf
.show_ref_callgraph
&&
2256 strstr(ev_name
, "call-graph=no"))
2258 nr_samples
= convert_unit(nr_samples
, &unit
);
2259 printed
= scnprintf(bf
, size
,
2260 "Samples: %lu%c of event '%s',%sEvent count (approx.): %" PRIu64
,
2261 nr_samples
, unit
, ev_name
, enable_ref
? ref
: " ", nr_events
);
2264 if (hists
->uid_filter_str
)
2265 printed
+= snprintf(bf
+ printed
, size
- printed
,
2266 ", UID: %s", hists
->uid_filter_str
);
2268 if (hists__has(hists
, thread
)) {
2269 printed
+= scnprintf(bf
+ printed
, size
- printed
,
2271 (thread
->comm_set
? thread__comm_str(thread
) : ""),
2274 printed
+= scnprintf(bf
+ printed
, size
- printed
,
2276 (thread
->comm_set
? thread__comm_str(thread
) : ""));
2280 printed
+= scnprintf(bf
+ printed
, size
- printed
,
2281 ", DSO: %s", dso
->short_name
);
2283 printed
+= scnprintf(bf
+ printed
, size
- printed
,
2284 ", Processor Socket: %d", socket_id
);
2285 if (!is_report_browser(hbt
)) {
2286 struct perf_top
*top
= hbt
->arg
;
2289 printed
+= scnprintf(bf
+ printed
, size
- printed
, " [z]");
2295 static inline void free_popup_options(char **options
, int n
)
2299 for (i
= 0; i
< n
; ++i
)
2304 * Only runtime switching of perf data file will make "input_name" point
2305 * to a malloced buffer. So add "is_input_name_malloced" flag to decide
2306 * whether we need to call free() for current "input_name" during the switch.
2308 static bool is_input_name_malloced
= false;
2310 static int switch_data_file(void)
2312 char *pwd
, *options
[32], *abs_path
[32], *tmp
;
2314 int nr_options
= 0, choice
= -1, ret
= -1;
2315 struct dirent
*dent
;
2317 pwd
= getenv("PWD");
2321 pwd_dir
= opendir(pwd
);
2325 memset(options
, 0, sizeof(options
));
2326 memset(abs_path
, 0, sizeof(abs_path
));
2328 while ((dent
= readdir(pwd_dir
))) {
2329 char path
[PATH_MAX
];
2331 char *name
= dent
->d_name
;
2334 if (!(dent
->d_type
== DT_REG
))
2337 snprintf(path
, sizeof(path
), "%s/%s", pwd
, name
);
2339 file
= fopen(path
, "r");
2343 if (fread(&magic
, 1, 8, file
) < 8)
2344 goto close_file_and_continue
;
2346 if (is_perf_magic(magic
)) {
2347 options
[nr_options
] = strdup(name
);
2348 if (!options
[nr_options
])
2349 goto close_file_and_continue
;
2351 abs_path
[nr_options
] = strdup(path
);
2352 if (!abs_path
[nr_options
]) {
2353 zfree(&options
[nr_options
]);
2354 ui__warning("Can't search all data files due to memory shortage.\n");
2362 close_file_and_continue
:
2364 if (nr_options
>= 32) {
2365 ui__warning("Too many perf data files in PWD!\n"
2366 "Only the first 32 files will be listed.\n");
2373 choice
= ui__popup_menu(nr_options
, options
);
2374 if (choice
< nr_options
&& choice
>= 0) {
2375 tmp
= strdup(abs_path
[choice
]);
2377 if (is_input_name_malloced
)
2378 free((void *)input_name
);
2380 is_input_name_malloced
= true;
2383 ui__warning("Data switch failed due to memory shortage!\n");
2387 free_popup_options(options
, nr_options
);
2388 free_popup_options(abs_path
, nr_options
);
2392 struct popup_action
{
2393 struct thread
*thread
;
2394 struct map_symbol ms
;
2397 int (*fn
)(struct hist_browser
*browser
, struct popup_action
*act
);
2401 do_annotate(struct hist_browser
*browser
, struct popup_action
*act
)
2403 struct perf_evsel
*evsel
;
2404 struct annotation
*notes
;
2405 struct hist_entry
*he
;
2408 if (!objdump_path
&& perf_env__lookup_objdump(browser
->env
))
2411 notes
= symbol__annotation(act
->ms
.sym
);
2415 evsel
= hists_to_evsel(browser
->hists
);
2416 err
= map_symbol__tui_annotate(&act
->ms
, evsel
, browser
->hbt
);
2417 he
= hist_browser__selected_entry(browser
);
2419 * offer option to annotate the other branch source or target
2420 * (if they exists) when returning from annotate
2422 if ((err
== 'q' || err
== CTRL('c')) && he
->branch_info
)
2425 ui_browser__update_nr_entries(&browser
->b
, browser
->hists
->nr_entries
);
2427 ui_browser__handle_resize(&browser
->b
);
2432 add_annotate_opt(struct hist_browser
*browser __maybe_unused
,
2433 struct popup_action
*act
, char **optstr
,
2434 struct map
*map
, struct symbol
*sym
)
2436 if (sym
== NULL
|| map
->dso
->annotate_warned
)
2439 if (asprintf(optstr
, "Annotate %s", sym
->name
) < 0)
2444 act
->fn
= do_annotate
;
2449 do_zoom_thread(struct hist_browser
*browser
, struct popup_action
*act
)
2451 struct thread
*thread
= act
->thread
;
2453 if ((!hists__has(browser
->hists
, thread
) &&
2454 !hists__has(browser
->hists
, comm
)) || thread
== NULL
)
2457 if (browser
->hists
->thread_filter
) {
2458 pstack__remove(browser
->pstack
, &browser
->hists
->thread_filter
);
2459 perf_hpp__set_elide(HISTC_THREAD
, false);
2460 thread__zput(browser
->hists
->thread_filter
);
2463 if (hists__has(browser
->hists
, thread
)) {
2464 ui_helpline__fpush("To zoom out press ESC or ENTER + \"Zoom out of %s(%d) thread\"",
2465 thread
->comm_set
? thread__comm_str(thread
) : "",
2468 ui_helpline__fpush("To zoom out press ESC or ENTER + \"Zoom out of %s thread\"",
2469 thread
->comm_set
? thread__comm_str(thread
) : "");
2472 browser
->hists
->thread_filter
= thread__get(thread
);
2473 perf_hpp__set_elide(HISTC_THREAD
, false);
2474 pstack__push(browser
->pstack
, &browser
->hists
->thread_filter
);
2477 hists__filter_by_thread(browser
->hists
);
2478 hist_browser__reset(browser
);
2483 add_thread_opt(struct hist_browser
*browser
, struct popup_action
*act
,
2484 char **optstr
, struct thread
*thread
)
2488 if ((!hists__has(browser
->hists
, thread
) &&
2489 !hists__has(browser
->hists
, comm
)) || thread
== NULL
)
2492 if (hists__has(browser
->hists
, thread
)) {
2493 ret
= asprintf(optstr
, "Zoom %s %s(%d) thread",
2494 browser
->hists
->thread_filter
? "out of" : "into",
2495 thread
->comm_set
? thread__comm_str(thread
) : "",
2498 ret
= asprintf(optstr
, "Zoom %s %s thread",
2499 browser
->hists
->thread_filter
? "out of" : "into",
2500 thread
->comm_set
? thread__comm_str(thread
) : "");
2505 act
->thread
= thread
;
2506 act
->fn
= do_zoom_thread
;
2511 do_zoom_dso(struct hist_browser
*browser
, struct popup_action
*act
)
2513 struct map
*map
= act
->ms
.map
;
2515 if (!hists__has(browser
->hists
, dso
) || map
== NULL
)
2518 if (browser
->hists
->dso_filter
) {
2519 pstack__remove(browser
->pstack
, &browser
->hists
->dso_filter
);
2520 perf_hpp__set_elide(HISTC_DSO
, false);
2521 browser
->hists
->dso_filter
= NULL
;
2524 ui_helpline__fpush("To zoom out press ESC or ENTER + \"Zoom out of %s DSO\"",
2525 __map__is_kernel(map
) ? "the Kernel" : map
->dso
->short_name
);
2526 browser
->hists
->dso_filter
= map
->dso
;
2527 perf_hpp__set_elide(HISTC_DSO
, true);
2528 pstack__push(browser
->pstack
, &browser
->hists
->dso_filter
);
2531 hists__filter_by_dso(browser
->hists
);
2532 hist_browser__reset(browser
);
2537 add_dso_opt(struct hist_browser
*browser
, struct popup_action
*act
,
2538 char **optstr
, struct map
*map
)
2540 if (!hists__has(browser
->hists
, dso
) || map
== NULL
)
2543 if (asprintf(optstr
, "Zoom %s %s DSO",
2544 browser
->hists
->dso_filter
? "out of" : "into",
2545 __map__is_kernel(map
) ? "the Kernel" : map
->dso
->short_name
) < 0)
2549 act
->fn
= do_zoom_dso
;
2554 do_browse_map(struct hist_browser
*browser __maybe_unused
,
2555 struct popup_action
*act
)
2557 map__browse(act
->ms
.map
);
2562 add_map_opt(struct hist_browser
*browser
,
2563 struct popup_action
*act
, char **optstr
, struct map
*map
)
2565 if (!hists__has(browser
->hists
, dso
) || map
== NULL
)
2568 if (asprintf(optstr
, "Browse map details") < 0)
2572 act
->fn
= do_browse_map
;
2577 do_run_script(struct hist_browser
*browser __maybe_unused
,
2578 struct popup_action
*act
)
2580 char script_opt
[64];
2581 memset(script_opt
, 0, sizeof(script_opt
));
2584 scnprintf(script_opt
, sizeof(script_opt
), " -c %s ",
2585 thread__comm_str(act
->thread
));
2586 } else if (act
->ms
.sym
) {
2587 scnprintf(script_opt
, sizeof(script_opt
), " -S %s ",
2591 script_browse(script_opt
);
2596 add_script_opt(struct hist_browser
*browser __maybe_unused
,
2597 struct popup_action
*act
, char **optstr
,
2598 struct thread
*thread
, struct symbol
*sym
)
2601 if (asprintf(optstr
, "Run scripts for samples of thread [%s]",
2602 thread__comm_str(thread
)) < 0)
2605 if (asprintf(optstr
, "Run scripts for samples of symbol [%s]",
2609 if (asprintf(optstr
, "Run scripts for all samples") < 0)
2613 act
->thread
= thread
;
2615 act
->fn
= do_run_script
;
2620 do_switch_data(struct hist_browser
*browser __maybe_unused
,
2621 struct popup_action
*act __maybe_unused
)
2623 if (switch_data_file()) {
2624 ui__warning("Won't switch the data files due to\n"
2625 "no valid data file get selected!\n");
2629 return K_SWITCH_INPUT_DATA
;
2633 add_switch_opt(struct hist_browser
*browser
,
2634 struct popup_action
*act
, char **optstr
)
2636 if (!is_report_browser(browser
->hbt
))
2639 if (asprintf(optstr
, "Switch to another data file in PWD") < 0)
2642 act
->fn
= do_switch_data
;
2647 do_exit_browser(struct hist_browser
*browser __maybe_unused
,
2648 struct popup_action
*act __maybe_unused
)
2654 add_exit_opt(struct hist_browser
*browser __maybe_unused
,
2655 struct popup_action
*act
, char **optstr
)
2657 if (asprintf(optstr
, "Exit") < 0)
2660 act
->fn
= do_exit_browser
;
2665 do_zoom_socket(struct hist_browser
*browser
, struct popup_action
*act
)
2667 if (!hists__has(browser
->hists
, socket
) || act
->socket
< 0)
2670 if (browser
->hists
->socket_filter
> -1) {
2671 pstack__remove(browser
->pstack
, &browser
->hists
->socket_filter
);
2672 browser
->hists
->socket_filter
= -1;
2673 perf_hpp__set_elide(HISTC_SOCKET
, false);
2675 browser
->hists
->socket_filter
= act
->socket
;
2676 perf_hpp__set_elide(HISTC_SOCKET
, true);
2677 pstack__push(browser
->pstack
, &browser
->hists
->socket_filter
);
2680 hists__filter_by_socket(browser
->hists
);
2681 hist_browser__reset(browser
);
2686 add_socket_opt(struct hist_browser
*browser
, struct popup_action
*act
,
2687 char **optstr
, int socket_id
)
2689 if (!hists__has(browser
->hists
, socket
) || socket_id
< 0)
2692 if (asprintf(optstr
, "Zoom %s Processor Socket %d",
2693 (browser
->hists
->socket_filter
> -1) ? "out of" : "into",
2697 act
->socket
= socket_id
;
2698 act
->fn
= do_zoom_socket
;
2702 static void hist_browser__update_nr_entries(struct hist_browser
*hb
)
2705 struct rb_node
*nd
= rb_first(&hb
->hists
->entries
);
2707 if (hb
->min_pcnt
== 0 && !symbol_conf
.report_hierarchy
) {
2708 hb
->nr_non_filtered_entries
= hb
->hists
->nr_non_filtered_entries
;
2712 while ((nd
= hists__filter_entries(nd
, hb
->min_pcnt
)) != NULL
) {
2714 nd
= rb_hierarchy_next(nd
);
2717 hb
->nr_non_filtered_entries
= nr_entries
;
2718 hb
->nr_hierarchy_entries
= nr_entries
;
2721 static void hist_browser__update_percent_limit(struct hist_browser
*hb
,
2724 struct hist_entry
*he
;
2725 struct rb_node
*nd
= rb_first(&hb
->hists
->entries
);
2726 u64 total
= hists__total_period(hb
->hists
);
2727 u64 min_callchain_hits
= total
* (percent
/ 100);
2729 hb
->min_pcnt
= callchain_param
.min_percent
= percent
;
2731 while ((nd
= hists__filter_entries(nd
, hb
->min_pcnt
)) != NULL
) {
2732 he
= rb_entry(nd
, struct hist_entry
, rb_node
);
2734 if (he
->has_no_entry
) {
2735 he
->has_no_entry
= false;
2739 if (!he
->leaf
|| !symbol_conf
.use_callchain
)
2742 if (callchain_param
.mode
== CHAIN_GRAPH_REL
) {
2743 total
= he
->stat
.period
;
2745 if (symbol_conf
.cumulate_callchain
)
2746 total
= he
->stat_acc
->period
;
2748 min_callchain_hits
= total
* (percent
/ 100);
2751 callchain_param
.sort(&he
->sorted_chain
, he
->callchain
,
2752 min_callchain_hits
, &callchain_param
);
2755 nd
= __rb_hierarchy_next(nd
, HMD_FORCE_CHILD
);
2757 /* force to re-evaluate folding state of callchains */
2758 he
->init_have_children
= false;
2759 hist_entry__set_folding(he
, hb
, false);
2763 static int perf_evsel__hists_browse(struct perf_evsel
*evsel
, int nr_events
,
2764 const char *helpline
,
2766 struct hist_browser_timer
*hbt
,
2768 struct perf_env
*env
,
2769 bool warn_lost_event
)
2771 struct hists
*hists
= evsel__hists(evsel
);
2772 struct hist_browser
*browser
= perf_evsel_browser__new(evsel
, hbt
, env
);
2773 struct branch_info
*bi
;
2774 #define MAX_OPTIONS 16
2775 char *options
[MAX_OPTIONS
];
2776 struct popup_action actions
[MAX_OPTIONS
];
2780 int delay_secs
= hbt
? hbt
->refresh
: 0;
2782 #define HIST_BROWSER_HELP_COMMON \
2783 "h/?/F1 Show this window\n" \
2785 "PGDN/SPACE Navigate\n" \
2786 "q/ESC/CTRL+C Exit browser\n\n" \
2787 "For multiple event sessions:\n\n" \
2788 "TAB/UNTAB Switch events\n\n" \
2789 "For symbolic views (--sort has sym):\n\n" \
2790 "ENTER Zoom into DSO/Threads & Annotate current symbol\n" \
2792 "a Annotate current symbol\n" \
2793 "C Collapse all callchains\n" \
2794 "d Zoom into current DSO\n" \
2795 "E Expand all callchains\n" \
2796 "F Toggle percentage of filtered entries\n" \
2797 "H Display column headers\n" \
2798 "L Change percent limit\n" \
2799 "m Display context menu\n" \
2800 "S Zoom into current Processor Socket\n" \
2802 /* help messages are sorted by lexical order of the hotkey */
2803 const char report_help
[] = HIST_BROWSER_HELP_COMMON
2804 "i Show header information\n"
2805 "P Print histograms to perf.hist.N\n"
2806 "r Run available scripts\n"
2807 "s Switch to another data file in PWD\n"
2808 "t Zoom into current Thread\n"
2809 "V Verbose (DSO names in callchains, etc)\n"
2810 "/ Filter symbol by name";
2811 const char top_help
[] = HIST_BROWSER_HELP_COMMON
2812 "P Print histograms to perf.hist.N\n"
2813 "t Zoom into current Thread\n"
2814 "V Verbose (DSO names in callchains, etc)\n"
2815 "z Toggle zeroing of samples\n"
2816 "f Enable/Disable events\n"
2817 "/ Filter symbol by name";
2819 if (browser
== NULL
)
2822 /* reset abort key so that it can get Ctrl-C as a key */
2824 SLang_init_tty(0, 0, 0);
2827 browser
->min_pcnt
= min_pcnt
;
2828 hist_browser__update_nr_entries(browser
);
2830 browser
->pstack
= pstack__new(3);
2831 if (browser
->pstack
== NULL
)
2834 ui_helpline__push(helpline
);
2836 memset(options
, 0, sizeof(options
));
2837 memset(actions
, 0, sizeof(actions
));
2839 if (symbol_conf
.col_width_list_str
)
2840 perf_hpp__set_user_width(symbol_conf
.col_width_list_str
);
2843 struct thread
*thread
= NULL
;
2844 struct map
*map
= NULL
;
2850 key
= hist_browser__run(browser
, helpline
,
2853 if (browser
->he_selection
!= NULL
) {
2854 thread
= hist_browser__selected_thread(browser
);
2855 map
= browser
->selection
->map
;
2856 socked_id
= browser
->he_selection
->socket
;
2864 * Exit the browser, let hists__browser_tree
2865 * go to the next or previous
2867 goto out_free_stack
;
2869 if (!hists__has(hists
, sym
)) {
2870 ui_browser__warning(&browser
->b
, delay_secs
* 2,
2871 "Annotation is only available for symbolic views, "
2872 "include \"sym*\" in --sort to use it.");
2876 if (browser
->selection
== NULL
||
2877 browser
->selection
->sym
== NULL
||
2878 browser
->selection
->map
->dso
->annotate_warned
)
2881 actions
->ms
.map
= browser
->selection
->map
;
2882 actions
->ms
.sym
= browser
->selection
->sym
;
2883 do_annotate(browser
, actions
);
2886 hist_browser__dump(browser
);
2889 actions
->ms
.map
= map
;
2890 do_zoom_dso(browser
, actions
);
2893 verbose
= (verbose
+ 1) % 4;
2894 browser
->show_dso
= verbose
> 0;
2895 ui_helpline__fpush("Verbosity level set to %d\n",
2899 actions
->thread
= thread
;
2900 do_zoom_thread(browser
, actions
);
2903 actions
->socket
= socked_id
;
2904 do_zoom_socket(browser
, actions
);
2907 if (ui_browser__input_window("Symbol to show",
2908 "Please enter the name of symbol you want to see.\n"
2909 "To remove the filter later, press / + ENTER.",
2910 buf
, "ENTER: OK, ESC: Cancel",
2911 delay_secs
* 2) == K_ENTER
) {
2912 hists
->symbol_filter_str
= *buf
? buf
: NULL
;
2913 hists__filter_by_symbol(hists
);
2914 hist_browser__reset(browser
);
2918 if (is_report_browser(hbt
)) {
2919 actions
->thread
= NULL
;
2920 actions
->ms
.sym
= NULL
;
2921 do_run_script(browser
, actions
);
2925 if (is_report_browser(hbt
)) {
2926 key
= do_switch_data(browser
, actions
);
2927 if (key
== K_SWITCH_INPUT_DATA
)
2928 goto out_free_stack
;
2932 /* env->arch is NULL for live-mode (i.e. perf top) */
2934 tui__header_window(env
);
2937 symbol_conf
.filter_relative
^= 1;
2940 if (!is_report_browser(hbt
)) {
2941 struct perf_top
*top
= hbt
->arg
;
2943 top
->zero
= !top
->zero
;
2947 if (ui_browser__input_window("Percent Limit",
2948 "Please enter the value you want to hide entries under that percent.",
2949 buf
, "ENTER: OK, ESC: Cancel",
2950 delay_secs
* 2) == K_ENTER
) {
2952 double new_percent
= strtod(buf
, &end
);
2954 if (new_percent
< 0 || new_percent
> 100) {
2955 ui_browser__warning(&browser
->b
, delay_secs
* 2,
2956 "Invalid percent: %.2f", new_percent
);
2960 hist_browser__update_percent_limit(browser
, new_percent
);
2961 hist_browser__reset(browser
);
2967 ui_browser__help_window(&browser
->b
,
2968 is_report_browser(hbt
) ? report_help
: top_help
);
2979 if (pstack__empty(browser
->pstack
)) {
2981 * Go back to the perf_evsel_menu__run or other user
2984 goto out_free_stack
;
2987 ui_browser__dialog_yesno(&browser
->b
,
2988 "Do you really want to exit?"))
2989 goto out_free_stack
;
2993 top
= pstack__peek(browser
->pstack
);
2994 if (top
== &browser
->hists
->dso_filter
) {
2996 * No need to set actions->dso here since
2997 * it's just to remove the current filter.
2998 * Ditto for thread below.
3000 do_zoom_dso(browser
, actions
);
3001 } else if (top
== &browser
->hists
->thread_filter
) {
3002 do_zoom_thread(browser
, actions
);
3003 } else if (top
== &browser
->hists
->socket_filter
) {
3004 do_zoom_socket(browser
, actions
);
3010 goto out_free_stack
;
3012 if (!is_report_browser(hbt
)) {
3013 struct perf_top
*top
= hbt
->arg
;
3015 perf_evlist__toggle_enable(top
->evlist
);
3017 * No need to refresh, resort/decay histogram
3018 * entries if we are not collecting samples:
3020 if (top
->evlist
->enabled
) {
3021 helpline
= "Press 'f' to disable the events or 'h' to see other hotkeys";
3022 hbt
->refresh
= delay_secs
;
3024 helpline
= "Press 'f' again to re-enable the events";
3031 helpline
= "Press '?' for help on key bindings";
3035 if (!hists__has(hists
, sym
) || browser
->selection
== NULL
)
3036 goto skip_annotation
;
3038 if (sort__mode
== SORT_MODE__BRANCH
) {
3039 bi
= browser
->he_selection
->branch_info
;
3042 goto skip_annotation
;
3044 nr_options
+= add_annotate_opt(browser
,
3045 &actions
[nr_options
],
3046 &options
[nr_options
],
3049 if (bi
->to
.sym
!= bi
->from
.sym
)
3050 nr_options
+= add_annotate_opt(browser
,
3051 &actions
[nr_options
],
3052 &options
[nr_options
],
3056 nr_options
+= add_annotate_opt(browser
,
3057 &actions
[nr_options
],
3058 &options
[nr_options
],
3059 browser
->selection
->map
,
3060 browser
->selection
->sym
);
3063 nr_options
+= add_thread_opt(browser
, &actions
[nr_options
],
3064 &options
[nr_options
], thread
);
3065 nr_options
+= add_dso_opt(browser
, &actions
[nr_options
],
3066 &options
[nr_options
], map
);
3067 nr_options
+= add_map_opt(browser
, &actions
[nr_options
],
3068 &options
[nr_options
],
3069 browser
->selection
?
3070 browser
->selection
->map
: NULL
);
3071 nr_options
+= add_socket_opt(browser
, &actions
[nr_options
],
3072 &options
[nr_options
],
3074 /* perf script support */
3075 if (!is_report_browser(hbt
))
3076 goto skip_scripting
;
3078 if (browser
->he_selection
) {
3079 if (hists__has(hists
, thread
) && thread
) {
3080 nr_options
+= add_script_opt(browser
,
3081 &actions
[nr_options
],
3082 &options
[nr_options
],
3086 * Note that browser->selection != NULL
3087 * when browser->he_selection is not NULL,
3088 * so we don't need to check browser->selection
3089 * before fetching browser->selection->sym like what
3090 * we do before fetching browser->selection->map.
3092 * See hist_browser__show_entry.
3094 if (hists__has(hists
, sym
) && browser
->selection
->sym
) {
3095 nr_options
+= add_script_opt(browser
,
3096 &actions
[nr_options
],
3097 &options
[nr_options
],
3098 NULL
, browser
->selection
->sym
);
3101 nr_options
+= add_script_opt(browser
, &actions
[nr_options
],
3102 &options
[nr_options
], NULL
, NULL
);
3103 nr_options
+= add_switch_opt(browser
, &actions
[nr_options
],
3104 &options
[nr_options
]);
3106 nr_options
+= add_exit_opt(browser
, &actions
[nr_options
],
3107 &options
[nr_options
]);
3110 struct popup_action
*act
;
3112 choice
= ui__popup_menu(nr_options
, options
);
3113 if (choice
== -1 || choice
>= nr_options
)
3116 act
= &actions
[choice
];
3117 key
= act
->fn(browser
, act
);
3120 if (key
== K_SWITCH_INPUT_DATA
)
3124 pstack__delete(browser
->pstack
);
3126 hist_browser__delete(browser
);
3127 free_popup_options(options
, MAX_OPTIONS
);
3131 struct perf_evsel_menu
{
3132 struct ui_browser b
;
3133 struct perf_evsel
*selection
;
3134 bool lost_events
, lost_events_warned
;
3136 struct perf_env
*env
;
3139 static void perf_evsel_menu__write(struct ui_browser
*browser
,
3140 void *entry
, int row
)
3142 struct perf_evsel_menu
*menu
= container_of(browser
,
3143 struct perf_evsel_menu
, b
);
3144 struct perf_evsel
*evsel
= list_entry(entry
, struct perf_evsel
, node
);
3145 struct hists
*hists
= evsel__hists(evsel
);
3146 bool current_entry
= ui_browser__is_current_entry(browser
, row
);
3147 unsigned long nr_events
= hists
->stats
.nr_events
[PERF_RECORD_SAMPLE
];
3148 const char *ev_name
= perf_evsel__name(evsel
);
3150 const char *warn
= " ";
3153 ui_browser__set_color(browser
, current_entry
? HE_COLORSET_SELECTED
:
3154 HE_COLORSET_NORMAL
);
3156 if (perf_evsel__is_group_event(evsel
)) {
3157 struct perf_evsel
*pos
;
3159 ev_name
= perf_evsel__group_name(evsel
);
3161 for_each_group_member(pos
, evsel
) {
3162 struct hists
*pos_hists
= evsel__hists(pos
);
3163 nr_events
+= pos_hists
->stats
.nr_events
[PERF_RECORD_SAMPLE
];
3167 nr_events
= convert_unit(nr_events
, &unit
);
3168 printed
= scnprintf(bf
, sizeof(bf
), "%lu%c%s%s", nr_events
,
3169 unit
, unit
== ' ' ? "" : " ", ev_name
);
3170 ui_browser__printf(browser
, "%s", bf
);
3172 nr_events
= hists
->stats
.nr_events
[PERF_RECORD_LOST
];
3173 if (nr_events
!= 0) {
3174 menu
->lost_events
= true;
3176 ui_browser__set_color(browser
, HE_COLORSET_TOP
);
3177 nr_events
= convert_unit(nr_events
, &unit
);
3178 printed
+= scnprintf(bf
, sizeof(bf
), ": %ld%c%schunks LOST!",
3179 nr_events
, unit
, unit
== ' ' ? "" : " ");
3183 ui_browser__write_nstring(browser
, warn
, browser
->width
- printed
);
3186 menu
->selection
= evsel
;
3189 static int perf_evsel_menu__run(struct perf_evsel_menu
*menu
,
3190 int nr_events
, const char *help
,
3191 struct hist_browser_timer
*hbt
,
3192 bool warn_lost_event
)
3194 struct perf_evlist
*evlist
= menu
->b
.priv
;
3195 struct perf_evsel
*pos
;
3196 const char *title
= "Available samples";
3197 int delay_secs
= hbt
? hbt
->refresh
: 0;
3200 if (ui_browser__show(&menu
->b
, title
,
3201 "ESC: exit, ENTER|->: Browse histograms") < 0)
3205 key
= ui_browser__run(&menu
->b
, delay_secs
);
3209 hbt
->timer(hbt
->arg
);
3211 if (!menu
->lost_events_warned
&&
3212 menu
->lost_events
&&
3214 ui_browser__warn_lost_events(&menu
->b
);
3215 menu
->lost_events_warned
= true;
3220 if (!menu
->selection
)
3222 pos
= menu
->selection
;
3224 perf_evlist__set_selected(evlist
, pos
);
3226 * Give the calling tool a chance to populate the non
3227 * default evsel resorted hists tree.
3230 hbt
->timer(hbt
->arg
);
3231 key
= perf_evsel__hists_browse(pos
, nr_events
, help
,
3236 ui_browser__show_title(&menu
->b
, title
);
3239 if (pos
->node
.next
== &evlist
->entries
)
3240 pos
= perf_evlist__first(evlist
);
3242 pos
= perf_evsel__next(pos
);
3245 if (pos
->node
.prev
== &evlist
->entries
)
3246 pos
= perf_evlist__last(evlist
);
3248 pos
= perf_evsel__prev(pos
);
3250 case K_SWITCH_INPUT_DATA
:
3261 if (!ui_browser__dialog_yesno(&menu
->b
,
3262 "Do you really want to exit?"))
3274 ui_browser__hide(&menu
->b
);
3278 static bool filter_group_entries(struct ui_browser
*browser __maybe_unused
,
3281 struct perf_evsel
*evsel
= list_entry(entry
, struct perf_evsel
, node
);
3283 if (symbol_conf
.event_group
&& !perf_evsel__is_group_leader(evsel
))
3289 static int __perf_evlist__tui_browse_hists(struct perf_evlist
*evlist
,
3290 int nr_entries
, const char *help
,
3291 struct hist_browser_timer
*hbt
,
3293 struct perf_env
*env
,
3294 bool warn_lost_event
)
3296 struct perf_evsel
*pos
;
3297 struct perf_evsel_menu menu
= {
3299 .entries
= &evlist
->entries
,
3300 .refresh
= ui_browser__list_head_refresh
,
3301 .seek
= ui_browser__list_head_seek
,
3302 .write
= perf_evsel_menu__write
,
3303 .filter
= filter_group_entries
,
3304 .nr_entries
= nr_entries
,
3307 .min_pcnt
= min_pcnt
,
3311 ui_helpline__push("Press ESC to exit");
3313 evlist__for_each_entry(evlist
, pos
) {
3314 const char *ev_name
= perf_evsel__name(pos
);
3315 size_t line_len
= strlen(ev_name
) + 7;
3317 if (menu
.b
.width
< line_len
)
3318 menu
.b
.width
= line_len
;
3321 return perf_evsel_menu__run(&menu
, nr_entries
, help
,
3322 hbt
, warn_lost_event
);
3325 int perf_evlist__tui_browse_hists(struct perf_evlist
*evlist
, const char *help
,
3326 struct hist_browser_timer
*hbt
,
3328 struct perf_env
*env
,
3329 bool warn_lost_event
)
3331 int nr_entries
= evlist
->nr_entries
;
3334 if (nr_entries
== 1) {
3335 struct perf_evsel
*first
= perf_evlist__first(evlist
);
3337 return perf_evsel__hists_browse(first
, nr_entries
, help
,
3338 false, hbt
, min_pcnt
,
3339 env
, warn_lost_event
);
3342 if (symbol_conf
.event_group
) {
3343 struct perf_evsel
*pos
;
3346 evlist__for_each_entry(evlist
, pos
) {
3347 if (perf_evsel__is_group_leader(pos
))
3351 if (nr_entries
== 1)
3355 return __perf_evlist__tui_browse_hists(evlist
, nr_entries
, help
,