diff --git a/src/libsysprof/sysprof-callgraph-private.h b/src/libsysprof/sysprof-callgraph-private.h index 5e588be9..b80c8283 100644 --- a/src/libsysprof/sysprof-callgraph-private.h +++ b/src/libsysprof/sysprof-callgraph-private.h @@ -71,6 +71,8 @@ struct _SysprofCallgraph GDestroyNotify augment_func_data_destroy; SysprofCallgraphNode root; + + guint height; }; void _sysprof_callgraph_new_async (SysprofDocument *document, diff --git a/src/libsysprof/sysprof-callgraph.c b/src/libsysprof/sysprof-callgraph.c index b7419f46..94d0c8a4 100644 --- a/src/libsysprof/sysprof-callgraph.c +++ b/src/libsysprof/sysprof-callgraph.c @@ -247,6 +247,7 @@ sysprof_callgraph_add_trace (SysprofCallgraph *self, if (_sysprof_symbol_equal (iter->summary->symbol, symbol)) { node = iter; + node->count++; goto next_symbol; } } @@ -256,6 +257,7 @@ sysprof_callgraph_add_trace (SysprofCallgraph *self, node->summary = sysprof_callgraph_get_summary (self, symbol); node->parent = parent; node->next = parent->children; + node->count = 1; if (parent->children) parent->children->prev = node; parent->children = node; @@ -421,6 +423,9 @@ sysprof_callgraph_add_traceable (SysprofCallgraph *self, symbols[n_symbols++] = _sysprof_document_process_symbol (self->document, pid); symbols[n_symbols++] = everything; + if (n_symbols > self->height) + self->height = n_symbols; + node = sysprof_callgraph_add_trace (self, symbols, n_symbols, @@ -428,7 +433,6 @@ sysprof_callgraph_add_traceable (SysprofCallgraph *self, !!(self->flags & SYSPROF_CALLGRAPH_FLAGS_HIDE_SYSTEM_LIBRARIES)); node->is_toplevel = TRUE; - node->count++; if (node && self->augment_func) self->augment_func (self, @@ -441,6 +445,50 @@ sysprof_callgraph_add_traceable (SysprofCallgraph *self, _sysprof_callgraph_categorize (self, node); } +static int +sort_by_symbol_name (gconstpointer a, + gconstpointer b) +{ + const SysprofCallgraphNode *node_a = *(const SysprofCallgraphNode * const *)a; + const SysprofCallgraphNode *node_b = *(const SysprofCallgraphNode * const *)b; + + return g_utf8_collate (node_a->summary->symbol->name, node_b->summary->symbol->name); +} + +static void +sort_children (SysprofCallgraphNode *node) +{ + SysprofCallgraphNode **children; + guint n_children = 0; + guint i = 0; + + if (node->children == NULL) + return; + + for (SysprofCallgraphNode *child = node->children; child; child = child->next) + { + sort_children (child); + n_children++; + } + + children = g_alloca (sizeof (SysprofCallgraphNode *) * (n_children + 1)); + for (SysprofCallgraphNode *child = node->children; child; child = child->next) + children[i++] = child; + children[i] = NULL; + + qsort (children, n_children, sizeof (SysprofCallgraphNode *), sort_by_symbol_name); + + node->children = children[0]; + node->children->prev = NULL; + node->children->next = children[1]; + + for (i = 1; i < n_children; i++) + { + children[i]->next = children[i+1]; + children[i]->prev = children[i-1]; + } +} + static void sysprof_callgraph_new_worker (GTask *task, gpointer source_object, @@ -471,6 +519,11 @@ sysprof_callgraph_new_worker (GTask *task, sysprof_callgraph_add_traceable (self, traceable, i); } + /* Sort callgraph nodes alphabetically so that we can use them in the + * flamegraph without any further processing. + */ + sort_children (&self->root); + g_task_return_pointer (task, g_object_ref (self), g_object_unref); } diff --git a/src/libsysprof/sysprof-descendants-model.c b/src/libsysprof/sysprof-descendants-model.c index c3b37654..a5a5fcc3 100644 --- a/src/libsysprof/sysprof-descendants-model.c +++ b/src/libsysprof/sysprof-descendants-model.c @@ -129,6 +129,7 @@ sysprof_descendants_model_add_trace (SysprofDescendantsModel *self, if (_sysprof_symbol_equal (iter->summary->symbol, symbol)) { node = iter; + node->count++; goto next_symbol; } } @@ -144,6 +145,7 @@ sysprof_descendants_model_add_trace (SysprofDescendantsModel *self, node->summary = summary; node->parent = parent; node->next = parent->children; + node->count = 1; if (parent->children) parent->children->prev = node; parent->children = node; @@ -211,7 +213,6 @@ sysprof_descendants_model_add_traceable (SysprofDescendantsModel *self, node = sysprof_descendants_model_add_trace (self, symbols, n_symbols); node->is_toplevel = TRUE; - node->count++; if (node && self->callgraph->augment_func) self->callgraph->augment_func (self->callgraph, diff --git a/src/sysprof/icons/scalable/actions/flamegraph-symbolic.svg b/src/sysprof/icons/scalable/actions/flamegraph-symbolic.svg new file mode 100644 index 00000000..602e5975 --- /dev/null +++ b/src/sysprof/icons/scalable/actions/flamegraph-symbolic.svg @@ -0,0 +1,66 @@ + + + + + + + + + + + + diff --git a/src/sysprof/meson.build b/src/sysprof/meson.build index a77f0043..7aa8b18e 100644 --- a/src/sysprof/meson.build +++ b/src/sysprof/meson.build @@ -20,6 +20,7 @@ sysprof_sources = [ 'sysprof-energy-section.c', 'sysprof-entry-popover.c', 'sysprof-files-section.c', + 'sysprof-flame-graph.c', 'sysprof-frame-utility.c', 'sysprof-graphics-section.c', 'sysprof-greeter.c', diff --git a/src/sysprof/style.css b/src/sysprof/style.css index 627e0ec0..9ea82c6d 100644 --- a/src/sysprof/style.css +++ b/src/sysprof/style.css @@ -91,3 +91,7 @@ timescrubber timecode { font-feature-settings: 'tnum'; font-size: .8em; } + +flamegraph { + font-size: 10px; +} diff --git a/src/sysprof/sysprof-category-icon.c b/src/sysprof/sysprof-category-icon.c index 8ee2e5b2..abffeb67 100644 --- a/src/sysprof/sysprof-category-icon.c +++ b/src/sysprof/sysprof-category-icon.c @@ -168,3 +168,12 @@ sysprof_category_icon_set_category (SysprofCategoryIcon *self, g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_CATEGORY]); gtk_widget_queue_draw (GTK_WIDGET (self)); } + +const GdkRGBA * +sysprof_callgraph_category_get_color (SysprofCallgraphCategory category) +{ + if (category < G_N_ELEMENTS (category_colors) && category_colors[category].alpha > 0) + return &category_colors[category]; + + return NULL; +} diff --git a/src/sysprof/sysprof-category-icon.h b/src/sysprof/sysprof-category-icon.h index 35263483..583645ae 100644 --- a/src/sysprof/sysprof-category-icon.h +++ b/src/sysprof/sysprof-category-icon.h @@ -34,4 +34,6 @@ SysprofCallgraphCategory sysprof_category_icon_get_category (SysprofCategoryIcon void sysprof_category_icon_set_category (SysprofCategoryIcon *self, SysprofCallgraphCategory category); +const GdkRGBA *sysprof_callgraph_category_get_color (SysprofCallgraphCategory category); + G_END_DECLS diff --git a/src/sysprof/sysprof-flame-graph.c b/src/sysprof/sysprof-flame-graph.c new file mode 100644 index 00000000..b64a2cae --- /dev/null +++ b/src/sysprof/sysprof-flame-graph.c @@ -0,0 +1,577 @@ +/* sysprof-flame-graph.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-category-icon.h" +#include "sysprof-color-iter-private.h" + +#include "sysprof-symbol-private.h" + +#include "sysprof-flame-graph.h" + +#define ROW_HEIGHT 14 + +typedef struct +{ + SysprofCallgraphNode *node; + guint16 x; + guint16 y; + guint16 w; + guint16 h; +} FlameRectangle; + +struct _SysprofFlameGraph +{ + GtkWidget parent_instance; + + SysprofCallgraph *callgraph; + GskRenderNode *rendered; + GArray *nodes; + + double motion_x; + double motion_y; +}; + +enum { + PROP_0, + PROP_CALLGRAPH, + N_PROPS +}; + +G_DEFINE_FINAL_TYPE (SysprofFlameGraph, sysprof_flame_graph, GTK_TYPE_WIDGET) + +static GParamSpec *properties [N_PROPS]; + +typedef struct +{ + graphene_point_t point; + int width; +} FlameSearch; + +static int +search_compare (gconstpointer key, + gconstpointer item) +{ + const FlameSearch *search = key; + const FlameRectangle *rect = item; + graphene_rect_t area; + + area = GRAPHENE_RECT_INIT (rect->x / (double)G_MAXUINT16 * search->width, + rect->y, + rect->w / (double)G_MAXUINT16 * search->width, + rect->h); + + if (search->point.y < area.origin.y) + return -1; + + if (search->point.y > area.origin.y + area.size.height) + return 1; + + if (search->point.x < area.origin.x) + return -1; + + if (search->point.x > area.origin.x + area.size.width) + return 1; + + return 0; +} + +static FlameRectangle * +find_node_at_coord (SysprofFlameGraph *self, + double x, + double y) +{ + FlameSearch search; + + if (self->nodes == NULL) + return NULL; + + search.point.x = x; + search.point.y = y; + search.width = gtk_widget_get_width (GTK_WIDGET (self)); + + return bsearch (&search, self->nodes->data, self->nodes->len, sizeof (FlameRectangle), search_compare); +} + +static void +sysprof_flame_graph_snapshot (GtkWidget *widget, + GtkSnapshot *snapshot) +{ + SysprofFlameGraph *self = (SysprofFlameGraph *)widget; + FlameRectangle *highlight = NULL; + graphene_point_t point; + int width; + + g_assert (SYSPROF_IS_FLAME_GRAPH (self)); + g_assert (GTK_IS_SNAPSHOT (snapshot)); + + if (self->nodes == NULL || self->nodes->len == 0) + return; + + point = GRAPHENE_POINT_INIT (self->motion_x, self->motion_y); + width = gtk_widget_get_width (widget); + + if (self->rendered == NULL) + { + GtkSnapshot *child_snapshot = gtk_snapshot_new (); + const GdkRGBA *default_color; + PangoLayout *layout; + SysprofColorIter iter; + + sysprof_color_iter_init (&iter); + default_color = sysprof_color_iter_next (&iter); + + layout = gtk_widget_create_pango_layout (widget, NULL); + pango_layout_set_ellipsize (layout, PANGO_ELLIPSIZE_END); + + for (guint i = 0; i < self->nodes->len; i++) + { + FlameRectangle *rect = &g_array_index (self->nodes, FlameRectangle, i); + SysprofCallgraphCategory category = SYSPROF_CALLGRAPH_CATEGORY_UNMASK (rect->node->category); + const GdkRGBA *color = sysprof_callgraph_category_get_color (category); + graphene_rect_t area; + + if (color == NULL) + color = default_color; + + area = GRAPHENE_RECT_INIT (rect->x / (double)G_MAXUINT16 * width, + rect->y, + rect->w / (double)G_MAXUINT16 * width, + rect->h); + + if (!highlight && graphene_rect_contains_point (&area, &point)) + highlight = rect; + + gtk_snapshot_append_color (child_snapshot, color, &area); + + if (area.size.width > ROW_HEIGHT) + { + pango_layout_set_text (layout, rect->node->summary->symbol->name, -1); + pango_layout_set_width (layout, PANGO_SCALE * area.size.width - 4); + + gtk_snapshot_save (child_snapshot); + gtk_snapshot_translate (child_snapshot, + &GRAPHENE_POINT_INIT (round (area.origin.x) + 2, + area.origin.y + area.size.height - ROW_HEIGHT)); + gtk_snapshot_append_layout (child_snapshot, layout, &(GdkRGBA) {1,1,1,1}); + gtk_snapshot_restore (child_snapshot); + } + } + + self->rendered = gtk_snapshot_free_to_node (child_snapshot); + + g_object_unref (layout); + } + + if (self->motion_x || self->motion_y) + { + if (highlight == NULL) + highlight = find_node_at_coord (self, self->motion_x, self->motion_y); + } + + gtk_snapshot_append_node (snapshot, self->rendered); + + if (highlight) + { + graphene_rect_t area; + + area = GRAPHENE_RECT_INIT (highlight->x / (double)G_MAXUINT16 * width, + highlight->y, + highlight->w / (double)G_MAXUINT16 * width, + highlight->h); + + gtk_snapshot_append_color (snapshot, &(GdkRGBA) {1,1,1,.25}, &area); + } +} + +static void +sysprof_flame_graph_measure (GtkWidget *widget, + GtkOrientation orientation, + int for_size, + int *minimum, + int *natural, + int *minimum_baseline, + int *natural_baseline) +{ + SysprofFlameGraph *self = (SysprofFlameGraph *)widget; + + g_assert (SYSPROF_IS_FLAME_GRAPH (self)); + + *minimum_baseline = -1; + *natural_baseline = -1; + + if (orientation == GTK_ORIENTATION_VERTICAL) + { + if (self->callgraph != NULL) + *minimum = ROW_HEIGHT * self->callgraph->height + self->callgraph->height + 1; + + *natural = *minimum; + } + else + { + *minimum = 1; + *natural = 500; + } +} + +static void +sysprof_flame_graph_motion_enter_cb (SysprofFlameGraph *self, + double x, + double y, + GtkEventControllerMotion *motion) +{ + g_assert (SYSPROF_IS_FLAME_GRAPH (self)); + g_assert (GTK_IS_EVENT_CONTROLLER_MOTION (motion)); + + self->motion_x = x; + self->motion_y = y; + + gtk_widget_queue_draw (GTK_WIDGET (self)); +} + +static void +sysprof_flame_graph_motion_motion_cb (SysprofFlameGraph *self, + double x, + double y, + GtkEventControllerMotion *motion) +{ + g_assert (SYSPROF_IS_FLAME_GRAPH (self)); + g_assert (GTK_IS_EVENT_CONTROLLER_MOTION (motion)); + + self->motion_x = x; + self->motion_y = y; + + gtk_widget_queue_draw (GTK_WIDGET (self)); +} + +static void +sysprof_flame_graph_motion_leave_cb (SysprofFlameGraph *self, + GtkEventControllerMotion *motion) +{ + g_assert (SYSPROF_IS_FLAME_GRAPH (self)); + g_assert (GTK_IS_EVENT_CONTROLLER_MOTION (motion)); + + self->motion_x = 0; + self->motion_y = 0; + + gtk_widget_queue_draw (GTK_WIDGET (self)); +} + +static void +sysprof_flame_graph_size_allocate (GtkWidget *widget, + int width, + int height, + int baseline) +{ + SysprofFlameGraph *self = (SysprofFlameGraph *)widget; + + g_assert (SYSPROF_IS_FLAME_GRAPH (self)); + + g_clear_pointer (&self->rendered, gsk_render_node_unref); +} + +static gboolean +sysprof_flame_graph_query_tooltip (GtkWidget *widget, + int x, + int y, + gboolean keyboard_tooltip, + GtkTooltip *tooltip) +{ + SysprofFlameGraph *self = SYSPROF_FLAME_GRAPH (widget); + FlameRectangle *rect; + + if ((rect = find_node_at_coord (self, x, y))) + { + g_autoptr(GString) string = g_string_new (NULL); + SysprofSymbol *symbol = rect->node->summary->symbol; + + g_string_append (string, symbol->name); + + if (symbol->binary_nick && symbol->binary_nick[0]) + g_string_append_printf (string, " [%s]", symbol->binary_nick); + + if (symbol->binary_path && symbol->binary_path[0]) + g_string_append_printf (string, "\n%s", symbol->binary_path); + + gtk_tooltip_set_text (tooltip, string->str); + + return TRUE; + } + + return FALSE; +} + +static void +sysprof_flame_graph_dispose (GObject *object) +{ + SysprofFlameGraph *self = (SysprofFlameGraph *)object; + + g_clear_pointer (&self->rendered, gsk_render_node_unref); + g_clear_pointer (&self->nodes, g_array_unref); + g_clear_object (&self->callgraph); + + G_OBJECT_CLASS (sysprof_flame_graph_parent_class)->dispose (object); +} + +static void +sysprof_flame_graph_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + SysprofFlameGraph *self = SYSPROF_FLAME_GRAPH (object); + + switch (prop_id) + { + case PROP_CALLGRAPH: + g_value_set_object (value, sysprof_flame_graph_get_callgraph (self)); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +static void +sysprof_flame_graph_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + SysprofFlameGraph *self = SYSPROF_FLAME_GRAPH (object); + + switch (prop_id) + { + case PROP_CALLGRAPH: + sysprof_flame_graph_set_callgraph (self, g_value_get_object (value)); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +static void +sysprof_flame_graph_class_init (SysprofFlameGraphClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); + + object_class->dispose = sysprof_flame_graph_dispose; + object_class->get_property = sysprof_flame_graph_get_property; + object_class->set_property = sysprof_flame_graph_set_property; + + widget_class->snapshot = sysprof_flame_graph_snapshot; + widget_class->measure = sysprof_flame_graph_measure; + widget_class->size_allocate = sysprof_flame_graph_size_allocate; + widget_class->query_tooltip = sysprof_flame_graph_query_tooltip; + + properties[PROP_CALLGRAPH] = + g_param_spec_object ("callgraph", NULL, NULL, + SYSPROF_TYPE_CALLGRAPH, + (G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS)); + + g_object_class_install_properties (object_class, N_PROPS, properties); + + gtk_widget_class_set_css_name (widget_class, "flamegraph"); +} + +static void +sysprof_flame_graph_init (SysprofFlameGraph *self) +{ + GtkEventController *motion; + + gtk_widget_add_css_class (GTK_WIDGET (self), "view"); + gtk_widget_set_has_tooltip (GTK_WIDGET (self), TRUE); + + motion = gtk_event_controller_motion_new (); + g_signal_connect_object (motion, + "enter", + G_CALLBACK (sysprof_flame_graph_motion_enter_cb), + self, + G_CONNECT_SWAPPED); + g_signal_connect_object (motion, + "leave", + G_CALLBACK (sysprof_flame_graph_motion_leave_cb), + self, + G_CONNECT_SWAPPED); + g_signal_connect_object (motion, + "motion", + G_CALLBACK (sysprof_flame_graph_motion_motion_cb), + self, + G_CONNECT_SWAPPED); + gtk_widget_add_controller (GTK_WIDGET (self), motion); +} + +GtkWidget * +sysprof_flame_graph_new (void) +{ + return g_object_new (SYSPROF_TYPE_FLAME_GRAPH, NULL); +} + +SysprofCallgraph * +sysprof_flame_graph_get_callgraph (SysprofFlameGraph *self) +{ + g_return_val_if_fail (SYSPROF_IS_FLAME_GRAPH (self), NULL); + + return self->callgraph; +} + +static void +sysprof_flame_graph_generate_cb (GObject *object, + GAsyncResult *result, + gpointer user_data) +{ + g_autoptr(SysprofFlameGraph) self = user_data; + g_autoptr(GArray) nodes = NULL; + GTask *task = (GTask *)result; + + g_assert (SYSPROF_IS_FLAME_GRAPH (self)); + g_assert (G_IS_TASK (task)); + + if ((nodes = g_task_propagate_pointer (task, NULL))) + { + g_clear_pointer (&self->nodes, g_array_unref); + self->nodes = g_steal_pointer (&nodes); + gtk_widget_queue_draw (GTK_WIDGET (self)); + } +} + +static void +generate (GArray *array, + SysprofCallgraphNode *node, + const graphene_rect_t *area) +{ + FlameRectangle rect; + + g_assert (array != NULL); + g_assert (node != NULL); + g_assert (area != NULL); + + rect.x = area->origin.x; + rect.y = area->origin.y + area->size.height - ROW_HEIGHT - 1; + rect.w = area->size.width; + rect.h = ROW_HEIGHT; + rect.node = node; + + g_array_append_val (array, rect); + + if (node->children != NULL) + { + graphene_rect_t child_area; + guint64 weight = 0; + + child_area.origin.x = area->origin.x; + child_area.origin.y = area->origin.y; + child_area.size.height = area->size.height - ROW_HEIGHT - 1; + + for (SysprofCallgraphNode *child = node->children; child; child = child->next) + weight += child->count; + + for (SysprofCallgraphNode *child = node->children; child; child = child->next) + { + double ratio = child->count / (double)weight; + double width; + + width = ratio * area->size.width; + + child_area.size.width = width; + + generate (array, child, &child_area); + + child_area.origin.x += width; + } + } +} + +static int +sort_by_coord (gconstpointer a, + gconstpointer b) +{ + const FlameRectangle *rect_a = a; + const FlameRectangle *rect_b = b; + + if (rect_a->y < rect_b->y) + return -1; + + if (rect_a->y > rect_b->y) + return 1; + + if (rect_a->x < rect_b->x) + return -1; + + if (rect_a->x > rect_b->x) + return 1; + + return 0; +} + +static void +sysprof_flame_graph_generate_worker (GTask *task, + gpointer source_object, + gpointer task_data, + GCancellable *cancellable) +{ + SysprofCallgraph *callgraph = task_data; + g_autoptr(GArray) array = NULL; + int height; + + g_assert (SYSPROF_IS_CALLGRAPH (callgraph)); + + array = g_array_sized_new (FALSE, FALSE, sizeof (FlameRectangle), callgraph->root.count); + height = callgraph->height * ROW_HEIGHT + callgraph->height + 1; + + generate (array, + &callgraph->root, + &GRAPHENE_RECT_INIT (0, 0, G_MAXUINT16, height)); + + g_array_sort (array, sort_by_coord); + + g_task_return_pointer (task, g_steal_pointer (&array), (GDestroyNotify)g_array_unref); +} + +void +sysprof_flame_graph_set_callgraph (SysprofFlameGraph *self, + SysprofCallgraph *callgraph) +{ + g_return_if_fail (SYSPROF_IS_FLAME_GRAPH (self)); + g_return_if_fail (!callgraph || SYSPROF_IS_CALLGRAPH (callgraph)); + + if (g_set_object (&self->callgraph, callgraph)) + { + g_clear_pointer (&self->rendered, gsk_render_node_unref); + g_clear_pointer (&self->nodes, g_array_unref); + + if (callgraph != NULL) + { + g_autoptr(GTask) task = NULL; + + task = g_task_new (NULL, NULL, sysprof_flame_graph_generate_cb, g_object_ref (self)); + g_task_set_task_data (task, g_object_ref (callgraph), g_object_unref); + g_task_run_in_thread (task, sysprof_flame_graph_generate_worker); + } + + g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_CALLGRAPH]); + + gtk_widget_queue_resize (GTK_WIDGET (self)); + } +} diff --git a/src/sysprof/sysprof-flame-graph.h b/src/sysprof/sysprof-flame-graph.h new file mode 100644 index 00000000..c8b67477 --- /dev/null +++ b/src/sysprof/sysprof-flame-graph.h @@ -0,0 +1,38 @@ +/* sysprof-flame-graph.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_FLAME_GRAPH (sysprof_flame_graph_get_type()) + +G_DECLARE_FINAL_TYPE (SysprofFlameGraph, sysprof_flame_graph, SYSPROF, FLAME_GRAPH, GtkWidget) + +GtkWidget *sysprof_flame_graph_new (void); +SysprofCallgraph *sysprof_flame_graph_get_callgraph (SysprofFlameGraph *self); +void sysprof_flame_graph_set_callgraph (SysprofFlameGraph *self, + SysprofCallgraph *callgraph); + +G_END_DECLS diff --git a/src/sysprof/sysprof-samples-section.c b/src/sysprof/sysprof-samples-section.c index b4e26c9f..96687b51 100644 --- a/src/sysprof/sysprof-samples-section.c +++ b/src/sysprof/sysprof-samples-section.c @@ -22,6 +22,7 @@ #include "sysprof-chart.h" #include "sysprof-column-layer.h" +#include "sysprof-flame-graph.h" #include "sysprof-sampled-model.h" #include "sysprof-samples-section.h" #include "sysprof-session-model-item.h" @@ -80,6 +81,7 @@ sysprof_samples_section_class_init (SysprofSamplesSectionClass *klass) g_type_ensure (SYSPROF_TYPE_COLUMN_LAYER); g_type_ensure (SYSPROF_TYPE_DOCUMENT_SAMPLE); g_type_ensure (SYSPROF_TYPE_DOCUMENT_TRACEABLE); + g_type_ensure (SYSPROF_TYPE_FLAME_GRAPH); g_type_ensure (SYSPROF_TYPE_SAMPLED_MODEL); g_type_ensure (SYSPROF_TYPE_TIME_FILTER_MODEL); g_type_ensure (SYSPROF_TYPE_TIME_SCRUBBER); diff --git a/src/sysprof/sysprof-samples-section.ui b/src/sysprof/sysprof-samples-section.ui index 7f9e100e..13594888 100644 --- a/src/sysprof/sysprof-samples-section.ui +++ b/src/sysprof/sysprof-samples-section.ui @@ -80,64 +80,99 @@ - + true - - - SysprofSamplesSection - - - - - SysprofSamplesSection - - - - - SysprofSamplesSection - - - - - SysprofSamplesSection - - - - - SysprofSamplesSection - - - - - SysprofSamplesSection - - - - - - - SysprofSamplesSection - - - - - false - - + + + threads-symbolic + Callgraph + + + true + + SysprofSamplesSection - - - - SysprofSamplesSection - + + + SysprofSamplesSection + + + SysprofSamplesSection + + + + + SysprofSamplesSection + + + + + SysprofSamplesSection + + + + + SysprofSamplesSection + + + + + + + SysprofSamplesSection + + + + + false + + + SysprofSamplesSection + + + + + + SysprofSamplesSection + + + + + + + - + + + + flamegraph-symbolic + Flamegraph + + + never + + + + callgraph_view + + + + + + + + + + + + true + stack diff --git a/src/sysprof/sysprof.gresource.xml b/src/sysprof/sysprof.gresource.xml index 2077ada6..ab26820b 100644 --- a/src/sysprof/sysprof.gresource.xml +++ b/src/sysprof/sysprof.gresource.xml @@ -7,6 +7,7 @@ icons/scalable/actions/dbus-symbolic.svg icons/scalable/actions/empty-symbolic.svg icons/scalable/actions/energy-symbolic.svg + icons/scalable/actions/flamegraph-symbolic.svg icons/scalable/actions/graphics-symbolic.svg icons/scalable/actions/mark-chart-symbolic.svg icons/scalable/actions/mark-table-symbolic.svg