From 7c93149315180cb155be3a5f5d1595bc85618b96 Mon Sep 17 00:00:00 2001 From: Georges Basile Stavracas Neto Date: Wed, 25 Sep 2024 15:27:10 -0300 Subject: [PATCH] Introduce sysprof-cat It's a simple CLI tool that dumps the contents of a Sysprof capture file in a syntax inspired by GTK's render node syntax. It prints metadata, the callgraph, marks, and counters. Other fields may be added as needed, but for now this is sufficient for inspecting WebKit captures. There are CLI args for not printing particular groups, so that we can filter out what we need. --- src/meson.build | 1 + src/sysprof-cat/meson.build | 9 + src/sysprof-cat/sysprof-cat.c | 482 ++++++++++++++++++++++++++++++++++ 3 files changed, 492 insertions(+) create mode 100644 src/sysprof-cat/meson.build create mode 100644 src/sysprof-cat/sysprof-cat.c diff --git a/src/meson.build b/src/meson.build index f3e0c61d..e221636a 100644 --- a/src/meson.build +++ b/src/meson.build @@ -28,6 +28,7 @@ endif if get_option('tools') subdir('sysprof-agent') + subdir('sysprof-cat') subdir('sysprof-cli') subdir('sysprof-diff') endif diff --git a/src/sysprof-cat/meson.build b/src/sysprof-cat/meson.build new file mode 100644 index 00000000..818d9cbb --- /dev/null +++ b/src/sysprof-cat/meson.build @@ -0,0 +1,9 @@ +sysprof_cat_deps = [ + libsysprof_static_dep, +] + +sysprof_cat = executable('sysprof-cat', 'sysprof-cat.c', + dependencies: sysprof_cat_deps, + c_args: release_flags, + install: true, +) diff --git a/src/sysprof-cat/sysprof-cat.c b/src/sysprof-cat/sysprof-cat.c new file mode 100644 index 00000000..1c6eb710 --- /dev/null +++ b/src/sysprof-cat/sysprof-cat.c @@ -0,0 +1,482 @@ +/* sysprof-cat.c + * + * Copyright 2024 Igalia S.L. + * + * 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 . + * + * Authors: Georges Basile Stavracas Neto + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#include "config.h" + +#include + +#include + +#include "sysprof-callgraph-private.h" +#include "sysprof-symbol-private.h" + +static GMainLoop *main_loop; +static int exit_code = -1; +static gboolean opt_skip_callgraph = FALSE; +static gboolean opt_skip_marks = FALSE; +static gboolean opt_skip_counters = FALSE; +static gboolean opt_skip_metadata = FALSE; +static const GOptionEntry options[] = { + { "no-callgraph", 0, 0, G_OPTION_ARG_NONE, &opt_skip_callgraph, "Do not dump the callgraph" , NULL }, + { "no-counters", 0, 0, G_OPTION_ARG_NONE, &opt_skip_counters, "Do not dump counters" , NULL }, + { "no-marks", 0, 0, G_OPTION_ARG_NONE, &opt_skip_marks, "Do not dump marks" , NULL }, + { "no-metadata", 0, 0, G_OPTION_ARG_NONE, &opt_skip_metadata, "Do not dump capture metadata" , NULL }, + { NULL } +}; + +static inline void +begin_document (SysprofDocument *document) +{ + const SysprofTimeSpan *timespan; + char *aux; + + g_print ("document {\n"); + if ((aux = sysprof_document_dup_title (document))) + { + g_autofree char *markup = g_markup_escape_text (aux, -1); + g_print (" title: \"%s\";\n", markup); + g_clear_pointer (&aux, g_free); + } + + if ((aux = sysprof_document_dup_subtitle (document))) + { + g_autofree char *markup = g_markup_escape_text (aux, -1); + g_print (" subtitle: \"%s\";\n", markup); + g_clear_pointer (&aux, g_free); + } + + timespan = sysprof_document_get_time_span (document); + g_print (" timespan: %" G_GINT64_FORMAT " %" G_GINT64_FORMAT ";\n", + timespan->begin_nsec, + timespan->end_nsec); + +} + +typedef struct +{ + guint size; + guint total; +} Augment; + +static void +augment_cb (SysprofCallgraph *callgraph, + SysprofCallgraphNode *node, + SysprofDocumentFrame *frame, + gboolean summarize, + gpointer user_data) +{ + Augment *aug; + + g_assert (SYSPROF_IS_CALLGRAPH (callgraph)); + g_assert (node != NULL); + g_assert (SYSPROF_IS_DOCUMENT_SAMPLE (frame)); + g_assert (user_data == NULL); + + aug = sysprof_callgraph_get_augment (callgraph, node); + aug->size += 1; + aug->total += 1; + + for (SysprofCallgraphNode *iter = node->parent; iter; iter = iter->parent) + { + aug = sysprof_callgraph_get_augment (callgraph, iter); + aug->total += 1; + } +} + +static void +load_callgraph_cb (GObject *object, + GAsyncResult *result, + gpointer user_data) +{ + g_autoptr(DexPromise) promise = user_data; + SysprofCallgraph *callgraph; + GError *error = NULL; + + if ((callgraph = sysprof_document_callgraph_finish (SYSPROF_DOCUMENT (object), result, &error))) + dex_promise_resolve_object (promise, callgraph); + else + dex_promise_reject (promise, error); +} + +static void +print_node_recursively (SysprofCallgraphNode *node, + int depth) +{ + const Augment *aug; + + if (!node) + return; + + aug = (const Augment *) &node->augment[0]; + + g_print ("%*cnode {\n", depth * 2, ' '); + g_print ("%*csymbol: \"%s\",\n", (depth + 1) * 2, ' ', node->summary->symbol->name); + g_print ("%*cself: %u,\n", (depth + 1) * 2, ' ', aug->size); + g_print ("%*ctotal: %u,\n", (depth + 1) * 2, ' ', aug->total); + + for (SysprofCallgraphNode *child = node->children; child != NULL; child = child->next) + { + if (!child->prev) + g_print ("\n"); + + print_node_recursively (child, depth + 1); + } + + g_print ("%*c}\n", depth * 2, ' '); +} + +static void +dump_callgraph (SysprofDocument *document) +{ + g_autoptr(SysprofCallgraph) callgraph = NULL; + g_autoptr(GListModel) traceables = NULL; + g_autoptr(GError) error = NULL; + SysprofCallgraphFlags flags = 0; + DexPromise *promise; + + if (opt_skip_callgraph) + return; + + traceables = sysprof_document_list_samples (document); + + promise = dex_promise_new (); + + sysprof_document_callgraph_async (document, + flags, + traceables, + sizeof (Augment), + augment_cb, + NULL, NULL, + dex_promise_get_cancellable (promise), + load_callgraph_cb, + dex_ref (promise)); + + callgraph = dex_await_object (DEX_FUTURE (promise), &error); + + if (error) + return; + + g_print ("\n"); + g_print (" callgraph {\n"); + + print_node_recursively (&callgraph->root, 2); + + g_print (" }\n"); +} + +static void +dump_counters (SysprofDocument *document) +{ + g_autoptr(GListModel) counters = NULL; + unsigned int n_counters; + + if (opt_skip_counters) + return; + + counters = sysprof_document_list_counters (document); + g_assert (G_IS_LIST_MODEL (counters)); + + n_counters = g_list_model_get_n_items (counters); + + if (n_counters == 0) + return; + + g_print ("\n"); + g_print (" counters {\n"); + + for (unsigned int i = 0; i < n_counters; i++) + { + g_autoptr(SysprofDocumentCounter) counter = NULL; + unsigned int n_values; + const char *aux; + + counter = g_list_model_get_item (counters, i); + g_assert (SYSPROF_IS_DOCUMENT_COUNTER (counter)); + + g_print (" counter {\n"); + + if ((aux = sysprof_document_counter_get_category (counter))) + { + g_autofree char *markup = g_markup_escape_text (aux, -1); + g_print (" category: \"%s\";\n", markup); + } + + if ((aux = sysprof_document_counter_get_name (counter))) + { + g_autofree char *markup = g_markup_escape_text (aux, -1); + g_print (" name: \"%s\";\n", markup); + } + + if ((aux = sysprof_document_counter_get_description (counter))) + { + g_autofree char *markup = g_markup_escape_text (aux, -1); + g_print (" description: \"%s\";\n", markup); + } + + n_values = sysprof_document_counter_get_n_values (counter); + if (n_values) + { + g_print ("\n"); + g_print (" values {\n"); + for (unsigned int j = 0; j < n_values; j++) + { + g_autoptr(SysprofDocumentCounterValue) value = NULL; + g_autofree char *formatted_value = NULL; + + value = g_list_model_get_item (G_LIST_MODEL (counter), j); + g_assert (SYSPROF_IS_DOCUMENT_COUNTER_VALUE (value)); + + formatted_value = sysprof_document_counter_value_format (value); + + g_print (" value {\n"); + g_print (" time: %" G_GINT64_FORMAT ";\n", sysprof_document_counter_value_get_time (value)); + g_print (" offset: %" G_GINT64_FORMAT ";\n", sysprof_document_counter_value_get_time (value)); + g_print (" value: %s;\n", formatted_value); + g_print (" }\n"); + } + g_print (" }\n"); + } + + g_print (" }\n"); + } + + g_print (" }\n"); +} + +static void +dump_marks (SysprofDocument *document) +{ + g_autoptr(GListModel) groups = NULL; + unsigned int n_groups; + + if (opt_skip_marks) + return; + + groups = sysprof_document_catalog_marks (document); + g_assert (G_IS_LIST_MODEL (groups)); + + n_groups = g_list_model_get_n_items (groups); + + if (n_groups == 0) + return; + + g_print ("\n"); + g_print (" marks {\n"); + + for (unsigned int i = 0; i < n_groups; i++) + { + g_autoptr(GListModel) catalogs = NULL; + unsigned int n_catalogs; + + catalogs = g_list_model_get_item (groups, i); + g_assert (G_IS_LIST_MODEL (catalogs)); + + g_print ("\n"); + g_print (" group {\n"); + + n_catalogs = g_list_model_get_n_items (catalogs); + for (unsigned int j = 0; j < n_catalogs; j++) + { + g_autoptr(SysprofMarkCatalog) catalog = NULL; + unsigned int n_marks; + const char *aux; + + catalog = g_list_model_get_item (catalogs, j); + g_assert (SYSPROF_IS_MARK_CATALOG (catalog)); + + if (j == 0 && + (aux = sysprof_mark_catalog_get_group (catalog))) + { + g_autofree char *markup = g_markup_escape_text (aux, -1); + g_print (" name: \"%s\";\n", markup); + g_print ("\n"); + } + + n_marks = g_list_model_get_n_items (G_LIST_MODEL (catalog)); + for (unsigned int k = 0; k < n_marks; k++) + { + g_autoptr(SysprofDocumentMark) mark = NULL; + + mark = g_list_model_get_item (G_LIST_MODEL (catalog), k); + g_assert (SYSPROF_IS_DOCUMENT_MARK (mark)); + + g_print (" mark {\n"); + + if ((aux = sysprof_document_mark_get_name (mark))) + { + g_autofree char *markup = g_markup_escape_text (aux, -1); + g_print (" name: \"%s\";\n", markup); + } + + if ((aux = sysprof_document_mark_get_message (mark))) + { + g_autofree char *markup = g_markup_escape_text (aux, -1); + g_print (" message: \"%s\";\n", markup); + } + + g_print (" duration: %" G_GINT64_FORMAT ";\n", sysprof_document_mark_get_duration (mark)); + g_print (" end-time: %" G_GINT64_FORMAT ";\n", sysprof_document_mark_get_end_time (mark)); + g_print (" }\n"); + } + } + + g_print (" }\n"); + } + + g_print (" }\n"); +} + +static void +dump_metadata (SysprofDocument *document) +{ + g_autoptr(GListModel) metadatas = NULL; + unsigned int n_metadatas; + + if (opt_skip_metadata) + return; + + metadatas = sysprof_document_list_metadata (document); + g_assert (G_IS_LIST_MODEL (metadatas)); + + n_metadatas = g_list_model_get_n_items (metadatas); + + if (metadatas == 0) + return; + + g_print ("\n"); + g_print (" metadata {\n"); + + for (unsigned int i = 0; i < n_metadatas; i++) + { + g_autoptr(SysprofDocumentMetadata) metadata = NULL; + g_autofree char *value = NULL; + g_autofree char *id = NULL; + + metadata = g_list_model_get_item (metadatas, i); + g_assert (SYSPROF_IS_DOCUMENT_METADATA (metadata)); + + id = g_markup_escape_text (sysprof_document_metadata_get_id (metadata), -1); + value = g_markup_escape_text (sysprof_document_metadata_get_value (metadata), -1); + + g_print (" \"%s\": \"%s\";\n", id, value); + } + + g_print (" }\n"); +} + +static inline void +end_document (SysprofDocument *document) +{ + g_print ("}\n"); +} + +static DexFuture * +sysprof_cat_fiber (gpointer data) +{ + g_autoptr(SysprofDocumentLoader) loader = NULL; + g_autoptr(SysprofDocument) document = NULL; + g_autoptr(GError) error = NULL; + const char *filename = data; + + g_assert (filename != NULL); + + loader = sysprof_document_loader_new (filename); + + if (!(document = sysprof_document_loader_load (loader, NULL, &error))) + return dex_future_new_for_error (g_steal_pointer (&error)); + + begin_document (document); + { + dump_metadata (document); + dump_callgraph (document); + dump_marks (document); + dump_counters (document); + } + end_document (document); + + exit_code = EXIT_SUCCESS; + + g_main_loop_quit (main_loop); + + return dex_future_new_for_boolean (TRUE); +} + +static DexFuture * +sysprof_cat_catch_error_cb (DexFuture *future, + gpointer user_data) +{ + g_autoptr(GError) error = NULL; + + dex_await (dex_ref (future), &error); + + g_printerr ("Error: %s\n", error->message); + + exit_code = EXIT_FAILURE; + + g_main_loop_quit (main_loop); + + return NULL; +} + +int +main (int argc, + char *argv[]) +{ + g_autoptr(GOptionContext) context = NULL; + g_autoptr(GError) error = NULL; + + dex_init (); + + g_set_prgname ("sysprof-cat"); + g_set_application_name ("sysprof-cat"); + + main_loop = g_main_loop_new (NULL, FALSE); + + context = g_option_context_new ("CAPTURE -- Dump the contents of a capture"); + g_option_context_add_main_entries (context, options, NULL); + + if (!g_option_context_parse (context, &argc, &argv, &error)) + { + g_printerr ("%s\n", error->message); + return EXIT_FAILURE; + } + + if (argc < 2) + { + g_printerr ("usage: %s CAPTURE\n", argv[0]); + return EXIT_FAILURE; + } + + dex_future_disown ( + dex_future_catch ( + dex_scheduler_spawn (NULL, 0, + sysprof_cat_fiber, + g_strdup (argv[1]), + g_free), + sysprof_cat_catch_error_cb, + NULL, NULL)); + + if (exit_code == -1) + g_main_loop_run (main_loop); + + return exit_code; +}