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