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 (symbol_conf.use_callchain && hists__has(hists, sym)) { 386 if (callchain_param.mode == CHAIN_GRAPH_REL) 387 total = symbol_conf.cumulate_callchain ? 388 h->stat_acc->period : h->stat.period; 389 390 perf_gtk__add_callchain(&h->sorted_chain, store, &iter, 391 sym_col, total); 392 } 393 } 394 395 gtk_tree_view_set_rules_hint(GTK_TREE_VIEW(view), TRUE); 396 397 g_signal_connect(view, "row-activated", 398 G_CALLBACK(on_row_activated), NULL); 399 gtk_container_add(GTK_CONTAINER(window), view); 400 } 401 402 static void perf_gtk__add_hierarchy_entries(struct hists *hists, 403 struct rb_root *root, 404 GtkTreeStore *store, 405 GtkTreeIter *parent, 406 struct perf_hpp *hpp, 407 float min_pcnt) 408 { 409 int col_idx = 0; 410 struct rb_node *node; 411 struct hist_entry *he; 412 struct perf_hpp_fmt *fmt; 413 struct perf_hpp_list_node *fmt_node; 414 u64 total = hists__total_period(hists); 415 int size; 416 417 for (node = rb_first(root); node; node = rb_next(node)) { 418 GtkTreeIter iter; 419 float percent; 420 char *bf; 421 422 he = rb_entry(node, struct hist_entry, rb_node); 423 if (he->filtered) 424 continue; 425 426 percent = hist_entry__get_percent_limit(he); 427 if (percent < min_pcnt) 428 continue; 429 430 gtk_tree_store_append(store, &iter, parent); 431 432 col_idx = 0; 433 434 /* the first hpp_list_node is for overhead columns */ 435 fmt_node = list_first_entry(&hists->hpp_formats, 436 struct perf_hpp_list_node, list); 437 perf_hpp_list__for_each_format(&fmt_node->hpp, fmt) { 438 if (fmt->color) 439 fmt->color(fmt, hpp, he); 440 else 441 fmt->entry(fmt, hpp, he); 442 443 gtk_tree_store_set(store, &iter, col_idx++, hpp->buf, -1); 444 } 445 446 bf = hpp->buf; 447 size = hpp->size; 448 perf_hpp_list__for_each_format(he->hpp_list, fmt) { 449 int ret; 450 451 if (fmt->color) 452 ret = fmt->color(fmt, hpp, he); 453 else 454 ret = fmt->entry(fmt, hpp, he); 455 456 snprintf(hpp->buf + ret, hpp->size - ret, " "); 457 advance_hpp(hpp, ret + 2); 458 } 459 460 gtk_tree_store_set(store, &iter, col_idx, ltrim(rtrim(bf)), -1); 461 462 if (!he->leaf) { 463 hpp->buf = bf; 464 hpp->size = size; 465 466 perf_gtk__add_hierarchy_entries(hists, &he->hroot_out, 467 store, &iter, hpp, 468 min_pcnt); 469 470 if (!hist_entry__has_hierarchy_children(he, min_pcnt)) { 471 char buf[32]; 472 GtkTreeIter child; 473 474 snprintf(buf, sizeof(buf), "no entry >= %.2f%%", 475 min_pcnt); 476 477 gtk_tree_store_append(store, &child, &iter); 478 gtk_tree_store_set(store, &child, col_idx, buf, -1); 479 } 480 } 481 482 if (symbol_conf.use_callchain && he->leaf) { 483 if (callchain_param.mode == CHAIN_GRAPH_REL) 484 total = symbol_conf.cumulate_callchain ? 485 he->stat_acc->period : he->stat.period; 486 487 perf_gtk__add_callchain(&he->sorted_chain, store, &iter, 488 col_idx, total); 489 } 490 } 491 492 } 493 494 static void perf_gtk__show_hierarchy(GtkWidget *window, struct hists *hists, 495 float min_pcnt) 496 { 497 struct perf_hpp_fmt *fmt; 498 struct perf_hpp_list_node *fmt_node; 499 GType col_types[MAX_COLUMNS]; 500 GtkCellRenderer *renderer; 501 GtkTreeStore *store; 502 GtkWidget *view; 503 int col_idx; 504 int nr_cols = 0; 505 char s[512]; 506 char buf[512]; 507 bool first_node, first_col; 508 struct perf_hpp hpp = { 509 .buf = s, 510 .size = sizeof(s), 511 }; 512 513 hists__for_each_format(hists, fmt) { 514 if (perf_hpp__is_sort_entry(fmt) || 515 perf_hpp__is_dynamic_entry(fmt)) 516 break; 517 518 col_types[nr_cols++] = G_TYPE_STRING; 519 } 520 col_types[nr_cols++] = G_TYPE_STRING; 521 522 store = gtk_tree_store_newv(nr_cols, col_types); 523 view = gtk_tree_view_new(); 524 renderer = gtk_cell_renderer_text_new(); 525 526 col_idx = 0; 527 528 /* the first hpp_list_node is for overhead columns */ 529 fmt_node = list_first_entry(&hists->hpp_formats, 530 struct perf_hpp_list_node, list); 531 perf_hpp_list__for_each_format(&fmt_node->hpp, fmt) { 532 gtk_tree_view_insert_column_with_attributes(GTK_TREE_VIEW(view), 533 -1, fmt->name, 534 renderer, "markup", 535 col_idx++, NULL); 536 } 537 538 /* construct merged column header since sort keys share single column */ 539 buf[0] = '\0'; 540 first_node = true; 541 list_for_each_entry_continue(fmt_node, &hists->hpp_formats, list) { 542 if (!first_node) 543 strcat(buf, " / "); 544 first_node = false; 545 546 first_col = true; 547 perf_hpp_list__for_each_format(&fmt_node->hpp ,fmt) { 548 if (perf_hpp__should_skip(fmt, hists)) 549 continue; 550 551 if (!first_col) 552 strcat(buf, "+"); 553 first_col = false; 554 555 fmt->header(fmt, &hpp, hists, 0, NULL); 556 strcat(buf, ltrim(rtrim(hpp.buf))); 557 } 558 } 559 560 gtk_tree_view_insert_column_with_attributes(GTK_TREE_VIEW(view), 561 -1, buf, 562 renderer, "markup", 563 col_idx++, NULL); 564 565 for (col_idx = 0; col_idx < nr_cols; col_idx++) { 566 GtkTreeViewColumn *column; 567 568 column = gtk_tree_view_get_column(GTK_TREE_VIEW(view), col_idx); 569 gtk_tree_view_column_set_resizable(column, TRUE); 570 571 if (col_idx == 0) { 572 gtk_tree_view_set_expander_column(GTK_TREE_VIEW(view), 573 column); 574 } 575 } 576 577 gtk_tree_view_set_model(GTK_TREE_VIEW(view), GTK_TREE_MODEL(store)); 578 g_object_unref(GTK_TREE_MODEL(store)); 579 580 perf_gtk__add_hierarchy_entries(hists, &hists->entries, store, 581 NULL, &hpp, min_pcnt); 582 583 gtk_tree_view_set_rules_hint(GTK_TREE_VIEW(view), TRUE); 584 585 g_signal_connect(view, "row-activated", 586 G_CALLBACK(on_row_activated), NULL); 587 gtk_container_add(GTK_CONTAINER(window), view); 588 } 589 590 int perf_evlist__gtk_browse_hists(struct perf_evlist *evlist, 591 const char *help, 592 struct hist_browser_timer *hbt __maybe_unused, 593 float min_pcnt) 594 { 595 struct perf_evsel *pos; 596 GtkWidget *vbox; 597 GtkWidget *notebook; 598 GtkWidget *info_bar; 599 GtkWidget *statbar; 600 GtkWidget *window; 601 602 signal(SIGSEGV, perf_gtk__signal); 603 signal(SIGFPE, perf_gtk__signal); 604 signal(SIGINT, perf_gtk__signal); 605 signal(SIGQUIT, perf_gtk__signal); 606 signal(SIGTERM, perf_gtk__signal); 607 608 window = gtk_window_new(GTK_WINDOW_TOPLEVEL); 609 610 gtk_window_set_title(GTK_WINDOW(window), "perf report"); 611 612 g_signal_connect(window, "delete_event", gtk_main_quit, NULL); 613 614 pgctx = perf_gtk__activate_context(window); 615 if (!pgctx) 616 return -1; 617 618 vbox = gtk_vbox_new(FALSE, 0); 619 620 notebook = gtk_notebook_new(); 621 622 gtk_box_pack_start(GTK_BOX(vbox), notebook, TRUE, TRUE, 0); 623 624 info_bar = perf_gtk__setup_info_bar(); 625 if (info_bar) 626 gtk_box_pack_start(GTK_BOX(vbox), info_bar, FALSE, FALSE, 0); 627 628 statbar = perf_gtk__setup_statusbar(); 629 gtk_box_pack_start(GTK_BOX(vbox), statbar, FALSE, FALSE, 0); 630 631 gtk_container_add(GTK_CONTAINER(window), vbox); 632 633 evlist__for_each_entry(evlist, pos) { 634 struct hists *hists = evsel__hists(pos); 635 const char *evname = perf_evsel__name(pos); 636 GtkWidget *scrolled_window; 637 GtkWidget *tab_label; 638 char buf[512]; 639 size_t size = sizeof(buf); 640 641 if (symbol_conf.event_group) { 642 if (!perf_evsel__is_group_leader(pos)) 643 continue; 644 645 if (pos->nr_members > 1) { 646 perf_evsel__group_desc(pos, buf, size); 647 evname = buf; 648 } 649 } 650 651 scrolled_window = gtk_scrolled_window_new(NULL, NULL); 652 653 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolled_window), 654 GTK_POLICY_AUTOMATIC, 655 GTK_POLICY_AUTOMATIC); 656 657 if (symbol_conf.report_hierarchy) 658 perf_gtk__show_hierarchy(scrolled_window, hists, min_pcnt); 659 else 660 perf_gtk__show_hists(scrolled_window, hists, min_pcnt); 661 662 tab_label = gtk_label_new(evname); 663 664 gtk_notebook_append_page(GTK_NOTEBOOK(notebook), scrolled_window, tab_label); 665 } 666 667 gtk_widget_show_all(window); 668 669 perf_gtk__resize_window(window); 670 671 gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER); 672 673 ui_helpline__push(help); 674 675 gtk_main(); 676 677 perf_gtk__deactivate_context(&pgctx); 678 679 return 0; 680 } 681