1 #include <stdio.h> 2 #include "../libslang.h" 3 #include <stdlib.h> 4 #include <string.h> 5 #include <linux/rbtree.h> 6 7 #include "../../util/evsel.h" 8 #include "../../util/evlist.h" 9 #include "../../util/hist.h" 10 #include "../../util/pstack.h" 11 #include "../../util/sort.h" 12 #include "../../util/util.h" 13 #include "../../arch/common.h" 14 15 #include "../browser.h" 16 #include "../helpline.h" 17 #include "../util.h" 18 #include "../ui.h" 19 #include "map.h" 20 #include "annotate.h" 21 22 struct hist_browser { 23 struct ui_browser b; 24 struct hists *hists; 25 struct hist_entry *he_selection; 26 struct map_symbol *selection; 27 int print_seq; 28 bool show_dso; 29 bool show_headers; 30 float min_pcnt; 31 u64 nr_non_filtered_entries; 32 u64 nr_callchain_rows; 33 }; 34 35 extern void hist_browser__init_hpp(void); 36 37 static int hists__browser_title(struct hists *hists, char *bf, size_t size); 38 static void hist_browser__update_nr_entries(struct hist_browser *hb); 39 40 static struct rb_node *hists__filter_entries(struct rb_node *nd, 41 float min_pcnt); 42 43 static bool hist_browser__has_filter(struct hist_browser *hb) 44 { 45 return hists__has_filter(hb->hists) || hb->min_pcnt; 46 } 47 48 static u32 hist_browser__nr_entries(struct hist_browser *hb) 49 { 50 u32 nr_entries; 51 52 if (hist_browser__has_filter(hb)) 53 nr_entries = hb->nr_non_filtered_entries; 54 else 55 nr_entries = hb->hists->nr_entries; 56 57 return nr_entries + hb->nr_callchain_rows; 58 } 59 60 static void hist_browser__update_rows(struct hist_browser *hb) 61 { 62 struct ui_browser *browser = &hb->b; 63 u16 header_offset = hb->show_headers ? 1 : 0, index_row; 64 65 browser->rows = browser->height - header_offset; 66 /* 67 * Verify if we were at the last line and that line isn't 68 * visibe because we now show the header line(s). 69 */ 70 index_row = browser->index - browser->top_idx; 71 if (index_row >= browser->rows) 72 browser->index -= index_row - browser->rows + 1; 73 } 74 75 static void hist_browser__refresh_dimensions(struct ui_browser *browser) 76 { 77 struct hist_browser *hb = container_of(browser, struct hist_browser, b); 78 79 /* 3 == +/- toggle symbol before actual hist_entry rendering */ 80 browser->width = 3 + (hists__sort_list_width(hb->hists) + sizeof("[k]")); 81 /* 82 * FIXME: Just keeping existing behaviour, but this really should be 83 * before updating browser->width, as it will invalidate the 84 * calculation above. Fix this and the fallout in another 85 * changeset. 86 */ 87 ui_browser__refresh_dimensions(browser); 88 hist_browser__update_rows(hb); 89 } 90 91 static void hist_browser__gotorc(struct hist_browser *browser, int row, int column) 92 { 93 u16 header_offset = browser->show_headers ? 1 : 0; 94 95 ui_browser__gotorc(&browser->b, row + header_offset, column); 96 } 97 98 static void hist_browser__reset(struct hist_browser *browser) 99 { 100 /* 101 * The hists__remove_entry_filter() already folds non-filtered 102 * entries so we can assume it has 0 callchain rows. 103 */ 104 browser->nr_callchain_rows = 0; 105 106 hist_browser__update_nr_entries(browser); 107 browser->b.nr_entries = hist_browser__nr_entries(browser); 108 hist_browser__refresh_dimensions(&browser->b); 109 ui_browser__reset_index(&browser->b); 110 } 111 112 static char tree__folded_sign(bool unfolded) 113 { 114 return unfolded ? '-' : '+'; 115 } 116 117 static char map_symbol__folded(const struct map_symbol *ms) 118 { 119 return ms->has_children ? tree__folded_sign(ms->unfolded) : ' '; 120 } 121 122 static char hist_entry__folded(const struct hist_entry *he) 123 { 124 return map_symbol__folded(&he->ms); 125 } 126 127 static char callchain_list__folded(const struct callchain_list *cl) 128 { 129 return map_symbol__folded(&cl->ms); 130 } 131 132 static void map_symbol__set_folding(struct map_symbol *ms, bool unfold) 133 { 134 ms->unfolded = unfold ? ms->has_children : false; 135 } 136 137 static int callchain_node__count_rows_rb_tree(struct callchain_node *node) 138 { 139 int n = 0; 140 struct rb_node *nd; 141 142 for (nd = rb_first(&node->rb_root); nd; nd = rb_next(nd)) { 143 struct callchain_node *child = rb_entry(nd, struct callchain_node, rb_node); 144 struct callchain_list *chain; 145 char folded_sign = ' '; /* No children */ 146 147 list_for_each_entry(chain, &child->val, list) { 148 ++n; 149 /* We need this because we may not have children */ 150 folded_sign = callchain_list__folded(chain); 151 if (folded_sign == '+') 152 break; 153 } 154 155 if (folded_sign == '-') /* Have children and they're unfolded */ 156 n += callchain_node__count_rows_rb_tree(child); 157 } 158 159 return n; 160 } 161 162 static int callchain_node__count_rows(struct callchain_node *node) 163 { 164 struct callchain_list *chain; 165 bool unfolded = false; 166 int n = 0; 167 168 list_for_each_entry(chain, &node->val, list) { 169 ++n; 170 unfolded = chain->ms.unfolded; 171 } 172 173 if (unfolded) 174 n += callchain_node__count_rows_rb_tree(node); 175 176 return n; 177 } 178 179 static int callchain__count_rows(struct rb_root *chain) 180 { 181 struct rb_node *nd; 182 int n = 0; 183 184 for (nd = rb_first(chain); nd; nd = rb_next(nd)) { 185 struct callchain_node *node = rb_entry(nd, struct callchain_node, rb_node); 186 n += callchain_node__count_rows(node); 187 } 188 189 return n; 190 } 191 192 static bool map_symbol__toggle_fold(struct map_symbol *ms) 193 { 194 if (!ms) 195 return false; 196 197 if (!ms->has_children) 198 return false; 199 200 ms->unfolded = !ms->unfolded; 201 return true; 202 } 203 204 static void callchain_node__init_have_children_rb_tree(struct callchain_node *node) 205 { 206 struct rb_node *nd = rb_first(&node->rb_root); 207 208 for (nd = rb_first(&node->rb_root); nd; nd = rb_next(nd)) { 209 struct callchain_node *child = rb_entry(nd, struct callchain_node, rb_node); 210 struct callchain_list *chain; 211 bool first = true; 212 213 list_for_each_entry(chain, &child->val, list) { 214 if (first) { 215 first = false; 216 chain->ms.has_children = chain->list.next != &child->val || 217 !RB_EMPTY_ROOT(&child->rb_root); 218 } else 219 chain->ms.has_children = chain->list.next == &child->val && 220 !RB_EMPTY_ROOT(&child->rb_root); 221 } 222 223 callchain_node__init_have_children_rb_tree(child); 224 } 225 } 226 227 static void callchain_node__init_have_children(struct callchain_node *node) 228 { 229 struct callchain_list *chain; 230 231 list_for_each_entry(chain, &node->val, list) 232 chain->ms.has_children = !RB_EMPTY_ROOT(&node->rb_root); 233 234 callchain_node__init_have_children_rb_tree(node); 235 } 236 237 static void callchain__init_have_children(struct rb_root *root) 238 { 239 struct rb_node *nd; 240 241 for (nd = rb_first(root); nd; nd = rb_next(nd)) { 242 struct callchain_node *node = rb_entry(nd, struct callchain_node, rb_node); 243 callchain_node__init_have_children(node); 244 } 245 } 246 247 static void hist_entry__init_have_children(struct hist_entry *he) 248 { 249 if (!he->init_have_children) { 250 he->ms.has_children = !RB_EMPTY_ROOT(&he->sorted_chain); 251 callchain__init_have_children(&he->sorted_chain); 252 he->init_have_children = true; 253 } 254 } 255 256 static bool hist_browser__toggle_fold(struct hist_browser *browser) 257 { 258 if (map_symbol__toggle_fold(browser->selection)) { 259 struct hist_entry *he = browser->he_selection; 260 261 hist_entry__init_have_children(he); 262 browser->b.nr_entries -= he->nr_rows; 263 browser->nr_callchain_rows -= he->nr_rows; 264 265 if (he->ms.unfolded) 266 he->nr_rows = callchain__count_rows(&he->sorted_chain); 267 else 268 he->nr_rows = 0; 269 270 browser->b.nr_entries += he->nr_rows; 271 browser->nr_callchain_rows += he->nr_rows; 272 273 return true; 274 } 275 276 /* If it doesn't have children, no toggling performed */ 277 return false; 278 } 279 280 static int callchain_node__set_folding_rb_tree(struct callchain_node *node, bool unfold) 281 { 282 int n = 0; 283 struct rb_node *nd; 284 285 for (nd = rb_first(&node->rb_root); nd; nd = rb_next(nd)) { 286 struct callchain_node *child = rb_entry(nd, struct callchain_node, rb_node); 287 struct callchain_list *chain; 288 bool has_children = false; 289 290 list_for_each_entry(chain, &child->val, list) { 291 ++n; 292 map_symbol__set_folding(&chain->ms, unfold); 293 has_children = chain->ms.has_children; 294 } 295 296 if (has_children) 297 n += callchain_node__set_folding_rb_tree(child, unfold); 298 } 299 300 return n; 301 } 302 303 static int callchain_node__set_folding(struct callchain_node *node, bool unfold) 304 { 305 struct callchain_list *chain; 306 bool has_children = false; 307 int n = 0; 308 309 list_for_each_entry(chain, &node->val, list) { 310 ++n; 311 map_symbol__set_folding(&chain->ms, unfold); 312 has_children = chain->ms.has_children; 313 } 314 315 if (has_children) 316 n += callchain_node__set_folding_rb_tree(node, unfold); 317 318 return n; 319 } 320 321 static int callchain__set_folding(struct rb_root *chain, bool unfold) 322 { 323 struct rb_node *nd; 324 int n = 0; 325 326 for (nd = rb_first(chain); nd; nd = rb_next(nd)) { 327 struct callchain_node *node = rb_entry(nd, struct callchain_node, rb_node); 328 n += callchain_node__set_folding(node, unfold); 329 } 330 331 return n; 332 } 333 334 static void hist_entry__set_folding(struct hist_entry *he, bool unfold) 335 { 336 hist_entry__init_have_children(he); 337 map_symbol__set_folding(&he->ms, unfold); 338 339 if (he->ms.has_children) { 340 int n = callchain__set_folding(&he->sorted_chain, unfold); 341 he->nr_rows = unfold ? n : 0; 342 } else 343 he->nr_rows = 0; 344 } 345 346 static void 347 __hist_browser__set_folding(struct hist_browser *browser, bool unfold) 348 { 349 struct rb_node *nd; 350 struct hists *hists = browser->hists; 351 352 for (nd = rb_first(&hists->entries); 353 (nd = hists__filter_entries(nd, browser->min_pcnt)) != NULL; 354 nd = rb_next(nd)) { 355 struct hist_entry *he = rb_entry(nd, struct hist_entry, rb_node); 356 hist_entry__set_folding(he, unfold); 357 browser->nr_callchain_rows += he->nr_rows; 358 } 359 } 360 361 static void hist_browser__set_folding(struct hist_browser *browser, bool unfold) 362 { 363 browser->nr_callchain_rows = 0; 364 __hist_browser__set_folding(browser, unfold); 365 366 browser->b.nr_entries = hist_browser__nr_entries(browser); 367 /* Go to the start, we may be way after valid entries after a collapse */ 368 ui_browser__reset_index(&browser->b); 369 } 370 371 static void ui_browser__warn_lost_events(struct ui_browser *browser) 372 { 373 ui_browser__warning(browser, 4, 374 "Events are being lost, check IO/CPU overload!\n\n" 375 "You may want to run 'perf' using a RT scheduler policy:\n\n" 376 " perf top -r 80\n\n" 377 "Or reduce the sampling frequency."); 378 } 379 380 static int hist_browser__run(struct hist_browser *browser, 381 struct hist_browser_timer *hbt) 382 { 383 int key; 384 char title[160]; 385 int delay_secs = hbt ? hbt->refresh : 0; 386 387 browser->b.entries = &browser->hists->entries; 388 browser->b.nr_entries = hist_browser__nr_entries(browser); 389 390 hists__browser_title(browser->hists, title, sizeof(title)); 391 392 if (ui_browser__show(&browser->b, title, 393 "Press '?' for help on key bindings") < 0) 394 return -1; 395 396 while (1) { 397 key = ui_browser__run(&browser->b, delay_secs); 398 399 switch (key) { 400 case K_TIMER: { 401 u64 nr_entries; 402 hbt->timer(hbt->arg); 403 404 if (hist_browser__has_filter(browser)) 405 hist_browser__update_nr_entries(browser); 406 407 nr_entries = hist_browser__nr_entries(browser); 408 ui_browser__update_nr_entries(&browser->b, nr_entries); 409 410 if (browser->hists->stats.nr_lost_warned != 411 browser->hists->stats.nr_events[PERF_RECORD_LOST]) { 412 browser->hists->stats.nr_lost_warned = 413 browser->hists->stats.nr_events[PERF_RECORD_LOST]; 414 ui_browser__warn_lost_events(&browser->b); 415 } 416 417 hists__browser_title(browser->hists, title, sizeof(title)); 418 ui_browser__show_title(&browser->b, title); 419 continue; 420 } 421 case 'D': { /* Debug */ 422 static int seq; 423 struct hist_entry *h = rb_entry(browser->b.top, 424 struct hist_entry, rb_node); 425 ui_helpline__pop(); 426 ui_helpline__fpush("%d: nr_ent=(%d,%d), rows=%d, idx=%d, fve: idx=%d, row_off=%d, nrows=%d", 427 seq++, browser->b.nr_entries, 428 browser->hists->nr_entries, 429 browser->b.rows, 430 browser->b.index, 431 browser->b.top_idx, 432 h->row_offset, h->nr_rows); 433 } 434 break; 435 case 'C': 436 /* Collapse the whole world. */ 437 hist_browser__set_folding(browser, false); 438 break; 439 case 'E': 440 /* Expand the whole world. */ 441 hist_browser__set_folding(browser, true); 442 break; 443 case 'H': 444 browser->show_headers = !browser->show_headers; 445 hist_browser__update_rows(browser); 446 break; 447 case K_ENTER: 448 if (hist_browser__toggle_fold(browser)) 449 break; 450 /* fall thru */ 451 default: 452 goto out; 453 } 454 } 455 out: 456 ui_browser__hide(&browser->b); 457 return key; 458 } 459 460 static char *callchain_list__sym_name(struct callchain_list *cl, 461 char *bf, size_t bfsize, bool show_dso) 462 { 463 int printed; 464 465 if (cl->ms.sym) 466 printed = scnprintf(bf, bfsize, "%s", cl->ms.sym->name); 467 else 468 printed = scnprintf(bf, bfsize, "%#" PRIx64, cl->ip); 469 470 if (show_dso) 471 scnprintf(bf + printed, bfsize - printed, " %s", 472 cl->ms.map ? cl->ms.map->dso->short_name : "unknown"); 473 474 return bf; 475 } 476 477 #define LEVEL_OFFSET_STEP 3 478 479 static int hist_browser__show_callchain_node_rb_tree(struct hist_browser *browser, 480 struct callchain_node *chain_node, 481 u64 total, int level, 482 unsigned short row, 483 off_t *row_offset, 484 bool *is_current_entry) 485 { 486 struct rb_node *node; 487 int first_row = row, width, offset = level * LEVEL_OFFSET_STEP; 488 u64 new_total, remaining; 489 490 if (callchain_param.mode == CHAIN_GRAPH_REL) 491 new_total = chain_node->children_hit; 492 else 493 new_total = total; 494 495 remaining = new_total; 496 node = rb_first(&chain_node->rb_root); 497 while (node) { 498 struct callchain_node *child = rb_entry(node, struct callchain_node, rb_node); 499 struct rb_node *next = rb_next(node); 500 u64 cumul = callchain_cumul_hits(child); 501 struct callchain_list *chain; 502 char folded_sign = ' '; 503 int first = true; 504 int extra_offset = 0; 505 506 remaining -= cumul; 507 508 list_for_each_entry(chain, &child->val, list) { 509 char bf[1024], *alloc_str; 510 const char *str; 511 int color; 512 bool was_first = first; 513 514 if (first) 515 first = false; 516 else 517 extra_offset = LEVEL_OFFSET_STEP; 518 519 folded_sign = callchain_list__folded(chain); 520 if (*row_offset != 0) { 521 --*row_offset; 522 goto do_next; 523 } 524 525 alloc_str = NULL; 526 str = callchain_list__sym_name(chain, bf, sizeof(bf), 527 browser->show_dso); 528 if (was_first) { 529 double percent = cumul * 100.0 / new_total; 530 531 if (asprintf(&alloc_str, "%2.2f%% %s", percent, str) < 0) 532 str = "Not enough memory!"; 533 else 534 str = alloc_str; 535 } 536 537 color = HE_COLORSET_NORMAL; 538 width = browser->b.width - (offset + extra_offset + 2); 539 if (ui_browser__is_current_entry(&browser->b, row)) { 540 browser->selection = &chain->ms; 541 color = HE_COLORSET_SELECTED; 542 *is_current_entry = true; 543 } 544 545 ui_browser__set_color(&browser->b, color); 546 hist_browser__gotorc(browser, row, 0); 547 slsmg_write_nstring(" ", offset + extra_offset); 548 slsmg_printf("%c ", folded_sign); 549 slsmg_write_nstring(str, width); 550 free(alloc_str); 551 552 if (++row == browser->b.rows) 553 goto out; 554 do_next: 555 if (folded_sign == '+') 556 break; 557 } 558 559 if (folded_sign == '-') { 560 const int new_level = level + (extra_offset ? 2 : 1); 561 row += hist_browser__show_callchain_node_rb_tree(browser, child, new_total, 562 new_level, row, row_offset, 563 is_current_entry); 564 } 565 if (row == browser->b.rows) 566 goto out; 567 node = next; 568 } 569 out: 570 return row - first_row; 571 } 572 573 static int hist_browser__show_callchain_node(struct hist_browser *browser, 574 struct callchain_node *node, 575 int level, unsigned short row, 576 off_t *row_offset, 577 bool *is_current_entry) 578 { 579 struct callchain_list *chain; 580 int first_row = row, 581 offset = level * LEVEL_OFFSET_STEP, 582 width = browser->b.width - offset; 583 char folded_sign = ' '; 584 585 list_for_each_entry(chain, &node->val, list) { 586 char bf[1024], *s; 587 int color; 588 589 folded_sign = callchain_list__folded(chain); 590 591 if (*row_offset != 0) { 592 --*row_offset; 593 continue; 594 } 595 596 color = HE_COLORSET_NORMAL; 597 if (ui_browser__is_current_entry(&browser->b, row)) { 598 browser->selection = &chain->ms; 599 color = HE_COLORSET_SELECTED; 600 *is_current_entry = true; 601 } 602 603 s = callchain_list__sym_name(chain, bf, sizeof(bf), 604 browser->show_dso); 605 hist_browser__gotorc(browser, row, 0); 606 ui_browser__set_color(&browser->b, color); 607 slsmg_write_nstring(" ", offset); 608 slsmg_printf("%c ", folded_sign); 609 slsmg_write_nstring(s, width - 2); 610 611 if (++row == browser->b.rows) 612 goto out; 613 } 614 615 if (folded_sign == '-') 616 row += hist_browser__show_callchain_node_rb_tree(browser, node, 617 browser->hists->stats.total_period, 618 level + 1, row, 619 row_offset, 620 is_current_entry); 621 out: 622 return row - first_row; 623 } 624 625 static int hist_browser__show_callchain(struct hist_browser *browser, 626 struct rb_root *chain, 627 int level, unsigned short row, 628 off_t *row_offset, 629 bool *is_current_entry) 630 { 631 struct rb_node *nd; 632 int first_row = row; 633 634 for (nd = rb_first(chain); nd; nd = rb_next(nd)) { 635 struct callchain_node *node = rb_entry(nd, struct callchain_node, rb_node); 636 637 row += hist_browser__show_callchain_node(browser, node, level, 638 row, row_offset, 639 is_current_entry); 640 if (row == browser->b.rows) 641 break; 642 } 643 644 return row - first_row; 645 } 646 647 struct hpp_arg { 648 struct ui_browser *b; 649 char folded_sign; 650 bool current_entry; 651 }; 652 653 static int __hpp__slsmg_color_printf(struct perf_hpp *hpp, const char *fmt, ...) 654 { 655 struct hpp_arg *arg = hpp->ptr; 656 int ret; 657 va_list args; 658 double percent; 659 660 va_start(args, fmt); 661 percent = va_arg(args, double); 662 va_end(args); 663 664 ui_browser__set_percent_color(arg->b, percent, arg->current_entry); 665 666 ret = scnprintf(hpp->buf, hpp->size, fmt, percent); 667 slsmg_printf("%s", hpp->buf); 668 669 advance_hpp(hpp, ret); 670 return ret; 671 } 672 673 #define __HPP_COLOR_PERCENT_FN(_type, _field) \ 674 static u64 __hpp_get_##_field(struct hist_entry *he) \ 675 { \ 676 return he->stat._field; \ 677 } \ 678 \ 679 static int \ 680 hist_browser__hpp_color_##_type(struct perf_hpp_fmt *fmt __maybe_unused,\ 681 struct perf_hpp *hpp, \ 682 struct hist_entry *he) \ 683 { \ 684 return __hpp__fmt(hpp, he, __hpp_get_##_field, " %6.2f%%", \ 685 __hpp__slsmg_color_printf, true); \ 686 } 687 688 #define __HPP_COLOR_ACC_PERCENT_FN(_type, _field) \ 689 static u64 __hpp_get_acc_##_field(struct hist_entry *he) \ 690 { \ 691 return he->stat_acc->_field; \ 692 } \ 693 \ 694 static int \ 695 hist_browser__hpp_color_##_type(struct perf_hpp_fmt *fmt __maybe_unused,\ 696 struct perf_hpp *hpp, \ 697 struct hist_entry *he) \ 698 { \ 699 if (!symbol_conf.cumulate_callchain) { \ 700 int ret = scnprintf(hpp->buf, hpp->size, "%8s", "N/A"); \ 701 slsmg_printf("%s", hpp->buf); \ 702 \ 703 return ret; \ 704 } \ 705 return __hpp__fmt(hpp, he, __hpp_get_acc_##_field, " %6.2f%%", \ 706 __hpp__slsmg_color_printf, true); \ 707 } 708 709 __HPP_COLOR_PERCENT_FN(overhead, period) 710 __HPP_COLOR_PERCENT_FN(overhead_sys, period_sys) 711 __HPP_COLOR_PERCENT_FN(overhead_us, period_us) 712 __HPP_COLOR_PERCENT_FN(overhead_guest_sys, period_guest_sys) 713 __HPP_COLOR_PERCENT_FN(overhead_guest_us, period_guest_us) 714 __HPP_COLOR_ACC_PERCENT_FN(overhead_acc, period) 715 716 #undef __HPP_COLOR_PERCENT_FN 717 #undef __HPP_COLOR_ACC_PERCENT_FN 718 719 void hist_browser__init_hpp(void) 720 { 721 perf_hpp__format[PERF_HPP__OVERHEAD].color = 722 hist_browser__hpp_color_overhead; 723 perf_hpp__format[PERF_HPP__OVERHEAD_SYS].color = 724 hist_browser__hpp_color_overhead_sys; 725 perf_hpp__format[PERF_HPP__OVERHEAD_US].color = 726 hist_browser__hpp_color_overhead_us; 727 perf_hpp__format[PERF_HPP__OVERHEAD_GUEST_SYS].color = 728 hist_browser__hpp_color_overhead_guest_sys; 729 perf_hpp__format[PERF_HPP__OVERHEAD_GUEST_US].color = 730 hist_browser__hpp_color_overhead_guest_us; 731 perf_hpp__format[PERF_HPP__OVERHEAD_ACC].color = 732 hist_browser__hpp_color_overhead_acc; 733 } 734 735 static int hist_browser__show_entry(struct hist_browser *browser, 736 struct hist_entry *entry, 737 unsigned short row) 738 { 739 char s[256]; 740 int printed = 0; 741 int width = browser->b.width; 742 char folded_sign = ' '; 743 bool current_entry = ui_browser__is_current_entry(&browser->b, row); 744 off_t row_offset = entry->row_offset; 745 bool first = true; 746 struct perf_hpp_fmt *fmt; 747 748 if (current_entry) { 749 browser->he_selection = entry; 750 browser->selection = &entry->ms; 751 } 752 753 if (symbol_conf.use_callchain) { 754 hist_entry__init_have_children(entry); 755 folded_sign = hist_entry__folded(entry); 756 } 757 758 if (row_offset == 0) { 759 struct hpp_arg arg = { 760 .b = &browser->b, 761 .folded_sign = folded_sign, 762 .current_entry = current_entry, 763 }; 764 struct perf_hpp hpp = { 765 .buf = s, 766 .size = sizeof(s), 767 .ptr = &arg, 768 }; 769 770 hist_browser__gotorc(browser, row, 0); 771 772 perf_hpp__for_each_format(fmt) { 773 if (perf_hpp__should_skip(fmt)) 774 continue; 775 776 if (current_entry && browser->b.navkeypressed) { 777 ui_browser__set_color(&browser->b, 778 HE_COLORSET_SELECTED); 779 } else { 780 ui_browser__set_color(&browser->b, 781 HE_COLORSET_NORMAL); 782 } 783 784 if (first) { 785 if (symbol_conf.use_callchain) { 786 slsmg_printf("%c ", folded_sign); 787 width -= 2; 788 } 789 first = false; 790 } else { 791 slsmg_printf(" "); 792 width -= 2; 793 } 794 795 if (fmt->color) { 796 width -= fmt->color(fmt, &hpp, entry); 797 } else { 798 width -= fmt->entry(fmt, &hpp, entry); 799 slsmg_printf("%s", s); 800 } 801 } 802 803 /* The scroll bar isn't being used */ 804 if (!browser->b.navkeypressed) 805 width += 1; 806 807 slsmg_write_nstring("", width); 808 809 ++row; 810 ++printed; 811 } else 812 --row_offset; 813 814 if (folded_sign == '-' && row != browser->b.rows) { 815 printed += hist_browser__show_callchain(browser, &entry->sorted_chain, 816 1, row, &row_offset, 817 ¤t_entry); 818 if (current_entry) 819 browser->he_selection = entry; 820 } 821 822 return printed; 823 } 824 825 static int advance_hpp_check(struct perf_hpp *hpp, int inc) 826 { 827 advance_hpp(hpp, inc); 828 return hpp->size <= 0; 829 } 830 831 static int hists__scnprintf_headers(char *buf, size_t size, struct hists *hists) 832 { 833 struct perf_hpp dummy_hpp = { 834 .buf = buf, 835 .size = size, 836 }; 837 struct perf_hpp_fmt *fmt; 838 size_t ret = 0; 839 840 if (symbol_conf.use_callchain) { 841 ret = scnprintf(buf, size, " "); 842 if (advance_hpp_check(&dummy_hpp, ret)) 843 return ret; 844 } 845 846 perf_hpp__for_each_format(fmt) { 847 if (perf_hpp__should_skip(fmt)) 848 continue; 849 850 /* We need to add the length of the columns header. */ 851 perf_hpp__reset_width(fmt, hists); 852 853 ret = fmt->header(fmt, &dummy_hpp, hists_to_evsel(hists)); 854 if (advance_hpp_check(&dummy_hpp, ret)) 855 break; 856 857 ret = scnprintf(dummy_hpp.buf, dummy_hpp.size, " "); 858 if (advance_hpp_check(&dummy_hpp, ret)) 859 break; 860 } 861 862 return ret; 863 } 864 865 static void hist_browser__show_headers(struct hist_browser *browser) 866 { 867 char headers[1024]; 868 869 hists__scnprintf_headers(headers, sizeof(headers), browser->hists); 870 ui_browser__gotorc(&browser->b, 0, 0); 871 ui_browser__set_color(&browser->b, HE_COLORSET_ROOT); 872 slsmg_write_nstring(headers, browser->b.width + 1); 873 } 874 875 static void ui_browser__hists_init_top(struct ui_browser *browser) 876 { 877 if (browser->top == NULL) { 878 struct hist_browser *hb; 879 880 hb = container_of(browser, struct hist_browser, b); 881 browser->top = rb_first(&hb->hists->entries); 882 } 883 } 884 885 static unsigned int hist_browser__refresh(struct ui_browser *browser) 886 { 887 unsigned row = 0; 888 u16 header_offset = 0; 889 struct rb_node *nd; 890 struct hist_browser *hb = container_of(browser, struct hist_browser, b); 891 892 if (hb->show_headers) { 893 hist_browser__show_headers(hb); 894 header_offset = 1; 895 } 896 897 ui_browser__hists_init_top(browser); 898 899 for (nd = browser->top; nd; nd = rb_next(nd)) { 900 struct hist_entry *h = rb_entry(nd, struct hist_entry, rb_node); 901 float percent; 902 903 if (h->filtered) 904 continue; 905 906 percent = hist_entry__get_percent_limit(h); 907 if (percent < hb->min_pcnt) 908 continue; 909 910 row += hist_browser__show_entry(hb, h, row); 911 if (row == browser->rows) 912 break; 913 } 914 915 return row + header_offset; 916 } 917 918 static struct rb_node *hists__filter_entries(struct rb_node *nd, 919 float min_pcnt) 920 { 921 while (nd != NULL) { 922 struct hist_entry *h = rb_entry(nd, struct hist_entry, rb_node); 923 float percent = hist_entry__get_percent_limit(h); 924 925 if (!h->filtered && percent >= min_pcnt) 926 return nd; 927 928 nd = rb_next(nd); 929 } 930 931 return NULL; 932 } 933 934 static struct rb_node *hists__filter_prev_entries(struct rb_node *nd, 935 float min_pcnt) 936 { 937 while (nd != NULL) { 938 struct hist_entry *h = rb_entry(nd, struct hist_entry, rb_node); 939 float percent = hist_entry__get_percent_limit(h); 940 941 if (!h->filtered && percent >= min_pcnt) 942 return nd; 943 944 nd = rb_prev(nd); 945 } 946 947 return NULL; 948 } 949 950 static void ui_browser__hists_seek(struct ui_browser *browser, 951 off_t offset, int whence) 952 { 953 struct hist_entry *h; 954 struct rb_node *nd; 955 bool first = true; 956 struct hist_browser *hb; 957 958 hb = container_of(browser, struct hist_browser, b); 959 960 if (browser->nr_entries == 0) 961 return; 962 963 ui_browser__hists_init_top(browser); 964 965 switch (whence) { 966 case SEEK_SET: 967 nd = hists__filter_entries(rb_first(browser->entries), 968 hb->min_pcnt); 969 break; 970 case SEEK_CUR: 971 nd = browser->top; 972 goto do_offset; 973 case SEEK_END: 974 nd = hists__filter_prev_entries(rb_last(browser->entries), 975 hb->min_pcnt); 976 first = false; 977 break; 978 default: 979 return; 980 } 981 982 /* 983 * Moves not relative to the first visible entry invalidates its 984 * row_offset: 985 */ 986 h = rb_entry(browser->top, struct hist_entry, rb_node); 987 h->row_offset = 0; 988 989 /* 990 * Here we have to check if nd is expanded (+), if it is we can't go 991 * the next top level hist_entry, instead we must compute an offset of 992 * what _not_ to show and not change the first visible entry. 993 * 994 * This offset increments when we are going from top to bottom and 995 * decreases when we're going from bottom to top. 996 * 997 * As we don't have backpointers to the top level in the callchains 998 * structure, we need to always print the whole hist_entry callchain, 999 * skipping the first ones that are before the first visible entry 1000 * and stop when we printed enough lines to fill the screen. 1001 */ 1002 do_offset: 1003 if (offset > 0) { 1004 do { 1005 h = rb_entry(nd, struct hist_entry, rb_node); 1006 if (h->ms.unfolded) { 1007 u16 remaining = h->nr_rows - h->row_offset; 1008 if (offset > remaining) { 1009 offset -= remaining; 1010 h->row_offset = 0; 1011 } else { 1012 h->row_offset += offset; 1013 offset = 0; 1014 browser->top = nd; 1015 break; 1016 } 1017 } 1018 nd = hists__filter_entries(rb_next(nd), hb->min_pcnt); 1019 if (nd == NULL) 1020 break; 1021 --offset; 1022 browser->top = nd; 1023 } while (offset != 0); 1024 } else if (offset < 0) { 1025 while (1) { 1026 h = rb_entry(nd, struct hist_entry, rb_node); 1027 if (h->ms.unfolded) { 1028 if (first) { 1029 if (-offset > h->row_offset) { 1030 offset += h->row_offset; 1031 h->row_offset = 0; 1032 } else { 1033 h->row_offset += offset; 1034 offset = 0; 1035 browser->top = nd; 1036 break; 1037 } 1038 } else { 1039 if (-offset > h->nr_rows) { 1040 offset += h->nr_rows; 1041 h->row_offset = 0; 1042 } else { 1043 h->row_offset = h->nr_rows + offset; 1044 offset = 0; 1045 browser->top = nd; 1046 break; 1047 } 1048 } 1049 } 1050 1051 nd = hists__filter_prev_entries(rb_prev(nd), 1052 hb->min_pcnt); 1053 if (nd == NULL) 1054 break; 1055 ++offset; 1056 browser->top = nd; 1057 if (offset == 0) { 1058 /* 1059 * Last unfiltered hist_entry, check if it is 1060 * unfolded, if it is then we should have 1061 * row_offset at its last entry. 1062 */ 1063 h = rb_entry(nd, struct hist_entry, rb_node); 1064 if (h->ms.unfolded) 1065 h->row_offset = h->nr_rows; 1066 break; 1067 } 1068 first = false; 1069 } 1070 } else { 1071 browser->top = nd; 1072 h = rb_entry(nd, struct hist_entry, rb_node); 1073 h->row_offset = 0; 1074 } 1075 } 1076 1077 static int hist_browser__fprintf_callchain_node_rb_tree(struct hist_browser *browser, 1078 struct callchain_node *chain_node, 1079 u64 total, int level, 1080 FILE *fp) 1081 { 1082 struct rb_node *node; 1083 int offset = level * LEVEL_OFFSET_STEP; 1084 u64 new_total, remaining; 1085 int printed = 0; 1086 1087 if (callchain_param.mode == CHAIN_GRAPH_REL) 1088 new_total = chain_node->children_hit; 1089 else 1090 new_total = total; 1091 1092 remaining = new_total; 1093 node = rb_first(&chain_node->rb_root); 1094 while (node) { 1095 struct callchain_node *child = rb_entry(node, struct callchain_node, rb_node); 1096 struct rb_node *next = rb_next(node); 1097 u64 cumul = callchain_cumul_hits(child); 1098 struct callchain_list *chain; 1099 char folded_sign = ' '; 1100 int first = true; 1101 int extra_offset = 0; 1102 1103 remaining -= cumul; 1104 1105 list_for_each_entry(chain, &child->val, list) { 1106 char bf[1024], *alloc_str; 1107 const char *str; 1108 bool was_first = first; 1109 1110 if (first) 1111 first = false; 1112 else 1113 extra_offset = LEVEL_OFFSET_STEP; 1114 1115 folded_sign = callchain_list__folded(chain); 1116 1117 alloc_str = NULL; 1118 str = callchain_list__sym_name(chain, bf, sizeof(bf), 1119 browser->show_dso); 1120 if (was_first) { 1121 double percent = cumul * 100.0 / new_total; 1122 1123 if (asprintf(&alloc_str, "%2.2f%% %s", percent, str) < 0) 1124 str = "Not enough memory!"; 1125 else 1126 str = alloc_str; 1127 } 1128 1129 printed += fprintf(fp, "%*s%c %s\n", offset + extra_offset, " ", folded_sign, str); 1130 free(alloc_str); 1131 if (folded_sign == '+') 1132 break; 1133 } 1134 1135 if (folded_sign == '-') { 1136 const int new_level = level + (extra_offset ? 2 : 1); 1137 printed += hist_browser__fprintf_callchain_node_rb_tree(browser, child, new_total, 1138 new_level, fp); 1139 } 1140 1141 node = next; 1142 } 1143 1144 return printed; 1145 } 1146 1147 static int hist_browser__fprintf_callchain_node(struct hist_browser *browser, 1148 struct callchain_node *node, 1149 int level, FILE *fp) 1150 { 1151 struct callchain_list *chain; 1152 int offset = level * LEVEL_OFFSET_STEP; 1153 char folded_sign = ' '; 1154 int printed = 0; 1155 1156 list_for_each_entry(chain, &node->val, list) { 1157 char bf[1024], *s; 1158 1159 folded_sign = callchain_list__folded(chain); 1160 s = callchain_list__sym_name(chain, bf, sizeof(bf), browser->show_dso); 1161 printed += fprintf(fp, "%*s%c %s\n", offset, " ", folded_sign, s); 1162 } 1163 1164 if (folded_sign == '-') 1165 printed += hist_browser__fprintf_callchain_node_rb_tree(browser, node, 1166 browser->hists->stats.total_period, 1167 level + 1, fp); 1168 return printed; 1169 } 1170 1171 static int hist_browser__fprintf_callchain(struct hist_browser *browser, 1172 struct rb_root *chain, int level, FILE *fp) 1173 { 1174 struct rb_node *nd; 1175 int printed = 0; 1176 1177 for (nd = rb_first(chain); nd; nd = rb_next(nd)) { 1178 struct callchain_node *node = rb_entry(nd, struct callchain_node, rb_node); 1179 1180 printed += hist_browser__fprintf_callchain_node(browser, node, level, fp); 1181 } 1182 1183 return printed; 1184 } 1185 1186 static int hist_browser__fprintf_entry(struct hist_browser *browser, 1187 struct hist_entry *he, FILE *fp) 1188 { 1189 char s[8192]; 1190 int printed = 0; 1191 char folded_sign = ' '; 1192 struct perf_hpp hpp = { 1193 .buf = s, 1194 .size = sizeof(s), 1195 }; 1196 struct perf_hpp_fmt *fmt; 1197 bool first = true; 1198 int ret; 1199 1200 if (symbol_conf.use_callchain) 1201 folded_sign = hist_entry__folded(he); 1202 1203 if (symbol_conf.use_callchain) 1204 printed += fprintf(fp, "%c ", folded_sign); 1205 1206 perf_hpp__for_each_format(fmt) { 1207 if (perf_hpp__should_skip(fmt)) 1208 continue; 1209 1210 if (!first) { 1211 ret = scnprintf(hpp.buf, hpp.size, " "); 1212 advance_hpp(&hpp, ret); 1213 } else 1214 first = false; 1215 1216 ret = fmt->entry(fmt, &hpp, he); 1217 advance_hpp(&hpp, ret); 1218 } 1219 printed += fprintf(fp, "%s\n", rtrim(s)); 1220 1221 if (folded_sign == '-') 1222 printed += hist_browser__fprintf_callchain(browser, &he->sorted_chain, 1, fp); 1223 1224 return printed; 1225 } 1226 1227 static int hist_browser__fprintf(struct hist_browser *browser, FILE *fp) 1228 { 1229 struct rb_node *nd = hists__filter_entries(rb_first(browser->b.entries), 1230 browser->min_pcnt); 1231 int printed = 0; 1232 1233 while (nd) { 1234 struct hist_entry *h = rb_entry(nd, struct hist_entry, rb_node); 1235 1236 printed += hist_browser__fprintf_entry(browser, h, fp); 1237 nd = hists__filter_entries(rb_next(nd), browser->min_pcnt); 1238 } 1239 1240 return printed; 1241 } 1242 1243 static int hist_browser__dump(struct hist_browser *browser) 1244 { 1245 char filename[64]; 1246 FILE *fp; 1247 1248 while (1) { 1249 scnprintf(filename, sizeof(filename), "perf.hist.%d", browser->print_seq); 1250 if (access(filename, F_OK)) 1251 break; 1252 /* 1253 * XXX: Just an arbitrary lazy upper limit 1254 */ 1255 if (++browser->print_seq == 8192) { 1256 ui_helpline__fpush("Too many perf.hist.N files, nothing written!"); 1257 return -1; 1258 } 1259 } 1260 1261 fp = fopen(filename, "w"); 1262 if (fp == NULL) { 1263 char bf[64]; 1264 const char *err = strerror_r(errno, bf, sizeof(bf)); 1265 ui_helpline__fpush("Couldn't write to %s: %s", filename, err); 1266 return -1; 1267 } 1268 1269 ++browser->print_seq; 1270 hist_browser__fprintf(browser, fp); 1271 fclose(fp); 1272 ui_helpline__fpush("%s written!", filename); 1273 1274 return 0; 1275 } 1276 1277 static struct hist_browser *hist_browser__new(struct hists *hists) 1278 { 1279 struct hist_browser *browser = zalloc(sizeof(*browser)); 1280 1281 if (browser) { 1282 browser->hists = hists; 1283 browser->b.refresh = hist_browser__refresh; 1284 browser->b.refresh_dimensions = hist_browser__refresh_dimensions; 1285 browser->b.seek = ui_browser__hists_seek; 1286 browser->b.use_navkeypressed = true; 1287 browser->show_headers = symbol_conf.show_hist_headers; 1288 } 1289 1290 return browser; 1291 } 1292 1293 static void hist_browser__delete(struct hist_browser *browser) 1294 { 1295 free(browser); 1296 } 1297 1298 static struct hist_entry *hist_browser__selected_entry(struct hist_browser *browser) 1299 { 1300 return browser->he_selection; 1301 } 1302 1303 static struct thread *hist_browser__selected_thread(struct hist_browser *browser) 1304 { 1305 return browser->he_selection->thread; 1306 } 1307 1308 static int hists__browser_title(struct hists *hists, char *bf, size_t size) 1309 { 1310 char unit; 1311 int printed; 1312 const struct dso *dso = hists->dso_filter; 1313 const struct thread *thread = hists->thread_filter; 1314 unsigned long nr_samples = hists->stats.nr_events[PERF_RECORD_SAMPLE]; 1315 u64 nr_events = hists->stats.total_period; 1316 struct perf_evsel *evsel = hists_to_evsel(hists); 1317 const char *ev_name = perf_evsel__name(evsel); 1318 char buf[512]; 1319 size_t buflen = sizeof(buf); 1320 1321 if (symbol_conf.filter_relative) { 1322 nr_samples = hists->stats.nr_non_filtered_samples; 1323 nr_events = hists->stats.total_non_filtered_period; 1324 } 1325 1326 if (perf_evsel__is_group_event(evsel)) { 1327 struct perf_evsel *pos; 1328 1329 perf_evsel__group_desc(evsel, buf, buflen); 1330 ev_name = buf; 1331 1332 for_each_group_member(pos, evsel) { 1333 if (symbol_conf.filter_relative) { 1334 nr_samples += pos->hists.stats.nr_non_filtered_samples; 1335 nr_events += pos->hists.stats.total_non_filtered_period; 1336 } else { 1337 nr_samples += pos->hists.stats.nr_events[PERF_RECORD_SAMPLE]; 1338 nr_events += pos->hists.stats.total_period; 1339 } 1340 } 1341 } 1342 1343 nr_samples = convert_unit(nr_samples, &unit); 1344 printed = scnprintf(bf, size, 1345 "Samples: %lu%c of event '%s', Event count (approx.): %lu", 1346 nr_samples, unit, ev_name, nr_events); 1347 1348 1349 if (hists->uid_filter_str) 1350 printed += snprintf(bf + printed, size - printed, 1351 ", UID: %s", hists->uid_filter_str); 1352 if (thread) 1353 printed += scnprintf(bf + printed, size - printed, 1354 ", Thread: %s(%d)", 1355 (thread->comm_set ? thread__comm_str(thread) : ""), 1356 thread->tid); 1357 if (dso) 1358 printed += scnprintf(bf + printed, size - printed, 1359 ", DSO: %s", dso->short_name); 1360 return printed; 1361 } 1362 1363 static inline void free_popup_options(char **options, int n) 1364 { 1365 int i; 1366 1367 for (i = 0; i < n; ++i) 1368 zfree(&options[i]); 1369 } 1370 1371 /* Check whether the browser is for 'top' or 'report' */ 1372 static inline bool is_report_browser(void *timer) 1373 { 1374 return timer == NULL; 1375 } 1376 1377 /* 1378 * Only runtime switching of perf data file will make "input_name" point 1379 * to a malloced buffer. So add "is_input_name_malloced" flag to decide 1380 * whether we need to call free() for current "input_name" during the switch. 1381 */ 1382 static bool is_input_name_malloced = false; 1383 1384 static int switch_data_file(void) 1385 { 1386 char *pwd, *options[32], *abs_path[32], *tmp; 1387 DIR *pwd_dir; 1388 int nr_options = 0, choice = -1, ret = -1; 1389 struct dirent *dent; 1390 1391 pwd = getenv("PWD"); 1392 if (!pwd) 1393 return ret; 1394 1395 pwd_dir = opendir(pwd); 1396 if (!pwd_dir) 1397 return ret; 1398 1399 memset(options, 0, sizeof(options)); 1400 memset(options, 0, sizeof(abs_path)); 1401 1402 while ((dent = readdir(pwd_dir))) { 1403 char path[PATH_MAX]; 1404 u64 magic; 1405 char *name = dent->d_name; 1406 FILE *file; 1407 1408 if (!(dent->d_type == DT_REG)) 1409 continue; 1410 1411 snprintf(path, sizeof(path), "%s/%s", pwd, name); 1412 1413 file = fopen(path, "r"); 1414 if (!file) 1415 continue; 1416 1417 if (fread(&magic, 1, 8, file) < 8) 1418 goto close_file_and_continue; 1419 1420 if (is_perf_magic(magic)) { 1421 options[nr_options] = strdup(name); 1422 if (!options[nr_options]) 1423 goto close_file_and_continue; 1424 1425 abs_path[nr_options] = strdup(path); 1426 if (!abs_path[nr_options]) { 1427 zfree(&options[nr_options]); 1428 ui__warning("Can't search all data files due to memory shortage.\n"); 1429 fclose(file); 1430 break; 1431 } 1432 1433 nr_options++; 1434 } 1435 1436 close_file_and_continue: 1437 fclose(file); 1438 if (nr_options >= 32) { 1439 ui__warning("Too many perf data files in PWD!\n" 1440 "Only the first 32 files will be listed.\n"); 1441 break; 1442 } 1443 } 1444 closedir(pwd_dir); 1445 1446 if (nr_options) { 1447 choice = ui__popup_menu(nr_options, options); 1448 if (choice < nr_options && choice >= 0) { 1449 tmp = strdup(abs_path[choice]); 1450 if (tmp) { 1451 if (is_input_name_malloced) 1452 free((void *)input_name); 1453 input_name = tmp; 1454 is_input_name_malloced = true; 1455 ret = 0; 1456 } else 1457 ui__warning("Data switch failed due to memory shortage!\n"); 1458 } 1459 } 1460 1461 free_popup_options(options, nr_options); 1462 free_popup_options(abs_path, nr_options); 1463 return ret; 1464 } 1465 1466 static void hist_browser__update_nr_entries(struct hist_browser *hb) 1467 { 1468 u64 nr_entries = 0; 1469 struct rb_node *nd = rb_first(&hb->hists->entries); 1470 1471 if (hb->min_pcnt == 0) { 1472 hb->nr_non_filtered_entries = hb->hists->nr_non_filtered_entries; 1473 return; 1474 } 1475 1476 while ((nd = hists__filter_entries(nd, hb->min_pcnt)) != NULL) { 1477 nr_entries++; 1478 nd = rb_next(nd); 1479 } 1480 1481 hb->nr_non_filtered_entries = nr_entries; 1482 } 1483 1484 static int perf_evsel__hists_browse(struct perf_evsel *evsel, int nr_events, 1485 const char *helpline, 1486 bool left_exits, 1487 struct hist_browser_timer *hbt, 1488 float min_pcnt, 1489 struct perf_session_env *env) 1490 { 1491 struct hists *hists = &evsel->hists; 1492 struct hist_browser *browser = hist_browser__new(hists); 1493 struct branch_info *bi; 1494 struct pstack *fstack; 1495 char *options[16]; 1496 int nr_options = 0; 1497 int key = -1; 1498 char buf[64]; 1499 char script_opt[64]; 1500 int delay_secs = hbt ? hbt->refresh : 0; 1501 1502 #define HIST_BROWSER_HELP_COMMON \ 1503 "h/?/F1 Show this window\n" \ 1504 "UP/DOWN/PGUP\n" \ 1505 "PGDN/SPACE Navigate\n" \ 1506 "q/ESC/CTRL+C Exit browser\n\n" \ 1507 "For multiple event sessions:\n\n" \ 1508 "TAB/UNTAB Switch events\n\n" \ 1509 "For symbolic views (--sort has sym):\n\n" \ 1510 "-> Zoom into DSO/Threads & Annotate current symbol\n" \ 1511 "<- Zoom out\n" \ 1512 "a Annotate current symbol\n" \ 1513 "C Collapse all callchains\n" \ 1514 "d Zoom into current DSO\n" \ 1515 "E Expand all callchains\n" \ 1516 "F Toggle percentage of filtered entries\n" \ 1517 "H Display column headers\n" \ 1518 1519 /* help messages are sorted by lexical order of the hotkey */ 1520 const char report_help[] = HIST_BROWSER_HELP_COMMON 1521 "i Show header information\n" 1522 "P Print histograms to perf.hist.N\n" 1523 "r Run available scripts\n" 1524 "s Switch to another data file in PWD\n" 1525 "t Zoom into current Thread\n" 1526 "V Verbose (DSO names in callchains, etc)\n" 1527 "/ Filter symbol by name"; 1528 const char top_help[] = HIST_BROWSER_HELP_COMMON 1529 "P Print histograms to perf.hist.N\n" 1530 "t Zoom into current Thread\n" 1531 "V Verbose (DSO names in callchains, etc)\n" 1532 "/ Filter symbol by name"; 1533 1534 if (browser == NULL) 1535 return -1; 1536 1537 if (min_pcnt) { 1538 browser->min_pcnt = min_pcnt; 1539 hist_browser__update_nr_entries(browser); 1540 } 1541 1542 fstack = pstack__new(2); 1543 if (fstack == NULL) 1544 goto out; 1545 1546 ui_helpline__push(helpline); 1547 1548 memset(options, 0, sizeof(options)); 1549 1550 while (1) { 1551 const struct thread *thread = NULL; 1552 const struct dso *dso = NULL; 1553 int choice = 0, 1554 annotate = -2, zoom_dso = -2, zoom_thread = -2, 1555 annotate_f = -2, annotate_t = -2, browse_map = -2; 1556 int scripts_comm = -2, scripts_symbol = -2, 1557 scripts_all = -2, switch_data = -2; 1558 1559 nr_options = 0; 1560 1561 key = hist_browser__run(browser, hbt); 1562 1563 if (browser->he_selection != NULL) { 1564 thread = hist_browser__selected_thread(browser); 1565 dso = browser->selection->map ? browser->selection->map->dso : NULL; 1566 } 1567 switch (key) { 1568 case K_TAB: 1569 case K_UNTAB: 1570 if (nr_events == 1) 1571 continue; 1572 /* 1573 * Exit the browser, let hists__browser_tree 1574 * go to the next or previous 1575 */ 1576 goto out_free_stack; 1577 case 'a': 1578 if (!sort__has_sym) { 1579 ui_browser__warning(&browser->b, delay_secs * 2, 1580 "Annotation is only available for symbolic views, " 1581 "include \"sym*\" in --sort to use it."); 1582 continue; 1583 } 1584 1585 if (browser->selection == NULL || 1586 browser->selection->sym == NULL || 1587 browser->selection->map->dso->annotate_warned) 1588 continue; 1589 goto do_annotate; 1590 case 'P': 1591 hist_browser__dump(browser); 1592 continue; 1593 case 'd': 1594 goto zoom_dso; 1595 case 'V': 1596 browser->show_dso = !browser->show_dso; 1597 continue; 1598 case 't': 1599 goto zoom_thread; 1600 case '/': 1601 if (ui_browser__input_window("Symbol to show", 1602 "Please enter the name of symbol you want to see", 1603 buf, "ENTER: OK, ESC: Cancel", 1604 delay_secs * 2) == K_ENTER) { 1605 hists->symbol_filter_str = *buf ? buf : NULL; 1606 hists__filter_by_symbol(hists); 1607 hist_browser__reset(browser); 1608 } 1609 continue; 1610 case 'r': 1611 if (is_report_browser(hbt)) 1612 goto do_scripts; 1613 continue; 1614 case 's': 1615 if (is_report_browser(hbt)) 1616 goto do_data_switch; 1617 continue; 1618 case 'i': 1619 /* env->arch is NULL for live-mode (i.e. perf top) */ 1620 if (env->arch) 1621 tui__header_window(env); 1622 continue; 1623 case 'F': 1624 symbol_conf.filter_relative ^= 1; 1625 continue; 1626 case K_F1: 1627 case 'h': 1628 case '?': 1629 ui_browser__help_window(&browser->b, 1630 is_report_browser(hbt) ? report_help : top_help); 1631 continue; 1632 case K_ENTER: 1633 case K_RIGHT: 1634 /* menu */ 1635 break; 1636 case K_LEFT: { 1637 const void *top; 1638 1639 if (pstack__empty(fstack)) { 1640 /* 1641 * Go back to the perf_evsel_menu__run or other user 1642 */ 1643 if (left_exits) 1644 goto out_free_stack; 1645 continue; 1646 } 1647 top = pstack__pop(fstack); 1648 if (top == &browser->hists->dso_filter) 1649 goto zoom_out_dso; 1650 if (top == &browser->hists->thread_filter) 1651 goto zoom_out_thread; 1652 continue; 1653 } 1654 case K_ESC: 1655 if (!left_exits && 1656 !ui_browser__dialog_yesno(&browser->b, 1657 "Do you really want to exit?")) 1658 continue; 1659 /* Fall thru */ 1660 case 'q': 1661 case CTRL('c'): 1662 goto out_free_stack; 1663 default: 1664 continue; 1665 } 1666 1667 if (!sort__has_sym) 1668 goto add_exit_option; 1669 1670 if (sort__mode == SORT_MODE__BRANCH) { 1671 bi = browser->he_selection->branch_info; 1672 if (browser->selection != NULL && 1673 bi && 1674 bi->from.sym != NULL && 1675 !bi->from.map->dso->annotate_warned && 1676 asprintf(&options[nr_options], "Annotate %s", 1677 bi->from.sym->name) > 0) 1678 annotate_f = nr_options++; 1679 1680 if (browser->selection != NULL && 1681 bi && 1682 bi->to.sym != NULL && 1683 !bi->to.map->dso->annotate_warned && 1684 (bi->to.sym != bi->from.sym || 1685 bi->to.map->dso != bi->from.map->dso) && 1686 asprintf(&options[nr_options], "Annotate %s", 1687 bi->to.sym->name) > 0) 1688 annotate_t = nr_options++; 1689 } else { 1690 if (browser->selection != NULL && 1691 browser->selection->sym != NULL && 1692 !browser->selection->map->dso->annotate_warned) { 1693 struct annotation *notes; 1694 1695 notes = symbol__annotation(browser->selection->sym); 1696 1697 if (notes->src && 1698 asprintf(&options[nr_options], "Annotate %s", 1699 browser->selection->sym->name) > 0) 1700 annotate = nr_options++; 1701 } 1702 } 1703 1704 if (thread != NULL && 1705 asprintf(&options[nr_options], "Zoom %s %s(%d) thread", 1706 (browser->hists->thread_filter ? "out of" : "into"), 1707 (thread->comm_set ? thread__comm_str(thread) : ""), 1708 thread->tid) > 0) 1709 zoom_thread = nr_options++; 1710 1711 if (dso != NULL && 1712 asprintf(&options[nr_options], "Zoom %s %s DSO", 1713 (browser->hists->dso_filter ? "out of" : "into"), 1714 (dso->kernel ? "the Kernel" : dso->short_name)) > 0) 1715 zoom_dso = nr_options++; 1716 1717 if (browser->selection != NULL && 1718 browser->selection->map != NULL && 1719 asprintf(&options[nr_options], "Browse map details") > 0) 1720 browse_map = nr_options++; 1721 1722 /* perf script support */ 1723 if (browser->he_selection) { 1724 struct symbol *sym; 1725 1726 if (asprintf(&options[nr_options], "Run scripts for samples of thread [%s]", 1727 thread__comm_str(browser->he_selection->thread)) > 0) 1728 scripts_comm = nr_options++; 1729 1730 sym = browser->he_selection->ms.sym; 1731 if (sym && sym->namelen && 1732 asprintf(&options[nr_options], "Run scripts for samples of symbol [%s]", 1733 sym->name) > 0) 1734 scripts_symbol = nr_options++; 1735 } 1736 1737 if (asprintf(&options[nr_options], "Run scripts for all samples") > 0) 1738 scripts_all = nr_options++; 1739 1740 if (is_report_browser(hbt) && asprintf(&options[nr_options], 1741 "Switch to another data file in PWD") > 0) 1742 switch_data = nr_options++; 1743 add_exit_option: 1744 options[nr_options++] = (char *)"Exit"; 1745 retry_popup_menu: 1746 choice = ui__popup_menu(nr_options, options); 1747 1748 if (choice == nr_options - 1) 1749 break; 1750 1751 if (choice == -1) { 1752 free_popup_options(options, nr_options - 1); 1753 continue; 1754 } 1755 1756 if (choice == annotate || choice == annotate_t || choice == annotate_f) { 1757 struct hist_entry *he; 1758 struct annotation *notes; 1759 int err; 1760 do_annotate: 1761 if (!objdump_path && perf_session_env__lookup_objdump(env)) 1762 continue; 1763 1764 he = hist_browser__selected_entry(browser); 1765 if (he == NULL) 1766 continue; 1767 1768 /* 1769 * we stash the branch_info symbol + map into the 1770 * the ms so we don't have to rewrite all the annotation 1771 * code to use branch_info. 1772 * in branch mode, the ms struct is not used 1773 */ 1774 if (choice == annotate_f) { 1775 he->ms.sym = he->branch_info->from.sym; 1776 he->ms.map = he->branch_info->from.map; 1777 } else if (choice == annotate_t) { 1778 he->ms.sym = he->branch_info->to.sym; 1779 he->ms.map = he->branch_info->to.map; 1780 } 1781 1782 notes = symbol__annotation(he->ms.sym); 1783 if (!notes->src) 1784 continue; 1785 1786 /* 1787 * Don't let this be freed, say, by hists__decay_entry. 1788 */ 1789 he->used = true; 1790 err = hist_entry__tui_annotate(he, evsel, hbt); 1791 he->used = false; 1792 /* 1793 * offer option to annotate the other branch source or target 1794 * (if they exists) when returning from annotate 1795 */ 1796 if ((err == 'q' || err == CTRL('c')) 1797 && annotate_t != -2 && annotate_f != -2) 1798 goto retry_popup_menu; 1799 1800 ui_browser__update_nr_entries(&browser->b, browser->hists->nr_entries); 1801 if (err) 1802 ui_browser__handle_resize(&browser->b); 1803 1804 } else if (choice == browse_map) 1805 map__browse(browser->selection->map); 1806 else if (choice == zoom_dso) { 1807 zoom_dso: 1808 if (browser->hists->dso_filter) { 1809 pstack__remove(fstack, &browser->hists->dso_filter); 1810 zoom_out_dso: 1811 ui_helpline__pop(); 1812 browser->hists->dso_filter = NULL; 1813 perf_hpp__set_elide(HISTC_DSO, false); 1814 } else { 1815 if (dso == NULL) 1816 continue; 1817 ui_helpline__fpush("To zoom out press <- or -> + \"Zoom out of %s DSO\"", 1818 dso->kernel ? "the Kernel" : dso->short_name); 1819 browser->hists->dso_filter = dso; 1820 perf_hpp__set_elide(HISTC_DSO, true); 1821 pstack__push(fstack, &browser->hists->dso_filter); 1822 } 1823 hists__filter_by_dso(hists); 1824 hist_browser__reset(browser); 1825 } else if (choice == zoom_thread) { 1826 zoom_thread: 1827 if (browser->hists->thread_filter) { 1828 pstack__remove(fstack, &browser->hists->thread_filter); 1829 zoom_out_thread: 1830 ui_helpline__pop(); 1831 browser->hists->thread_filter = NULL; 1832 perf_hpp__set_elide(HISTC_THREAD, false); 1833 } else { 1834 ui_helpline__fpush("To zoom out press <- or -> + \"Zoom out of %s(%d) thread\"", 1835 thread->comm_set ? thread__comm_str(thread) : "", 1836 thread->tid); 1837 browser->hists->thread_filter = thread; 1838 perf_hpp__set_elide(HISTC_THREAD, false); 1839 pstack__push(fstack, &browser->hists->thread_filter); 1840 } 1841 hists__filter_by_thread(hists); 1842 hist_browser__reset(browser); 1843 } 1844 /* perf scripts support */ 1845 else if (choice == scripts_all || choice == scripts_comm || 1846 choice == scripts_symbol) { 1847 do_scripts: 1848 memset(script_opt, 0, 64); 1849 1850 if (choice == scripts_comm) 1851 sprintf(script_opt, " -c %s ", thread__comm_str(browser->he_selection->thread)); 1852 1853 if (choice == scripts_symbol) 1854 sprintf(script_opt, " -S %s ", browser->he_selection->ms.sym->name); 1855 1856 script_browse(script_opt); 1857 } 1858 /* Switch to another data file */ 1859 else if (choice == switch_data) { 1860 do_data_switch: 1861 if (!switch_data_file()) { 1862 key = K_SWITCH_INPUT_DATA; 1863 break; 1864 } else 1865 ui__warning("Won't switch the data files due to\n" 1866 "no valid data file get selected!\n"); 1867 } 1868 } 1869 out_free_stack: 1870 pstack__delete(fstack); 1871 out: 1872 hist_browser__delete(browser); 1873 free_popup_options(options, nr_options - 1); 1874 return key; 1875 } 1876 1877 struct perf_evsel_menu { 1878 struct ui_browser b; 1879 struct perf_evsel *selection; 1880 bool lost_events, lost_events_warned; 1881 float min_pcnt; 1882 struct perf_session_env *env; 1883 }; 1884 1885 static void perf_evsel_menu__write(struct ui_browser *browser, 1886 void *entry, int row) 1887 { 1888 struct perf_evsel_menu *menu = container_of(browser, 1889 struct perf_evsel_menu, b); 1890 struct perf_evsel *evsel = list_entry(entry, struct perf_evsel, node); 1891 bool current_entry = ui_browser__is_current_entry(browser, row); 1892 unsigned long nr_events = evsel->hists.stats.nr_events[PERF_RECORD_SAMPLE]; 1893 const char *ev_name = perf_evsel__name(evsel); 1894 char bf[256], unit; 1895 const char *warn = " "; 1896 size_t printed; 1897 1898 ui_browser__set_color(browser, current_entry ? HE_COLORSET_SELECTED : 1899 HE_COLORSET_NORMAL); 1900 1901 if (perf_evsel__is_group_event(evsel)) { 1902 struct perf_evsel *pos; 1903 1904 ev_name = perf_evsel__group_name(evsel); 1905 1906 for_each_group_member(pos, evsel) { 1907 nr_events += pos->hists.stats.nr_events[PERF_RECORD_SAMPLE]; 1908 } 1909 } 1910 1911 nr_events = convert_unit(nr_events, &unit); 1912 printed = scnprintf(bf, sizeof(bf), "%lu%c%s%s", nr_events, 1913 unit, unit == ' ' ? "" : " ", ev_name); 1914 slsmg_printf("%s", bf); 1915 1916 nr_events = evsel->hists.stats.nr_events[PERF_RECORD_LOST]; 1917 if (nr_events != 0) { 1918 menu->lost_events = true; 1919 if (!current_entry) 1920 ui_browser__set_color(browser, HE_COLORSET_TOP); 1921 nr_events = convert_unit(nr_events, &unit); 1922 printed += scnprintf(bf, sizeof(bf), ": %ld%c%schunks LOST!", 1923 nr_events, unit, unit == ' ' ? "" : " "); 1924 warn = bf; 1925 } 1926 1927 slsmg_write_nstring(warn, browser->width - printed); 1928 1929 if (current_entry) 1930 menu->selection = evsel; 1931 } 1932 1933 static int perf_evsel_menu__run(struct perf_evsel_menu *menu, 1934 int nr_events, const char *help, 1935 struct hist_browser_timer *hbt) 1936 { 1937 struct perf_evlist *evlist = menu->b.priv; 1938 struct perf_evsel *pos; 1939 const char *title = "Available samples"; 1940 int delay_secs = hbt ? hbt->refresh : 0; 1941 int key; 1942 1943 if (ui_browser__show(&menu->b, title, 1944 "ESC: exit, ENTER|->: Browse histograms") < 0) 1945 return -1; 1946 1947 while (1) { 1948 key = ui_browser__run(&menu->b, delay_secs); 1949 1950 switch (key) { 1951 case K_TIMER: 1952 hbt->timer(hbt->arg); 1953 1954 if (!menu->lost_events_warned && menu->lost_events) { 1955 ui_browser__warn_lost_events(&menu->b); 1956 menu->lost_events_warned = true; 1957 } 1958 continue; 1959 case K_RIGHT: 1960 case K_ENTER: 1961 if (!menu->selection) 1962 continue; 1963 pos = menu->selection; 1964 browse_hists: 1965 perf_evlist__set_selected(evlist, pos); 1966 /* 1967 * Give the calling tool a chance to populate the non 1968 * default evsel resorted hists tree. 1969 */ 1970 if (hbt) 1971 hbt->timer(hbt->arg); 1972 key = perf_evsel__hists_browse(pos, nr_events, help, 1973 true, hbt, 1974 menu->min_pcnt, 1975 menu->env); 1976 ui_browser__show_title(&menu->b, title); 1977 switch (key) { 1978 case K_TAB: 1979 if (pos->node.next == &evlist->entries) 1980 pos = perf_evlist__first(evlist); 1981 else 1982 pos = perf_evsel__next(pos); 1983 goto browse_hists; 1984 case K_UNTAB: 1985 if (pos->node.prev == &evlist->entries) 1986 pos = perf_evlist__last(evlist); 1987 else 1988 pos = perf_evsel__prev(pos); 1989 goto browse_hists; 1990 case K_ESC: 1991 if (!ui_browser__dialog_yesno(&menu->b, 1992 "Do you really want to exit?")) 1993 continue; 1994 /* Fall thru */ 1995 case K_SWITCH_INPUT_DATA: 1996 case 'q': 1997 case CTRL('c'): 1998 goto out; 1999 default: 2000 continue; 2001 } 2002 case K_LEFT: 2003 continue; 2004 case K_ESC: 2005 if (!ui_browser__dialog_yesno(&menu->b, 2006 "Do you really want to exit?")) 2007 continue; 2008 /* Fall thru */ 2009 case 'q': 2010 case CTRL('c'): 2011 goto out; 2012 default: 2013 continue; 2014 } 2015 } 2016 2017 out: 2018 ui_browser__hide(&menu->b); 2019 return key; 2020 } 2021 2022 static bool filter_group_entries(struct ui_browser *browser __maybe_unused, 2023 void *entry) 2024 { 2025 struct perf_evsel *evsel = list_entry(entry, struct perf_evsel, node); 2026 2027 if (symbol_conf.event_group && !perf_evsel__is_group_leader(evsel)) 2028 return true; 2029 2030 return false; 2031 } 2032 2033 static int __perf_evlist__tui_browse_hists(struct perf_evlist *evlist, 2034 int nr_entries, const char *help, 2035 struct hist_browser_timer *hbt, 2036 float min_pcnt, 2037 struct perf_session_env *env) 2038 { 2039 struct perf_evsel *pos; 2040 struct perf_evsel_menu menu = { 2041 .b = { 2042 .entries = &evlist->entries, 2043 .refresh = ui_browser__list_head_refresh, 2044 .seek = ui_browser__list_head_seek, 2045 .write = perf_evsel_menu__write, 2046 .filter = filter_group_entries, 2047 .nr_entries = nr_entries, 2048 .priv = evlist, 2049 }, 2050 .min_pcnt = min_pcnt, 2051 .env = env, 2052 }; 2053 2054 ui_helpline__push("Press ESC to exit"); 2055 2056 evlist__for_each(evlist, pos) { 2057 const char *ev_name = perf_evsel__name(pos); 2058 size_t line_len = strlen(ev_name) + 7; 2059 2060 if (menu.b.width < line_len) 2061 menu.b.width = line_len; 2062 } 2063 2064 return perf_evsel_menu__run(&menu, nr_entries, help, hbt); 2065 } 2066 2067 int perf_evlist__tui_browse_hists(struct perf_evlist *evlist, const char *help, 2068 struct hist_browser_timer *hbt, 2069 float min_pcnt, 2070 struct perf_session_env *env) 2071 { 2072 int nr_entries = evlist->nr_entries; 2073 2074 single_entry: 2075 if (nr_entries == 1) { 2076 struct perf_evsel *first = perf_evlist__first(evlist); 2077 2078 return perf_evsel__hists_browse(first, nr_entries, help, 2079 false, hbt, min_pcnt, 2080 env); 2081 } 2082 2083 if (symbol_conf.event_group) { 2084 struct perf_evsel *pos; 2085 2086 nr_entries = 0; 2087 evlist__for_each(evlist, pos) { 2088 if (perf_evsel__is_group_leader(pos)) 2089 nr_entries++; 2090 } 2091 2092 if (nr_entries == 1) 2093 goto single_entry; 2094 } 2095 2096 return __perf_evlist__tui_browse_hists(evlist, nr_entries, help, 2097 hbt, min_pcnt, env); 2098 } 2099