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