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