Merge branch 'gbsneto/filter-by-marks' into 'master'

Filter samples by marks

See merge request GNOME/sysprof!129
This commit is contained in:
Christian Hergert
2025-03-26 17:55:54 +00:00
22 changed files with 980 additions and 48 deletions

View File

@ -1,3 +1,4 @@
subdir('eggbitset')
subdir('elfparser')
subdir('linereader')
subdir('tree')

7
contrib/tree/meson.build Normal file
View File

@ -0,0 +1,7 @@
libtree_deps = [
gio_dep,
]
libtree_static_dep = declare_dependency(
include_directories: include_directories('.'),
)

View File

@ -46,6 +46,7 @@ src/sysprof/sysprof-processes-section.ui
src/sysprof/sysprof-recording-pad.c
src/sysprof/sysprof-recording-pad.ui
src/sysprof/sysprof-samples-section.ui
src/sysprof/sysprof-session-filters-widget.ui
src/sysprof/sysprof-sidebar.ui
src/sysprof/sysprof-storage-section.ui
src/sysprof/sysprof-traceables-utility.ui

View File

@ -217,6 +217,7 @@ libsysprof_deps = [
libeggbitset_static_dep,
libelfparser_static_dep,
liblinereader_static_dep,
libtree_static_dep,
libsysprof_capture_dep,
]

View File

@ -0,0 +1,2 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" height="16px" viewBox="0 0 16 16" width="16px"><g fill="#222222"><path d="m 1.042969 0 c -0.8125 0 -1.285157 0.917969 -0.8125 1.582031 l 4.769531 6.738281 v 6.636719 c 0 0.742188 0.78125 1.226563 1.449219 0.894531 l 4 -2 c 0.335937 -0.171874 0.550781 -0.515624 0.550781 -0.894531 v -4.636719 l 4.8125 -6.738281 c 0.476562 -0.664062 0 -1.582031 -0.8125 -1.582031 z m 1.902343 2 h 10.109376 l -3.867188 5.417969 c -0.121094 0.167969 -0.1875 0.375 -0.1875 0.582031 v 4.339844 l -2 1 v -5.339844 c 0 -0.207031 -0.066406 -0.414062 -0.1875 -0.582031 z m 0 0"/><path d="m 3.859375 5 l 2.140625 3 v 7 l 4 -2 v -5 l 2.144531 -3 z m 0 0" fill-opacity="0.34902"/></g></svg>

After

Width:  |  Height:  |  Size: 742 B

View File

