1 #include "../evlist.h" 2 #include "../cache.h" 3 #include "../evsel.h" 4 #include "../sort.h" 5 #include "../hist.h" 6 #include "../helpline.h" 7 #include "../string2.h" 8 #include "gtk.h" 9 #include <signal.h> 10 11 #define MAX_COLUMNS 32 12 13 static int __percent_color_snprintf(struct perf_hpp *hpp, const char *fmt, ...) 14 { 15 int ret = 0; 16 int len; 17 va_list args; 18 double percent; 19 const char *markup; 20 char *buf = hpp->buf; 21 size_t size = hpp->size; 22 23 va_start(args, fmt); 24 len = va_arg(args, int); 25 percent = va_arg(args, double); 26 va_end(args); 27 28 markup = perf_gtk__get_percent_color(percent); 29 if (markup) 30 ret += scnprintf(buf, size, markup); 31 32 ret += scnprintf(buf + ret, size - ret, fmt, len, percent); 33 34 if (markup) 35 ret += scnprintf(buf + ret, size - ret, "</span>"); 36 37 return ret; 38 } 39 40 #define __HPP_COLOR_PERCENT_FN(_type, _field) \ 41 static u64 he_get_##_field(struct hist_entry *he) \ 42 { \ 43 return he->stat._field; \ 44 } \ 45 \ 46 static int perf_gtk__hpp_color_##_type(struct perf_hpp_fmt *fmt, \ 47 struct perf_hpp *hpp, \ 48 struct hist_entry *he) \ 49 { \ 50 return hpp__fmt(fmt, hpp, he, he_get_##_field, " %*.2f%%", \ 51 __percent_color_snprintf, true); \ 52 } 53 54 #define __HPP_COLOR_ACC_PERCENT_FN(_type, _field) \ 55 static u64 he_get_acc_##_field(struct hist_entry *he) \ 56 { \ 57 return he->stat_acc->_field; \ 58 } \ 59 \ 60 static int perf_gtk__hpp_color_##_type(struct perf_hpp_fmt *fmt, \ 61 struct perf_hpp *hpp, \ 62 struct hist_entry *he) \ 63 { \ 64 return hpp__fmt_acc(fmt, hpp, he, he_get_acc_##_field, " %*.2f%%", \ 65 __percent_color_snprintf, true); \ 66 } 67 68 __HPP_COLOR_PERCENT_FN(overhead, period) 69 __HPP_COLOR_PERCENT_FN(overhead_sys, period_sys) 70 __HPP_COLOR_PERCENT_FN(overhead_us, period_us) 71 __HPP_COLOR_PERCENT_FN(overhead_guest_sys, period_guest_sys) 72 __HPP_COLOR_PERCENT_FN(overhead_guest_us, period_guest_us) 73 __HPP_COLOR_ACC_PERCENT_FN(overhead_acc, period) 74 75 #undef __HPP_COLOR_PERCENT_FN 76 77 78 void perf_gtk__init_hpp(void) 79 { 80 perf_hpp__format[PERF_HPP__OVERHEAD].color = 81 perf_gtk__hpp_color_overhead; 82 perf_hpp__format[PERF_HPP__OVERHEAD_SYS].color = 83 perf_gtk__hpp_color_overhead_sys; 84 perf_hpp__format[PERF_HPP__OVERHEAD_US].color = 85 perf_gtk__hpp_color_overhead_us; 86 perf_hpp__format[PERF_HPP__OVERHEAD_GUEST_SYS].color = 87 perf_gtk__hpp_color_overhead_guest_sys; 88 perf_hpp__format[PERF_HPP__OVERHEAD_GUEST_US].color = 89 perf_gtk__hpp_color_overhead_guest_us; 90 perf_hpp__format[PERF_HPP__OVERHEAD_ACC].color = 91 perf_gtk__hpp_color_overhead_acc; 92 } 93 94 static void perf_gtk__add_callchain_flat(struct rb_root *root, GtkTreeStore *store, 95 GtkTreeIter *parent, int col, u64 total) 96 { 97 struct rb_node *nd; 98 bool has_single_node = (rb_first(root) == rb_last(root)); 99 100 for (nd = rb_first(root); nd; nd = rb_next(nd)) { 101 struct callchain_node *node; 102 struct callchain_list *chain; 103 GtkTreeIter iter, new_parent; 104 bool need_new_parent; 105 106 node = rb_entry(nd, struct callchain_node, rb_node); 107 108 new_parent = *parent; 109 need_new_parent = !has_single_node; 110 111 callchain_node__make_parent_list(node); 112 113 list_for_each_entry(chain, &node->parent_val, list) { 114 char buf[128]; 115 116 gtk_tree_store_append(store, &iter, &new_parent); 117 118 callchain_node__scnprintf_value(node, buf, sizeof(buf), total); 119 gtk_tree_store_set(store, &iter, 0, buf, -1); 120 121 callchain_list__sym_name(chain, buf, sizeof(buf), false); 122 gtk_tree_store_set(store, &iter, col, buf, -1); 123 124 if (need_new_parent) { 125 /* 126 * Only show the top-most symbol in a callchain 127 * if it's not the only callchain. 128 */ 129 new_parent = iter; 130 need_new_parent = false; 131 } 132 } 133 134 list_for_each_entry(chain, &node->val, list) { 135 char buf[128]; 136 137 gtk_tree_store_append(store, &iter, &new_parent); 138 139 callchain_node__scnprintf_value(node, buf, sizeof(buf), total); 140 gtk_tree_store_set(store, &iter, 0, buf, -1); 141 142 callchain_list__sym_name(chain, buf, sizeof(buf), false); 143 gtk_tree_store_set(store, &iter, col, buf, -1); 144 145 if (need_new_parent) { 146 /* 147 * Only show the top-most symbol in a callchain 148 * if it's not the only callchain. 149 */ 150 new_parent = iter; 151 need_new_parent = false; 152 } 153 } 154 } 155 } 156 157 static void perf_gtk__add_callchain_folded(struct rb_root *root, GtkTreeStore *store, 158 GtkTreeIter *parent, int col, u64 total) 159 { 160 struct rb_node *nd; 161 162 for (nd = rb_first(root); nd; nd = rb_next(nd)) { 163 struct callchain_node *node; 164 struct callchain_list *chain; 165 GtkTreeIter iter; 166 char buf[64]; 167 char *str, *str_alloc = NULL; 168 bool first = true; 169 170 node = rb_entry(nd, struct callchain_node, rb_node); 171 172 callchain_node__make_parent_list(node); 173 174 list_for_each_entry(chain, &node->parent_val, list) { 175 char name[1024]; 176 177 callchain_list__sym_name(chain, name, sizeof(name), false); 178 179 if (asprintf(&str, "%s%s%s", 180 first ? "" : str_alloc, 181 first ? "" : symbol_conf.field_sep ?: "; ", 182 name) < 0) 183 return; 184 185 first = false; 186 free(str_alloc); 187 str_alloc = str; 188 } 189 190 list_for_each_entry(chain, &node->val, list) { 191 char name[1024]; 192 193 callchain_list__sym_name(chain, name, sizeof(name), false); 194 195 if (asprintf(&str, "%s%s%s", 196 first ? "" : str_alloc, 197 first ? "" : symbol_conf.field_sep ?: "; ", 198 name) < 0) 199 return; 200 201 first = false; 202 free(str_alloc); 203 str_alloc = str; 204 } 205 206 gtk_tree_store_append(store, &iter, parent); 207 208 callchain_node__scnprintf_value(node, buf, sizeof(buf), total); 209 gtk_tree_store_set(store, &iter, 0, buf, -1); 210 211 gtk_tree_store_set(store, &iter, col, str, -1); 212 213 free(str_alloc); 214 } 215 } 216 217 static void perf_gtk__add_callchain_graph(struct rb_root *root, GtkTreeStore *store, 218 GtkTreeIter *parent, int col, u64 total) 219 { 220 struct rb_node *nd; 221 bool has_single_node = (rb_first(root) == rb_last(root)); 222 223 for (nd = rb_first(root); nd; nd = rb_next(nd)) { 224 struct callchain_node *node; 225 struct callchain_list *chain; 226 GtkTreeIter iter, new_parent; 227 bool need_new_parent; 228 u64 child_total; 229 230 node = rb_entry(nd, struct callchain_node, rb_node); 231 232 new_parent = *parent; 233 need_new_parent = !has_single_node && (node->val_nr > 1); 234 235 list_for_each_entry(chain, &node->val, list) { 236 char buf[128]; 237 238 gtk_tree_store_append(store, &iter, &new_parent); 239 240 callchain_node__scnprintf_value(node, buf, sizeof(buf), total); 241 gtk_tree_store_set(store, &iter, 0, buf, -1); 242 243 callchain_list__sym_name(chain, buf, sizeof(buf), false); 244 gtk_tree_store_set(store, &iter, col, buf, -1); 245 246 if (need_new_parent) { 247 /* 248 * Only show the top-most symbol in a callchain 249 * if it's not the only callchain. 250 */ 251 new_parent = iter; 252 need_new_parent = false; 253 } 254 } 255 256 if (callchain_param.mode == CHAIN_GRAPH_REL) 257 child_total = node->children_hit; 258 else 259 child_total = total; 260 261 /* Now 'iter' contains info of the last callchain_list */ 262 perf_gtk__add_callchain_graph(&node->rb_root, store, &iter, col, 263 child_total); 264 } 265 } 266 267 static void perf_gtk__add_callchain(struct rb_root *root, GtkTreeStore *store, 268 GtkTreeIter *parent, int col, u64 total) 269 { 270 if (callchain_param.mode == CHAIN_FLAT) 271 perf_gtk__add_callchain_flat(root, store, parent, col, total); 272 else if (callchain_param.mode == CHAIN_FOLDED) 273 perf_gtk__add_callchain_folded(root, store, parent, col, total); 274 else 275 perf_gtk__add_callchain_graph(root, store, parent, col, total); 276 } 277 278 static void on_row_activated(GtkTreeView *view, GtkTreePath *path, 279 GtkTreeViewColumn *col __maybe_unused, 280 gpointer user_data __maybe_unused) 281 { 282 bool expanded = gtk_tree_view_row_expanded(view, path); 283 284 if (expanded) 285 gtk_tree_view_collapse_row(view, path); 286 else 287 gtk_tree_view_expand_row(view, path, FALSE); 288 } 289 290 static void perf_gtk__show_hists(GtkWidget *window, struct hists *hists, 291 float min_pcnt) 292 { 293 struct perf_hpp_fmt *fmt; 294 GType col_types[MAX_COLUMNS]; 295 GtkCellRenderer *renderer; 296 GtkTreeStore *store; 297 struct rb_node *nd; 298 GtkWidget *view; 299 int col_idx; 300 int sym_col = -1; 301 int nr_cols; 302 char s[512]; 303 304 struct perf_hpp hpp = { 305 .buf = s, 306 .size = sizeof(s), 307 }; 308 309 nr_cols = 0; 310 311 hists__for_each_format(hists, fmt) 312 col_types[nr_cols++] = G_TYPE_STRING; 313 314 store = gtk_tree_store_newv(nr_cols, col_types); 315 316 view = gtk_tree_view_new(); 317 318 renderer = gtk_cell_renderer_text_new(); 319 320 col_idx = 0; 321 322 hists__for_each_format(hists, fmt) { 323 if (perf_hpp__should_skip(fmt, hists)) 324 continue; 325 326 /* 327 * XXX no way to determine where symcol column is.. 328 * Just use last column for now. 329 */ 330 if (perf_hpp__is_sort_entry(fmt)) 331 sym_col = col_idx; 332 333 gtk_tree_view_insert_column_with_attributes(GTK_TREE_VIEW(view), 334 -1, fmt->name, 335 renderer, "markup", 336 col_idx++, NULL); 337 } 338 339 for (col_idx = 0; col_idx < nr_cols; col_idx++) { 340 GtkTreeViewColumn *column; 341 342 column = gtk_tree_view_get_column(GTK_TREE_VIEW(view), col_idx); 343 gtk_tree_view_column_set_resizable(column, TRUE); 344 345 if (col_idx == sym_col) { 346 gtk_tree_view_set_expander_column(GTK_TREE_VIEW(view), 347 column); 348 } 349 } 350 351 gtk_tree_view_set_model(GTK_TREE_VIEW(view), GTK_TREE_MODEL(store)); 352 353 g_object_unref(GTK_TREE_MODEL(store)); 354 355 for (nd = rb_first(&hists->entries); nd; nd = rb_next(nd)) { 356 struct hist_entry *h = rb_entry(nd, struct hist_entry, rb_node); 357 GtkTreeIter iter; 358 u64 total = hists__total_period(h->hists); 359 float percent; 360 361 if (h->filtered) 362 continue; 363 364 percent = hist_entry__get_percent_limit(h); 365 if (percent < min_pcnt) 366 continue; 367 368 gtk_tree_store_append(store, &iter, NULL); 369 370 col_idx = 0; 371 372 hists__for_each_format(hists, fmt) { 373 if (perf_hpp__should_skip(fmt, h->hists)) 374 continue; 375 376 if (fmt->color) 377 fmt->color(fmt, &hpp, h); 378 else 379 fmt->entry(fmt, &hpp, h); 380 381 gtk_tree_store_set(store, &iter, col_idx++, s, -1); 382 } 383 384 if (symbol_conf.use_callchain && hists__has(hists, sym)) { 385 if (callchain_param.mode == CHAIN_GRAPH_REL) 386 total = symbol_conf.cumulate_callchain ? 387 h->stat_acc->period : h->stat.period; 388 389 perf_gtk__add_callchain(&h->sorted_chain, store, &iter, 390 sym_col, total); 391 } 392 } 393 394 gtk_tree_view_set_rules_hint(GTK_TREE_VIEW(view), TRUE); 395 396 g_signal_connect(view, "row-activated", 397 G_CALLBACK(on_row_activated), NULL); 398 gtk_container_add(GTK_CONTAINER(window), view); 399 } 400 401 static void perf_gtk__add_hierarchy_entries(struct hists *hists, 402 struct rb_root *root, 403 GtkTreeStore *store, 404 GtkTreeIter *parent, 405 struct perf_hpp *hpp, 406 float min_pcnt) 407 { 408 int col_idx = 0; 409 struct rb_node *node; 410 struct hist_entry *he; 411 struct perf_hpp_fmt *fmt; 412 struct perf_hpp_list_node *fmt_node; 413 u64 total = hists__total_period(hists); 414 int size; 415 416 for (node = rb_first(root); node; node = rb_next(node)) { 417 GtkTreeIter iter; 418 float percent; 419 char *bf; 420 421 he = rb_entry(node, struct hist_entry, rb_node); 422 if (he->filtered) 423 continue; 424 425 percent = hist_entry__get_percent_limit(he); 426 if (percent < min_pcnt) 427 continue; 428 429 gtk_tree_store_append(store, &iter, parent); 430 431 col_idx = 0; 432 433 /* the first hpp_list_node is for overhead columns */ 434 fmt_node = list_first_entry(&hists->hpp_formats, 435 struct perf_hpp_list_node, list); 436 perf_hpp_list__for_each_format(&fmt_node->hpp, fmt) { 437 if (fmt->color) 438 fmt->color(fmt, hpp, he); 439 else 440 fmt->entry(fmt, hpp, he); 441 442 gtk_tree_store_set(store, &iter, col_idx++, hpp->buf, -1); 443 } 444 445 bf = hpp->buf; 446 size = hpp->size; 447 perf_hpp_list__for_each_format(he->hpp_list, fmt) { 448 int ret; 449 450 if (fmt->color) 451 ret = fmt->color(fmt, hpp, he); 452 else 453 ret = fmt->entry(fmt, hpp, he); 454 455 snprintf(hpp->buf + ret, hpp->size - ret, " "); 456 advance_hpp(hpp, ret + 2); 457 } 458 459 gtk_tree_store_set(store, &iter, col_idx, ltrim(rtrim(bf)), -1); 460 461 if (!he->leaf) { 462 hpp->buf = bf; 463 hpp->size = size; 464 465 perf_gtk__add_hierarchy_entries(hists, &he->hroot_out, 466 store, &iter, hpp, 467 min_pcnt); 468 469 if (!hist_entry__has_hierarchy_children(he, min_pcnt)) { 470 char buf[32]; 471 GtkTreeIter child; 472 473 snprintf(buf, sizeof(buf), "no entry >= %.2f%%", 474 min_pcnt); 475 476 gtk_tree_store_append(store, &child, &iter); 477 gtk_tree_store_set(store, &child, col_idx, buf, -1); 478 } 479 } 480 481 if (symbol_conf.use_callchain && he->leaf) { 482 if (callchain_param.mode == CHAIN_GRAPH_REL) 483 total = symbol_conf.cumulate_callchain ? 484 he->stat_acc->period : he->stat.period; 485 486 perf_gtk__add_callchain(&he->sorted_chain, store, &iter, 487 col_idx, total); 488 } 489 } 490 491 } 492 493 static void perf_gtk__show_hierarchy(GtkWidget *window, struct hists *hists, 494 float min_pcnt) 495 { 496 struct perf_hpp_fmt *fmt; 497 struct perf_hpp_list_node *fmt_node; 498 GType col_types[MAX_COLUMNS]; 499 GtkCellRenderer *renderer; 500 GtkTreeStore *store; 501 GtkWidget *view; 502 int col_idx; 503 int nr_cols = 0; 504 char s[512]; 505 char buf[512]; 506 bool first_node, first_col; 507 struct perf_hpp hpp = { 508 .buf = s, 509 .size = sizeof(s), 510 }; 511 512 hists__for_each_format(hists, fmt) { 513 if (perf_hpp__is_sort_entry(fmt) || 514 perf_hpp__is_dynamic_entry(fmt)) 515 break; 516 517 col_types[nr_cols++] = G_TYPE_STRING; 518 } 519 col_types[nr_cols++] = G_TYPE_STRING; 520 521 store = gtk_tree_store_newv(nr_cols, col_types); 522 view = gtk_tree_view_new(); 523 renderer = gtk_cell_renderer_text_new(); 524 525 col_idx = 0; 526 527 /* the first hpp_list_node is for overhead columns */ 528 fmt_node = list_first_entry(&hists->hpp_formats, 529 struct perf_hpp_list_node, list); 530 perf_hpp_list__for_each_format(&fmt_node->hpp, fmt) { 531 gtk_tree_view_insert_column_with_attributes(GTK_TREE_VIEW(view), 532 -1, fmt->name, 533 renderer, "markup", 534 col_idx++, NULL); 535 } 536 537 /* construct merged column header since sort keys share single column */ 538 buf[0] = '\0'; 539 first_node = true; 540 list_for_each_entry_continue(fmt_node, &hists->hpp_formats, list) { 541 if (!first_node) 542 strcat(buf, " / "); 543 first_node = false; 544 545 first_col = true; 546 perf_hpp_list__for_each_format(&fmt_node->hpp ,fmt) { 547 if (perf_hpp__should_skip(fmt, hists)) 548 continue; 549 550 if (!first_col) 551 strcat(buf, "+"); 552 first_col = false; 553 554 fmt->header(fmt, &hpp, hists, 0, NULL); 555 strcat(buf, ltrim(rtrim(hpp.buf))); 556 } 557 } 558 559 gtk_tree_view_insert_column_with_attributes(GTK_TREE_VIEW(view), 560 -1, buf, 561 renderer, "markup", 562 col_idx++, NULL); 563 564 for (col_idx = 0; col_idx < nr_cols; col_idx++) { 565 GtkTreeViewColumn *column; 566 567 column = gtk_tree_view_get_column(GTK_TREE_VIEW(view), col_idx); 568 gtk_tree_view_column_set_resizable(column, TRUE); 569 570 if (col_idx == 0) { 571 gtk_tree_view_set_expander_column(GTK_TREE_VIEW(view), 572 column); 573 } 574 } 575 576 gtk_tree_view_set_model(GTK_TREE_VIEW(view), GTK_TREE_MODEL(store)); 577 g_object_unref(GTK_TREE_MODEL(store)); 578 579 perf_gtk__add_hierarchy_entries(hists, &hists->entries, store, 580 NULL, &hpp, min_pcnt); 581 582 gtk_tree_view_set_rules_hint(GTK_TREE_VIEW(view), TRUE); 583 584 g_signal_connect(view, "row-activated", 585 G_CALLBACK(on_row_activated), NULL); 586 gtk_container_add(GTK_CONTAINER(window), view); 587 } 588 589 int perf_evlist__gtk_browse_hists(struct perf_evlist *evlist, 590 const char *help, 591 struct hist_browser_timer *hbt __maybe_unused, 592 float min_pcnt) 593 { 594 struct perf_evsel *pos; 595 GtkWidget *vbox; 596 GtkWidget *notebook; 597 GtkWidget *info_bar; 598 GtkWidget *statbar; 599 GtkWidget *window; 600 601 signal(SIGSEGV, perf_gtk__signal); 602 signal(SIGFPE, perf_gtk__signal); 603 signal(SIGINT, perf_gtk__signal); 604 signal(SIGQUIT, perf_gtk__signal); 605 signal(SIGTERM, perf_gtk__signal); 606 607 window = gtk_window_new(GTK_WINDOW_TOPLEVEL); 608 609 gtk_window_set_title(GTK_WINDOW(window), "perf report"); 610 611 g_signal_connect(window, "delete_event", gtk_main_quit, NULL); 612 613 pgctx = perf_gtk__activate_context(window); 614 if (!pgctx) 615 return -1; 616 617 vbox = gtk_vbox_new(FALSE, 0); 618 619 notebook = gtk_notebook_new(); 620 621 gtk_box_pack_start(GTK_BOX(vbox), notebook, TRUE, TRUE, 0); 622 623 info_bar = perf_gtk__setup_info_bar(); 624 if (info_bar) 625 gtk_box_pack_start(GTK_BOX(vbox), info_bar, FALSE, FALSE, 0); 626 627 statbar = perf_gtk__setup_statusbar(); 628 gtk_box_pack_start(GTK_BOX(vbox), statbar, FALSE, FALSE, 0); 629 630 gtk_container_add(GTK_CONTAINER(window), vbox); 631 632 evlist__for_each_entry(evlist, pos) { 633 struct hists *hists = evsel__hists(pos); 634 const char *evname = perf_evsel__name(pos); 635 GtkWidget *scrolled_window; 636 GtkWidget *tab_label; 637 char buf[512]; 638 size_t size = sizeof(buf); 639 640 if (symbol_conf.event_group) { 641 if (!perf_evsel__is_group_leader(pos)) 642 continue; 643 644 if (pos->nr_members > 1) { 645 perf_evsel__group_desc(pos, buf, size); 646 evname = buf; 647 } 648 } 649 650 scrolled_window = gtk_scrolled_window_new(NULL, NULL); 651 652 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolled_window), 653 GTK_POLICY_AUTOMATIC, 654 GTK_POLICY_AUTOMATIC); 655 656 if (symbol_conf.report_hierarchy) 657 perf_gtk__show_hierarchy(scrolled_window, hists, min_pcnt); 658 else 659 perf_gtk__show_hists(scrolled_window, hists, min_pcnt); 660 661 tab_label = gtk_label_new(evname); 662 663 gtk_notebook_append_page(GTK_NOTEBOOK(notebook), scrolled_window, tab_label); 664 } 665 666 gtk_widget_show_all(window); 667 668 perf_gtk__resize_window(window); 669 670 gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER); 671 672 ui_helpline__push(help); 673 674 gtk_main(); 675 676 perf_gtk__deactivate_context(&pgctx); 677 678 return 0; 679 } 680