xref: /openbmc/linux/tools/perf/ui/gtk/hists.c (revision 9a8f3203)
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