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 bool first = true; 614 615 if (current_entry) { 616 browser->he_selection = entry; 617 browser->selection = &entry->ms; 618 } 619 620 if (symbol_conf.use_callchain) { 621 hist_entry__init_have_children(entry); 622 folded_sign = hist_entry__folded(entry); 623 } 624 625 if (row_offset == 0) { 626 struct perf_hpp hpp = { 627 .buf = s, 628 .size = sizeof(s), 629 }; 630 631 ui_browser__gotorc(&browser->b, row, 0); 632 633 for (i = 0; i < PERF_HPP__MAX_INDEX; i++) { 634 if (!perf_hpp__format[i].cond) 635 continue; 636 637 if (!first) { 638 slsmg_printf(" "); 639 width -= 2; 640 } 641 first = false; 642 643 if (perf_hpp__format[i].color) { 644 hpp.ptr = &percent; 645 /* It will set percent for us. See HPP__COLOR_FN above. */ 646 width -= perf_hpp__format[i].color(&hpp, entry); 647 648 ui_browser__set_percent_color(&browser->b, percent, current_entry); 649 650 if (i == PERF_HPP__OVERHEAD && symbol_conf.use_callchain) { 651 slsmg_printf("%c ", folded_sign); 652 width -= 2; 653 } 654 655 slsmg_printf("%s", s); 656 657 if (!current_entry || !browser->b.navkeypressed) 658 ui_browser__set_color(&browser->b, HE_COLORSET_NORMAL); 659 } else { 660 width -= perf_hpp__format[i].entry(&hpp, entry); 661 slsmg_printf("%s", s); 662 } 663 } 664 665 /* The scroll bar isn't being used */ 666 if (!browser->b.navkeypressed) 667 width += 1; 668 669 hist_entry__sort_snprintf(entry, s, sizeof(s), browser->hists); 670 slsmg_write_nstring(s, width); 671 ++row; 672 ++printed; 673 } else 674 --row_offset; 675 676 if (folded_sign == '-' && row != browser->b.height) { 677 printed += hist_browser__show_callchain(browser, &entry->sorted_chain, 678 1, row, &row_offset, 679 ¤t_entry); 680 if (current_entry) 681 browser->he_selection = entry; 682 } 683 684 return printed; 685 } 686 687 static void ui_browser__hists_init_top(struct ui_browser *browser) 688 { 689 if (browser->top == NULL) { 690 struct hist_browser *hb; 691 692 hb = container_of(browser, struct hist_browser, b); 693 browser->top = rb_first(&hb->hists->entries); 694 } 695 } 696 697 static unsigned int hist_browser__refresh(struct ui_browser *browser) 698 { 699 unsigned row = 0; 700 struct rb_node *nd; 701 struct hist_browser *hb = container_of(browser, struct hist_browser, b); 702 703 ui_browser__hists_init_top(browser); 704 705 for (nd = browser->top; nd; nd = rb_next(nd)) { 706 struct hist_entry *h = rb_entry(nd, struct hist_entry, rb_node); 707 708 if (h->filtered) 709 continue; 710 711 row += hist_browser__show_entry(hb, h, row); 712 if (row == browser->height) 713 break; 714 } 715 716 return row; 717 } 718 719 static struct rb_node *hists__filter_entries(struct rb_node *nd) 720 { 721 while (nd != NULL) { 722 struct hist_entry *h = rb_entry(nd, struct hist_entry, rb_node); 723 if (!h->filtered) 724 return nd; 725 726 nd = rb_next(nd); 727 } 728 729 return NULL; 730 } 731 732 static struct rb_node *hists__filter_prev_entries(struct rb_node *nd) 733 { 734 while (nd != NULL) { 735 struct hist_entry *h = rb_entry(nd, struct hist_entry, rb_node); 736 if (!h->filtered) 737 return nd; 738 739 nd = rb_prev(nd); 740 } 741 742 return NULL; 743 } 744 745 static void ui_browser__hists_seek(struct ui_browser *browser, 746 off_t offset, int whence) 747 { 748 struct hist_entry *h; 749 struct rb_node *nd; 750 bool first = true; 751 752 if (browser->nr_entries == 0) 753 return; 754 755 ui_browser__hists_init_top(browser); 756 757 switch (whence) { 758 case SEEK_SET: 759 nd = hists__filter_entries(rb_first(browser->entries)); 760 break; 761 case SEEK_CUR: 762 nd = browser->top; 763 goto do_offset; 764 case SEEK_END: 765 nd = hists__filter_prev_entries(rb_last(browser->entries)); 766 first = false; 767 break; 768 default: 769 return; 770 } 771 772 /* 773 * Moves not relative to the first visible entry invalidates its 774 * row_offset: 775 */ 776 h = rb_entry(browser->top, struct hist_entry, rb_node); 777 h->row_offset = 0; 778 779 /* 780 * Here we have to check if nd is expanded (+), if it is we can't go 781 * the next top level hist_entry, instead we must compute an offset of 782 * what _not_ to show and not change the first visible entry. 783 * 784 * This offset increments when we are going from top to bottom and 785 * decreases when we're going from bottom to top. 786 * 787 * As we don't have backpointers to the top level in the callchains 788 * structure, we need to always print the whole hist_entry callchain, 789 * skipping the first ones that are before the first visible entry 790 * and stop when we printed enough lines to fill the screen. 791 */ 792 do_offset: 793 if (offset > 0) { 794 do { 795 h = rb_entry(nd, struct hist_entry, rb_node); 796 if (h->ms.unfolded) { 797 u16 remaining = h->nr_rows - h->row_offset; 798 if (offset > remaining) { 799 offset -= remaining; 800 h->row_offset = 0; 801 } else { 802 h->row_offset += offset; 803 offset = 0; 804 browser->top = nd; 805 break; 806 } 807 } 808 nd = hists__filter_entries(rb_next(nd)); 809 if (nd == NULL) 810 break; 811 --offset; 812 browser->top = nd; 813 } while (offset != 0); 814 } else if (offset < 0) { 815 while (1) { 816 h = rb_entry(nd, struct hist_entry, rb_node); 817 if (h->ms.unfolded) { 818 if (first) { 819 if (-offset > h->row_offset) { 820 offset += h->row_offset; 821 h->row_offset = 0; 822 } else { 823 h->row_offset += offset; 824 offset = 0; 825 browser->top = nd; 826 break; 827 } 828 } else { 829 if (-offset > h->nr_rows) { 830 offset += h->nr_rows; 831 h->row_offset = 0; 832 } else { 833 h->row_offset = h->nr_rows + offset; 834 offset = 0; 835 browser->top = nd; 836 break; 837 } 838 } 839 } 840 841 nd = hists__filter_prev_entries(rb_prev(nd)); 842 if (nd == NULL) 843 break; 844 ++offset; 845 browser->top = nd; 846 if (offset == 0) { 847 /* 848 * Last unfiltered hist_entry, check if it is 849 * unfolded, if it is then we should have 850 * row_offset at its last entry. 851 */ 852 h = rb_entry(nd, struct hist_entry, rb_node); 853 if (h->ms.unfolded) 854 h->row_offset = h->nr_rows; 855 break; 856 } 857 first = false; 858 } 859 } else { 860 browser->top = nd; 861 h = rb_entry(nd, struct hist_entry, rb_node); 862 h->row_offset = 0; 863 } 864 } 865 866 static int hist_browser__fprintf_callchain_node_rb_tree(struct hist_browser *browser, 867 struct callchain_node *chain_node, 868 u64 total, int level, 869 FILE *fp) 870 { 871 struct rb_node *node; 872 int offset = level * LEVEL_OFFSET_STEP; 873 u64 new_total, remaining; 874 int printed = 0; 875 876 if (callchain_param.mode == CHAIN_GRAPH_REL) 877 new_total = chain_node->children_hit; 878 else 879 new_total = total; 880 881 remaining = new_total; 882 node = rb_first(&chain_node->rb_root); 883 while (node) { 884 struct callchain_node *child = rb_entry(node, struct callchain_node, rb_node); 885 struct rb_node *next = rb_next(node); 886 u64 cumul = callchain_cumul_hits(child); 887 struct callchain_list *chain; 888 char folded_sign = ' '; 889 int first = true; 890 int extra_offset = 0; 891 892 remaining -= cumul; 893 894 list_for_each_entry(chain, &child->val, list) { 895 char bf[1024], *alloc_str; 896 const char *str; 897 bool was_first = first; 898 899 if (first) 900 first = false; 901 else 902 extra_offset = LEVEL_OFFSET_STEP; 903 904 folded_sign = callchain_list__folded(chain); 905 906 alloc_str = NULL; 907 str = callchain_list__sym_name(chain, bf, sizeof(bf), 908 browser->show_dso); 909 if (was_first) { 910 double percent = cumul * 100.0 / new_total; 911 912 if (asprintf(&alloc_str, "%2.2f%% %s", percent, str) < 0) 913 str = "Not enough memory!"; 914 else 915 str = alloc_str; 916 } 917 918 printed += fprintf(fp, "%*s%c %s\n", offset + extra_offset, " ", folded_sign, str); 919 free(alloc_str); 920 if (folded_sign == '+') 921 break; 922 } 923 924 if (folded_sign == '-') { 925 const int new_level = level + (extra_offset ? 2 : 1); 926 printed += hist_browser__fprintf_callchain_node_rb_tree(browser, child, new_total, 927 new_level, fp); 928 } 929 930 node = next; 931 } 932 933 return printed; 934 } 935 936 static int hist_browser__fprintf_callchain_node(struct hist_browser *browser, 937 struct callchain_node *node, 938 int level, FILE *fp) 939 { 940 struct callchain_list *chain; 941 int offset = level * LEVEL_OFFSET_STEP; 942 char folded_sign = ' '; 943 int printed = 0; 944 945 list_for_each_entry(chain, &node->val, list) { 946 char bf[1024], *s; 947 948 folded_sign = callchain_list__folded(chain); 949 s = callchain_list__sym_name(chain, bf, sizeof(bf), browser->show_dso); 950 printed += fprintf(fp, "%*s%c %s\n", offset, " ", folded_sign, s); 951 } 952 953 if (folded_sign == '-') 954 printed += hist_browser__fprintf_callchain_node_rb_tree(browser, node, 955 browser->hists->stats.total_period, 956 level + 1, fp); 957 return printed; 958 } 959 960 static int hist_browser__fprintf_callchain(struct hist_browser *browser, 961 struct rb_root *chain, int level, FILE *fp) 962 { 963 struct rb_node *nd; 964 int printed = 0; 965 966 for (nd = rb_first(chain); nd; nd = rb_next(nd)) { 967 struct callchain_node *node = rb_entry(nd, struct callchain_node, rb_node); 968 969 printed += hist_browser__fprintf_callchain_node(browser, node, level, fp); 970 } 971 972 return printed; 973 } 974 975 static int hist_browser__fprintf_entry(struct hist_browser *browser, 976 struct hist_entry *he, FILE *fp) 977 { 978 char s[8192]; 979 double percent; 980 int printed = 0; 981 char folded_sign = ' '; 982 983 if (symbol_conf.use_callchain) 984 folded_sign = hist_entry__folded(he); 985 986 hist_entry__sort_snprintf(he, s, sizeof(s), browser->hists); 987 percent = (he->stat.period * 100.0) / browser->hists->stats.total_period; 988 989 if (symbol_conf.use_callchain) 990 printed += fprintf(fp, "%c ", folded_sign); 991 992 printed += fprintf(fp, " %5.2f%%", percent); 993 994 if (symbol_conf.show_nr_samples) 995 printed += fprintf(fp, " %11u", he->stat.nr_events); 996 997 if (symbol_conf.show_total_period) 998 printed += fprintf(fp, " %12" PRIu64, he->stat.period); 999 1000 printed += fprintf(fp, "%s\n", rtrim(s)); 1001 1002 if (folded_sign == '-') 1003 printed += hist_browser__fprintf_callchain(browser, &he->sorted_chain, 1, fp); 1004 1005 return printed; 1006 } 1007 1008 static int hist_browser__fprintf(struct hist_browser *browser, FILE *fp) 1009 { 1010 struct rb_node *nd = hists__filter_entries(rb_first(browser->b.entries)); 1011 int printed = 0; 1012 1013 while (nd) { 1014 struct hist_entry *h = rb_entry(nd, struct hist_entry, rb_node); 1015 1016 printed += hist_browser__fprintf_entry(browser, h, fp); 1017 nd = hists__filter_entries(rb_next(nd)); 1018 } 1019 1020 return printed; 1021 } 1022 1023 static int hist_browser__dump(struct hist_browser *browser) 1024 { 1025 char filename[64]; 1026 FILE *fp; 1027 1028 while (1) { 1029 scnprintf(filename, sizeof(filename), "perf.hist.%d", browser->print_seq); 1030 if (access(filename, F_OK)) 1031 break; 1032 /* 1033 * XXX: Just an arbitrary lazy upper limit 1034 */ 1035 if (++browser->print_seq == 8192) { 1036 ui_helpline__fpush("Too many perf.hist.N files, nothing written!"); 1037 return -1; 1038 } 1039 } 1040 1041 fp = fopen(filename, "w"); 1042 if (fp == NULL) { 1043 char bf[64]; 1044 const char *err = strerror_r(errno, bf, sizeof(bf)); 1045 ui_helpline__fpush("Couldn't write to %s: %s", filename, err); 1046 return -1; 1047 } 1048 1049 ++browser->print_seq; 1050 hist_browser__fprintf(browser, fp); 1051 fclose(fp); 1052 ui_helpline__fpush("%s written!", filename); 1053 1054 return 0; 1055 } 1056 1057 static struct hist_browser *hist_browser__new(struct hists *hists) 1058 { 1059 struct hist_browser *browser = zalloc(sizeof(*browser)); 1060 1061 if (browser) { 1062 browser->hists = hists; 1063 browser->b.refresh = hist_browser__refresh; 1064 browser->b.seek = ui_browser__hists_seek; 1065 browser->b.use_navkeypressed = true; 1066 if (sort__branch_mode == 1) 1067 browser->has_symbols = sort_sym_from.list.next != NULL; 1068 else 1069 browser->has_symbols = sort_sym.list.next != NULL; 1070 } 1071 1072 return browser; 1073 } 1074 1075 static void hist_browser__delete(struct hist_browser *browser) 1076 { 1077 free(browser); 1078 } 1079 1080 static struct hist_entry *hist_browser__selected_entry(struct hist_browser *browser) 1081 { 1082 return browser->he_selection; 1083 } 1084 1085 static struct thread *hist_browser__selected_thread(struct hist_browser *browser) 1086 { 1087 return browser->he_selection->thread; 1088 } 1089 1090 static int hists__browser_title(struct hists *hists, char *bf, size_t size, 1091 const char *ev_name) 1092 { 1093 char unit; 1094 int printed; 1095 const struct dso *dso = hists->dso_filter; 1096 const struct thread *thread = hists->thread_filter; 1097 unsigned long nr_samples = hists->stats.nr_events[PERF_RECORD_SAMPLE]; 1098 u64 nr_events = hists->stats.total_period; 1099 1100 nr_samples = convert_unit(nr_samples, &unit); 1101 printed = scnprintf(bf, size, 1102 "Samples: %lu%c of event '%s', Event count (approx.): %lu", 1103 nr_samples, unit, ev_name, nr_events); 1104 1105 1106 if (hists->uid_filter_str) 1107 printed += snprintf(bf + printed, size - printed, 1108 ", UID: %s", hists->uid_filter_str); 1109 if (thread) 1110 printed += scnprintf(bf + printed, size - printed, 1111 ", Thread: %s(%d)", 1112 (thread->comm_set ? thread->comm : ""), 1113 thread->pid); 1114 if (dso) 1115 printed += scnprintf(bf + printed, size - printed, 1116 ", DSO: %s", dso->short_name); 1117 return printed; 1118 } 1119 1120 static inline void free_popup_options(char **options, int n) 1121 { 1122 int i; 1123 1124 for (i = 0; i < n; ++i) { 1125 free(options[i]); 1126 options[i] = NULL; 1127 } 1128 } 1129 1130 static int perf_evsel__hists_browse(struct perf_evsel *evsel, int nr_events, 1131 const char *helpline, const char *ev_name, 1132 bool left_exits, 1133 void(*timer)(void *arg), void *arg, 1134 int delay_secs) 1135 { 1136 struct hists *hists = &evsel->hists; 1137 struct hist_browser *browser = hist_browser__new(hists); 1138 struct branch_info *bi; 1139 struct pstack *fstack; 1140 char *options[16]; 1141 int nr_options = 0; 1142 int key = -1; 1143 char buf[64]; 1144 1145 if (browser == NULL) 1146 return -1; 1147 1148 fstack = pstack__new(2); 1149 if (fstack == NULL) 1150 goto out; 1151 1152 ui_helpline__push(helpline); 1153 1154 memset(options, 0, sizeof(options)); 1155 1156 while (1) { 1157 const struct thread *thread = NULL; 1158 const struct dso *dso = NULL; 1159 int choice = 0, 1160 annotate = -2, zoom_dso = -2, zoom_thread = -2, 1161 annotate_f = -2, annotate_t = -2, browse_map = -2; 1162 1163 nr_options = 0; 1164 1165 key = hist_browser__run(browser, ev_name, timer, arg, delay_secs); 1166 1167 if (browser->he_selection != NULL) { 1168 thread = hist_browser__selected_thread(browser); 1169 dso = browser->selection->map ? browser->selection->map->dso : NULL; 1170 } 1171 switch (key) { 1172 case K_TAB: 1173 case K_UNTAB: 1174 if (nr_events == 1) 1175 continue; 1176 /* 1177 * Exit the browser, let hists__browser_tree 1178 * go to the next or previous 1179 */ 1180 goto out_free_stack; 1181 case 'a': 1182 if (!browser->has_symbols) { 1183 ui_browser__warning(&browser->b, delay_secs * 2, 1184 "Annotation is only available for symbolic views, " 1185 "include \"sym*\" in --sort to use it."); 1186 continue; 1187 } 1188 1189 if (browser->selection == NULL || 1190 browser->selection->sym == NULL || 1191 browser->selection->map->dso->annotate_warned) 1192 continue; 1193 goto do_annotate; 1194 case 'P': 1195 hist_browser__dump(browser); 1196 continue; 1197 case 'd': 1198 goto zoom_dso; 1199 case 'V': 1200 browser->show_dso = !browser->show_dso; 1201 continue; 1202 case 't': 1203 goto zoom_thread; 1204 case '/': 1205 if (ui_browser__input_window("Symbol to show", 1206 "Please enter the name of symbol you want to see", 1207 buf, "ENTER: OK, ESC: Cancel", 1208 delay_secs * 2) == K_ENTER) { 1209 hists->symbol_filter_str = *buf ? buf : NULL; 1210 hists__filter_by_symbol(hists); 1211 hist_browser__reset(browser); 1212 } 1213 continue; 1214 case K_F1: 1215 case 'h': 1216 case '?': 1217 ui_browser__help_window(&browser->b, 1218 "h/?/F1 Show this window\n" 1219 "UP/DOWN/PGUP\n" 1220 "PGDN/SPACE Navigate\n" 1221 "q/ESC/CTRL+C Exit browser\n\n" 1222 "For multiple event sessions:\n\n" 1223 "TAB/UNTAB Switch events\n\n" 1224 "For symbolic views (--sort has sym):\n\n" 1225 "-> Zoom into DSO/Threads & Annotate current symbol\n" 1226 "<- Zoom out\n" 1227 "a Annotate current symbol\n" 1228 "C Collapse all callchains\n" 1229 "E Expand all callchains\n" 1230 "d Zoom into current DSO\n" 1231 "t Zoom into current Thread\n" 1232 "P Print histograms to perf.hist.N\n" 1233 "V Verbose (DSO names in callchains, etc)\n" 1234 "/ Filter symbol by name"); 1235 continue; 1236 case K_ENTER: 1237 case K_RIGHT: 1238 /* menu */ 1239 break; 1240 case K_LEFT: { 1241 const void *top; 1242 1243 if (pstack__empty(fstack)) { 1244 /* 1245 * Go back to the perf_evsel_menu__run or other user 1246 */ 1247 if (left_exits) 1248 goto out_free_stack; 1249 continue; 1250 } 1251 top = pstack__pop(fstack); 1252 if (top == &browser->hists->dso_filter) 1253 goto zoom_out_dso; 1254 if (top == &browser->hists->thread_filter) 1255 goto zoom_out_thread; 1256 continue; 1257 } 1258 case K_ESC: 1259 if (!left_exits && 1260 !ui_browser__dialog_yesno(&browser->b, 1261 "Do you really want to exit?")) 1262 continue; 1263 /* Fall thru */ 1264 case 'q': 1265 case CTRL('c'): 1266 goto out_free_stack; 1267 default: 1268 continue; 1269 } 1270 1271 if (!browser->has_symbols) 1272 goto add_exit_option; 1273 1274 if (sort__branch_mode == 1) { 1275 bi = browser->he_selection->branch_info; 1276 if (browser->selection != NULL && 1277 bi && 1278 bi->from.sym != NULL && 1279 !bi->from.map->dso->annotate_warned && 1280 asprintf(&options[nr_options], "Annotate %s", 1281 bi->from.sym->name) > 0) 1282 annotate_f = nr_options++; 1283 1284 if (browser->selection != NULL && 1285 bi && 1286 bi->to.sym != NULL && 1287 !bi->to.map->dso->annotate_warned && 1288 (bi->to.sym != bi->from.sym || 1289 bi->to.map->dso != bi->from.map->dso) && 1290 asprintf(&options[nr_options], "Annotate %s", 1291 bi->to.sym->name) > 0) 1292 annotate_t = nr_options++; 1293 } else { 1294 1295 if (browser->selection != NULL && 1296 browser->selection->sym != NULL && 1297 !browser->selection->map->dso->annotate_warned && 1298 asprintf(&options[nr_options], "Annotate %s", 1299 browser->selection->sym->name) > 0) 1300 annotate = nr_options++; 1301 } 1302 1303 if (thread != NULL && 1304 asprintf(&options[nr_options], "Zoom %s %s(%d) thread", 1305 (browser->hists->thread_filter ? "out of" : "into"), 1306 (thread->comm_set ? thread->comm : ""), 1307 thread->pid) > 0) 1308 zoom_thread = nr_options++; 1309 1310 if (dso != NULL && 1311 asprintf(&options[nr_options], "Zoom %s %s DSO", 1312 (browser->hists->dso_filter ? "out of" : "into"), 1313 (dso->kernel ? "the Kernel" : dso->short_name)) > 0) 1314 zoom_dso = nr_options++; 1315 1316 if (browser->selection != NULL && 1317 browser->selection->map != NULL && 1318 asprintf(&options[nr_options], "Browse map details") > 0) 1319 browse_map = nr_options++; 1320 add_exit_option: 1321 options[nr_options++] = (char *)"Exit"; 1322 retry_popup_menu: 1323 choice = ui__popup_menu(nr_options, options); 1324 1325 if (choice == nr_options - 1) 1326 break; 1327 1328 if (choice == -1) { 1329 free_popup_options(options, nr_options - 1); 1330 continue; 1331 } 1332 1333 if (choice == annotate || choice == annotate_t || choice == annotate_f) { 1334 struct hist_entry *he; 1335 int err; 1336 do_annotate: 1337 he = hist_browser__selected_entry(browser); 1338 if (he == NULL) 1339 continue; 1340 1341 /* 1342 * we stash the branch_info symbol + map into the 1343 * the ms so we don't have to rewrite all the annotation 1344 * code to use branch_info. 1345 * in branch mode, the ms struct is not used 1346 */ 1347 if (choice == annotate_f) { 1348 he->ms.sym = he->branch_info->from.sym; 1349 he->ms.map = he->branch_info->from.map; 1350 } else if (choice == annotate_t) { 1351 he->ms.sym = he->branch_info->to.sym; 1352 he->ms.map = he->branch_info->to.map; 1353 } 1354 1355 /* 1356 * Don't let this be freed, say, by hists__decay_entry. 1357 */ 1358 he->used = true; 1359 err = hist_entry__tui_annotate(he, evsel->idx, 1360 timer, arg, delay_secs); 1361 he->used = false; 1362 /* 1363 * offer option to annotate the other branch source or target 1364 * (if they exists) when returning from annotate 1365 */ 1366 if ((err == 'q' || err == CTRL('c')) 1367 && annotate_t != -2 && annotate_f != -2) 1368 goto retry_popup_menu; 1369 1370 ui_browser__update_nr_entries(&browser->b, browser->hists->nr_entries); 1371 if (err) 1372 ui_browser__handle_resize(&browser->b); 1373 1374 } else if (choice == browse_map) 1375 map__browse(browser->selection->map); 1376 else if (choice == zoom_dso) { 1377 zoom_dso: 1378 if (browser->hists->dso_filter) { 1379 pstack__remove(fstack, &browser->hists->dso_filter); 1380 zoom_out_dso: 1381 ui_helpline__pop(); 1382 browser->hists->dso_filter = NULL; 1383 sort_dso.elide = false; 1384 } else { 1385 if (dso == NULL) 1386 continue; 1387 ui_helpline__fpush("To zoom out press <- or -> + \"Zoom out of %s DSO\"", 1388 dso->kernel ? "the Kernel" : dso->short_name); 1389 browser->hists->dso_filter = dso; 1390 sort_dso.elide = true; 1391 pstack__push(fstack, &browser->hists->dso_filter); 1392 } 1393 hists__filter_by_dso(hists); 1394 hist_browser__reset(browser); 1395 } else if (choice == zoom_thread) { 1396 zoom_thread: 1397 if (browser->hists->thread_filter) { 1398 pstack__remove(fstack, &browser->hists->thread_filter); 1399 zoom_out_thread: 1400 ui_helpline__pop(); 1401 browser->hists->thread_filter = NULL; 1402 sort_thread.elide = false; 1403 } else { 1404 ui_helpline__fpush("To zoom out press <- or -> + \"Zoom out of %s(%d) thread\"", 1405 thread->comm_set ? thread->comm : "", 1406 thread->pid); 1407 browser->hists->thread_filter = thread; 1408 sort_thread.elide = true; 1409 pstack__push(fstack, &browser->hists->thread_filter); 1410 } 1411 hists__filter_by_thread(hists); 1412 hist_browser__reset(browser); 1413 } 1414 } 1415 out_free_stack: 1416 pstack__delete(fstack); 1417 out: 1418 hist_browser__delete(browser); 1419 free_popup_options(options, nr_options - 1); 1420 return key; 1421 } 1422 1423 struct perf_evsel_menu { 1424 struct ui_browser b; 1425 struct perf_evsel *selection; 1426 bool lost_events, lost_events_warned; 1427 }; 1428 1429 static void perf_evsel_menu__write(struct ui_browser *browser, 1430 void *entry, int row) 1431 { 1432 struct perf_evsel_menu *menu = container_of(browser, 1433 struct perf_evsel_menu, b); 1434 struct perf_evsel *evsel = list_entry(entry, struct perf_evsel, node); 1435 bool current_entry = ui_browser__is_current_entry(browser, row); 1436 unsigned long nr_events = evsel->hists.stats.nr_events[PERF_RECORD_SAMPLE]; 1437 const char *ev_name = perf_evsel__name(evsel); 1438 char bf[256], unit; 1439 const char *warn = " "; 1440 size_t printed; 1441 1442 ui_browser__set_color(browser, current_entry ? HE_COLORSET_SELECTED : 1443 HE_COLORSET_NORMAL); 1444 1445 nr_events = convert_unit(nr_events, &unit); 1446 printed = scnprintf(bf, sizeof(bf), "%lu%c%s%s", nr_events, 1447 unit, unit == ' ' ? "" : " ", ev_name); 1448 slsmg_printf("%s", bf); 1449 1450 nr_events = evsel->hists.stats.nr_events[PERF_RECORD_LOST]; 1451 if (nr_events != 0) { 1452 menu->lost_events = true; 1453 if (!current_entry) 1454 ui_browser__set_color(browser, HE_COLORSET_TOP); 1455 nr_events = convert_unit(nr_events, &unit); 1456 printed += scnprintf(bf, sizeof(bf), ": %ld%c%schunks LOST!", 1457 nr_events, unit, unit == ' ' ? "" : " "); 1458 warn = bf; 1459 } 1460 1461 slsmg_write_nstring(warn, browser->width - printed); 1462 1463 if (current_entry) 1464 menu->selection = evsel; 1465 } 1466 1467 static int perf_evsel_menu__run(struct perf_evsel_menu *menu, 1468 int nr_events, const char *help, 1469 void(*timer)(void *arg), void *arg, int delay_secs) 1470 { 1471 struct perf_evlist *evlist = menu->b.priv; 1472 struct perf_evsel *pos; 1473 const char *ev_name, *title = "Available samples"; 1474 int key; 1475 1476 if (ui_browser__show(&menu->b, title, 1477 "ESC: exit, ENTER|->: Browse histograms") < 0) 1478 return -1; 1479 1480 while (1) { 1481 key = ui_browser__run(&menu->b, delay_secs); 1482 1483 switch (key) { 1484 case K_TIMER: 1485 timer(arg); 1486 1487 if (!menu->lost_events_warned && menu->lost_events) { 1488 ui_browser__warn_lost_events(&menu->b); 1489 menu->lost_events_warned = true; 1490 } 1491 continue; 1492 case K_RIGHT: 1493 case K_ENTER: 1494 if (!menu->selection) 1495 continue; 1496 pos = menu->selection; 1497 browse_hists: 1498 perf_evlist__set_selected(evlist, pos); 1499 /* 1500 * Give the calling tool a chance to populate the non 1501 * default evsel resorted hists tree. 1502 */ 1503 if (timer) 1504 timer(arg); 1505 ev_name = perf_evsel__name(pos); 1506 key = perf_evsel__hists_browse(pos, nr_events, help, 1507 ev_name, true, timer, 1508 arg, delay_secs); 1509 ui_browser__show_title(&menu->b, title); 1510 switch (key) { 1511 case K_TAB: 1512 if (pos->node.next == &evlist->entries) 1513 pos = list_entry(evlist->entries.next, struct perf_evsel, node); 1514 else 1515 pos = list_entry(pos->node.next, struct perf_evsel, node); 1516 goto browse_hists; 1517 case K_UNTAB: 1518 if (pos->node.prev == &evlist->entries) 1519 pos = list_entry(evlist->entries.prev, struct perf_evsel, node); 1520 else 1521 pos = list_entry(pos->node.prev, struct perf_evsel, node); 1522 goto browse_hists; 1523 case K_ESC: 1524 if (!ui_browser__dialog_yesno(&menu->b, 1525 "Do you really want to exit?")) 1526 continue; 1527 /* Fall thru */ 1528 case 'q': 1529 case CTRL('c'): 1530 goto out; 1531 default: 1532 continue; 1533 } 1534 case K_LEFT: 1535 continue; 1536 case K_ESC: 1537 if (!ui_browser__dialog_yesno(&menu->b, 1538 "Do you really want to exit?")) 1539 continue; 1540 /* Fall thru */ 1541 case 'q': 1542 case CTRL('c'): 1543 goto out; 1544 default: 1545 continue; 1546 } 1547 } 1548 1549 out: 1550 ui_browser__hide(&menu->b); 1551 return key; 1552 } 1553 1554 static int __perf_evlist__tui_browse_hists(struct perf_evlist *evlist, 1555 const char *help, 1556 void(*timer)(void *arg), void *arg, 1557 int delay_secs) 1558 { 1559 struct perf_evsel *pos; 1560 struct perf_evsel_menu menu = { 1561 .b = { 1562 .entries = &evlist->entries, 1563 .refresh = ui_browser__list_head_refresh, 1564 .seek = ui_browser__list_head_seek, 1565 .write = perf_evsel_menu__write, 1566 .nr_entries = evlist->nr_entries, 1567 .priv = evlist, 1568 }, 1569 }; 1570 1571 ui_helpline__push("Press ESC to exit"); 1572 1573 list_for_each_entry(pos, &evlist->entries, node) { 1574 const char *ev_name = perf_evsel__name(pos); 1575 size_t line_len = strlen(ev_name) + 7; 1576 1577 if (menu.b.width < line_len) 1578 menu.b.width = line_len; 1579 } 1580 1581 return perf_evsel_menu__run(&menu, evlist->nr_entries, help, timer, 1582 arg, delay_secs); 1583 } 1584 1585 int perf_evlist__tui_browse_hists(struct perf_evlist *evlist, const char *help, 1586 void(*timer)(void *arg), void *arg, 1587 int delay_secs) 1588 { 1589 if (evlist->nr_entries == 1) { 1590 struct perf_evsel *first = list_entry(evlist->entries.next, 1591 struct perf_evsel, node); 1592 const char *ev_name = perf_evsel__name(first); 1593 return perf_evsel__hists_browse(first, evlist->nr_entries, help, 1594 ev_name, false, timer, arg, 1595 delay_secs); 1596 } 1597 1598 return __perf_evlist__tui_browse_hists(evlist, help, 1599 timer, arg, delay_secs); 1600 } 1601