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