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