From 1f6cc395545315d123263615632809682338f5f1 Mon Sep 17 00:00:00 2001 From: Christian Hergert Date: Tue, 13 Jun 2023 11:30:45 -0700 Subject: [PATCH] libsysprof-profile: add SysprofDiagnostic to recordings This allows instruments to record a diagnostic and have it land as an object in a GListModel of diagnostics available to the API consumer. Such items may be used by recording UI to display issues with the recording to the user. --- src/libsysprof-profile/meson.build | 2 + .../sysprof-diagnostic-private.h | 31 ++++ src/libsysprof-profile/sysprof-diagnostic.c | 151 ++++++++++++++++++ src/libsysprof-profile/sysprof-diagnostic.h | 41 +++++ src/libsysprof-profile/sysprof-profile.h | 1 + .../sysprof-recording-private.h | 8 + src/libsysprof-profile/sysprof-recording.c | 77 +++++++++ src/libsysprof-profile/sysprof-recording.h | 30 ++-- src/libsysprof-profile/tests/test-profiler.c | 29 ++++ 9 files changed, 356 insertions(+), 14 deletions(-) create mode 100644 src/libsysprof-profile/sysprof-diagnostic-private.h create mode 100644 src/libsysprof-profile/sysprof-diagnostic.c create mode 100644 src/libsysprof-profile/sysprof-diagnostic.h diff --git a/src/libsysprof-profile/meson.build b/src/libsysprof-profile/meson.build index cc0ba551..9f7cbec4 100644 --- a/src/libsysprof-profile/meson.build +++ b/src/libsysprof-profile/meson.build @@ -1,6 +1,7 @@ libsysprof_profile_public_sources = [ 'sysprof-battery-charge.c', 'sysprof-cpu-usage.c', + 'sysprof-diagnostic.c', 'sysprof-disk-usage.c', 'sysprof-energy-usage.c', 'sysprof-instrument.c', @@ -28,6 +29,7 @@ libsysprof_profile_public_headers = [ 'sysprof-battery-charge.h', 'sysprof-cpu-usage.h', + 'sysprof-diagnostic.h', 'sysprof-disk-usage.h', 'sysprof-energy-usage.h', 'sysprof-instrument.h', diff --git a/src/libsysprof-profile/sysprof-diagnostic-private.h b/src/libsysprof-profile/sysprof-diagnostic-private.h new file mode 100644 index 00000000..50e65054 --- /dev/null +++ b/src/libsysprof-profile/sysprof-diagnostic-private.h @@ -0,0 +1,31 @@ +/* sysprof-diagnostic-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-diagnostic.h" + +G_BEGIN_DECLS + +SysprofDiagnostic *_sysprof_diagnostic_new (char *domain, + char *message, + gboolean fatal); + +G_END_DECLS diff --git a/src/libsysprof-profile/sysprof-diagnostic.c b/src/libsysprof-profile/sysprof-diagnostic.c new file mode 100644 index 00000000..7aa5f099 --- /dev/null +++ b/src/libsysprof-profile/sysprof-diagnostic.c @@ -0,0 +1,151 @@ +/* sysprof-diagnostic.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 "sysprof-diagnostic-private.h" + +struct _SysprofDiagnostic +{ + GObject parent_instance; + GRefString *domain; + char *message; + guint fatal : 1; +}; + +enum { + PROP_0, + PROP_DOMAIN, + PROP_MESSAGE, + PROP_FATAL, + N_PROPS +}; + +G_DEFINE_FINAL_TYPE (SysprofDiagnostic, sysprof_diagnostic, G_TYPE_OBJECT) + +static GParamSpec *properties [N_PROPS]; + +static void +sysprof_diagnostic_finalize (GObject *object) +{ + SysprofDiagnostic *self = (SysprofDiagnostic *)object; + + g_clear_pointer (&self->domain, g_free); + g_clear_pointer (&self->message, g_free); + + G_OBJECT_CLASS (sysprof_diagnostic_parent_class)->finalize (object); +} + +static void +sysprof_diagnostic_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + SysprofDiagnostic *self = SYSPROF_DIAGNOSTIC (object); + + switch (prop_id) + { + case PROP_DOMAIN: + g_value_set_string (value, self->domain); + break; + + case PROP_MESSAGE: + g_value_set_string (value, self->message); + break; + + case PROP_FATAL: + g_value_set_boolean (value, self->fatal); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +static void +sysprof_diagnostic_class_init (SysprofDiagnosticClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->finalize = sysprof_diagnostic_finalize; + object_class->get_property = sysprof_diagnostic_get_property; + + properties [PROP_DOMAIN] = + g_param_spec_string ("domain", NULL, NULL, + NULL, + (G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); + + properties [PROP_MESSAGE] = + g_param_spec_string ("message", NULL, NULL, + NULL, + (G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); + + properties [PROP_FATAL] = + g_param_spec_boolean ("fatal", NULL, NULL, + FALSE, + (G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); + + g_object_class_install_properties (object_class, N_PROPS, properties); +} + +static void +sysprof_diagnostic_init (SysprofDiagnostic *self) +{ +} + +const char * +sysprof_diagnostic_get_domain (SysprofDiagnostic *self) +{ + g_return_val_if_fail (SYSPROF_IS_DIAGNOSTIC (self), NULL); + + return self->domain; +} + +const char * +sysprof_diagnostic_get_message (SysprofDiagnostic *self) +{ + g_return_val_if_fail (SYSPROF_IS_DIAGNOSTIC (self), NULL); + + return self->message; +} + +gboolean +sysprof_diagnostic_get_fatal (SysprofDiagnostic *self) +{ + g_return_val_if_fail (SYSPROF_IS_DIAGNOSTIC (self), FALSE); + + return self->fatal; +} + +SysprofDiagnostic * +_sysprof_diagnostic_new (char *domain, + char *message, + gboolean fatal) +{ + SysprofDiagnostic *self; + + self = g_object_new (SYSPROF_TYPE_DIAGNOSTIC, NULL); + self->message = message; + self->domain = domain; + self->fatal = !!fatal; + + return self; +} diff --git a/src/libsysprof-profile/sysprof-diagnostic.h b/src/libsysprof-profile/sysprof-diagnostic.h new file mode 100644 index 00000000..3c265b65 --- /dev/null +++ b/src/libsysprof-profile/sysprof-diagnostic.h @@ -0,0 +1,41 @@ +/* sysprof-diagnostic.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 + +#include + +G_BEGIN_DECLS + +#define SYSPROF_TYPE_DIAGNOSTIC (sysprof_diagnostic_get_type()) + +SYSPROF_AVAILABLE_IN_ALL +G_DECLARE_FINAL_TYPE (SysprofDiagnostic, sysprof_diagnostic, SYSPROF, DIAGNOSTIC, GObject) + +SYSPROF_AVAILABLE_IN_ALL +const char *sysprof_diagnostic_get_domain (SysprofDiagnostic *self); +SYSPROF_AVAILABLE_IN_ALL +const char *sysprof_diagnostic_get_message (SysprofDiagnostic *self); +SYSPROF_AVAILABLE_IN_ALL +gboolean sysprof_diagnostic_get_fatal (SysprofDiagnostic *self); + +G_END_DECLS diff --git a/src/libsysprof-profile/sysprof-profile.h b/src/libsysprof-profile/sysprof-profile.h index a33d1ec9..9bf03fcb 100644 --- a/src/libsysprof-profile/sysprof-profile.h +++ b/src/libsysprof-profile/sysprof-profile.h @@ -27,6 +27,7 @@ G_BEGIN_DECLS #define SYSPROF_PROFILE_INSIDE # include "sysprof-battery-charge.h" # include "sysprof-cpu-usage.h" +# include "sysprof-diagnostic.h" # include "sysprof-disk-usage.h" # include "sysprof-energy-usage.h" # include "sysprof-instrument.h" diff --git a/src/libsysprof-profile/sysprof-recording-private.h b/src/libsysprof-profile/sysprof-recording-private.h index 88a54626..8d36b718 100644 --- a/src/libsysprof-profile/sysprof-recording-private.h +++ b/src/libsysprof-profile/sysprof-recording-private.h @@ -42,5 +42,13 @@ void _sysprof_recording_add_file_data (SysprofRecording *s const char *path, const char *contents, gssize length); +void _sysprof_recording_diagnostic (SysprofRecording *self, + const char *domain, + const char *format, + ...) G_GNUC_PRINTF (3, 4); +void _sysprof_recording_error (SysprofRecording *self, + const char *domain, + const char *format, + ...) G_GNUC_PRINTF (3, 4); G_END_DECLS diff --git a/src/libsysprof-profile/sysprof-recording.c b/src/libsysprof-profile/sysprof-recording.c index 43fb45bf..0a8bdf76 100644 --- a/src/libsysprof-profile/sysprof-recording.c +++ b/src/libsysprof-profile/sysprof-recording.c @@ -22,6 +22,7 @@ #include +#include "sysprof-diagnostic-private.h" #include "sysprof-instrument-private.h" #include "sysprof-polkit-private.h" #include "sysprof-recording-private.h" @@ -35,6 +36,13 @@ struct _SysprofRecording { GObject parent_instance; + /* Diagnostics that may be added by instruments during the recording. + * Some may be fatal, meaning that they stop the recording when the + * diagnostic is submitted. That can happen in situations like + * miss-configuration or failed authorization. + */ + GListStore *diagnostics; + /* If we are spawning a process as part of this recording, this * is the SysprofSpawnable used to spawn the process. */ @@ -197,6 +205,7 @@ sysprof_recording_finalize (GObject *object) g_clear_pointer (&self->writer, sysprof_capture_writer_unref); g_clear_pointer (&self->instruments, g_ptr_array_unref); g_clear_object (&self->spawnable); + g_clear_object (&self->diagnostics); dex_clear (&self->fiber); G_OBJECT_CLASS (sysprof_recording_parent_class)->finalize (object); @@ -215,6 +224,7 @@ sysprof_recording_init (SysprofRecording *self) { self->channel = dex_channel_new (0); self->instruments = g_ptr_array_new_with_free_func (g_object_unref); + self->diagnostics = g_list_store_new (SYSPROF_TYPE_DIAGNOSTIC); } SysprofRecording * @@ -510,3 +520,70 @@ _sysprof_recording_add_file_data (SysprofRecording *self, contents += to_write; } } + +static void +_sysprof_recording_message_internal (SysprofRecording *self, + const char *domain, + const char *format, + va_list *args, + gboolean fatal) +{ + g_autoptr(SysprofDiagnostic) diagnostic = NULL; + + g_assert (SYSPROF_IS_RECORDING (self)); + g_assert (domain != NULL); + g_assert (format != NULL); + g_assert (args != NULL); + + diagnostic = _sysprof_diagnostic_new (g_strdup (domain), + g_strdup_vprintf (format, *args), + fatal); + + g_list_store_append (self->diagnostics, diagnostic); + + if (fatal) + sysprof_recording_stop_async (self, NULL, NULL, NULL); +} + +void +_sysprof_recording_diagnostic (SysprofRecording *self, + const char *domain, + const char *format, + ...) +{ + va_list args; + + va_start (args, format); + _sysprof_recording_message_internal (self, domain, format, &args, FALSE); + va_end (args); +} + +void +_sysprof_recording_error (SysprofRecording *self, + const char *domain, + const char *format, + ...) +{ + va_list args; + + va_start (args, format); + _sysprof_recording_message_internal (self, domain, format, &args, TRUE); + va_end (args); +} + +/** + * sysprof_recording_list_diagnostics: + * @self: a #SysprofRecording + * + * Gets the diagnostics for the recording which may be updated as + * instruments discover issues with the recording or configuration. + * + * Returns: (transfer full): a #GListModel of #SysprofDiagnostic + */ +GListModel * +sysprof_recording_list_diagnostics (SysprofRecording *self) +{ + g_return_val_if_fail (SYSPROF_IS_RECORDING (self), NULL); + + return g_object_ref (G_LIST_MODEL (self->diagnostics)); +} diff --git a/src/libsysprof-profile/sysprof-recording.h b/src/libsysprof-profile/sysprof-recording.h index ab1a6074..c9d9efa2 100644 --- a/src/libsysprof-profile/sysprof-recording.h +++ b/src/libsysprof-profile/sysprof-recording.h @@ -32,22 +32,24 @@ SYSPROF_AVAILABLE_IN_ALL G_DECLARE_FINAL_TYPE (SysprofRecording, sysprof_recording, SYSPROF, RECORDING, GObject) SYSPROF_AVAILABLE_IN_ALL -void sysprof_recording_wait_async (SysprofRecording *self, - GCancellable *cancellable, - GAsyncReadyCallback callback, - gpointer user_data); +GListModel *sysprof_recording_list_diagnostics (SysprofRecording *self); SYSPROF_AVAILABLE_IN_ALL -gboolean sysprof_recording_wait_finish (SysprofRecording *self, - GAsyncResult *result, - GError **error); +void sysprof_recording_wait_async (SysprofRecording *self, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data); SYSPROF_AVAILABLE_IN_ALL -void sysprof_recording_stop_async (SysprofRecording *self, - GCancellable *cancellable, - GAsyncReadyCallback callback, - gpointer user_data); +gboolean sysprof_recording_wait_finish (SysprofRecording *self, + GAsyncResult *result, + GError **error); SYSPROF_AVAILABLE_IN_ALL -gboolean sysprof_recording_stop_finish (SysprofRecording *self, - GAsyncResult *result, - GError **error); +void sysprof_recording_stop_async (SysprofRecording *self, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data); +SYSPROF_AVAILABLE_IN_ALL +gboolean sysprof_recording_stop_finish (SysprofRecording *self, + GAsyncResult *result, + GError **error); G_END_DECLS diff --git a/src/libsysprof-profile/tests/test-profiler.c b/src/libsysprof-profile/tests/test-profiler.c index 09861302..af2800f5 100644 --- a/src/libsysprof-profile/tests/test-profiler.c +++ b/src/libsysprof-profile/tests/test-profiler.c @@ -51,6 +51,23 @@ wait_cb (GObject *object, g_main_loop_quit (main_loop); } +static void +diagnostics_items_changed_cb (GListModel *model, + guint position, + guint removed, + guint added, + gpointer user_data) +{ + for (guint i = 0; i < added; i++) + { + g_autoptr(SysprofDiagnostic) diagnostic = g_list_model_get_item (model, position+i); + + g_printerr ("%s: %s\n", + sysprof_diagnostic_get_domain (diagnostic), + sysprof_diagnostic_get_message (diagnostic)); + } +} + static void record_cb (GObject *object, GAsyncResult *result, @@ -58,11 +75,23 @@ record_cb (GObject *object, { g_autoptr(GError) error = NULL; g_autoptr(SysprofRecording) recording = sysprof_profiler_record_finish (SYSPROF_PROFILER (object), result, &error); + g_autoptr(GListModel) diagnostics = NULL; g_assert_no_error (error); g_assert_nonnull (recording); g_assert_true (SYSPROF_IS_RECORDING (recording)); + diagnostics = sysprof_recording_list_diagnostics (recording); + g_signal_connect (diagnostics, + "items-changed", + G_CALLBACK (diagnostics_items_changed_cb), + NULL); + diagnostics_items_changed_cb (diagnostics, + 0, + 0, + g_list_model_get_n_items (diagnostics), + NULL); + sysprof_recording_wait_async (recording, NULL, wait_cb, NULL); active_recording = recording;