From ef191d3f52ad904a9af8908ff5c2a1e44dfd27b0 Mon Sep 17 00:00:00 2001 From: Christian Hergert Date: Sun, 3 Nov 2024 10:53:29 -0800 Subject: [PATCH 16/18] libsysprof: add SysprofUserSampler for live unwinding This instrument triggers the live unwinder in sysprofd to capture a pre-configured amount of stack contents and CPU registers. You can use this instead of SysprofSampler in cases where you do not have frame- pointers but want a useful trace. It does have a moderate amount of CPU overhead compared to just relying on frame-pointers so keep that in mind. Generally useful on platforms that do not have frame pointers such as CentOS. --- src/libsysprof/meson.build | 7 + src/libsysprof/sysprof-user-sampler.c | 570 ++++++++++++++++++++++++++ src/libsysprof/sysprof-user-sampler.h | 43 ++ src/libsysprof/sysprof.h | 1 + 4 files changed, 621 insertions(+) create mode 100644 src/libsysprof/sysprof-user-sampler.c create mode 100644 src/libsysprof/sysprof-user-sampler.h diff --git a/src/libsysprof/meson.build b/src/libsysprof/meson.build index f3fa0850..e49c3a37 100644 --- a/src/libsysprof/meson.build +++ b/src/libsysprof/meson.build @@ -63,6 +63,7 @@ libsysprof_public_sources = [ 'sysprof-time-span.c', 'sysprof-tracefd-consumer.c', 'sysprof-tracer.c', + 'sysprof-user-sampler.c', ] libsysprof_public_headers = [ @@ -130,6 +131,7 @@ libsysprof_public_headers = [ 'sysprof-time-span.h', 'sysprof-tracefd-consumer.h', 'sysprof-tracer.h', + 'sysprof-user-sampler.h', ] libsysprof_private_sources = [ @@ -154,6 +156,11 @@ libsysprof_private_sources = [ 'sysprof-strings.c', 'sysprof-symbol-cache.c', 'timsort/gtktimsort.c', + + gnome.gdbus_codegen('ipc-unwinder', + sources: '../sysprofd/org.gnome.Sysprof3.Unwinder.xml', + interface_prefix: 'org.gnome.Sysprof3.', + namespace: 'Ipc'), ] if debuginfod_dep.found() diff --git a/src/libsysprof/sysprof-user-sampler.c b/src/libsysprof/sysprof-user-sampler.c new file mode 100644 index 00000000..0e3afeae --- /dev/null +++ b/src/libsysprof/sysprof-user-sampler.c @@ -0,0 +1,570 @@ +/* sysprof-user-sampler.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 + +#include + +#include + +#include "sysprof-instrument-private.h" +#include "sysprof-perf-event-stream-private.h" +#include "sysprof-recording-private.h" +#include "sysprof-user-sampler.h" +#include "sysprof-muxer-source.h" + +#include "ipc-unwinder.h" + +/* The following was provided to Sysprof by Serhei Makarov as part + * of the eu-stacktrace prototype work. + */ +#ifdef _ASM_X86_PERF_REGS_H +/* #define SYSPROF_ARCH_PREFERRED_REGS PERF_REG_EXTENDED_MASK -- error on x86_64 due to including segment regs*/ +#define REG(R) (1ULL << PERF_REG_X86_ ## R) +#define DWARF_NEEDED_REGS (/* no FLAGS */ REG(IP) | REG(SP) | REG(AX) | REG(CX) | REG(DX) | REG(BX) | REG(SI) | REG(DI) | REG(SP) | REG(BP) | /* no segment regs */ REG(R8) | REG(R9) | REG(R10) | REG(R11) | REG(R12) | REG(R13) | REG(R14) | REG(R15)) +/* XXX register ordering is defined in linux arch/x86/include/uapi/asm/perf_regs.h; + see code in tools/perf/util/intel-pt.c intel_pt_add_gp_regs() + and note how registers are added in the same order as the perf_regs.h enum */ +#define SYSPROF_ARCH_PREFERRED_REGS DWARF_NEEDED_REGS +/* TODO: add other architectures, imitating the linux tools/perf tree */ +#else +# define SYSPROF_ARCH_PREFERRED_REGS PERF_REG_EXTENDED_MASK +#endif /* _ASM_{arch}_PERF_REGS_H */ + +#define N_WAKEUP_EVENTS 149 + +struct _SysprofUserSampler +{ + SysprofInstrument parent_instance; + GArray *perf_fds; + int capture_fd; + int event_fd; + guint stack_size; +}; + +struct _SysprofUserSamplerClass +{ + SysprofInstrumentClass parent_class; +}; + +G_DEFINE_FINAL_TYPE (SysprofUserSampler, sysprof_user_sampler, SYSPROF_TYPE_INSTRUMENT) + +static void +close_fd (gpointer data) +{ + int *fdp = data; + + if (*fdp != -1) + { + close (*fdp); + *fdp = -1; + } +} + +static void +sysprof_user_sampler_ioctl (SysprofUserSampler *self, + gboolean enable) +{ + for (guint i = 0; i < self->perf_fds->len; i++) + { + int perf_fd = g_array_index (self->perf_fds, int, i); + + if (0 != ioctl (perf_fd, enable ? PERF_EVENT_IOC_ENABLE : PERF_EVENT_IOC_DISABLE)) + { + int errsv = errno; + g_warning ("Failed to toggle perf_fd: %s", g_strerror (errsv)); + } + } +} + +static char ** +sysprof_user_sampler_list_required_policy (SysprofInstrument *instrument) +{ + static const char *policy[] = {"org.gnome.sysprof3.profile", NULL}; + + return g_strdupv ((char **)policy); +} + +typedef struct _Prepare +{ + SysprofRecording *recording; + SysprofUserSampler *sampler; + guint stack_size; +} Prepare; + +static void +prepare_free (Prepare *prepare) +{ + g_clear_object (&prepare->recording); + g_clear_object (&prepare->sampler); + g_free (prepare); +} + +static void +_perf_event_open_cb (GObject *object, + GAsyncResult *result, + gpointer user_data) +{ + GDBusConnection *connection = (GDBusConnection *)object; + g_autoptr(DexPromise) promise = user_data; + g_autoptr(GUnixFDList) fd_list = NULL; + g_autoptr(GVariant) ret = NULL; + g_autoptr(GError) error = NULL; + + g_assert (G_IS_DBUS_CONNECTION (connection)); + g_assert (G_IS_ASYNC_RESULT (result)); + g_assert (DEX_IS_PROMISE (promise)); + + if ((ret = g_dbus_connection_call_with_unix_fd_list_finish (connection, &fd_list, result, &error))) + { + g_autofd int fd = -1; + int handle; + + g_variant_get (ret, "(h)", &handle); + + if (-1 == (fd = g_unix_fd_list_get (fd_list, handle, &error))) + goto failure; + + dex_promise_resolve_fd (promise, g_steal_fd (&fd)); + return; + } + +failure: + dex_promise_reject (promise, g_steal_pointer (&error)); +} + +static int +_perf_event_open (GDBusConnection *connection, + int cpu, + guint stack_size, + GError **error) +{ + g_autoptr(DexPromise) promise = NULL; + g_autoptr(GVariant) options = NULL; + g_autofd int perf_fd = -1; + struct perf_event_attr attr = {0}; + gboolean with_mmap2 = TRUE; + gboolean use_software = FALSE; + + g_assert (G_IS_DBUS_CONNECTION (connection)); + +try_again: + attr.sample_type = PERF_SAMPLE_IP + | PERF_SAMPLE_TID + | PERF_SAMPLE_IDENTIFIER + | PERF_SAMPLE_CALLCHAIN + | PERF_SAMPLE_STACK_USER + | PERF_SAMPLE_REGS_USER + | PERF_SAMPLE_TIME; + attr.wakeup_events = N_WAKEUP_EVENTS; + attr.disabled = TRUE; + attr.mmap = TRUE; + attr.mmap2 = with_mmap2; + attr.comm = 1; + attr.task = 1; + attr.exclude_idle = 1; + attr.sample_id_all = 1; + +#ifdef HAVE_PERF_CLOCKID + attr.clockid = sysprof_clock; + attr.use_clockid = 1; +#endif + + attr.sample_stack_user = stack_size; + attr.sample_regs_user = SYSPROF_ARCH_PREFERRED_REGS; + + attr.size = sizeof attr; + + if (use_software) + { + attr.type = PERF_TYPE_SOFTWARE; + attr.config = PERF_COUNT_SW_CPU_CLOCK; + attr.sample_period = 1000000; + } + else + { + attr.type = PERF_TYPE_HARDWARE; + attr.config = PERF_COUNT_HW_CPU_CYCLES; + attr.sample_period = 1200000; + } + + options = _sysprof_perf_event_attr_to_variant (&attr); + promise = dex_promise_new (); + + g_dbus_connection_call_with_unix_fd_list (connection, + "org.gnome.Sysprof3", + "/org/gnome/Sysprof3", + "org.gnome.Sysprof3.Service", + "PerfEventOpen", + g_variant_new ("(@a{sv}iiht)", + options, + -1, + cpu, + -1, + 0), + G_VARIANT_TYPE ("(h)"), + G_DBUS_CALL_FLAGS_NONE, + G_MAXUINT, + NULL, + NULL, + _perf_event_open_cb, + dex_ref (promise)); + + if (-1 == (perf_fd = dex_await_fd (dex_ref (promise), error))) + { + g_clear_pointer (&options, g_variant_unref); + + if (with_mmap2) + { + with_mmap2 = FALSE; + goto try_again; + } + + if (use_software == FALSE) + { + with_mmap2 = TRUE; + use_software = TRUE; + goto try_again; + } + + return -1; + } + + return g_steal_fd (&perf_fd); +} + +static void +call_unwind_cb (GObject *object, + GAsyncResult *result, + gpointer user_data) +{ + g_autoptr(DexPromise) promise = user_data; + g_autoptr(GUnixFDList) out_fd_list = NULL; + g_autoptr(GVariant) out_capture_fd = NULL; + g_autofd int capture_fd = -1; + GError *error = NULL; + + g_assert (IPC_IS_UNWINDER (object)); + g_assert (G_IS_ASYNC_RESULT (result)); + g_assert (DEX_IS_PROMISE (promise)); + + if (ipc_unwinder_call_unwind_finish (IPC_UNWINDER (object), &out_capture_fd, &out_fd_list, result, &error) && + -1 != (capture_fd = g_unix_fd_list_get (out_fd_list, g_variant_get_handle (out_capture_fd), &error))) + dex_promise_resolve_fd (promise, g_steal_fd (&capture_fd)); + else + dex_promise_reject (promise, error); +} + +static void +create_unwinder_cb (GObject *object, + GAsyncResult *result, + gpointer user_data) +{ + g_autoptr(DexPromise) promise = user_data; + IpcUnwinder *unwinder; + GError *error = NULL; + + if ((unwinder = ipc_unwinder_proxy_new_finish (result, &error))) + dex_promise_resolve_object (promise, unwinder); + else + dex_promise_reject (promise, error); +} + +static IpcUnwinder * +create_unwinder (GDBusConnection *connection, + GError **error) +{ + g_autoptr(DexPromise) promise = dex_promise_new (); + ipc_unwinder_proxy_new (connection, G_DBUS_PROXY_FLAGS_NONE, + "org.gnome.Sysprof3", + "/org/gnome/Sysprof3/Unwinder", + NULL, + create_unwinder_cb, + dex_ref (promise)); + return dex_await_object (dex_ref (promise), error); +} + +static DexFuture * +sysprof_user_sampler_prepare_fiber (gpointer user_data) +{ + Prepare *prepare = user_data; + g_autoptr(GDBusConnection) connection = NULL; + g_autoptr(GUnixFDList) fd_list = NULL; + g_autoptr(GError) error = NULL; + GVariantBuilder builder; + gboolean all_failed = TRUE; + guint n_cpu; + + g_assert (prepare != NULL); + g_assert (SYSPROF_IS_RECORDING (prepare->recording)); + g_assert (SYSPROF_IS_USER_SAMPLER (prepare->sampler)); + + if (!(connection = dex_await_object (dex_bus_get (G_BUS_TYPE_SYSTEM), &error))) + return dex_future_new_for_error (g_steal_pointer (&error)); + + if (!dex_await (_sysprof_recording_add_file (prepare->recording, + "/proc/kallsyms", + TRUE), + &error)) + { + _sysprof_recording_diagnostic (prepare->recording, + "Sampler", + "Failed to record copy of “kallsyms” to capture: %s", + error->message); + g_clear_error (&error); + } + + n_cpu = g_get_num_processors (); + fd_list = g_unix_fd_list_new (); + + g_variant_builder_init (&builder, G_VARIANT_TYPE ("a(hi)")); + + for (guint i = 0; i < n_cpu; i++) + { + g_autofd int fd = _perf_event_open (connection, i, prepare->stack_size, &error); + + if (fd == -1) + { + _sysprof_recording_diagnostic (prepare->recording, + "Sampler", + "Failed to load Perf event stream for CPU %d with stack size %u: %s", + i, prepare->stack_size, error->message); + g_clear_error (&error); + } + else + { + int handle = g_unix_fd_list_append (fd_list, fd, &error); + + if (handle == -1) + { + _sysprof_recording_diagnostic (prepare->recording, + "Sampler", + "Out of FDs to add to FDList: %s", + error->message); + g_clear_error (&error); + } + else + { + g_array_append_val (prepare->sampler->perf_fds, fd); + fd = -1; + + g_variant_builder_add (&builder, "(hi)", handle, i); + + all_failed = FALSE; + } + } + } + + if (!all_failed) + { + g_autoptr(IpcUnwinder) unwinder = create_unwinder (connection, &error); + + if (unwinder == NULL) + { + _sysprof_recording_diagnostic (prepare->recording, + "Sampler", + "Failed to locate unwinder service: %s", + error->message); + g_clear_error (&error); + } + else + { + g_autoptr(DexPromise) promise = dex_promise_new (); + int event_fd_handle = g_unix_fd_list_append (fd_list, prepare->sampler->event_fd, NULL); + g_autofd int fd = -1; + + ipc_unwinder_call_unwind (unwinder, + prepare->stack_size, + g_variant_builder_end (&builder), + g_variant_new_handle (event_fd_handle), + fd_list, + NULL, + call_unwind_cb, + dex_ref (promise)); + + fd = dex_await_fd (dex_ref (promise), &error); + + if (fd == -1) + { + _sysprof_recording_diagnostic (prepare->recording, + "Sampler", + "Failed to setup user-space unwinder: %s", + error->message); + g_clear_error (&error); + } + else + { + prepare->sampler->capture_fd = g_steal_fd (&fd); + } + } + } + + g_variant_builder_clear (&builder); + + return dex_future_new_for_boolean (TRUE); +} + +static DexFuture * +sysprof_user_sampler_prepare (SysprofInstrument *instrument, + SysprofRecording *recording) +{ + SysprofUserSampler *self = (SysprofUserSampler *)instrument; + Prepare *prepare; + + g_assert (SYSPROF_IS_INSTRUMENT (instrument)); + g_assert (SYSPROF_IS_RECORDING (recording)); + + prepare = g_new0 (Prepare, 1); + prepare->recording = g_object_ref (recording); + prepare->sampler = g_object_ref (self); + prepare->stack_size = self->stack_size; + + return dex_scheduler_spawn (NULL, 0, + sysprof_user_sampler_prepare_fiber, + prepare, + (GDestroyNotify)prepare_free); +} + +typedef struct _Record +{ + SysprofRecording *recording; + SysprofUserSampler *sampler; + DexFuture *cancellable; +} Record; + +static void +record_free (Record *record) +{ + g_clear_object (&record->recording); + g_clear_object (&record->sampler); + dex_clear (&record->cancellable); + g_free (record); +} + +static DexFuture * +sysprof_user_sampler_record_fiber (gpointer user_data) +{ + SysprofCaptureWriter *writer; + Record *record = user_data; + g_autoptr(GError) error = NULL; + g_autoptr(GSource) muxer_source = NULL; + guint64 exiting = 1234; + + g_assert (record != NULL); + g_assert (SYSPROF_IS_USER_SAMPLER (record->sampler)); + g_assert (SYSPROF_IS_RECORDING (record->recording)); + g_assert (DEX_IS_FUTURE (record->cancellable)); + + writer = _sysprof_recording_writer (record->recording); + + sysprof_user_sampler_ioctl (record->sampler, TRUE); + + g_debug ("Staring muxer for capture_fd"); + muxer_source = sysprof_muxer_source_new (g_steal_fd (&record->sampler->capture_fd), writer); + g_source_set_static_name (muxer_source, "[stack-muxer]"); + g_source_attach (muxer_source, NULL); + + if (!dex_await (dex_ref (record->cancellable), &error)) + g_debug ("UserSampler shutting down for reason: %s", error->message); + + write (record->sampler->event_fd, &exiting, sizeof exiting); + + g_source_destroy (muxer_source); + + sysprof_user_sampler_ioctl (record->sampler, FALSE); + + return dex_future_new_for_boolean (TRUE); +} + +static DexFuture * +sysprof_user_sampler_record (SysprofInstrument *instrument, + SysprofRecording *recording, + GCancellable *cancellable) +{ + SysprofUserSampler *self = (SysprofUserSampler *)instrument; + Record *record; + + g_assert (SYSPROF_IS_INSTRUMENT (instrument)); + g_assert (SYSPROF_IS_RECORDING (recording)); + g_assert (!cancellable || G_IS_CANCELLABLE (cancellable)); + + record = g_new0 (Record, 1); + record->recording = g_object_ref (recording); + record->sampler = g_object_ref (self); + record->cancellable = dex_cancellable_new_from_cancellable (cancellable); + + return dex_scheduler_spawn (NULL, 0, + sysprof_user_sampler_record_fiber, + record, + (GDestroyNotify)record_free); +} + +static void +sysprof_user_sampler_finalize (GObject *object) +{ + SysprofUserSampler *self = (SysprofUserSampler *)object; + + g_clear_pointer (&self->perf_fds, g_array_unref); + + g_clear_fd (&self->capture_fd, NULL); + g_clear_fd (&self->event_fd, NULL); + + G_OBJECT_CLASS (sysprof_user_sampler_parent_class)->finalize (object); +} + +static void +sysprof_user_sampler_class_init (SysprofUserSamplerClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + SysprofInstrumentClass *instrument_class = SYSPROF_INSTRUMENT_CLASS (klass); + + object_class->finalize = sysprof_user_sampler_finalize; + + instrument_class->list_required_policy = sysprof_user_sampler_list_required_policy; + instrument_class->prepare = sysprof_user_sampler_prepare; + instrument_class->record = sysprof_user_sampler_record; +} + +static void +sysprof_user_sampler_init (SysprofUserSampler *self) +{ + self->capture_fd = -1; + self->event_fd = eventfd (0, EFD_CLOEXEC); + + self->perf_fds = g_array_new (FALSE, FALSE, sizeof (int)); + g_array_set_clear_func (self->perf_fds, close_fd); +} + +SysprofInstrument * +sysprof_user_sampler_new (guint stack_size) +{ + SysprofUserSampler *self; + + g_return_val_if_fail (stack_size > 0, NULL); + g_return_val_if_fail (stack_size % sysprof_getpagesize () == 0, NULL); + + self = g_object_new (SYSPROF_TYPE_USER_SAMPLER, NULL); + self->stack_size = stack_size; + + return SYSPROF_INSTRUMENT (self); +} diff --git a/src/libsysprof/sysprof-user-sampler.h b/src/libsysprof/sysprof-user-sampler.h new file mode 100644 index 00000000..2d9e56b5 --- /dev/null +++ b/src/libsysprof/sysprof-user-sampler.h @@ -0,0 +1,43 @@ +/* + * sysprof-user-sampler.h + * + * Copyright 2024 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.h" + +G_BEGIN_DECLS + +#define SYSPROF_TYPE_USER_SAMPLER (sysprof_user_sampler_get_type()) +#define SYSPROF_IS_USER_SAMPLER(obj) G_TYPE_CHECK_INSTANCE_TYPE(obj, SYSPROF_TYPE_USER_SAMPLER) +#define SYSPROF_USER_SAMPLER(obj) G_TYPE_CHECK_INSTANCE_CAST(obj, SYSPROF_TYPE_USER_SAMPLER, SysprofUserSampler) +#define SYSPROF_USER_SAMPLER_CLASS(klass) G_TYPE_CHECK_CLASS_CAST(klass, SYSPROF_TYPE_USER_SAMPLER, SysprofUserSamplerClass) + +typedef struct _SysprofUserSampler SysprofUserSampler; +typedef struct _SysprofUserSamplerClass SysprofUserSamplerClass; + +SYSPROF_AVAILABLE_IN_ALL +GType sysprof_user_sampler_get_type (void) G_GNUC_CONST; +SYSPROF_AVAILABLE_IN_ALL +SysprofInstrument *sysprof_user_sampler_new (guint stack_size); + +G_DEFINE_AUTOPTR_CLEANUP_FUNC (SysprofUserSampler, g_object_unref) + +G_END_DECLS diff --git a/src/libsysprof/sysprof.h b/src/libsysprof/sysprof.h index d30f9fd4..514e332b 100644 --- a/src/libsysprof/sysprof.h +++ b/src/libsysprof/sysprof.h @@ -89,6 +89,7 @@ G_BEGIN_DECLS # include "sysprof-time-span.h" # include "sysprof-tracefd-consumer.h" # include "sysprof-tracer.h" +# include "sysprof-user-sampler.h" #undef SYSPROF_INSIDE G_END_DECLS -- 2.45.2