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