@ -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',
@ -51,6 +52,7 @@ sysprof_sources = [
'sysprof-scheduler.c',
'sysprof-section.c',
'sysprof-series.c',
'sysprof-session-filters-widget.c',
'sysprof-session-model-item.c',
'sysprof-session-model.c',
'sysprof-session.c',
@ -90,6 +92,7 @@ sysprof_deps = [
dependency('libadwaita-1', version: '>= 1.6.0'),
dependency('libpanel-1', version: '>= 1.4'),
libtree_static_dep,
libsysprof_static_dep,
]

View File

@ -96,3 +96,17 @@ popover.tasks listview row {
popover.tasks listview row label.heading {
padding-bottom: 3px;
}
sessionfilters row {
padding: 3px 0;
}
sessionfilters row:hover {
background: none;
}
sessionfilters row button {
min-width: 0;
min-height: 0;
padding: 3px;
}

View File

@ -31,6 +31,9 @@ struct _SysprofMarkChartRow
{
GtkWidget parent_instance;
GMenuModel *context_menu;
GtkWidget *context_menu_popover;
SysprofMarkChartItem *item;
SysprofChart *chart;
@ -47,6 +50,27 @@ G_DEFINE_FINAL_TYPE (SysprofMarkChartRow, sysprof_mark_chart_row, GTK_TYPE_WIDGE
static GParamSpec *properties [N_PROPS];
static void
filter_by_mark_action (GtkWidget *widget,
const char *action_name,
GVariant *parameter)
{
SysprofMarkChartRow *self = (SysprofMarkChartRow *)widget;
SysprofMarkCatalog *catalog;
SysprofSession *session;
g_assert (SYSPROF_IS_MARK_CHART_ROW (self));
if (self->item == NULL ||
!(session = sysprof_mark_chart_item_get_session (self->item)))
return;
catalog = sysprof_mark_chart_item_get_catalog (self->item);
g_assert (SYSPROF_IS_MARK_CATALOG (catalog));
sysprof_session_filter_by_mark (session, catalog);
}
static gboolean
sysprof_mark_chart_row_activate_layer_item_cb (SysprofMarkChartRow *self,
SysprofChartLayer *layer,
@ -79,6 +103,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 +221,12 @@ 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);
gtk_widget_class_install_action (widget_class, "markchartrow.filter-by-mark", NULL, filter_by_mark_action);
g_type_ensure (SYSPROF_TYPE_CHART);
g_type_ensure (SYSPROF_TYPE_TIME_SERIES_ITEM);

View File

@ -1,28 +1,70 @@
<?xml version="1.0" encoding="UTF-8"?>
<interface>
<template class="SysprofMarkChartRow" parent="GtkWidget">
<menu id="context_menu">
<section>
<item>
<attribute name="label" translatable="yes">Set as Filter</attribute>
<attribute name="action">markchartrow.filter-by-mark</attribute>
</item>
</section>
</menu>
<child>
<object class="SysprofChart" id="chart">
<signal name="activate-layer-item" handler="sysprof_mark_chart_row_activate_layer_item_cb" swapped="true"/>
<property name="hexpand">true</property>
<property name="vexpand">false</property>
<object class="GtkGestureClick">
<property name="button">0</property>
<property name="exclusive">true</property>
<signal name="pressed" handler="sysprof_mark_chart_row_button_pressed_cb" swapped="yes" />
</object>
</child>
<child>
<object class="GtkBox">
<child>
<object class="SysprofTimeSpanLayer" id="layer">
<binding name="axis">
<lookup name="selected-time-axis" type="SysprofSession">
<lookup name="session" type="SysprofMarkChartItem">
<object class="GtkInscription">
<property name="xalign">1</property>
<property name="nat-chars">30</property>
<property name="min-chars">30</property>
<property name="margin-end">8</property>
<property name="text-overflow">ellipsize-end</property>
<binding name="text">
<lookup name="name" type="SysprofMarkCatalog">
<lookup name="catalog" type="SysprofMarkChartItem">
<lookup name="item">SysprofMarkChartRow</lookup>
</lookup>
</lookup>
</binding>
<binding name="series">
<lookup name="series" type="SysprofMarkChartItem">
<lookup name="item">SysprofMarkChartRow</lookup>
</lookup>
</binding>
</object>
</child>
<child>
<object class="SysprofChart" id="chart">
<signal name="activate-layer-item" handler="sysprof_mark_chart_row_activate_layer_item_cb" swapped="true"/>
<property name="hexpand">true</property>
<property name="vexpand">false</property>
<child>
<object class="SysprofTimeSpanLayer" id="layer">
<binding name="axis">
<lookup name="selected-time-axis" type="SysprofSession">
<lookup name="session" type="SysprofMarkChartItem">
<lookup name="item">SysprofMarkChartRow</lookup>
</lookup>
</lookup>
</binding>
<binding name="series">
<lookup name="series" type="SysprofMarkChartItem">
<lookup name="item">SysprofMarkChartRow</lookup>
</lookup>
</binding>
</object>
</child>
</object>
</child>
</object>
</child>
</template>
</interface>

View File

@ -45,31 +45,11 @@
<interface>
<template class="GtkListItem">
<property name="child">
<object class="GtkBox">
<child>
<object class="GtkInscription">
<property name="xalign">1</property>
<property name="nat-chars">30</property>
<property name="min-chars">30</property>
<property name="margin-end">8</property>
<property name="text-overflow">ellipsize-end</property>
<binding name="text">
<lookup name="name" type="SysprofMarkCatalog">
<lookup name="catalog" type="SysprofMarkChartItem">
<lookup name="item">GtkListItem</lookup>
</lookup>
</lookup>
</binding>
</object>
</child>
<child>
<object class="SysprofMarkChartRow">
<property name="hexpand">true</property>
<binding name="item">
<lookup name="item">GtkListItem</lookup>
</binding>
</object>
</child>
<object class="SysprofMarkChartRow">
<property name="hexpand">true</property>
<binding name="item">
<lookup name="item">GtkListItem</lookup>
</binding>
</object>
</property>
</template>

View File

@ -0,0 +1,331 @@
/*
* sysprof-mark-filter.c
*
* Copyright 2025 Georges Basile Stavracas Neto <feaneron@igalia.com>
*
* 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 <http://www.gnu.org/licenses/>.
*
* 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;
}

View File

@ -0,0 +1,38 @@
/*
* sysprof-mark-filter.h
*
* Copyright 2025 Georges Basile Stavracas Neto <feaneron@igalia.com>
*
* 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 <http://www.gnu.org/licenses/>.
*
* SPDX-License-Identifier: GPL-3.0-or-later
*/
#pragma once
#include <gtk/gtk.h>
#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

View File

@ -44,19 +44,28 @@
<object class="SysprofSampledModel">
<property name="max-items">5000</property>
<property name="model">
<object class="SysprofTimeFilterModel">
<binding name="time-span">
<lookup name="visible-time" type="SysprofSession">
<object class="GtkFilterListModel">
<binding name="filter">
<lookup name="filter">
<lookup name="session">SysprofSamplesSection</lookup>
</lookup>
</binding>
<binding name="model">
<lookup name="samples" type="SysprofDocument">
<lookup name="document" type="SysprofSession">
<lookup name="session">SysprofSamplesSection</lookup>
</lookup>
</lookup>
</binding>
<property name="model">
<object class="SysprofTimeFilterModel">
<binding name="time-span">
<lookup name="visible-time" type="SysprofSession">
<lookup name="session">SysprofSamplesSection</lookup>
</lookup>
</binding>
<binding name="model">
<lookup name="samples" type="SysprofDocument">
<lookup name="document" type="SysprofSession">
<lookup name="session">SysprofSamplesSection</lookup>
</lookup>
</lookup>
</binding>
</object>
</property>
</object>
</property>
</object>

View File

@ -0,0 +1,225 @@
/*
* sysprof-session-filters-widget.c
*
* Copyright 2025 Georges Basile Stavracas Neto <feaneron@igalia.com>
*
* 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 <http://www.gnu.org/licenses/>.
*
* SPDX-License-Identifier: GPL-3.0-or-later
*/
#include "sysprof-mark-filter.h"
#include "sysprof-session-filters-widget.h"
#include "sysprof-session.h"
struct _SysprofSessionFiltersWidget
{
GtkWidget parent_instance;
SysprofSession *session;
};
G_DEFINE_FINAL_TYPE (SysprofSessionFiltersWidget, sysprof_session_filters_widget, GTK_TYPE_WIDGET)
enum {
PROP_0,
PROP_SESSION,
N_PROPS,
};
static GParamSpec *properties [N_PROPS];
static void
sysprof_session_filters_widget_remove_filter (GtkWidget *widget,
const char *action_name,
GVariant *param)
{
SysprofSessionFiltersWidget *self = (SysprofSessionFiltersWidget *) widget;
GtkFilter *filter;
unsigned int position;
g_assert (SYSPROF_IS_SESSION_FILTERS_WIDGET (widget));
g_assert (SYSPROF_IS_SESSION (self->session));
filter = sysprof_session_get_filter (self->session);
g_assert (GTK_IS_MULTI_FILTER (filter));
position = g_variant_get_uint32 (param);
gtk_multi_filter_remove (GTK_MULTI_FILTER (filter), position);
}
static GVariant *
item_to_action_target (gpointer unused,
GtkFilter *filter,
unsigned int position)
{
if (filter)
return g_variant_new_uint32 (position);
return NULL;
}
static char *
filter_to_icon_name (gpointer unused,
GtkFilter *filter)
{
if (!filter)
return g_strdup ("");
if (SYSPROF_IS_MARK_FILTER (filter))
return g_strdup ("mark-chart-symbolic");
return g_strdup ("funnel-outline-symbolic");
}
static char *
filter_to_string (gpointer unused,
GtkFilter *filter)
{
if (!filter)
return g_strdup ("");
if (SYSPROF_IS_MARK_FILTER (filter))
{
SysprofMarkFilter *mark_filter = (SysprofMarkFilter *) filter;
SysprofMarkCatalog *catalog = sysprof_mark_filter_get_catalog (mark_filter);
return g_strdup_printf ("%s / %s",
sysprof_mark_catalog_get_group (catalog),
sysprof_mark_catalog_get_name (catalog));
}
return g_strdup (G_OBJECT_TYPE_NAME (filter));
}
static void
clear_all_filters (SysprofSessionFiltersWidget *self,
GtkButton *button)
{
GtkFilter *filter;
g_assert (SYSPROF_IS_SESSION_FILTERS_WIDGET (self));
g_assert (SYSPROF_IS_SESSION (self->session));
g_assert (GTK_IS_BUTTON (button));
filter = sysprof_session_get_filter (self->session);
g_assert (GTK_IS_MULTI_FILTER (filter));
while (g_list_model_get_n_items (G_LIST_MODEL (filter)) > 0)
gtk_multi_filter_remove (GTK_MULTI_FILTER (filter), 0);
}
static void
sysprof_session_filters_widget_set_session (SysprofSessionFiltersWidget *self,
SysprofSession *session)
{
if (g_set_object (&self->session, session))
g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_SESSION]);
}
static void
sysprof_session_filters_widget_finalize (GObject *object)
{
SysprofSessionFiltersWidget *self = (SysprofSessionFiltersWidget *)object;
g_clear_object (&self->session);
G_OBJECT_CLASS (sysprof_session_filters_widget_parent_class)->finalize (object);
}
static void
sysprof_session_filters_widget_dispose (GObject *object)
{
SysprofSessionFiltersWidget *self = (SysprofSessionFiltersWidget *)object;
gtk_widget_dispose_template (GTK_WIDGET (self), SYSPROF_TYPE_SESSION_FILTERS_WIDGET);
G_OBJECT_CLASS (sysprof_session_filters_widget_parent_class)->dispose (object);
}
static void
sysprof_session_filters_widget_get_property (GObject *object,
guint prop_id,
GValue *value,
GParamSpec *pspec)
{
SysprofSessionFiltersWidget *self = SYSPROF_SESSION_FILTERS_WIDGET (object);
switch (prop_id)
{
case PROP_SESSION:
g_value_set_object (value, self->session);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
}
}
static void
sysprof_session_filters_widget_set_property (GObject *object,
guint prop_id,
const GValue *value,
GParamSpec *pspec)
{
SysprofSessionFiltersWidget *self = SYSPROF_SESSION_FILTERS_WIDGET (object);
switch (prop_id)
{
case PROP_SESSION:
sysprof_session_filters_widget_set_session (self, g_value_get_object (value));
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
}
}
static void
sysprof_session_filters_widget_class_init (SysprofSessionFiltersWidgetClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
object_class->finalize = sysprof_session_filters_widget_finalize;
object_class->dispose = sysprof_session_filters_widget_dispose;
object_class->get_property = sysprof_session_filters_widget_get_property;
object_class->set_property = sysprof_session_filters_widget_set_property;
properties[PROP_SESSION] =
g_param_spec_object ("session", NULL, NULL,
SYSPROF_TYPE_SESSION,
(G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
g_object_class_install_properties (object_class, N_PROPS, properties);
gtk_widget_class_set_template_from_resource (widget_class, "/org/gnome/sysprof/sysprof-session-filters-widget.ui");
gtk_widget_class_set_css_name (widget_class, "sessionfilters");
gtk_widget_class_set_layout_manager_type (widget_class, GTK_TYPE_BIN_LAYOUT);
gtk_widget_class_bind_template_callback (widget_class, clear_all_filters);
gtk_widget_class_bind_template_callback (widget_class, filter_to_icon_name);
gtk_widget_class_bind_template_callback (widget_class, filter_to_string);
gtk_widget_class_bind_template_callback (widget_class, item_to_action_target);
gtk_widget_class_install_action (widget_class, "sessionfilters.remove-filter", "u", sysprof_session_filters_widget_remove_filter);
}
static void
sysprof_session_filters_widget_init (SysprofSessionFiltersWidget *self)
{
gtk_widget_init_template (GTK_WIDGET (self));
}

View File

@ -0,0 +1,32 @@
/*
* sysprof-session-filters-widget.c
*
* Copyright 2025 Georges Basile Stavracas Neto <feaneron@igalia.com>
*
* 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 <http://www.gnu.org/licenses/>.
*
* SPDX-License-Identifier: GPL-3.0-or-later
*/
#pragma once
#include <gtk/gtk.h>
G_BEGIN_DECLS
#define SYSPROF_TYPE_SESSION_FILTERS_WIDGET (sysprof_session_filters_widget_get_type())
G_DECLARE_FINAL_TYPE (SysprofSessionFiltersWidget, sysprof_session_filters_widget, SYSPROF, SESSION_FILTERS_WIDGET, GtkWidget)
G_END_DECLS

View File

@ -0,0 +1,112 @@
<?xml version="1.0" encoding="UTF-8"?>
<interface>
<template class="SysprofSessionFiltersWidget" parent="GtkWidget">
<child>
<object class="GtkBox">
<property name="orientation">vertical</property>
<child>
<object class="GtkScrolledWindow">
<property name="hscrollbar-policy">never</property>
<property name="max-content-height">400</property>
<property name="propagate-natural-height">true</property>
<child>
<object class="GtkListView">
<property name="margin-start">6</property>
<property name="margin-end">6</property>
<property name="margin-top">6</property>
<property name="model">
<object class="GtkNoSelection">
<binding name="model">
<lookup name="filter">
<lookup name="session">SysprofSessionFiltersWidget</lookup>
</lookup>
</binding>
</object>
</property>
<property name="factory">
<object class="GtkBuilderListItemFactory">
<property name="bytes"><![CDATA[
<?xml version="1.0" encoding="UTF-8"?>
<interface>
<template class="GtkListItem">
<property name="activatable">false</property>
<property name="child">
<object class="GtkBox">
<child>
<object class="GtkImage">
<property name="pixel-size">12</property>
<binding name="icon-name">
<closure type="gchararray" function="filter_to_icon_name">
<lookup name="item">GtkListItem</lookup>
</closure>
</binding>
</object>
</child>
<child>
<object class="GtkLabel">
<property name="xalign">0</property>
<property name="hexpand">true</property>
<binding name="label">
<closure type="gchararray" function="filter_to_string">
<lookup name="item">GtkListItem</lookup>
</closure>
</binding>
</object>
</child>
<child>
<object class="GtkButton">
<property name="icon-name">window-close-symbolic</property>
<property name="action-name">sessionfilters.remove-filter</property>
<binding name="action-target">
<closure type="GVariant" function="item_to_action_target">
<lookup name="item">GtkListItem</lookup>
<lookup name="position">GtkListItem</lookup>
</closure>
</binding>
<style>
<class name="flat" />
<class name="circular" />
</style>
</object>
</child>
</object>
</property>
</template>
</interface>
]]>
</property>
</object>
</property>
</object>
</child>
</object>
</child>
<child>
<object class="GtkSeparator" />
</child>
<child>
<object class="GtkButton">
<property name="margin-start">6</property>
<property name="margin-end">6</property>
<property name="margin-bottom">6</property>
<property name="label" translatable="yes">Clear All Filters</property>
<signal name="clicked" handler="clear_all_filters" swapped="yes" />
</object>
</child>
</object>
</child>
</template>
</interface>

View File

@ -22,6 +22,7 @@
#include <glib/gi18n.h>
#include "sysprof-mark-filter.h"
#include "sysprof-session-private.h"
#include "sysprof-value-axis.h"
@ -488,6 +489,24 @@ sysprof_session_zoom_to_selection (SysprofSession *self)
sysprof_session_update_axis (self);
}
void
sysprof_session_filter_by_mark (SysprofSession *self,
SysprofMarkCatalog *catalog)
{
g_return_if_fail (SYSPROF_IS_SESSION (self));
g_return_if_fail (catalog == NULL || SYSPROF_IS_MARK_CATALOG (catalog));
while (g_list_model_get_n_items (G_LIST_MODEL (self->filter)) > 0)
gtk_multi_filter_remove (GTK_MULTI_FILTER (self->filter), 0);
if (catalog)
{
g_autoptr(SysprofMarkFilter) mark_filter = sysprof_mark_filter_new (self->document, catalog);
gtk_multi_filter_append (GTK_MULTI_FILTER (self->filter), GTK_FILTER (g_steal_pointer (&mark_filter)));
}
}
static char *
get_time_str (gint64 o)
{

View File

@ -44,5 +44,7 @@ SysprofAxis *sysprof_session_get_selected_time_axis (SysprofSession
void sysprof_session_select_time (SysprofSession *self,
const SysprofTimeSpan *time_span);
void sysprof_session_zoom_to_selection (SysprofSession *self);
void sysprof_session_filter_by_mark (SysprofSession *self,
SysprofMarkCatalog *catalog);
G_END_DECLS

View File

@ -41,6 +41,7 @@
#include "sysprof-pair.h"
#include "sysprof-processes-section.h"
#include "sysprof-samples-section.h"
#include "sysprof-session-filters-widget.h"
#include "sysprof-sidebar.h"
#include "sysprof-storage-section.h"
#include "sysprof-task-row.h"
@ -328,6 +329,13 @@ main_view_notify_sidebar (SysprofWindow *self,
gtk_widget_set_sensitive (GTK_WIDGET (self->show_right_sidebar), sidebar != NULL);
}
static gboolean
n_filters_to_button_visibility (SysprofWindow *self,
unsigned int n_filters)
{
return n_filters > 0;
}
static void
sysprof_window_session_seek_backward (GtkWidget *widget,
const char *action_name,
@ -617,6 +625,7 @@ sysprof_window_class_init (SysprofWindowClass *klass)
gtk_widget_class_bind_template_child (widget_class, SysprofWindow, stack_title);
gtk_widget_class_bind_template_callback (widget_class, main_view_notify_sidebar);
gtk_widget_class_bind_template_callback (widget_class, n_filters_to_button_visibility);
gtk_widget_class_install_action (widget_class, "win.open-capture", NULL, sysprof_window_open_capture_action);
gtk_widget_class_install_action (widget_class, "win.record-capture", NULL, sysprof_window_record_capture_action);
@ -649,6 +658,7 @@ sysprof_window_class_init (SysprofWindowClass *klass)
g_type_ensure (SYSPROF_TYPE_NETWORK_SECTION);
g_type_ensure (SYSPROF_TYPE_PROCESSES_SECTION);
g_type_ensure (SYSPROF_TYPE_SAMPLES_SECTION);
g_type_ensure (SYSPROF_TYPE_SESSION_FILTERS_WIDGET);
g_type_ensure (SYSPROF_TYPE_STORAGE_SECTION);
g_type_ensure (SYSPROF_TYPE_SESSION);
g_type_ensure (SYSPROF_TYPE_SYMBOL);

View File

@ -126,6 +126,36 @@
<property name="tooltip-text" translatable="yes">Toggle Left Panel</property>
</object>
</child>
<child type="start">
<object class="GtkMenuButton">
<property name="visible">false</property>
<property name="icon-name">funnel-outline-symbolic</property>
<property name="tooltip-text" translatable="yes">View Filters</property>
<property name="popover">
<object class="GtkPopover">
<style>
<class name="menu" />
</style>
<property name="child">
<object class="SysprofSessionFiltersWidget">
<binding name="session">
<lookup name="session">SysprofWindow</lookup>
</binding>
</object>
</property>
</object>
</property>
<binding name="visible">
<closure type="gboolean" function="n_filters_to_button_visibility">
<lookup name="n-items" type="GtkMultiFilter">
<lookup name="filter" type="SysprofSession">
<lookup name="session">SysprofWindow</lookup>
</lookup>
</lookup>
</closure>
</binding>
</object>
</child>
<child type="start">
<object class="GtkBox">
<property name="margin-start">6</property>

View File

@ -8,6 +8,7 @@
<file preprocess="xml-stripblanks">icons/scalable/actions/empty-symbolic.svg</file>
<file preprocess="xml-stripblanks">icons/scalable/actions/energy-symbolic.svg</file>
<file preprocess="xml-stripblanks">icons/scalable/actions/flamegraph-symbolic.svg</file>
<file preprocess="xml-stripblanks">icons/scalable/actions/funnel-outline-symbolic.svg</file>
<file preprocess="xml-stripblanks">icons/scalable/actions/graphics-symbolic.svg</file>
<file preprocess="xml-stripblanks">icons/scalable/actions/mark-chart-symbolic.svg</file>
<file preprocess="xml-stripblanks">icons/scalable/actions/mark-table-symbolic.svg</file>
@ -50,6 +51,7 @@
<file preprocess="xml-stripblanks">sysprof-processes-section.ui</file>
<file preprocess="xml-stripblanks">sysprof-recording-pad.ui</file>
<file preprocess="xml-stripblanks">sysprof-samples-section.ui</file>
<file preprocess="xml-stripblanks">sysprof-session-filters-widget.ui</file>
<file preprocess="xml-stripblanks">sysprof-sidebar.ui</file>
<file preprocess="xml-stripblanks">sysprof-storage-section.ui</file>
<file preprocess="xml-stripblanks">sysprof-task-row.ui</file>