1 #define _GNU_SOURCE 2 #include <stdio.h> 3 #undef _GNU_SOURCE 4 #include "../libslang.h" 5 #include <stdlib.h> 6 #include <string.h> 7 #include <newt.h> 8 #include <linux/rbtree.h> 9 10 #include "../../evsel.h" 11 #include "../../evlist.h" 12 #include "../../hist.h" 13 #include "../../pstack.h" 14 #include "../../sort.h" 15 #include "../../util.h" 16 17 #include "../browser.h" 18 #include "../helpline.h" 19 #include "../util.h" 20 #include "map.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 }; 28 29 static void hist_browser__refresh_dimensions(struct hist_browser *self) 30 { 31 /* 3 == +/- toggle symbol before actual hist_entry rendering */ 32 self->b.width = 3 + (hists__sort_list_width(self->hists) + 33 sizeof("[k]")); 34 } 35 36 static void hist_browser__reset(struct hist_browser *self) 37 { 38 self->b.nr_entries = self->hists->nr_entries; 39 hist_browser__refresh_dimensions(self); 40 ui_browser__reset_index(&self->b); 41 } 42 43 static char tree__folded_sign(bool unfolded) 44 { 45 return unfolded ? '-' : '+'; 46 } 47 48 static char map_symbol__folded(const struct map_symbol *self) 49 { 50 return self->has_children ? tree__folded_sign(self->unfolded) : ' '; 51 } 52 53 static char hist_entry__folded(const struct hist_entry *self) 54 { 55 return map_symbol__folded(&self->ms); 56 } 57 58 static char callchain_list__folded(const struct callchain_list *self) 59 { 60 return map_symbol__folded(&self->ms); 61 } 62 63 static void map_symbol__set_folding(struct map_symbol *self, bool unfold) 64 { 65 self->unfolded = unfold ? self->has_children : false; 66 } 67 68 static int callchain_node__count_rows_rb_tree(struct callchain_node *self) 69 { 70 int n = 0; 71 struct rb_node *nd; 72 73 for (nd = rb_first(&self->rb_root); nd; nd = rb_next(nd)) { 74 struct callchain_node *child = rb_entry(nd, struct callchain_node, rb_node); 75 struct callchain_list *chain; 76 char folded_sign = ' '; /* No children */ 77 78 list_for_each_entry(chain, &child->val, list) { 79 ++n; 80 /* We need this because we may not have children */ 81 folded_sign = callchain_list__folded(chain); 82 if (folded_sign == '+') 83 break; 84 } 85 86 if (folded_sign == '-') /* Have children and they're unfolded */ 87 n += callchain_node__count_rows_rb_tree(child); 88 } 89 90 return n; 91 } 92 93 static int callchain_node__count_rows(struct callchain_node *node) 94 { 95 struct callchain_list *chain; 96 bool unfolded = false; 97 int n = 0; 98 99 list_for_each_entry(chain, &node->val, list) { 100 ++n; 101 unfolded = chain->ms.unfolded; 102 } 103 104 if (unfolded) 105 n += callchain_node__count_rows_rb_tree(node); 106 107 return n; 108 } 109 110 static int callchain__count_rows(struct rb_root *chain) 111 { 112 struct rb_node *nd; 113 int n = 0; 114 115 for (nd = rb_first(chain); nd; nd = rb_next(nd)) { 116 struct callchain_node *node = rb_entry(nd, struct callchain_node, rb_node); 117 n += callchain_node__count_rows(node); 118 } 119 120 return n; 121 } 122 123 static bool map_symbol__toggle_fold(struct map_symbol *self) 124 { 125 if (!self->has_children) 126 return false; 127 128 self->unfolded = !self->unfolded; 129 return true; 130 } 131 132 static void callchain_node__init_have_children_rb_tree(struct callchain_node *self) 133 { 134 struct rb_node *nd = rb_first(&self->rb_root); 135 136 for (nd = rb_first(&self->rb_root); nd; nd = rb_next(nd)) { 137 struct callchain_node *child = rb_entry(nd, struct callchain_node, rb_node); 138 struct callchain_list *chain; 139 bool first = true; 140 141 list_for_each_entry(chain, &child->val, list) { 142 if (first) { 143 first = false; 144 chain->ms.has_children = chain->list.next != &child->val || 145 !RB_EMPTY_ROOT(&child->rb_root); 146 } else 147 chain->ms.has_children = chain->list.next == &child->val && 148 !RB_EMPTY_ROOT(&child->rb_root); 149 } 150 151 callchain_node__init_have_children_rb_tree(child); 152 } 153 } 154 155 static void callchain_node__init_have_children(struct callchain_node *self) 156 { 157 struct callchain_list *chain; 158 159 list_for_each_entry(chain, &self->val, list) 160 chain->ms.has_children = !RB_EMPTY_ROOT(&self->rb_root); 161 162 callchain_node__init_have_children_rb_tree(self); 163 } 164 165 static void callchain__init_have_children(struct rb_root *self) 166 { 167 struct rb_node *nd; 168 169 for (nd = rb_first(self); nd; nd = rb_next(nd)) { 170 struct callchain_node *node = rb_entry(nd, struct callchain_node, rb_node); 171 callchain_node__init_have_children(node); 172 } 173 } 174 175 static void hist_entry__init_have_children(struct hist_entry *self) 176 { 177 if (!self->init_have_children) { 178 self->ms.has_children = !RB_EMPTY_ROOT(&self->sorted_chain); 179 callchain__init_have_children(&self->sorted_chain); 180 self->init_have_children = true; 181 } 182 } 183 184 static bool hist_browser__toggle_fold(struct hist_browser *self) 185 { 186 if (map_symbol__toggle_fold(self->selection)) { 187 struct hist_entry *he = self->he_selection; 188 189 hist_entry__init_have_children(he); 190 self->hists->nr_entries -= he->nr_rows; 191 192 if (he->ms.unfolded) 193 he->nr_rows = callchain__count_rows(&he->sorted_chain); 194 else 195 he->nr_rows = 0; 196 self->hists->nr_entries += he->nr_rows; 197 self->b.nr_entries = self->hists->nr_entries; 198 199 return true; 200 } 201 202 /* If it doesn't have children, no toggling performed */ 203 return false; 204 } 205 206 static int callchain_node__set_folding_rb_tree(struct callchain_node *self, bool unfold) 207 { 208 int n = 0; 209 struct rb_node *nd; 210 211 for (nd = rb_first(&self->rb_root); nd; nd = rb_next(nd)) { 212 struct callchain_node *child = rb_entry(nd, struct callchain_node, rb_node); 213 struct callchain_list *chain; 214 bool has_children = false; 215 216 list_for_each_entry(chain, &child->val, list) { 217 ++n; 218 map_symbol__set_folding(&chain->ms, unfold); 219 has_children = chain->ms.has_children; 220 } 221 222 if (has_children) 223 n += callchain_node__set_folding_rb_tree(child, unfold); 224 } 225 226 return n; 227 } 228 229 static int callchain_node__set_folding(struct callchain_node *node, bool unfold) 230 { 231 struct callchain_list *chain; 232 bool has_children = false; 233 int n = 0; 234 235 list_for_each_entry(chain, &node->val, list) { 236 ++n; 237 map_symbol__set_folding(&chain->ms, unfold); 238 has_children = chain->ms.has_children; 239 } 240 241 if (has_children) 242 n += callchain_node__set_folding_rb_tree(node, unfold); 243 244 return n; 245 } 246 247 static int callchain__set_folding(struct rb_root *chain, bool unfold) 248 { 249 struct rb_node *nd; 250 int n = 0; 251 252 for (nd = rb_first(chain); nd; nd = rb_next(nd)) { 253 struct callchain_node *node = rb_entry(nd, struct callchain_node, rb_node); 254 n += callchain_node__set_folding(node, unfold); 255 } 256 257 return n; 258 } 259 260 static void hist_entry__set_folding(struct hist_entry *self, bool unfold) 261 { 262 hist_entry__init_have_children(self); 263 map_symbol__set_folding(&self->ms, unfold); 264 265 if (self->ms.has_children) { 266 int n = callchain__set_folding(&self->sorted_chain, unfold); 267 self->nr_rows = unfold ? n : 0; 268 } else 269 self->nr_rows = 0; 270 } 271 272 static void hists__set_folding(struct hists *self, bool unfold) 273 { 274 struct rb_node *nd; 275 276 self->nr_entries = 0; 277 278 for (nd = rb_first(&self->entries); nd; nd = rb_next(nd)) { 279 struct hist_entry *he = rb_entry(nd, struct hist_entry, rb_node); 280 hist_entry__set_folding(he, unfold); 281 self->nr_entries += 1 + he->nr_rows; 282 } 283 } 284 285 static void hist_browser__set_folding(struct hist_browser *self, bool unfold) 286 { 287 hists__set_folding(self->hists, unfold); 288 self->b.nr_entries = self->hists->nr_entries; 289 /* Go to the start, we may be way after valid entries after a collapse */ 290 ui_browser__reset_index(&self->b); 291 } 292 293 static int hist_browser__run(struct hist_browser *self, const char *title) 294 { 295 int key; 296 int exit_keys[] = { 'a', '?', 'h', 'C', 'd', 'D', 'E', 't', 297 NEWT_KEY_ENTER, NEWT_KEY_RIGHT, NEWT_KEY_LEFT, 298 NEWT_KEY_TAB, NEWT_KEY_UNTAB, 0, }; 299 300 self->b.entries = &self->hists->entries; 301 self->b.nr_entries = self->hists->nr_entries; 302 303 hist_browser__refresh_dimensions(self); 304 305 if (ui_browser__show(&self->b, title, 306 "Press '?' for help on key bindings") < 0) 307 return -1; 308 309 ui_browser__add_exit_keys(&self->b, exit_keys); 310 311 while (1) { 312 key = ui_browser__run(&self->b); 313 314 switch (key) { 315 case 'D': { /* Debug */ 316 static int seq; 317 struct hist_entry *h = rb_entry(self->b.top, 318 struct hist_entry, rb_node); 319 ui_helpline__pop(); 320 ui_helpline__fpush("%d: nr_ent=(%d,%d), height=%d, idx=%d, fve: idx=%d, row_off=%d, nrows=%d", 321 seq++, self->b.nr_entries, 322 self->hists->nr_entries, 323 self->b.height, 324 self->b.index, 325 self->b.top_idx, 326 h->row_offset, h->nr_rows); 327 } 328 break; 329 case 'C': 330 /* Collapse the whole world. */ 331 hist_browser__set_folding(self, false); 332 break; 333 case 'E': 334 /* Expand the whole world. */ 335 hist_browser__set_folding(self, true); 336 break; 337 case NEWT_KEY_ENTER: 338 if (hist_browser__toggle_fold(self)) 339 break; 340 /* fall thru */ 341 default: 342 goto out; 343 } 344 } 345 out: 346 ui_browser__hide(&self->b); 347 return key; 348 } 349 350 static char *callchain_list__sym_name(struct callchain_list *self, 351 char *bf, size_t bfsize) 352 { 353 if (self->ms.sym) 354 return self->ms.sym->name; 355 356 snprintf(bf, bfsize, "%#" PRIx64, self->ip); 357 return bf; 358 } 359 360 #define LEVEL_OFFSET_STEP 3 361 362 static int hist_browser__show_callchain_node_rb_tree(struct hist_browser *self, 363 struct callchain_node *chain_node, 364 u64 total, int level, 365 unsigned short row, 366 off_t *row_offset, 367 bool *is_current_entry) 368 { 369 struct rb_node *node; 370 int first_row = row, width, offset = level * LEVEL_OFFSET_STEP; 371 u64 new_total, remaining; 372 373 if (callchain_param.mode == CHAIN_GRAPH_REL) 374 new_total = chain_node->children_hit; 375 else 376 new_total = total; 377 378 remaining = new_total; 379 node = rb_first(&chain_node->rb_root); 380 while (node) { 381 struct callchain_node *child = rb_entry(node, struct callchain_node, rb_node); 382 struct rb_node *next = rb_next(node); 383 u64 cumul = callchain_cumul_hits(child); 384 struct callchain_list *chain; 385 char folded_sign = ' '; 386 int first = true; 387 int extra_offset = 0; 388 389 remaining -= cumul; 390 391 list_for_each_entry(chain, &child->val, list) { 392 char ipstr[BITS_PER_LONG / 4 + 1], *alloc_str; 393 const char *str; 394 int color; 395 bool was_first = first; 396 397 if (first) 398 first = false; 399 else 400 extra_offset = LEVEL_OFFSET_STEP; 401 402 folded_sign = callchain_list__folded(chain); 403 if (*row_offset != 0) { 404 --*row_offset; 405 goto do_next; 406 } 407 408 alloc_str = NULL; 409 str = callchain_list__sym_name(chain, ipstr, sizeof(ipstr)); 410 if (was_first) { 411 double percent = cumul * 100.0 / new_total; 412 413 if (asprintf(&alloc_str, "%2.2f%% %s", percent, str) < 0) 414 str = "Not enough memory!"; 415 else 416 str = alloc_str; 417 } 418 419 color = HE_COLORSET_NORMAL; 420 width = self->b.width - (offset + extra_offset + 2); 421 if (ui_browser__is_current_entry(&self->b, row)) { 422 self->selection = &chain->ms; 423 color = HE_COLORSET_SELECTED; 424 *is_current_entry = true; 425 } 426 427 ui_browser__set_color(&self->b, color); 428 ui_browser__gotorc(&self->b, row, 0); 429 slsmg_write_nstring(" ", offset + extra_offset); 430 slsmg_printf("%c ", folded_sign); 431 slsmg_write_nstring(str, width); 432 free(alloc_str); 433 434 if (++row == self->b.height) 435 goto out; 436 do_next: 437 if (folded_sign == '+') 438 break; 439 } 440 441 if (folded_sign == '-') { 442 const int new_level = level + (extra_offset ? 2 : 1); 443 row += hist_browser__show_callchain_node_rb_tree(self, child, new_total, 444 new_level, row, row_offset, 445 is_current_entry); 446 } 447 if (row == self->b.height) 448 goto out; 449 node = next; 450 } 451 out: 452 return row - first_row; 453 } 454 455 static int hist_browser__show_callchain_node(struct hist_browser *self, 456 struct callchain_node *node, 457 int level, unsigned short row, 458 off_t *row_offset, 459 bool *is_current_entry) 460 { 461 struct callchain_list *chain; 462 int first_row = row, 463 offset = level * LEVEL_OFFSET_STEP, 464 width = self->b.width - offset; 465 char folded_sign = ' '; 466 467 list_for_each_entry(chain, &node->val, list) { 468 char ipstr[BITS_PER_LONG / 4 + 1], *s; 469 int color; 470 471 folded_sign = callchain_list__folded(chain); 472 473 if (*row_offset != 0) { 474 --*row_offset; 475 continue; 476 } 477 478 color = HE_COLORSET_NORMAL; 479 if (ui_browser__is_current_entry(&self->b, row)) { 480 self->selection = &chain->ms; 481 color = HE_COLORSET_SELECTED; 482 *is_current_entry = true; 483 } 484 485 s = callchain_list__sym_name(chain, ipstr, sizeof(ipstr)); 486 ui_browser__gotorc(&self->b, row, 0); 487 ui_browser__set_color(&self->b, color); 488 slsmg_write_nstring(" ", offset); 489 slsmg_printf("%c ", folded_sign); 490 slsmg_write_nstring(s, width - 2); 491 492 if (++row == self->b.height) 493 goto out; 494 } 495 496 if (folded_sign == '-') 497 row += hist_browser__show_callchain_node_rb_tree(self, node, 498 self->hists->stats.total_period, 499 level + 1, row, 500 row_offset, 501 is_current_entry); 502 out: 503 return row - first_row; 504 } 505 506 static int hist_browser__show_callchain(struct hist_browser *self, 507 struct rb_root *chain, 508 int level, unsigned short row, 509 off_t *row_offset, 510 bool *is_current_entry) 511 { 512 struct rb_node *nd; 513 int first_row = row; 514 515 for (nd = rb_first(chain); nd; nd = rb_next(nd)) { 516 struct callchain_node *node = rb_entry(nd, struct callchain_node, rb_node); 517 518 row += hist_browser__show_callchain_node(self, node, level, 519 row, row_offset, 520 is_current_entry); 521 if (row == self->b.height) 522 break; 523 } 524 525 return row - first_row; 526 } 527 528 static int hist_browser__show_entry(struct hist_browser *self, 529 struct hist_entry *entry, 530 unsigned short row) 531 { 532 char s[256]; 533 double percent; 534 int printed = 0; 535 int color, width = self->b.width; 536 char folded_sign = ' '; 537 bool current_entry = ui_browser__is_current_entry(&self->b, row); 538 off_t row_offset = entry->row_offset; 539 540 if (current_entry) { 541 self->he_selection = entry; 542 self->selection = &entry->ms; 543 } 544 545 if (symbol_conf.use_callchain) { 546 hist_entry__init_have_children(entry); 547 folded_sign = hist_entry__folded(entry); 548 } 549 550 if (row_offset == 0) { 551 hist_entry__snprintf(entry, s, sizeof(s), self->hists, NULL, false, 552 0, false, self->hists->stats.total_period); 553 percent = (entry->period * 100.0) / self->hists->stats.total_period; 554 555 color = HE_COLORSET_SELECTED; 556 if (!current_entry) { 557 if (percent >= MIN_RED) 558 color = HE_COLORSET_TOP; 559 else if (percent >= MIN_GREEN) 560 color = HE_COLORSET_MEDIUM; 561 else 562 color = HE_COLORSET_NORMAL; 563 } 564 565 ui_browser__set_color(&self->b, color); 566 ui_browser__gotorc(&self->b, row, 0); 567 if (symbol_conf.use_callchain) { 568 slsmg_printf("%c ", folded_sign); 569 width -= 2; 570 } 571 slsmg_write_nstring(s, width); 572 ++row; 573 ++printed; 574 } else 575 --row_offset; 576 577 if (folded_sign == '-' && row != self->b.height) { 578 printed += hist_browser__show_callchain(self, &entry->sorted_chain, 579 1, row, &row_offset, 580 ¤t_entry); 581 if (current_entry) 582 self->he_selection = entry; 583 } 584 585 return printed; 586 } 587 588 static unsigned int hist_browser__refresh(struct ui_browser *self) 589 { 590 unsigned row = 0; 591 struct rb_node *nd; 592 struct hist_browser *hb = container_of(self, struct hist_browser, b); 593 594 if (self->top == NULL) 595 self->top = rb_first(&hb->hists->entries); 596 597 for (nd = self->top; nd; nd = rb_next(nd)) { 598 struct hist_entry *h = rb_entry(nd, struct hist_entry, rb_node); 599 600 if (h->filtered) 601 continue; 602 603 row += hist_browser__show_entry(hb, h, row); 604 if (row == self->height) 605 break; 606 } 607 608 return row; 609 } 610 611 static struct rb_node *hists__filter_entries(struct rb_node *nd) 612 { 613 while (nd != NULL) { 614 struct hist_entry *h = rb_entry(nd, struct hist_entry, rb_node); 615 if (!h->filtered) 616 return nd; 617 618 nd = rb_next(nd); 619 } 620 621 return NULL; 622 } 623 624 static struct rb_node *hists__filter_prev_entries(struct rb_node *nd) 625 { 626 while (nd != NULL) { 627 struct hist_entry *h = rb_entry(nd, struct hist_entry, rb_node); 628 if (!h->filtered) 629 return nd; 630 631 nd = rb_prev(nd); 632 } 633 634 return NULL; 635 } 636 637 static void ui_browser__hists_seek(struct ui_browser *self, 638 off_t offset, int whence) 639 { 640 struct hist_entry *h; 641 struct rb_node *nd; 642 bool first = true; 643 644 if (self->nr_entries == 0) 645 return; 646 647 switch (whence) { 648 case SEEK_SET: 649 nd = hists__filter_entries(rb_first(self->entries)); 650 break; 651 case SEEK_CUR: 652 nd = self->top; 653 goto do_offset; 654 case SEEK_END: 655 nd = hists__filter_prev_entries(rb_last(self->entries)); 656 first = false; 657 break; 658 default: 659 return; 660 } 661 662 /* 663 * Moves not relative to the first visible entry invalidates its 664 * row_offset: 665 */ 666 h = rb_entry(self->top, struct hist_entry, rb_node); 667 h->row_offset = 0; 668 669 /* 670 * Here we have to check if nd is expanded (+), if it is we can't go 671 * the next top level hist_entry, instead we must compute an offset of 672 * what _not_ to show and not change the first visible entry. 673 * 674 * This offset increments when we are going from top to bottom and 675 * decreases when we're going from bottom to top. 676 * 677 * As we don't have backpointers to the top level in the callchains 678 * structure, we need to always print the whole hist_entry callchain, 679 * skipping the first ones that are before the first visible entry 680 * and stop when we printed enough lines to fill the screen. 681 */ 682 do_offset: 683 if (offset > 0) { 684 do { 685 h = rb_entry(nd, struct hist_entry, rb_node); 686 if (h->ms.unfolded) { 687 u16 remaining = h->nr_rows - h->row_offset; 688 if (offset > remaining) { 689 offset -= remaining; 690 h->row_offset = 0; 691 } else { 692 h->row_offset += offset; 693 offset = 0; 694 self->top = nd; 695 break; 696 } 697 } 698 nd = hists__filter_entries(rb_next(nd)); 699 if (nd == NULL) 700 break; 701 --offset; 702 self->top = nd; 703 } while (offset != 0); 704 } else if (offset < 0) { 705 while (1) { 706 h = rb_entry(nd, struct hist_entry, rb_node); 707 if (h->ms.unfolded) { 708 if (first) { 709 if (-offset > h->row_offset) { 710 offset += h->row_offset; 711 h->row_offset = 0; 712 } else { 713 h->row_offset += offset; 714 offset = 0; 715 self->top = nd; 716 break; 717 } 718 } else { 719 if (-offset > h->nr_rows) { 720 offset += h->nr_rows; 721 h->row_offset = 0; 722 } else { 723 h->row_offset = h->nr_rows + offset; 724 offset = 0; 725 self->top = nd; 726 break; 727 } 728 } 729 } 730 731 nd = hists__filter_prev_entries(rb_prev(nd)); 732 if (nd == NULL) 733 break; 734 ++offset; 735 self->top = nd; 736 if (offset == 0) { 737 /* 738 * Last unfiltered hist_entry, check if it is 739 * unfolded, if it is then we should have 740 * row_offset at its last entry. 741 */ 742 h = rb_entry(nd, struct hist_entry, rb_node); 743 if (h->ms.unfolded) 744 h->row_offset = h->nr_rows; 745 break; 746 } 747 first = false; 748 } 749 } else { 750 self->top = nd; 751 h = rb_entry(nd, struct hist_entry, rb_node); 752 h->row_offset = 0; 753 } 754 } 755 756 static struct hist_browser *hist_browser__new(struct hists *hists) 757 { 758 struct hist_browser *self = zalloc(sizeof(*self)); 759 760 if (self) { 761 self->hists = hists; 762 self->b.refresh = hist_browser__refresh; 763 self->b.seek = ui_browser__hists_seek; 764 } 765 766 return self; 767 } 768 769 static void hist_browser__delete(struct hist_browser *self) 770 { 771 free(self); 772 } 773 774 static struct hist_entry *hist_browser__selected_entry(struct hist_browser *self) 775 { 776 return self->he_selection; 777 } 778 779 static struct thread *hist_browser__selected_thread(struct hist_browser *self) 780 { 781 return self->he_selection->thread; 782 } 783 784 static int hists__browser_title(struct hists *self, char *bf, size_t size, 785 const char *ev_name, const struct dso *dso, 786 const struct thread *thread) 787 { 788 char unit; 789 int printed; 790 unsigned long nr_events = self->stats.nr_events[PERF_RECORD_SAMPLE]; 791 792 nr_events = convert_unit(nr_events, &unit); 793 printed = snprintf(bf, size, "Events: %lu%c %s", nr_events, unit, ev_name); 794 795 if (thread) 796 printed += snprintf(bf + printed, size - printed, 797 ", Thread: %s(%d)", 798 (thread->comm_set ? thread->comm : ""), 799 thread->pid); 800 if (dso) 801 printed += snprintf(bf + printed, size - printed, 802 ", DSO: %s", dso->short_name); 803 return printed; 804 } 805 806 static int perf_evsel__hists_browse(struct perf_evsel *evsel, 807 const char *helpline, const char *ev_name, 808 bool left_exits) 809 { 810 struct hists *self = &evsel->hists; 811 struct hist_browser *browser = hist_browser__new(self); 812 struct pstack *fstack; 813 const struct thread *thread_filter = NULL; 814 const struct dso *dso_filter = NULL; 815 char msg[160]; 816 int key = -1; 817 818 if (browser == NULL) 819 return -1; 820 821 fstack = pstack__new(2); 822 if (fstack == NULL) 823 goto out; 824 825 ui_helpline__push(helpline); 826 827 hists__browser_title(self, msg, sizeof(msg), ev_name, 828 dso_filter, thread_filter); 829 while (1) { 830 const struct thread *thread = NULL; 831 const struct dso *dso = NULL; 832 char *options[16]; 833 int nr_options = 0, choice = 0, i, 834 annotate = -2, zoom_dso = -2, zoom_thread = -2, 835 browse_map = -2; 836 837 key = hist_browser__run(browser, msg); 838 839 if (browser->he_selection != NULL) { 840 thread = hist_browser__selected_thread(browser); 841 dso = browser->selection->map ? browser->selection->map->dso : NULL; 842 } 843 844 switch (key) { 845 case NEWT_KEY_TAB: 846 case NEWT_KEY_UNTAB: 847 /* 848 * Exit the browser, let hists__browser_tree 849 * go to the next or previous 850 */ 851 goto out_free_stack; 852 case 'a': 853 if (browser->selection == NULL || 854 browser->selection->sym == NULL || 855 browser->selection->map->dso->annotate_warned) 856 continue; 857 goto do_annotate; 858 case 'd': 859 goto zoom_dso; 860 case 't': 861 goto zoom_thread; 862 case NEWT_KEY_F1: 863 case 'h': 864 case '?': 865 ui__help_window("-> Zoom into DSO/Threads & Annotate current symbol\n" 866 "<- Zoom out\n" 867 "a Annotate current symbol\n" 868 "h/?/F1 Show this window\n" 869 "C Collapse all callchains\n" 870 "E Expand all callchains\n" 871 "d Zoom into current DSO\n" 872 "t Zoom into current Thread\n" 873 "TAB/UNTAB Switch events\n" 874 "q/CTRL+C Exit browser"); 875 continue; 876 case NEWT_KEY_ENTER: 877 case NEWT_KEY_RIGHT: 878 /* menu */ 879 break; 880 case NEWT_KEY_LEFT: { 881 const void *top; 882 883 if (pstack__empty(fstack)) { 884 /* 885 * Go back to the perf_evsel_menu__run or other user 886 */ 887 if (left_exits) 888 goto out_free_stack; 889 continue; 890 } 891 top = pstack__pop(fstack); 892 if (top == &dso_filter) 893 goto zoom_out_dso; 894 if (top == &thread_filter) 895 goto zoom_out_thread; 896 continue; 897 } 898 case NEWT_KEY_ESCAPE: 899 if (!left_exits && 900 !ui__dialog_yesno("Do you really want to exit?")) 901 continue; 902 /* Fall thru */ 903 default: 904 goto out_free_stack; 905 } 906 907 if (browser->selection != NULL && 908 browser->selection->sym != NULL && 909 !browser->selection->map->dso->annotate_warned && 910 asprintf(&options[nr_options], "Annotate %s", 911 browser->selection->sym->name) > 0) 912 annotate = nr_options++; 913 914 if (thread != NULL && 915 asprintf(&options[nr_options], "Zoom %s %s(%d) thread", 916 (thread_filter ? "out of" : "into"), 917 (thread->comm_set ? thread->comm : ""), 918 thread->pid) > 0) 919 zoom_thread = nr_options++; 920 921 if (dso != NULL && 922 asprintf(&options[nr_options], "Zoom %s %s DSO", 923 (dso_filter ? "out of" : "into"), 924 (dso->kernel ? "the Kernel" : dso->short_name)) > 0) 925 zoom_dso = nr_options++; 926 927 if (browser->selection != NULL && 928 browser->selection->map != NULL && 929 asprintf(&options[nr_options], "Browse map details") > 0) 930 browse_map = nr_options++; 931 932 options[nr_options++] = (char *)"Exit"; 933 934 choice = ui__popup_menu(nr_options, options); 935 936 for (i = 0; i < nr_options - 1; ++i) 937 free(options[i]); 938 939 if (choice == nr_options - 1) 940 break; 941 942 if (choice == -1) 943 continue; 944 945 if (choice == annotate) { 946 struct hist_entry *he; 947 do_annotate: 948 he = hist_browser__selected_entry(browser); 949 if (he == NULL) 950 continue; 951 952 hist_entry__tui_annotate(he, evsel->idx); 953 } else if (choice == browse_map) 954 map__browse(browser->selection->map); 955 else if (choice == zoom_dso) { 956 zoom_dso: 957 if (dso_filter) { 958 pstack__remove(fstack, &dso_filter); 959 zoom_out_dso: 960 ui_helpline__pop(); 961 dso_filter = NULL; 962 } else { 963 if (dso == NULL) 964 continue; 965 ui_helpline__fpush("To zoom out press <- or -> + \"Zoom out of %s DSO\"", 966 dso->kernel ? "the Kernel" : dso->short_name); 967 dso_filter = dso; 968 pstack__push(fstack, &dso_filter); 969 } 970 hists__filter_by_dso(self, dso_filter); 971 hists__browser_title(self, msg, sizeof(msg), ev_name, 972 dso_filter, thread_filter); 973 hist_browser__reset(browser); 974 } else if (choice == zoom_thread) { 975 zoom_thread: 976 if (thread_filter) { 977 pstack__remove(fstack, &thread_filter); 978 zoom_out_thread: 979 ui_helpline__pop(); 980 thread_filter = NULL; 981 } else { 982 ui_helpline__fpush("To zoom out press <- or -> + \"Zoom out of %s(%d) thread\"", 983 thread->comm_set ? thread->comm : "", 984 thread->pid); 985 thread_filter = thread; 986 pstack__push(fstack, &thread_filter); 987 } 988 hists__filter_by_thread(self, thread_filter); 989 hists__browser_title(self, msg, sizeof(msg), ev_name, 990 dso_filter, thread_filter); 991 hist_browser__reset(browser); 992 } 993 } 994 out_free_stack: 995 pstack__delete(fstack); 996 out: 997 hist_browser__delete(browser); 998 return key; 999 } 1000 1001 struct perf_evsel_menu { 1002 struct ui_browser b; 1003 struct perf_evsel *selection; 1004 }; 1005 1006 static void perf_evsel_menu__write(struct ui_browser *browser, 1007 void *entry, int row) 1008 { 1009 struct perf_evsel_menu *menu = container_of(browser, 1010 struct perf_evsel_menu, b); 1011 struct perf_evsel *evsel = list_entry(entry, struct perf_evsel, node); 1012 bool current_entry = ui_browser__is_current_entry(browser, row); 1013 unsigned long nr_events = evsel->hists.stats.nr_events[PERF_RECORD_SAMPLE]; 1014 const char *ev_name = event_name(evsel); 1015 char bf[256], unit; 1016 1017 ui_browser__set_color(browser, current_entry ? HE_COLORSET_SELECTED : 1018 HE_COLORSET_NORMAL); 1019 1020 nr_events = convert_unit(nr_events, &unit); 1021 snprintf(bf, sizeof(bf), "%lu%c%s%s", nr_events, 1022 unit, unit == ' ' ? "" : " ", ev_name); 1023 slsmg_write_nstring(bf, browser->width); 1024 1025 if (current_entry) 1026 menu->selection = evsel; 1027 } 1028 1029 static int perf_evsel_menu__run(struct perf_evsel_menu *menu, const char *help) 1030 { 1031 int exit_keys[] = { NEWT_KEY_ENTER, NEWT_KEY_RIGHT, 0, }; 1032 struct perf_evlist *evlist = menu->b.priv; 1033 struct perf_evsel *pos; 1034 const char *ev_name, *title = "Available samples"; 1035 int key; 1036 1037 if (ui_browser__show(&menu->b, title, 1038 "ESC: exit, ENTER|->: Browse histograms") < 0) 1039 return -1; 1040 1041 ui_browser__add_exit_keys(&menu->b, exit_keys); 1042 1043 while (1) { 1044 key = ui_browser__run(&menu->b); 1045 1046 switch (key) { 1047 case NEWT_KEY_RIGHT: 1048 case NEWT_KEY_ENTER: 1049 if (!menu->selection) 1050 continue; 1051 pos = menu->selection; 1052 browse_hists: 1053 ev_name = event_name(pos); 1054 key = perf_evsel__hists_browse(pos, help, ev_name, true); 1055 ui_browser__show_title(&menu->b, title); 1056 break; 1057 case NEWT_KEY_LEFT: 1058 continue; 1059 case NEWT_KEY_ESCAPE: 1060 if (!ui__dialog_yesno("Do you really want to exit?")) 1061 continue; 1062 /* Fall thru */ 1063 default: 1064 goto out; 1065 } 1066 1067 switch (key) { 1068 case NEWT_KEY_TAB: 1069 if (pos->node.next == &evlist->entries) 1070 pos = list_entry(evlist->entries.next, struct perf_evsel, node); 1071 else 1072 pos = list_entry(pos->node.next, struct perf_evsel, node); 1073 goto browse_hists; 1074 case NEWT_KEY_UNTAB: 1075 if (pos->node.prev == &evlist->entries) 1076 pos = list_entry(evlist->entries.prev, struct perf_evsel, node); 1077 else 1078 pos = list_entry(pos->node.prev, struct perf_evsel, node); 1079 goto browse_hists; 1080 case 'q': 1081 case CTRL('c'): 1082 goto out; 1083 default: 1084 break; 1085 } 1086 } 1087 1088 out: 1089 ui_browser__hide(&menu->b); 1090 return key; 1091 } 1092 1093 static int __perf_evlist__tui_browse_hists(struct perf_evlist *evlist, 1094 const char *help) 1095 { 1096 struct perf_evsel *pos; 1097 struct perf_evsel_menu menu = { 1098 .b = { 1099 .entries = &evlist->entries, 1100 .refresh = ui_browser__list_head_refresh, 1101 .seek = ui_browser__list_head_seek, 1102 .write = perf_evsel_menu__write, 1103 .nr_entries = evlist->nr_entries, 1104 .priv = evlist, 1105 }, 1106 }; 1107 1108 ui_helpline__push("Press ESC to exit"); 1109 1110 list_for_each_entry(pos, &evlist->entries, node) { 1111 const char *ev_name = event_name(pos); 1112 size_t line_len = strlen(ev_name) + 7; 1113 1114 if (menu.b.width < line_len) 1115 menu.b.width = line_len; 1116 /* 1117 * Cache the evsel name, tracepoints have a _high_ cost per 1118 * event_name() call. 1119 */ 1120 if (pos->name == NULL) 1121 pos->name = strdup(ev_name); 1122 } 1123 1124 return perf_evsel_menu__run(&menu, help); 1125 } 1126 1127 int perf_evlist__tui_browse_hists(struct perf_evlist *evlist, const char *help) 1128 { 1129 1130 if (evlist->nr_entries == 1) { 1131 struct perf_evsel *first = list_entry(evlist->entries.next, 1132 struct perf_evsel, node); 1133 const char *ev_name = event_name(first); 1134 return perf_evsel__hists_browse(first, help, ev_name, false); 1135 } 1136 1137 return __perf_evlist__tui_browse_hists(evlist, help); 1138 } 1139