diff --git a/src/libsysprof/categories.txt b/src/libsysprof/categories.txt
new file mode 100644
index 00000000..e98e049a
--- /dev/null
+++ b/src/libsysprof/categories.txt
@@ -0,0 +1,64 @@
+EGL:
+egl* paint inherit
+
+
+FontConfig:
+* layout inherit
+
+
+GL:
+gl* paint inherit
+
+
+GLib:
+gtk_main_* main-loop
+gtk_wakeup_* main-loop inherit
+
+
+GTK 4:
+gtk_widget_snapshot paint inherit
+gtk_widget_real_snapshot paint inherit
+gtk_widget_render paint inherit
+gdk_snapshot_* paint inherit
+gtk_snapshot_* paint inherit
+gsk_* paint inherit
+gdk_frame_clock_paint paint inherit
+_gdk_frame_clock_emit_paint paint inherit
+_gdk_frame_clock_emit_after_paint paint inherit
+
+gtk_widget_measure layout inherit
+gtk_css_* layout inherit
+_gdk_frame_clock_emit_layout layout inherit
+gtk_builder_* layout inherit
+gtk_buildable_* layout inherit
+
+gdk_surface_handle_event input inherit
+gtk_im_* input inherit
+do_update_im_spot_location input inherit
+
+gtk_window_present* windowing inherit
+
+gtk_accessible_* a11y inherit
+gtk_at_* a11y inherit
+
+
+Harfbuzz:
+* layout inherit
+
+
+libc:
+poll main-loop inherit
+select main-loop inherit
+epoll main-loop inherit
+
+
+Pango:
+* layout inherit
+
+
+Wayland Client:
+wl_* windowing inherit
+
+
+Wayland Server:
+wl_* windowing inherit
diff --git a/src/libsysprof/libsysprof.gresource.xml b/src/libsysprof/libsysprof.gresource.xml
new file mode 100644
index 00000000..bdd76a46
--- /dev/null
+++ b/src/libsysprof/libsysprof.gresource.xml
@@ -0,0 +1,6 @@
+
+
+
+ categories.txt
+
+
diff --git a/src/libsysprof/meson.build b/src/libsysprof/meson.build
index 8c42c13f..0b55302b 100644
--- a/src/libsysprof/meson.build
+++ b/src/libsysprof/meson.build
@@ -5,6 +5,7 @@ libsysprof_public_sources = [
'sysprof-callgraph-symbol.c',
'sysprof-callgraph.c',
'sysprof-callgraph-categorize.c',
+ 'sysprof-categories.c',
'sysprof-cpu-info.c',
'sysprof-cpu-usage.c',
'sysprof-diagnostic.c',
@@ -157,6 +158,11 @@ libsysprof_enums = gnome.mkenums_simple('sysprof-enums',
install_dir: sysprof_header_dir,
)
+libsysprof_resources = gnome.compile_resources('libsysprof-resources', 'libsysprof.gresource.xml',
+ source_dir: 'resources',
+ c_name: 'libsysprof',
+)
+
libsysprof_deps = [
dependency('gio-2.0', version: glib_req_version),
dependency('gio-unix-2.0',
@@ -179,6 +185,7 @@ libsysprof_static = static_library(
libsysprof_public_sources +
libsysprof_private_sources +
libsysprof_enums,
+ libsysprof_resources,
include_directories: [include_directories('.'),
libsysprof_capture_include_dirs],
diff --git a/src/libsysprof/sysprof-callgraph-categorize.c b/src/libsysprof/sysprof-callgraph-categorize.c
index ed79b86e..207a9815 100644
--- a/src/libsysprof/sysprof-callgraph-categorize.c
+++ b/src/libsysprof/sysprof-callgraph-categorize.c
@@ -21,114 +21,16 @@
#include "config.h"
#include "sysprof-callgraph-private.h"
+#include "sysprof-categories-private.h"
#include "sysprof-symbol-private.h"
-enum {
- RULE_PREFIX = 1,
- RULE_EXACT,
-};
-
-typedef struct _Rule
-{
- guint8 kind : 2;
- SysprofCallgraphCategory category;
- const char *match;
-} Rule;
-
-typedef struct _RuleGroup
-{
- const char *nick;
- const Rule *rules;
-} RuleGroup;
-
-static RuleGroup rule_groups[] = {
- { "EGL",
- (const Rule[]) {
- { RULE_PREFIX, SYSPROF_CALLGRAPH_CATEGORY_PAINT|SYSPROF_CALLGRAPH_CATEGORY_INHERIT, "egl" },
- { 0 }
- }
- },
-
- { "FontConfig",
- (const Rule[]) {
- { RULE_PREFIX, SYSPROF_CALLGRAPH_CATEGORY_LAYOUT|SYSPROF_CALLGRAPH_CATEGORY_INHERIT, "" },
- { 0 }
- }
- },
-
- { "GLib",
- (const Rule[]) {
- { RULE_PREFIX, SYSPROF_CALLGRAPH_CATEGORY_MAIN_LOOP, "g_main_loop_" },
- { RULE_PREFIX, SYSPROF_CALLGRAPH_CATEGORY_MAIN_LOOP, "g_main_context_" },
- { RULE_PREFIX, SYSPROF_CALLGRAPH_CATEGORY_MAIN_LOOP|SYSPROF_CALLGRAPH_CATEGORY_INHERIT, "g_wakeup_" },
- { RULE_EXACT, SYSPROF_CALLGRAPH_CATEGORY_MAIN_LOOP, "g_main_dispatch" },
- { 0 }
- }
- },
-
- { "GTK 4",
- (const Rule[]) {
- { RULE_PREFIX, SYSPROF_CALLGRAPH_CATEGORY_LAYOUT|SYSPROF_CALLGRAPH_CATEGORY_INHERIT, "gtk_css_" },
- { RULE_PREFIX, SYSPROF_CALLGRAPH_CATEGORY_LAYOUT|SYSPROF_CALLGRAPH_CATEGORY_INHERIT, "gtk_widget_measure" },
- { RULE_PREFIX, SYSPROF_CALLGRAPH_CATEGORY_PAINT|SYSPROF_CALLGRAPH_CATEGORY_INHERIT, "gdk_snapshot" },
- { RULE_PREFIX, SYSPROF_CALLGRAPH_CATEGORY_PAINT|SYSPROF_CALLGRAPH_CATEGORY_INHERIT, "gtk_snapshot" },
- { RULE_PREFIX, SYSPROF_CALLGRAPH_CATEGORY_LAYOUT|SYSPROF_CALLGRAPH_CATEGORY_INHERIT, "gtk_widget_reposition" },
- { RULE_PREFIX, SYSPROF_CALLGRAPH_CATEGORY_WINDOWING|SYSPROF_CALLGRAPH_CATEGORY_INHERIT, "gtk_window_present" },
- { RULE_PREFIX, SYSPROF_CALLGRAPH_CATEGORY_ACTIONS|SYSPROF_CALLGRAPH_CATEGORY_INHERIT, "gtk_action_muxer_" },
- { RULE_PREFIX, SYSPROF_CALLGRAPH_CATEGORY_A11Y|SYSPROF_CALLGRAPH_CATEGORY_INHERIT, "gtk_accessible_" },
- { RULE_PREFIX, SYSPROF_CALLGRAPH_CATEGORY_A11Y|SYSPROF_CALLGRAPH_CATEGORY_INHERIT, "gtk_at_" },
- { RULE_PREFIX, SYSPROF_CALLGRAPH_CATEGORY_LAYOUT|SYSPROF_CALLGRAPH_CATEGORY_INHERIT, "gtk_builder_" },
- { RULE_PREFIX, SYSPROF_CALLGRAPH_CATEGORY_LAYOUT|SYSPROF_CALLGRAPH_CATEGORY_INHERIT, "gtk_buildable_" },
- { RULE_PREFIX, SYSPROF_CALLGRAPH_CATEGORY_LAYOUT|SYSPROF_CALLGRAPH_CATEGORY_INHERIT, "gtk_widget_root" },
- { RULE_PREFIX, SYSPROF_CALLGRAPH_CATEGORY_PAINT|SYSPROF_CALLGRAPH_CATEGORY_INHERIT, "gdk_frame_clock_paint" },
- { RULE_PREFIX, SYSPROF_CALLGRAPH_CATEGORY_LAYOUT|SYSPROF_CALLGRAPH_CATEGORY_INHERIT, "_gdk_frame_clock_emit_layout" },
- { RULE_PREFIX, SYSPROF_CALLGRAPH_CATEGORY_PAINT|SYSPROF_CALLGRAPH_CATEGORY_INHERIT, "_gdk_frame_clock_emit_paint" },
- { RULE_PREFIX, SYSPROF_CALLGRAPH_CATEGORY_PAINT|SYSPROF_CALLGRAPH_CATEGORY_INHERIT, "_gdk_frame_clock_emit_after_paint" },
- { RULE_PREFIX, SYSPROF_CALLGRAPH_CATEGORY_INPUT|SYSPROF_CALLGRAPH_CATEGORY_INHERIT, "gdk_surface_handle_event" },
- { RULE_EXACT, SYSPROF_CALLGRAPH_CATEGORY_PAINT|SYSPROF_CALLGRAPH_CATEGORY_INHERIT, "gdk_surface_paint_on_clock" },
- { RULE_EXACT, SYSPROF_CALLGRAPH_CATEGORY_PAINT|SYSPROF_CALLGRAPH_CATEGORY_INHERIT, "gdk_widget_render" },
- { RULE_EXACT, SYSPROF_CALLGRAPH_CATEGORY_PAINT|SYSPROF_CALLGRAPH_CATEGORY_INHERIT, "gdk_widget_real_snapshot" },
- { RULE_PREFIX, SYSPROF_CALLGRAPH_CATEGORY_PAINT|SYSPROF_CALLGRAPH_CATEGORY_INHERIT, "gsk_gl_" },
- { RULE_PREFIX, SYSPROF_CALLGRAPH_CATEGORY_INPUT|SYSPROF_CALLGRAPH_CATEGORY_INHERIT, "gtk_im_" },
- { RULE_EXACT, SYSPROF_CALLGRAPH_CATEGORY_INPUT|SYSPROF_CALLGRAPH_CATEGORY_INHERIT, "do_update_im_spot_location" },
- { 0 }
- }
- },
-
- { "libc",
- (const Rule[]) {
- { RULE_EXACT, SYSPROF_CALLGRAPH_CATEGORY_MAIN_LOOP|SYSPROF_CALLGRAPH_CATEGORY_INHERIT, "poll" },
- { 0 }
- }
- },
-
- { "Pango",
- (const Rule[]) {
- { RULE_PREFIX, SYSPROF_CALLGRAPH_CATEGORY_LAYOUT|SYSPROF_CALLGRAPH_CATEGORY_INHERIT, "" },
- { 0 }
- }
- },
-
- { "Wayland Client",
- (const Rule[]) {
- { RULE_PREFIX, SYSPROF_CALLGRAPH_CATEGORY_WINDOWING|SYSPROF_CALLGRAPH_CATEGORY_INHERIT, "wl_" },
- { 0 }
- }
- },
-
- { "Wayland Server",
- (const Rule[]) {
- { RULE_PREFIX, SYSPROF_CALLGRAPH_CATEGORY_WINDOWING|SYSPROF_CALLGRAPH_CATEGORY_INHERIT, "wl_" },
- { 0 }
- }
- },
-};
-
+static SysprofCategories *categories;
SysprofCallgraphCategory
_sysprof_callgraph_node_categorize (SysprofCallgraphNode *node)
{
SysprofSymbol *symbol;
+ SysprofCallgraphCategory category;
g_return_val_if_fail (node, SYSPROF_CALLGRAPH_CATEGORY_UNCATEGORIZED);
g_return_val_if_fail (node->summary, SYSPROF_CALLGRAPH_CATEGORY_UNCATEGORIZED);
@@ -145,27 +47,13 @@ _sysprof_callgraph_node_categorize (SysprofCallgraphNode *node)
symbol->binary_nick == NULL)
return SYSPROF_CALLGRAPH_CATEGORY_UNCATEGORIZED;
- for (guint i = 0; i < G_N_ELEMENTS (rule_groups); i++)
- {
- if (strcmp (rule_groups[i].nick, symbol->binary_nick) != 0)
- continue;
+ if G_UNLIKELY (categories == NULL)
+ categories = sysprof_categories_new ();
- for (guint j = 0; rule_groups[i].rules[j].kind; j++)
- {
- if (rule_groups[i].rules[j].kind == RULE_PREFIX)
- {
- if (g_str_has_prefix (symbol->name, rule_groups[i].rules[j].match))
- return rule_groups[i].rules[j].category;
- }
- else if (rule_groups[i].rules[j].kind == RULE_EXACT)
- {
- if (strcmp (symbol->name, rule_groups[i].rules[j].match) == 0)
- return rule_groups[i].rules[j].category;
- }
- }
+ category = sysprof_categories_lookup (categories, symbol->binary_nick, symbol->name);
- break;
- }
+ if (category == 0)
+ return SYSPROF_CALLGRAPH_CATEGORY_UNCATEGORIZED;
- return SYSPROF_CALLGRAPH_CATEGORY_UNCATEGORIZED;
+ return category;
}
diff --git a/src/libsysprof/sysprof-categories-private.h b/src/libsysprof/sysprof-categories-private.h
new file mode 100644
index 00000000..c1508bf8
--- /dev/null
+++ b/src/libsysprof/sysprof-categories-private.h
@@ -0,0 +1,35 @@
+/* sysprof-categories-private.h
+ *
+ * Copyright 2023 Christian Hergert
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ *
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+#pragma once
+
+#include "sysprof-callgraph.h"
+
+G_BEGIN_DECLS
+
+typedef struct _SysprofCategories SysprofCategories;
+
+SysprofCategories *sysprof_categories_new (void);
+void sysprof_categories_free (SysprofCategories *categories);
+SysprofCallgraphCategory sysprof_categories_lookup (SysprofCategories *categories,
+ const char *binary_nick,
+ const char *symbol);
+
+G_END_DECLS
diff --git a/src/libsysprof/sysprof-categories.c b/src/libsysprof/sysprof-categories.c
new file mode 100644
index 00000000..8f597105
--- /dev/null
+++ b/src/libsysprof/sysprof-categories.c
@@ -0,0 +1,279 @@
+/* sysprof-categories.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 "libsysprof-resources.h"
+
+#include "sysprof-callgraph-private.h"
+#include "sysprof-categories-private.h"
+#include "sysprof-enums.h"
+
+#include "line-reader-private.h"
+
+struct _SysprofCategories
+{
+ GHashTable *binary_nick_to_rules;
+};
+
+enum {
+ MATCH_EXACT,
+ MATCH_PREFIX,
+ MATCH_SUFFIX,
+};
+
+typedef struct _Rule
+{
+ guint8 kind : 2;
+ guint8 inherit : 1;
+ guint8 category : 5;
+ char *match;
+} Rule;
+
+static SysprofCallgraphCategory
+parse_category (const char *category)
+{
+ static GEnumClass *enum_class;
+ const GEnumValue *value;
+
+ if (enum_class == NULL)
+ enum_class = g_type_class_ref (SYSPROF_TYPE_CALLGRAPH_CATEGORY);
+
+ if (!(value = g_enum_get_value_by_nick (enum_class, category)))
+ return 0;
+
+ return value->value;
+}
+
+static void
+clear_rule (gpointer element)
+{
+ Rule *rule = element;
+
+ g_clear_pointer (&rule->match, g_free);
+}
+
+static void
+sysprof_categories_add_rule (SysprofCategories *categories,
+ const char *binary_nick,
+ const char *match,
+ SysprofCallgraphCategory category,
+ gboolean inherit)
+{
+ Rule rule;
+ GArray *ar;
+
+ g_assert (categories != NULL);
+ g_assert (binary_nick != NULL);
+ g_assert (match != NULL);
+
+ if (match[0] == 0)
+ return;
+
+ if G_UNLIKELY (!(ar = g_hash_table_lookup (categories->binary_nick_to_rules, binary_nick)))
+ {
+ ar = g_array_new (FALSE, FALSE, sizeof (Rule));
+ g_array_set_clear_func (ar, clear_rule);
+ g_hash_table_insert (categories->binary_nick_to_rules,
+ g_strdup (binary_nick),
+ ar);
+ }
+
+ if (match[0] == '*')
+ {
+ rule.kind = MATCH_SUFFIX;
+ rule.match = g_strdup (&match[1]);
+ rule.category = category;
+ rule.inherit = inherit;
+
+ g_array_append_val (ar, rule);
+ }
+ else if (match[strlen(match)-1] == '*')
+ {
+ rule.kind = MATCH_PREFIX;
+ rule.match = g_strndup (match, strlen (match)-1);
+ rule.category = category;
+ rule.inherit = inherit;
+
+ g_array_append_val (ar, rule);
+ }
+ else
+ {
+ rule.kind = MATCH_EXACT;
+ rule.match = g_strdup (match);
+ rule.category = category;
+ rule.inherit = inherit;
+
+ g_array_append_val (ar, rule);
+ }
+}
+
+SysprofCategories *
+sysprof_categories_new (void)
+{
+ SysprofCategories *categories;
+ g_autoptr(GBytes) bytes = NULL;
+ g_autofree char *binary_nick = NULL;
+ const char *str;
+ const char *lineptr;
+ gsize line_len;
+ gsize len;
+ guint lineno = 0;
+ LineReader reader;
+
+ g_resources_register (libsysprof_get_resource ());
+
+ if (!(bytes = g_resources_lookup_data ("/org/gnome/libsysprof/categories.txt", 0, NULL)))
+ return NULL;
+
+ if (!(str = (const char *)g_bytes_get_data (bytes, &len)))
+ return NULL;
+
+ line_reader_init (&reader, (char *)str, len);
+
+ categories = g_new0 (SysprofCategories, 1);
+ categories->binary_nick_to_rules = g_hash_table_new_full (g_str_hash,
+ g_str_equal,
+ g_free,
+ (GDestroyNotify)g_array_unref);
+
+ while ((lineptr = line_reader_next (&reader, &line_len)))
+ {
+ g_autofree char *line = g_strndup (lineptr, line_len);
+ SysprofCallgraphCategory category_value;
+ g_auto(GStrv) parts = NULL;
+ const char *match = NULL;
+ const char *category = NULL;
+ const char *inherit = NULL;
+
+ lineno++;
+ g_strstrip (line);
+
+ if (line[0] == 0)
+ continue;
+
+ /* Group lines look like "binary nick:\n" */
+ if (g_str_has_suffix (line, ":"))
+ {
+ line[line_len-1] = 0;
+ g_set_str (&binary_nick, g_strstrip (line));
+ continue;
+ }
+
+ parts = g_strsplit (line, " ", 0);
+
+ for (guint i = 0; parts[i]; i++)
+ {
+ g_strstrip (parts[i]);
+
+ if (parts[i][0] == 0)
+ continue;
+
+ if (match == NULL)
+ {
+ match = parts[i];
+ continue;
+ }
+
+ if (category == NULL)
+ {
+ category = parts[i];
+ continue;
+ }
+
+ if (inherit == NULL)
+ {
+ inherit = parts[i];
+ continue;
+ }
+
+ break;
+ }
+
+ if (match == NULL || category == NULL)
+ {
+ g_warning ("categories.txt: line: %d: Incomplete rule", lineno);
+ continue;
+ }
+
+ if (inherit && !g_str_equal (inherit, "inherit"))
+ {
+ g_warning ("categories.txt line %d: malformated inherit", lineno);
+ continue;
+ }
+
+ if (!(category_value = parse_category (category)))
+ {
+ g_warning ("categories.txt line %d: malformated category", lineno);
+ continue;
+ }
+
+ sysprof_categories_add_rule (categories, binary_nick, match, category_value, !!inherit);
+ }
+
+ return categories;
+}
+
+void
+sysprof_categories_free (SysprofCategories *categories)
+{
+ if (categories != NULL)
+ {
+ g_clear_pointer (&categories->binary_nick_to_rules, g_hash_table_unref);
+ g_free (categories);
+ }
+}
+
+SysprofCallgraphCategory
+sysprof_categories_lookup (SysprofCategories *categories,
+ const char *binary_nick,
+ const char *symbol)
+{
+ GArray *rules;
+
+ if (binary_nick == NULL || symbol == NULL)
+ return 0;
+
+ if (!(rules = g_hash_table_lookup (categories->binary_nick_to_rules, binary_nick)))
+ return 0;
+
+ for (guint i = 0; i < rules->len; i++)
+ {
+ const Rule *rule = &g_array_index (rules, Rule, i);
+ gboolean ret = FALSE;
+
+ if (rule->kind == MATCH_EXACT)
+ ret = strcmp (rule->match, symbol) == 0;
+ else if (rule->kind == MATCH_PREFIX)
+ ret = g_str_has_prefix (symbol, rule->match);
+ else if (rule->kind == MATCH_SUFFIX)
+ ret = g_str_has_suffix (symbol, rule->match);
+
+ if (ret)
+ {
+ if (rule->inherit)
+ return rule->category | SYSPROF_CALLGRAPH_CATEGORY_INHERIT;
+
+ return rule->category;
+ }
+ }
+
+ return 0;
+}