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