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; +}