Merge tag 'scsi-fixes' of git://git.kernel.org/pub/scm/linux/kernel/git/jejb/scsi
[linux-2.6-block.git] / tools / perf / ui / gtk / hists.c
1 // SPDX-License-Identifier: GPL-2.0
2 #include "gtk.h"
3 #include "../evlist.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 <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 evlist__gtk_browse_hists(struct evlist *evlist, const char *help,
594                              struct hist_browser_timer *hbt __maybe_unused, float min_pcnt)
595 {
596         struct 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 = 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 (!evsel__is_group_leader(pos))
644                                 continue;
645
646                         if (pos->core.nr_members > 1) {
647                                 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 }