diff --git a/src/libsysprof-profile/meson.build b/src/libsysprof-profile/meson.build index 9d31c715..56893e7b 100644 --- a/src/libsysprof-profile/meson.build +++ b/src/libsysprof-profile/meson.build @@ -6,6 +6,7 @@ libsysprof_profile_public_sources = [ 'sysprof-memory-usage.c', 'sysprof-network-usage.c', 'sysprof-profiler.c', + 'sysprof-proxied-instrument.c', 'sysprof-recording.c', 'sysprof-sampler.c', 'sysprof-spawnable.c', @@ -29,6 +30,7 @@ libsysprof_profile_public_headers = [ 'sysprof-memory-usage.h', 'sysprof-network-usage.h', 'sysprof-profiler.h', + 'sysprof-proxied-instrument.h', 'sysprof-recording.h', 'sysprof-sampler.h', 'sysprof-spawnable.h', diff --git a/src/libsysprof-profile/sysprof-profile.h b/src/libsysprof-profile/sysprof-profile.h index cdc527df..1c3b9a05 100644 --- a/src/libsysprof-profile/sysprof-profile.h +++ b/src/libsysprof-profile/sysprof-profile.h @@ -32,6 +32,7 @@ G_BEGIN_DECLS # include "sysprof-memory-usage.h" # include "sysprof-network-usage.h" # include "sysprof-profiler.h" +# include "sysprof-proxied-instrument.h" # include "sysprof-recording.h" # include "sysprof-sampler.h" # include "sysprof-spawnable.h" diff --git a/src/libsysprof-profile/sysprof-proxied-instrument-private.h b/src/libsysprof-profile/sysprof-proxied-instrument-private.h new file mode 100644 index 00000000..327e03d2 --- /dev/null +++ b/src/libsysprof-profile/sysprof-proxied-instrument-private.h @@ -0,0 +1,42 @@ +/* sysprof-proxied-instrument-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-instrument-private.h" +#include "sysprof-proxied-instrument.h" + +G_BEGIN_DECLS + +struct _SysprofProxiedInstrument +{ + SysprofInstrument parent_instance; + GBusType bus_type; + char *bus_name; + char *object_path; + guint call_stop_first : 1; +}; + +struct _SysprofProxiedInstrumentClass +{ + SysprofInstrumentClass parent_class; +}; + +G_END_DECLS diff --git a/src/libsysprof-profile/sysprof-proxied-instrument.c b/src/libsysprof-profile/sysprof-proxied-instrument.c new file mode 100644 index 00000000..9e01541a --- /dev/null +++ b/src/libsysprof-profile/sysprof-proxied-instrument.c @@ -0,0 +1,310 @@ +/* sysprof-proxied-instrument.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 + +#include "sysprof-proxied-instrument-private.h" +#include "sysprof-recording-private.h" + + +enum { + PROP_0, + PROP_BUS_TYPE, + PROP_BUS_NAME, + PROP_OBJECT_PATH, + N_PROPS +}; + +G_DEFINE_TYPE (SysprofProxiedInstrument, sysprof_proxied_instrument, SYSPROF_TYPE_INSTRUMENT) + +static GParamSpec *properties [N_PROPS]; + +typedef struct _Record +{ + SysprofRecording *recording; + DexFuture *cancellable; + char *bus_name; + char *object_path; + GBusType bus_type; + guint call_stop_first : 1; +} Record; + +static void +record_free (Record *record) +{ + g_clear_object (&record->recording); + dex_clear (&record->cancellable); + g_clear_pointer (&record->bus_name, g_free); + g_clear_pointer (&record->object_path, g_free); + g_free (record); +} + +static DexFuture * +sysprof_proxied_instrument_record_fiber (gpointer user_data) +{ + Record *record = user_data; + SysprofCaptureWriter *writer; + SysprofCaptureReader *reader; + g_autoptr(GDBusConnection) connection = NULL; + g_autoptr(GUnixFDList) fd_list = NULL; + g_autoptr(DexFuture) started = NULL; + g_autoptr(GError) error = NULL; + GVariantDict options = G_VARIANT_DICT_INIT (NULL); + g_autofd int proxy_fd = -1; + int handle; + + g_assert (record != NULL); + g_assert (record->bus_type != G_BUS_TYPE_NONE); + g_assert (record->bus_name != NULL); + g_assert (record->object_path != NULL); + g_assert (SYSPROF_IS_RECORDING (record->recording)); + g_assert (DEX_IS_CANCELLABLE (record->cancellable)); + + /* Wait for our connection to be available */ + if (!(connection = dex_await_object (dex_bus_get (record->bus_type), &error))) + return dex_future_new_for_error (g_steal_pointer (&error)); + + /* If we should try calling stop first to cancel any in-flight + * recording. We could pipeline this, but if the other side + * handles messages on threads, they would race. + */ + if (record->call_stop_first) + dex_await (dex_dbus_connection_call (connection, + record->bus_name, + record->object_path, + "org.gnome.Sysprof3.Profiler", + "Stop", + g_variant_new ("()"), + G_VARIANT_TYPE ("()"), + G_DBUS_CALL_FLAGS_NONE, + -1), + NULL); + + /* Create a new FD that the peer will be able to write to and + * we will concatenate into the capture after recording. + */ + if (-1 == (proxy_fd = sysprof_memfd_create ("[sysprof-proxy]"))) + return dex_future_new_for_errno (errno); + + /* Create FDList to pass to the peer so they can get our FD */ + fd_list = g_unix_fd_list_new (); + if (-1 == (handle = g_unix_fd_list_append (fd_list, proxy_fd, &error))) + return dex_future_new_for_error (g_steal_pointer (&error)); + + /* Call the proxy and give it our FD to start recording */ + if (!dex_await (dex_dbus_connection_call_with_unix_fd_list (connection, + record->bus_name, + record->object_path, + "org.gnome.Sysprof3.Profiler", + "Start", + g_variant_new ("(@a{sv}h)", + g_variant_dict_end (&options), + handle), + G_VARIANT_TYPE ("()"), + G_DBUS_CALL_FLAGS_ALLOW_INTERACTIVE_AUTHORIZATION, + G_MAXUINT, + fd_list), + &error)) + { + g_debug ("Failed to start profiler at %s %s: %s", + record->bus_name, + record->object_path, + error->message); + return dex_future_new_for_error (g_steal_pointer (&error)); + } + + /* Await completion of recording */ + dex_await (dex_ref (record->cancellable), NULL); + + /* Call the proxy and let them know to stop */ + dex_await (dex_dbus_connection_call (connection, + record->bus_name, + record->object_path, + "org.gnome.Sysprof3.Profiler", + "Stop", + g_variant_new ("()"), + G_VARIANT_TYPE ("()"), + G_DBUS_CALL_FLAGS_NONE, + -1), + &error); + + if (error != NULL) + g_warning ("Failed to stop profiler at %s %s: %s", + record->bus_name, + record->object_path, + error->message); + + /* Reset the file position to the start */ + lseek (proxy_fd, 0, SEEK_SET); + + /* Now cat the recording into our writer */ + writer = _sysprof_recording_writer (record->recording); + reader = sysprof_capture_reader_new_from_fd (g_steal_fd (&proxy_fd)); + sysprof_capture_writer_cat (writer, reader); + sysprof_capture_reader_unref (reader); + + return dex_future_new_for_boolean (TRUE); +} + +static DexFuture * +sysprof_proxied_instrument_record (SysprofInstrument *instrument, + SysprofRecording *recording, + GCancellable *cancellable) +{ + SysprofProxiedInstrument *self = (SysprofProxiedInstrument *)instrument; + Record *record; + + g_assert (SYSPROF_IS_PROXIED_INSTRUMENT (self)); + g_assert (SYSPROF_IS_RECORDING (recording)); + g_assert (G_IS_CANCELLABLE (cancellable)); + + record = g_new0 (Record, 1); + record->recording = g_object_ref (recording); + record->cancellable = dex_cancellable_new_from_cancellable (cancellable); + record->bus_name = g_strdup (self->bus_name); + record->object_path = g_strdup (self->object_path); + record->bus_type = self->bus_type; + record->call_stop_first = self->call_stop_first; + + return dex_scheduler_spawn (NULL, 0, + sysprof_proxied_instrument_record_fiber, + record, + (GDestroyNotify)record_free); +} + +static void +sysprof_proxied_instrument_finalize (GObject *object) +{ + SysprofProxiedInstrument *self = (SysprofProxiedInstrument *)object; + + g_clear_pointer (&self->bus_name, g_free); + g_clear_pointer (&self->object_path, g_free); + + G_OBJECT_CLASS (sysprof_proxied_instrument_parent_class)->finalize (object); +} + +static void +sysprof_proxied_instrument_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + SysprofProxiedInstrument *self = SYSPROF_PROXIED_INSTRUMENT (object); + + switch (prop_id) + { + case PROP_BUS_TYPE: + g_value_set_enum (value, self->bus_type); + break; + + case PROP_BUS_NAME: + g_value_set_string (value, self->bus_name); + break; + + case PROP_OBJECT_PATH: + g_value_set_string (value, self->object_path); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +static void +sysprof_proxied_instrument_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + SysprofProxiedInstrument *self = SYSPROF_PROXIED_INSTRUMENT (object); + + switch (prop_id) + { + case PROP_BUS_TYPE: + self->bus_type = g_value_get_enum (value); + break; + + case PROP_BUS_NAME: + self->bus_name = g_value_dup_string (value); + break; + + case PROP_OBJECT_PATH: + self->object_path = g_value_dup_string (value); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +static void +sysprof_proxied_instrument_class_init (SysprofProxiedInstrumentClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + SysprofInstrumentClass *instrument_class = SYSPROF_INSTRUMENT_CLASS (klass); + + object_class->finalize = sysprof_proxied_instrument_finalize; + object_class->get_property = sysprof_proxied_instrument_get_property; + object_class->set_property = sysprof_proxied_instrument_set_property; + + instrument_class->record = sysprof_proxied_instrument_record; + + properties [PROP_BUS_TYPE] = + g_param_spec_enum ("bus-type", NULL, NULL, + G_TYPE_BUS_TYPE, + G_BUS_TYPE_NONE, + (G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS)); + + properties [PROP_BUS_NAME] = + g_param_spec_string ("bus-name", NULL, NULL, + NULL, + (G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS)); + + properties [PROP_OBJECT_PATH] = + g_param_spec_string ("object-path", NULL, NULL, + NULL, + (G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS)); + + g_object_class_install_properties (object_class, N_PROPS, properties); +} + +static void +sysprof_proxied_instrument_init (SysprofProxiedInstrument *self) +{ +} + +SysprofInstrument * +sysprof_proxied_instrument_new (GBusType bus_type, + const char *bus_name, + const char *object_path) +{ + g_return_val_if_fail (bus_type == G_BUS_TYPE_SYSTEM || + bus_type == G_BUS_TYPE_SESSION, NULL); + g_return_val_if_fail (bus_name != NULL, NULL); + g_return_val_if_fail (object_path != NULL, NULL); + + return g_object_new (SYSPROF_TYPE_PROXIED_INSTRUMENT, + "bus-type", bus_type, + "bus-name", bus_name, + "object-path", object_path, + NULL); +} diff --git a/src/libsysprof-profile/sysprof-proxied-instrument.h b/src/libsysprof-profile/sysprof-proxied-instrument.h new file mode 100644 index 00000000..0af100fe --- /dev/null +++ b/src/libsysprof-profile/sysprof-proxied-instrument.h @@ -0,0 +1,46 @@ +/* sysprof-proxied-instrument.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 "sysprof-instrument.h" + +G_BEGIN_DECLS + +#define SYSPROF_TYPE_PROXIED_INSTRUMENT (sysprof_proxied_instrument_get_type()) +#define SYSPROF_IS_PROXIED_INSTRUMENT(obj) G_TYPE_CHECK_INSTANCE_TYPE(obj, SYSPROF_TYPE_PROXIED_INSTRUMENT) +#define SYSPROF_PROXIED_INSTRUMENT(obj) G_TYPE_CHECK_INSTANCE_CAST(obj, SYSPROF_TYPE_PROXIED_INSTRUMENT, SysprofProxiedInstrument) +#define SYSPROF_PROXIED_INSTRUMENT_CLASS(klass) G_TYPE_CHECK_CLASS_CAST(klass, SYSPROF_TYPE_PROXIED_INSTRUMENT, SysprofProxiedInstrumentClass) + +typedef struct _SysprofProxiedInstrument SysprofProxiedInstrument; +typedef struct _SysprofProxiedInstrumentClass SysprofProxiedInstrumentClass; + +SYSPROF_AVAILABLE_IN_ALL +GType sysprof_proxied_instrument_get_type (void) G_GNUC_CONST; +SYSPROF_AVAILABLE_IN_ALL +SysprofInstrument *sysprof_proxied_instrument_new (GBusType bus_type, + const char *bus_name, + const char *object_path); + +G_DEFINE_AUTOPTR_CLEANUP_FUNC (SysprofProxiedInstrument, g_object_unref) + +G_END_DECLS