diff --git a/src/libsysprof-analyze/sysprof-callgraph-frame-private.h b/src/libsysprof-analyze/sysprof-callgraph-frame-private.h new file mode 100644 index 00000000..a33c68db --- /dev/null +++ b/src/libsysprof-analyze/sysprof-callgraph-frame-private.h @@ -0,0 +1,31 @@ +/* sysprof-callgraph-frame-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 "sysprof-callgraph.h" +#include "sysprof-callgraph-frame.h" + +G_BEGIN_DECLS + +SysprofCallgraphFrame *_sysprof_callgraph_frame_new (SysprofCallgraph *callgraph, + SysprofCallgraphNode *node); + +G_END_DECLS diff --git a/src/libsysprof-analyze/sysprof-callgraph-frame.c b/src/libsysprof-analyze/sysprof-callgraph-frame.c index 67ba2bee..b98a94d4 100644 --- a/src/libsysprof-analyze/sysprof-callgraph-frame.c +++ b/src/libsysprof-analyze/sysprof-callgraph-frame.c @@ -20,25 +20,82 @@ #include "config.h" -#include "sysprof-callgraph-frame.h" +#include + +#include "sysprof-callgraph-private.h" +#include "sysprof-callgraph-frame-private.h" struct _SysprofCallgraphFrame { - GObject parent_instance; + GObject parent_instance; + SysprofCallgraph *callgraph; + SysprofCallgraphNode *node; + guint n_children; }; enum { PROP_0, + PROP_SYMBOL, N_PROPS }; -G_DEFINE_FINAL_TYPE (SysprofCallgraphFrame, sysprof_callgraph_frame, G_TYPE_OBJECT) +static GType +sysprof_callgraph_frame_get_item_type (GListModel *model) +{ + return SYSPROF_TYPE_CALLGRAPH_FRAME; +} + +static guint +sysprof_callgraph_frame_get_n_items (GListModel *model) +{ + return SYSPROF_CALLGRAPH_FRAME (model)->n_children; +} + +static gpointer +sysprof_callgraph_frame_get_item (GListModel *model, + guint position) +{ + SysprofCallgraphFrame *self = SYSPROF_CALLGRAPH_FRAME (model); + SysprofCallgraphNode *iter; + + if (self->callgraph == NULL) + return NULL; + + iter = self->node->children; + + while (iter != NULL && position > 0) + { + iter = iter->next; + position--; + } + + if (iter == NULL) + return NULL; + + return _sysprof_callgraph_frame_new (self->callgraph, iter); +} + +static void +list_model_iface_init (GListModelInterface *iface) +{ + iface->get_item_type = sysprof_callgraph_frame_get_item_type; + iface->get_n_items = sysprof_callgraph_frame_get_n_items; + iface->get_item = sysprof_callgraph_frame_get_item; +} + +G_DEFINE_FINAL_TYPE_WITH_CODE (SysprofCallgraphFrame, sysprof_callgraph_frame, G_TYPE_OBJECT, + G_IMPLEMENT_INTERFACE (G_TYPE_LIST_MODEL, list_model_iface_init)) static GParamSpec *properties [N_PROPS]; static void sysprof_callgraph_frame_finalize (GObject *object) { + SysprofCallgraphFrame *self = (SysprofCallgraphFrame *)object; + + g_clear_weak_pointer (&self->callgraph); + self->node = NULL; + G_OBJECT_CLASS (sysprof_callgraph_frame_parent_class)->finalize (object); } @@ -48,21 +105,14 @@ sysprof_callgraph_frame_get_property (GObject *object, GValue *value, GParamSpec *pspec) { - switch (prop_id) - { - default: - G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); - } -} + SysprofCallgraphFrame *self = SYSPROF_CALLGRAPH_FRAME (object); -static void -sysprof_callgraph_frame_set_property (GObject *object, - guint prop_id, - const GValue *value, - GParamSpec *pspec) -{ switch (prop_id) { + case PROP_SYMBOL: + g_value_set_object (value, sysprof_callgraph_frame_get_symbol (self)); + break; + default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); } @@ -75,10 +125,75 @@ sysprof_callgraph_frame_class_init (SysprofCallgraphFrameClass *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; + + properties [PROP_SYMBOL] = + g_param_spec_object ("symbol", NULL, NULL, + SYSPROF_TYPE_SYMBOL, + (G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); + + g_object_class_install_properties (object_class, N_PROPS, properties); } static void sysprof_callgraph_frame_init (SysprofCallgraphFrame *self) { } + +SysprofCallgraphFrame * +_sysprof_callgraph_frame_new (SysprofCallgraph *callgraph, + SysprofCallgraphNode *node) +{ + SysprofCallgraphFrame *self; + + g_return_val_if_fail (SYSPROF_IS_CALLGRAPH (callgraph), NULL); + g_return_val_if_fail (node != NULL, NULL); + + self = g_object_new (SYSPROF_TYPE_CALLGRAPH_FRAME, NULL); + g_set_weak_pointer (&self->callgraph, callgraph); + self->node = node; + + for (const SysprofCallgraphNode *iter = node->children; + iter != NULL; + iter = iter->next) + self->n_children++; + + return self; +} + +/** + * sysprof_callgraph_frame_get_symbol: + * @self: a #SysprofCallgraphFrame + * + * Gets the symbol for the frame. + * + * Returns: (nullable) (transfer none): a #SysprofSymbol + */ +SysprofSymbol * +sysprof_callgraph_frame_get_symbol (SysprofCallgraphFrame *self) +{ + g_return_val_if_fail (SYSPROF_IS_CALLGRAPH_FRAME (self), NULL); + + if (self->callgraph == NULL) + return NULL; + + return self->node->symbol; +} + +/** + * sysprof_callgraph_frame_get_augment: (skip) + * @self: a #SysprofCallgraphFrame + * + * Gets the augmentation that was attached to the callgrpah node. + * + * Returns: (nullable) (transfer none): the augmentation data + */ +gpointer +sysprof_callgraph_frame_get_augment (SysprofCallgraphFrame *self) +{ + g_return_val_if_fail (SYSPROF_IS_CALLGRAPH_FRAME (self), NULL); + + if (self->callgraph == NULL) + return NULL; + + return sysprof_callgraph_get_augment (self->callgraph, self->node); +} diff --git a/src/libsysprof-analyze/sysprof-callgraph-frame.h b/src/libsysprof-analyze/sysprof-callgraph-frame.h index 2c650fd7..97b0c6b0 100644 --- a/src/libsysprof-analyze/sysprof-callgraph-frame.h +++ b/src/libsysprof-analyze/sysprof-callgraph-frame.h @@ -24,6 +24,8 @@ #include +#include "sysprof-symbol.h" + G_BEGIN_DECLS #define SYSPROF_TYPE_CALLGRAPH_FRAME (sysprof_callgraph_frame_get_type()) @@ -31,4 +33,9 @@ G_BEGIN_DECLS SYSPROF_AVAILABLE_IN_ALL G_DECLARE_FINAL_TYPE (SysprofCallgraphFrame, sysprof_callgraph_frame, SYSPROF, CALLGRAPH_FRAME, GObject) +SYSPROF_AVAILABLE_IN_ALL +SysprofSymbol *sysprof_callgraph_frame_get_symbol (SysprofCallgraphFrame *self); +SYSPROF_AVAILABLE_IN_ALL +gpointer sysprof_callgraph_frame_get_augment (SysprofCallgraphFrame *self); + G_END_DECLS diff --git a/src/libsysprof-analyze/sysprof-callgraph-private.h b/src/libsysprof-analyze/sysprof-callgraph-private.h index e1bd247b..0765ee2e 100644 --- a/src/libsysprof-analyze/sysprof-callgraph-private.h +++ b/src/libsysprof-analyze/sysprof-callgraph-private.h @@ -27,12 +27,26 @@ 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); +struct _SysprofCallgraphNode +{ + SysprofCallgraphNode *parent; + SysprofCallgraphNode *prev; + SysprofCallgraphNode *next; + SysprofCallgraphNode *children; + SysprofSymbol *symbol; + gpointer augment; +}; + +void _sysprof_callgraph_new_async (SysprofDocument *document, + GListModel *traceables, + gsize augment_size, + SysprofAugmentationFunc augment_func, + gpointer augment_func_data, + GDestroyNotify augment_func_data_destroy, + 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 index 79918052..a3bf441f 100644 --- a/src/libsysprof-analyze/sysprof-callgraph.c +++ b/src/libsysprof-analyze/sysprof-callgraph.c @@ -21,36 +21,30 @@ #include "config.h" #include "sysprof-callgraph-private.h" -#include "sysprof-callgraph-frame.h" +#include "sysprof-callgraph-frame-private.h" +#include "sysprof-document-private.h" #include "sysprof-document-traceable.h" +#include "sysprof-symbol-private.h" #define MAX_STACK_DEPTH 1024 struct _SysprofCallgraph { - GObject parent_instance; - SysprofDocument *document; - GListModel *traceables; + GObject parent_instance; + + SysprofDocument *document; + GListModel *traceables; + + SysprofSymbol *everything; + + gsize augment_size; + SysprofAugmentationFunc augment_func; + gpointer augment_func_data; + GDestroyNotify augment_func_data_destroy; + + SysprofCallgraphNode root; }; -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) { @@ -60,14 +54,19 @@ sysprof_callgraph_get_item_type (GListModel *model) static guint sysprof_callgraph_get_n_items (GListModel *model) { - return 0; + return 1; } static gpointer sysprof_callgraph_get_item (GListModel *model, guint position) { - return NULL; + SysprofCallgraph *self = SYSPROF_CALLGRAPH (model); + + if (position > 0) + return NULL; + + return _sysprof_callgraph_frame_new (self, &self->root); } static void @@ -81,6 +80,24 @@ list_model_iface_init (GListModelInterface *iface) 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_dispose (GObject *object) +{ + SysprofCallgraph *self = (SysprofCallgraph *)object; + GDestroyNotify notify = self->augment_func_data_destroy; + gpointer notify_data = self->augment_func_data; + + self->augment_size = 0; + self->augment_func = NULL; + self->augment_func_data = NULL; + self->augment_func_data_destroy = NULL; + + if (notify != NULL) + notify (notify_data); + + G_OBJECT_CLASS (sysprof_callgraph_parent_class)->dispose (object); +} + static void sysprof_callgraph_finalize (GObject *object) { @@ -88,6 +105,7 @@ sysprof_callgraph_finalize (GObject *object) g_clear_object (&self->document); g_clear_object (&self->traceables); + g_clear_object (&self->everything); G_OBJECT_CLASS (sysprof_callgraph_parent_class)->finalize (object); } @@ -97,68 +115,105 @@ sysprof_callgraph_class_init (SysprofCallgraphClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); + object_class->dispose = sysprof_callgraph_dispose; object_class->finalize = sysprof_callgraph_finalize; } static void sysprof_callgraph_init (SysprofCallgraph *self) { + self->everything = _sysprof_symbol_new (g_ref_string_new_intern ("[Everything]"), NULL, NULL, 0, 0); + self->root.symbol = self->everything; } -static void -sysprof_callgraph_trace_free (SysprofCallgraphTrace *trace) +static SysprofCallgraphNode * +sysprof_callgraph_add_trace (SysprofCallgraph *self, + SysprofSymbol **symbols, + guint n_symbols) { - g_free (trace); -} + SysprofCallgraphNode *parent; -static void -sysprof_callgraph_add_trace (SysprofCallgraph *self, - SysprofCallgraphTrace *trace) -{ g_assert (SYSPROF_IS_CALLGRAPH (self)); - g_assert (trace != NULL); + g_assert (n_symbols > 2); + g_assert (symbols[n_symbols-1] == self->everything); - sysprof_callgraph_trace_free (trace); + parent = &self->root; + + for (guint i = n_symbols - 1; i > 0; i--) + { + SysprofSymbol *symbol = symbols[i-1]; + SysprofCallgraphNode *node = NULL; + + /* Try to find @symbol within the children of @parent */ + for (SysprofCallgraphNode *iter = parent->children; + iter != NULL; + iter = iter->next) + { + g_assert (iter != NULL); + g_assert (iter->symbol != NULL); + g_assert (symbol != NULL); + + if (_sysprof_symbol_equal (iter->symbol, symbol)) + { + node = iter; + goto next_symbol; + } + } + + /* Otherwise create a new node */ + node = g_new0 (SysprofCallgraphNode, 1); + node->symbol = symbol; + node->parent = parent; + node->next = parent->children; + if (parent->children) + parent->children->prev = node; + parent->children = node; + + next_symbol: + parent = node; + } + + return parent; } static void sysprof_callgraph_add_traceable (SysprofCallgraph *self, - SysprofDocumentTraceable *traceable, - guint model_position) + SysprofDocumentTraceable *traceable) { - SysprofCallgraphTrace *trace; + SysprofCallgraphNode *node; SysprofSymbol **symbols; guint stack_depth; guint n_symbols; + int pid; g_assert (SYSPROF_IS_CALLGRAPH (self)); g_assert (SYSPROF_IS_DOCUMENT_TRACEABLE (traceable)); + pid = sysprof_document_frame_get_pid (SYSPROF_DOCUMENT_FRAME (traceable)); stack_depth = sysprof_document_traceable_get_stack_depth (traceable); - if (stack_depth > MAX_STACK_DEPTH) + if (stack_depth == 0 || stack_depth > MAX_STACK_DEPTH) return; - symbols = g_newa (SysprofSymbol *, stack_depth); + symbols = g_newa (SysprofSymbol *, stack_depth + 2); n_symbols = sysprof_document_symbolize_traceable (self->document, traceable, symbols, stack_depth); - if (n_symbols == 0) - return; + g_assert (n_symbols > 0); + g_assert (n_symbols <= stack_depth); - trace = g_malloc (sizeof *trace + (n_symbols * sizeof (SysprofCallgraphNode))); - trace->model_position = model_position; - trace->n_nodes = n_symbols; + /* We saved 2 extra spaces for these above so that we can + * tack on the "Process" symbol and the "Everything" symbol. + */ + symbols[n_symbols++] = _sysprof_document_process_symbol (self->document, pid); + symbols[n_symbols++] = self->everything; - 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; - } + node = sysprof_callgraph_add_trace (self, symbols, n_symbols); - sysprof_callgraph_add_trace (self, trace); + if (node && self->augment_func) + self->augment_func (self, + node, + SYSPROF_DOCUMENT_FRAME (traceable), + self->augment_func_data); } static void @@ -181,18 +236,22 @@ sysprof_callgraph_new_worker (GTask *task, { g_autoptr(SysprofDocumentTraceable) traceable = g_list_model_get_item (self->traceables, i); - sysprof_callgraph_add_traceable (self, traceable, i); + sysprof_callgraph_add_traceable (self, traceable); } 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) +_sysprof_callgraph_new_async (SysprofDocument *document, + GListModel *traceables, + gsize augment_size, + SysprofAugmentationFunc augment_func, + gpointer augment_func_data, + GDestroyNotify augment_func_data_destroy, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) { g_autoptr(SysprofCallgraph) self = NULL; g_autoptr(GTask) task = NULL; @@ -204,6 +263,10 @@ _sysprof_callgraph_new_async (SysprofDocument *document, self = g_object_new (SYSPROF_TYPE_CALLGRAPH, NULL); self->document = g_object_ref (document); self->traceables = g_object_ref (traceables); + self->augment_size = augment_size; + self->augment_func = augment_func; + self->augment_func_data = augment_func_data; + self->augment_func_data_destroy = augment_func_data_destroy; task = g_task_new (NULL, cancellable, callback, user_data); g_task_set_source_tag (task, _sysprof_callgraph_new_async); @@ -219,3 +282,24 @@ _sysprof_callgraph_new_finish (GAsyncResult *result, return g_task_propagate_pointer (G_TASK (result), error); } + +gpointer +sysprof_callgraph_get_augment (SysprofCallgraph *callgraph, + SysprofCallgraphNode *node) +{ + if (callgraph->augment_size == 0) + return NULL; + else if (callgraph->augment_size <= GLIB_SIZEOF_VOID_P) + return &node->augment; + + if (node->augment == NULL) + node->augment = g_malloc0 (callgraph->augment_size); + + return node->augment; +} + +SysprofCallgraphNode * +sysprof_callgraph_node_parent (SysprofCallgraphNode *node) +{ + return node->parent; +} diff --git a/src/libsysprof-analyze/sysprof-callgraph.h b/src/libsysprof-analyze/sysprof-callgraph.h index 2391758a..5407b62d 100644 --- a/src/libsysprof-analyze/sysprof-callgraph.h +++ b/src/libsysprof-analyze/sysprof-callgraph.h @@ -24,6 +24,8 @@ #include +#include "sysprof-document-frame.h" + G_BEGIN_DECLS #define SYSPROF_TYPE_CALLGRAPH (sysprof_callgraph_get_type()) @@ -31,4 +33,32 @@ G_BEGIN_DECLS SYSPROF_AVAILABLE_IN_ALL G_DECLARE_FINAL_TYPE (SysprofCallgraph, sysprof_callgraph, SYSPROF, CALLGRAPH, GObject) +typedef struct _SysprofCallgraphNode SysprofCallgraphNode; + +/** + * SysprofAugmentationFunc: + * @callgraph: the callgraph being augmented + * @node: the node within the callgraph + * @frame: the frame used to generate this node + * @user_data: closure data for augmentation func + * + * This function is called for the bottom most node in a trace as it is added + * to a callgraph. + * + * The augmentation func should augment the node in whatever way it sees fit + * and generally will want to walk up the node tree to the root to augment the + * parents as it goes. Your augmentation function is not called for each node, + * only the last node. + */ +typedef void (*SysprofAugmentationFunc) (SysprofCallgraph *callgraph, + SysprofCallgraphNode *node, + SysprofDocumentFrame *frame, + gpointer user_data); + +SYSPROF_AVAILABLE_IN_ALL +gpointer sysprof_callgraph_get_augment (SysprofCallgraph *callgraph, + SysprofCallgraphNode *node); +SYSPROF_AVAILABLE_IN_ALL +SysprofCallgraphNode *sysprof_callgraph_node_parent (SysprofCallgraphNode *node); + G_END_DECLS diff --git a/src/libsysprof-analyze/sysprof-document.c b/src/libsysprof-analyze/sysprof-document.c index b30354e0..5a327345 100644 --- a/src/libsysprof-analyze/sysprof-document.c +++ b/src/libsysprof-analyze/sysprof-document.c @@ -1146,19 +1146,35 @@ sysprof_document_callgraph_cb (GObject *object, * sysprof_document_callgraph_async: * @self: a #SysprofDocument * @traceables: a list model of traceables for the callgraph + * @augment_size: the size of data to reserve for augmentation in + * the callgraph. + * @augment_func: (nullable) (scope notified): an optional callback + * to be executed for each node in the callgraph traces to augment + * as the callgraph is generated. + * @augment_func_data: (closure augment_func) (nullable): the closure + * data for @augment_func + * @augment_func_data_destroy: (destroy augment_func) (nullable): the + * destroy callback for @augment_func_data. * @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. + * + * Ideally you want @augment_size to be <= the size of a pointer to + * avoid extra allocations per callgraph node. */ void -sysprof_document_callgraph_async (SysprofDocument *self, - GListModel *traceables, - GCancellable *cancellable, - GAsyncReadyCallback callback, - gpointer user_data) +sysprof_document_callgraph_async (SysprofDocument *self, + GListModel *traceables, + gsize augment_size, + SysprofAugmentationFunc augment_func, + gpointer augment_func_data, + GDestroyNotify augment_func_data_destroy, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) { g_autoptr(GTask) task = NULL; @@ -1171,6 +1187,10 @@ sysprof_document_callgraph_async (SysprofDocument *self, _sysprof_callgraph_new_async (self, traceables, + augment_size, + augment_func, + augment_func_data, + augment_func_data_destroy, cancellable, sysprof_document_callgraph_cb, g_steal_pointer (&task)); diff --git a/src/libsysprof-analyze/sysprof-document.h b/src/libsysprof-analyze/sysprof-document.h index 6413065e..96377270 100644 --- a/src/libsysprof-analyze/sysprof-document.h +++ b/src/libsysprof-analyze/sysprof-document.h @@ -57,11 +57,15 @@ guint sysprof_document_symbolize_traceable (SysprofDocument 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); +void sysprof_document_callgraph_async (SysprofDocument *self, + GListModel *traceables, + gsize augment_size, + SysprofAugmentationFunc augment_func, + gpointer augment_func_data, + GDestroyNotify augment_func_data_destroy, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data); SYSPROF_AVAILABLE_IN_ALL SysprofCallgraph *sysprof_document_callgraph_finish (SysprofDocument *self, GAsyncResult *result, diff --git a/src/libsysprof-analyze/tests/meson.build b/src/libsysprof-analyze/tests/meson.build index 9c39724a..152ba494 100644 --- a/src/libsysprof-analyze/tests/meson.build +++ b/src/libsysprof-analyze/tests/meson.build @@ -12,6 +12,7 @@ libsysprof_analyze_testsuite_c_args = [ ] libsysprof_analyze_testsuite = { + 'test-callgraph' : {'skip': true}, 'test-capture-model' : {'skip': true}, 'test-elf-loader' : {'skip': true}, 'test-list-counters' : {'skip': true}, diff --git a/src/libsysprof-analyze/tests/test-callgraph.c b/src/libsysprof-analyze/tests/test-callgraph.c new file mode 100644 index 00000000..94f4ec79 --- /dev/null +++ b/src/libsysprof-analyze/tests/test-callgraph.c @@ -0,0 +1,154 @@ +/* test-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 + +#include + +typedef struct _Augment +{ + guint32 size; + guint32 total; +} Augment; + +static const GOptionEntry entries[] = { + { 0 } +}; + +static void +print_callgraph (GListModel *model, + guint depth, + guint total) +{ + guint n_items = g_list_model_get_n_items (model); + + for (guint i = 0; i < n_items; i++) + { + g_autoptr(SysprofCallgraphFrame) frame = g_list_model_get_item (model, i); + SysprofSymbol *symbol = sysprof_callgraph_frame_get_symbol (frame); + Augment *aug = sysprof_callgraph_frame_get_augment (frame); + char tstr[16]; + + g_snprintf (tstr, sizeof tstr, "%.2lf%%", 100. * (aug->total / (double)total)); + + g_print (" [%6u] [%8s] ", aug->total, tstr); + for (guint j = 0; j < depth; j++) + g_print (" "); + g_print ("%s\n", sysprof_symbol_get_name (symbol)); + + print_callgraph (G_LIST_MODEL (frame), depth+1, total); + } +} + +static void +callgraph_cb (GObject *object, + GAsyncResult *result, + gpointer user_data) +{ + SysprofDocument *document = SYSPROF_DOCUMENT (object); + GMainLoop *main_loop = user_data; + g_autoptr(GError) error = NULL; + g_autoptr(SysprofCallgraph) callgraph = sysprof_document_callgraph_finish (document, result, &error); + g_autoptr(SysprofCallgraphFrame) root = NULL; + Augment *aug; + + g_assert_no_error (error); + g_assert_true (SYSPROF_IS_CALLGRAPH (callgraph)); + + root = g_list_model_get_item (G_LIST_MODEL (callgraph), 0); + aug = sysprof_callgraph_frame_get_augment (root); + + g_print (" Hits Percent\n"); + print_callgraph (G_LIST_MODEL (callgraph), 0, aug->total); + + g_main_loop_quit (main_loop); +} + +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); + + 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 ("- test callgraph generation"); + g_autoptr(GMainLoop) main_loop = g_main_loop_new (NULL, FALSE); + 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; + + setlocale (LC_ALL, ""); + bindtextdomain (GETTEXT_PACKAGE, LOCALEDIR); + bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8"); + textdomain (GETTEXT_PACKAGE); + + g_option_context_add_main_entries (context, entries, NULL); + if (!g_option_context_parse (context, &argc, &argv, &error)) + g_error ("%s", error->message); + + if (argc < 2) + g_error ("usage: %s CAPTURE_FILE", argv[0]); + + multi = sysprof_multi_symbolizer_new (); + 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 (argv[1]); + sysprof_document_loader_set_symbolizer (loader, SYSPROF_SYMBOLIZER (multi)); + if (!(document = sysprof_document_loader_load (loader, NULL, &error))) + g_error ("Failed to load document: %s", error->message); + + 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; +}