mirror of
https://github.com/varun-r-mallya/sysprof.git
synced 2025-12-31 20:36:25 +00:00
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:
11
src/sysprof-diff/meson.build
Normal file
11
src/sysprof-diff/meson.build
Normal 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,
|
||||
)
|
||||
289
src/sysprof-diff/sysprof-diff.c
Normal file
289
src/sysprof-diff/sysprof-diff.c
Normal 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;
|
||||
}
|
||||
Reference in New Issue
Block a user