695 lines
		
	
	
		
			22 KiB
		
	
	
	
		
			Diff
		
	
	
	
	
	
			
		
		
	
	
			695 lines
		
	
	
		
			22 KiB
		
	
	
	
		
			Diff
		
	
	
	
	
	
| From ef191d3f52ad904a9af8908ff5c2a1e44dfd27b0 Mon Sep 17 00:00:00 2001
 | |
| From: Christian Hergert <chergert@redhat.com>
 | |
| 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 <chergert@redhat.com>
 | |
| + *
 | |
| + * 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 <http://www.gnu.org/licenses/>.
 | |
| + *
 | |
| + * SPDX-License-Identifier: GPL-3.0-or-later
 | |
| + */
 | |
| +
 | |
| +#include "config.h"
 | |
| +
 | |
| +#include <sys/ioctl.h>
 | |
| +#include <sys/eventfd.h>
 | |
| +
 | |
| +#include <asm/perf_regs.h>
 | |
| +
 | |
| +#include <glib/gstdio.h>
 | |
| +
 | |
| +#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 <chergert@redhat.com>
 | |
| + *
 | |
| + * 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 <http://www.gnu.org/licenses/>.
 | |
| + *
 | |
| + * 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
 | |
| 
 |