From 8348f7fe2c327c5b5f28095612e5b4137c379c75 Mon Sep 17 00:00:00 2001 From: Christian Hergert Date: Wed, 24 May 2023 13:00:47 -0700 Subject: [PATCH] libsysprof-analyze: start on scaffolding for callgraph API --- src/libsysprof-analyze/meson.build | 4 + src/libsysprof-analyze/sysprof-analyze.h | 2 + .../sysprof-callgraph-frame.c | 84 +++++++ .../sysprof-callgraph-frame.h | 34 +++ .../sysprof-callgraph-private.h | 38 +++ src/libsysprof-analyze/sysprof-callgraph.c | 221 ++++++++++++++++++ src/libsysprof-analyze/sysprof-callgraph.h | 34 +++ src/libsysprof-analyze/sysprof-document.c | 75 ++++++ src/libsysprof-analyze/sysprof-document.h | 11 + 9 files changed, 503 insertions(+) create mode 100644 src/libsysprof-analyze/sysprof-callgraph-frame.c create mode 100644 src/libsysprof-analyze/sysprof-callgraph-frame.h create mode 100644 src/libsysprof-analyze/sysprof-callgraph-private.h create mode 100644 src/libsysprof-analyze/sysprof-callgraph.c create mode 100644 src/libsysprof-analyze/sysprof-callgraph.h diff --git a/src/libsysprof-analyze/meson.build b/src/libsysprof-analyze/meson.build index 00e3f408..d8d25a12 100644 --- a/src/libsysprof-analyze/meson.build +++ b/src/libsysprof-analyze/meson.build @@ -1,5 +1,7 @@ libsysprof_analyze_public_sources = [ 'sysprof-bundled-symbolizer.c', + 'sysprof-callgraph.c', + 'sysprof-callgraph-frame.c', 'sysprof-document.c', 'sysprof-document-allocation.c', 'sysprof-document-counter.c', @@ -49,6 +51,8 @@ libsysprof_analyze_private_sources = [ libsysprof_analyze_public_headers = [ 'sysprof-analyze.h', + 'sysprof-callgraph.h', + 'sysprof-callgraph-frame.h', 'sysprof-bundled-symbolizer.h', 'sysprof-document.h', 'sysprof-document-allocation.h', diff --git a/src/libsysprof-analyze/sysprof-analyze.h b/src/libsysprof-analyze/sysprof-analyze.h index 2962fc67..4eda88a9 100644 --- a/src/libsysprof-analyze/sysprof-analyze.h +++ b/src/libsysprof-analyze/sysprof-analyze.h @@ -26,6 +26,8 @@ G_BEGIN_DECLS #define SYSPROF_ANALYZE_INSIDE # include "sysprof-bundled-symbolizer.h" +# include "sysprof-callgraph.h" +# include "sysprof-callgraph-frame.h" # include "sysprof-document.h" # include "sysprof-document-allocation.h" # include "sysprof-document-counter.h" diff --git a/src/libsysprof-analyze/sysprof-callgraph-frame.c b/src/libsysprof-analyze/sysprof-callgraph-frame.c new file mode 100644 index 00000000..67ba2bee --- /dev/null +++ b/src/libsysprof-analyze/sysprof-callgraph-frame.c @@ -0,0 +1,84 @@ +/* sysprof-callgraph-frame.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 "sysprof-callgraph-frame.h" + +struct _SysprofCallgraphFrame +{ + GObject parent_instance; +}; + +enum { + PROP_0, + N_PROPS +}; + +G_DEFINE_FINAL_TYPE (SysprofCallgraphFrame, sysprof_callgraph_frame, G_TYPE_OBJECT) + +static GParamSpec *properties [N_PROPS]; + +static void +sysprof_callgraph_frame_finalize (GObject *object) +{ + G_OBJECT_CLASS (sysprof_callgraph_frame_parent_class)->finalize (object); +} + +static void +sysprof_callgraph_frame_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + switch (prop_id) + { + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +static void +sysprof_callgraph_frame_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + switch (prop_id) + { + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +static void +sysprof_callgraph_frame_class_init (SysprofCallgraphFrameClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->finalize = sysprof_callgraph_frame_finalize; + object_class->get_property = sysprof_callgraph_frame_get_property; + object_class->set_property = sysprof_callgraph_frame_set_property; +} + +static void +sysprof_callgraph_frame_init (SysprofCallgraphFrame *self) +{ +} diff --git a/src/libsysprof-analyze/sysprof-callgraph-frame.h b/src/libsysprof-analyze/sysprof-callgraph-frame.h new file mode 100644 index 00000000..2c650fd7 --- /dev/null +++ b/src/libsysprof-analyze/sysprof-callgraph-frame.h @@ -0,0 +1,34 @@ +/* sysprof-callgraph-frame.h + * + * 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 + */ + +#pragma once + +#include + +#include + +G_BEGIN_DECLS + +#define SYSPROF_TYPE_CALLGRAPH_FRAME (sysprof_callgraph_frame_get_type()) + +SYSPROF_AVAILABLE_IN_ALL +G_DECLARE_FINAL_TYPE (SysprofCallgraphFrame, sysprof_callgraph_frame, SYSPROF, CALLGRAPH_FRAME, GObject) + +G_END_DECLS diff --git a/src/libsysprof-analyze/sysprof-callgraph-private.h b/src/libsysprof-analyze/sysprof-callgraph-private.h new file mode 100644 index 00000000..e1bd247b --- /dev/null +++ b/src/libsysprof-analyze/sysprof-callgraph-private.h @@ -0,0 +1,38 @@ +/* sysprof-callgraph-private.h + * + * 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 + */ + +#pragma once + +#include + +#include "sysprof-callgraph.h" +#include "sysprof-document.h" + +G_BEGIN_DECLS + +void _sysprof_callgraph_new_async (SysprofDocument *document, + GListModel *traceables, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data); +SysprofCallgraph *_sysprof_callgraph_new_finish (GAsyncResult *result, + GError **error); + +G_END_DECLS diff --git a/src/libsysprof-analyze/sysprof-callgraph.c b/src/libsysprof-analyze/sysprof-callgraph.c new file mode 100644 index 00000000..79918052 --- /dev/null +++ b/src/libsysprof-analyze/sysprof-callgraph.c @@ -0,0 +1,221 @@ +/* sysprof-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 "sysprof-callgraph-private.h" +#include "sysprof-callgraph-frame.h" +#include "sysprof-document-traceable.h" + +#define MAX_STACK_DEPTH 1024 + +struct _SysprofCallgraph +{ + GObject parent_instance; + SysprofDocument *document; + GListModel *traceables; +}; + +typedef struct _SysprofCallgraphNode +{ + SysprofSymbol *symbol; + + struct _SysprofCallgraphNode *next; + struct _SysprofCallgraphNode *prev; + + struct _SysprofCallgraphNode *parent; + struct _SysprofCallgraphNode *children; +} SysprofCallgraphNode; + +typedef struct _SysprofCallgraphTrace +{ + guint model_position; + guint n_nodes; + SysprofCallgraphNode nodes[]; +} SysprofCallgraphTrace; + +static GType +sysprof_callgraph_get_item_type (GListModel *model) +{ + return SYSPROF_TYPE_CALLGRAPH_FRAME; +} + +static guint +sysprof_callgraph_get_n_items (GListModel *model) +{ + return 0; +} + +static gpointer +sysprof_callgraph_get_item (GListModel *model, + guint position) +{ + return NULL; +} + +static void +list_model_iface_init (GListModelInterface *iface) +{ + iface->get_item_type = sysprof_callgraph_get_item_type; + iface->get_n_items = sysprof_callgraph_get_n_items; + iface->get_item = sysprof_callgraph_get_item; +} + +G_DEFINE_FINAL_TYPE_WITH_CODE (SysprofCallgraph, sysprof_callgraph, G_TYPE_OBJECT, + G_IMPLEMENT_INTERFACE (G_TYPE_LIST_MODEL, list_model_iface_init)) + +static void +sysprof_callgraph_finalize (GObject *object) +{ + SysprofCallgraph *self = (SysprofCallgraph *)object; + + g_clear_object (&self->document); + g_clear_object (&self->traceables); + + G_OBJECT_CLASS (sysprof_callgraph_parent_class)->finalize (object); +} + +static void +sysprof_callgraph_class_init (SysprofCallgraphClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->finalize = sysprof_callgraph_finalize; +} + +static void +sysprof_callgraph_init (SysprofCallgraph *self) +{ +} + +static void +sysprof_callgraph_trace_free (SysprofCallgraphTrace *trace) +{ + g_free (trace); +} + +static void +sysprof_callgraph_add_trace (SysprofCallgraph *self, + SysprofCallgraphTrace *trace) +{ + g_assert (SYSPROF_IS_CALLGRAPH (self)); + g_assert (trace != NULL); + + sysprof_callgraph_trace_free (trace); +} + +static void +sysprof_callgraph_add_traceable (SysprofCallgraph *self, + SysprofDocumentTraceable *traceable, + guint model_position) +{ + SysprofCallgraphTrace *trace; + SysprofSymbol **symbols; + guint stack_depth; + guint n_symbols; + + g_assert (SYSPROF_IS_CALLGRAPH (self)); + g_assert (SYSPROF_IS_DOCUMENT_TRACEABLE (traceable)); + + stack_depth = sysprof_document_traceable_get_stack_depth (traceable); + + if (stack_depth > MAX_STACK_DEPTH) + return; + + symbols = g_newa (SysprofSymbol *, stack_depth); + n_symbols = sysprof_document_symbolize_traceable (self->document, traceable, symbols, stack_depth); + + if (n_symbols == 0) + return; + + trace = g_malloc (sizeof *trace + (n_symbols * sizeof (SysprofCallgraphNode))); + trace->model_position = model_position; + trace->n_nodes = n_symbols; + + for (guint i = 0; i < n_symbols; i++) + { + trace->nodes[i].symbol = symbols[i]; + trace->nodes[i].children = NULL; + trace->nodes[i].parent = NULL; + trace->nodes[i].next = NULL; + trace->nodes[i].prev = NULL; + } + + sysprof_callgraph_add_trace (self, trace); +} + +static void +sysprof_callgraph_new_worker (GTask *task, + gpointer source_object, + gpointer task_data, + GCancellable *cancellable) +{ + SysprofCallgraph *self = task_data; + guint n_items; + + g_assert (G_IS_TASK (task)); + g_assert (source_object == NULL); + g_assert (SYSPROF_IS_CALLGRAPH (self)); + g_assert (!cancellable || G_IS_CANCELLABLE (cancellable)); + + n_items = g_list_model_get_n_items (self->traceables); + + for (guint i = 0; i < n_items; i++) + { + g_autoptr(SysprofDocumentTraceable) traceable = g_list_model_get_item (self->traceables, i); + + sysprof_callgraph_add_traceable (self, traceable, i); + } + + g_task_return_pointer (task, g_object_ref (self), g_object_unref); +} + +void +_sysprof_callgraph_new_async (SysprofDocument *document, + GListModel *traceables, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + g_autoptr(SysprofCallgraph) self = NULL; + g_autoptr(GTask) task = NULL; + + g_return_if_fail (SYSPROF_IS_DOCUMENT (document)); + g_return_if_fail (G_IS_LIST_MODEL (traceables)); + g_return_if_fail (!cancellable || G_IS_CANCELLABLE (cancellable)); + + self = g_object_new (SYSPROF_TYPE_CALLGRAPH, NULL); + self->document = g_object_ref (document); + self->traceables = g_object_ref (traceables); + + task = g_task_new (NULL, cancellable, callback, user_data); + g_task_set_source_tag (task, _sysprof_callgraph_new_async); + g_task_set_task_data (task, g_object_ref (self), g_object_unref); + g_task_run_in_thread (task, sysprof_callgraph_new_worker); +} + +SysprofCallgraph * +_sysprof_callgraph_new_finish (GAsyncResult *result, + GError **error) +{ + g_return_val_if_fail (G_IS_TASK (result), NULL); + + return g_task_propagate_pointer (G_TASK (result), error); +} diff --git a/src/libsysprof-analyze/sysprof-callgraph.h b/src/libsysprof-analyze/sysprof-callgraph.h new file mode 100644 index 00000000..2391758a --- /dev/null +++ b/src/libsysprof-analyze/sysprof-callgraph.h @@ -0,0 +1,34 @@ +/* sysprof-callgraph.h + * + * 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 + */ + +#pragma once + +#include + +#include + +G_BEGIN_DECLS + +#define SYSPROF_TYPE_CALLGRAPH (sysprof_callgraph_get_type()) + +SYSPROF_AVAILABLE_IN_ALL +G_DECLARE_FINAL_TYPE (SysprofCallgraph, sysprof_callgraph, SYSPROF, CALLGRAPH, GObject) + +G_END_DECLS diff --git a/src/libsysprof-analyze/sysprof-document.c b/src/libsysprof-analyze/sysprof-document.c index 866ddd4e..64e76ea5 100644 --- a/src/libsysprof-analyze/sysprof-document.c +++ b/src/libsysprof-analyze/sysprof-document.c @@ -26,6 +26,7 @@ #include "sysprof-document-private.h" +#include "sysprof-callgraph-private.h" #include "sysprof-document-bitset-index-private.h" #include "sysprof-document-counter-private.h" #include "sysprof-document-ctrdef.h" @@ -1056,3 +1057,77 @@ sysprof_document_list_counters (SysprofDocument *self) return g_object_ref (G_LIST_MODEL (self->counters)); } + +static void +sysprof_document_callgraph_cb (GObject *object, + GAsyncResult *result, + gpointer user_data) +{ + g_autoptr(SysprofCallgraph) callgraph = NULL; + g_autoptr(GError) error = NULL; + g_autoptr(GTask) task = user_data; + + g_assert (G_IS_ASYNC_RESULT (result)); + g_assert (G_IS_TASK (task)); + + if (!(callgraph = _sysprof_callgraph_new_finish (result, &error))) + g_task_return_error (task, g_steal_pointer (&error)); + else + g_task_return_pointer (task, g_steal_pointer (&callgraph), g_object_unref); +} + +/** + * sysprof_document_callgraph_async: + * @self: a #SysprofDocument + * @traceables: a list model of traceables for the callgraph + * @cancellable: (nullable): a #GCancellable or %NULL + * @callback: a callback to execute upon completion + * @user_data: closure data for @callback + * + * Asynchronously generates a callgraph using the @traceables to get + * the call stacks. + */ +void +sysprof_document_callgraph_async (SysprofDocument *self, + GListModel *traceables, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + g_autoptr(GTask) task = NULL; + + g_return_if_fail (SYSPROF_IS_DOCUMENT (self)); + g_return_if_fail (G_IS_LIST_MODEL (traceables)); + g_return_if_fail (!cancellable || G_IS_CANCELLABLE (cancellable)); + + task = g_task_new (self, cancellable, callback, user_data); + g_task_set_source_tag (task, sysprof_document_callgraph_async); + + _sysprof_callgraph_new_async (self, + traceables, + cancellable, + sysprof_document_callgraph_cb, + g_steal_pointer (&task)); +} + +/** + * sysprof_document_callgraph_finish: + * @self: a #SysprofDocument + * @result: the #GAsyncResult provided to callback + * @error: a location for a #GError + * + * Completes a request to generate a callgraph. + * + * Returns: (transfer full): a #SysprofCallgraph if successful; otherwise %NULL + * and @error is set. + */ +SysprofCallgraph * +sysprof_document_callgraph_finish (SysprofDocument *self, + GAsyncResult *result, + GError **error) +{ + g_return_val_if_fail (SYSPROF_IS_DOCUMENT (self), NULL); + g_return_val_if_fail (G_IS_TASK (result), NULL); + + return g_task_propagate_pointer (G_TASK (result), error); +} diff --git a/src/libsysprof-analyze/sysprof-document.h b/src/libsysprof-analyze/sysprof-document.h index df2371e7..26cd392c 100644 --- a/src/libsysprof-analyze/sysprof-document.h +++ b/src/libsysprof-analyze/sysprof-document.h @@ -24,6 +24,7 @@ #include +#include "sysprof-callgraph.h" #include "sysprof-document-file.h" #include "sysprof-document-traceable.h" #include "sysprof-symbol.h" @@ -53,5 +54,15 @@ guint sysprof_document_symbolize_traceable (SysprofDocument SysprofDocumentTraceable *traceable, SysprofSymbol **symbols, guint n_symbols); +SYSPROF_AVAILABLE_IN_ALL +void sysprof_document_callgraph_async (SysprofDocument *self, + GListModel *traceables, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data); +SYSPROF_AVAILABLE_IN_ALL +SysprofCallgraph *sysprof_document_callgraph_finish (SysprofDocument *self, + GAsyncResult *result, + GError **error); G_END_DECLS