From a7afaf555963d2ac39185e670117968f128e190f Mon Sep 17 00:00:00 2001 From: Georges Basile Stavracas Neto Date: Wed, 26 Mar 2025 14:42:01 -0300 Subject: [PATCH 1/8] libsysprof: move tree.h to contrib/ Soon src/sysprof will use it as well. --- contrib/meson.build | 1 + contrib/tree/meson.build | 7 +++++++ {src/libsysprof => contrib/tree}/tree.h | 0 src/libsysprof/meson.build | 1 + 4 files changed, 9 insertions(+) create mode 100644 contrib/tree/meson.build rename {src/libsysprof => contrib/tree}/tree.h (100%) diff --git a/contrib/meson.build b/contrib/meson.build index 574a013a..63c38a76 100644 --- a/contrib/meson.build +++ b/contrib/meson.build @@ -1,3 +1,4 @@ subdir('eggbitset') subdir('elfparser') subdir('linereader') +subdir('tree') diff --git a/contrib/tree/meson.build b/contrib/tree/meson.build new file mode 100644 index 00000000..05ef08f5 --- /dev/null +++ b/contrib/tree/meson.build @@ -0,0 +1,7 @@ +libtree_deps = [ + gio_dep, +] + +libtree_static_dep = declare_dependency( + include_directories: include_directories('.'), +) diff --git a/src/libsysprof/tree.h b/contrib/tree/tree.h similarity index 100% rename from src/libsysprof/tree.h rename to contrib/tree/tree.h diff --git a/src/libsysprof/meson.build b/src/libsysprof/meson.build index 2ae977ca..e44caf8e 100644 --- a/src/libsysprof/meson.build +++ b/src/libsysprof/meson.build @@ -214,6 +214,7 @@ libsysprof_deps = [ libeggbitset_static_dep, libelfparser_static_dep, liblinereader_static_dep, + libtree_static_dep, libsysprof_capture_dep, ] From d637c5521495c18f9f569f09c441e31be65c1ff1 Mon Sep 17 00:00:00 2001 From: Georges Basile Stavracas Neto Date: Tue, 25 Mar 2025 16:22:10 -0300 Subject: [PATCH 2/8] sysprof: Introduce SysprofMarkFilter It's a GtkFilter implementation that takes a marks catalog, and filters frames based on whether the timestamps of the frames hit any mark in the catalog. Internally this uses an augmented RB tree based on sys/tree.h. The augment is a timestamp interval. This tree is used to store the mark intervals and perform the hit test. This can probably be optimized / improved by building the tree on demand but as is, this code seems to be able to handle dozens of thousands of marks without any visible choke. --- src/sysprof/meson.build | 2 + src/sysprof/sysprof-mark-filter.c | 331 ++++++++++++++++++++++++++++++ src/sysprof/sysprof-mark-filter.h | 38 ++++ 3 files changed, 371 insertions(+) create mode 100644 src/sysprof/sysprof-mark-filter.c create mode 100644 src/sysprof/sysprof-mark-filter.h diff --git a/src/sysprof/meson.build b/src/sysprof/meson.build index 62f3441f..6a7ccf77 100644 --- a/src/sysprof/meson.build +++ b/src/sysprof/meson.build @@ -31,6 +31,7 @@ sysprof_sources = [ 'sysprof-mark-chart-item.c', 'sysprof-mark-chart-row.c', 'sysprof-mark-chart.c', + 'sysprof-mark-filter.c', 'sysprof-mark-table.c', 'sysprof-marks-section.c', 'sysprof-memory-callgraph-view.c', @@ -90,6 +91,7 @@ sysprof_deps = [ dependency('libadwaita-1', version: '>= 1.6.0'), dependency('libpanel-1', version: '>= 1.4'), + libtree_static_dep, libsysprof_static_dep, ] diff --git a/src/sysprof/sysprof-mark-filter.c b/src/sysprof/sysprof-mark-filter.c new file mode 100644 index 00000000..00f99da5 --- /dev/null +++ b/src/sysprof/sysprof-mark-filter.c @@ -0,0 +1,331 @@ +/* + * sysprof-mark-filter.c + * + * Copyright 2025 Georges Basile Stavracas Neto + * + * 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 "sysprof-mark-filter.h" + +typedef struct _SysprofMarkFilterNode SysprofMarkFilterNode; +static void sysprof_mark_filter_node_augment (SysprofMarkFilterNode *node); +#define RB_AUGMENT(elem) (sysprof_mark_filter_node_augment (elem)) + +#include "tree.h" + +struct _SysprofMarkFilterNode +{ + RB_ENTRY (_SysprofMarkFilterNode) link; + SysprofTimeSpan time_span; + gint64 max; +}; + +struct _SysprofMarkFilter +{ + GtkFilter parent_instance; + + SysprofDocument *document; + SysprofMarkCatalog *catalog; + + RB_HEAD (sysprof_mark_filter, _SysprofMarkFilterNode) head; +}; + +G_DEFINE_FINAL_TYPE (SysprofMarkFilter, sysprof_mark_filter, GTK_TYPE_FILTER) + +enum { + PROP_0, + PROP_DOCUMENT, + PROP_CATALOG, + N_PROPS, +}; + +static GParamSpec *properties [N_PROPS]; + +static inline int +sysprof_mark_filter_node_compare (SysprofMarkFilterNode *a, + SysprofMarkFilterNode *b) +{ + if (a->time_span.begin_nsec < b->time_span.begin_nsec) + return -1; + else if (a->time_span.begin_nsec > b->time_span.begin_nsec) + return 1; + else if (a->time_span.end_nsec < b->time_span.end_nsec) + return -1; + else if (a->time_span.end_nsec > b->time_span.end_nsec) + return -1; + else + return 0; +} + +RB_GENERATE_STATIC (sysprof_mark_filter, _SysprofMarkFilterNode, link, sysprof_mark_filter_node_compare); + +static void +sysprof_mark_filter_node_augment (SysprofMarkFilterNode *node) +{ + node->max = node->time_span.end_nsec; + + if (RB_LEFT (node, link) && RB_LEFT (node, link)->max > node->max) + node->max = RB_LEFT (node, link)->max; + + if (RB_RIGHT (node, link) && RB_RIGHT (node, link)->max > node->max) + node->max = RB_RIGHT (node, link)->max; +} + +static void +sysprof_mark_filter_node_finalize (SysprofMarkFilterNode *node) +{ + g_free (node); +} + +static void +sysprof_mark_filter_node_free (SysprofMarkFilterNode *node) +{ + SysprofMarkFilterNode *right = RB_RIGHT (node, link); + SysprofMarkFilterNode *left = RB_LEFT (node, link); + + if (left != NULL) + sysprof_mark_filter_node_free (left); + + sysprof_mark_filter_node_finalize (node); + + if (right != NULL) + sysprof_mark_filter_node_free (right); +} + +static void +populate_intervals (SysprofMarkFilter *self) +{ + unsigned int n_marks; + GListModel *model; + int64_t begin_nsec; + + g_assert (SYSPROF_IS_MARK_FILTER (self)); + g_assert (SYSPROF_IS_MARK_CATALOG (self->catalog)); + g_assert (SYSPROF_IS_DOCUMENT (self->document)); + + begin_nsec = sysprof_document_get_time_span (self->document)->begin_nsec; + + model = G_LIST_MODEL (self->catalog); + n_marks = g_list_model_get_n_items (model); + + for (unsigned int i = 0; i < n_marks; i++) + { + g_autoptr(SysprofDocumentMark) mark = g_list_model_get_item (model, i); + SysprofMarkFilterNode *parent; + SysprofMarkFilterNode *node; + SysprofMarkFilterNode *ret; + SysprofTimeSpan time_span; + + g_assert (SYSPROF_IS_DOCUMENT_MARK (mark)); + + time_span.begin_nsec = sysprof_document_frame_get_time (SYSPROF_DOCUMENT_FRAME (mark)); + time_span.end_nsec = sysprof_document_mark_get_end_time (mark); + + if (sysprof_time_span_duration (time_span) == 0) + continue; + + time_span = sysprof_time_span_relative_to (time_span, begin_nsec); + + node = g_new0 (SysprofMarkFilterNode, 1); + node->time_span = time_span; + node->max = time_span.end_nsec; + + /* If there is a collision, then the node is returned. Otherwise + * if the node was inserted, NULL is returned. + */ + if ((ret = RB_INSERT (sysprof_mark_filter, &self->head, node))) + { + sysprof_mark_filter_node_free (node); + return; + } + + parent = RB_PARENT (node, link); + while (parent != NULL) + { + if (node->max > parent->max) + parent->max = node->max; + node = parent; + parent = RB_PARENT (parent, link); + } + } +} + +static gboolean +sysprof_mark_filter_match (GtkFilter *filter, + gpointer item) +{ + SysprofDocumentFrame *frame = (SysprofDocumentFrame *) item; + SysprofMarkFilter *self = (SysprofMarkFilter *)filter; + SysprofMarkFilterNode *node; + int64_t value; + + g_assert (SYSPROF_IS_MARK_FILTER (self)); + g_assert (SYSPROF_IS_DOCUMENT_FRAME (item)); + + value = sysprof_document_frame_get_time_offset (frame); + node = RB_ROOT (&self->head); + + /* The root node contains our calculated max as augmented in RBTree. + * Therefore, we can know if value falls beyond the upper bound + * in O(1) without having to add a branch to the while loop below. + */ + if (node == NULL || node->max < value) + return FALSE; + + while (node != NULL) + { + g_assert (RB_LEFT (node, link) == NULL || + node->max >= RB_LEFT (node, link)->max); + g_assert (RB_RIGHT (node, link) == NULL || + node->max >= RB_RIGHT (node, link)->max); + + if (value >= node->time_span.begin_nsec && value <= node->time_span.end_nsec) + return TRUE; + + if (RB_LEFT (node, link) && RB_LEFT (node, link)->max >= value) + node = RB_LEFT (node, link); + else + node = RB_RIGHT (node, link); + } + + return FALSE; +} + +static void +sysprof_mark_filter_finalize (GObject *object) +{ + SysprofMarkFilter *self = (SysprofMarkFilter *)object; + SysprofMarkFilterNode *node = RB_ROOT(&self->head); + + g_clear_object (&self->catalog); + + if (node != NULL) + sysprof_mark_filter_node_free (node); + + G_OBJECT_CLASS (sysprof_mark_filter_parent_class)->finalize (object); +} + +static void +sysprof_mark_filter_constructed (GObject *object) +{ + SysprofMarkFilter *self = (SysprofMarkFilter *)object; + + G_OBJECT_CLASS (sysprof_mark_filter_parent_class)->constructed (object); + + populate_intervals (self); +} + +static void +sysprof_mark_filter_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + SysprofMarkFilter *self = SYSPROF_MARK_FILTER (object); + + switch (prop_id) + { + case PROP_DOCUMENT: + g_value_set_object (value, self->document); + break; + + case PROP_CATALOG: + g_value_set_object (value, self->catalog); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +static void +sysprof_mark_filter_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + SysprofMarkFilter *self = SYSPROF_MARK_FILTER (object); + + switch (prop_id) + { + case PROP_CATALOG: + g_assert (self->catalog == NULL); + self->catalog = g_value_dup_object (value); + break; + + case PROP_DOCUMENT: + g_assert (self->document == NULL); + self->document = g_value_dup_object (value); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +static void +sysprof_mark_filter_class_init (SysprofMarkFilterClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GtkFilterClass *filter_class = GTK_FILTER_CLASS (klass); + + object_class->finalize = sysprof_mark_filter_finalize; + object_class->constructed = sysprof_mark_filter_constructed; + object_class->get_property = sysprof_mark_filter_get_property; + object_class->set_property = sysprof_mark_filter_set_property; + + filter_class->match = sysprof_mark_filter_match; + + properties[PROP_CATALOG] = + g_param_spec_object ("catalog", NULL, NULL, + SYSPROF_TYPE_MARK_CATALOG, + (G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS)); + + properties[PROP_DOCUMENT] = + g_param_spec_object ("document", NULL, NULL, + SYSPROF_TYPE_DOCUMENT, + (G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS)); + + g_object_class_install_properties (object_class, N_PROPS, properties); +} + +static void +sysprof_mark_filter_init (SysprofMarkFilter *self) +{ + RB_INIT (&self->head); +} + +SysprofMarkFilter * +sysprof_mark_filter_new (SysprofDocument *document, + SysprofMarkCatalog *catalog) +{ + g_return_val_if_fail (SYSPROF_IS_DOCUMENT (document), NULL); + g_return_val_if_fail (SYSPROF_IS_MARK_CATALOG (catalog), NULL); + + return g_object_new (SYSPROF_TYPE_MARK_FILTER, + "catalog", catalog, + "document", document, + NULL); +} + +SysprofMarkCatalog * +sysprof_mark_filter_get_catalog (SysprofMarkFilter *self) +{ + g_return_val_if_fail (SYSPROF_IS_MARK_FILTER (self), NULL); + + return self->catalog; +} diff --git a/src/sysprof/sysprof-mark-filter.h b/src/sysprof/sysprof-mark-filter.h new file mode 100644 index 00000000..c64364bd --- /dev/null +++ b/src/sysprof/sysprof-mark-filter.h @@ -0,0 +1,38 @@ +/* + * sysprof-mark-filter.h + * + * Copyright 2025 Georges Basile Stavracas Neto + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#pragma once + +#include + +#include "sysprof-session.h" + +G_BEGIN_DECLS + +#define SYSPROF_TYPE_MARK_FILTER (sysprof_mark_filter_get_type()) + +G_DECLARE_FINAL_TYPE (SysprofMarkFilter, sysprof_mark_filter, SYSPROF, MARK_FILTER, GtkFilter) + +SysprofMarkFilter *sysprof_mark_filter_new (SysprofDocument *document, + SysprofMarkCatalog *catalog); +SysprofMarkCatalog *sysprof_mark_filter_get_catalog (SysprofMarkFilter *self); + +G_END_DECLS From c73f4fa40d2f3fa5a30d2e35133dca990c09dd7f Mon Sep 17 00:00:00 2001 From: Georges Basile Stavracas Neto Date: Tue, 25 Mar 2025 16:31:48 -0300 Subject: [PATCH 3/8] sysprof: Apply session filter to sample's scrubber So that the actual filtered samples are properly represented in the time scrubber. This will be useful for filtering by marks. --- src/sysprof/sysprof-samples-section.ui | 29 +++++++++++++++++--------- 1 file changed, 19 insertions(+), 10 deletions(-) diff --git a/src/sysprof/sysprof-samples-section.ui b/src/sysprof/sysprof-samples-section.ui index 7ef6325f..3f3719be 100644 --- a/src/sysprof/sysprof-samples-section.ui +++ b/src/sysprof/sysprof-samples-section.ui @@ -44,19 +44,28 @@ 5000 - - - + + + SysprofSamplesSection - - - - SysprofSamplesSection - - - + + + + + SysprofSamplesSection + + + + + + SysprofSamplesSection + + + + + From fb30dd23004cf347b53ea7f47fbe693c57bf7574 Mon Sep 17 00:00:00 2001 From: Georges Basile Stavracas Neto Date: Tue, 25 Mar 2025 16:39:36 -0300 Subject: [PATCH 4/8] sysprof: Move GtkInscription to SysprofMarkChartRow No functional changes. --- src/sysprof/sysprof-mark-chart-row.ui | 50 ++++++++++++++++++++------- src/sysprof/sysprof-mark-chart.ui | 30 +++------------- 2 files changed, 42 insertions(+), 38 deletions(-) diff --git a/src/sysprof/sysprof-mark-chart-row.ui b/src/sysprof/sysprof-mark-chart-row.ui index f11bd532..b5683569 100644 --- a/src/sysprof/sysprof-mark-chart-row.ui +++ b/src/sysprof/sysprof-mark-chart-row.ui @@ -2,27 +2,51 @@ diff --git a/src/sysprof/sysprof-mark-chart.ui b/src/sysprof/sysprof-mark-chart.ui index 02e3c200..15f6e22e 100644 --- a/src/sysprof/sysprof-mark-chart.ui +++ b/src/sysprof/sysprof-mark-chart.ui @@ -45,31 +45,11 @@ From 02b1d9603b83d99cb1abdb4b6cf04cb6c604cdb6 Mon Sep 17 00:00:00 2001 From: Georges Basile Stavracas Neto Date: Tue, 25 Mar 2025 16:56:18 -0300 Subject: [PATCH 5/8] sysprof: Add stub context menu to SysprofMarkChartRow This will expose the action to set this row's mark catalog as a session filter. --- src/sysprof/sysprof-mark-chart-row.c | 48 +++++++++++++++++++++++++++ src/sysprof/sysprof-mark-chart-row.ui | 18 ++++++++++ 2 files changed, 66 insertions(+) diff --git a/src/sysprof/sysprof-mark-chart-row.c b/src/sysprof/sysprof-mark-chart-row.c index 5d41ad81..e91f3c96 100644 --- a/src/sysprof/sysprof-mark-chart-row.c +++ b/src/sysprof/sysprof-mark-chart-row.c @@ -31,6 +31,9 @@ struct _SysprofMarkChartRow { GtkWidget parent_instance; + GMenuModel *context_menu; + GtkWidget *context_menu_popover; + SysprofMarkChartItem *item; SysprofChart *chart; @@ -79,6 +82,49 @@ sysprof_mark_chart_row_activate_layer_item_cb (SysprofMarkChartRow *self, return FALSE; } +static void +sysprof_mark_chart_row_button_pressed_cb (SysprofMarkChartRow *self, + int n_press, + double x, + double y, + GtkGestureClick *gesture) +{ + GdkEventSequence *current; + GdkEvent *event; + + g_assert (SYSPROF_IS_MARK_CHART_ROW (self)); + g_assert (GTK_IS_GESTURE_CLICK (gesture)); + + current = gtk_gesture_single_get_current_sequence (GTK_GESTURE_SINGLE (gesture)); + event = gtk_gesture_get_last_event (GTK_GESTURE (gesture), current); + + if (!gdk_event_triggers_context_menu (event)) + return; + + if (!self->context_menu_popover) + { + self->context_menu_popover = gtk_popover_menu_new_from_model (self->context_menu); + gtk_popover_set_position (GTK_POPOVER (self->context_menu_popover), GTK_POS_BOTTOM); + gtk_popover_set_has_arrow (GTK_POPOVER (self->context_menu_popover), FALSE); + gtk_widget_set_halign (self->context_menu_popover, GTK_ALIGN_START); + gtk_widget_set_parent (GTK_WIDGET (self->context_menu_popover), GTK_WIDGET (self)); + } + + if (x != -1 && y != -1) + { + GdkRectangle rect = { x, y, 1, 1 }; + gtk_popover_set_pointing_to (GTK_POPOVER (self->context_menu_popover), &rect); + } + else + { + gtk_popover_set_pointing_to (GTK_POPOVER (self->context_menu_popover), NULL); + } + + gtk_popover_popup (GTK_POPOVER (self->context_menu_popover)); + + gtk_gesture_set_state (GTK_GESTURE (gesture), GTK_EVENT_SEQUENCE_CLAIMED); +} + static void sysprof_mark_chart_row_dispose (GObject *object) { @@ -154,8 +200,10 @@ sysprof_mark_chart_row_class_init (SysprofMarkChartRowClass *klass) gtk_widget_class_set_layout_manager_type (widget_class, GTK_TYPE_BIN_LAYOUT); gtk_widget_class_set_template_from_resource (widget_class, "/org/gnome/sysprof/sysprof-mark-chart-row.ui"); gtk_widget_class_bind_template_child (widget_class, SysprofMarkChartRow, chart); + gtk_widget_class_bind_template_child (widget_class, SysprofMarkChartRow, context_menu); gtk_widget_class_bind_template_child (widget_class, SysprofMarkChartRow, layer); gtk_widget_class_bind_template_callback (widget_class, sysprof_mark_chart_row_activate_layer_item_cb); + gtk_widget_class_bind_template_callback (widget_class, sysprof_mark_chart_row_button_pressed_cb); g_type_ensure (SYSPROF_TYPE_CHART); g_type_ensure (SYSPROF_TYPE_TIME_SERIES_ITEM); diff --git a/src/sysprof/sysprof-mark-chart-row.ui b/src/sysprof/sysprof-mark-chart-row.ui index b5683569..3bd54d45 100644 --- a/src/sysprof/sysprof-mark-chart-row.ui +++ b/src/sysprof/sysprof-mark-chart-row.ui @@ -1,6 +1,24 @@