1// SPDX-License-Identifier: GPL-2.0 2#include <dirent.h> 3#include <errno.h> 4#include <inttypes.h> 5#include <stdio.h> 6#include <stdlib.h> 7#include <string.h> 8#include <linux/rbtree.h> 9#include <linux/string.h> 10#include <sys/ttydefaults.h> 11#include <linux/time64.h> 12#include <linux/zalloc.h> 13 14#include "../../util/debug.h" 15#include "../../util/dso.h" 16#include "../../util/callchain.h" 17#include "../../util/evsel.h" 18#include "../../util/evlist.h" 19#include "../../util/header.h" 20#include "../../util/hist.h" 21#include "../../util/machine.h" 22#include "../../util/map.h" 23#include "../../util/maps.h" 24#include "../../util/symbol.h" 25#include "../../util/map_symbol.h" 26#include "../../util/branch.h" 27#include "../../util/pstack.h" 28#include "../../util/sort.h" 29#include "../../util/top.h" 30#include "../../util/thread.h" 31#include "../../util/block-info.h" 32#include "../../arch/common.h" 33#include "../../perf.h" 34 35#include "../browsers/hists.h" 36#include "../helpline.h" 37#include "../util.h" 38#include "../ui.h" 39#include "map.h" 40#include "annotate.h" 41#include "srcline.h" 42#include "string2.h" 43#include "units.h" 44#include "time-utils.h" 45 46#include <linux/ctype.h> 47 48extern void hist_browser__init_hpp(void); 49 50static int hists_browser__scnprintf_title(struct hist_browser *browser, char *bf, size_t size); 51static void hist_browser__update_nr_entries(struct hist_browser *hb); 52 53static struct rb_node *hists__filter_entries(struct rb_node *nd, 54 float min_pcnt); 55 56static bool hist_browser__has_filter(struct hist_browser *hb) 57{ 58 return hists__has_filter(hb->hists) || hb->min_pcnt || symbol_conf.has_filter || hb->c2c_filter; 59} 60 61static int hist_browser__get_folding(struct hist_browser *browser) 62{ 63 struct rb_node *nd; 64 struct hists *hists = browser->hists; 65 int unfolded_rows = 0; 66 67 for (nd = rb_first_cached(&hists->entries); 68 (nd = hists__filter_entries(nd, browser->min_pcnt)) != NULL; 69 nd = rb_hierarchy_next(nd)) { 70 struct hist_entry *he = 71 rb_entry(nd, struct hist_entry, rb_node); 72 73 if (he->leaf && he->unfolded) 74 unfolded_rows += he->nr_rows; 75 } 76 return unfolded_rows; 77} 78 79static void hist_browser__set_title_space(struct hist_browser *hb) 80{ 81 struct ui_browser *browser = &hb->b; 82 struct hists *hists = hb->hists; 83 struct perf_hpp_list *hpp_list = hists->hpp_list; 84 85 browser->extra_title_lines = hb->show_headers ? hpp_list->nr_header_lines : 0; 86} 87 88static u32 hist_browser__nr_entries(struct hist_browser *hb) 89{ 90 u32 nr_entries; 91 92 if (symbol_conf.report_hierarchy) 93 nr_entries = hb->nr_hierarchy_entries; 94 else if (hist_browser__has_filter(hb)) 95 nr_entries = hb->nr_non_filtered_entries; 96 else 97 nr_entries = hb->hists->nr_entries; 98 99 hb->nr_callchain_rows = hist_browser__get_folding(hb); 100 return nr_entries + hb->nr_callchain_rows; 101} 102 103static void hist_browser__update_rows(struct hist_browser *hb) 104{ 105 struct ui_browser *browser = &hb->b; 106 struct hists *hists = hb->hists; 107 struct perf_hpp_list *hpp_list = hists->hpp_list; 108 u16 index_row; 109 110 if (!hb->show_headers) { 111 browser->rows += browser->extra_title_lines; 112 browser->extra_title_lines = 0; 113 return; 114 } 115 116 browser->extra_title_lines = hpp_list->nr_header_lines; 117 browser->rows -= browser->extra_title_lines; 118 /* 119 * Verify if we were at the last line and that line isn't 120 * visibe because we now show the header line(s). 121 */ 122 index_row = browser->index - browser->top_idx; 123 if (index_row >= browser->rows) 124 browser->index -= index_row - browser->rows + 1; 125} 126 127static void hist_browser__refresh_dimensions(struct ui_browser *browser) 128{ 129 struct hist_browser *hb = container_of(browser, struct hist_browser, b); 130 131 /* 3 == +/- toggle symbol before actual hist_entry rendering */ 132 browser->width = 3 + (hists__sort_list_width(hb->hists) + sizeof("[k]")); 133 /* 134 * FIXME: Just keeping existing behaviour, but this really should be 135 * before updating browser->width, as it will invalidate the 136 * calculation above. Fix this and the fallout in another 137 * changeset. 138 */ 139 ui_browser__refresh_dimensions(browser); 140} 141 142static void hist_browser__reset(struct hist_browser *browser) 143{ 144 /* 145 * The hists__remove_entry_filter() already folds non-filtered 146 * entries so we can assume it has 0 callchain rows. 147 */ 148 browser->nr_callchain_rows = 0; 149 150 hist_browser__update_nr_entries(browser); 151 browser->b.nr_entries = hist_browser__nr_entries(browser); 152 hist_browser__refresh_dimensions(&browser->b); 153 ui_browser__reset_index(&browser->b); 154} 155 156static char tree__folded_sign(bool unfolded) 157{ 158 return unfolded ? '-' : '+'; 159} 160 161static char hist_entry__folded(const struct hist_entry *he) 162{ 163 return he->has_children ? tree__folded_sign(he->unfolded) : ' '; 164} 165 166static char callchain_list__folded(const struct callchain_list *cl) 167{ 168 return cl->has_children ? tree__folded_sign(cl->unfolded) : ' '; 169} 170 171static void callchain_list__set_folding(struct callchain_list *cl, bool unfold) 172{ 173 cl->unfolded = unfold ? cl->has_children : false; 174} 175 176static int callchain_node__count_rows_rb_tree(struct callchain_node *node) 177{ 178 int n = 0; 179 struct rb_node *nd; 180 181 for (nd = rb_first(&node->rb_root); nd; nd = rb_next(nd)) { 182 struct callchain_node *child = rb_entry(nd, struct callchain_node, rb_node); 183 struct callchain_list *chain; 184 char folded_sign = ' '; /* No children */ 185 186 list_for_each_entry(chain, &child->val, list) { 187 ++n; 188 189 /* We need this because we may not have children */ 190 folded_sign = callchain_list__folded(chain); 191 if (folded_sign == '+') 192 break; 193 } 194 195 if (folded_sign == '-') /* Have children and they're unfolded */ 196 n += callchain_node__count_rows_rb_tree(child); 197 } 198 199 return n; 200} 201 202static int callchain_node__count_flat_rows(struct callchain_node *node) 203{ 204 struct callchain_list *chain; 205 char folded_sign = 0; 206 int n = 0; 207 208 list_for_each_entry(chain, &node->parent_val, list) { 209 if (!folded_sign) { 210 /* only check first chain list entry */ 211 folded_sign = callchain_list__folded(chain); 212 if (folded_sign == '+') 213 return 1; 214 } 215 n++; 216 } 217 218 list_for_each_entry(chain, &node->val, list) { 219 if (!folded_sign) { 220 /* node->parent_val list might be empty */ 221 folded_sign = callchain_list__folded(chain); 222 if (folded_sign == '+') 223 return 1; 224 } 225 n++; 226 } 227 228 return n; 229} 230 231static int callchain_node__count_folded_rows(struct callchain_node *node __maybe_unused) 232{ 233 return 1; 234} 235 236static int callchain_node__count_rows(struct callchain_node *node) 237{ 238 struct callchain_list *chain; 239 bool unfolded = false; 240 int n = 0; 241 242 if (callchain_param.mode == CHAIN_FLAT) 243 return callchain_node__count_flat_rows(node); 244 else if (callchain_param.mode == CHAIN_FOLDED) 245 return callchain_node__count_folded_rows(node); 246 247 list_for_each_entry(chain, &node->val, list) { 248 ++n; 249 250 unfolded = chain->unfolded; 251 } 252 253 if (unfolded) 254 n += callchain_node__count_rows_rb_tree(node); 255 256 return n; 257} 258 259static int callchain__count_rows(struct rb_root *chain) 260{ 261 struct rb_node *nd; 262 int n = 0; 263 264 for (nd = rb_first(chain); nd; nd = rb_next(nd)) { 265 struct callchain_node *node = rb_entry(nd, struct callchain_node, rb_node); 266 n += callchain_node__count_rows(node); 267 } 268 269 return n; 270} 271 272static int hierarchy_count_rows(struct hist_browser *hb, struct hist_entry *he, 273 bool include_children) 274{ 275 int count = 0; 276 struct rb_node *node; 277 struct hist_entry *child; 278 279 if (he->leaf) 280 return callchain__count_rows(&he->sorted_chain); 281 282 if (he->has_no_entry) 283 return 1; 284 285 node = rb_first_cached(&he->hroot_out); 286 while (node) { 287 float percent; 288 289 child = rb_entry(node, struct hist_entry, rb_node); 290 percent = hist_entry__get_percent_limit(child); 291 292 if (!child->filtered && percent >= hb->min_pcnt) { 293 count++; 294 295 if (include_children && child->unfolded) 296 count += hierarchy_count_rows(hb, child, true); 297 } 298 299 node = rb_next(node); 300 } 301 return count; 302} 303 304static bool hist_entry__toggle_fold(struct hist_entry *he) 305{ 306 if (!he) 307 return false; 308 309 if (!he->has_children) 310 return false; 311 312 he->unfolded = !he->unfolded; 313 return true; 314} 315 316static bool callchain_list__toggle_fold(struct callchain_list *cl) 317{ 318 if (!cl) 319 return false; 320 321 if (!cl->has_children) 322 return false; 323 324 cl->unfolded = !cl->unfolded; 325 return true; 326} 327 328static void callchain_node__init_have_children_rb_tree(struct callchain_node *node) 329{ 330 struct rb_node *nd = rb_first(&node->rb_root); 331 332 for (nd = rb_first(&node->rb_root); nd; nd = rb_next(nd)) { 333 struct callchain_node *child = rb_entry(nd, struct callchain_node, rb_node); 334 struct callchain_list *chain; 335 bool first = true; 336 337 list_for_each_entry(chain, &child->val, list) { 338 if (first) { 339 first = false; 340 chain->has_children = chain->list.next != &child->val || 341 !RB_EMPTY_ROOT(&child->rb_root); 342 } else 343 chain->has_children = chain->list.next == &child->val && 344 !RB_EMPTY_ROOT(&child->rb_root); 345 } 346 347 callchain_node__init_have_children_rb_tree(child); 348 } 349} 350 351static void callchain_node__init_have_children(struct callchain_node *node, 352 bool has_sibling) 353{ 354 struct callchain_list *chain; 355 356 chain = list_entry(node->val.next, struct callchain_list, list); 357 chain->has_children = has_sibling; 358 359 if (!list_empty(&node->val)) { 360 chain = list_entry(node->val.prev, struct callchain_list, list); 361 chain->has_children = !RB_EMPTY_ROOT(&node->rb_root); 362 } 363 364 callchain_node__init_have_children_rb_tree(node); 365} 366 367static void callchain__init_have_children(struct rb_root *root) 368{ 369 struct rb_node *nd = rb_first(root); 370 bool has_sibling = nd && rb_next(nd); 371 372 for (nd = rb_first(root); nd; nd = rb_next(nd)) { 373 struct callchain_node *node = rb_entry(nd, struct callchain_node, rb_node); 374 callchain_node__init_have_children(node, has_sibling); 375 if (callchain_param.mode == CHAIN_FLAT || 376 callchain_param.mode == CHAIN_FOLDED) 377 callchain_node__make_parent_list(node); 378 } 379} 380 381static void hist_entry__init_have_children(struct hist_entry *he) 382{ 383 if (he->init_have_children) 384 return; 385 386 if (he->leaf) { 387 he->has_children = !RB_EMPTY_ROOT(&he->sorted_chain); 388 callchain__init_have_children(&he->sorted_chain); 389 } else { 390 he->has_children = !RB_EMPTY_ROOT(&he->hroot_out.rb_root); 391 } 392 393 he->init_have_children = true; 394} 395 396static bool hist_browser__selection_has_children(struct hist_browser *browser) 397{ 398 struct hist_entry *he = browser->he_selection; 399 struct map_symbol *ms = browser->selection; 400 401 if (!he || !ms) 402 return false; 403 404 if (ms == &he->ms) 405 return he->has_children; 406 407 return container_of(ms, struct callchain_list, ms)->has_children; 408} 409 410static bool hist_browser__selection_unfolded(struct hist_browser *browser) 411{ 412 struct hist_entry *he = browser->he_selection; 413 struct map_symbol *ms = browser->selection; 414 415 if (!he || !ms) 416 return false; 417 418 if (ms == &he->ms) 419 return he->unfolded; 420 421 return container_of(ms, struct callchain_list, ms)->unfolded; 422} 423 424static char *hist_browser__selection_sym_name(struct hist_browser *browser, char *bf, size_t size) 425{ 426 struct hist_entry *he = browser->he_selection; 427 struct map_symbol *ms = browser->selection; 428 struct callchain_list *callchain_entry; 429 430 if (!he || !ms) 431 return NULL; 432 433 if (ms == &he->ms) { 434 hist_entry__sym_snprintf(he, bf, size, 0); 435 return bf + 4; // skip the level, e.g. '[k] ' 436 } 437 438 callchain_entry = container_of(ms, struct callchain_list, ms); 439 return callchain_list__sym_name(callchain_entry, bf, size, browser->show_dso); 440} 441 442static bool hist_browser__toggle_fold(struct hist_browser *browser) 443{ 444 struct hist_entry *he = browser->he_selection; 445 struct map_symbol *ms = browser->selection; 446 struct callchain_list *cl = container_of(ms, struct callchain_list, ms); 447 bool has_children; 448 449 if (!he || !ms) 450 return false; 451 452 if (ms == &he->ms) 453 has_children = hist_entry__toggle_fold(he); 454 else 455 has_children = callchain_list__toggle_fold(cl); 456 457 if (has_children) { 458 int child_rows = 0; 459 460 hist_entry__init_have_children(he); 461 browser->b.nr_entries -= he->nr_rows; 462 463 if (he->leaf) 464 browser->nr_callchain_rows -= he->nr_rows; 465 else 466 browser->nr_hierarchy_entries -= he->nr_rows; 467 468 if (symbol_conf.report_hierarchy) 469 child_rows = hierarchy_count_rows(browser, he, true); 470 471 if (he->unfolded) { 472 if (he->leaf) 473 he->nr_rows = callchain__count_rows( 474 &he->sorted_chain); 475 else 476 he->nr_rows = hierarchy_count_rows(browser, he, false); 477 478 /* account grand children */ 479 if (symbol_conf.report_hierarchy) 480 browser->b.nr_entries += child_rows - he->nr_rows; 481 482 if (!he->leaf && he->nr_rows == 0) { 483 he->has_no_entry = true; 484 he->nr_rows = 1; 485 } 486 } else { 487 if (symbol_conf.report_hierarchy) 488 browser->b.nr_entries -= child_rows - he->nr_rows; 489 490 if (he->has_no_entry) 491 he->has_no_entry = false; 492 493 he->nr_rows = 0; 494 } 495 496 browser->b.nr_entries += he->nr_rows; 497 498 if (he->leaf) 499 browser->nr_callchain_rows += he->nr_rows; 500 else 501 browser->nr_hierarchy_entries += he->nr_rows; 502 503 return true; 504 } 505 506 /* If it doesn't have children, no toggling performed */ 507 return false; 508} 509 510static int callchain_node__set_folding_rb_tree(struct callchain_node *node, bool unfold) 511{ 512 int n = 0; 513 struct rb_node *nd; 514 515 for (nd = rb_first(&node->rb_root); nd; nd = rb_next(nd)) { 516 struct callchain_node *child = rb_entry(nd, struct callchain_node, rb_node); 517 struct callchain_list *chain; 518 bool has_children = false; 519 520 list_for_each_entry(chain, &child->val, list) { 521 ++n; 522 callchain_list__set_folding(chain, unfold); 523 has_children = chain->has_children; 524 } 525 526 if (has_children) 527 n += callchain_node__set_folding_rb_tree(child, unfold); 528 } 529 530 return n; 531} 532 533static int callchain_node__set_folding(struct callchain_node *node, bool unfold) 534{ 535 struct callchain_list *chain; 536 bool has_children = false; 537 int n = 0; 538 539 list_for_each_entry(chain, &node->val, list) { 540 ++n; 541 callchain_list__set_folding(chain, unfold); 542 has_children = chain->has_children; 543 } 544 545 if (has_children) 546 n += callchain_node__set_folding_rb_tree(node, unfold); 547 548 return n; 549} 550 551static int callchain__set_folding(struct rb_root *chain, bool unfold) 552{ 553 struct rb_node *nd; 554 int n = 0; 555 556 for (nd = rb_first(chain); nd; nd = rb_next(nd)) { 557 struct callchain_node *node = rb_entry(nd, struct callchain_node, rb_node); 558 n += callchain_node__set_folding(node, unfold); 559 } 560 561 return n; 562} 563 564static int hierarchy_set_folding(struct hist_browser *hb, struct hist_entry *he, 565 bool unfold __maybe_unused) 566{ 567 float percent; 568 struct rb_node *nd; 569 struct hist_entry *child; 570 int n = 0; 571 572 for (nd = rb_first_cached(&he->hroot_out); nd; nd = rb_next(nd)) { 573 child = rb_entry(nd, struct hist_entry, rb_node); 574 percent = hist_entry__get_percent_limit(child); 575 if (!child->filtered && percent >= hb->min_pcnt) 576 n++; 577 } 578 579 return n; 580} 581 582static void hist_entry__set_folding(struct hist_entry *he, 583 struct hist_browser *hb, bool unfold) 584{ 585 hist_entry__init_have_children(he); 586 he->unfolded = unfold ? he->has_children : false; 587 588 if (he->has_children) { 589 int n; 590 591 if (he->leaf) 592 n = callchain__set_folding(&he->sorted_chain, unfold); 593 else 594 n = hierarchy_set_folding(hb, he, unfold); 595 596 he->nr_rows = unfold ? n : 0; 597 } else 598 he->nr_rows = 0; 599} 600 601static void 602__hist_browser__set_folding(struct hist_browser *browser, bool unfold) 603{ 604 struct rb_node *nd; 605 struct hist_entry *he; 606 double percent; 607 608 nd = rb_first_cached(&browser->hists->entries); 609 while (nd) { 610 he = rb_entry(nd, struct hist_entry, rb_node); 611 612 /* set folding state even if it's currently folded */ 613 nd = __rb_hierarchy_next(nd, HMD_FORCE_CHILD); 614 615 hist_entry__set_folding(he, browser, unfold); 616 617 percent = hist_entry__get_percent_limit(he); 618 if (he->filtered || percent < browser->min_pcnt) 619 continue; 620 621 if (!he->depth || unfold) 622 browser->nr_hierarchy_entries++; 623 if (he->leaf) 624 browser->nr_callchain_rows += he->nr_rows; 625 else if (unfold && !hist_entry__has_hierarchy_children(he, browser->min_pcnt)) { 626 browser->nr_hierarchy_entries++; 627 he->has_no_entry = true; 628 he->nr_rows = 1; 629 } else 630 he->has_no_entry = false; 631 } 632} 633 634static void hist_browser__set_folding(struct hist_browser *browser, bool unfold) 635{ 636 browser->nr_hierarchy_entries = 0; 637 browser->nr_callchain_rows = 0; 638 __hist_browser__set_folding(browser, unfold); 639 640 browser->b.nr_entries = hist_browser__nr_entries(browser); 641 /* Go to the start, we may be way after valid entries after a collapse */ 642 ui_browser__reset_index(&browser->b); 643} 644 645static void hist_browser__set_folding_selected(struct hist_browser *browser, bool unfold) 646{ 647 if (!browser->he_selection) 648 return; 649 650 if (unfold == browser->he_selection->unfolded) 651 return; 652 653 hist_browser__toggle_fold(browser); 654} 655 656static void ui_browser__warn_lost_events(struct ui_browser *browser) 657{ 658 ui_browser__warning(browser, 4, 659 "Events are being lost, check IO/CPU overload!\n\n" 660 "You may want to run 'perf' using a RT scheduler policy:\n\n" 661 " perf top -r 80\n\n" 662 "Or reduce the sampling frequency."); 663} 664 665static int hist_browser__title(struct hist_browser *browser, char *bf, size_t size) 666{ 667 return browser->title ? browser->title(browser, bf, size) : 0; 668} 669 670static int hist_browser__handle_hotkey(struct hist_browser *browser, bool warn_lost_event, char *title, size_t size, int key) 671{ 672 switch (key) { 673 case K_TIMER: { 674 struct hist_browser_timer *hbt = browser->hbt; 675 u64 nr_entries; 676 677 WARN_ON_ONCE(!hbt); 678 679 if (hbt) 680 hbt->timer(hbt->arg); 681 682 if (hist_browser__has_filter(browser) || symbol_conf.report_hierarchy) 683 hist_browser__update_nr_entries(browser); 684 685 nr_entries = hist_browser__nr_entries(browser); 686 ui_browser__update_nr_entries(&browser->b, nr_entries); 687 688 if (warn_lost_event && 689 (browser->hists->stats.nr_lost_warned != 690 browser->hists->stats.nr_events[PERF_RECORD_LOST])) { 691 browser->hists->stats.nr_lost_warned = 692 browser->hists->stats.nr_events[PERF_RECORD_LOST]; 693 ui_browser__warn_lost_events(&browser->b); 694 } 695 696 hist_browser__title(browser, title, size); 697 ui_browser__show_title(&browser->b, title); 698 break; 699 } 700 case 'D': { /* Debug */ 701 struct hist_entry *h = rb_entry(browser->b.top, struct hist_entry, rb_node); 702 static int seq; 703 704 ui_helpline__pop(); 705 ui_helpline__fpush("%d: nr_ent=(%d,%d), etl: %d, rows=%d, idx=%d, fve: idx=%d, row_off=%d, nrows=%d", 706 seq++, browser->b.nr_entries, browser->hists->nr_entries, 707 browser->b.extra_title_lines, browser->b.rows, 708 browser->b.index, browser->b.top_idx, h->row_offset, h->nr_rows); 709 } 710 break; 711 case 'C': 712 /* Collapse the whole world. */ 713 hist_browser__set_folding(browser, false); 714 break; 715 case 'c': 716 /* Collapse the selected entry. */ 717 hist_browser__set_folding_selected(browser, false); 718 break; 719 case 'E': 720 /* Expand the whole world. */ 721 hist_browser__set_folding(browser, true); 722 break; 723 case 'e': 724 /* Toggle expand/collapse the selected entry. */ 725 hist_browser__toggle_fold(browser); 726 break; 727 case 'H': 728 browser->show_headers = !browser->show_headers; 729 hist_browser__update_rows(browser); 730 break; 731 case '+': 732 if (hist_browser__toggle_fold(browser)) 733 break; 734 /* fall thru */ 735 default: 736 return -1; 737 } 738 739 return 0; 740} 741 742int hist_browser__run(struct hist_browser *browser, const char *help, 743 bool warn_lost_event, int key) 744{ 745 char title[160]; 746 struct hist_browser_timer *hbt = browser->hbt; 747 int delay_secs = hbt ? hbt->refresh : 0; 748 749 browser->b.entries = &browser->hists->entries; 750 browser->b.nr_entries = hist_browser__nr_entries(browser); 751 752 hist_browser__title(browser, title, sizeof(title)); 753 754 if (ui_browser__show(&browser->b, title, "%s", help) < 0) 755 return -1; 756 757 if (key && hist_browser__handle_hotkey(browser, warn_lost_event, title, sizeof(title), key)) 758 goto out; 759 760 while (1) { 761 key = ui_browser__run(&browser->b, delay_secs); 762 763 if (hist_browser__handle_hotkey(browser, warn_lost_event, title, sizeof(title), key)) 764 break; 765 } 766out: 767 ui_browser__hide(&browser->b); 768 return key; 769} 770 771struct callchain_print_arg { 772 /* for hists browser */ 773 off_t row_offset; 774 bool is_current_entry; 775 776 /* for file dump */ 777 FILE *fp; 778 int printed; 779}; 780 781typedef void (*print_callchain_entry_fn)(struct hist_browser *browser, 782 struct callchain_list *chain, 783 const char *str, int offset, 784 unsigned short row, 785 struct callchain_print_arg *arg); 786 787static void hist_browser__show_callchain_entry(struct hist_browser *browser, 788 struct callchain_list *chain, 789 const char *str, int offset, 790 unsigned short row, 791 struct callchain_print_arg *arg) 792{ 793 int color, width; 794 char folded_sign = callchain_list__folded(chain); 795 bool show_annotated = browser->show_dso && chain->ms.sym && symbol__annotation(chain->ms.sym)->src; 796 797 color = HE_COLORSET_NORMAL; 798 width = browser->b.width - (offset + 2); 799 if (ui_browser__is_current_entry(&browser->b, row)) { 800 browser->selection = &chain->ms; 801 color = HE_COLORSET_SELECTED; 802 arg->is_current_entry = true; 803 } 804 805 ui_browser__set_color(&browser->b, color); 806 ui_browser__gotorc(&browser->b, row, 0); 807 ui_browser__write_nstring(&browser->b, " ", offset); 808 ui_browser__printf(&browser->b, "%c", folded_sign); 809 ui_browser__write_graph(&browser->b, show_annotated ? SLSMG_RARROW_CHAR : ' '); 810 ui_browser__write_nstring(&browser->b, str, width); 811} 812 813static void hist_browser__fprintf_callchain_entry(struct hist_browser *b __maybe_unused, 814 struct callchain_list *chain, 815 const char *str, int offset, 816 unsigned short row __maybe_unused, 817 struct callchain_print_arg *arg) 818{ 819 char folded_sign = callchain_list__folded(chain); 820 821 arg->printed += fprintf(arg->fp, "%*s%c %s\n", offset, " ", 822 folded_sign, str); 823} 824 825typedef bool (*check_output_full_fn)(struct hist_browser *browser, 826 unsigned short row); 827 828static bool hist_browser__check_output_full(struct hist_browser *browser, 829 unsigned short row) 830{ 831 return browser->b.rows == row; 832} 833 834static bool hist_browser__check_dump_full(struct hist_browser *browser __maybe_unused, 835 unsigned short row __maybe_unused) 836{ 837 return false; 838} 839 840#define LEVEL_OFFSET_STEP 3 841 842static int hist_browser__show_callchain_list(struct hist_browser *browser, 843 struct callchain_node *node, 844 struct callchain_list *chain, 845 unsigned short row, u64 total, 846 bool need_percent, int offset, 847 print_callchain_entry_fn print, 848 struct callchain_print_arg *arg) 849{ 850 char bf[1024], *alloc_str; 851 char buf[64], *alloc_str2; 852 const char *str; 853 int ret = 1; 854 855 if (arg->row_offset != 0) { 856 arg->row_offset--; 857 return 0; 858 } 859 860 alloc_str = NULL; 861 alloc_str2 = NULL; 862 863 str = callchain_list__sym_name(chain, bf, sizeof(bf), 864 browser->show_dso); 865 866 if (symbol_conf.show_branchflag_count) { 867 callchain_list_counts__printf_value(chain, NULL, 868 buf, sizeof(buf)); 869 870 if (asprintf(&alloc_str2, "%s%s", str, buf) < 0) 871 str = "Not enough memory!"; 872 else 873 str = alloc_str2; 874 } 875 876 if (need_percent) { 877 callchain_node__scnprintf_value(node, buf, sizeof(buf), 878 total); 879 880 if (asprintf(&alloc_str, "%s %s", buf, str) < 0) 881 str = "Not enough memory!"; 882 else 883 str = alloc_str; 884 } 885 886 print(browser, chain, str, offset, row, arg); 887 free(alloc_str); 888 free(alloc_str2); 889 890 return ret; 891} 892 893static bool check_percent_display(struct rb_node *node, u64 parent_total) 894{ 895 struct callchain_node *child; 896 897 if (node == NULL) 898 return false; 899 900 if (rb_next(node)) 901 return true; 902 903 child = rb_entry(node, struct callchain_node, rb_node); 904 return callchain_cumul_hits(child) != parent_total; 905} 906 907static int hist_browser__show_callchain_flat(struct hist_browser *browser, 908 struct rb_root *root, 909 unsigned short row, u64 total, 910 u64 parent_total, 911 print_callchain_entry_fn print, 912 struct callchain_print_arg *arg, 913 check_output_full_fn is_output_full) 914{ 915 struct rb_node *node; 916 int first_row = row, offset = LEVEL_OFFSET_STEP; 917 bool need_percent; 918 919 node = rb_first(root); 920 need_percent = check_percent_display(node, parent_total); 921 922 while (node) { 923 struct callchain_node *child = rb_entry(node, struct callchain_node, rb_node); 924 struct rb_node *next = rb_next(node); 925 struct callchain_list *chain; 926 char folded_sign = ' '; 927 int first = true; 928 int extra_offset = 0; 929 930 list_for_each_entry(chain, &child->parent_val, list) { 931 bool was_first = first; 932 933 if (first) 934 first = false; 935 else if (need_percent) 936 extra_offset = LEVEL_OFFSET_STEP; 937 938 folded_sign = callchain_list__folded(chain); 939 940 row += hist_browser__show_callchain_list(browser, child, 941 chain, row, total, 942 was_first && need_percent, 943 offset + extra_offset, 944 print, arg); 945 946 if (is_output_full(browser, row)) 947 goto out; 948 949 if (folded_sign == '+') 950 goto next; 951 } 952 953 list_for_each_entry(chain, &child->val, list) { 954 bool was_first = first; 955 956 if (first) 957 first = false; 958 else if (need_percent) 959 extra_offset = LEVEL_OFFSET_STEP; 960 961 folded_sign = callchain_list__folded(chain); 962 963 row += hist_browser__show_callchain_list(browser, child, 964 chain, row, total, 965 was_first && need_percent, 966 offset + extra_offset, 967 print, arg); 968 969 if (is_output_full(browser, row)) 970 goto out; 971 972 if (folded_sign == '+') 973 break; 974 } 975 976next: 977 if (is_output_full(browser, row)) 978 break; 979 node = next; 980 } 981out: 982 return row - first_row; 983} 984 985static char *hist_browser__folded_callchain_str(struct hist_browser *browser, 986 struct callchain_list *chain, 987 char *value_str, char *old_str) 988{ 989 char bf[1024]; 990 const char *str; 991 char *new; 992 993 str = callchain_list__sym_name(chain, bf, sizeof(bf), 994 browser->show_dso); 995 if (old_str) { 996 if (asprintf(&new, "%s%s%s", old_str, 997 symbol_conf.field_sep ?: ";", str) < 0) 998 new = NULL; 999 } else { 1000 if (value_str) { 1001 if (asprintf(&new, "%s %s", value_str, str) < 0) 1002 new = NULL; 1003 } else { 1004 if (asprintf(&new, "%s", str) < 0) 1005 new = NULL; 1006 } 1007 } 1008 return new; 1009} 1010 1011static int hist_browser__show_callchain_folded(struct hist_browser *browser, 1012 struct rb_root *root, 1013 unsigned short row, u64 total, 1014 u64 parent_total, 1015 print_callchain_entry_fn print, 1016 struct callchain_print_arg *arg, 1017 check_output_full_fn is_output_full) 1018{ 1019 struct rb_node *node; 1020 int first_row = row, offset = LEVEL_OFFSET_STEP; 1021 bool need_percent; 1022 1023 node = rb_first(root); 1024 need_percent = check_percent_display(node, parent_total); 1025 1026 while (node) { 1027 struct callchain_node *child = rb_entry(node, struct callchain_node, rb_node); 1028 struct rb_node *next = rb_next(node); 1029 struct callchain_list *chain, *first_chain = NULL; 1030 int first = true; 1031 char *value_str = NULL, *value_str_alloc = NULL; 1032 char *chain_str = NULL, *chain_str_alloc = NULL; 1033 1034 if (arg->row_offset != 0) { 1035 arg->row_offset--; 1036 goto next; 1037 } 1038 1039 if (need_percent) { 1040 char buf[64]; 1041 1042 callchain_node__scnprintf_value(child, buf, sizeof(buf), total); 1043 if (asprintf(&value_str, "%s", buf) < 0) { 1044 value_str = (char *)"<...>"; 1045 goto do_print; 1046 } 1047 value_str_alloc = value_str; 1048 } 1049 1050 list_for_each_entry(chain, &child->parent_val, list) { 1051 chain_str = hist_browser__folded_callchain_str(browser, 1052 chain, value_str, chain_str); 1053 if (first) { 1054 first = false; 1055 first_chain = chain; 1056 } 1057 1058 if (chain_str == NULL) { 1059 chain_str = (char *)"Not enough memory!"; 1060 goto do_print; 1061 } 1062 1063 chain_str_alloc = chain_str; 1064 } 1065 1066 list_for_each_entry(chain, &child->val, list) { 1067 chain_str = hist_browser__folded_callchain_str(browser, 1068 chain, value_str, chain_str); 1069 if (first) { 1070 first = false; 1071 first_chain = chain; 1072 } 1073 1074 if (chain_str == NULL) { 1075 chain_str = (char *)"Not enough memory!"; 1076 goto do_print; 1077 } 1078 1079 chain_str_alloc = chain_str; 1080 } 1081 1082do_print: 1083 print(browser, first_chain, chain_str, offset, row++, arg); 1084 free(value_str_alloc); 1085 free(chain_str_alloc); 1086 1087next: 1088 if (is_output_full(browser, row)) 1089 break; 1090 node = next; 1091 } 1092 1093 return row - first_row; 1094} 1095 1096static int hist_browser__show_callchain_graph(struct hist_browser *browser, 1097 struct rb_root *root, int level, 1098 unsigned short row, u64 total, 1099 u64 parent_total, 1100 print_callchain_entry_fn print, 1101 struct callchain_print_arg *arg, 1102 check_output_full_fn is_output_full) 1103{ 1104 struct rb_node *node; 1105 int first_row = row, offset = level * LEVEL_OFFSET_STEP; 1106 bool need_percent; 1107 u64 percent_total = total; 1108 1109 if (callchain_param.mode == CHAIN_GRAPH_REL) 1110 percent_total = parent_total; 1111 1112 node = rb_first(root); 1113 need_percent = check_percent_display(node, parent_total); 1114 1115 while (node) { 1116 struct callchain_node *child = rb_entry(node, struct callchain_node, rb_node); 1117 struct rb_node *next = rb_next(node); 1118 struct callchain_list *chain; 1119 char folded_sign = ' '; 1120 int first = true; 1121 int extra_offset = 0; 1122 1123 list_for_each_entry(chain, &child->val, list) { 1124 bool was_first = first; 1125 1126 if (first) 1127 first = false; 1128 else if (need_percent) 1129 extra_offset = LEVEL_OFFSET_STEP; 1130 1131 folded_sign = callchain_list__folded(chain); 1132 1133 row += hist_browser__show_callchain_list(browser, child, 1134 chain, row, percent_total, 1135 was_first && need_percent, 1136 offset + extra_offset, 1137 print, arg); 1138 1139 if (is_output_full(browser, row)) 1140 goto out; 1141 1142 if (folded_sign == '+') 1143 break; 1144 } 1145 1146 if (folded_sign == '-') { 1147 const int new_level = level + (extra_offset ? 2 : 1); 1148 1149 row += hist_browser__show_callchain_graph(browser, &child->rb_root, 1150 new_level, row, total, 1151 child->children_hit, 1152 print, arg, is_output_full); 1153 } 1154 if (is_output_full(browser, row)) 1155 break; 1156 node = next; 1157 } 1158out: 1159 return row - first_row; 1160} 1161 1162static int hist_browser__show_callchain(struct hist_browser *browser, 1163 struct hist_entry *entry, int level, 1164 unsigned short row, 1165 print_callchain_entry_fn print, 1166 struct callchain_print_arg *arg, 1167 check_output_full_fn is_output_full) 1168{ 1169 u64 total = hists__total_period(entry->hists); 1170 u64 parent_total; 1171 int printed; 1172 1173 if (symbol_conf.cumulate_callchain) 1174 parent_total = entry->stat_acc->period; 1175 else 1176 parent_total = entry->stat.period; 1177 1178 if (callchain_param.mode == CHAIN_FLAT) { 1179 printed = hist_browser__show_callchain_flat(browser, 1180 &entry->sorted_chain, row, 1181 total, parent_total, print, arg, 1182 is_output_full); 1183 } else if (callchain_param.mode == CHAIN_FOLDED) { 1184 printed = hist_browser__show_callchain_folded(browser, 1185 &entry->sorted_chain, row, 1186 total, parent_total, print, arg, 1187 is_output_full); 1188 } else { 1189 printed = hist_browser__show_callchain_graph(browser, 1190 &entry->sorted_chain, level, row, 1191 total, parent_total, print, arg, 1192 is_output_full); 1193 } 1194 1195 if (arg->is_current_entry) 1196 browser->he_selection = entry; 1197 1198 return printed; 1199} 1200 1201struct hpp_arg { 1202 struct ui_browser *b; 1203 char folded_sign; 1204 bool current_entry; 1205}; 1206 1207int __hpp__slsmg_color_printf(struct perf_hpp *hpp, const char *fmt, ...) 1208{ 1209 struct hpp_arg *arg = hpp->ptr; 1210 int ret, len; 1211 va_list args; 1212 double percent; 1213 1214 va_start(args, fmt); 1215 len = va_arg(args, int); 1216 percent = va_arg(args, double); 1217 va_end(args); 1218 1219 ui_browser__set_percent_color(arg->b, percent, arg->current_entry); 1220 1221 ret = scnprintf(hpp->buf, hpp->size, fmt, len, percent); 1222 ui_browser__printf(arg->b, "%s", hpp->buf); 1223 1224 return ret; 1225} 1226 1227#define __HPP_COLOR_PERCENT_FN(_type, _field) \ 1228static u64 __hpp_get_##_field(struct hist_entry *he) \ 1229{ \ 1230 return he->stat._field; \ 1231} \ 1232 \ 1233static int \ 1234hist_browser__hpp_color_##_type(struct perf_hpp_fmt *fmt, \ 1235 struct perf_hpp *hpp, \ 1236 struct hist_entry *he) \ 1237{ \ 1238 return hpp__fmt(fmt, hpp, he, __hpp_get_##_field, " %*.2f%%", \ 1239 __hpp__slsmg_color_printf, true); \ 1240} 1241 1242#define __HPP_COLOR_ACC_PERCENT_FN(_type, _field) \ 1243static u64 __hpp_get_acc_##_field(struct hist_entry *he) \ 1244{ \ 1245 return he->stat_acc->_field; \ 1246} \ 1247 \ 1248static int \ 1249hist_browser__hpp_color_##_type(struct perf_hpp_fmt *fmt, \ 1250 struct perf_hpp *hpp, \ 1251 struct hist_entry *he) \ 1252{ \ 1253 if (!symbol_conf.cumulate_callchain) { \ 1254 struct hpp_arg *arg = hpp->ptr; \ 1255 int len = fmt->user_len ?: fmt->len; \ 1256 int ret = scnprintf(hpp->buf, hpp->size, \ 1257 "%*s", len, "N/A"); \ 1258 ui_browser__printf(arg->b, "%s", hpp->buf); \ 1259 \ 1260 return ret; \ 1261 } \ 1262 return hpp__fmt(fmt, hpp, he, __hpp_get_acc_##_field, \ 1263 " %*.2f%%", __hpp__slsmg_color_printf, true); \ 1264} 1265 1266__HPP_COLOR_PERCENT_FN(overhead, period) 1267__HPP_COLOR_PERCENT_FN(overhead_sys, period_sys) 1268__HPP_COLOR_PERCENT_FN(overhead_us, period_us) 1269__HPP_COLOR_PERCENT_FN(overhead_guest_sys, period_guest_sys) 1270__HPP_COLOR_PERCENT_FN(overhead_guest_us, period_guest_us) 1271__HPP_COLOR_ACC_PERCENT_FN(overhead_acc, period) 1272 1273#undef __HPP_COLOR_PERCENT_FN 1274#undef __HPP_COLOR_ACC_PERCENT_FN 1275 1276void hist_browser__init_hpp(void) 1277{ 1278 perf_hpp__format[PERF_HPP__OVERHEAD].color = 1279 hist_browser__hpp_color_overhead; 1280 perf_hpp__format[PERF_HPP__OVERHEAD_SYS].color = 1281 hist_browser__hpp_color_overhead_sys; 1282 perf_hpp__format[PERF_HPP__OVERHEAD_US].color = 1283 hist_browser__hpp_color_overhead_us; 1284 perf_hpp__format[PERF_HPP__OVERHEAD_GUEST_SYS].color = 1285 hist_browser__hpp_color_overhead_guest_sys; 1286 perf_hpp__format[PERF_HPP__OVERHEAD_GUEST_US].color = 1287 hist_browser__hpp_color_overhead_guest_us; 1288 perf_hpp__format[PERF_HPP__OVERHEAD_ACC].color = 1289 hist_browser__hpp_color_overhead_acc; 1290 1291 res_sample_init(); 1292} 1293 1294static int hist_browser__show_entry(struct hist_browser *browser, 1295 struct hist_entry *entry, 1296 unsigned short row) 1297{ 1298 int printed = 0; 1299 int width = browser->b.width; 1300 char folded_sign = ' '; 1301 bool current_entry = ui_browser__is_current_entry(&browser->b, row); 1302 bool use_callchain = hist_entry__has_callchains(entry) && symbol_conf.use_callchain; 1303 off_t row_offset = entry->row_offset; 1304 bool first = true; 1305 struct perf_hpp_fmt *fmt; 1306 1307 if (current_entry) { 1308 browser->he_selection = entry; 1309 browser->selection = &entry->ms; 1310 } 1311 1312 if (use_callchain) { 1313 hist_entry__init_have_children(entry); 1314 folded_sign = hist_entry__folded(entry); 1315 } 1316 1317 if (row_offset == 0) { 1318 struct hpp_arg arg = { 1319 .b = &browser->b, 1320 .folded_sign = folded_sign, 1321 .current_entry = current_entry, 1322 }; 1323 int column = 0; 1324 1325 ui_browser__gotorc(&browser->b, row, 0); 1326 1327 hists__for_each_format(browser->hists, fmt) { 1328 char s[2048]; 1329 struct perf_hpp hpp = { 1330 .buf = s, 1331 .size = sizeof(s), 1332 .ptr = &arg, 1333 }; 1334 1335 if (perf_hpp__should_skip(fmt, entry->hists) || 1336 column++ < browser->b.horiz_scroll) 1337 continue; 1338 1339 if (current_entry && browser->b.navkeypressed) { 1340 ui_browser__set_color(&browser->b, 1341 HE_COLORSET_SELECTED); 1342 } else { 1343 ui_browser__set_color(&browser->b, 1344 HE_COLORSET_NORMAL); 1345 } 1346 1347 if (first) { 1348 if (use_callchain) { 1349 ui_browser__printf(&browser->b, "%c ", folded_sign); 1350 width -= 2; 1351 } 1352 first = false; 1353 } else { 1354 ui_browser__printf(&browser->b, " "); 1355 width -= 2; 1356 } 1357 1358 if (fmt->color) { 1359 int ret = fmt->color(fmt, &hpp, entry); 1360 hist_entry__snprintf_alignment(entry, &hpp, fmt, ret); 1361 /* 1362 * fmt->color() already used ui_browser to 1363 * print the non alignment bits, skip it (+ret): 1364 */ 1365 ui_browser__printf(&browser->b, "%s", s + ret); 1366 } else { 1367 hist_entry__snprintf_alignment(entry, &hpp, fmt, fmt->entry(fmt, &hpp, entry)); 1368 ui_browser__printf(&browser->b, "%s", s); 1369 } 1370 width -= hpp.buf - s; 1371 } 1372 1373 /* The scroll bar isn't being used */ 1374 if (!browser->b.navkeypressed) 1375 width += 1; 1376 1377 ui_browser__write_nstring(&browser->b, "", width); 1378 1379 ++row; 1380 ++printed; 1381 } else 1382 --row_offset; 1383 1384 if (folded_sign == '-' && row != browser->b.rows) { 1385 struct callchain_print_arg arg = { 1386 .row_offset = row_offset, 1387 .is_current_entry = current_entry, 1388 }; 1389 1390 printed += hist_browser__show_callchain(browser, 1391 entry, 1, row, 1392 hist_browser__show_callchain_entry, 1393 &arg, 1394 hist_browser__check_output_full); 1395 } 1396 1397 return printed; 1398} 1399 1400static int hist_browser__show_hierarchy_entry(struct hist_browser *browser, 1401 struct hist_entry *entry, 1402 unsigned short row, 1403 int level) 1404{ 1405 int printed = 0; 1406 int width = browser->b.width; 1407 char folded_sign = ' '; 1408 bool current_entry = ui_browser__is_current_entry(&browser->b, row); 1409 off_t row_offset = entry->row_offset; 1410 bool first = true; 1411 struct perf_hpp_fmt *fmt; 1412 struct perf_hpp_list_node *fmt_node; 1413 struct hpp_arg arg = { 1414 .b = &browser->b, 1415 .current_entry = current_entry, 1416 }; 1417 int column = 0; 1418 int hierarchy_indent = (entry->hists->nr_hpp_node - 2) * HIERARCHY_INDENT; 1419 1420 if (current_entry) { 1421 browser->he_selection = entry; 1422 browser->selection = &entry->ms; 1423 } 1424 1425 hist_entry__init_have_children(entry); 1426 folded_sign = hist_entry__folded(entry); 1427 arg.folded_sign = folded_sign; 1428 1429 if (entry->leaf && row_offset) { 1430 row_offset--; 1431 goto show_callchain; 1432 } 1433 1434 ui_browser__gotorc(&browser->b, row, 0); 1435 1436 if (current_entry && browser->b.navkeypressed) 1437 ui_browser__set_color(&browser->b, HE_COLORSET_SELECTED); 1438 else 1439 ui_browser__set_color(&browser->b, HE_COLORSET_NORMAL); 1440 1441 ui_browser__write_nstring(&browser->b, "", level * HIERARCHY_INDENT); 1442 width -= level * HIERARCHY_INDENT; 1443 1444 /* the first hpp_list_node is for overhead columns */ 1445 fmt_node = list_first_entry(&entry->hists->hpp_formats, 1446 struct perf_hpp_list_node, list); 1447 perf_hpp_list__for_each_format(&fmt_node->hpp, fmt) { 1448 char s[2048]; 1449 struct perf_hpp hpp = { 1450 .buf = s, 1451 .size = sizeof(s), 1452 .ptr = &arg, 1453 }; 1454 1455 if (perf_hpp__should_skip(fmt, entry->hists) || 1456 column++ < browser->b.horiz_scroll) 1457 continue; 1458 1459 if (current_entry && browser->b.navkeypressed) { 1460 ui_browser__set_color(&browser->b, 1461 HE_COLORSET_SELECTED); 1462 } else { 1463 ui_browser__set_color(&browser->b, 1464 HE_COLORSET_NORMAL); 1465 } 1466 1467 if (first) { 1468 ui_browser__printf(&browser->b, "%c ", folded_sign); 1469 width -= 2; 1470 first = false; 1471 } else { 1472 ui_browser__printf(&browser->b, " "); 1473 width -= 2; 1474 } 1475 1476 if (fmt->color) { 1477 int ret = fmt->color(fmt, &hpp, entry); 1478 hist_entry__snprintf_alignment(entry, &hpp, fmt, ret); 1479 /* 1480 * fmt->color() already used ui_browser to 1481 * print the non alignment bits, skip it (+ret): 1482 */ 1483 ui_browser__printf(&browser->b, "%s", s + ret); 1484 } else { 1485 int ret = fmt->entry(fmt, &hpp, entry); 1486 hist_entry__snprintf_alignment(entry, &hpp, fmt, ret); 1487 ui_browser__printf(&browser->b, "%s", s); 1488 } 1489 width -= hpp.buf - s; 1490 } 1491 1492 if (!first) { 1493 ui_browser__write_nstring(&browser->b, "", hierarchy_indent); 1494 width -= hierarchy_indent; 1495 } 1496 1497 if (column >= browser->b.horiz_scroll) { 1498 char s[2048]; 1499 struct perf_hpp hpp = { 1500 .buf = s, 1501 .size = sizeof(s), 1502 .ptr = &arg, 1503 }; 1504 1505 if (current_entry && browser->b.navkeypressed) { 1506 ui_browser__set_color(&browser->b, 1507 HE_COLORSET_SELECTED); 1508 } else { 1509 ui_browser__set_color(&browser->b, 1510 HE_COLORSET_NORMAL); 1511 } 1512 1513 perf_hpp_list__for_each_format(entry->hpp_list, fmt) { 1514 if (first) { 1515 ui_browser__printf(&browser->b, "%c ", folded_sign); 1516 first = false; 1517 } else { 1518 ui_browser__write_nstring(&browser->b, "", 2); 1519 } 1520 1521 width -= 2; 1522 1523 /* 1524 * No need to call hist_entry__snprintf_alignment() 1525 * since this fmt is always the last column in the 1526 * hierarchy mode. 1527 */ 1528 if (fmt->color) { 1529 width -= fmt->color(fmt, &hpp, entry); 1530 } else { 1531 int i = 0; 1532 1533 width -= fmt->entry(fmt, &hpp, entry); 1534 ui_browser__printf(&browser->b, "%s", skip_spaces(s)); 1535 1536 while (isspace(s[i++])) 1537 width++; 1538 } 1539 } 1540 } 1541 1542 /* The scroll bar isn't being used */ 1543 if (!browser->b.navkeypressed) 1544 width += 1; 1545 1546 ui_browser__write_nstring(&browser->b, "", width); 1547 1548 ++row; 1549 ++printed; 1550 1551show_callchain: 1552 if (entry->leaf && folded_sign == '-' && row != browser->b.rows) { 1553 struct callchain_print_arg carg = { 1554 .row_offset = row_offset, 1555 }; 1556 1557 printed += hist_browser__show_callchain(browser, entry, 1558 level + 1, row, 1559 hist_browser__show_callchain_entry, &carg, 1560 hist_browser__check_output_full); 1561 } 1562 1563 return printed; 1564} 1565 1566static int hist_browser__show_no_entry(struct hist_browser *browser, 1567 unsigned short row, int level) 1568{ 1569 int width = browser->b.width; 1570 bool current_entry = ui_browser__is_current_entry(&browser->b, row); 1571 bool first = true; 1572 int column = 0; 1573 int ret; 1574 struct perf_hpp_fmt *fmt; 1575 struct perf_hpp_list_node *fmt_node; 1576 int indent = browser->hists->nr_hpp_node - 2; 1577 1578 if (current_entry) { 1579 browser->he_selection = NULL; 1580 browser->selection = NULL; 1581 } 1582 1583 ui_browser__gotorc(&browser->b, row, 0); 1584 1585 if (current_entry && browser->b.navkeypressed) 1586 ui_browser__set_color(&browser->b, HE_COLORSET_SELECTED); 1587 else 1588 ui_browser__set_color(&browser->b, HE_COLORSET_NORMAL); 1589 1590 ui_browser__write_nstring(&browser->b, "", level * HIERARCHY_INDENT); 1591 width -= level * HIERARCHY_INDENT; 1592 1593 /* the first hpp_list_node is for overhead columns */ 1594 fmt_node = list_first_entry(&browser->hists->hpp_formats, 1595 struct perf_hpp_list_node, list); 1596 perf_hpp_list__for_each_format(&fmt_node->hpp, fmt) { 1597 if (perf_hpp__should_skip(fmt, browser->hists) || 1598 column++ < browser->b.horiz_scroll) 1599 continue; 1600 1601 ret = fmt->width(fmt, NULL, browser->hists); 1602 1603 if (first) { 1604 /* for folded sign */ 1605 first = false; 1606 ret++; 1607 } else { 1608 /* space between columns */ 1609 ret += 2; 1610 } 1611 1612 ui_browser__write_nstring(&browser->b, "", ret); 1613 width -= ret; 1614 } 1615 1616 ui_browser__write_nstring(&browser->b, "", indent * HIERARCHY_INDENT); 1617 width -= indent * HIERARCHY_INDENT; 1618 1619 if (column >= browser->b.horiz_scroll) { 1620 char buf[32]; 1621 1622 ret = snprintf(buf, sizeof(buf), "no entry >= %.2f%%", browser->min_pcnt); 1623 ui_browser__printf(&browser->b, " %s", buf); 1624 width -= ret + 2; 1625 } 1626 1627 /* The scroll bar isn't being used */ 1628 if (!browser->b.navkeypressed) 1629 width += 1; 1630 1631 ui_browser__write_nstring(&browser->b, "", width); 1632 return 1; 1633} 1634 1635static int advance_hpp_check(struct perf_hpp *hpp, int inc) 1636{ 1637 advance_hpp(hpp, inc); 1638 return hpp->size <= 0; 1639} 1640 1641static int 1642hists_browser__scnprintf_headers(struct hist_browser *browser, char *buf, 1643 size_t size, int line) 1644{ 1645 struct hists *hists = browser->hists; 1646 struct perf_hpp dummy_hpp = { 1647 .buf = buf, 1648 .size = size, 1649 }; 1650 struct perf_hpp_fmt *fmt; 1651 size_t ret = 0; 1652 int column = 0; 1653 int span = 0; 1654 1655 if (hists__has_callchains(hists) && symbol_conf.use_callchain) { 1656 ret = scnprintf(buf, size, " "); 1657 if (advance_hpp_check(&dummy_hpp, ret)) 1658 return ret; 1659 } 1660 1661 hists__for_each_format(browser->hists, fmt) { 1662 if (perf_hpp__should_skip(fmt, hists) || column++ < browser->b.horiz_scroll) 1663 continue; 1664 1665 ret = fmt->header(fmt, &dummy_hpp, hists, line, &span); 1666 if (advance_hpp_check(&dummy_hpp, ret)) 1667 break; 1668 1669 if (span) 1670 continue; 1671 1672 ret = scnprintf(dummy_hpp.buf, dummy_hpp.size, " "); 1673 if (advance_hpp_check(&dummy_hpp, ret)) 1674 break; 1675 } 1676 1677 return ret; 1678} 1679 1680static int hists_browser__scnprintf_hierarchy_headers(struct hist_browser *browser, char *buf, size_t size) 1681{ 1682 struct hists *hists = browser->hists; 1683 struct perf_hpp dummy_hpp = { 1684 .buf = buf, 1685 .size = size, 1686 }; 1687 struct perf_hpp_fmt *fmt; 1688 struct perf_hpp_list_node *fmt_node; 1689 size_t ret = 0; 1690 int column = 0; 1691 int indent = hists->nr_hpp_node - 2; 1692 bool first_node, first_col; 1693 1694 ret = scnprintf(buf, size, " "); 1695 if (advance_hpp_check(&dummy_hpp, ret)) 1696 return ret; 1697 1698 first_node = true; 1699 /* the first hpp_list_node is for overhead columns */ 1700 fmt_node = list_first_entry(&hists->hpp_formats, 1701 struct perf_hpp_list_node, list); 1702 perf_hpp_list__for_each_format(&fmt_node->hpp, fmt) { 1703 if (column++ < browser->b.horiz_scroll) 1704 continue; 1705 1706 ret = fmt->header(fmt, &dummy_hpp, hists, 0, NULL); 1707 if (advance_hpp_check(&dummy_hpp, ret)) 1708 break; 1709 1710 ret = scnprintf(dummy_hpp.buf, dummy_hpp.size, " "); 1711 if (advance_hpp_check(&dummy_hpp, ret)) 1712 break; 1713 1714 first_node = false; 1715 } 1716 1717 if (!first_node) { 1718 ret = scnprintf(dummy_hpp.buf, dummy_hpp.size, "%*s", 1719 indent * HIERARCHY_INDENT, ""); 1720 if (advance_hpp_check(&dummy_hpp, ret)) 1721 return ret; 1722 } 1723 1724 first_node = true; 1725 list_for_each_entry_continue(fmt_node, &hists->hpp_formats, list) { 1726 if (!first_node) { 1727 ret = scnprintf(dummy_hpp.buf, dummy_hpp.size, " / "); 1728 if (advance_hpp_check(&dummy_hpp, ret)) 1729 break; 1730 } 1731 first_node = false; 1732 1733 first_col = true; 1734 perf_hpp_list__for_each_format(&fmt_node->hpp, fmt) { 1735 char *start; 1736 1737 if (perf_hpp__should_skip(fmt, hists)) 1738 continue; 1739 1740 if (!first_col) { 1741 ret = scnprintf(dummy_hpp.buf, dummy_hpp.size, "+"); 1742 if (advance_hpp_check(&dummy_hpp, ret)) 1743 break; 1744 } 1745 first_col = false; 1746 1747 ret = fmt->header(fmt, &dummy_hpp, hists, 0, NULL); 1748 dummy_hpp.buf[ret] = '\0'; 1749 1750 start = strim(dummy_hpp.buf); 1751 ret = strlen(start); 1752 1753 if (start != dummy_hpp.buf) 1754 memmove(dummy_hpp.buf, start, ret + 1); 1755 1756 if (advance_hpp_check(&dummy_hpp, ret)) 1757 break; 1758 } 1759 } 1760 1761 return ret; 1762} 1763 1764static void hists_browser__hierarchy_headers(struct hist_browser *browser) 1765{ 1766 char headers[1024]; 1767 1768 hists_browser__scnprintf_hierarchy_headers(browser, headers, 1769 sizeof(headers)); 1770 1771 ui_browser__gotorc_title(&browser->b, 0, 0); 1772 ui_browser__set_color(&browser->b, HE_COLORSET_ROOT); 1773 ui_browser__write_nstring(&browser->b, headers, browser->b.width + 1); 1774} 1775 1776static void hists_browser__headers(struct hist_browser *browser) 1777{ 1778 struct hists *hists = browser->hists; 1779 struct perf_hpp_list *hpp_list = hists->hpp_list; 1780 1781 int line; 1782 1783 for (line = 0; line < hpp_list->nr_header_lines; line++) { 1784 char headers[1024]; 1785 1786 hists_browser__scnprintf_headers(browser, headers, 1787 sizeof(headers), line); 1788 1789 ui_browser__gotorc_title(&browser->b, line, 0); 1790 ui_browser__set_color(&browser->b, HE_COLORSET_ROOT); 1791 ui_browser__write_nstring(&browser->b, headers, browser->b.width + 1); 1792 } 1793} 1794 1795static void hist_browser__show_headers(struct hist_browser *browser) 1796{ 1797 if (symbol_conf.report_hierarchy) 1798 hists_browser__hierarchy_headers(browser); 1799 else 1800 hists_browser__headers(browser); 1801} 1802 1803static void ui_browser__hists_init_top(struct ui_browser *browser) 1804{ 1805 if (browser->top == NULL) { 1806 struct hist_browser *hb; 1807 1808 hb = container_of(browser, struct hist_browser, b); 1809 browser->top = rb_first_cached(&hb->hists->entries); 1810 } 1811} 1812 1813static unsigned int hist_browser__refresh(struct ui_browser *browser) 1814{ 1815 unsigned row = 0; 1816 struct rb_node *nd; 1817 struct hist_browser *hb = container_of(browser, struct hist_browser, b); 1818 1819 if (hb->show_headers) 1820 hist_browser__show_headers(hb); 1821 1822 ui_browser__hists_init_top(browser); 1823 hb->he_selection = NULL; 1824 hb->selection = NULL; 1825 1826 for (nd = browser->top; nd; nd = rb_hierarchy_next(nd)) { 1827 struct hist_entry *h = rb_entry(nd, struct hist_entry, rb_node); 1828 float percent; 1829 1830 if (h->filtered) { 1831 /* let it move to sibling */ 1832 h->unfolded = false; 1833 continue; 1834 } 1835 1836 if (symbol_conf.report_individual_block) 1837 percent = block_info__total_cycles_percent(h); 1838 else 1839 percent = hist_entry__get_percent_limit(h); 1840 1841 if (percent < hb->min_pcnt) 1842 continue; 1843 1844 if (symbol_conf.report_hierarchy) { 1845 row += hist_browser__show_hierarchy_entry(hb, h, row, 1846 h->depth); 1847 if (row == browser->rows) 1848 break; 1849 1850 if (h->has_no_entry) { 1851 hist_browser__show_no_entry(hb, row, h->depth + 1); 1852 row++; 1853 } 1854 } else { 1855 row += hist_browser__show_entry(hb, h, row); 1856 } 1857 1858 if (row == browser->rows) 1859 break; 1860 } 1861 1862 return row; 1863} 1864 1865static struct rb_node *hists__filter_entries(struct rb_node *nd, 1866 float min_pcnt) 1867{ 1868 while (nd != NULL) { 1869 struct hist_entry *h = rb_entry(nd, struct hist_entry, rb_node); 1870 float percent = hist_entry__get_percent_limit(h); 1871 1872 if (!h->filtered && percent >= min_pcnt) 1873 return nd; 1874 1875 /* 1876 * If it's filtered, its all children also were filtered. 1877 * So move to sibling node. 1878 */ 1879 if (rb_next(nd)) 1880 nd = rb_next(nd); 1881 else 1882 nd = rb_hierarchy_next(nd); 1883 } 1884 1885 return NULL; 1886} 1887 1888static struct rb_node *hists__filter_prev_entries(struct rb_node *nd, 1889 float min_pcnt) 1890{ 1891 while (nd != NULL) { 1892 struct hist_entry *h = rb_entry(nd, struct hist_entry, rb_node); 1893 float percent = hist_entry__get_percent_limit(h); 1894 1895 if (!h->filtered && percent >= min_pcnt) 1896 return nd; 1897 1898 nd = rb_hierarchy_prev(nd); 1899 } 1900 1901 return NULL; 1902} 1903 1904static void ui_browser__hists_seek(struct ui_browser *browser, 1905 off_t offset, int whence) 1906{ 1907 struct hist_entry *h; 1908 struct rb_node *nd; 1909 bool first = true; 1910 struct hist_browser *hb; 1911 1912 hb = container_of(browser, struct hist_browser, b); 1913 1914 if (browser->nr_entries == 0) 1915 return; 1916 1917 ui_browser__hists_init_top(browser); 1918 1919 switch (whence) { 1920 case SEEK_SET: 1921 nd = hists__filter_entries(rb_first(browser->entries), 1922 hb->min_pcnt); 1923 break; 1924 case SEEK_CUR: 1925 nd = browser->top; 1926 goto do_offset; 1927 case SEEK_END: 1928 nd = rb_hierarchy_last(rb_last(browser->entries)); 1929 nd = hists__filter_prev_entries(nd, hb->min_pcnt); 1930 first = false; 1931 break; 1932 default: 1933 return; 1934 } 1935 1936 /* 1937 * Moves not relative to the first visible entry invalidates its 1938 * row_offset: 1939 */ 1940 h = rb_entry(browser->top, struct hist_entry, rb_node); 1941 h->row_offset = 0; 1942 1943 /* 1944 * Here we have to check if nd is expanded (+), if it is we can't go 1945 * the next top level hist_entry, instead we must compute an offset of 1946 * what _not_ to show and not change the first visible entry. 1947 * 1948 * This offset increments when we are going from top to bottom and 1949 * decreases when we're going from bottom to top. 1950 * 1951 * As we don't have backpointers to the top level in the callchains 1952 * structure, we need to always print the whole hist_entry callchain, 1953 * skipping the first ones that are before the first visible entry 1954 * and stop when we printed enough lines to fill the screen. 1955 */ 1956do_offset: 1957 if (!nd) 1958 return; 1959 1960 if (offset > 0) { 1961 do { 1962 h = rb_entry(nd, struct hist_entry, rb_node); 1963 if (h->unfolded && h->leaf) { 1964 u16 remaining = h->nr_rows - h->row_offset; 1965 if (offset > remaining) { 1966 offset -= remaining; 1967 h->row_offset = 0; 1968 } else { 1969 h->row_offset += offset; 1970 offset = 0; 1971 browser->top = nd; 1972 break; 1973 } 1974 } 1975 nd = hists__filter_entries(rb_hierarchy_next(nd), 1976 hb->min_pcnt); 1977 if (nd == NULL) 1978 break; 1979 --offset; 1980 browser->top = nd; 1981 } while (offset != 0); 1982 } else if (offset < 0) { 1983 while (1) { 1984 h = rb_entry(nd, struct hist_entry, rb_node); 1985 if (h->unfolded && h->leaf) { 1986 if (first) { 1987 if (-offset > h->row_offset) { 1988 offset += h->row_offset; 1989 h->row_offset = 0; 1990 } else { 1991 h->row_offset += offset; 1992 offset = 0; 1993 browser->top = nd; 1994 break; 1995 } 1996 } else { 1997 if (-offset > h->nr_rows) { 1998 offset += h->nr_rows; 1999 h->row_offset = 0; 2000 } else { 2001 h->row_offset = h->nr_rows + offset; 2002 offset = 0; 2003 browser->top = nd; 2004 break; 2005 } 2006 } 2007 } 2008 2009 nd = hists__filter_prev_entries(rb_hierarchy_prev(nd), 2010 hb->min_pcnt); 2011 if (nd == NULL) 2012 break; 2013 ++offset; 2014 browser->top = nd; 2015 if (offset == 0) { 2016 /* 2017 * Last unfiltered hist_entry, check if it is 2018 * unfolded, if it is then we should have 2019 * row_offset at its last entry. 2020 */ 2021 h = rb_entry(nd, struct hist_entry, rb_node); 2022 if (h->unfolded && h->leaf) 2023 h->row_offset = h->nr_rows; 2024 break; 2025 } 2026 first = false; 2027 } 2028 } else { 2029 browser->top = nd; 2030 h = rb_entry(nd, struct hist_entry, rb_node); 2031 h->row_offset = 0; 2032 } 2033} 2034 2035static int hist_browser__fprintf_callchain(struct hist_browser *browser, 2036 struct hist_entry *he, FILE *fp, 2037 int level) 2038{ 2039 struct callchain_print_arg arg = { 2040 .fp = fp, 2041 }; 2042 2043 hist_browser__show_callchain(browser, he, level, 0, 2044 hist_browser__fprintf_callchain_entry, &arg, 2045 hist_browser__check_dump_full); 2046 return arg.printed; 2047} 2048 2049static int hist_browser__fprintf_entry(struct hist_browser *browser, 2050 struct hist_entry *he, FILE *fp) 2051{ 2052 char s[8192]; 2053 int printed = 0; 2054 char folded_sign = ' '; 2055 struct perf_hpp hpp = { 2056 .buf = s, 2057 .size = sizeof(s), 2058 }; 2059 struct perf_hpp_fmt *fmt; 2060 bool first = true; 2061 int ret; 2062 2063 if (hist_entry__has_callchains(he) && symbol_conf.use_callchain) { 2064 folded_sign = hist_entry__folded(he); 2065 printed += fprintf(fp, "%c ", folded_sign); 2066 } 2067 2068 hists__for_each_format(browser->hists, fmt) { 2069 if (perf_hpp__should_skip(fmt, he->hists)) 2070 continue; 2071 2072 if (!first) { 2073 ret = scnprintf(hpp.buf, hpp.size, " "); 2074 advance_hpp(&hpp, ret); 2075 } else 2076 first = false; 2077 2078 ret = fmt->entry(fmt, &hpp, he); 2079 ret = hist_entry__snprintf_alignment(he, &hpp, fmt, ret); 2080 advance_hpp(&hpp, ret); 2081 } 2082 printed += fprintf(fp, "%s\n", s); 2083 2084 if (folded_sign == '-') 2085 printed += hist_browser__fprintf_callchain(browser, he, fp, 1); 2086 2087 return printed; 2088} 2089 2090 2091static int hist_browser__fprintf_hierarchy_entry(struct hist_browser *browser, 2092 struct hist_entry *he, 2093 FILE *fp, int level) 2094{ 2095 char s[8192]; 2096 int printed = 0; 2097 char folded_sign = ' '; 2098 struct perf_hpp hpp = { 2099 .buf = s, 2100 .size = sizeof(s), 2101 }; 2102 struct perf_hpp_fmt *fmt; 2103 struct perf_hpp_list_node *fmt_node; 2104 bool first = true; 2105 int ret; 2106 int hierarchy_indent = (he->hists->nr_hpp_node - 2) * HIERARCHY_INDENT; 2107 2108 printed = fprintf(fp, "%*s", level * HIERARCHY_INDENT, ""); 2109 2110 folded_sign = hist_entry__folded(he); 2111 printed += fprintf(fp, "%c", folded_sign); 2112 2113 /* the first hpp_list_node is for overhead columns */ 2114 fmt_node = list_first_entry(&he->hists->hpp_formats, 2115 struct perf_hpp_list_node, list); 2116 perf_hpp_list__for_each_format(&fmt_node->hpp, fmt) { 2117 if (!first) { 2118 ret = scnprintf(hpp.buf, hpp.size, " "); 2119 advance_hpp(&hpp, ret); 2120 } else 2121 first = false; 2122 2123 ret = fmt->entry(fmt, &hpp, he); 2124 advance_hpp(&hpp, ret); 2125 } 2126 2127 ret = scnprintf(hpp.buf, hpp.size, "%*s", hierarchy_indent, ""); 2128 advance_hpp(&hpp, ret); 2129 2130 perf_hpp_list__for_each_format(he->hpp_list, fmt) { 2131 ret = scnprintf(hpp.buf, hpp.size, " "); 2132 advance_hpp(&hpp, ret); 2133 2134 ret = fmt->entry(fmt, &hpp, he); 2135 advance_hpp(&hpp, ret); 2136 } 2137 2138 strim(s); 2139 printed += fprintf(fp, "%s\n", s); 2140 2141 if (he->leaf && folded_sign == '-') { 2142 printed += hist_browser__fprintf_callchain(browser, he, fp, 2143 he->depth + 1); 2144 } 2145 2146 return printed; 2147} 2148 2149static int hist_browser__fprintf(struct hist_browser *browser, FILE *fp) 2150{ 2151 struct rb_node *nd = hists__filter_entries(rb_first(browser->b.entries), 2152 browser->min_pcnt); 2153 int printed = 0; 2154 2155 while (nd) { 2156 struct hist_entry *h = rb_entry(nd, struct hist_entry, rb_node); 2157 2158 if (symbol_conf.report_hierarchy) { 2159 printed += hist_browser__fprintf_hierarchy_entry(browser, 2160 h, fp, 2161 h->depth); 2162 } else { 2163 printed += hist_browser__fprintf_entry(browser, h, fp); 2164 } 2165 2166 nd = hists__filter_entries(rb_hierarchy_next(nd), 2167 browser->min_pcnt); 2168 } 2169 2170 return printed; 2171} 2172 2173static int hist_browser__dump(struct hist_browser *browser) 2174{ 2175 char filename[64]; 2176 FILE *fp; 2177 2178 while (1) { 2179 scnprintf(filename, sizeof(filename), "perf.hist.%d", browser->print_seq); 2180 if (access(filename, F_OK)) 2181 break; 2182 /* 2183 * XXX: Just an arbitrary lazy upper limit 2184 */ 2185 if (++browser->print_seq == 8192) { 2186 ui_helpline__fpush("Too many perf.hist.N files, nothing written!"); 2187 return -1; 2188 } 2189 } 2190 2191 fp = fopen(filename, "w"); 2192 if (fp == NULL) { 2193 char bf[64]; 2194 const char *err = str_error_r(errno, bf, sizeof(bf)); 2195 ui_helpline__fpush("Couldn't write to %s: %s", filename, err); 2196 return -1; 2197 } 2198 2199 ++browser->print_seq; 2200 hist_browser__fprintf(browser, fp); 2201 fclose(fp); 2202 ui_helpline__fpush("%s written!", filename); 2203 2204 return 0; 2205} 2206 2207void hist_browser__init(struct hist_browser *browser, 2208 struct hists *hists) 2209{ 2210 struct perf_hpp_fmt *fmt; 2211 2212 browser->hists = hists; 2213 browser->b.refresh = hist_browser__refresh; 2214 browser->b.refresh_dimensions = hist_browser__refresh_dimensions; 2215 browser->b.seek = ui_browser__hists_seek; 2216 browser->b.use_navkeypressed = true; 2217 browser->show_headers = symbol_conf.show_hist_headers; 2218 hist_browser__set_title_space(browser); 2219 2220 if (symbol_conf.report_hierarchy) { 2221 struct perf_hpp_list_node *fmt_node; 2222 2223 /* count overhead columns (in the first node) */ 2224 fmt_node = list_first_entry(&hists->hpp_formats, 2225 struct perf_hpp_list_node, list); 2226 perf_hpp_list__for_each_format(&fmt_node->hpp, fmt) 2227 ++browser->b.columns; 2228 2229 /* add a single column for whole hierarchy sort keys*/ 2230 ++browser->b.columns; 2231 } else { 2232 hists__for_each_format(hists, fmt) 2233 ++browser->b.columns; 2234 } 2235 2236 hists__reset_column_width(hists); 2237} 2238 2239struct hist_browser *hist_browser__new(struct hists *hists) 2240{ 2241 struct hist_browser *browser = zalloc(sizeof(*browser)); 2242 2243 if (browser) 2244 hist_browser__init(browser, hists); 2245 2246 return browser; 2247} 2248 2249static struct hist_browser * 2250perf_evsel_browser__new(struct evsel *evsel, 2251 struct hist_browser_timer *hbt, 2252 struct perf_env *env, 2253 struct annotation_options *annotation_opts) 2254{ 2255 struct hist_browser *browser = hist_browser__new(evsel__hists(evsel)); 2256 2257 if (browser) { 2258 browser->hbt = hbt; 2259 browser->env = env; 2260 browser->title = hists_browser__scnprintf_title; 2261 browser->annotation_opts = annotation_opts; 2262 } 2263 return browser; 2264} 2265 2266void hist_browser__delete(struct hist_browser *browser) 2267{ 2268 free(browser); 2269} 2270 2271static struct hist_entry *hist_browser__selected_entry(struct hist_browser *browser) 2272{ 2273 return browser->he_selection; 2274} 2275 2276static struct thread *hist_browser__selected_thread(struct hist_browser *browser) 2277{ 2278 return browser->he_selection->thread; 2279} 2280 2281static struct res_sample *hist_browser__selected_res_sample(struct hist_browser *browser) 2282{ 2283 return browser->he_selection ? browser->he_selection->res_samples : NULL; 2284} 2285 2286/* Check whether the browser is for 'top' or 'report' */ 2287static inline bool is_report_browser(void *timer) 2288{ 2289 return timer == NULL; 2290} 2291 2292static int hists_browser__scnprintf_title(struct hist_browser *browser, char *bf, size_t size) 2293{ 2294 struct hist_browser_timer *hbt = browser->hbt; 2295 int printed = __hists__scnprintf_title(browser->hists, bf, size, !is_report_browser(hbt)); 2296 2297 if (!is_report_browser(hbt)) { 2298 struct perf_top *top = hbt->arg; 2299 2300 printed += scnprintf(bf + printed, size - printed, 2301 " lost: %" PRIu64 "/%" PRIu64, 2302 top->lost, top->lost_total); 2303 2304 printed += scnprintf(bf + printed, size - printed, 2305 " drop: %" PRIu64 "/%" PRIu64, 2306 top->drop, top->drop_total); 2307 2308 if (top->zero) 2309 printed += scnprintf(bf + printed, size - printed, " [z]"); 2310 2311 perf_top__reset_sample_counters(top); 2312 } 2313 2314 2315 return printed; 2316} 2317 2318static inline void free_popup_options(char **options, int n) 2319{ 2320 int i; 2321 2322 for (i = 0; i < n; ++i) 2323 zfree(&options[i]); 2324} 2325 2326/* 2327 * Only runtime switching of perf data file will make "input_name" point 2328 * to a malloced buffer. So add "is_input_name_malloced" flag to decide 2329 * whether we need to call free() for current "input_name" during the switch. 2330 */ 2331static bool is_input_name_malloced = false; 2332 2333static int switch_data_file(void) 2334{ 2335 char *pwd, *options[32], *abs_path[32], *tmp; 2336 DIR *pwd_dir; 2337 int nr_options = 0, choice = -1, ret = -1; 2338 struct dirent *dent; 2339 2340 pwd = getenv("PWD"); 2341 if (!pwd) 2342 return ret; 2343 2344 pwd_dir = opendir(pwd); 2345 if (!pwd_dir) 2346 return ret; 2347 2348 memset(options, 0, sizeof(options)); 2349 memset(abs_path, 0, sizeof(abs_path)); 2350 2351 while ((dent = readdir(pwd_dir))) { 2352 char path[PATH_MAX]; 2353 u64 magic; 2354 char *name = dent->d_name; 2355 FILE *file; 2356 2357 if (!(dent->d_type == DT_REG)) 2358 continue; 2359 2360 snprintf(path, sizeof(path), "%s/%s", pwd, name); 2361 2362 file = fopen(path, "r"); 2363 if (!file) 2364 continue; 2365 2366 if (fread(&magic, 1, 8, file) < 8) 2367 goto close_file_and_continue; 2368 2369 if (is_perf_magic(magic)) { 2370 options[nr_options] = strdup(name); 2371 if (!options[nr_options]) 2372 goto close_file_and_continue; 2373 2374 abs_path[nr_options] = strdup(path); 2375 if (!abs_path[nr_options]) { 2376 zfree(&options[nr_options]); 2377 ui__warning("Can't search all data files due to memory shortage.\n"); 2378 fclose(file); 2379 break; 2380 } 2381 2382 nr_options++; 2383 } 2384 2385close_file_and_continue: 2386 fclose(file); 2387 if (nr_options >= 32) { 2388 ui__warning("Too many perf data files in PWD!\n" 2389 "Only the first 32 files will be listed.\n"); 2390 break; 2391 } 2392 } 2393 closedir(pwd_dir); 2394 2395 if (nr_options) { 2396 choice = ui__popup_menu(nr_options, options, NULL); 2397 if (choice < nr_options && choice >= 0) { 2398 tmp = strdup(abs_path[choice]); 2399 if (tmp) { 2400 if (is_input_name_malloced) 2401 free((void *)input_name); 2402 input_name = tmp; 2403 is_input_name_malloced = true; 2404 ret = 0; 2405 } else 2406 ui__warning("Data switch failed due to memory shortage!\n"); 2407 } 2408 } 2409 2410 free_popup_options(options, nr_options); 2411 free_popup_options(abs_path, nr_options); 2412 return ret; 2413} 2414 2415struct popup_action { 2416 unsigned long time; 2417 struct thread *thread; 2418 struct map_symbol ms; 2419 int socket; 2420 struct evsel *evsel; 2421 enum rstype rstype; 2422 2423 int (*fn)(struct hist_browser *browser, struct popup_action *act); 2424}; 2425 2426static int 2427do_annotate(struct hist_browser *browser, struct popup_action *act) 2428{ 2429 struct evsel *evsel; 2430 struct annotation *notes; 2431 struct hist_entry *he; 2432 int err; 2433 2434 if (!browser->annotation_opts->objdump_path && 2435 perf_env__lookup_objdump(browser->env, &browser->annotation_opts->objdump_path)) 2436 return 0; 2437 2438 notes = symbol__annotation(act->ms.sym); 2439 if (!notes->src) 2440 return 0; 2441 2442 if (browser->block_evsel) 2443 evsel = browser->block_evsel; 2444 else 2445 evsel = hists_to_evsel(browser->hists); 2446 2447 err = map_symbol__tui_annotate(&act->ms, evsel, browser->hbt, 2448 browser->annotation_opts); 2449 he = hist_browser__selected_entry(browser); 2450 /* 2451 * offer option to annotate the other branch source or target 2452 * (if they exists) when returning from annotate 2453 */ 2454 if ((err == 'q' || err == CTRL('c')) && he->branch_info) 2455 return 1; 2456 2457 ui_browser__update_nr_entries(&browser->b, browser->hists->nr_entries); 2458 if (err) 2459 ui_browser__handle_resize(&browser->b); 2460 return 0; 2461} 2462 2463static struct symbol *symbol__new_unresolved(u64 addr, struct map *map) 2464{ 2465 struct annotated_source *src; 2466 struct symbol *sym; 2467 char name[64]; 2468 2469 snprintf(name, sizeof(name), "%.*" PRIx64, BITS_PER_LONG / 4, addr); 2470 2471 sym = symbol__new(addr, ANNOTATION_DUMMY_LEN, 0, 0, name); 2472 if (sym) { 2473 src = symbol__hists(sym, 1); 2474 if (!src) { 2475 symbol__delete(sym); 2476 return NULL; 2477 } 2478 2479 dso__insert_symbol(map->dso, sym); 2480 } 2481 2482 return sym; 2483} 2484 2485static int 2486add_annotate_opt(struct hist_browser *browser __maybe_unused, 2487 struct popup_action *act, char **optstr, 2488 struct map_symbol *ms, 2489 u64 addr) 2490{ 2491 if (!ms->map || !ms->map->dso || ms->map->dso->annotate_warned) 2492 return 0; 2493 2494 if (!ms->sym) 2495 ms->sym = symbol__new_unresolved(addr, ms->map); 2496 2497 if (ms->sym == NULL || symbol__annotation(ms->sym)->src == NULL) 2498 return 0; 2499 2500 if (asprintf(optstr, "Annotate %s", ms->sym->name) < 0) 2501 return 0; 2502 2503 act->ms = *ms; 2504 act->fn = do_annotate; 2505 return 1; 2506} 2507 2508static int 2509do_zoom_thread(struct hist_browser *browser, struct popup_action *act) 2510{ 2511 struct thread *thread = act->thread; 2512 2513 if ((!hists__has(browser->hists, thread) && 2514 !hists__has(browser->hists, comm)) || thread == NULL) 2515 return 0; 2516 2517 if (browser->hists->thread_filter) { 2518 pstack__remove(browser->pstack, &browser->hists->thread_filter); 2519 perf_hpp__set_elide(HISTC_THREAD, false); 2520 thread__zput(browser->hists->thread_filter); 2521 ui_helpline__pop(); 2522 } else { 2523 if (hists__has(browser->hists, thread)) { 2524 ui_helpline__fpush("To zoom out press ESC or ENTER + \"Zoom out of %s(%d) thread\"", 2525 thread->comm_set ? thread__comm_str(thread) : "", 2526 thread->tid); 2527 } else { 2528 ui_helpline__fpush("To zoom out press ESC or ENTER + \"Zoom out of %s thread\"", 2529 thread->comm_set ? thread__comm_str(thread) : ""); 2530 } 2531 2532 browser->hists->thread_filter = thread__get(thread); 2533 perf_hpp__set_elide(HISTC_THREAD, false); 2534 pstack__push(browser->pstack, &browser->hists->thread_filter); 2535 } 2536 2537 hists__filter_by_thread(browser->hists); 2538 hist_browser__reset(browser); 2539 return 0; 2540} 2541 2542static int 2543add_thread_opt(struct hist_browser *browser, struct popup_action *act, 2544 char **optstr, struct thread *thread) 2545{ 2546 int ret; 2547 2548 if ((!hists__has(browser->hists, thread) && 2549 !hists__has(browser->hists, comm)) || thread == NULL) 2550 return 0; 2551 2552 if (hists__has(browser->hists, thread)) { 2553 ret = asprintf(optstr, "Zoom %s %s(%d) thread", 2554 browser->hists->thread_filter ? "out of" : "into", 2555 thread->comm_set ? thread__comm_str(thread) : "", 2556 thread->tid); 2557 } else { 2558 ret = asprintf(optstr, "Zoom %s %s thread", 2559 browser->hists->thread_filter ? "out of" : "into", 2560 thread->comm_set ? thread__comm_str(thread) : ""); 2561 } 2562 if (ret < 0) 2563 return 0; 2564 2565 act->thread = thread; 2566 act->fn = do_zoom_thread; 2567 return 1; 2568} 2569 2570static int hists_browser__zoom_map(struct hist_browser *browser, struct map *map) 2571{ 2572 if (!hists__has(browser->hists, dso) || map == NULL) 2573 return 0; 2574 2575 if (browser->hists->dso_filter) { 2576 pstack__remove(browser->pstack, &browser->hists->dso_filter); 2577 perf_hpp__set_elide(HISTC_DSO, false); 2578 browser->hists->dso_filter = NULL; 2579 ui_helpline__pop(); 2580 } else { 2581 ui_helpline__fpush("To zoom out press ESC or ENTER + \"Zoom out of %s DSO\"", 2582 __map__is_kernel(map) ? "the Kernel" : map->dso->short_name); 2583 browser->hists->dso_filter = map->dso; 2584 perf_hpp__set_elide(HISTC_DSO, true); 2585 pstack__push(browser->pstack, &browser->hists->dso_filter); 2586 } 2587 2588 hists__filter_by_dso(browser->hists); 2589 hist_browser__reset(browser); 2590 return 0; 2591} 2592 2593static int 2594do_zoom_dso(struct hist_browser *browser, struct popup_action *act) 2595{ 2596 return hists_browser__zoom_map(browser, act->ms.map); 2597} 2598 2599static int 2600add_dso_opt(struct hist_browser *browser, struct popup_action *act, 2601 char **optstr, struct map *map) 2602{ 2603 if (!hists__has(browser->hists, dso) || map == NULL) 2604 return 0; 2605 2606 if (asprintf(optstr, "Zoom %s %s DSO (use the 'k' hotkey to zoom directly into the kernel)", 2607 browser->hists->dso_filter ? "out of" : "into", 2608 __map__is_kernel(map) ? "the Kernel" : map->dso->short_name) < 0) 2609 return 0; 2610 2611 act->ms.map = map; 2612 act->fn = do_zoom_dso; 2613 return 1; 2614} 2615 2616static int do_toggle_callchain(struct hist_browser *browser, struct popup_action *act __maybe_unused) 2617{ 2618 hist_browser__toggle_fold(browser); 2619 return 0; 2620} 2621 2622static int add_callchain_toggle_opt(struct hist_browser *browser, struct popup_action *act, char **optstr) 2623{ 2624 char sym_name[512]; 2625 2626 if (!hist_browser__selection_has_children(browser)) 2627 return 0; 2628 2629 if (asprintf(optstr, "%s [%s] callchain (one level, same as '+' hotkey, use 'e'/'c' for the whole main level entry)", 2630 hist_browser__selection_unfolded(browser) ? "Collapse" : "Expand", 2631 hist_browser__selection_sym_name(browser, sym_name, sizeof(sym_name))) < 0) 2632 return 0; 2633 2634 act->fn = do_toggle_callchain; 2635 return 1; 2636} 2637 2638static int 2639do_browse_map(struct hist_browser *browser __maybe_unused, 2640 struct popup_action *act) 2641{ 2642 map__browse(act->ms.map); 2643 return 0; 2644} 2645 2646static int 2647add_map_opt(struct hist_browser *browser, 2648 struct popup_action *act, char **optstr, struct map *map) 2649{ 2650 if (!hists__has(browser->hists, dso) || map == NULL) 2651 return 0; 2652 2653 if (asprintf(optstr, "Browse map details") < 0) 2654 return 0; 2655 2656 act->ms.map = map; 2657 act->fn = do_browse_map; 2658 return 1; 2659} 2660 2661static int 2662do_run_script(struct hist_browser *browser __maybe_unused, 2663 struct popup_action *act) 2664{ 2665 char *script_opt; 2666 int len; 2667 int n = 0; 2668 2669 len = 100; 2670 if (act->thread) 2671 len += strlen(thread__comm_str(act->thread)); 2672 else if (act->ms.sym) 2673 len += strlen(act->ms.sym->name); 2674 script_opt = malloc(len); 2675 if (!script_opt) 2676 return -1; 2677 2678 script_opt[0] = 0; 2679 if (act->thread) { 2680 n = scnprintf(script_opt, len, " -c %s ", 2681 thread__comm_str(act->thread)); 2682 } else if (act->ms.sym) { 2683 n = scnprintf(script_opt, len, " -S %s ", 2684 act->ms.sym->name); 2685 } 2686 2687 if (act->time) { 2688 char start[32], end[32]; 2689 unsigned long starttime = act->time; 2690 unsigned long endtime = act->time + symbol_conf.time_quantum; 2691 2692 if (starttime == endtime) { /* Display 1ms as fallback */ 2693 starttime -= 1*NSEC_PER_MSEC; 2694 endtime += 1*NSEC_PER_MSEC; 2695 } 2696 timestamp__scnprintf_usec(starttime, start, sizeof start); 2697 timestamp__scnprintf_usec(endtime, end, sizeof end); 2698 n += snprintf(script_opt + n, len - n, " --time %s,%s", start, end); 2699 } 2700 2701 script_browse(script_opt, act->evsel); 2702 free(script_opt); 2703 return 0; 2704} 2705 2706static int 2707do_res_sample_script(struct hist_browser *browser __maybe_unused, 2708 struct popup_action *act) 2709{ 2710 struct hist_entry *he; 2711 2712 he = hist_browser__selected_entry(browser); 2713 res_sample_browse(he->res_samples, he->num_res, act->evsel, act->rstype); 2714 return 0; 2715} 2716 2717static int 2718add_script_opt_2(struct hist_browser *browser __maybe_unused, 2719 struct popup_action *act, char **optstr, 2720 struct thread *thread, struct symbol *sym, 2721 struct evsel *evsel, const char *tstr) 2722{ 2723 2724 if (thread) { 2725 if (asprintf(optstr, "Run scripts for samples of thread [%s]%s", 2726 thread__comm_str(thread), tstr) < 0) 2727 return 0; 2728 } else if (sym) { 2729 if (asprintf(optstr, "Run scripts for samples of symbol [%s]%s", 2730 sym->name, tstr) < 0) 2731 return 0; 2732 } else { 2733 if (asprintf(optstr, "Run scripts for all samples%s", tstr) < 0) 2734 return 0; 2735 } 2736 2737 act->thread = thread; 2738 act->ms.sym = sym; 2739 act->evsel = evsel; 2740 act->fn = do_run_script; 2741 return 1; 2742} 2743 2744static int 2745add_script_opt(struct hist_browser *browser, 2746 struct popup_action *act, char **optstr, 2747 struct thread *thread, struct symbol *sym, 2748 struct evsel *evsel) 2749{ 2750 int n, j; 2751 struct hist_entry *he; 2752 2753 n = add_script_opt_2(browser, act, optstr, thread, sym, evsel, ""); 2754 2755 he = hist_browser__selected_entry(browser); 2756 if (sort_order && strstr(sort_order, "time")) { 2757 char tstr[128]; 2758 2759 optstr++; 2760 act++; 2761 j = sprintf(tstr, " in "); 2762 j += timestamp__scnprintf_usec(he->time, tstr + j, 2763 sizeof tstr - j); 2764 j += sprintf(tstr + j, "-"); 2765 timestamp__scnprintf_usec(he->time + symbol_conf.time_quantum, 2766 tstr + j, sizeof tstr - j); 2767 n += add_script_opt_2(browser, act, optstr, thread, sym, 2768 evsel, tstr); 2769 act->time = he->time; 2770 } 2771 return n; 2772} 2773 2774static int 2775add_res_sample_opt(struct hist_browser *browser __maybe_unused, 2776 struct popup_action *act, char **optstr, 2777 struct res_sample *res_sample, 2778 struct evsel *evsel, 2779 enum rstype type) 2780{ 2781 if (!res_sample) 2782 return 0; 2783 2784 if (asprintf(optstr, "Show context for individual samples %s", 2785 type == A_ASM ? "with assembler" : 2786 type == A_SOURCE ? "with source" : "") < 0) 2787 return 0; 2788 2789 act->fn = do_res_sample_script; 2790 act->evsel = evsel; 2791 act->rstype = type; 2792 return 1; 2793} 2794 2795static int 2796do_switch_data(struct hist_browser *browser __maybe_unused, 2797 struct popup_action *act __maybe_unused) 2798{ 2799 if (switch_data_file()) { 2800 ui__warning("Won't switch the data files due to\n" 2801 "no valid data file get selected!\n"); 2802 return 0; 2803 } 2804 2805 return K_SWITCH_INPUT_DATA; 2806} 2807 2808static int 2809add_switch_opt(struct hist_browser *browser, 2810 struct popup_action *act, char **optstr) 2811{ 2812 if (!is_report_browser(browser->hbt)) 2813 return 0; 2814 2815 if (asprintf(optstr, "Switch to another data file in PWD") < 0) 2816 return 0; 2817 2818 act->fn = do_switch_data; 2819 return 1; 2820} 2821 2822static int 2823do_exit_browser(struct hist_browser *browser __maybe_unused, 2824 struct popup_action *act __maybe_unused) 2825{ 2826 return 0; 2827} 2828 2829static int 2830add_exit_opt(struct hist_browser *browser __maybe_unused, 2831 struct popup_action *act, char **optstr) 2832{ 2833 if (asprintf(optstr, "Exit") < 0) 2834 return 0; 2835 2836 act->fn = do_exit_browser; 2837 return 1; 2838} 2839 2840static int 2841do_zoom_socket(struct hist_browser *browser, struct popup_action *act) 2842{ 2843 if (!hists__has(browser->hists, socket) || act->socket < 0) 2844 return 0; 2845 2846 if (browser->hists->socket_filter > -1) { 2847 pstack__remove(browser->pstack, &browser->hists->socket_filter); 2848 browser->hists->socket_filter = -1; 2849 perf_hpp__set_elide(HISTC_SOCKET, false); 2850 } else { 2851 browser->hists->socket_filter = act->socket; 2852 perf_hpp__set_elide(HISTC_SOCKET, true); 2853 pstack__push(browser->pstack, &browser->hists->socket_filter); 2854 } 2855 2856 hists__filter_by_socket(browser->hists); 2857 hist_browser__reset(browser); 2858 return 0; 2859} 2860 2861static int 2862add_socket_opt(struct hist_browser *browser, struct popup_action *act, 2863 char **optstr, int socket_id) 2864{ 2865 if (!hists__has(browser->hists, socket) || socket_id < 0) 2866 return 0; 2867 2868 if (asprintf(optstr, "Zoom %s Processor Socket %d", 2869 (browser->hists->socket_filter > -1) ? "out of" : "into", 2870 socket_id) < 0) 2871 return 0; 2872 2873 act->socket = socket_id; 2874 act->fn = do_zoom_socket; 2875 return 1; 2876} 2877 2878static void hist_browser__update_nr_entries(struct hist_browser *hb) 2879{ 2880 u64 nr_entries = 0; 2881 struct rb_node *nd = rb_first_cached(&hb->hists->entries); 2882 2883 if (hb->min_pcnt == 0 && !symbol_conf.report_hierarchy) { 2884 hb->nr_non_filtered_entries = hb->hists->nr_non_filtered_entries; 2885 return; 2886 } 2887 2888 while ((nd = hists__filter_entries(nd, hb->min_pcnt)) != NULL) { 2889 nr_entries++; 2890 nd = rb_hierarchy_next(nd); 2891 } 2892 2893 hb->nr_non_filtered_entries = nr_entries; 2894 hb->nr_hierarchy_entries = nr_entries; 2895} 2896 2897static void hist_browser__update_percent_limit(struct hist_browser *hb, 2898 double percent) 2899{ 2900 struct hist_entry *he; 2901 struct rb_node *nd = rb_first_cached(&hb->hists->entries); 2902 u64 total = hists__total_period(hb->hists); 2903 u64 min_callchain_hits = total * (percent / 100); 2904 2905 hb->min_pcnt = callchain_param.min_percent = percent; 2906 2907 while ((nd = hists__filter_entries(nd, hb->min_pcnt)) != NULL) { 2908 he = rb_entry(nd, struct hist_entry, rb_node); 2909 2910 if (he->has_no_entry) { 2911 he->has_no_entry = false; 2912 he->nr_rows = 0; 2913 } 2914 2915 if (!he->leaf || !hist_entry__has_callchains(he) || !symbol_conf.use_callchain) 2916 goto next; 2917 2918 if (callchain_param.mode == CHAIN_GRAPH_REL) { 2919 total = he->stat.period; 2920 2921 if (symbol_conf.cumulate_callchain) 2922 total = he->stat_acc->period; 2923 2924 min_callchain_hits = total * (percent / 100); 2925 } 2926 2927 callchain_param.sort(&he->sorted_chain, he->callchain, 2928 min_callchain_hits, &callchain_param); 2929 2930next: 2931 nd = __rb_hierarchy_next(nd, HMD_FORCE_CHILD); 2932 2933 /* force to re-evaluate folding state of callchains */ 2934 he->init_have_children = false; 2935 hist_entry__set_folding(he, hb, false); 2936 } 2937} 2938 2939static int perf_evsel__hists_browse(struct evsel *evsel, int nr_events, 2940 const char *helpline, 2941 bool left_exits, 2942 struct hist_browser_timer *hbt, 2943 float min_pcnt, 2944 struct perf_env *env, 2945 bool warn_lost_event, 2946 struct annotation_options *annotation_opts) 2947{ 2948 struct hists *hists = evsel__hists(evsel); 2949 struct hist_browser *browser = perf_evsel_browser__new(evsel, hbt, env, annotation_opts); 2950 struct branch_info *bi = NULL; 2951#define MAX_OPTIONS 16 2952 char *options[MAX_OPTIONS]; 2953 struct popup_action actions[MAX_OPTIONS]; 2954 int nr_options = 0; 2955 int key = -1; 2956 char buf[128]; 2957 int delay_secs = hbt ? hbt->refresh : 0; 2958 2959#define HIST_BROWSER_HELP_COMMON \ 2960 "h/?/F1 Show this window\n" \ 2961 "UP/DOWN/PGUP\n" \ 2962 "PGDN/SPACE Navigate\n" \ 2963 "q/ESC/CTRL+C Exit browser or go back to previous screen\n\n" \ 2964 "For multiple event sessions:\n\n" \ 2965 "TAB/UNTAB Switch events\n\n" \ 2966 "For symbolic views (--sort has sym):\n\n" \ 2967 "ENTER Zoom into DSO/Threads & Annotate current symbol\n" \ 2968 "ESC Zoom out\n" \ 2969 "+ Expand/Collapse one callchain level\n" \ 2970 "a Annotate current symbol\n" \ 2971 "C Collapse all callchains\n" \ 2972 "d Zoom into current DSO\n" \ 2973 "e Expand/Collapse main entry callchains\n" \ 2974 "E Expand all callchains\n" \ 2975 "F Toggle percentage of filtered entries\n" \ 2976 "H Display column headers\n" \ 2977 "k Zoom into the kernel map\n" \ 2978 "L Change percent limit\n" \ 2979 "m Display context menu\n" \ 2980 "S Zoom into current Processor Socket\n" \ 2981 2982 /* help messages are sorted by lexical order of the hotkey */ 2983 static const char report_help[] = HIST_BROWSER_HELP_COMMON 2984 "i Show header information\n" 2985 "P Print histograms to perf.hist.N\n" 2986 "r Run available scripts\n" 2987 "s Switch to another data file in PWD\n" 2988 "t Zoom into current Thread\n" 2989 "V Verbose (DSO names in callchains, etc)\n" 2990 "/ Filter symbol by name\n" 2991 "0-9 Sort by event n in group"; 2992 static const char top_help[] = HIST_BROWSER_HELP_COMMON 2993 "P Print histograms to perf.hist.N\n" 2994 "t Zoom into current Thread\n" 2995 "V Verbose (DSO names in callchains, etc)\n" 2996 "z Toggle zeroing of samples\n" 2997 "f Enable/Disable events\n" 2998 "/ Filter symbol by name"; 2999 3000 if (browser == NULL) 3001 return -1; 3002 3003 /* reset abort key so that it can get Ctrl-C as a key */ 3004 SLang_reset_tty(); 3005 SLang_init_tty(0, 0, 0); 3006 3007 if (min_pcnt) 3008 browser->min_pcnt = min_pcnt; 3009 hist_browser__update_nr_entries(browser); 3010 3011 browser->pstack = pstack__new(3); 3012 if (browser->pstack == NULL) 3013 goto out; 3014 3015 ui_helpline__push(helpline); 3016 3017 memset(options, 0, sizeof(options)); 3018 memset(actions, 0, sizeof(actions)); 3019 3020 if (symbol_conf.col_width_list_str) 3021 perf_hpp__set_user_width(symbol_conf.col_width_list_str); 3022 3023 if (!is_report_browser(hbt)) 3024 browser->b.no_samples_msg = "Collecting samples..."; 3025 3026 while (1) { 3027 struct thread *thread = NULL; 3028 struct map *map = NULL; 3029 int choice; 3030 int socked_id = -1; 3031 3032 key = 0; // reset key 3033do_hotkey: // key came straight from options ui__popup_menu() 3034 choice = nr_options = 0; 3035 key = hist_browser__run(browser, helpline, warn_lost_event, key); 3036 3037 if (browser->he_selection != NULL) { 3038 thread = hist_browser__selected_thread(browser); 3039 map = browser->selection->map; 3040 socked_id = browser->he_selection->socket; 3041 } 3042 switch (key) { 3043 case K_TAB: 3044 case K_UNTAB: 3045 if (nr_events == 1) 3046 continue; 3047 /* 3048 * Exit the browser, let hists__browser_tree 3049 * go to the next or previous 3050 */ 3051 goto out_free_stack; 3052 case '0' ... '9': 3053 if (!symbol_conf.event_group || 3054 evsel->core.nr_members < 2) { 3055 snprintf(buf, sizeof(buf), 3056 "Sort by index only available with group events!"); 3057 helpline = buf; 3058 continue; 3059 } 3060 3061 if (key - '0' == symbol_conf.group_sort_idx) 3062 continue; 3063 3064 symbol_conf.group_sort_idx = key - '0'; 3065 3066 if (symbol_conf.group_sort_idx >= evsel->core.nr_members) { 3067 snprintf(buf, sizeof(buf), 3068 "Max event group index to sort is %d (index from 0 to %d)", 3069 evsel->core.nr_members - 1, 3070 evsel->core.nr_members - 1); 3071 helpline = buf; 3072 continue; 3073 } 3074 3075 key = K_RELOAD; 3076 goto out_free_stack; 3077 case 'a': 3078 if (!hists__has(hists, sym)) { 3079 ui_browser__warning(&browser->b, delay_secs * 2, 3080 "Annotation is only available for symbolic views, " 3081 "include \"sym*\" in --sort to use it."); 3082 continue; 3083 } 3084 3085 if (!browser->selection || 3086 !browser->selection->map || 3087 !browser->selection->map->dso || 3088 browser->selection->map->dso->annotate_warned) { 3089 continue; 3090 } 3091 3092 if (!browser->selection->sym) { 3093 if (!browser->he_selection) 3094 continue; 3095 3096 if (sort__mode == SORT_MODE__BRANCH) { 3097 bi = browser->he_selection->branch_info; 3098 if (!bi || !bi->to.ms.map) 3099 continue; 3100 3101 actions->ms.sym = symbol__new_unresolved(bi->to.al_addr, bi->to.ms.map); 3102 actions->ms.map = bi->to.ms.map; 3103 } else { 3104 actions->ms.sym = symbol__new_unresolved(browser->he_selection->ip, 3105 browser->selection->map); 3106 actions->ms.map = browser->selection->map; 3107 } 3108 3109 if (!actions->ms.sym) 3110 continue; 3111 } else { 3112 if (symbol__annotation(browser->selection->sym)->src == NULL) { 3113 ui_browser__warning(&browser->b, delay_secs * 2, 3114 "No samples for the \"%s\" symbol.\n\n" 3115 "Probably appeared just in a callchain", 3116 browser->selection->sym->name); 3117 continue; 3118 } 3119 3120 actions->ms.map = browser->selection->map; 3121 actions->ms.sym = browser->selection->sym; 3122 } 3123 3124 do_annotate(browser, actions); 3125 continue; 3126 case 'P': 3127 hist_browser__dump(browser); 3128 continue; 3129 case 'd': 3130 actions->ms.map = map; 3131 do_zoom_dso(browser, actions); 3132 continue; 3133 case 'k': 3134 if (browser->selection != NULL) 3135 hists_browser__zoom_map(browser, browser->selection->maps->machine->vmlinux_map); 3136 continue; 3137 case 'V': 3138 verbose = (verbose + 1) % 4; 3139 browser->show_dso = verbose > 0; 3140 ui_helpline__fpush("Verbosity level set to %d\n", 3141 verbose); 3142 continue; 3143 case 't': 3144 actions->thread = thread; 3145 do_zoom_thread(browser, actions); 3146 continue; 3147 case 'S': 3148 actions->socket = socked_id; 3149 do_zoom_socket(browser, actions); 3150 continue; 3151 case '/': 3152 if (ui_browser__input_window("Symbol to show", 3153 "Please enter the name of symbol you want to see.\n" 3154 "To remove the filter later, press / + ENTER.", 3155 buf, "ENTER: OK, ESC: Cancel", 3156 delay_secs * 2) == K_ENTER) { 3157 hists->symbol_filter_str = *buf ? buf : NULL; 3158 hists__filter_by_symbol(hists); 3159 hist_browser__reset(browser); 3160 } 3161 continue; 3162 case 'r': 3163 if (is_report_browser(hbt)) { 3164 actions->thread = NULL; 3165 actions->ms.sym = NULL; 3166 do_run_script(browser, actions); 3167 } 3168 continue; 3169 case 's': 3170 if (is_report_browser(hbt)) { 3171 key = do_switch_data(browser, actions); 3172 if (key == K_SWITCH_INPUT_DATA) 3173 goto out_free_stack; 3174 } 3175 continue; 3176 case 'i': 3177 /* env->arch is NULL for live-mode (i.e. perf top) */ 3178 if (env->arch) 3179 tui__header_window(env); 3180 continue; 3181 case 'F': 3182 symbol_conf.filter_relative ^= 1; 3183 continue; 3184 case 'z': 3185 if (!is_report_browser(hbt)) { 3186 struct perf_top *top = hbt->arg; 3187 3188 top->zero = !top->zero; 3189 } 3190 continue; 3191 case 'L': 3192 if (ui_browser__input_window("Percent Limit", 3193 "Please enter the value you want to hide entries under that percent.", 3194 buf, "ENTER: OK, ESC: Cancel", 3195 delay_secs * 2) == K_ENTER) { 3196 char *end; 3197 double new_percent = strtod(buf, &end); 3198 3199 if (new_percent < 0 || new_percent > 100) { 3200 ui_browser__warning(&browser->b, delay_secs * 2, 3201 "Invalid percent: %.2f", new_percent); 3202 continue; 3203 } 3204 3205 hist_browser__update_percent_limit(browser, new_percent); 3206 hist_browser__reset(browser); 3207 } 3208 continue; 3209 case K_F1: 3210 case 'h': 3211 case '?': 3212 ui_browser__help_window(&browser->b, 3213 is_report_browser(hbt) ? report_help : top_help); 3214 continue; 3215 case K_ENTER: 3216 case K_RIGHT: 3217 case 'm': 3218 /* menu */ 3219 break; 3220 case K_ESC: 3221 case K_LEFT: { 3222 const void *top; 3223 3224 if (pstack__empty(browser->pstack)) { 3225 /* 3226 * Go back to the perf_evsel_menu__run or other user 3227 */ 3228 if (left_exits) 3229 goto out_free_stack; 3230 3231 if (key == K_ESC && 3232 ui_browser__dialog_yesno(&browser->b, 3233 "Do you really want to exit?")) 3234 goto out_free_stack; 3235 3236 continue; 3237 } 3238 actions->ms.map = map; 3239 top = pstack__peek(browser->pstack); 3240 if (top == &browser->hists->dso_filter) { 3241 /* 3242 * No need to set actions->dso here since 3243 * it's just to remove the current filter. 3244 * Ditto for thread below. 3245 */ 3246 do_zoom_dso(browser, actions); 3247 } else if (top == &browser->hists->thread_filter) { 3248 do_zoom_thread(browser, actions); 3249 } else if (top == &browser->hists->socket_filter) { 3250 do_zoom_socket(browser, actions); 3251 } 3252 continue; 3253 } 3254 case 'q': 3255 case CTRL('c'): 3256 goto out_free_stack; 3257 case 'f': 3258 if (!is_report_browser(hbt)) { 3259 struct perf_top *top = hbt->arg; 3260 3261 perf_evlist__toggle_enable(top->evlist); 3262 /* 3263 * No need to refresh, resort/decay histogram 3264 * entries if we are not collecting samples: 3265 */ 3266 if (top->evlist->enabled) { 3267 helpline = "Press 'f' to disable the events or 'h' to see other hotkeys"; 3268 hbt->refresh = delay_secs; 3269 } else { 3270 helpline = "Press 'f' again to re-enable the events"; 3271 hbt->refresh = 0; 3272 } 3273 continue; 3274 } 3275 /* Fall thru */ 3276 default: 3277 helpline = "Press '?' for help on key bindings"; 3278 continue; 3279 } 3280 3281 if (!hists__has(hists, sym) || browser->selection == NULL) 3282 goto skip_annotation; 3283 3284 if (sort__mode == SORT_MODE__BRANCH) { 3285 3286 if (browser->he_selection) 3287 bi = browser->he_selection->branch_info; 3288 3289 if (bi == NULL) 3290 goto skip_annotation; 3291 3292 nr_options += add_annotate_opt(browser, 3293 &actions[nr_options], 3294 &options[nr_options], 3295 &bi->from.ms, 3296 bi->from.al_addr); 3297 if (bi->to.ms.sym != bi->from.ms.sym) 3298 nr_options += add_annotate_opt(browser, 3299 &actions[nr_options], 3300 &options[nr_options], 3301 &bi->to.ms, 3302 bi->to.al_addr); 3303 } else { 3304 nr_options += add_annotate_opt(browser, 3305 &actions[nr_options], 3306 &options[nr_options], 3307 browser->selection, 3308 browser->he_selection->ip); 3309 } 3310skip_annotation: 3311 nr_options += add_thread_opt(browser, &actions[nr_options], 3312 &options[nr_options], thread); 3313 nr_options += add_dso_opt(browser, &actions[nr_options], 3314 &options[nr_options], map); 3315 nr_options += add_callchain_toggle_opt(browser, &actions[nr_options], &options[nr_options]); 3316 nr_options += add_map_opt(browser, &actions[nr_options], 3317 &options[nr_options], 3318 browser->selection ? 3319 browser->selection->map : NULL); 3320 nr_options += add_socket_opt(browser, &actions[nr_options], 3321 &options[nr_options], 3322 socked_id); 3323 /* perf script support */ 3324 if (!is_report_browser(hbt)) 3325 goto skip_scripting; 3326 3327 if (browser->he_selection) { 3328 if (hists__has(hists, thread) && thread) { 3329 nr_options += add_script_opt(browser, 3330 &actions[nr_options], 3331 &options[nr_options], 3332 thread, NULL, evsel); 3333 } 3334 /* 3335 * Note that browser->selection != NULL 3336 * when browser->he_selection is not NULL, 3337 * so we don't need to check browser->selection 3338 * before fetching browser->selection->sym like what 3339 * we do before fetching browser->selection->map. 3340 * 3341 * See hist_browser__show_entry. 3342 */ 3343 if (hists__has(hists, sym) && browser->selection->sym) { 3344 nr_options += add_script_opt(browser, 3345 &actions[nr_options], 3346 &options[nr_options], 3347 NULL, browser->selection->sym, 3348 evsel); 3349 } 3350 } 3351 nr_options += add_script_opt(browser, &actions[nr_options], 3352 &options[nr_options], NULL, NULL, evsel); 3353 nr_options += add_res_sample_opt(browser, &actions[nr_options], 3354 &options[nr_options], 3355 hist_browser__selected_res_sample(browser), 3356 evsel, A_NORMAL); 3357 nr_options += add_res_sample_opt(browser, &actions[nr_options], 3358 &options[nr_options], 3359 hist_browser__selected_res_sample(browser), 3360 evsel, A_ASM); 3361 nr_options += add_res_sample_opt(browser, &actions[nr_options], 3362 &options[nr_options], 3363 hist_browser__selected_res_sample(browser), 3364 evsel, A_SOURCE); 3365 nr_options += add_switch_opt(browser, &actions[nr_options], 3366 &options[nr_options]); 3367skip_scripting: 3368 nr_options += add_exit_opt(browser, &actions[nr_options], 3369 &options[nr_options]); 3370 3371 do { 3372 struct popup_action *act; 3373 3374 choice = ui__popup_menu(nr_options, options, &key); 3375 if (choice == -1) 3376 break; 3377 3378 if (choice == nr_options) 3379 goto do_hotkey; 3380 3381 act = &actions[choice]; 3382 key = act->fn(browser, act); 3383 } while (key == 1); 3384 3385 if (key == K_SWITCH_INPUT_DATA) 3386 break; 3387 } 3388out_free_stack: 3389 pstack__delete(browser->pstack); 3390out: 3391 hist_browser__delete(browser); 3392 free_popup_options(options, MAX_OPTIONS); 3393 return key; 3394} 3395 3396struct evsel_menu { 3397 struct ui_browser b; 3398 struct evsel *selection; 3399 struct annotation_options *annotation_opts; 3400 bool lost_events, lost_events_warned; 3401 float min_pcnt; 3402 struct perf_env *env; 3403}; 3404 3405static void perf_evsel_menu__write(struct ui_browser *browser, 3406 void *entry, int row) 3407{ 3408 struct evsel_menu *menu = container_of(browser, 3409 struct evsel_menu, b); 3410 struct evsel *evsel = list_entry(entry, struct evsel, core.node); 3411 struct hists *hists = evsel__hists(evsel); 3412 bool current_entry = ui_browser__is_current_entry(browser, row); 3413 unsigned long nr_events = hists->stats.nr_events[PERF_RECORD_SAMPLE]; 3414 const char *ev_name = evsel__name(evsel); 3415 char bf[256], unit; 3416 const char *warn = " "; 3417 size_t printed; 3418 3419 ui_browser__set_color(browser, current_entry ? HE_COLORSET_SELECTED : 3420 HE_COLORSET_NORMAL); 3421 3422 if (evsel__is_group_event(evsel)) { 3423 struct evsel *pos; 3424 3425 ev_name = evsel__group_name(evsel); 3426 3427 for_each_group_member(pos, evsel) { 3428 struct hists *pos_hists = evsel__hists(pos); 3429 nr_events += pos_hists->stats.nr_events[PERF_RECORD_SAMPLE]; 3430 } 3431 } 3432 3433 nr_events = convert_unit(nr_events, &unit); 3434 printed = scnprintf(bf, sizeof(bf), "%lu%c%s%s", nr_events, 3435 unit, unit == ' ' ? "" : " ", ev_name); 3436 ui_browser__printf(browser, "%s", bf); 3437 3438 nr_events = hists->stats.nr_events[PERF_RECORD_LOST]; 3439 if (nr_events != 0) { 3440 menu->lost_events = true; 3441 if (!current_entry) 3442 ui_browser__set_color(browser, HE_COLORSET_TOP); 3443 nr_events = convert_unit(nr_events, &unit); 3444 printed += scnprintf(bf, sizeof(bf), ": %ld%c%schunks LOST!", 3445 nr_events, unit, unit == ' ' ? "" : " "); 3446 warn = bf; 3447 } 3448 3449 ui_browser__write_nstring(browser, warn, browser->width - printed); 3450 3451 if (current_entry) 3452 menu->selection = evsel; 3453} 3454 3455static int perf_evsel_menu__run(struct evsel_menu *menu, 3456 int nr_events, const char *help, 3457 struct hist_browser_timer *hbt, 3458 bool warn_lost_event) 3459{ 3460 struct evlist *evlist = menu->b.priv; 3461 struct evsel *pos; 3462 const char *title = "Available samples"; 3463 int delay_secs = hbt ? hbt->refresh : 0; 3464 int key; 3465 3466 if (ui_browser__show(&menu->b, title, 3467 "ESC: exit, ENTER|->: Browse histograms") < 0) 3468 return -1; 3469 3470 while (1) { 3471 key = ui_browser__run(&menu->b, delay_secs); 3472 3473 switch (key) { 3474 case K_TIMER: 3475 if (hbt) 3476 hbt->timer(hbt->arg); 3477 3478 if (!menu->lost_events_warned && 3479 menu->lost_events && 3480 warn_lost_event) { 3481 ui_browser__warn_lost_events(&menu->b); 3482 menu->lost_events_warned = true; 3483 } 3484 continue; 3485 case K_RIGHT: 3486 case K_ENTER: 3487 if (!menu->selection) 3488 continue; 3489 pos = menu->selection; 3490browse_hists: 3491 perf_evlist__set_selected(evlist, pos); 3492 /* 3493 * Give the calling tool a chance to populate the non 3494 * default evsel resorted hists tree. 3495 */ 3496 if (hbt) 3497 hbt->timer(hbt->arg); 3498 key = perf_evsel__hists_browse(pos, nr_events, help, 3499 true, hbt, 3500 menu->min_pcnt, 3501 menu->env, 3502 warn_lost_event, 3503 menu->annotation_opts); 3504 ui_browser__show_title(&menu->b, title); 3505 switch (key) { 3506 case K_TAB: 3507 if (pos->core.node.next == &evlist->core.entries) 3508 pos = evlist__first(evlist); 3509 else 3510 pos = evsel__next(pos); 3511 goto browse_hists; 3512 case K_UNTAB: 3513 if (pos->core.node.prev == &evlist->core.entries) 3514 pos = evlist__last(evlist); 3515 else 3516 pos = evsel__prev(pos); 3517 goto browse_hists; 3518 case K_SWITCH_INPUT_DATA: 3519 case K_RELOAD: 3520 case 'q': 3521 case CTRL('c'): 3522 goto out; 3523 case K_ESC: 3524 default: 3525 continue; 3526 } 3527 case K_LEFT: 3528 continue; 3529 case K_ESC: 3530 if (!ui_browser__dialog_yesno(&menu->b, 3531 "Do you really want to exit?")) 3532 continue; 3533 /* Fall thru */ 3534 case 'q': 3535 case CTRL('c'): 3536 goto out; 3537 default: 3538 continue; 3539 } 3540 } 3541 3542out: 3543 ui_browser__hide(&menu->b); 3544 return key; 3545} 3546 3547static bool filter_group_entries(struct ui_browser *browser __maybe_unused, 3548 void *entry) 3549{ 3550 struct evsel *evsel = list_entry(entry, struct evsel, core.node); 3551 3552 if (symbol_conf.event_group && !evsel__is_group_leader(evsel)) 3553 return true; 3554 3555 return false; 3556} 3557 3558static int __perf_evlist__tui_browse_hists(struct evlist *evlist, 3559 int nr_entries, const char *help, 3560 struct hist_browser_timer *hbt, 3561 float min_pcnt, 3562 struct perf_env *env, 3563 bool warn_lost_event, 3564 struct annotation_options *annotation_opts) 3565{ 3566 struct evsel *pos; 3567 struct evsel_menu menu = { 3568 .b = { 3569 .entries = &evlist->core.entries, 3570 .refresh = ui_browser__list_head_refresh, 3571 .seek = ui_browser__list_head_seek, 3572 .write = perf_evsel_menu__write, 3573 .filter = filter_group_entries, 3574 .nr_entries = nr_entries, 3575 .priv = evlist, 3576 }, 3577 .min_pcnt = min_pcnt, 3578 .env = env, 3579 .annotation_opts = annotation_opts, 3580 }; 3581 3582 ui_helpline__push("Press ESC to exit"); 3583 3584 evlist__for_each_entry(evlist, pos) { 3585 const char *ev_name = evsel__name(pos); 3586 size_t line_len = strlen(ev_name) + 7; 3587 3588 if (menu.b.width < line_len) 3589 menu.b.width = line_len; 3590 } 3591 3592 return perf_evsel_menu__run(&menu, nr_entries, help, 3593 hbt, warn_lost_event); 3594} 3595 3596static bool perf_evlist__single_entry(struct evlist *evlist) 3597{ 3598 int nr_entries = evlist->core.nr_entries; 3599 3600 if (nr_entries == 1) 3601 return true; 3602 3603 if (nr_entries == 2) { 3604 struct evsel *last = evlist__last(evlist); 3605 3606 if (evsel__is_dummy_event(last)) 3607 return true; 3608 } 3609 3610 return false; 3611} 3612 3613int perf_evlist__tui_browse_hists(struct evlist *evlist, const char *help, 3614 struct hist_browser_timer *hbt, 3615 float min_pcnt, 3616 struct perf_env *env, 3617 bool warn_lost_event, 3618 struct annotation_options *annotation_opts) 3619{ 3620 int nr_entries = evlist->core.nr_entries; 3621 3622 if (perf_evlist__single_entry(evlist)) { 3623single_entry: { 3624 struct evsel *first = evlist__first(evlist); 3625 3626 return perf_evsel__hists_browse(first, nr_entries, help, 3627 false, hbt, min_pcnt, 3628 env, warn_lost_event, 3629 annotation_opts); 3630 } 3631 } 3632 3633 if (symbol_conf.event_group) { 3634 struct evsel *pos; 3635 3636 nr_entries = 0; 3637 evlist__for_each_entry(evlist, pos) { 3638 if (evsel__is_group_leader(pos)) 3639 nr_entries++; 3640 } 3641 3642 if (nr_entries == 1) 3643 goto single_entry; 3644 } 3645 3646 return __perf_evlist__tui_browse_hists(evlist, nr_entries, help, 3647 hbt, min_pcnt, env, 3648 warn_lost_event, 3649 annotation_opts); 3650} 3651 3652static int block_hists_browser__title(struct hist_browser *browser, char *bf, 3653 size_t size) 3654{ 3655 struct hists *hists = evsel__hists(browser->block_evsel); 3656 const char *evname = evsel__name(browser->block_evsel); 3657 unsigned long nr_samples = hists->stats.nr_events[PERF_RECORD_SAMPLE]; 3658 int ret; 3659 3660 ret = scnprintf(bf, size, "# Samples: %lu", nr_samples); 3661 if (evname) 3662 scnprintf(bf + ret, size - ret, " of event '%s'", evname); 3663 3664 return 0; 3665} 3666 3667int block_hists_tui_browse(struct block_hist *bh, struct evsel *evsel, 3668 float min_percent, struct perf_env *env, 3669 struct annotation_options *annotation_opts) 3670{ 3671 struct hists *hists = &bh->block_hists; 3672 struct hist_browser *browser; 3673 int key = -1; 3674 struct popup_action action; 3675 static const char help[] = 3676 " q Quit \n"; 3677 3678 browser = hist_browser__new(hists); 3679 if (!browser) 3680 return -1; 3681 3682 browser->block_evsel = evsel; 3683 browser->title = block_hists_browser__title; 3684 browser->min_pcnt = min_percent; 3685 browser->env = env; 3686 browser->annotation_opts = annotation_opts; 3687 3688 /* reset abort key so that it can get Ctrl-C as a key */ 3689 SLang_reset_tty(); 3690 SLang_init_tty(0, 0, 0); 3691 3692 memset(&action, 0, sizeof(action)); 3693 3694 while (1) { 3695 key = hist_browser__run(browser, "? - help", true, 0); 3696 3697 switch (key) { 3698 case 'q': 3699 goto out; 3700 case '?': 3701 ui_browser__help_window(&browser->b, help); 3702 break; 3703 case 'a': 3704 case K_ENTER: 3705 if (!browser->selection || 3706 !browser->selection->sym) { 3707 continue; 3708 } 3709 3710 action.ms.map = browser->selection->map; 3711 action.ms.sym = browser->selection->sym; 3712 do_annotate(browser, &action); 3713 continue; 3714 default: 3715 break; 3716 } 3717 } 3718 3719out: 3720 hist_browser__delete(browser); 3721 return 0; 3722} 3723