From b5ce671e23fed0b9ec57a34b25aa046bc277b88b Mon Sep 17 00:00:00 2001 From: Christian Hergert Date: Thu, 25 May 2023 12:16:18 -0700 Subject: [PATCH] tools: start on callgraph example using columnview This just serves as a prototyping ground so that we can have a callgraph view widget in the future based on these principles. It also shows some areas that still need work, such as sorting within the tree and fixing the text offset calculation for ELF symbols. --- src/tools/callgraph.c | 357 ++++++++++++++++++++++++++++++++++++++++++ src/tools/meson.build | 9 ++ 2 files changed, 366 insertions(+) create mode 100644 src/tools/callgraph.c diff --git a/src/tools/callgraph.c b/src/tools/callgraph.c new file mode 100644 index 00000000..76a793eb --- /dev/null +++ b/src/tools/callgraph.c @@ -0,0 +1,357 @@ +/* callgraph.c + * + * Copyright 2023 Christian Hergert + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#include "config.h" + +#include +#include + +static GMainLoop *main_loop; +static char *kallsyms_path; +static char *filename; +static double total; +static const GOptionEntry entries[] = { + { "kallsyms", 'k', 0, G_OPTION_ARG_FILENAME, &kallsyms_path, "The path to kallsyms to use for decoding", "PATH" }, + { 0 } +}; + +typedef struct _Augment +{ + guint32 size; + guint32 total; +} Augment; + +static void +function_setup (GtkSignalListItemFactory *factory, + GtkListItem *list_item, + SysprofCallgraph *callgraph) +{ + GtkWidget *expander; + GtkWidget *text; + + g_assert (GTK_IS_SIGNAL_LIST_ITEM_FACTORY (factory)); + g_assert (GTK_IS_LIST_ITEM (list_item)); + g_assert (SYSPROF_IS_CALLGRAPH (callgraph)); + + expander = gtk_tree_expander_new (); + text = g_object_new (GTK_TYPE_INSCRIPTION, + "hexpand", TRUE, + NULL); + gtk_tree_expander_set_child (GTK_TREE_EXPANDER (expander), text); + gtk_list_item_set_child (list_item, expander); +} + +static void +function_bind (GtkSignalListItemFactory *factory, + GtkListItem *list_item, + SysprofCallgraph *callgraph) +{ + SysprofCallgraphFrame *frame; + GtkTreeListRow *row; + SysprofSymbol *symbol; + const char *name; + GtkWidget *expander; + GtkWidget *text; + + g_assert (GTK_IS_SIGNAL_LIST_ITEM_FACTORY (factory)); + g_assert (GTK_IS_LIST_ITEM (list_item)); + g_assert (SYSPROF_IS_CALLGRAPH (callgraph)); + + row = gtk_list_item_get_item (list_item); + frame = gtk_tree_list_row_get_item (row); + symbol = sysprof_callgraph_frame_get_symbol (frame); + name = sysprof_symbol_get_name (symbol); + + expander = gtk_list_item_get_child (list_item); + text = gtk_tree_expander_get_child (GTK_TREE_EXPANDER (expander)); + gtk_tree_expander_set_list_row (GTK_TREE_EXPANDER (expander), row); + gtk_inscription_set_text (GTK_INSCRIPTION (text), name); +} + +static void +total_setup (GtkSignalListItemFactory *factory, + GtkListItem *list_item, + SysprofCallgraph *callgraph) +{ + GtkWidget *child; + + g_assert (GTK_IS_SIGNAL_LIST_ITEM_FACTORY (factory)); + g_assert (GTK_IS_LIST_ITEM (list_item)); + g_assert (SYSPROF_IS_CALLGRAPH (callgraph)); + + child = g_object_new (GTK_TYPE_PROGRESS_BAR, + "show-text", TRUE, + NULL); + gtk_list_item_set_child (list_item, child); +} + +static void +total_bind (GtkSignalListItemFactory *factory, + GtkListItem *list_item, + gpointer user_data) +{ + g_autofree char *text = NULL; + SysprofCallgraphFrame *frame; + GtkTreeListRow *row; + GtkWidget *child; + double fraction; + Augment *aug; + + g_assert (GTK_IS_SIGNAL_LIST_ITEM_FACTORY (factory)); + g_assert (GTK_IS_LIST_ITEM (list_item)); + + row = gtk_list_item_get_item (list_item); + frame = gtk_tree_list_row_get_item (row); + aug = sysprof_callgraph_frame_get_augment (frame); + fraction = aug->total / total; + + text = g_strdup_printf ("%.2lf", fraction*100.); + + child = gtk_list_item_get_child (list_item); + gtk_progress_bar_set_fraction (GTK_PROGRESS_BAR (child), fraction); + gtk_progress_bar_set_text (GTK_PROGRESS_BAR (child), text); +} + +static void +self_setup (GtkSignalListItemFactory *factory, + GtkListItem *list_item, + SysprofCallgraph *callgraph) +{ + GtkWidget *child; + + g_assert (GTK_IS_SIGNAL_LIST_ITEM_FACTORY (factory)); + g_assert (GTK_IS_LIST_ITEM (list_item)); + g_assert (SYSPROF_IS_CALLGRAPH (callgraph)); + + child = g_object_new (GTK_TYPE_PROGRESS_BAR, + "show-text", TRUE, + NULL); + gtk_list_item_set_child (list_item, child); +} + +static void +self_bind (GtkSignalListItemFactory *factory, + GtkListItem *list_item, + gpointer user_data) +{ + g_autofree char *text = NULL; + SysprofCallgraphFrame *frame; + GtkTreeListRow *row; + GtkWidget *child; + double fraction; + Augment *aug; + + g_assert (GTK_IS_SIGNAL_LIST_ITEM_FACTORY (factory)); + g_assert (GTK_IS_LIST_ITEM (list_item)); + + row = gtk_list_item_get_item (list_item); + frame = gtk_tree_list_row_get_item (row); + aug = sysprof_callgraph_frame_get_augment (frame); + fraction = aug->size / total; + + text = g_strdup_printf ("%.2lf", fraction*100.); + + child = gtk_list_item_get_child (list_item); + gtk_progress_bar_set_fraction (GTK_PROGRESS_BAR (child), fraction); + gtk_progress_bar_set_text (GTK_PROGRESS_BAR (child), text); +} + +static GListModel * +create_model_func (gpointer item, + gpointer user_data) +{ + if (g_list_model_get_n_items (G_LIST_MODEL (item)) > 0) + return g_object_ref (G_LIST_MODEL (item)); + return NULL; +} + +static void +show_callgraph (SysprofCallgraph *callgraph) +{ + g_autofree char *name = g_path_get_basename (filename); + g_autoptr(GtkTreeListModel) tree = NULL; + g_autoptr(GtkNoSelection) model = NULL; + g_autoptr(SysprofCallgraphFrame) root = NULL; + GtkColumnViewColumn *column; + GtkScrolledWindow *scroller; + GtkColumnView *column_view; + GtkListItemFactory *factory; + GtkWindow *window; + Augment *aug; + + root = g_list_model_get_item (G_LIST_MODEL (callgraph), 0); + aug = sysprof_callgraph_frame_get_augment (root); + total = aug->total; + + tree = gtk_tree_list_model_new (g_object_ref (G_LIST_MODEL (callgraph)), + FALSE, + FALSE, + create_model_func, NULL, NULL); + model = gtk_no_selection_new (g_object_ref (G_LIST_MODEL (tree))); + + window = g_object_new (GTK_TYPE_WINDOW, + "default-width", 1024, + "default-height", 800, + "title", name, + NULL); + scroller = g_object_new (GTK_TYPE_SCROLLED_WINDOW, NULL); + gtk_window_set_child (GTK_WINDOW (window), GTK_WIDGET (scroller)); + column_view = g_object_new (GTK_TYPE_COLUMN_VIEW, + "model", model, + NULL); + gtk_scrolled_window_set_child (scroller, GTK_WIDGET (column_view)); + + factory = gtk_signal_list_item_factory_new (); + g_signal_connect_object (factory, "setup", G_CALLBACK (function_setup), callgraph, 0); + g_signal_connect_object (factory, "bind", G_CALLBACK (function_bind), callgraph, 0); + column = gtk_column_view_column_new ("Function", factory); + gtk_column_view_column_set_expand (column, TRUE); + gtk_column_view_append_column (column_view, column); + + factory = gtk_signal_list_item_factory_new (); + g_signal_connect_object (factory, "setup", G_CALLBACK (self_setup), callgraph, 0); + g_signal_connect (factory, "bind", G_CALLBACK (self_bind), NULL); + column = gtk_column_view_column_new ("Self", factory); + gtk_column_view_column_set_expand (column, FALSE); + gtk_column_view_append_column (column_view, column); + + factory = gtk_signal_list_item_factory_new (); + g_signal_connect_object (factory, "setup", G_CALLBACK (total_setup), callgraph, 0); + g_signal_connect (factory, "bind", G_CALLBACK (total_bind), NULL); + column = gtk_column_view_column_new ("Total", factory); + gtk_column_view_column_set_expand (column, FALSE); + gtk_column_view_append_column (column_view, column); + + g_signal_connect_swapped (window, + "close-request", + G_CALLBACK (g_main_loop_quit), + main_loop); + gtk_window_present (window); +} + +static void +callgraph_cb (GObject *object, + GAsyncResult *result, + gpointer user_data) +{ + SysprofDocument *document = SYSPROF_DOCUMENT (object); + g_autoptr(GError) error = NULL; + g_autoptr(SysprofCallgraph) callgraph = sysprof_document_callgraph_finish (document, result, &error); + + g_print ("Done.\n"); + + g_assert_no_error (error); + g_assert_true (SYSPROF_IS_CALLGRAPH (callgraph)); + + show_callgraph (callgraph); +} + +static void +augment_cb (SysprofCallgraph *callgraph, + SysprofCallgraphNode *node, + SysprofDocumentFrame *frame, + gpointer user_data) +{ + Augment *aug; + + g_assert (SYSPROF_IS_CALLGRAPH (callgraph)); + g_assert (node != NULL); + g_assert (SYSPROF_IS_DOCUMENT_SAMPLE (frame)); + g_assert (user_data == NULL); + + aug = sysprof_callgraph_get_augment (callgraph, node); + aug->size += 1; + + for (; node; node = sysprof_callgraph_node_parent (node)) + { + aug = sysprof_callgraph_get_augment (callgraph, node); + aug->total += 1; + } +} + +int +main (int argc, + char *argv[]) +{ + g_autoptr(GOptionContext) context = g_option_context_new ("- show a callgraph"); + g_autoptr(SysprofDocumentLoader) loader = NULL; + g_autoptr(SysprofDocument) document = NULL; + g_autoptr(SysprofMultiSymbolizer) multi = NULL; + g_autoptr(GListModel) samples = NULL; + g_autoptr(GError) error = NULL; + + gtk_init (); + sysprof_clock_init (); + + g_option_context_add_main_entries (context, entries, NULL); + if (!g_option_context_parse (context, &argc, &argv, &error)) + { + g_printerr ("%s\n", error->message); + return 1; + } + + if (argc != 2) + { + g_print ("usage: %s [OPTIONS] CAPTURE_FILE\n", argv[0]); + return 1; + } + + filename = argv[1]; + + main_loop = g_main_loop_new (NULL, FALSE); + + multi = sysprof_multi_symbolizer_new (); + + if (kallsyms_path) + { + g_autoptr(GFile) kallsyms_file = g_file_new_for_path (kallsyms_path); + GFileInputStream *kallsyms_stream = g_file_read (kallsyms_file, NULL, NULL); + + sysprof_multi_symbolizer_take (multi, sysprof_kallsyms_symbolizer_new_for_symbols (G_INPUT_STREAM (kallsyms_stream))); + } + else + { + sysprof_multi_symbolizer_take (multi, sysprof_kallsyms_symbolizer_new ()); + } + + sysprof_multi_symbolizer_take (multi, sysprof_elf_symbolizer_new ()); + sysprof_multi_symbolizer_take (multi, sysprof_jitmap_symbolizer_new ()); + + loader = sysprof_document_loader_new (filename); + sysprof_document_loader_set_symbolizer (loader, SYSPROF_SYMBOLIZER (multi)); + + g_print ("Loading %s, ignoring embedded symbols...\n", filename); + if (!(document = sysprof_document_loader_load (loader, NULL, &error))) + g_error ("Failed to load document: %s", error->message); + + g_print ("Loaded and symbolized. Generating callgraph...\n"); + samples = sysprof_document_list_samples (document); + sysprof_document_callgraph_async (document, + samples, + sizeof (Augment), + augment_cb, NULL, NULL, + NULL, + callgraph_cb, + main_loop); + + g_main_loop_run (main_loop); + + return 0; +} diff --git a/src/tools/meson.build b/src/tools/meson.build index c48c4e10..295e534c 100644 --- a/src/tools/meson.build +++ b/src/tools/meson.build @@ -56,3 +56,12 @@ if get_option('agent') install: true, ) endif + +if get_option('gtk') + callgraph = executable('callgraph', ['callgraph.c'], + dependencies: [libsysprof_analyze_static_dep, + dependency('gtk4', version: gtk_req_version)], + c_args: tools_cflags, + install: false, + ) +endif