4 #include "../libslang.h"
8 #include <linux/rbtree.h>
10 #include "../../hist.h"
11 #include "../../pstack.h"
12 #include "../../sort.h"
13 #include "../../util.h"
15 #include "../browser.h"
16 #include "../helpline.h"
23 struct hist_entry
*he_selection
;
24 struct map_symbol
*selection
;
27 static void hist_browser__refresh_dimensions(struct hist_browser
*self
)
29 /* 3 == +/- toggle symbol before actual hist_entry rendering */
30 self
->b
.width
= 3 + (hists__sort_list_width(self
->hists
) +
34 static void hist_browser__reset(struct hist_browser
*self
)
36 self
->b
.nr_entries
= self
->hists
->nr_entries
;
37 hist_browser__refresh_dimensions(self
);
38 ui_browser__reset_index(&self
->b
);
41 static char tree__folded_sign(bool unfolded
)
43 return unfolded
? '-' : '+';
46 static char map_symbol__folded(const struct map_symbol
*self
)
48 return self
->has_children
? tree__folded_sign(self
->unfolded
) : ' ';
51 static char hist_entry__folded(const struct hist_entry
*self
)
53 return map_symbol__folded(&self
->ms
);
56 static char callchain_list__folded(const struct callchain_list
*self
)
58 return map_symbol__folded(&self
->ms
);
61 static int callchain_node__count_rows_rb_tree(struct callchain_node
*self
)
66 for (nd
= rb_first(&self
->rb_root
); nd
; nd
= rb_next(nd
)) {
67 struct callchain_node
*child
= rb_entry(nd
, struct callchain_node
, rb_node
);
68 struct callchain_list
*chain
;
69 char folded_sign
= ' '; /* No children */
71 list_for_each_entry(chain
, &child
->val
, list
) {
73 /* We need this because we may not have children */
74 folded_sign
= callchain_list__folded(chain
);
75 if (folded_sign
== '+')
79 if (folded_sign
== '-') /* Have children and they're unfolded */
80 n
+= callchain_node__count_rows_rb_tree(child
);
86 static int callchain_node__count_rows(struct callchain_node
*node
)
88 struct callchain_list
*chain
;
89 bool unfolded
= false;
92 list_for_each_entry(chain
, &node
->val
, list
) {
94 unfolded
= chain
->ms
.unfolded
;
98 n
+= callchain_node__count_rows_rb_tree(node
);
103 static int callchain__count_rows(struct rb_root
*chain
)
108 for (nd
= rb_first(chain
); nd
; nd
= rb_next(nd
)) {
109 struct callchain_node
*node
= rb_entry(nd
, struct callchain_node
, rb_node
);
110 n
+= callchain_node__count_rows(node
);
116 static bool map_symbol__toggle_fold(struct map_symbol
*self
)
118 if (!self
->has_children
)
121 self
->unfolded
= !self
->unfolded
;
125 static void callchain_node__init_have_children_rb_tree(struct callchain_node
*self
)
127 struct rb_node
*nd
= rb_first(&self
->rb_root
);
129 for (nd
= rb_first(&self
->rb_root
); nd
; nd
= rb_next(nd
)) {
130 struct callchain_node
*child
= rb_entry(nd
, struct callchain_node
, rb_node
);
131 struct callchain_list
*chain
;
134 list_for_each_entry(chain
, &child
->val
, list
) {
137 chain
->ms
.has_children
= chain
->list
.next
!= &child
->val
||
138 rb_first(&child
->rb_root
) != NULL
;
140 chain
->ms
.has_children
= chain
->list
.next
== &child
->val
&&
141 rb_first(&child
->rb_root
) != NULL
;
144 callchain_node__init_have_children_rb_tree(child
);
148 static void callchain_node__init_have_children(struct callchain_node
*self
)
150 struct callchain_list
*chain
;
152 list_for_each_entry(chain
, &self
->val
, list
)
153 chain
->ms
.has_children
= rb_first(&self
->rb_root
) != NULL
;
155 callchain_node__init_have_children_rb_tree(self
);
158 static void callchain__init_have_children(struct rb_root
*self
)
162 for (nd
= rb_first(self
); nd
; nd
= rb_next(nd
)) {
163 struct callchain_node
*node
= rb_entry(nd
, struct callchain_node
, rb_node
);
164 callchain_node__init_have_children(node
);
168 static void hist_entry__init_have_children(struct hist_entry
*self
)
170 if (!self
->init_have_children
) {
171 callchain__init_have_children(&self
->sorted_chain
);
172 self
->init_have_children
= true;
176 static bool hist_browser__toggle_fold(struct hist_browser
*self
)
178 if (map_symbol__toggle_fold(self
->selection
)) {
179 struct hist_entry
*he
= self
->he_selection
;
181 hist_entry__init_have_children(he
);
182 self
->hists
->nr_entries
-= he
->nr_rows
;
185 he
->nr_rows
= callchain__count_rows(&he
->sorted_chain
);
188 self
->hists
->nr_entries
+= he
->nr_rows
;
189 self
->b
.nr_entries
= self
->hists
->nr_entries
;
194 /* If it doesn't have children, no toggling performed */
198 static int hist_browser__run(struct hist_browser
*self
, const char *title
,
199 struct newtExitStruct
*es
)
202 unsigned long nr_events
= self
->hists
->stats
.nr_events
[PERF_RECORD_SAMPLE
];
204 self
->b
.entries
= &self
->hists
->entries
;
205 self
->b
.nr_entries
= self
->hists
->nr_entries
;
207 hist_browser__refresh_dimensions(self
);
209 nr_events
= convert_unit(nr_events
, &unit
);
210 snprintf(str
, sizeof(str
), "Events: %lu%c ",
212 newtDrawRootText(0, 0, str
);
214 if (ui_browser__show(&self
->b
, title
,
215 "Press '?' for help on key bindings") < 0)
218 newtFormAddHotKey(self
->b
.form
, 'a');
219 newtFormAddHotKey(self
->b
.form
, '?');
220 newtFormAddHotKey(self
->b
.form
, 'h');
221 newtFormAddHotKey(self
->b
.form
, 'd');
222 newtFormAddHotKey(self
->b
.form
, 'D');
223 newtFormAddHotKey(self
->b
.form
, 't');
225 newtFormAddHotKey(self
->b
.form
, NEWT_KEY_LEFT
);
226 newtFormAddHotKey(self
->b
.form
, NEWT_KEY_RIGHT
);
227 newtFormAddHotKey(self
->b
.form
, NEWT_KEY_ENTER
);
230 ui_browser__run(&self
->b
, es
);
232 if (es
->reason
!= NEWT_EXIT_HOTKEY
)
235 case 'D': { /* Debug */
237 struct hist_entry
*h
= rb_entry(self
->b
.top
,
238 struct hist_entry
, rb_node
);
240 ui_helpline__fpush("%d: nr_ent=(%d,%d), height=%d, idx=%d, fve: idx=%d, row_off=%d, nrows=%d",
241 seq
++, self
->b
.nr_entries
,
242 self
->hists
->nr_entries
,
246 h
->row_offset
, h
->nr_rows
);
250 if (hist_browser__toggle_fold(self
))
258 ui_browser__hide(&self
->b
);
262 static char *callchain_list__sym_name(struct callchain_list
*self
,
263 char *bf
, size_t bfsize
)
266 return self
->ms
.sym
->name
;
268 snprintf(bf
, bfsize
, "%#Lx", self
->ip
);
272 #define LEVEL_OFFSET_STEP 3
274 static int hist_browser__show_callchain_node_rb_tree(struct hist_browser
*self
,
275 struct callchain_node
*chain_node
,
276 u64 total
, int level
,
279 bool *is_current_entry
)
281 struct rb_node
*node
;
282 int first_row
= row
, width
, offset
= level
* LEVEL_OFFSET_STEP
;
283 u64 new_total
, remaining
;
285 if (callchain_param
.mode
== CHAIN_GRAPH_REL
)
286 new_total
= chain_node
->children_hit
;
290 remaining
= new_total
;
291 node
= rb_first(&chain_node
->rb_root
);
293 struct callchain_node
*child
= rb_entry(node
, struct callchain_node
, rb_node
);
294 struct rb_node
*next
= rb_next(node
);
295 u64 cumul
= cumul_hits(child
);
296 struct callchain_list
*chain
;
297 char folded_sign
= ' ';
299 int extra_offset
= 0;
303 list_for_each_entry(chain
, &child
->val
, list
) {
304 char ipstr
[BITS_PER_LONG
/ 4 + 1], *alloc_str
;
307 bool was_first
= first
;
311 chain
->ms
.has_children
= chain
->list
.next
!= &child
->val
||
312 rb_first(&child
->rb_root
) != NULL
;
314 extra_offset
= LEVEL_OFFSET_STEP
;
315 chain
->ms
.has_children
= chain
->list
.next
== &child
->val
&&
316 rb_first(&child
->rb_root
) != NULL
;
319 folded_sign
= callchain_list__folded(chain
);
320 if (*row_offset
!= 0) {
326 str
= callchain_list__sym_name(chain
, ipstr
, sizeof(ipstr
));
328 double percent
= cumul
* 100.0 / new_total
;
330 if (asprintf(&alloc_str
, "%2.2f%% %s", percent
, str
) < 0)
331 str
= "Not enough memory!";
336 color
= HE_COLORSET_NORMAL
;
337 width
= self
->b
.width
- (offset
+ extra_offset
+ 2);
338 if (ui_browser__is_current_entry(&self
->b
, row
)) {
339 self
->selection
= &chain
->ms
;
340 color
= HE_COLORSET_SELECTED
;
341 *is_current_entry
= true;
344 SLsmg_set_color(color
);
345 SLsmg_gotorc(self
->b
.y
+ row
, self
->b
.x
);
346 slsmg_write_nstring(" ", offset
+ extra_offset
);
347 slsmg_printf("%c ", folded_sign
);
348 slsmg_write_nstring(str
, width
);
351 if (++row
== self
->b
.height
)
354 if (folded_sign
== '+')
358 if (folded_sign
== '-') {
359 const int new_level
= level
+ (extra_offset
? 2 : 1);
360 row
+= hist_browser__show_callchain_node_rb_tree(self
, child
, new_total
,
361 new_level
, row
, row_offset
,
364 if (row
== self
->b
.height
)
369 return row
- first_row
;
372 static int hist_browser__show_callchain_node(struct hist_browser
*self
,
373 struct callchain_node
*node
,
374 int level
, unsigned short row
,
376 bool *is_current_entry
)
378 struct callchain_list
*chain
;
380 offset
= level
* LEVEL_OFFSET_STEP
,
381 width
= self
->b
.width
- offset
;
382 char folded_sign
= ' ';
384 list_for_each_entry(chain
, &node
->val
, list
) {
385 char ipstr
[BITS_PER_LONG
/ 4 + 1], *s
;
387 chain
->ms
.has_children
= rb_first(&node
->rb_root
) != NULL
;
388 folded_sign
= callchain_list__folded(chain
);
390 if (*row_offset
!= 0) {
395 color
= HE_COLORSET_NORMAL
;
396 if (ui_browser__is_current_entry(&self
->b
, row
)) {
397 self
->selection
= &chain
->ms
;
398 color
= HE_COLORSET_SELECTED
;
399 *is_current_entry
= true;
402 s
= callchain_list__sym_name(chain
, ipstr
, sizeof(ipstr
));
403 SLsmg_gotorc(self
->b
.y
+ row
, self
->b
.x
);
404 SLsmg_set_color(color
);
405 slsmg_write_nstring(" ", offset
);
406 slsmg_printf("%c ", folded_sign
);
407 slsmg_write_nstring(s
, width
- 2);
409 if (++row
== self
->b
.height
)
413 if (folded_sign
== '-')
414 row
+= hist_browser__show_callchain_node_rb_tree(self
, node
,
415 self
->hists
->stats
.total_period
,
420 return row
- first_row
;
423 static int hist_browser__show_callchain(struct hist_browser
*self
,
424 struct rb_root
*chain
,
425 int level
, unsigned short row
,
427 bool *is_current_entry
)
432 for (nd
= rb_first(chain
); nd
; nd
= rb_next(nd
)) {
433 struct callchain_node
*node
= rb_entry(nd
, struct callchain_node
, rb_node
);
435 row
+= hist_browser__show_callchain_node(self
, node
, level
,
438 if (row
== self
->b
.height
)
442 return row
- first_row
;
445 static int hist_browser__show_entry(struct hist_browser
*self
,
446 struct hist_entry
*entry
,
452 int color
, width
= self
->b
.width
;
453 char folded_sign
= ' ';
454 bool current_entry
= ui_browser__is_current_entry(&self
->b
, row
);
455 off_t row_offset
= entry
->row_offset
;
458 self
->he_selection
= entry
;
459 self
->selection
= &entry
->ms
;
462 if (symbol_conf
.use_callchain
) {
463 entry
->ms
.has_children
= !RB_EMPTY_ROOT(&entry
->sorted_chain
);
464 folded_sign
= hist_entry__folded(entry
);
467 if (row_offset
== 0) {
468 hist_entry__snprintf(entry
, s
, sizeof(s
), self
->hists
, NULL
, false,
469 0, false, self
->hists
->stats
.total_period
);
470 percent
= (entry
->period
* 100.0) / self
->hists
->stats
.total_period
;
472 color
= HE_COLORSET_SELECTED
;
473 if (!current_entry
) {
474 if (percent
>= MIN_RED
)
475 color
= HE_COLORSET_TOP
;
476 else if (percent
>= MIN_GREEN
)
477 color
= HE_COLORSET_MEDIUM
;
479 color
= HE_COLORSET_NORMAL
;
482 SLsmg_set_color(color
);
483 SLsmg_gotorc(self
->b
.y
+ row
, self
->b
.x
);
484 if (symbol_conf
.use_callchain
) {
485 slsmg_printf("%c ", folded_sign
);
488 slsmg_write_nstring(s
, width
);
494 if (folded_sign
== '-' && row
!= self
->b
.height
) {
495 printed
+= hist_browser__show_callchain(self
, &entry
->sorted_chain
,
499 self
->he_selection
= entry
;
505 static unsigned int hist_browser__refresh(struct ui_browser
*self
)
509 struct hist_browser
*hb
= container_of(self
, struct hist_browser
, b
);
511 if (self
->top
== NULL
)
512 self
->top
= rb_first(&hb
->hists
->entries
);
514 for (nd
= self
->top
; nd
; nd
= rb_next(nd
)) {
515 struct hist_entry
*h
= rb_entry(nd
, struct hist_entry
, rb_node
);
520 row
+= hist_browser__show_entry(hb
, h
, row
);
521 if (row
== self
->height
)
528 static struct rb_node
*hists__filter_entries(struct rb_node
*nd
)
531 struct hist_entry
*h
= rb_entry(nd
, struct hist_entry
, rb_node
);
541 static struct rb_node
*hists__filter_prev_entries(struct rb_node
*nd
)
544 struct hist_entry
*h
= rb_entry(nd
, struct hist_entry
, rb_node
);
554 static void ui_browser__hists_seek(struct ui_browser
*self
,
555 off_t offset
, int whence
)
557 struct hist_entry
*h
;
563 nd
= hists__filter_entries(rb_first(self
->entries
));
569 nd
= hists__filter_prev_entries(rb_last(self
->entries
));
577 * Moves not relative to the first visible entry invalidates its
580 h
= rb_entry(self
->top
, struct hist_entry
, rb_node
);
584 * Here we have to check if nd is expanded (+), if it is we can't go
585 * the next top level hist_entry, instead we must compute an offset of
586 * what _not_ to show and not change the first visible entry.
588 * This offset increments when we are going from top to bottom and
589 * decreases when we're going from bottom to top.
591 * As we don't have backpointers to the top level in the callchains
592 * structure, we need to always print the whole hist_entry callchain,
593 * skipping the first ones that are before the first visible entry
594 * and stop when we printed enough lines to fill the screen.
599 h
= rb_entry(nd
, struct hist_entry
, rb_node
);
600 if (h
->ms
.unfolded
) {
601 u16 remaining
= h
->nr_rows
- h
->row_offset
;
602 if (offset
> remaining
) {
606 h
->row_offset
+= offset
;
612 nd
= hists__filter_entries(rb_next(nd
));
617 } while (offset
!= 0);
618 } else if (offset
< 0) {
620 h
= rb_entry(nd
, struct hist_entry
, rb_node
);
621 if (h
->ms
.unfolded
) {
623 if (-offset
> h
->row_offset
) {
624 offset
+= h
->row_offset
;
627 h
->row_offset
+= offset
;
633 if (-offset
> h
->nr_rows
) {
634 offset
+= h
->nr_rows
;
637 h
->row_offset
= h
->nr_rows
+ offset
;
645 nd
= hists__filter_prev_entries(rb_prev(nd
));
652 * Last unfiltered hist_entry, check if it is
653 * unfolded, if it is then we should have
654 * row_offset at its last entry.
656 h
= rb_entry(nd
, struct hist_entry
, rb_node
);
658 h
->row_offset
= h
->nr_rows
;
665 h
= rb_entry(nd
, struct hist_entry
, rb_node
);
670 static struct hist_browser
*hist_browser__new(struct hists
*hists
)
672 struct hist_browser
*self
= zalloc(sizeof(*self
));
676 self
->b
.refresh
= hist_browser__refresh
;
677 self
->b
.seek
= ui_browser__hists_seek
;
683 static void hist_browser__delete(struct hist_browser
*self
)
685 newtFormDestroy(self
->b
.form
);
690 static struct hist_entry
*hist_browser__selected_entry(struct hist_browser
*self
)
692 return self
->he_selection
;
695 static struct thread
*hist_browser__selected_thread(struct hist_browser
*self
)
697 return self
->he_selection
->thread
;
700 static int hist_browser__title(char *bf
, size_t size
, const char *ev_name
,
701 const struct dso
*dso
, const struct thread
*thread
)
706 printed
+= snprintf(bf
+ printed
, size
- printed
,
708 (thread
->comm_set
? thread
->comm
: ""),
711 printed
+= snprintf(bf
+ printed
, size
- printed
,
712 "%sDSO: %s", thread
? " " : "",
714 return printed
?: snprintf(bf
, size
, "Event: %s", ev_name
);
717 int hists__browse(struct hists
*self
, const char *helpline
, const char *ev_name
)
719 struct hist_browser
*browser
= hist_browser__new(self
);
720 struct pstack
*fstack
;
721 const struct thread
*thread_filter
= NULL
;
722 const struct dso
*dso_filter
= NULL
;
723 struct newtExitStruct es
;
730 fstack
= pstack__new(2);
734 ui_helpline__push(helpline
);
736 hist_browser__title(msg
, sizeof(msg
), ev_name
,
737 dso_filter
, thread_filter
);
740 const struct thread
*thread
;
741 const struct dso
*dso
;
743 int nr_options
= 0, choice
= 0, i
,
744 annotate
= -2, zoom_dso
= -2, zoom_thread
= -2,
747 if (hist_browser__run(browser
, msg
, &es
))
750 thread
= hist_browser__selected_thread(browser
);
751 dso
= browser
->selection
->map
? browser
->selection
->map
->dso
: NULL
;
753 if (es
.reason
== NEWT_EXIT_HOTKEY
) {
762 * Exit the browser, let hists__browser_tree
763 * go to the next or previous
771 if (browser
->selection
->map
== NULL
||
772 browser
->selection
->map
->dso
->annotate_warned
)
782 ui__help_window("-> Zoom into DSO/Threads & Annotate current symbol\n"
784 "a Annotate current symbol\n"
785 "h/?/F1 Show this window\n"
786 "d Zoom into current DSO\n"
787 "t Zoom into current Thread\n"
788 "q/CTRL+C Exit browser");
792 if (is_exit_key(key
)) {
793 if (key
== NEWT_KEY_ESCAPE
&&
794 !ui__dialog_yesno("Do you really want to exit?"))
799 if (es
.u
.key
== NEWT_KEY_LEFT
) {
802 if (pstack__empty(fstack
))
804 top
= pstack__pop(fstack
);
805 if (top
== &dso_filter
)
807 if (top
== &thread_filter
)
808 goto zoom_out_thread
;
813 if (browser
->selection
->sym
!= NULL
&&
814 !browser
->selection
->map
->dso
->annotate_warned
&&
815 asprintf(&options
[nr_options
], "Annotate %s",
816 browser
->selection
->sym
->name
) > 0)
817 annotate
= nr_options
++;
819 if (thread
!= NULL
&&
820 asprintf(&options
[nr_options
], "Zoom %s %s(%d) thread",
821 (thread_filter
? "out of" : "into"),
822 (thread
->comm_set
? thread
->comm
: ""),
824 zoom_thread
= nr_options
++;
827 asprintf(&options
[nr_options
], "Zoom %s %s DSO",
828 (dso_filter
? "out of" : "into"),
829 (dso
->kernel
? "the Kernel" : dso
->short_name
)) > 0)
830 zoom_dso
= nr_options
++;
832 if (browser
->selection
->map
!= NULL
&&
833 asprintf(&options
[nr_options
], "Browse map details") > 0)
834 browse_map
= nr_options
++;
836 options
[nr_options
++] = (char *)"Exit";
838 choice
= ui__popup_menu(nr_options
, options
);
840 for (i
= 0; i
< nr_options
- 1; ++i
)
843 if (choice
== nr_options
- 1)
849 if (choice
== annotate
) {
850 struct hist_entry
*he
;
852 if (browser
->selection
->map
->dso
->origin
== DSO__ORIG_KERNEL
) {
853 browser
->selection
->map
->dso
->annotate_warned
= 1;
854 ui_helpline__puts("No vmlinux file found, can't "
855 "annotate with just a "
860 he
= hist_browser__selected_entry(browser
);
864 hist_entry__tui_annotate(he
);
865 } else if (choice
== browse_map
)
866 map__browse(browser
->selection
->map
);
867 else if (choice
== zoom_dso
) {
870 pstack__remove(fstack
, &dso_filter
);
877 ui_helpline__fpush("To zoom out press <- or -> + \"Zoom out of %s DSO\"",
878 dso
->kernel
? "the Kernel" : dso
->short_name
);
880 pstack__push(fstack
, &dso_filter
);
882 hists__filter_by_dso(self
, dso_filter
);
883 hist_browser__title(msg
, sizeof(msg
), ev_name
,
884 dso_filter
, thread_filter
);
885 hist_browser__reset(browser
);
886 } else if (choice
== zoom_thread
) {
889 pstack__remove(fstack
, &thread_filter
);
892 thread_filter
= NULL
;
894 ui_helpline__fpush("To zoom out press <- or -> + \"Zoom out of %s(%d) thread\"",
895 thread
->comm_set
? thread
->comm
: "",
897 thread_filter
= thread
;
898 pstack__push(fstack
, &thread_filter
);
900 hists__filter_by_thread(self
, thread_filter
);
901 hist_browser__title(msg
, sizeof(msg
), ev_name
,
902 dso_filter
, thread_filter
);
903 hist_browser__reset(browser
);
907 pstack__delete(fstack
);
909 hist_browser__delete(browser
);
913 int hists__tui_browse_tree(struct rb_root
*self
, const char *help
)
915 struct rb_node
*first
= rb_first(self
), *nd
= first
, *next
;
919 struct hists
*hists
= rb_entry(nd
, struct hists
, rb_node
);
920 const char *ev_name
= __event_name(hists
->type
, hists
->config
);
922 key
= hists__browse(hists
, help
, ev_name
);
924 if (is_exit_key(key
))