From bfc989c6c0ee082823f9980c1dd7f45961b1eecb Mon Sep 17 00:00:00 2001 From: Christian Hergert Date: Wed, 30 Aug 2023 16:20:51 -0700 Subject: [PATCH] build: add sysprof-diff tool This is not installed currently. It's for testing now but we can start installing it if/when people want to use it. We could also try to make it part of sysprof-cli someday. This tool prints the difference of two trees giving you a -/+ change of the number of hits incurred on that frame-pointer. --- src/meson.build | 1 + src/sysprof-diff/meson.build | 11 ++ src/sysprof-diff/sysprof-diff.c | 289 ++++++++++++++++++++++++++++++++ 3 files changed, 301 insertions(+) create mode 100644 src/sysprof-diff/meson.build create mode 100644 src/sysprof-diff/sysprof-diff.c diff --git a/src/meson.build b/src/meson.build index c5afe9d4..94ffaf25 100644 --- a/src/meson.build +++ b/src/meson.build @@ -29,4 +29,5 @@ endif if get_option('tools') subdir('sysprof-agent') subdir('sysprof-cli') + subdir('sysprof-diff') endif diff --git a/src/sysprof-diff/meson.build b/src/sysprof-diff/meson.build new file mode 100644 index 00000000..7bdb8e7b --- /dev/null +++ b/src/sysprof-diff/meson.build @@ -0,0 +1,11 @@ +sysprof_diff_deps = [ + libsysprof_static_dep, +] + +# Do not install for now, until we have a better idea of +# how we want to use this. +sysprof_diff = executable('sysprof-diff', 'sysprof-diff.c', + dependencies: sysprof_diff_deps, + c_args: release_flags, + install: false, +) diff --git a/src/sysprof-diff/sysprof-diff.c b/src/sysprof-diff/sysprof-diff.c new file mode 100644 index 00000000..11bc99bc --- /dev/null +++ b/src/sysprof-diff/sysprof-diff.c @@ -0,0 +1,289 @@ +/* sysprof-diff.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 + +#include "sysprof-callgraph-private.h" +#include "sysprof-symbol-private.h" + +static GMainLoop *main_loop; +static int exit_code = -1; +static char empty[1024]; +static const GOptionEntry options[] = { + { NULL } +}; + +static void +load_callgraph_cb (GObject *object, + GAsyncResult *result, + gpointer user_data) +{ + g_autoptr(DexPromise) promise = user_data; + SysprofCallgraph *callgraph; + GError *error = NULL; + + if ((callgraph = sysprof_document_callgraph_finish (SYSPROF_DOCUMENT (object), result, &error))) + dex_promise_resolve_object (promise, callgraph); + else + dex_promise_reject (promise, error); +} + +static DexFuture * +load_callgraph (SysprofDocument *document, + SysprofCallgraphFlags flags, + GListModel *traceables, + gsize augment_size, + SysprofAugmentationFunc augment_func, + gpointer augment_func_data, + GDestroyNotify augment_func_data_destroy) +{ + DexPromise *promise; + + g_assert (SYSPROF_IS_DOCUMENT (document)); + g_assert (G_IS_LIST_MODEL (traceables)); + + promise = dex_promise_new (); + + sysprof_document_callgraph_async (document, + flags, + traceables, + augment_size, + augment_func, + augment_func_data, + augment_func_data_destroy, + dex_promise_get_cancellable (promise), + load_callgraph_cb, + dex_ref (promise)); + + return DEX_FUTURE (promise); +} + +typedef struct +{ + guint size; + guint total; +} Augment; + +static void +augment_cb (SysprofCallgraph *callgraph, + SysprofCallgraphNode *node, + SysprofDocumentFrame *frame, + gboolean summarize, + 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; + aug->total += 1; + + for (SysprofCallgraphNode *iter = node->parent; iter; iter = iter->parent) + { + aug = sysprof_callgraph_get_augment (callgraph, iter); + aug->total += 1; + } +} + +static int +compare (SysprofCallgraphNode *a, + SysprofCallgraphNode *b) +{ + if (a == b) + return 0; + + if (a != NULL && b == NULL) + return -1; + + if (a == NULL && b != NULL) + return 1; + + return strcmp (a->summary->symbol->name, + b->summary->symbol->name); +} + +static void +diff (SysprofCallgraphNode *left, + SysprofCallgraphNode *right, + guint depth) +{ + const Augment *l_aug = left ? (const Augment *)&left->augment[0] : NULL; + const Augment *r_aug = right ? (const Augment *)&right->augment[0] : NULL; + int l_total = l_aug ? l_aug->total : 0; + int r_total = r_aug ? r_aug->total : 0; + int change = r_total - l_total; + const char *name = left ? left->summary->symbol->name : right->summary->symbol->name; + SysprofCallgraphNode *l_iter; + SysprofCallgraphNode *r_iter; + + g_print ("%6u | %6u | %+6d | ", l_total, r_total, change); + if (depth > 0) + write (STDOUT_FILENO, empty, MIN (sizeof empty, depth*2)); + g_print ("%s\n", name); + + l_iter = left ? left->children : NULL; + r_iter = right ? right->children : NULL; + + while (l_iter || r_iter) + { + while (r_iter && compare (r_iter, l_iter) < 0) + { + diff (NULL, r_iter, depth+1); + r_iter = r_iter->next; + } + + if (l_iter) + { + if (r_iter && compare (l_iter, r_iter) == 0) + diff (l_iter, r_iter, depth+1); + else + diff (l_iter, NULL, depth+1); + + l_iter = l_iter->next; + } + } +} + +static DexFuture * +sysprof_diff_fiber (gpointer data) +{ + const char * const *filenames = data; + g_autoptr(SysprofDocumentLoader) loader1 = NULL; + g_autoptr(SysprofDocumentLoader) loader2 = NULL; + g_autoptr(SysprofDocument) document1 = NULL; + g_autoptr(SysprofDocument) document2 = NULL; + g_autoptr(SysprofCallgraph) callgraph1 = NULL; + g_autoptr(SysprofCallgraph) callgraph2 = NULL; + g_autoptr(GListModel) traceables1 = NULL; + g_autoptr(GListModel) traceables2 = NULL; + g_autoptr(GError) error = NULL; + SysprofCallgraphFlags flags = 0; + + g_assert (filenames != NULL); + g_assert (filenames[0] != NULL); + g_assert (filenames[1] != NULL); + + loader1 = sysprof_document_loader_new (filenames[0]); + loader2 = sysprof_document_loader_new (filenames[1]); + + g_printerr ("Loading %s...\n", filenames[0]); + if (!(document1 = sysprof_document_loader_load (loader1, NULL, &error))) + return dex_future_new_for_error (g_steal_pointer (&error)); + + traceables1 = sysprof_document_list_samples (document1); + + if (!(callgraph1 = dex_await_object (load_callgraph (document1, flags, traceables1, sizeof (Augment), augment_cb, NULL, NULL), &error))) + return dex_future_new_for_error (g_steal_pointer (&error)); + + g_printerr ("Loading %s...\n", filenames[1]); + if (!(document2 = sysprof_document_loader_load (loader2, NULL, &error))) + return dex_future_new_for_error (g_steal_pointer (&error)); + + traceables2 = sysprof_document_list_samples (document2); + + if (!(callgraph2 = dex_await_object (load_callgraph (document2, flags, traceables2, sizeof (Augment), augment_cb, NULL, NULL), &error))) + return dex_future_new_for_error (g_steal_pointer (&error)); + + g_print ("BEFORE | AFTER | CHANGE | FUNCTION\n"); + g_print ("-------|--------|--------|---------\n"); + diff (&callgraph1->root, &callgraph2->root, 0); + + exit_code = EXIT_SUCCESS; + + g_main_loop_quit (main_loop); + + return dex_future_new_for_boolean (TRUE); +} + +static DexFuture * +sysprof_diff_catch_error_cb (DexFuture *future, + gpointer user_data) +{ + g_autoptr(GError) error = NULL; + + dex_await (dex_ref (future), &error); + + g_printerr ("Error: %s\n", error->message); + + exit_code = EXIT_FAILURE; + + g_main_loop_quit (main_loop); + + return NULL; +} + +int +main (int argc, + char *argv[]) +{ + g_autoptr(GOptionContext) context = NULL; + g_autoptr(GError) error = NULL; + const char *copy[3]; + + dex_init (); + + g_set_prgname ("sysprof-diff"); + g_set_application_name ("sysprof-diff"); + + main_loop = g_main_loop_new (NULL, FALSE); + + context = g_option_context_new ("BEFORE_CAPTURE AFTER_CAPTURE -- Generate a callgraph diff between two captures"); + g_option_context_add_main_entries (context, options, NULL); + + if (!g_option_context_parse (context, &argc, &argv, &error)) + { + g_printerr ("%s\n", error->message); + return EXIT_FAILURE; + } + + if (argc < 3) + { + g_printerr ("usage: %s BEFORE_CAPTURE AFTER_CAPTURE\n", argv[0]); + return EXIT_FAILURE; + } + + copy[0] = argv[1]; + copy[1] = argv[2]; + copy[2] = NULL; + + memset (empty, ' ', sizeof empty); + + dex_future_disown ( + dex_future_catch ( + dex_scheduler_spawn (NULL, 0, + sysprof_diff_fiber, + g_strdupv ((char **)copy), + (GDestroyNotify)g_strfreev), + sysprof_diff_catch_error_cb, + NULL, NULL)); + + if (exit_code == -1) + g_main_loop_run (main_loop); + + return exit_code; +}