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.
This commit is contained in:
Christian Hergert
2023-08-30 16:20:51 -07:00
parent f1234280aa
commit bfc989c6c0
3 changed files with 301 additions and 0 deletions

View File

@ -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,
)

View File

@ -0,0 +1,289 @@
/* sysprof-diff.c
*
* Copyright 2023 Christian Hergert <chergert@redhat.com>
*
* 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 <http://www.gnu.org/licenses/>.
*
* SPDX-License-Identifier: GPL-3.0-or-later
*/
#include "config.h"
#include <libdex.h>
#include <sysprof.h>
#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;
}