From 2cf6701adf24d01c6352e95825a1ce931aadd340 Mon Sep 17 00:00:00 2001 From: Christian Hergert Date: Wed, 23 Aug 2023 14:07:17 -0700 Subject: [PATCH 01/16] sysprof: add scaffolding for flamegraph --- .../scalable/actions/flamegraph-symbolic.svg | 66 +++++++++ src/sysprof/meson.build | 1 + src/sysprof/sysprof-flame-graph.c | 138 ++++++++++++++++++ src/sysprof/sysprof-flame-graph.h | 38 +++++ src/sysprof/sysprof-samples-section.c | 2 + src/sysprof/sysprof-samples-section.ui | 125 +++++++++------- src/sysprof/sysprof.gresource.xml | 1 + 7 files changed, 322 insertions(+), 49 deletions(-) create mode 100644 src/sysprof/icons/scalable/actions/flamegraph-symbolic.svg create mode 100644 src/sysprof/sysprof-flame-graph.c create mode 100644 src/sysprof/sysprof-flame-graph.h 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/sysprof-flame-graph.c b/src/sysprof/sysprof-flame-graph.c new file mode 100644 index 00000000..a73af9d4 --- /dev/null +++ b/src/sysprof/sysprof-flame-graph.c @@ -0,0 +1,138 @@ +/* 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-flame-graph.h" + +struct _SysprofFlameGraph +{ + GtkWidget parent_instance; + + SysprofCallgraph *callgraph; +}; + +enum { + PROP_0, + PROP_CALLGRAPH, + N_PROPS +}; + +G_DEFINE_FINAL_TYPE (SysprofFlameGraph, sysprof_flame_graph, GTK_TYPE_WIDGET) + +static GParamSpec *properties [N_PROPS]; + +static void +sysprof_flame_graph_dispose (GObject *object) +{ + SysprofFlameGraph *self = (SysprofFlameGraph *)object; + + 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); + + 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; + + 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); +} + +static void +sysprof_flame_graph_init (SysprofFlameGraph *self) +{ +} + +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; +} + +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_object_notify_by_pspec (G_OBJECT (self), properties[PROP_CALLGRAPH]); + gtk_widget_queue_draw (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..50839823 100644 --- a/src/sysprof/sysprof-samples-section.ui +++ b/src/sysprof/sysprof-samples-section.ui @@ -80,64 +80,91 @@ - + 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 + Flame Graph + + + + + + + + + + + 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 From fd618ccb4b7ce8c1342a668e4e23bdfdc7be6ab2 Mon Sep 17 00:00:00 2001 From: Christian Hergert Date: Wed, 23 Aug 2023 14:31:10 -0700 Subject: [PATCH 02/16] callgraph: track max height of callgraph --- src/libsysprof/sysprof-callgraph-private.h | 2 ++ src/libsysprof/sysprof-callgraph.c | 3 +++ 2 files changed, 5 insertions(+) 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..a8ef4736 100644 --- a/src/libsysprof/sysprof-callgraph.c +++ b/src/libsysprof/sysprof-callgraph.c @@ -421,6 +421,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, From b80c8b8b5b99d047d7b598757d5127db2a09a2bc Mon Sep 17 00:00:00 2001 From: Christian Hergert Date: Wed, 23 Aug 2023 14:32:04 -0700 Subject: [PATCH 03/16] libsysprof: always increment count for callgraph nodes This shouldn't affect categorizing because that only uses the value if is_toplevel. But with this added, we can use the count for weights in other tooling w/o needing augmentation. --- src/libsysprof/sysprof-callgraph.c | 3 ++- src/libsysprof/sysprof-descendants-model.c | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/libsysprof/sysprof-callgraph.c b/src/libsysprof/sysprof-callgraph.c index a8ef4736..561c4d6b 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; @@ -431,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, 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, From be2970929f4cb265f92e161b840e0ef3f169109d Mon Sep 17 00:00:00 2001 From: Christian Hergert Date: Wed, 23 Aug 2023 14:32:18 -0700 Subject: [PATCH 04/16] sysprof: bind callgraph to flame graph --- src/sysprof/sysprof-samples-section.ui | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/sysprof/sysprof-samples-section.ui b/src/sysprof/sysprof-samples-section.ui index 50839823..4897be7b 100644 --- a/src/sysprof/sysprof-samples-section.ui +++ b/src/sysprof/sysprof-samples-section.ui @@ -155,6 +155,9 @@ Flame Graph + + callgraph_view + From 9de7199373e48912faabc419b39e5593f956ec78 Mon Sep 17 00:00:00 2001 From: Christian Hergert Date: Wed, 23 Aug 2023 15:25:07 -0700 Subject: [PATCH 05/16] sysprof: add helper to get category color --- src/sysprof/sysprof-category-icon.c | 14 ++++++++++++++ src/sysprof/sysprof-category-icon.h | 2 ++ 2 files changed, 16 insertions(+) diff --git a/src/sysprof/sysprof-category-icon.c b/src/sysprof/sysprof-category-icon.c index 8ee2e5b2..4748426e 100644 --- a/src/sysprof/sysprof-category-icon.c +++ b/src/sysprof/sysprof-category-icon.c @@ -168,3 +168,17 @@ 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) +{ + static GdkRGBA gray; + + if (category < G_N_ELEMENTS (category_colors) && category_colors[category].alpha > 0) + return &category_colors[category]; + + if (gray.alpha == 0) + gdk_rgba_parse (&gray, "#77767b"); + + return &gray; +} 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 From cfc40225c9e9cc4d072036add351f603bd76d118 Mon Sep 17 00:00:00 2001 From: Christian Hergert Date: Wed, 23 Aug 2023 15:25:41 -0700 Subject: [PATCH 06/16] sysprof: add very simple graph generation This is not yet interactive, nor very descriptive of what is contained within it. --- src/sysprof/sysprof-flame-graph.c | 82 +++++++++++++++++++++++++++++++ 1 file changed, 82 insertions(+) diff --git a/src/sysprof/sysprof-flame-graph.c b/src/sysprof/sysprof-flame-graph.c index a73af9d4..8e400e77 100644 --- a/src/sysprof/sysprof-flame-graph.c +++ b/src/sysprof/sysprof-flame-graph.c @@ -20,6 +20,9 @@ #include "config.h" +#include "sysprof-callgraph-private.h" +#include "sysprof-category-icon.h" + #include "sysprof-flame-graph.h" struct _SysprofFlameGraph @@ -39,6 +42,82 @@ G_DEFINE_FINAL_TYPE (SysprofFlameGraph, sysprof_flame_graph, GTK_TYPE_WIDGET) static GParamSpec *properties [N_PROPS]; +static void +sysprof_flame_graph_snapshot_node (GtkSnapshot *snapshot, + SysprofCallgraphNode *node, + const graphene_rect_t *area, + int row_height, + double min_width) +{ + const GdkRGBA *color; + + g_assert (GTK_IS_SNAPSHOT (snapshot)); + g_assert (node != NULL); + g_assert (area != NULL); + + color = sysprof_callgraph_category_get_color (node->category); + + gtk_snapshot_append_color (snapshot, + color, + &GRAPHENE_RECT_INIT (area->origin.x, + area->origin.y + area->size.height - row_height, + area->size.width, + row_height)); + + if (node->children != NULL) + { + graphene_rect_t child_area; + guint64 weight = 0; + + for (SysprofCallgraphNode *child = node->children; child; child = child->next) + weight += child->count; + + child_area.origin.x = area->origin.x; + child_area.origin.y = area->origin.y; + child_area.size.height = area->size.height - row_height; + + for (SysprofCallgraphNode *child = node->children; child; child = child->next) + { + double width = child->count / (double)weight * area->size.width; + + if (width < min_width) + child_area.size.width = min_width; + else + child_area.size.width = width; + + child_area.size.width = area->origin.x + area->size.width - child_area.origin.x; + + sysprof_flame_graph_snapshot_node (snapshot, child, &child_area, row_height, min_width); + + child_area.origin.x += width; + } + } +} + +static void +sysprof_flame_graph_snapshot (GtkWidget *widget, + GtkSnapshot *snapshot) +{ + SysprofFlameGraph *self = (SysprofFlameGraph *)widget; + int row_height; + + g_assert (SYSPROF_IS_FLAME_GRAPH (self)); + g_assert (GTK_IS_SNAPSHOT (snapshot)); + + if (self->callgraph == NULL) + return; + + row_height = MIN (24, gtk_widget_get_height (widget) / (double)self->callgraph->height); + + sysprof_flame_graph_snapshot_node (snapshot, + &self->callgraph->root, + &GRAPHENE_RECT_INIT (0, 0, + gtk_widget_get_width (widget), + gtk_widget_get_height (widget)), + row_height, + 1. / (double)gtk_widget_get_scale_factor (widget)); +} + static void sysprof_flame_graph_dispose (GObject *object) { @@ -91,11 +170,14 @@ 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; + properties[PROP_CALLGRAPH] = g_param_spec_object ("callgraph", NULL, NULL, SYSPROF_TYPE_CALLGRAPH, From 790408f10cfa63d1bf8ef7c7760ee96d60eabc2c Mon Sep 17 00:00:00 2001 From: Christian Hergert Date: Wed, 23 Aug 2023 16:01:00 -0700 Subject: [PATCH 07/16] sysprof: allow caller to check for invisible nodes --- src/sysprof/sysprof-category-icon.c | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/src/sysprof/sysprof-category-icon.c b/src/sysprof/sysprof-category-icon.c index 4748426e..abffeb67 100644 --- a/src/sysprof/sysprof-category-icon.c +++ b/src/sysprof/sysprof-category-icon.c @@ -172,13 +172,8 @@ sysprof_category_icon_set_category (SysprofCategoryIcon *self, const GdkRGBA * sysprof_callgraph_category_get_color (SysprofCallgraphCategory category) { - static GdkRGBA gray; - if (category < G_N_ELEMENTS (category_colors) && category_colors[category].alpha > 0) return &category_colors[category]; - if (gray.alpha == 0) - gdk_rgba_parse (&gray, "#77767b"); - - return &gray; + return NULL; } From f9dabe582a2040aff1e626f86017498d31bb4d44 Mon Sep 17 00:00:00 2001 From: Christian Hergert Date: Wed, 23 Aug 2023 16:01:14 -0700 Subject: [PATCH 08/16] sysprof: some work on graph recursion --- src/sysprof/sysprof-flame-graph.c | 49 +++++++++++++++++++++++-------- 1 file changed, 37 insertions(+), 12 deletions(-) diff --git a/src/sysprof/sysprof-flame-graph.c b/src/sysprof/sysprof-flame-graph.c index 8e400e77..b4c7ce61 100644 --- a/src/sysprof/sysprof-flame-graph.c +++ b/src/sysprof/sysprof-flame-graph.c @@ -22,6 +22,7 @@ #include "sysprof-callgraph-private.h" #include "sysprof-category-icon.h" +#include "sysprof-color-iter-private.h" #include "sysprof-flame-graph.h" @@ -45,17 +46,26 @@ static GParamSpec *properties [N_PROPS]; static void sysprof_flame_graph_snapshot_node (GtkSnapshot *snapshot, SysprofCallgraphNode *node, + guint64 weight, const graphene_rect_t *area, - int row_height, - double min_width) + double row_height, + double min_width, + const GdkRGBA *default_color, + int depth) { const GdkRGBA *color; + GdkRGBA fallback; g_assert (GTK_IS_SNAPSHOT (snapshot)); g_assert (node != NULL); g_assert (area != NULL); - color = sysprof_callgraph_category_get_color (node->category); + if (!(color = sysprof_callgraph_category_get_color (node->category)) || color->alpha == 0) + { + fallback = *default_color; + fallback.alpha -= depth * .005, + color = &fallback; + } gtk_snapshot_append_color (snapshot, color, @@ -67,10 +77,6 @@ sysprof_flame_graph_snapshot_node (GtkSnapshot *snapshot, if (node->children != NULL) { graphene_rect_t child_area; - guint64 weight = 0; - - for (SysprofCallgraphNode *child = node->children; child; child = child->next) - weight += child->count; child_area.origin.x = area->origin.x; child_area.origin.y = area->origin.y; @@ -78,7 +84,10 @@ sysprof_flame_graph_snapshot_node (GtkSnapshot *snapshot, for (SysprofCallgraphNode *child = node->children; child; child = child->next) { - double width = child->count / (double)weight * area->size.width; + double ratio = child->count / (double)weight; + double width; + + width = ratio * area->size.width; if (width < min_width) child_area.size.width = min_width; @@ -87,7 +96,14 @@ sysprof_flame_graph_snapshot_node (GtkSnapshot *snapshot, child_area.size.width = area->origin.x + area->size.width - child_area.origin.x; - sysprof_flame_graph_snapshot_node (snapshot, child, &child_area, row_height, min_width); + sysprof_flame_graph_snapshot_node (snapshot, + child, + weight, + &child_area, + row_height, + min_width, + default_color, + depth + 1); child_area.origin.x += width; } @@ -99,7 +115,9 @@ sysprof_flame_graph_snapshot (GtkWidget *widget, GtkSnapshot *snapshot) { SysprofFlameGraph *self = (SysprofFlameGraph *)widget; - int row_height; + SysprofColorIter iter; + guint64 weight = 0; + double row_height; g_assert (SYSPROF_IS_FLAME_GRAPH (self)); g_assert (GTK_IS_SNAPSHOT (snapshot)); @@ -107,15 +125,22 @@ sysprof_flame_graph_snapshot (GtkWidget *widget, if (self->callgraph == NULL) return; - row_height = MIN (24, gtk_widget_get_height (widget) / (double)self->callgraph->height); + sysprof_color_iter_init (&iter); + row_height = gtk_widget_get_height (widget) / (double)self->callgraph->height; + + for (SysprofCallgraphNode *child = self->callgraph->root.children; child; child = child->next) + weight += child->count; sysprof_flame_graph_snapshot_node (snapshot, &self->callgraph->root, + weight, &GRAPHENE_RECT_INIT (0, 0, gtk_widget_get_width (widget), gtk_widget_get_height (widget)), row_height, - 1. / (double)gtk_widget_get_scale_factor (widget)); + 1. / (double)gtk_widget_get_scale_factor (widget), + sysprof_color_iter_next (&iter), + 0); } static void From 5a5e269e7c1f489be71c88615769811e064f49ae Mon Sep 17 00:00:00 2001 From: Christian Hergert Date: Wed, 23 Aug 2023 16:16:28 -0700 Subject: [PATCH 09/16] sysprof: fix category and weights for drawings --- src/sysprof/sysprof-flame-graph.c | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/src/sysprof/sysprof-flame-graph.c b/src/sysprof/sysprof-flame-graph.c index b4c7ce61..fbf2762a 100644 --- a/src/sysprof/sysprof-flame-graph.c +++ b/src/sysprof/sysprof-flame-graph.c @@ -46,13 +46,13 @@ static GParamSpec *properties [N_PROPS]; static void sysprof_flame_graph_snapshot_node (GtkSnapshot *snapshot, SysprofCallgraphNode *node, - guint64 weight, const graphene_rect_t *area, double row_height, double min_width, const GdkRGBA *default_color, int depth) { + SysprofCallgraphCategory category; const GdkRGBA *color; GdkRGBA fallback; @@ -60,7 +60,9 @@ sysprof_flame_graph_snapshot_node (GtkSnapshot *snapshot, g_assert (node != NULL); g_assert (area != NULL); - if (!(color = sysprof_callgraph_category_get_color (node->category)) || color->alpha == 0) + category = SYSPROF_CALLGRAPH_CATEGORY_UNMASK (node->category); + + if (!(color = sysprof_callgraph_category_get_color (category)) || color->alpha == 0) { fallback = *default_color; fallback.alpha -= depth * .005, @@ -77,11 +79,15 @@ sysprof_flame_graph_snapshot_node (GtkSnapshot *snapshot, 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; + 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; @@ -94,11 +100,8 @@ sysprof_flame_graph_snapshot_node (GtkSnapshot *snapshot, else child_area.size.width = width; - child_area.size.width = area->origin.x + area->size.width - child_area.origin.x; - sysprof_flame_graph_snapshot_node (snapshot, child, - weight, &child_area, row_height, min_width, @@ -116,7 +119,6 @@ sysprof_flame_graph_snapshot (GtkWidget *widget, { SysprofFlameGraph *self = (SysprofFlameGraph *)widget; SysprofColorIter iter; - guint64 weight = 0; double row_height; g_assert (SYSPROF_IS_FLAME_GRAPH (self)); @@ -128,12 +130,8 @@ sysprof_flame_graph_snapshot (GtkWidget *widget, sysprof_color_iter_init (&iter); row_height = gtk_widget_get_height (widget) / (double)self->callgraph->height; - for (SysprofCallgraphNode *child = self->callgraph->root.children; child; child = child->next) - weight += child->count; - sysprof_flame_graph_snapshot_node (snapshot, &self->callgraph->root, - weight, &GRAPHENE_RECT_INIT (0, 0, gtk_widget_get_width (widget), gtk_widget_get_height (widget)), From 4d6f0fe0617e548cfcf811427e7dc416e4912e66 Mon Sep 17 00:00:00 2001 From: Christian Hergert Date: Wed, 23 Aug 2023 16:32:20 -0700 Subject: [PATCH 10/16] sysprof: use Flamegraph for naming That is a closer match to callgraph, and the original name anyway. --- src/sysprof/sysprof-samples-section.ui | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sysprof/sysprof-samples-section.ui b/src/sysprof/sysprof-samples-section.ui index 4897be7b..092d2374 100644 --- a/src/sysprof/sysprof-samples-section.ui +++ b/src/sysprof/sysprof-samples-section.ui @@ -152,7 +152,7 @@ flamegraph-symbolic - Flame Graph + Flamegraph From debd576c2e0e47b409c2e1c1b3958275234331f6 Mon Sep 17 00:00:00 2001 From: Christian Hergert Date: Thu, 24 Aug 2023 11:21:28 -0700 Subject: [PATCH 11/16] sysprof: sort flamegraph alphabetically for X axis This is what flamegraph.pl does, and there is somewhat an expectation that we match that. --- src/sysprof/sysprof-flame-graph.c | 31 ++++++++++++++++++++++++++++--- 1 file changed, 28 insertions(+), 3 deletions(-) diff --git a/src/sysprof/sysprof-flame-graph.c b/src/sysprof/sysprof-flame-graph.c index fbf2762a..e26c3a1f 100644 --- a/src/sysprof/sysprof-flame-graph.c +++ b/src/sysprof/sysprof-flame-graph.c @@ -24,6 +24,8 @@ #include "sysprof-category-icon.h" #include "sysprof-color-iter-private.h" +#include "sysprof-symbol-private.h" + #include "sysprof-flame-graph.h" struct _SysprofFlameGraph @@ -43,6 +45,16 @@ G_DEFINE_FINAL_TYPE (SysprofFlameGraph, sysprof_flame_graph, GTK_TYPE_WIDGET) static GParamSpec *properties [N_PROPS]; +static int +compare_node (gconstpointer a, + gconstpointer b) +{ + const SysprofCallgraphNode *node_a = *(const SysprofCallgraphNode * const *)a; + const SysprofCallgraphNode *node_b = *(const SysprofCallgraphNode * const *)b; + + return strcmp (node_a->summary->symbol->name, node_b->summary->symbol->name); +} + static void sysprof_flame_graph_snapshot_node (GtkSnapshot *snapshot, SysprofCallgraphNode *node, @@ -78,18 +90,31 @@ sysprof_flame_graph_snapshot_node (GtkSnapshot *snapshot, if (node->children != NULL) { + SysprofCallgraphNode **children; graphene_rect_t child_area; guint64 weight = 0; + guint n_children = 0; + guint i = 0; child_area.origin.x = area->origin.x; child_area.origin.y = area->origin.y; child_area.size.height = area->size.height - row_height; - for (SysprofCallgraphNode *child = node->children; child; child = child->next) - weight += child->count; - for (SysprofCallgraphNode *child = node->children; child; child = child->next) { + weight += child->count; + n_children++; + } + + children = g_alloca (sizeof (SysprofCallgraphNode *) * n_children); + for (SysprofCallgraphNode *child = node->children; child; child = child->next) + children[i++] = child; + + qsort (children, n_children, sizeof (SysprofCallgraphNode *), compare_node); + + for (i = 0; i < n_children; i++) + { + SysprofCallgraphNode *child = children[i]; double ratio = child->count / (double)weight; double width; From 7c34f8a6873d85e7c788c5943b0e1cfc0616c244 Mon Sep 17 00:00:00 2001 From: Christian Hergert Date: Thu, 24 Aug 2023 12:00:42 -0700 Subject: [PATCH 12/16] sysprof: fixed row height and text labels This makes things look a bit more like flamegraphs.pl in the sense that we have some labels and separation between rows. Also, use a ScrolledWindow so that we can have much taller graphs to accommodate deep stack traces. We might want to jump to the bottom at some point, but this gets things in place for now. Icicle graphs are another option (invert). --- src/sysprof/style.css | 4 ++ src/sysprof/sysprof-flame-graph.c | 68 ++++++++++++++++++++++---- src/sysprof/sysprof-samples-section.ui | 13 +++-- 3 files changed, 71 insertions(+), 14 deletions(-) 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-flame-graph.c b/src/sysprof/sysprof-flame-graph.c index e26c3a1f..cc1b9b5a 100644 --- a/src/sysprof/sysprof-flame-graph.c +++ b/src/sysprof/sysprof-flame-graph.c @@ -28,6 +28,8 @@ #include "sysprof-flame-graph.h" +#define ROW_HEIGHT 14 + struct _SysprofFlameGraph { GtkWidget parent_instance; @@ -59,9 +61,9 @@ static void sysprof_flame_graph_snapshot_node (GtkSnapshot *snapshot, SysprofCallgraphNode *node, const graphene_rect_t *area, - double row_height, double min_width, const GdkRGBA *default_color, + PangoLayout *layout, int depth) { SysprofCallgraphCategory category; @@ -84,9 +86,20 @@ sysprof_flame_graph_snapshot_node (GtkSnapshot *snapshot, gtk_snapshot_append_color (snapshot, color, &GRAPHENE_RECT_INIT (area->origin.x, - area->origin.y + area->size.height - row_height, + area->origin.y + area->size.height - ROW_HEIGHT - 1, area->size.width, - row_height)); + ROW_HEIGHT)); + + if (area->size.width > ROW_HEIGHT) + { + pango_layout_set_text (layout, node->summary->symbol->name, -1); + pango_layout_set_width (layout, PANGO_SCALE * area->size.width); + + gtk_snapshot_save (snapshot); + gtk_snapshot_translate (snapshot, &GRAPHENE_POINT_INIT (round (area->origin.x), round (area->origin.y) + area->size.height - ROW_HEIGHT)); + gtk_snapshot_append_layout (snapshot, layout, &(GdkRGBA) {1,1,1,1}); + gtk_snapshot_restore (snapshot); + } if (node->children != NULL) { @@ -98,7 +111,7 @@ sysprof_flame_graph_snapshot_node (GtkSnapshot *snapshot, child_area.origin.x = area->origin.x; child_area.origin.y = area->origin.y; - child_area.size.height = area->size.height - row_height; + child_area.size.height = area->size.height - ROW_HEIGHT - 1; for (SysprofCallgraphNode *child = node->children; child; child = child->next) { @@ -128,9 +141,9 @@ sysprof_flame_graph_snapshot_node (GtkSnapshot *snapshot, sysprof_flame_graph_snapshot_node (snapshot, child, &child_area, - row_height, min_width, default_color, + layout, depth + 1); child_area.origin.x += width; @@ -144,7 +157,7 @@ sysprof_flame_graph_snapshot (GtkWidget *widget, { SysprofFlameGraph *self = (SysprofFlameGraph *)widget; SysprofColorIter iter; - double row_height; + PangoLayout *layout; g_assert (SYSPROF_IS_FLAME_GRAPH (self)); g_assert (GTK_IS_SNAPSHOT (snapshot)); @@ -153,17 +166,48 @@ sysprof_flame_graph_snapshot (GtkWidget *widget, return; sysprof_color_iter_init (&iter); - row_height = gtk_widget_get_height (widget) / (double)self->callgraph->height; - + layout = gtk_widget_create_pango_layout (widget, NULL); + pango_layout_set_ellipsize (layout, PANGO_ELLIPSIZE_END); sysprof_flame_graph_snapshot_node (snapshot, &self->callgraph->root, &GRAPHENE_RECT_INIT (0, 0, gtk_widget_get_width (widget), gtk_widget_get_height (widget)), - row_height, 1. / (double)gtk_widget_get_scale_factor (widget), sysprof_color_iter_next (&iter), + layout, 0); + g_object_unref (layout); +} + +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 @@ -225,6 +269,7 @@ sysprof_flame_graph_class_init (SysprofFlameGraphClass *klass) object_class->set_property = sysprof_flame_graph_set_property; widget_class->snapshot = sysprof_flame_graph_snapshot; + widget_class->measure = sysprof_flame_graph_measure; properties[PROP_CALLGRAPH] = g_param_spec_object ("callgraph", NULL, NULL, @@ -232,11 +277,14 @@ sysprof_flame_graph_class_init (SysprofFlameGraphClass *klass) (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) { + gtk_widget_add_css_class (GTK_WIDGET (self), "view"); } GtkWidget * @@ -263,6 +311,6 @@ sysprof_flame_graph_set_callgraph (SysprofFlameGraph *self, if (g_set_object (&self->callgraph, callgraph)) { g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_CALLGRAPH]); - gtk_widget_queue_draw (GTK_WIDGET (self)); + gtk_widget_queue_resize (GTK_WIDGET (self)); } } diff --git a/src/sysprof/sysprof-samples-section.ui b/src/sysprof/sysprof-samples-section.ui index 092d2374..13594888 100644 --- a/src/sysprof/sysprof-samples-section.ui +++ b/src/sysprof/sysprof-samples-section.ui @@ -154,10 +154,15 @@ flamegraph-symbolic Flamegraph - - - callgraph_view - + + never + + + + callgraph_view + + + From ec9d930d58eff0d9d839a39d4535b496dd3369d3 Mon Sep 17 00:00:00 2001 From: Christian Hergert Date: Thu, 24 Aug 2023 13:04:16 -0700 Subject: [PATCH 13/16] sysprof: tweak drawing of flame graph --- src/sysprof/sysprof-flame-graph.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/sysprof/sysprof-flame-graph.c b/src/sysprof/sysprof-flame-graph.c index cc1b9b5a..71904f7f 100644 --- a/src/sysprof/sysprof-flame-graph.c +++ b/src/sysprof/sysprof-flame-graph.c @@ -93,10 +93,12 @@ sysprof_flame_graph_snapshot_node (GtkSnapshot *snapshot, if (area->size.width > ROW_HEIGHT) { pango_layout_set_text (layout, node->summary->symbol->name, -1); - pango_layout_set_width (layout, PANGO_SCALE * area->size.width); + pango_layout_set_width (layout, PANGO_SCALE * area->size.width - 4); gtk_snapshot_save (snapshot); - gtk_snapshot_translate (snapshot, &GRAPHENE_POINT_INIT (round (area->origin.x), round (area->origin.y) + area->size.height - ROW_HEIGHT)); + gtk_snapshot_translate (snapshot, + &GRAPHENE_POINT_INIT (round (area->origin.x) + 2, + area->origin.y + area->size.height - ROW_HEIGHT)); gtk_snapshot_append_layout (snapshot, layout, &(GdkRGBA) {1,1,1,1}); gtk_snapshot_restore (snapshot); } From 507c0e79c23cab31b7c2b99c121c6d8729384a98 Mon Sep 17 00:00:00 2001 From: Christian Hergert Date: Thu, 24 Aug 2023 14:13:17 -0700 Subject: [PATCH 14/16] libsysprof: move callgraph sorting to thread worker --- src/libsysprof/sysprof-callgraph.c | 49 ++++++++++++++++++++++++++++++ src/sysprof/sysprof-flame-graph.c | 25 +-------------- 2 files changed, 50 insertions(+), 24 deletions(-) diff --git a/src/libsysprof/sysprof-callgraph.c b/src/libsysprof/sysprof-callgraph.c index 561c4d6b..94d0c8a4 100644 --- a/src/libsysprof/sysprof-callgraph.c +++ b/src/libsysprof/sysprof-callgraph.c @@ -445,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, @@ -475,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/sysprof/sysprof-flame-graph.c b/src/sysprof/sysprof-flame-graph.c index 71904f7f..c4471df2 100644 --- a/src/sysprof/sysprof-flame-graph.c +++ b/src/sysprof/sysprof-flame-graph.c @@ -47,16 +47,6 @@ G_DEFINE_FINAL_TYPE (SysprofFlameGraph, sysprof_flame_graph, GTK_TYPE_WIDGET) static GParamSpec *properties [N_PROPS]; -static int -compare_node (gconstpointer a, - gconstpointer b) -{ - const SysprofCallgraphNode *node_a = *(const SysprofCallgraphNode * const *)a; - const SysprofCallgraphNode *node_b = *(const SysprofCallgraphNode * const *)b; - - return strcmp (node_a->summary->symbol->name, node_b->summary->symbol->name); -} - static void sysprof_flame_graph_snapshot_node (GtkSnapshot *snapshot, SysprofCallgraphNode *node, @@ -105,31 +95,18 @@ sysprof_flame_graph_snapshot_node (GtkSnapshot *snapshot, if (node->children != NULL) { - SysprofCallgraphNode **children; graphene_rect_t child_area; guint64 weight = 0; - guint n_children = 0; - guint i = 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; - n_children++; - } + weight += child->count; - children = g_alloca (sizeof (SysprofCallgraphNode *) * n_children); for (SysprofCallgraphNode *child = node->children; child; child = child->next) - children[i++] = child; - - qsort (children, n_children, sizeof (SysprofCallgraphNode *), compare_node); - - for (i = 0; i < n_children; i++) { - SysprofCallgraphNode *child = children[i]; double ratio = child->count / (double)weight; double width; From 4a97716f5f189d29b52c16511c341d46a643a623 Mon Sep 17 00:00:00 2001 From: Christian Hergert Date: Thu, 24 Aug 2023 17:05:28 -0700 Subject: [PATCH 15/16] sysprof: generate flamegraph rectangles off thread Then cache the render tree so that we can have highlight nodes without having to use children widgets. --- src/sysprof/sysprof-flame-graph.c | 419 ++++++++++++++++++++++++------ 1 file changed, 334 insertions(+), 85 deletions(-) diff --git a/src/sysprof/sysprof-flame-graph.c b/src/sysprof/sysprof-flame-graph.c index c4471df2..4723192b 100644 --- a/src/sysprof/sysprof-flame-graph.c +++ b/src/sysprof/sysprof-flame-graph.c @@ -30,11 +30,25 @@ #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 { @@ -47,87 +61,55 @@ G_DEFINE_FINAL_TYPE (SysprofFlameGraph, sysprof_flame_graph, GTK_TYPE_WIDGET) static GParamSpec *properties [N_PROPS]; -static void -sysprof_flame_graph_snapshot_node (GtkSnapshot *snapshot, - SysprofCallgraphNode *node, - const graphene_rect_t *area, - double min_width, - const GdkRGBA *default_color, - PangoLayout *layout, - int depth) +typedef struct { - SysprofCallgraphCategory category; - const GdkRGBA *color; - GdkRGBA fallback; + graphene_point_t point; + int width; +} FlameSearch; - g_assert (GTK_IS_SNAPSHOT (snapshot)); - g_assert (node != NULL); - g_assert (area != NULL); +static int +search_compare (gconstpointer key, + gconstpointer item) +{ + const FlameSearch *search = key; + const FlameRectangle *rect = item; + graphene_rect_t area; - category = SYSPROF_CALLGRAPH_CATEGORY_UNMASK (node->category); + area = GRAPHENE_RECT_INIT (rect->x / (double)G_MAXUINT16 * search->width, + rect->y, + rect->w / (double)G_MAXUINT16 * search->width, + rect->h); - if (!(color = sysprof_callgraph_category_get_color (category)) || color->alpha == 0) - { - fallback = *default_color; - fallback.alpha -= depth * .005, - color = &fallback; - } + if (search->point.y < area.origin.y) + return -1; - gtk_snapshot_append_color (snapshot, - color, - &GRAPHENE_RECT_INIT (area->origin.x, - area->origin.y + area->size.height - ROW_HEIGHT - 1, - area->size.width, - ROW_HEIGHT)); + if (search->point.y > area.origin.y + area.size.height) + return 1; - if (area->size.width > ROW_HEIGHT) - { - pango_layout_set_text (layout, node->summary->symbol->name, -1); - pango_layout_set_width (layout, PANGO_SCALE * area->size.width - 4); + if (search->point.x < area.origin.x) + return -1; - gtk_snapshot_save (snapshot); - gtk_snapshot_translate (snapshot, - &GRAPHENE_POINT_INIT (round (area->origin.x) + 2, - area->origin.y + area->size.height - ROW_HEIGHT)); - gtk_snapshot_append_layout (snapshot, layout, &(GdkRGBA) {1,1,1,1}); - gtk_snapshot_restore (snapshot); - } + if (search->point.x > area.origin.x + area.size.width) + return 1; - if (node->children != NULL) - { - graphene_rect_t child_area; - guint64 weight = 0; + return 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; +static FlameRectangle * +find_node_at_coord (SysprofFlameGraph *self, + double x, + double y) +{ + FlameSearch search; - for (SysprofCallgraphNode *child = node->children; child; child = child->next) - weight += child->count; + if (self->nodes == NULL) + return NULL; - for (SysprofCallgraphNode *child = node->children; child; child = child->next) - { - double ratio = child->count / (double)weight; - double width; + search.point.x = x; + search.point.y = y; + search.width = gtk_widget_get_width (GTK_WIDGET (self)); - width = ratio * area->size.width; - - if (width < min_width) - child_area.size.width = min_width; - else - child_area.size.width = width; - - sysprof_flame_graph_snapshot_node (snapshot, - child, - &child_area, - min_width, - default_color, - layout, - depth + 1); - - child_area.origin.x += width; - } - } + return bsearch (&search, self->nodes->data, self->nodes->len, sizeof (FlameRectangle), search_compare); } static void @@ -135,28 +117,90 @@ sysprof_flame_graph_snapshot (GtkWidget *widget, GtkSnapshot *snapshot) { SysprofFlameGraph *self = (SysprofFlameGraph *)widget; - SysprofColorIter iter; - PangoLayout *layout; + FlameRectangle *highlight = NULL; + graphene_point_t point; + int width; g_assert (SYSPROF_IS_FLAME_GRAPH (self)); g_assert (GTK_IS_SNAPSHOT (snapshot)); - if (self->callgraph == NULL) + if (self->nodes == NULL || self->nodes->len == 0) return; - sysprof_color_iter_init (&iter); - layout = gtk_widget_create_pango_layout (widget, NULL); - pango_layout_set_ellipsize (layout, PANGO_ELLIPSIZE_END); - sysprof_flame_graph_snapshot_node (snapshot, - &self->callgraph->root, - &GRAPHENE_RECT_INIT (0, 0, - gtk_widget_get_width (widget), - gtk_widget_get_height (widget)), - 1. / (double)gtk_widget_get_scale_factor (widget), - sysprof_color_iter_next (&iter), - layout, - 0); - g_object_unref (layout); + 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 @@ -189,11 +233,69 @@ sysprof_flame_graph_measure (GtkWidget *widget, } } +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 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); @@ -249,6 +351,7 @@ sysprof_flame_graph_class_init (SysprofFlameGraphClass *klass) widget_class->snapshot = sysprof_flame_graph_snapshot; widget_class->measure = sysprof_flame_graph_measure; + widget_class->size_allocate = sysprof_flame_graph_size_allocate; properties[PROP_CALLGRAPH] = g_param_spec_object ("callgraph", NULL, NULL, @@ -263,7 +366,27 @@ sysprof_flame_graph_class_init (SysprofFlameGraphClass *klass) static void sysprof_flame_graph_init (SysprofFlameGraph *self) { + GtkEventController *motion; + gtk_widget_add_css_class (GTK_WIDGET (self), "view"); + + 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 * @@ -280,6 +403,119 @@ sysprof_flame_graph_get_callgraph (SysprofFlameGraph *self) 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) @@ -289,7 +525,20 @@ sysprof_flame_graph_set_callgraph (SysprofFlameGraph *self, 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)); } } From 9776f1f41b413b59b8ed6f893007ab86562f3134 Mon Sep 17 00:00:00 2001 From: Christian Hergert Date: Thu, 24 Aug 2023 17:17:15 -0700 Subject: [PATCH 16/16] sysprof: add tooltip for flamegraphs --- src/sysprof/sysprof-flame-graph.c | 33 +++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/src/sysprof/sysprof-flame-graph.c b/src/sysprof/sysprof-flame-graph.c index 4723192b..b64a2cae 100644 --- a/src/sysprof/sysprof-flame-graph.c +++ b/src/sysprof/sysprof-flame-graph.c @@ -289,6 +289,37 @@ sysprof_flame_graph_size_allocate (GtkWidget *widget, 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) { @@ -352,6 +383,7 @@ sysprof_flame_graph_class_init (SysprofFlameGraphClass *klass) 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, @@ -369,6 +401,7 @@ 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,