Merge branch 'wip/chergert/translate' into 'master'

Add support for unwinding without frame-pointers

See merge request GNOME/sysprof!110
This commit is contained in:
Christian Hergert
2024-11-03 22:41:43 +00:00
31 changed files with 3772 additions and 21 deletions

View File

@ -39,12 +39,13 @@ need_glib = (need_gtk or
get_option('tools') or get_option('tools') or
get_option('tests')) get_option('tests'))
need_libsysprof = (need_gtk or need_libsysprof = (need_gtk or
get_option('sysprofd') == 'bundled' or
get_option('libsysprof') or get_option('libsysprof') or
get_option('examples') or get_option('examples') or
get_option('tools') or get_option('tools') or
get_option('tests')) get_option('tests'))
dex_req = '0.6' dex_req = '0.9'
glib_req = '2.76.0' glib_req = '2.76.0'
gtk_req = '4.15' gtk_req = '4.15'
polkit_req = '0.105' polkit_req = '0.105'

View File

@ -63,6 +63,7 @@ libsysprof_public_sources = [
'sysprof-time-span.c', 'sysprof-time-span.c',
'sysprof-tracefd-consumer.c', 'sysprof-tracefd-consumer.c',
'sysprof-tracer.c', 'sysprof-tracer.c',
'sysprof-user-sampler.c',
] ]
libsysprof_public_headers = [ libsysprof_public_headers = [
@ -130,6 +131,7 @@ libsysprof_public_headers = [
'sysprof-time-span.h', 'sysprof-time-span.h',
'sysprof-tracefd-consumer.h', 'sysprof-tracefd-consumer.h',
'sysprof-tracer.h', 'sysprof-tracer.h',
'sysprof-user-sampler.h',
] ]
libsysprof_private_sources = [ libsysprof_private_sources = [
@ -147,12 +149,18 @@ libsysprof_private_sources = [
'sysprof-mount-device.c', 'sysprof-mount-device.c',
'sysprof-mount-namespace.c', 'sysprof-mount-namespace.c',
'sysprof-mount.c', 'sysprof-mount.c',
'sysprof-muxer-source.c',
'sysprof-perf-event-stream.c', 'sysprof-perf-event-stream.c',
'sysprof-podman.c', 'sysprof-podman.c',
'sysprof-process-info.c', 'sysprof-process-info.c',
'sysprof-strings.c', 'sysprof-strings.c',
'sysprof-symbol-cache.c', 'sysprof-symbol-cache.c',
'timsort/gtktimsort.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() if debuginfod_dep.found()

View File

@ -0,0 +1,173 @@
/*
* sysprof-muxer-source.c
*
* 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
*/
#include "config.h"
#include <glib/gstdio.h>
#include <glib-unix.h>
#include "sysprof-muxer-source.h"
#define DEFAULT_BUFFER_SIZE (4096*16)
typedef struct _SysprofMuxerSource
{
GSource gsource;
int capture_fd;
SysprofCaptureWriter *writer;
struct {
guint8 *allocation;
guint8 *begin;
guint8 *end;
guint8 *capacity;
gsize to_skip;
} buffer;
} SysprofMuxerSource;
static gboolean
sysprof_muxer_source_size (SysprofMuxerSource *source)
{
return source->buffer.end - source->buffer.begin;
}
static gboolean
sysprof_muxer_source_dispatch (GSource *gsource,
GSourceFunc callback,
gpointer user_data)
{
SysprofMuxerSource *source = (SysprofMuxerSource *)gsource;
gssize n_read;
g_assert (source != NULL);
g_assert (source->writer != NULL);
/* Try to read the next chunk */
n_read = read (source->capture_fd, source->buffer.end, source->buffer.capacity - source->buffer.end);
if (n_read > 0)
{
const SysprofCaptureFrame *frame;
/* Advance tail to what was filled */
source->buffer.end += n_read;
/* Get to next alignment */
if (source->buffer.to_skip)
{
gsize amount = MIN (source->buffer.to_skip, source->buffer.end - source->buffer.begin);
source->buffer.begin += amount;
source->buffer.to_skip -= amount;
}
/* If there is enough to read the frame header, try to read and dispatch
* it in raw form. We assume we're the same endianness here because this
* is coming from the same host (live-unwinder currently).
*/
while (sysprof_muxer_source_size (source) >= sizeof *frame)
{
frame = (const SysprofCaptureFrame *)source->buffer.begin;
if (frame->len <= sysprof_muxer_source_size (source))
{
source->buffer.begin += frame->len;
if (frame->len % sizeof (guint64) != 0)
source->buffer.to_skip = sizeof (guint64) - (frame->len % sizeof (guint64));
/* TODO: Technically for counters/JIT map we need to translate them. */
_sysprof_capture_writer_add_raw (source->writer, frame);
}
if (source->buffer.to_skip > 0 &&
source->buffer.to_skip <= sysprof_muxer_source_size (source))
{
source->buffer.begin += source->buffer.to_skip;
source->buffer.to_skip = 0;
continue;
}
break;
}
/* Move anything left to the head of the buffer so we can
* fill in the entire next frame of data.
*/
if (source->buffer.begin < source->buffer.end)
{
/* TODO: Should we adjust for alignment here? */
memmove (source->buffer.allocation,
source->buffer.begin,
source->buffer.end - source->buffer.begin);
source->buffer.end = source->buffer.allocation + (source->buffer.end - source->buffer.begin);
source->buffer.begin = source->buffer.allocation;
}
else
{
source->buffer.end = source->buffer.allocation;
source->buffer.begin = source->buffer.allocation;
}
}
return G_SOURCE_CONTINUE;
}
static void
sysprof_muxer_source_finalize (GSource *gsource)
{
SysprofMuxerSource *source = (SysprofMuxerSource *)gsource;
g_clear_fd (&source->capture_fd, NULL);
g_clear_pointer (&source->writer, sysprof_capture_writer_unref);
g_clear_pointer (&source->buffer.begin, g_free);
}
static const GSourceFuncs source_funcs = {
.dispatch = sysprof_muxer_source_dispatch,
.finalize = sysprof_muxer_source_finalize,
};
GSource *
sysprof_muxer_source_new (int capture_fd,
SysprofCaptureWriter *writer)
{
SysprofMuxerSource *source;
g_return_val_if_fail (capture_fd > -1, NULL);
g_return_val_if_fail (writer != NULL, NULL);
source = (SysprofMuxerSource *)g_source_new ((GSourceFuncs *)&source_funcs, sizeof (SysprofMuxerSource));
source->capture_fd = capture_fd;
source->writer = sysprof_capture_writer_ref (writer);
source->buffer.allocation = g_malloc (DEFAULT_BUFFER_SIZE);
source->buffer.begin = source->buffer.allocation;
source->buffer.end = source->buffer.allocation;
source->buffer.capacity = source->buffer.allocation + DEFAULT_BUFFER_SIZE;
source->buffer.to_skip = sizeof (SysprofCaptureFileHeader);
g_unix_set_fd_nonblocking (capture_fd, TRUE, NULL);
g_source_add_unix_fd ((GSource *)source, capture_fd, G_IO_IN);
return (GSource *)source;
}

View File

@ -0,0 +1,33 @@
/*
* sysprof-muxer-source.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 <glib.h>
#include <sysprof-capture.h>
G_BEGIN_DECLS
GSource *sysprof_muxer_source_new (int capture_fd,
SysprofCaptureWriter *writer);
G_END_DECLS

View File

@ -177,5 +177,6 @@ gboolean sysprof_perf_event_stream_enable (SysprofPerfEventStream *self,
GError **error); GError **error);
gboolean sysprof_perf_event_stream_disable (SysprofPerfEventStream *self, gboolean sysprof_perf_event_stream_disable (SysprofPerfEventStream *self,
GError **error); GError **error);
GVariant *_sysprof_perf_event_attr_to_variant (const struct perf_event_attr *attr);
G_END_DECLS G_END_DECLS

View File

@ -109,8 +109,8 @@ G_DEFINE_FINAL_TYPE (SysprofPerfEventStream, sysprof_perf_event_stream, G_TYPE_O
static GParamSpec *properties [N_PROPS]; static GParamSpec *properties [N_PROPS];
static GVariant * GVariant *
build_options_dict (const struct perf_event_attr *attr) _sysprof_perf_event_attr_to_variant (const struct perf_event_attr *attr)
{ {
return g_variant_take_ref ( return g_variant_take_ref (
g_variant_new_parsed ("[" g_variant_new_parsed ("["
@ -130,6 +130,8 @@ build_options_dict (const struct perf_event_attr *attr)
"{'sample_period', <%t>}," "{'sample_period', <%t>},"
"{'sample_type', <%t>}," "{'sample_type', <%t>},"
"{'task', <%b>}," "{'task', <%b>},"
"{'sample_stack_user', <%u>},"
"{'sample_regs_user', <%t>},"
"{'type', <%u>}" "{'type', <%u>}"
"]", "]",
(gboolean)!!attr->comm, (gboolean)!!attr->comm,
@ -148,6 +150,8 @@ build_options_dict (const struct perf_event_attr *attr)
(guint64)attr->sample_period, (guint64)attr->sample_period,
(guint64)attr->sample_type, (guint64)attr->sample_type,
(gboolean)!!attr->task, (gboolean)!!attr->task,
(guint32)attr->sample_stack_user,
(guint64)attr->sample_regs_user,
(guint32)attr->type)); (guint32)attr->type));
} }
@ -513,7 +517,7 @@ sysprof_perf_event_stream_new (GDBusConnection *connection,
group_fd_handle = g_unix_fd_list_append (fd_list, group_fd, NULL); group_fd_handle = g_unix_fd_list_append (fd_list, group_fd, NULL);
} }
options = build_options_dict (attr); options = _sysprof_perf_event_attr_to_variant (attr);
g_dbus_connection_call_with_unix_fd_list (connection, g_dbus_connection_call_with_unix_fd_list (connection,
"org.gnome.Sysprof3", "org.gnome.Sysprof3",

View File

@ -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);
}

View File

@ -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_48
GType sysprof_user_sampler_get_type (void) G_GNUC_CONST;
SYSPROF_AVAILABLE_IN_48
SysprofInstrument *sysprof_user_sampler_new (guint stack_size);
G_DEFINE_AUTOPTR_CLEANUP_FUNC (SysprofUserSampler, g_object_unref)
G_END_DECLS

View File

@ -89,6 +89,7 @@ G_BEGIN_DECLS
# include "sysprof-time-span.h" # include "sysprof-time-span.h"
# include "sysprof-tracefd-consumer.h" # include "sysprof-tracefd-consumer.h"
# include "sysprof-tracer.h" # include "sysprof-tracer.h"
# include "sysprof-user-sampler.h"
#undef SYSPROF_INSIDE #undef SYSPROF_INSIDE
G_END_DECLS G_END_DECLS

View File

@ -11,6 +11,8 @@ sysprof_version_conf.set('MINOR_VERSION', sysprof_version[1])
sysprof_version_conf.set('MICRO_VERSION', 0) sysprof_version_conf.set('MICRO_VERSION', 0)
sysprof_version_conf.set('VERSION', meson.project_version()) sysprof_version_conf.set('VERSION', meson.project_version())
pkglibexecdir = join_paths(get_option('prefix'), get_option('libexecdir'))
subdir('libsysprof-capture') subdir('libsysprof-capture')
if need_libsysprof if need_libsysprof
@ -20,6 +22,7 @@ endif
if get_option('sysprofd') == 'bundled' if get_option('sysprofd') == 'bundled'
subdir('sysprofd') subdir('sysprofd')
subdir('sysprof-live-unwinder')
endif endif
if get_option('gtk') if get_option('gtk')

View File

@ -303,6 +303,7 @@ main (int argc,
gboolean scheduler_details = FALSE; gboolean scheduler_details = FALSE;
gboolean system_bus = FALSE; gboolean system_bus = FALSE;
gboolean session_bus = FALSE; gboolean session_bus = FALSE;
int stack_size = 0;
int pid = -1; int pid = -1;
int fd; int fd;
int flags; int flags;
@ -335,6 +336,7 @@ main (int argc,
{ "version", 0, 0, G_OPTION_ARG_NONE, &version, N_("Print the sysprof-cli version and exit") }, { "version", 0, 0, G_OPTION_ARG_NONE, &version, N_("Print the sysprof-cli version and exit") },
{ "buffer-size", 0, 0, G_OPTION_ARG_INT, &n_buffer_pages, N_("The size of the buffer in pages (1 = 1 page)") }, { "buffer-size", 0, 0, G_OPTION_ARG_INT, &n_buffer_pages, N_("The size of the buffer in pages (1 = 1 page)") },
{ "monitor-bus", 0, 0, G_OPTION_ARG_STRING_ARRAY, &monitor_bus, N_("Additional D-Bus address to monitor") }, { "monitor-bus", 0, 0, G_OPTION_ARG_STRING_ARRAY, &monitor_bus, N_("Additional D-Bus address to monitor") },
{ "stack-size", 0, 0, G_OPTION_ARG_INT, &stack_size, N_("Stack size to copy for unwinding in user-space") },
{ NULL } { NULL }
}; };
@ -379,6 +381,10 @@ Examples:\n\
\n\ \n\
# Merge multiple syscap files into one\n\ # Merge multiple syscap files into one\n\
sysprof-cli --merge a.syscap b.syscap > c.syscap\n\ sysprof-cli --merge a.syscap b.syscap > c.syscap\n\
\n\
# Unwind by capturing stack/register contents instead of frame-pointers\n\
# where the stack-size is a multiple of page-size\n\
sysprof-cli --stack-size=8192\n\
")); "));
if (!g_option_context_parse (context, &argc, &argv, &error)) if (!g_option_context_parse (context, &argc, &argv, &error))
@ -533,7 +539,12 @@ Examples:\n\
sysprof_profiler_add_instrument (profiler, sysprof_system_logs_new ()); sysprof_profiler_add_instrument (profiler, sysprof_system_logs_new ());
if (!no_perf) if (!no_perf)
{
if (stack_size == 0)
sysprof_profiler_add_instrument (profiler, sysprof_sampler_new ()); sysprof_profiler_add_instrument (profiler, sysprof_sampler_new ());
else
sysprof_profiler_add_instrument (profiler, sysprof_user_sampler_new (stack_size));
}
if (!no_disk) if (!no_disk)
sysprof_profiler_add_instrument (profiler, sysprof_disk_usage_new ()); sysprof_profiler_add_instrument (profiler, sysprof_disk_usage_new ());

View File

@ -0,0 +1,752 @@
/*
* main.c
*
* 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
*/
#include "config.h"
#include <stdatomic.h>
#include <sys/mman.h>
#include <sys/resource.h>
#include <glib/gstdio.h>
#include <glib-unix.h>
#include <sysprof.h>
#include "sysprof-live-unwinder.h"
#include "sysprof-perf-event-stream-private.h"
#define CAPTURE_BUFFER_SIZE (4096*16)
#define N_PAGES 32
#define DUMP_BYTES(_n, _b, _l) \
G_STMT_START { \
GString *str, *astr; \
gsize _i; \
g_log(G_LOG_DOMAIN, G_LOG_LEVEL_DEBUG, \
" %s = %p [%d]", #_n, _b, (gint)_l); \
str = g_string_sized_new (80); \
astr = g_string_sized_new (16); \
for (_i = 0; _i < _l; _i++) \
{ \
if ((_i % 16) == 0) \
g_string_append_printf (str, "%06x: ", (guint)_i); \
g_string_append_printf (str, " %02x", _b[_i]); \
\
if (g_ascii_isprint(_b[_i])) \
g_string_append_printf (astr, " %c", _b[_i]); \
else \
g_string_append (astr, " ."); \
\
if ((_i % 16) == 15) \
{ \
g_log (G_LOG_DOMAIN, G_LOG_LEVEL_DEBUG, \
"%s %s", str->str, astr->str); \
str->str[0] = str->len = 0; \
astr->str[0] = astr->len = 0; \
} \
else if ((_i % 16) == 7) \
{ \
g_string_append (str, " "); \
g_string_append (astr, " "); \
} \
} \
\
if (_i != 16) \
g_log (G_LOG_DOMAIN, G_LOG_LEVEL_DEBUG, \
"%-56s %s", str->str, astr->str); \
\
g_string_free (str, TRUE); \
g_string_free (astr, TRUE); \
} G_STMT_END
G_DEFINE_AUTOPTR_CLEANUP_FUNC (SysprofCaptureWriter, sysprof_capture_writer_unref)
typedef struct _PerfSource
{
GSource gsource;
SysprofLiveUnwinder *unwinder;
SysprofCaptureWriter *writer;
guint64 map_size;
struct perf_event_mmap_page *map;
guint8 *map_data;
const guint8 *map_data_end;
guint64 map_data_size;
guint64 tail;
guint8 *buffer;
int cpu;
GPid self_pid;
guint stack_size;
} PerfSource;
typedef struct _PerfFDArg
{
int fd;
int cpu;
} PerfFDArg;
SYSPROF_ALIGNED_BEGIN(8);
typedef struct _StackRegs
{
guint64 abi;
guint64 registers[0];
} StackRegs
SYSPROF_ALIGNED_END(8);
SYSPROF_ALIGNED_BEGIN(8);
typedef struct _StackUser
{
guint64 size;
guint8 data[0];
} StackUser
SYSPROF_ALIGNED_END(8);
static GArray *all_perf_fds;
static inline void
realign (gsize *pos,
gsize align)
{
*pos = (*pos + align - 1) & ~(align - 1);
}
static void
handle_event (PerfSource *source,
const SysprofPerfEvent *event)
{
gsize offset;
gint64 time;
g_assert (source != NULL);
g_assert (event != NULL);
switch (event->header.type)
{
case PERF_RECORD_COMM:
offset = strlen (event->comm.comm) + 1;
realign (&offset, sizeof (guint64));
offset += sizeof (GPid) + sizeof (GPid);
memcpy (&time, event->comm.comm + offset, sizeof time);
if (event->comm.pid == event->comm.tid)
{
sysprof_live_unwinder_seen_process (source->unwinder,
time,
source->cpu,
event->comm.pid,
event->comm.comm);
}
break;
case PERF_RECORD_EXIT:
/* Ignore fork exits for now */
if (event->exit.tid != event->exit.pid)
break;
sysprof_live_unwinder_process_exited (source->unwinder,
event->exit.time,
source->cpu,
event->exit.pid);
break;
case PERF_RECORD_FORK:
sysprof_live_unwinder_process_forked (source->unwinder,
event->fork.time,
source->cpu,
event->fork.ptid,
event->fork.tid);
break;
case PERF_RECORD_LOST:
{
char message[64];
g_snprintf (message, sizeof message,
"Lost %"G_GUINT64_FORMAT" samples",
event->lost.lost);
sysprof_capture_writer_add_log (source->writer,
SYSPROF_CAPTURE_CURRENT_TIME,
source->cpu,
-1,
G_LOG_LEVEL_CRITICAL, "Sampler", message);
break;
}
case PERF_RECORD_MMAP:
offset = strlen (event->mmap.filename) + 1;
realign (&offset, sizeof (guint64));
offset += sizeof (GPid) + sizeof (GPid);
memcpy (&time, event->mmap.filename + offset, sizeof time);
sysprof_live_unwinder_track_mmap (source->unwinder,
time,
source->cpu,
event->mmap.pid,
event->mmap.addr,
event->mmap.addr + event->mmap.len,
event->mmap.pgoff,
0,
event->mmap.filename,
NULL);
break;
case PERF_RECORD_MMAP2:
offset = strlen (event->mmap2.filename) + 1;
realign (&offset, sizeof (guint64));
offset += sizeof (GPid) + sizeof (GPid);
memcpy (&time, event->mmap2.filename + offset, sizeof time);
if ((event->header.misc & PERF_RECORD_MISC_MMAP_BUILD_ID) != 0)
{
char build_id[G_N_ELEMENTS (event->mmap2.build_id) * 2 + 1];
guint len = MIN (G_N_ELEMENTS (event->mmap2.build_id), event->mmap2.build_id_size);
for (guint i = 0; i < len; i++)
g_snprintf (&build_id[len*2], 3, "%02x", event->mmap2.build_id[i]);
build_id[len*2] = 0;
sysprof_live_unwinder_track_mmap (source->unwinder,
time,
source->cpu,
event->mmap2.pid,
event->mmap2.addr,
event->mmap2.addr + event->mmap2.len,
event->mmap2.pgoff,
event->mmap2.ino,
event->mmap2.filename,
build_id);
}
else
{
sysprof_live_unwinder_track_mmap (source->unwinder,
time,
source->cpu,
event->mmap2.pid,
event->mmap2.addr,
event->mmap2.addr + event->mmap2.len,
event->mmap2.pgoff,
event->mmap2.ino,
event->mmap2.filename,
NULL);
}
break;
case PERF_RECORD_READ:
break;
case PERF_RECORD_SAMPLE:
{
const guint8 *endptr = (const guint8 *)event + event->header.size;
const guint64 *ips = event->callchain.ips;
int n_ips = event->callchain.n_ips;
guint64 trace[3];
/* We always expect PERF_RECORD_SAMPLE to contain a callchain because
* we need that even if we sample the stack for user-space unwinding.
* Otherwise we lose the blended stack trace.
*/
if (n_ips == 0)
{
if (event->callchain.header.misc & PERF_RECORD_MISC_KERNEL)
{
trace[0] = PERF_CONTEXT_KERNEL;
trace[1] = event->callchain.ip;
trace[2] = PERF_CONTEXT_USER;
ips = trace;
n_ips = 3;
}
else
{
trace[0] = PERF_CONTEXT_USER;
trace[1] = event->callchain.ip;
ips = trace;
n_ips = 2;
}
}
if (source->stack_size && source->stack_size < event->header.size)
{
guint64 dyn_size = *((const guint64 *)endptr - 1);
const StackUser *stack_user = (const StackUser *)(endptr - sizeof (guint64) - source->stack_size - sizeof (StackUser));
const StackRegs *stack_regs = (const StackRegs *)&event->callchain.ips[event->callchain.n_ips];
guint n_regs = ((const guint8 *)stack_user - (const guint8 *)stack_regs->registers) / sizeof (guint64);
#if 0
g_print ("n_ips=%u stack_size=%ld dyn_size=%ld abi=%ld n_regs=%d\n",
n_ips, stack_user->size, dyn_size, stack_regs->abi, n_regs);
#endif
sysprof_live_unwinder_process_sampled_with_stack (source->unwinder,
event->callchain.time,
source->cpu,
event->callchain.pid,
event->callchain.tid,
ips,
n_ips,
stack_user->data,
stack_user->size,
dyn_size,
stack_regs->abi,
stack_regs->registers,
n_regs);
break;
}
sysprof_live_unwinder_process_sampled (source->unwinder,
event->callchain.time,
source->cpu,
event->callchain.pid,
event->callchain.tid,
ips,
n_ips);
break;
}
case PERF_RECORD_THROTTLE:
case PERF_RECORD_UNTHROTTLE:
default:
break;
}
}
static gboolean
perf_source_prepare (GSource *gsource,
int *timeout)
{
*timeout = 50;
return FALSE;
}
static gboolean
perf_source_check (GSource *gsource)
{
PerfSource *source = (PerfSource *)gsource;
guint64 head;
guint64 tail;
atomic_thread_fence (memory_order_acquire);
tail = source->tail;
head = source->map->data_head;
if (head < tail)
tail = head;
return (head - tail) >= sizeof (struct perf_event_header);
}
static gboolean
perf_source_dispatch (GSource *gsource,
GSourceFunc callback,
gpointer user_data)
{
PerfSource *source = (PerfSource *)gsource;
guint64 n_bytes = source->map_data_size;
guint64 mask = n_bytes - 1;
guint64 head;
guint64 tail;
guint us = 0;
guint them = 0;
g_assert (source != NULL);
tail = source->tail;
head = source->map->data_head;
atomic_thread_fence (memory_order_acquire);
if (head < tail)
tail = head;
while ((head - tail) >= sizeof (struct perf_event_header))
{
const SysprofPerfEvent *event;
struct perf_event_header *header;
gboolean is_self = FALSE;
/* Note that:
*
* - perf events are a multiple of 64 bits
* - the perf event header is 64 bits
* - the data area is a multiple of 64 bits
*
* which means there will always be space for one header, which means we
* can safely dereference the size field.
*/
header = (struct perf_event_header *)(gpointer)(source->map_data + (tail & mask));
if (header->size > head - tail)
{
/* The kernel did not generate a complete event.
* I don't think that can happen, but we may as well
* be paranoid.
*/
g_warn_if_reached ();
break;
}
if (source->map_data + (tail & mask) + header->size > source->map_data_end)
{
guint8 *b = source->buffer;
gint n_before;
gint n_after;
n_after = (tail & mask) + header->size - n_bytes;
n_before = header->size - n_after;
memcpy (b, source->map_data + (tail & mask), n_before);
memcpy (b + n_before, source->map_data, n_after);
header = (struct perf_event_header *)(gpointer)b;
}
event = (SysprofPerfEvent *)header;
switch (event->header.type)
{
default:
case PERF_RECORD_COMM:
case PERF_RECORD_EXIT:
case PERF_RECORD_FORK:
case PERF_RECORD_MMAP:
case PERF_RECORD_MMAP2:
break;
case PERF_RECORD_SAMPLE:
is_self = event->callchain.pid == source->self_pid;
break;
case PERF_RECORD_READ:
case PERF_RECORD_THROTTLE:
case PERF_RECORD_UNTHROTTLE:
goto skip_callback;
case PERF_RECORD_LOST:
break;
}
handle_event (source, event);
us += is_self;
them += !is_self;
skip_callback:
tail += header->size;
}
source->tail = tail;
atomic_thread_fence (memory_order_seq_cst);
source->map->data_tail = tail;
sysprof_capture_writer_flush (source->writer);
return G_SOURCE_CONTINUE;
}
static void
perf_source_finalize (GSource *gsource)
{
PerfSource *source = (PerfSource *)gsource;
if (source->map != NULL &&
(gpointer)source->map != MAP_FAILED)
munmap ((gpointer)source->map, source->map_size);
g_clear_pointer (&source->buffer, g_free);
g_clear_pointer (&source->writer, sysprof_capture_writer_unref);
g_clear_object (&source->unwinder);
source->map = NULL;
source->map_data = NULL;
source->map_data_end = NULL;
source->map_data_size = 0;
source->map_size = 0;
source->tail = 0;
}
static const GSourceFuncs source_funcs = {
.prepare = perf_source_prepare,
.check = perf_source_check,
.dispatch = perf_source_dispatch,
.finalize = perf_source_finalize,
};
static gboolean
perf_source_init (PerfSource *source,
int fd,
SysprofLiveUnwinder *unwinder,
SysprofCaptureWriter *writer,
int cpu,
int stack_size,
GError **error)
{
gsize map_size;
guint8 *map;
g_assert (source != NULL);
g_assert (writer != NULL);
g_assert (SYSPROF_IS_LIVE_UNWINDER (unwinder));
g_assert (fd > STDERR_FILENO);
map_size = N_PAGES * sysprof_getpagesize () + sysprof_getpagesize ();
map = mmap (NULL, map_size, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);
if ((gpointer)map == MAP_FAILED)
{
int errsv = errno;
g_set_error_literal (error,
G_FILE_ERROR,
g_file_error_from_errno (errsv),
g_strerror (errsv));
return FALSE;
}
source->writer = sysprof_capture_writer_ref (writer);
source->unwinder = g_object_ref (unwinder);
source->buffer = g_malloc (map_size);
source->map_size = map_size;
source->map = (gpointer)map;
source->map_data = map + sysprof_getpagesize ();
source->map_data_size = N_PAGES * sysprof_getpagesize ();
source->map_data_end = source->map_data + source->map_data_size;
source->tail = 0;
source->self_pid = getpid ();
source->cpu = cpu;
source->stack_size = stack_size;
g_source_add_unix_fd ((GSource *)source, fd, G_IO_IN);
return TRUE;
}
static GSource *
perf_source_new (int perf_fd,
SysprofLiveUnwinder *unwinder,
SysprofCaptureWriter *writer,
int cpu,
int stack_size,
GError **error)
{
GSource *source;
if (perf_fd <= STDERR_FILENO)
{
g_set_error (error,
G_FILE_ERROR,
G_FILE_ERROR_BADF,
"Invalid file-descriptor for perf event stream");
return NULL;
}
source = g_source_new ((GSourceFuncs *)&source_funcs, sizeof (PerfSource));
g_source_set_static_name (source, "[perf-event-stream]");
g_source_set_priority (source, G_PRIORITY_HIGH);
if (!perf_source_init ((PerfSource *)source, perf_fd, unwinder, writer, cpu, stack_size, error))
g_clear_pointer (&source, g_source_unref);
return source;
}
static void
clear_perf_fd (gpointer data)
{
PerfFDArg *arg = data;
if (arg->fd > STDERR_FILENO)
{
close (arg->fd);
arg->fd = -1;
}
}
static gboolean
perf_fd_callback (const char *option_name,
const char *option_value,
gpointer data,
GError **error)
{
PerfFDArg arg = {-1, -1};
if (sscanf (option_value, "%d:%d", &arg.fd, &arg.cpu) < 1)
{
g_set_error (error,
G_FILE_ERROR,
G_FILE_ERROR_BADF,
"--perf-fd must be in the format FD or FD:CPU_NUMBER");
return FALSE;
}
if (arg.fd <= STDERR_FILENO)
{
g_set_error (error,
G_FILE_ERROR,
G_FILE_ERROR_BADF,
"--perf-fd must be >= %d",
STDERR_FILENO);
return FALSE;
}
g_array_append_val (all_perf_fds, arg);
return TRUE;
}
static void
bump_to_max_fd_limit (void)
{
struct rlimit limit;
if (getrlimit (RLIMIT_NOFILE, &limit) == 0)
{
limit.rlim_cur = limit.rlim_max;
if (setrlimit (RLIMIT_NOFILE, &limit) != 0)
g_warning ("Failed to set FD limit to %"G_GSSIZE_FORMAT"",
(gssize)limit.rlim_max);
else
g_debug ("Set RLIMIT_NOFILE to %"G_GSSIZE_FORMAT"",
(gssize)limit.rlim_max);
}
}
static gboolean
exit_callback (gpointer user_data)
{
g_main_loop_quit (user_data);
return G_SOURCE_REMOVE;
}
int
main (int argc,
char *argv[])
{
g_autoptr(SysprofCaptureWriter) writer = NULL;
g_autoptr(SysprofLiveUnwinder) unwinder = NULL;
g_autoptr(GOptionContext) context = NULL;
g_autoptr(GMainLoop) main_loop = NULL;
g_autoptr(GError) error = NULL;
g_autofd int capture_fd = -1;
g_autofd int kallsyms_fd = -1;
g_autofd int event_fd = -1;
int stack_size = 0;
const GOptionEntry entries[] = {
{ "perf-fd", 0, 0, G_OPTION_ARG_CALLBACK, perf_fd_callback, "A file-descriptor to the perf event stream", "FD[:CPU]" },
{ "capture-fd", 0, 0, G_OPTION_ARG_INT, &capture_fd, "A file-descriptor to the sysprof capture", "FD" },
{ "event-fd", 0, 0, G_OPTION_ARG_INT, &event_fd, "A file-descriptor to an event-fd used to notify unwinder should exit", "FD" },
{ "kallsyms", 'k', 0, G_OPTION_ARG_INT, &kallsyms_fd, "Bundle kallsyms provided from passed FD", "FD" },
{ "stack-size", 's', 0, G_OPTION_ARG_INT, &stack_size, "Size of stacks being recorded", "STACK_SIZE" },
{ 0 }
};
main_loop = g_main_loop_new (NULL, FALSE);
all_perf_fds = g_array_new (FALSE, FALSE, sizeof (PerfFDArg));
g_array_set_clear_func (all_perf_fds, clear_perf_fd);
context = g_option_context_new ("- translate perf event stream to sysprof");
g_option_context_add_main_entries (context, entries, NULL);
g_option_context_set_summary (context, "\
This tool is used by sysprofd to process incoming perf events that\n\
include a copy of the stack and register state to the Sysprof capture\n\
format.\n\
\n\
It should be provided two file-descriptors. One is for the perf-event\n\
stream and one is for the Sysprof capture writer.\n\
\n\
Events that are not related to stack traces will also be passed along to\n\
to the capture in the standard Sysprof capture format. That includes mmap\n\
events, process information, and more.\n\
\n\
Examples:\n\
\n\
# FD 3 contains perf_event stream for CPU 1\n\
sysprof-translate --perf-fd=3:1 --capture-fd=4");
if (!g_option_context_parse (context, &argc, &argv, &error))
{
g_printerr ("%s\n", error->message);
return EXIT_FAILURE;
}
if (capture_fd <= STDERR_FILENO)
{
g_printerr ("--capture-fd must be > %d\n", STDERR_FILENO);
return EXIT_FAILURE;
}
writer = sysprof_capture_writer_new_from_fd (g_steal_fd (&capture_fd), CAPTURE_BUFFER_SIZE);
if (all_perf_fds->len == 0)
{
g_printerr ("You must secify at least one --perf-fd\n");
return EXIT_FAILURE;
}
bump_to_max_fd_limit ();
unwinder = sysprof_live_unwinder_new (writer, g_steal_fd (&kallsyms_fd));
for (guint i = 0; i < all_perf_fds->len; i++)
{
const PerfFDArg *arg = &g_array_index (all_perf_fds, PerfFDArg, i);
g_autoptr(GSource) perf_source = NULL;
if (!(perf_source = perf_source_new (arg->fd, unwinder, writer, arg->cpu, stack_size, &error)))
{
g_printerr ("Failed to initialize perf event stream: %s\n",
error->message);
return EXIT_FAILURE;
}
g_source_attach (perf_source, NULL);
}
if (event_fd != -1)
{
g_autoptr(GSource) exit_source = g_unix_fd_source_new (event_fd, G_IO_IN);
g_source_set_callback (exit_source,
exit_callback,
g_main_loop_ref (main_loop),
(GDestroyNotify) g_main_loop_unref);
g_source_attach (exit_source, NULL);
}
g_main_loop_run (main_loop);
g_clear_pointer (&all_perf_fds, g_array_unref);
return EXIT_SUCCESS;
}

View File

@ -0,0 +1,19 @@
sysprof_live_unwinder_deps = [
libsysprof_static_dep,
dependency('libdw'),
]
sysprof_live_unwinder_sources = [
'sysprof-live-process.c',
'sysprof-live-unwinder.c',
'main.c',
]
sysprof_live_unwinder = executable('sysprof-live-unwinder', sysprof_live_unwinder_sources,
dependencies: sysprof_live_unwinder_deps,
c_args: release_flags,
install: true,
install_dir: pkglibexecdir,
)
subdir('tests')

View File

@ -0,0 +1,505 @@
/*
* sysprof-live-pid.c
*
* 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
*/
#include "config.h"
#include <fcntl.h>
#include <unistd.h>
#include <sys/wait.h>
#include <sys/syscall.h>
/* Workaround for linux/fcntl.h also including the
* flock definitions in addition to libc.
*/
#ifndef _LINUX_FCNTL_H
# define _LINUX_FCNTL_H
# include <linux/pidfd.h>
# undef _LINUX_FCNTL_H
#else
# include <linux/pidfd.h>
#endif
#include <linux/perf_event.h>
#include <libelf.h>
#include <elfutils/libdwelf.h>
#include <elfutils/libdwfl.h>
#include <glib/gstdio.h>
#include <sysprof-capture.h>
#include "sysprof-live-process.h"
typedef struct _SysprofLiveProcess
{
Dwfl_Callbacks callbacks;
Dwfl *dwfl;
Elf *elf;
char *root;
GPid pid;
int fd;
} SysprofLiveProcess;
typedef struct _SysprofUnwinder
{
SysprofLiveProcess *process;
const guint8 *stack;
gsize stack_len;
const guint64 *registers;
guint n_registers;
guint64 *addresses;
guint addresses_capacity;
guint addresses_len;
guint64 abi;
guint64 sp;
guint64 pc;
guint64 base_address;
GPid pid;
GPid tid;
} SysprofUnwinder;
static SysprofUnwinder *current_unwinder;
static inline GPid
sysprof_unwinder_next_thread (Dwfl *dwfl,
void *user_data,
void **thread_argp)
{
SysprofUnwinder *unwinder = current_unwinder;
if (*thread_argp == NULL)
{
*thread_argp = unwinder;
return unwinder->tid;
}
return 0;
}
static inline bool
sysprof_unwinder_get_thread (Dwfl *dwfl,
pid_t tid,
void *user_data,
void **thread_argp)
{
SysprofUnwinder *unwinder = current_unwinder;
if (unwinder->tid == tid)
{
*thread_argp = unwinder;
return TRUE;
}
return FALSE;
}
static inline bool
copy_word (const guint8 *data,
Dwarf_Word *result,
guint64 abi)
{
if (abi == PERF_SAMPLE_REGS_ABI_64)
memcpy (result, data, sizeof (guint64));
else if (abi == PERF_SAMPLE_REGS_ABI_32)
memcpy (result, data, sizeof (guint32));
else
g_assert_not_reached ();
return TRUE;
}
static inline bool
sysprof_unwinder_memory_read (Dwfl *dwfl,
Dwarf_Addr addr,
Dwarf_Word *result,
void *user_data)
{
SysprofUnwinder *unwinder = current_unwinder;
if (addr < unwinder->base_address || addr - unwinder->base_address >= unwinder->stack_len)
{
Dwfl_Module *module = NULL;
Elf_Data *data = NULL;
Elf_Scn *section = NULL;
Dwarf_Addr bias;
if (!(module = dwfl_addrmodule (dwfl, addr)) ||
!(section = dwfl_module_address_section (module, &addr, &bias)) ||
!(data = elf_getdata (section, NULL)))
return FALSE;
if (data->d_buf && data->d_size > addr)
return copy_word ((guint8 *)data->d_buf + addr, result, unwinder->abi);
return FALSE;
}
return copy_word (&unwinder->stack[addr - unwinder->base_address], result, unwinder->abi);
}
static inline bool
sysprof_unwinder_set_initial_registers (Dwfl_Thread *thread,
void *user_data)
{
SysprofUnwinder *unwinder = current_unwinder;
dwfl_thread_state_register_pc (thread, unwinder->pc);
if (unwinder->abi == PERF_SAMPLE_REGS_ABI_64)
{
static const int regs_x86_64[] = {0, 3, 2, 1, 4, 5, 6, 7/*sp*/, 9, 10, 11, 12, 13, 14, 15, 16, 8/*ip*/};
for (int i = 0; i < G_N_ELEMENTS (regs_x86_64); i++)
{
int j = regs_x86_64[i];
dwfl_thread_state_registers (thread, i, 1, &unwinder->registers[j]);
}
}
else if (unwinder->abi == PERF_SAMPLE_REGS_ABI_32)
{
static const int regs_i386[] = {0, 2, 3, 1, 7/*sp*/, 6, 4, 5, 8/*ip*/};
for (int i = 0; i < G_N_ELEMENTS (regs_i386); i++)
{
int j = regs_i386[i];
dwfl_thread_state_registers (thread, i, 1, &unwinder->registers[j]);
}
}
return TRUE;
}
static const Dwfl_Thread_Callbacks thread_callbacks = {
sysprof_unwinder_next_thread,
sysprof_unwinder_get_thread,
sysprof_unwinder_memory_read,
sysprof_unwinder_set_initial_registers,
NULL, /* detach */
NULL, /* thread_detach */
};
static inline int
sysprof_unwinder_frame_cb (Dwfl_Frame *frame,
void *user_data)
{
SysprofUnwinder *unwinder = current_unwinder;
Dwarf_Addr pc;
Dwarf_Addr sp;
bool is_activation;
guint8 sp_register_id;
if (unwinder->addresses_len == unwinder->addresses_capacity)
return DWARF_CB_ABORT;
if (!dwfl_frame_pc (frame, &pc, &is_activation))
return DWARF_CB_ABORT;
if (unwinder->abi == PERF_SAMPLE_REGS_ABI_64)
sp_register_id = 7;
else if (unwinder->abi == PERF_SAMPLE_REGS_ABI_32)
sp_register_id = 4;
else
return DWARF_CB_ABORT;
if (dwfl_frame_reg (frame, sp_register_id, &sp) < 0)
return DWARF_CB_ABORT;
unwinder->addresses[unwinder->addresses_len++] = pc;
return DWARF_CB_OK;
}
static inline guint
sysprof_unwind (SysprofLiveProcess *self,
Dwfl *dwfl,
Elf *elf,
GPid pid,
GPid tid,
guint64 abi,
const guint64 *registers,
guint n_registers,
const guint8 *stack,
gsize stack_len,
guint64 *addresses,
guint n_addresses)
{
#if defined(__x86_64__) || defined(__i386__)
SysprofUnwinder unwinder;
g_assert (dwfl != NULL);
g_assert (elf != NULL);
/* Ignore anything byt 32/64 defined abi */
if (!(abi == PERF_SAMPLE_REGS_ABI_32 || abi == PERF_SAMPLE_REGS_ABI_64))
return 0;
/* Make sure we have registers/stack to work with */
if (registers == NULL || stack == NULL || stack_len == 0)
return 0;
/* 9 registers on 32-bit x86, 17 on 64-bit x86_64 */
if (!((abi == PERF_SAMPLE_REGS_ABI_32 && n_registers == 9) ||
(abi == PERF_SAMPLE_REGS_ABI_64 && n_registers == 17)))
return 0;
unwinder.process = self;
unwinder.sp = registers[7];
unwinder.pc = registers[8];
unwinder.base_address = unwinder.sp;
unwinder.addresses = addresses;
unwinder.addresses_capacity = n_addresses;
unwinder.addresses_len = 0;
unwinder.pid = pid;
unwinder.tid = tid;
unwinder.stack = stack;
unwinder.stack_len = stack_len;
unwinder.abi = abi;
unwinder.registers = registers;
unwinder.n_registers = n_registers;
current_unwinder = &unwinder;
dwfl_getthread_frames (dwfl, tid, sysprof_unwinder_frame_cb, NULL);
current_unwinder = NULL;
return unwinder.addresses_len;
#else
return 0;
#endif
}
G_GNUC_NO_INLINE static int
_pidfd_open (int pid,
unsigned flags)
{
int pidfd = syscall (SYS_pidfd_open, pid, flags);
if (pidfd != -1)
{
int old_flags = fcntl (pidfd, F_GETFD);
if (old_flags != -1)
fcntl (pidfd, F_SETFD, old_flags | FD_CLOEXEC);
}
return pidfd;
}
static int
sysprof_live_process_find_elf (Dwfl_Module *module,
void **user_data,
const char *module_name,
Dwarf_Addr base_addr,
char **filename,
Elf **elf)
{
g_assert (current_unwinder != NULL);
g_assert (current_unwinder->process != NULL);
*filename = NULL;
*elf = NULL;
if (module_name[0] == '/')
{
g_autofree char *path = g_strdup_printf ("/proc/%u/root/%s", current_unwinder->pid, module_name);
g_autofd int fd = open (path, O_RDONLY | O_CLOEXEC);
if (fd != -1)
{
*elf = dwelf_elf_begin (fd);
return g_steal_fd (&fd);
}
}
return dwfl_linux_proc_find_elf (module, user_data, module_name, base_addr, filename, elf);
}
static int
sysprof_live_process_find_debuginfo (Dwfl_Module *module,
void **user_data,
const char *module_name,
Dwarf_Addr base_addr,
const char *file_name,
const char *debuglink_file,
GElf_Word debuglink_crc,
char **debuginfo_file_name)
{
return -1;
}
SysprofLiveProcess *
sysprof_live_process_new (GPid pid)
{
SysprofLiveProcess *live_process;
live_process = g_atomic_rc_box_new0 (SysprofLiveProcess);
live_process->pid = pid;
live_process->fd = _pidfd_open (pid, 0);
live_process->root = g_strdup_printf ("/proc/%u/root/", pid);
live_process->callbacks.find_elf = sysprof_live_process_find_elf;
live_process->callbacks.find_debuginfo = sysprof_live_process_find_debuginfo;
live_process->callbacks.debuginfo_path = g_new0 (char *, 2);
live_process->callbacks.debuginfo_path[0] = g_build_filename (live_process->root, "usr/lib/debug", NULL);
return live_process;
}
SysprofLiveProcess *
sysprof_live_process_ref (SysprofLiveProcess *live_process)
{
g_return_val_if_fail (live_process != NULL, NULL);
return g_atomic_rc_box_acquire (live_process);
}
static void
sysprof_live_process_finalize (gpointer data)
{
SysprofLiveProcess *live_process = data;
if (live_process->fd != -1)
{
close (live_process->fd);
live_process->fd = -1;
}
g_clear_pointer (&live_process->elf, elf_end);
g_clear_pointer (&live_process->dwfl, dwfl_end);
g_clear_pointer (&live_process->root, g_free);
g_clear_pointer (&live_process->callbacks.debuginfo_path, g_free);
}
void
sysprof_live_process_unref (SysprofLiveProcess *live_process)
{
g_return_if_fail (live_process != NULL);
g_atomic_rc_box_release_full (live_process, sysprof_live_process_finalize);
}
gboolean
sysprof_live_process_is_active (SysprofLiveProcess *self)
{
g_return_val_if_fail (self != NULL, FALSE);
return self->fd > -1;
}
static Dwfl *
sysprof_live_process_get_dwfl (SysprofLiveProcess *self)
{
g_assert (self != NULL);
if G_UNLIKELY (self->dwfl == NULL)
{
self->dwfl = dwfl_begin (&self->callbacks);
dwfl_linux_proc_report (self->dwfl, self->pid);
dwfl_report_end (self->dwfl, NULL, NULL);
if (self->fd > -1)
{
char path[64];
g_autofd int exe_fd = -1;
g_snprintf (path, sizeof path, "/proc/%u/exe", self->pid);
exe_fd = open (path, O_RDONLY);
if (exe_fd > -1)
{
self->elf = elf_begin (exe_fd, ELF_C_READ_MMAP, NULL);
if (self->elf != NULL)
dwfl_attach_state (self->dwfl, self->elf, self->pid, &thread_callbacks, self);
}
}
else
g_warning ("Attmpting to load exited process\n");
}
return self->dwfl;
}
guint
sysprof_live_process_unwind (SysprofLiveProcess *self,
GPid tid,
guint64 abi,
const guint8 *stack,
gsize stack_len,
const guint64 *registers,
guint n_registers,
guint64 *addresses,
guint n_addresses)
{
#if defined(__x86_64__) || defined(__i386__)
Dwfl *dwfl;
g_assert (self != NULL);
g_assert (stack != NULL);
g_assert (registers != NULL);
g_assert (addresses != NULL);
if (!sysprof_live_process_is_active (self))
return 0;
if (!(dwfl = sysprof_live_process_get_dwfl (self)))
return 0;
if (self->elf == NULL)
return 0;
g_assert (self->dwfl != NULL);
g_assert (self->elf != NULL);
return sysprof_unwind (self,
self->dwfl,
self->elf,
self->pid,
tid,
abi,
registers,
n_registers,
stack,
stack_len,
addresses,
n_addresses);
#else
return 0;
#endif
}
void
sysprof_live_process_add_map (SysprofLiveProcess *self,
guint64 begin,
guint64 end,
guint64 offset,
guint64 inode,
const char *filename)
{
g_assert (self != NULL);
/* We'll reparse VMAs on next use */
g_clear_pointer (&self->dwfl, dwfl_end);
g_clear_pointer (&self->elf, elf_end);
}

View File

@ -0,0 +1,50 @@
/*
* sysprof-live-pid.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 <glib.h>
G_BEGIN_DECLS
typedef struct _SysprofLiveProcess SysprofLiveProcess;
SysprofLiveProcess *sysprof_live_process_new (GPid pid);
SysprofLiveProcess *sysprof_live_process_ref (SysprofLiveProcess *self);
void sysprof_live_process_unref (SysprofLiveProcess *self);
gboolean sysprof_live_process_is_active (SysprofLiveProcess *self);
void sysprof_live_process_add_map (SysprofLiveProcess *self,
guint64 begin,
guint64 end,
guint64 offset,
guint64 inode,
const char *filename);
guint sysprof_live_process_unwind (SysprofLiveProcess *self,
GPid tid,
guint64 abi,
const guint8 *stack,
gsize stack_len,
const guint64 *registers,
guint n_registers,
guint64 *addresses,
guint n_addresses);
G_END_DECLS

View File

@ -0,0 +1,427 @@
/*
* sysprof-live-unwinder.c
*
* 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
*/
#include "config.h"
#include <fcntl.h>
#include <unistd.h>
#include <linux/perf_event.h>
#include <glib/gstdio.h>
#include "sysprof-live-process.h"
#include "sysprof-live-unwinder.h"
#include "sysprof-maps-parser-private.h"
struct _SysprofLiveUnwinder
{
GObject parent_instance;
SysprofCaptureWriter *writer;
GHashTable *live_pids_by_pid;
};
G_DEFINE_FINAL_TYPE (SysprofLiveUnwinder, sysprof_live_unwinder, G_TYPE_OBJECT)
enum {
CLOSED,
N_SIGNALS
};
static guint signals[N_SIGNALS];
static char *
sysprof_live_unwinder_read_file (SysprofLiveUnwinder *self,
const char *path,
gboolean insert_into_capture)
{
gint64 when = SYSPROF_CAPTURE_CURRENT_TIME;
char *contents = NULL;
gsize len = 0;
gsize offset = 0;
g_assert (SYSPROF_IS_LIVE_UNWINDER (self));
g_assert (self->writer != NULL);
if (!g_file_get_contents (path, &contents, &len, NULL))
return NULL;
if (insert_into_capture)
{
while (len > 0)
{
gsize this_write = MIN (len, 4096*4);
if (!sysprof_capture_writer_add_file (self->writer,
when,
-1,
-1,
path,
this_write == len,
(const guint8 *)&contents[offset], this_write))
break;
len -= this_write;
offset += this_write;
}
}
return contents;
}
static char *
sysprof_live_unwinder_read_pid_file (SysprofLiveUnwinder *self,
GPid pid,
const char *path_part,
gboolean insert_into_capture)
{
g_autofree char *path = NULL;
g_assert (SYSPROF_IS_LIVE_UNWINDER (self));
g_assert (self->writer != NULL);
path = g_strdup_printf ("/proc/%d/%s", pid, path_part);
return sysprof_live_unwinder_read_file (self, path, insert_into_capture);
}
static SysprofLiveProcess *
sysprof_live_unwinder_find_pid (SysprofLiveUnwinder *self,
GPid pid,
gboolean send_comm)
{
SysprofLiveProcess *live_pid;
g_assert (SYSPROF_IS_LIVE_UNWINDER (self));
if (pid < 0)
return NULL;
if (!(live_pid = g_hash_table_lookup (self->live_pids_by_pid, GINT_TO_POINTER (pid))))
{
gint64 now = SYSPROF_CAPTURE_CURRENT_TIME;
live_pid = sysprof_live_process_new (pid);
g_hash_table_replace (self->live_pids_by_pid, GINT_TO_POINTER (pid), live_pid);
if (send_comm)
{
g_autofree char *path = g_strdup_printf ("/proc/%d/comm", pid);
g_autofree char *comm = NULL;
gsize len;
if (g_file_get_contents (path, &comm, &len, NULL))
{
g_autofree char *tmp = comm;
comm = g_strstrip (g_utf8_make_valid (tmp, len));
sysprof_capture_writer_add_process (self->writer, now, -1, pid, comm);
}
}
if (sysprof_live_process_is_active (live_pid))
{
g_autofree char *mountinfo = sysprof_live_unwinder_read_pid_file (self, pid, "mountinfo", TRUE);
g_autofree char *maps = sysprof_live_unwinder_read_pid_file (self, pid, "maps", FALSE);
if (maps != NULL)
{
SysprofMapsParser maps_parser;
guint64 begin, end, offset, inode;
char *filename;
sysprof_maps_parser_init (&maps_parser, maps, -1);
while (sysprof_maps_parser_next (&maps_parser, &begin, &end, &offset, &inode, &filename))
{
sysprof_live_process_add_map (live_pid, begin, end, offset, inode, filename);
sysprof_capture_writer_add_map (self->writer, now, -1, pid,
begin, end, offset,
inode, filename);
g_free (filename);
}
}
}
}
return live_pid;
}
static void
sysprof_live_unwinder_mine_pids (SysprofLiveUnwinder *self)
{
g_autoptr(GDir) dir = NULL;
const char *name;
g_assert (SYSPROF_IS_LIVE_UNWINDER (self));
g_assert (self->writer != NULL);
if (!(dir = g_dir_open ("/proc", 0, NULL)))
return;
while ((name = g_dir_read_name (dir)))
{
GPid pid;
if (!g_ascii_isdigit (*name))
continue;
if (!(pid = atoi (name)))
continue;
sysprof_live_unwinder_find_pid (self, pid, TRUE);
}
}
static void
sysprof_live_unwinder_finalize (GObject *object)
{
SysprofLiveUnwinder *self = (SysprofLiveUnwinder *)object;
g_clear_pointer (&self->writer, sysprof_capture_writer_unref);
g_clear_pointer (&self->live_pids_by_pid, g_hash_table_unref);
G_OBJECT_CLASS (sysprof_live_unwinder_parent_class)->finalize (object);
}
static void
sysprof_live_unwinder_class_init (SysprofLiveUnwinderClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
object_class->finalize = sysprof_live_unwinder_finalize;
signals[CLOSED] =
g_signal_new ("closed",
G_TYPE_FROM_CLASS (klass),
G_SIGNAL_RUN_LAST,
0,
NULL, NULL,
NULL,
G_TYPE_NONE, 0);
}
static void
sysprof_live_unwinder_init (SysprofLiveUnwinder *self)
{
self->live_pids_by_pid = g_hash_table_new_full (NULL,
NULL,
NULL,
(GDestroyNotify)sysprof_live_process_unref);
}
SysprofLiveUnwinder *
sysprof_live_unwinder_new (SysprofCaptureWriter *writer,
int kallsyms_fd)
{
SysprofLiveUnwinder *self;
g_autofree char *mounts = NULL;
g_return_val_if_fail (writer != NULL, NULL);
self = g_object_new (SYSPROF_TYPE_LIVE_UNWINDER, NULL);
self->writer = sysprof_capture_writer_ref (writer);
if (kallsyms_fd != -1)
{
sysprof_capture_writer_add_file_fd (writer,
SYSPROF_CAPTURE_CURRENT_TIME,
-1,
-1,
"/proc/kallsyms",
kallsyms_fd);
close (kallsyms_fd);
}
mounts = sysprof_live_unwinder_read_file (self, "/proc/mounts", TRUE);
sysprof_live_unwinder_mine_pids (self);
return self;
}
void
sysprof_live_unwinder_seen_process (SysprofLiveUnwinder *self,
gint64 time,
int cpu,
GPid pid,
const char *comm)
{
G_GNUC_UNUSED SysprofLiveProcess *live_pid;
g_assert (SYSPROF_IS_LIVE_UNWINDER (self));
live_pid = sysprof_live_unwinder_find_pid (self, pid, FALSE);
sysprof_capture_writer_add_process (self->writer, time, cpu, pid, comm);
}
void
sysprof_live_unwinder_process_exited (SysprofLiveUnwinder *self,
gint64 time,
int cpu,
GPid pid)
{
g_assert (SYSPROF_IS_LIVE_UNWINDER (self));
sysprof_capture_writer_add_exit (self->writer, time, cpu, pid);
}
void
sysprof_live_unwinder_process_forked (SysprofLiveUnwinder *self,
gint64 time,
int cpu,
GPid parent_tid,
GPid tid)
{
g_assert (SYSPROF_IS_LIVE_UNWINDER (self));
sysprof_capture_writer_add_fork (self->writer, time, cpu, parent_tid, tid);
}
void
sysprof_live_unwinder_track_mmap (SysprofLiveUnwinder *self,
gint64 time,
int cpu,
GPid pid,
SysprofCaptureAddress begin,
SysprofCaptureAddress end,
SysprofCaptureAddress offset,
guint64 inode,
const char *filename,
const char *build_id)
{
G_GNUC_UNUSED SysprofLiveProcess *live_pid;
g_assert (SYSPROF_IS_LIVE_UNWINDER (self));
live_pid = sysprof_live_unwinder_find_pid (self, pid, TRUE);
if (build_id != NULL)
sysprof_capture_writer_add_map_with_build_id (self->writer, time, cpu, pid,
begin, end, offset,
inode, filename,
build_id);
else
sysprof_capture_writer_add_map (self->writer, time, cpu, pid,
begin, end, offset,
inode, filename);
sysprof_live_process_add_map (live_pid, begin, end, offset, inode, filename);
}
void
sysprof_live_unwinder_process_sampled (SysprofLiveUnwinder *self,
gint64 time,
int cpu,
GPid pid,
GPid tid,
const SysprofCaptureAddress *addresses,
guint n_addresses)
{
G_GNUC_UNUSED SysprofLiveProcess *live_pid;
g_assert (SYSPROF_IS_LIVE_UNWINDER (self));
live_pid = sysprof_live_unwinder_find_pid (self, pid, TRUE);
sysprof_capture_writer_add_sample (self->writer, time, cpu, pid, tid,
addresses, n_addresses);
}
void
sysprof_live_unwinder_process_sampled_with_stack (SysprofLiveUnwinder *self,
gint64 time,
int cpu,
GPid pid,
GPid tid,
const SysprofCaptureAddress *addresses,
guint n_addresses,
const guint8 *stack,
guint64 stack_size,
guint64 stack_dyn_size,
guint64 abi,
const guint64 *registers,
guint n_registers)
{
SysprofLiveProcess *live_pid;
SysprofCaptureAddress unwound[256];
gboolean found_user = FALSE;
guint pos;
g_assert (SYSPROF_IS_LIVE_UNWINDER (self));
g_assert (stack != NULL);
g_assert (stack_dyn_size <= stack_size);
if (stack_dyn_size == 0 || n_addresses >= G_N_ELEMENTS (unwound))
{
sysprof_live_unwinder_process_sampled (self, time, cpu, pid, tid, addresses, n_addresses);
return;
}
live_pid = sysprof_live_unwinder_find_pid (self, pid, TRUE);
/* Copy addresses over (which might be kernel, context-switch, etc until
* we get to the PERF_CONTEXT_USER. We'll decode the stack right into the
* location after that.
*/
for (pos = 0; pos < n_addresses; pos++)
{
unwound[pos] = addresses[pos];
if (addresses[pos] == PERF_CONTEXT_USER)
{
found_user = TRUE;
break;
}
}
/* If we didn't find a user context (but we have a stack size) synthesize
* the PERF_CONTEXT_USER now.
*/
if (!found_user && pos < G_N_ELEMENTS (unwound))
unwound[pos++] = PERF_CONTEXT_USER;
/* Now request the live process unwind the user-space stack */
if (pos < G_N_ELEMENTS (unwound))
{
guint n_unwound;
n_unwound = sysprof_live_process_unwind (live_pid,
tid,
abi,
stack,
stack_dyn_size,
registers,
n_registers,
&unwound[pos],
G_N_ELEMENTS (unwound) - pos);
/* Only take DWARF unwind if it was better */
if (pos + n_unwound > n_addresses)
{
addresses = unwound;
n_addresses = pos + n_unwound;
}
}
sysprof_capture_writer_add_sample (self->writer, time, cpu, pid, tid, addresses, n_addresses);
}

View File

@ -0,0 +1,79 @@
/*
* sysprof-live-unwinder.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.h>
G_BEGIN_DECLS
#define SYSPROF_TYPE_LIVE_UNWINDER (sysprof_live_unwinder_get_type())
G_DECLARE_FINAL_TYPE (SysprofLiveUnwinder, sysprof_live_unwinder, SYSPROF, LIVE_UNWINDER, GObject)
SysprofLiveUnwinder *sysprof_live_unwinder_new (SysprofCaptureWriter *writer,
int kallsyms_fd);
void sysprof_live_unwinder_seen_process (SysprofLiveUnwinder *self,
gint64 time,
int cpu,
GPid pid,
const char *comm);
void sysprof_live_unwinder_process_exited (SysprofLiveUnwinder *self,
gint64 time,
int cpu,
GPid pid);
void sysprof_live_unwinder_process_forked (SysprofLiveUnwinder *self,
gint64 time,
int cpu,
GPid parent_tid,
GPid tid);
void sysprof_live_unwinder_track_mmap (SysprofLiveUnwinder *self,
gint64 time,
int cpu,
GPid pid,
SysprofCaptureAddress begin,
SysprofCaptureAddress end,
SysprofCaptureAddress offset,
guint64 inode,
const char *filename,
const char *build_id);
void sysprof_live_unwinder_process_sampled (SysprofLiveUnwinder *self,
gint64 time,
int cpu,
GPid pid,
GPid tid,
const SysprofCaptureAddress *addresses,
guint n_addresses);
void sysprof_live_unwinder_process_sampled_with_stack (SysprofLiveUnwinder *self,
gint64 time,
int cpu,
GPid pid,
GPid tid,
const SysprofCaptureAddress *addresses,
guint n_addresses,
const guint8 *stack,
guint64 stack_size,
guint64 stack_dyn_size,
guint64 abi,
const guint64 *registers,
guint n_registers);
G_END_DECLS

View File

@ -0,0 +1,35 @@
sysprof_live_unwinder_test_env = [
'G_DEBUG=gc-friendly',
'GSETTINGS_BACKEND=memory',
'MALLOC_CHECK_=2',
]
sysprof_live_unwinder_testsuite_c_args = [
'-DG_ENABLE_DEBUG',
'-UG_DISABLE_ASSERT',
'-UG_DISABLE_CAST_CHECKS',
'-DBUILDDIR="@0@"'.format(meson.current_build_dir()),
]
sysprof_live_unwinder_testsuite = {
'test-live-unwinder' : {'skip': true},
}
sysprof_live_unwinder_testsuite_deps = [
libsysprof_static_dep,
]
if polkit_agent_dep.found()
sysprof_live_unwinder_testsuite_deps += polkit_agent_dep
endif
foreach test, params: sysprof_live_unwinder_testsuite
test_exe = executable(test, '@0@.c'.format(test),
c_args: sysprof_live_unwinder_testsuite_c_args,
dependencies: sysprof_live_unwinder_testsuite_deps,
)
if not params.get('skip', false)
test(test, test_exe, env: sysprof_live_unwinder_test_env)
endif
endforeach

View File

@ -0,0 +1,391 @@
/*
* test-live-unwinder.c
*
* 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
*/
#include "config.h"
#include <fcntl.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <glib/gstdio.h>
#include <gio/gio.h>
#include <libdex.h>
#include <sysprof.h>
#include "sysprof-perf-event-stream-private.h"
#if HAVE_POLKIT_AGENT
# define POLKIT_AGENT_I_KNOW_API_IS_SUBJECT_TO_CHANGE
# include <polkit/polkit.h>
# include <polkitagent/polkitagent.h>
#endif
#include <asm/perf_regs.h>
#define N_WAKEUP_EVENTS 149
/* 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 */
static gboolean sample_stack;
static char *kallsyms = NULL;
static int sample_stack_size = 8192;
static void
open_perf_stream_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)))
{
int handle;
int fd;
g_variant_get (ret, "(h)", &handle);
if (-1 != (fd = g_unix_fd_list_get (fd_list, handle, &error)))
{
dex_promise_resolve_fd (promise, g_steal_fd (&fd));
return;
}
}
g_assert (error != NULL);
dex_promise_reject (promise, g_steal_pointer (&error));
}
static int
open_perf_stream (GDBusConnection *bus,
int cpu,
GError **error)
{
struct perf_event_attr attr = {0};
gboolean with_mmap2 = TRUE;
gboolean use_software = FALSE;
g_assert (G_IS_DBUS_CONNECTION (bus));
g_assert (cpu >= 0);
g_assert (error != NULL);
try_again:
attr.sample_type = PERF_SAMPLE_IP
| PERF_SAMPLE_TID
| PERF_SAMPLE_IDENTIFIER
| PERF_SAMPLE_CALLCHAIN
| PERF_SAMPLE_TIME;
if (sample_stack)
{
attr.sample_type |= (PERF_SAMPLE_REGS_USER | PERF_SAMPLE_STACK_USER);
attr.sample_stack_user = sample_stack_size;
attr.sample_regs_user = SYSPROF_ARCH_PREFERRED_REGS;
}
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.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;
}
{
g_autoptr(GVariant) options = _sysprof_perf_event_attr_to_variant (&attr);
g_autoptr(DexPromise) promise = dex_promise_new ();
g_autofd int fd = -1;
g_dbus_connection_call_with_unix_fd_list (bus,
"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_ALLOW_INTERACTIVE_AUTHORIZATION,
G_MAXUINT,
NULL,
dex_promise_get_cancellable (promise),
open_perf_stream_cb,
dex_ref (promise));
fd = dex_await_fd (dex_ref (promise), error);
if (*error == NULL)
{
g_printerr ("CPU[%d]: opened perf_event stream as FD %d\n", cpu, fd);
return g_steal_fd (&fd);
}
fd = -1;
}
if (with_mmap2)
{
g_clear_error (error);
with_mmap2 = FALSE;
goto try_again;
}
if (use_software == FALSE)
{
g_clear_error (error);
with_mmap2 = TRUE;
use_software = TRUE;
goto try_again;
}
g_assert (*error != NULL);
return -1;
}
static DexFuture *
main_fiber (gpointer user_data)
{
g_autoptr(GSubprocessLauncher) launcher = NULL;
g_autoptr(GDBusConnection) bus = NULL;
g_autoptr(GSubprocess) subprocess = NULL;
g_autoptr(GPtrArray) argv = NULL;
g_autoptr(GError) error = NULL;
g_autofd int writer_fd = -1;
int n_cpu = g_get_num_processors ();
int next_target_fd = 3;
/* Get our bus we will use for authorization */
if (!(bus = dex_await_object (dex_bus_get (G_BUS_TYPE_SYSTEM), &error)))
return dex_future_new_for_error (g_steal_pointer (&error));
/* Setup our launcher we'll use to map FDs into the translator */
launcher = g_subprocess_launcher_new (0);
/* Setup our argv which will notify the child about where to
* find the FDs containing perf event streams.
*/
argv = g_ptr_array_new_with_free_func (g_free);
g_ptr_array_add (argv, g_build_filename (BUILDDIR, "..", "sysprof-live-unwinder", NULL));
/* Provide kallsyms from the file provided */
if (kallsyms != NULL)
{
int fd = open (kallsyms, O_RDONLY | O_CLOEXEC);
if (fd != -1)
{
g_subprocess_launcher_take_fd (launcher, fd, next_target_fd);
g_ptr_array_add (argv, g_strdup_printf ("--kallsyms=%d", next_target_fd++));
}
}
if (sample_stack)
g_ptr_array_add (argv, g_strdup_printf ("--stack-size=%u", sample_stack_size));
g_printerr ("sysprof-live-unwinder at %s\n", (const char *)argv->pdata[0]);
/* First try to open a perf_event stream for as many CPUs as we
* can before we get complaints from the kernel.
*/
for (int cpu = 0; cpu < n_cpu; cpu++)
{
g_autoptr(GError) cpu_error = NULL;
g_autofd int perf_fd = open_perf_stream (bus, cpu, &cpu_error);
if (perf_fd == -1)
{
g_printerr ("CPU[%d]: %s\n", cpu, cpu_error->message);
continue;
}
if (0 != ioctl (perf_fd, PERF_EVENT_IOC_ENABLE))
{
int errsv = errno;
g_warning ("Failed to enable perf_fd: %s", g_strerror (errsv));
}
g_ptr_array_add (argv, g_strdup_printf ("--perf-fd=%d:%d", next_target_fd, cpu));
g_subprocess_launcher_take_fd (launcher, g_steal_fd (&perf_fd), next_target_fd);
next_target_fd++;
}
/* Now create a FD for our destination capture. */
if (-1 == (writer_fd = open ("translated.syscap", O_CREAT|O_RDWR|O_CLOEXEC, 0664)) ||
ftruncate (writer_fd, 0) != 0)
return dex_future_new_for_errno (errno);
g_ptr_array_add (argv, g_strdup_printf ("--capture-fd=%d", next_target_fd));
g_subprocess_launcher_take_fd (launcher, g_steal_fd (&writer_fd), next_target_fd);
next_target_fd++;
/* Null-terminate our argv */
g_ptr_array_add (argv, NULL);
/* Spawn our worker process with the perf FDs and writer provided */
if (!(subprocess = g_subprocess_launcher_spawnv (launcher,
(const char * const *)argv->pdata,
&error)))
return dex_future_new_for_error (g_steal_pointer (&error));
/* Now wait for the translation process to complete */
if (!dex_await_boolean (dex_subprocess_wait_check (subprocess), &error))
return dex_future_new_for_error (g_steal_pointer (&error));
return dex_future_new_true ();
}
static DexFuture *
finally_cb (DexFuture *future,
gpointer user_data)
{
g_autoptr(GError) error = NULL;
GMainLoop *main_loop = user_data;
if (!dex_await (dex_ref (future), &error))
{
g_printerr ("Error: %s\n", error->message);
exit (EXIT_FAILURE);
}
g_main_loop_quit (main_loop);
return NULL;
}
int
main (int argc,
char *argv[])
{
#if HAVE_POLKIT_AGENT
PolkitAgentListener *polkit = NULL;
PolkitSubject *subject = NULL;
#endif
g_autoptr(GOptionContext) context = NULL;
g_autoptr(GMainLoop) main_loop = NULL;
g_autoptr(GError) error = NULL;
DexFuture *future;
GOptionEntry entries[] = {
{ "sample-stack", 's', 0, G_OPTION_ARG_NONE, &sample_stack, "If the stack should be sampled for user-space unwinding" },
{ "sample-stack-size", 'S', 0, G_OPTION_ARG_INT, &sample_stack_size, "If size of the stack to sample in bytes" },
{ "kallsyms", 'k', 0, G_OPTION_ARG_FILENAME, &kallsyms, "Specify kallsyms for use" },
{ NULL }
};
sysprof_clock_init ();
dex_init ();
main_loop = g_main_loop_new (NULL, FALSE);
context = g_option_context_new ("- test sysprof-live-unwinder");
g_option_context_add_main_entries (context, entries, NULL);
if (!g_option_context_parse (context, &argc, &argv, &error))
{
g_printerr ("%s\n", error->message);
return EXIT_FAILURE;
}
#if HAVE_POLKIT_AGENT
/* Start polkit agent so that we can elevate privileges from a TTY */
if (g_getenv ("DESKTOP_SESSION") == NULL &&
(subject = polkit_unix_process_new_for_owner (getpid (), 0, -1)))
{
g_autoptr(GError) pkerror = NULL;
polkit = polkit_agent_text_listener_new (NULL, NULL);
polkit_agent_listener_register (polkit,
POLKIT_AGENT_REGISTER_FLAGS_NONE,
subject,
NULL,
NULL,
&pkerror);
if (pkerror != NULL)
{
g_dbus_error_strip_remote_error (pkerror);
g_printerr ("Failed to register polkit agent: %s\n",
pkerror->message);
}
}
#endif
future = dex_scheduler_spawn (NULL, 0, main_fiber, NULL, NULL);
future = dex_future_finally (future,
finally_cb,
g_main_loop_ref (main_loop),
(GDestroyNotify) g_main_loop_unref);
dex_future_disown (future);
g_main_loop_run (main_loop);
g_clear_pointer (&kallsyms, g_free);
return EXIT_SUCCESS;
}

View File

@ -57,6 +57,7 @@ sysprof_sources = [
'sysprof-sidebar.c', 'sysprof-sidebar.c',
'sysprof-single-model.c', 'sysprof-single-model.c',
'sysprof-split-layer.c', 'sysprof-split-layer.c',
'sysprof-stack-size.c',
'sysprof-storage-section.c', 'sysprof-storage-section.c',
'sysprof-symbol-label.c', 'sysprof-symbol-label.c',
'sysprof-task-row.c', 'sysprof-task-row.c',

View File

@ -31,6 +31,7 @@
#include "sysprof-power-profiles.h" #include "sysprof-power-profiles.h"
#include "sysprof-recording-pad.h" #include "sysprof-recording-pad.h"
#include "sysprof-recording-template.h" #include "sysprof-recording-template.h"
#include "sysprof-stack-size.h"
#include "sysprof-window.h" #include "sysprof-window.h"
G_DEFINE_AUTOPTR_CLEANUP_FUNC (SysprofCaptureWriter, sysprof_capture_writer_unref) G_DEFINE_AUTOPTR_CLEANUP_FUNC (SysprofCaptureWriter, sysprof_capture_writer_unref)
@ -56,6 +57,7 @@ struct _SysprofGreeter
GtkSwitch *bundle_symbols; GtkSwitch *bundle_symbols;
GtkButton *record_to_memory; GtkButton *record_to_memory;
AdwComboRow *power_combo; AdwComboRow *power_combo;
AdwComboRow *sample_user_stack_size;
SysprofRecordingTemplate *recording_template; SysprofRecordingTemplate *recording_template;
}; };
@ -455,6 +457,26 @@ translate_power_profile (GtkStringObject *strobj)
return g_strdup (str); return g_strdup (str);
} }
static void
on_stack_size_changed_cb (SysprofGreeter *self,
GParamSpec *pspec,
AdwComboRow *row)
{
GObject *item;
g_assert (SYSPROF_IS_GREETER (self));
g_assert (ADW_IS_COMBO_ROW (row));
if ((item = adw_combo_row_get_selected_item (row)))
{
guint stack_size = sysprof_stack_size_get_size (SYSPROF_STACK_SIZE (item));
g_object_set (self->recording_template,
"stack-size", stack_size,
NULL);
}
}
static void static void
sysprof_greeter_dispose (GObject *object) sysprof_greeter_dispose (GObject *object)
{ {
@ -492,6 +514,7 @@ sysprof_greeter_class_init (SysprofGreeterClass *klass)
gtk_widget_class_bind_template_child (widget_class, SysprofGreeter, recording_template); gtk_widget_class_bind_template_child (widget_class, SysprofGreeter, recording_template);
gtk_widget_class_bind_template_child (widget_class, SysprofGreeter, sample_javascript_stacks); gtk_widget_class_bind_template_child (widget_class, SysprofGreeter, sample_javascript_stacks);
gtk_widget_class_bind_template_child (widget_class, SysprofGreeter, sample_native_stacks); gtk_widget_class_bind_template_child (widget_class, SysprofGreeter, sample_native_stacks);
gtk_widget_class_bind_template_child (widget_class, SysprofGreeter, sample_user_stack_size);
gtk_widget_class_bind_template_child (widget_class, SysprofGreeter, sidebar_list_box); gtk_widget_class_bind_template_child (widget_class, SysprofGreeter, sidebar_list_box);
gtk_widget_class_bind_template_child (widget_class, SysprofGreeter, view_stack); gtk_widget_class_bind_template_child (widget_class, SysprofGreeter, view_stack);
@ -507,6 +530,7 @@ sysprof_greeter_class_init (SysprofGreeterClass *klass)
g_type_ensure (SYSPROF_TYPE_ENTRY_POPOVER); g_type_ensure (SYSPROF_TYPE_ENTRY_POPOVER);
g_type_ensure (SYSPROF_TYPE_RECORDING_TEMPLATE); g_type_ensure (SYSPROF_TYPE_RECORDING_TEMPLATE);
g_type_ensure (SYSPROF_TYPE_STACK_SIZE);
} }
static void static void
@ -540,6 +564,14 @@ sysprof_greeter_init (SysprofGreeter *self)
gtk_list_box_select_row (self->sidebar_list_box, row); gtk_list_box_select_row (self->sidebar_list_box, row);
sidebar_row_activated_cb (self, row, self->sidebar_list_box); sidebar_row_activated_cb (self, row, self->sidebar_list_box);
g_signal_connect_object (self->sample_user_stack_size,
"notify::selected-item",
G_CALLBACK (on_stack_size_changed_cb),
self,
G_CONNECT_SWAPPED);
/* Set to 16KB */
adw_combo_row_set_selected (self->sample_user_stack_size, 1);
gtk_widget_grab_focus (GTK_WIDGET (self->record_to_memory)); gtk_widget_grab_focus (GTK_WIDGET (self->record_to_memory));
} }

View File

@ -106,6 +106,41 @@
</child> </child>
</object> </object>
</child> </child>
<child>
<object class="AdwExpanderRow">
<property name="title" translatable="yes">Unwind Stacks in User Space</property>
<property name="subtitle" translatable="yes">Copy stack contents and registers for unwinding in user-space</property>
<property name="expanded" bind-source="sample_user_stack" bind-property="active" bind-flags="sync-create|bidirectional"/>
<child type="action">
<object class="GtkSwitch" id="sample_user_stack">
<property name="halign">end</property>
<property name="valign">center</property>
<property name="active" bind-source="recording_template" bind-flags="bidirectional|sync-create" bind-property="user-stacks"/>
</object>
</child>
<child>
<object class="AdwComboRow" id="sample_user_stack_size">
<property name="title" translatable="yes">Stack Size</property>
<property name="subtitle" translatable="yes">The number of bytes to copy from the stack</property>
<property name="model">stack_sizes</property>
<property name="expression">
<lookup name="label" type="SysprofStackSize"/>
</property>
</object>
</child>
</object>
</child>
<child>
<object class="GtkLabel">
<property name="label" translatable="yes">Unwinding in user-space has considerable overhead but may help in situations where frame-pointers are unavailable.</property>
<property name="xalign">0</property>
<property name="margin-top">8</property>
<style>
<class name="caption"/>
<class name="dim-label"/>
</style>
</object>
</child>
</object> </object>
</child> </child>
<child> <child>
@ -664,4 +699,36 @@
</object> </object>
<object class="GtkStringList" id="envvars"> <object class="GtkStringList" id="envvars">
</object> </object>
<object class="GListStore" id="stack_sizes">
<child>
<object class="SysprofStackSize">
<property name="label">8 KB</property>
<property name="size">8192</property>
</object>
</child>
<child>
<object class="SysprofStackSize">
<property name="label">16 KB</property>
<property name="size">16384</property>
</object>
</child>
<child>
<object class="SysprofStackSize">
<property name="label">24 KB</property>
<property name="size">24576</property>
</object>
</child>
<child>
<object class="SysprofStackSize">
<property name="label">32 KB</property>
<property name="size">32768</property>
</object>
</child>
<child>
<object class="SysprofStackSize">
<property name="label">64 KB</property>
<property name="size">65536</property>
</object>
</child>
</object>
</interface> </interface>

View File

@ -22,6 +22,8 @@
#include "sysprof-recording-template.h" #include "sysprof-recording-template.h"
#define DEFAULT_STACK_SIZE (4096*4)
struct _SysprofRecordingTemplate struct _SysprofRecordingTemplate
{ {
GObject parent_instance; GObject parent_instance;
@ -31,6 +33,8 @@ struct _SysprofRecordingTemplate
char *power_profile; char *power_profile;
char **environ; char **environ;
guint stack_size;
guint battery_charge : 1; guint battery_charge : 1;
guint bundle_symbols : 1; guint bundle_symbols : 1;
guint clear_environ : 1; guint clear_environ : 1;
@ -49,6 +53,7 @@ struct _SysprofRecordingTemplate
guint session_bus : 1; guint session_bus : 1;
guint system_bus : 1; guint system_bus : 1;
guint system_log : 1; guint system_log : 1;
guint user_stacks : 1;
}; };
enum { enum {
@ -73,8 +78,10 @@ enum {
PROP_POWER_PROFILE, PROP_POWER_PROFILE,
PROP_SCHEDULER_DETAILS, PROP_SCHEDULER_DETAILS,
PROP_SESSION_BUS, PROP_SESSION_BUS,
PROP_STACK_SIZE,
PROP_SYSTEM_BUS, PROP_SYSTEM_BUS,
PROP_SYSTEM_LOG, PROP_SYSTEM_LOG,
PROP_USER_STACKS,
N_PROPS N_PROPS
}; };
@ -185,6 +192,10 @@ sysprof_recording_template_get_property (GObject *object,
g_value_set_boolean (value, self->session_bus); g_value_set_boolean (value, self->session_bus);
break; break;
case PROP_STACK_SIZE:
g_value_set_uint (value, self->stack_size);
break;
case PROP_SYSTEM_BUS: case PROP_SYSTEM_BUS:
g_value_set_boolean (value, self->system_bus); g_value_set_boolean (value, self->system_bus);
break; break;
@ -193,6 +204,10 @@ sysprof_recording_template_get_property (GObject *object,
g_value_set_boolean (value, self->system_log); g_value_set_boolean (value, self->system_log);
break; break;
case PROP_USER_STACKS:
g_value_set_boolean (value, self->user_stacks);
break;
default: default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
} }
@ -289,6 +304,10 @@ sysprof_recording_template_set_property (GObject *object,
self->session_bus = g_value_get_boolean (value); self->session_bus = g_value_get_boolean (value);
break; break;
case PROP_STACK_SIZE:
self->stack_size = g_value_get_uint (value);
break;
case PROP_SYSTEM_BUS: case PROP_SYSTEM_BUS:
self->system_bus = g_value_get_boolean (value); self->system_bus = g_value_get_boolean (value);
break; break;
@ -297,6 +316,10 @@ sysprof_recording_template_set_property (GObject *object,
self->system_log = g_value_get_boolean (value); self->system_log = g_value_get_boolean (value);
break; break;
case PROP_USER_STACKS:
self->user_stacks = g_value_get_boolean (value);
break;
default: default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
} }
@ -421,6 +444,16 @@ sysprof_recording_template_class_init (SysprofRecordingTemplateClass *klass)
TRUE, TRUE,
(G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
properties[PROP_USER_STACKS] =
g_param_spec_boolean ("user-stacks", NULL, NULL,
FALSE,
(G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
properties[PROP_STACK_SIZE] =
g_param_spec_uint ("stack-size", NULL, NULL,
0, G_MAXUINT, DEFAULT_STACK_SIZE,
(G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
g_object_class_install_properties (object_class, N_PROPS, properties); g_object_class_install_properties (object_class, N_PROPS, properties);
} }
@ -439,6 +472,7 @@ sysprof_recording_template_init (SysprofRecordingTemplate *self)
self->system_log = TRUE; self->system_log = TRUE;
self->command_line = g_strdup (""); self->command_line = g_strdup ("");
self->cwd = g_strdup(""); self->cwd = g_strdup("");
self->stack_size = DEFAULT_STACK_SIZE;
} }
SysprofRecordingTemplate * SysprofRecordingTemplate *
@ -619,7 +653,12 @@ sysprof_recording_template_apply (SysprofRecordingTemplate *self,
sysprof_profiler_add_instrument (profiler, sysprof_memory_usage_new ()); sysprof_profiler_add_instrument (profiler, sysprof_memory_usage_new ());
if (self->native_stacks) if (self->native_stacks)
{
if (self->user_stacks)
sysprof_profiler_add_instrument (profiler, sysprof_user_sampler_new (self->stack_size));
else
sysprof_profiler_add_instrument (profiler, sysprof_sampler_new ()); sysprof_profiler_add_instrument (profiler, sysprof_sampler_new ());
}
if (self->network_usage) if (self->network_usage)
sysprof_profiler_add_instrument (profiler, sysprof_network_usage_new ()); sysprof_profiler_add_instrument (profiler, sysprof_network_usage_new ());

View File

@ -0,0 +1,141 @@
/*
* sysprof-stack-size.c
*
* 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
*/
#include "config.h"
#include "sysprof-stack-size.h"
struct _SysprofStackSize
{
GObject parent_instance;
char *label;
guint size;
};
enum {
PROP_0,
PROP_SIZE,
PROP_LABEL,
N_PROPS
};
G_DEFINE_FINAL_TYPE (SysprofStackSize, sysprof_stack_size, G_TYPE_OBJECT)
static GParamSpec *properties[N_PROPS];
static void
sysprof_stack_size_finalize (GObject *object)
{
SysprofStackSize *self = (SysprofStackSize *)object;
g_clear_pointer (&self->label, g_free);
G_OBJECT_CLASS (sysprof_stack_size_parent_class)->finalize (object);
}
static void
sysprof_stack_size_get_property (GObject *object,
guint prop_id,
GValue *value,
GParamSpec *pspec)
{
SysprofStackSize *self = SYSPROF_STACK_SIZE (object);
switch (prop_id)
{
case PROP_SIZE:
g_value_set_uint (value, self->size);
break;
case PROP_LABEL:
g_value_set_string (value, self->label);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
}
}
static void
sysprof_stack_size_set_property (GObject *object,
guint prop_id,
const GValue *value,
GParamSpec *pspec)
{
SysprofStackSize *self = SYSPROF_STACK_SIZE (object);
switch (prop_id)
{
case PROP_SIZE:
self->size = g_value_get_uint (value);
break;
case PROP_LABEL:
g_set_str (&self->label, g_value_get_string (value));
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
}
}
static void
sysprof_stack_size_class_init (SysprofStackSizeClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
object_class->finalize = sysprof_stack_size_finalize;
object_class->get_property = sysprof_stack_size_get_property;
object_class->set_property = sysprof_stack_size_set_property;
properties[PROP_SIZE] =
g_param_spec_uint ("size", NULL, NULL,
0, (4096*32), 0,
(G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
properties[PROP_LABEL] =
g_param_spec_string ("label", NULL, NULL,
NULL,
(G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
g_object_class_install_properties (object_class, N_PROPS, properties);
}
static void
sysprof_stack_size_init (SysprofStackSize *self)
{
}
guint
sysprof_stack_size_get_size (SysprofStackSize *self)
{
g_return_val_if_fail (SYSPROF_IS_STACK_SIZE (self), 0);
return self->size;
}
const char *
sysprof_stack_size_get_label (SysprofStackSize *self)
{
g_return_val_if_fail (SYSPROF_IS_STACK_SIZE (self), NULL);
return self->label;
}

View File

@ -0,0 +1,35 @@
/*
* sysprof-stack-size.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 <glib-object.h>
G_BEGIN_DECLS
#define SYSPROF_TYPE_STACK_SIZE (sysprof_stack_size_get_type())
G_DECLARE_FINAL_TYPE (SysprofStackSize, sysprof_stack_size, SYSPROF, STACK_SIZE, GObject)
guint sysprof_stack_size_get_size (SysprofStackSize *self);
const char *sysprof_stack_size_get_label (SysprofStackSize *self);
G_END_DECLS

View File

@ -127,6 +127,8 @@ helpers_perf_event_open (GVariant *options,
guint64 sample_period = 0; guint64 sample_period = 0;
guint64 sample_type = 0; guint64 sample_type = 0;
guint64 config = 0; guint64 config = 0;
guint64 sample_regs_user = 0;
guint sample_stack_user = 0;
int clockid = CLOCK_MONOTONIC; int clockid = CLOCK_MONOTONIC;
int comm = 0; int comm = 0;
int mmap_ = 0; int mmap_ = 0;
@ -236,6 +238,18 @@ helpers_perf_event_open (GVariant *options,
goto bad_arg; goto bad_arg;
use_clockid = g_variant_get_boolean (value); use_clockid = g_variant_get_boolean (value);
} }
else if (strcmp (key, "sample_stack_user") == 0)
{
if (!g_variant_is_of_type (value, G_VARIANT_TYPE_UINT32))
goto bad_arg;
sample_stack_user = g_variant_get_uint32 (value);
}
else if (strcmp (key, "sample_regs_user") == 0)
{
if (!g_variant_is_of_type (value, G_VARIANT_TYPE_UINT64))
goto bad_arg;
sample_regs_user = g_variant_get_uint64 (value);
}
continue; continue;
@ -257,6 +271,8 @@ helpers_perf_event_open (GVariant *options,
attr.task = !!task; attr.task = !!task;
attr.type = type; attr.type = type;
attr.wakeup_events = wakeup_events; attr.wakeup_events = wakeup_events;
attr.sample_regs_user = sample_regs_user;
attr.sample_stack_user = sample_stack_user;
#ifdef HAVE_PERF_CLOCKID #ifdef HAVE_PERF_CLOCKID
if (!use_clockid || clockid < 0) if (!use_clockid || clockid < 0)

View File

@ -0,0 +1,247 @@
/*
* ipc-unwinder-impl.c
*
* 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
*/
#define G_LOG_DOMAIN "ipc-unwinder-impl"
#include "config.h"
#include <errno.h>
#include <signal.h>
#include <sys/prctl.h>
#include <sys/socket.h>
#include <glib/gstdio.h>
#include <polkit/polkit.h>
#include "ipc-unwinder-impl.h"
struct _IpcUnwinderImpl
{
IpcUnwinderSkeleton parent_instance;
};
static void
child_setup (gpointer data)
{
prctl (PR_SET_PDEATHSIG, SIGKILL);
}
static gboolean
ipc_unwinder_impl_handle_unwind (IpcUnwinder *unwinder,
GDBusMethodInvocation *invocation,
GUnixFDList *fd_list,
guint stack_size,
GVariant *arg_perf_fds,
GVariant *arg_event_fd)
{
g_autoptr(GSubprocessLauncher) launcher = NULL;
g_autoptr(GSubprocess) subprocess = NULL;
g_autoptr(GUnixFDList) out_fd_list = NULL;
g_autoptr(GPtrArray) argv = NULL;
g_autoptr(GError) error = NULL;
g_autofd int our_fd = -1;
g_autofd int their_fd = -1;
g_autofd int event_fd = -1;
GVariantIter iter;
int capture_fd_handle;
int pair[2];
int next_target_fd = 3;
int perf_fd_handle;
int cpu;
g_assert (IPC_IS_UNWINDER_IMPL (unwinder));
g_assert (G_IS_DBUS_METHOD_INVOCATION (invocation));
g_assert (!fd_list || G_IS_UNIX_FD_LIST (fd_list));
if (stack_size == 0 || stack_size % sysconf (_SC_PAGESIZE) != 0)
{
g_dbus_method_invocation_return_error_literal (g_steal_pointer (&invocation),
G_DBUS_ERROR,
G_DBUS_ERROR_INVALID_ARGS,
"Stack size must be a multiple of the page size");
return TRUE;
}
if (fd_list == NULL)
{
g_dbus_method_invocation_return_error_literal (g_steal_pointer (&invocation),
G_DBUS_ERROR,
G_DBUS_ERROR_FILE_NOT_FOUND,
"Missing perf FDs");
return TRUE;
}
launcher = g_subprocess_launcher_new (0);
argv = g_ptr_array_new_with_free_func (g_free);
g_ptr_array_add (argv, g_strdup (PACKAGE_LIBEXECDIR "/sysprof-live-unwinder"));
g_ptr_array_add (argv, g_strdup_printf ("--stack-size=%u", stack_size));
if (-1 == (event_fd = g_unix_fd_list_get (fd_list, g_variant_get_handle (arg_event_fd), &error)))
{
g_dbus_method_invocation_return_gerror (g_steal_pointer (&invocation), error);
return TRUE;
}
g_ptr_array_add (argv, g_strdup_printf ("--event-fd=%u", next_target_fd));
g_subprocess_launcher_take_fd (launcher, g_steal_fd (&event_fd), next_target_fd++);
g_variant_iter_init (&iter, arg_perf_fds);
while (g_variant_iter_loop (&iter, "(hi)", &perf_fd_handle, &cpu))
{
g_autofd int perf_fd = g_unix_fd_list_get (fd_list, perf_fd_handle, &error);
if (perf_fd < 0)
{
g_dbus_method_invocation_return_gerror (g_steal_pointer (&invocation), error);
return TRUE;
}
g_ptr_array_add (argv, g_strdup_printf ("--perf-fd=%d:%d", next_target_fd, cpu));
g_subprocess_launcher_take_fd (launcher,
g_steal_fd (&perf_fd),
next_target_fd++);
}
g_subprocess_launcher_set_child_setup (launcher, child_setup, NULL, NULL);
if (socketpair (AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0, pair) < 0)
{
int errsv = errno;
g_dbus_method_invocation_return_error_literal (g_steal_pointer (&invocation),
G_IO_ERROR,
g_io_error_from_errno (errsv),
g_strerror (errsv));
return TRUE;
}
our_fd = g_steal_fd (&pair[0]);
their_fd = g_steal_fd (&pair[1]);
out_fd_list = g_unix_fd_list_new ();
capture_fd_handle = g_unix_fd_list_append (out_fd_list, their_fd, &error);
if (capture_fd_handle < 0)
{
g_dbus_method_invocation_return_gerror (g_steal_pointer (&invocation), error);
return TRUE;
}
g_ptr_array_add (argv, g_strdup_printf ("--capture-fd=%d", next_target_fd));
g_subprocess_launcher_take_fd (launcher, g_steal_fd (&our_fd), next_target_fd++);
g_ptr_array_add (argv, NULL);
if (!(subprocess = g_subprocess_launcher_spawnv (launcher, (const char * const *)argv->pdata, &error)))
{
g_dbus_method_invocation_return_gerror (g_steal_pointer (&invocation), error);
return TRUE;
}
ipc_unwinder_complete_unwind (unwinder,
g_steal_pointer (&invocation),
out_fd_list,
g_variant_new_handle (capture_fd_handle));
g_subprocess_wait_async (subprocess, NULL, NULL, NULL);
return TRUE;
}
static void
unwinder_iface_init (IpcUnwinderIface *iface)
{
iface->handle_unwind = ipc_unwinder_impl_handle_unwind;
}
G_DEFINE_FINAL_TYPE_WITH_CODE (IpcUnwinderImpl, ipc_unwinder_impl, IPC_TYPE_UNWINDER_SKELETON,
G_IMPLEMENT_INTERFACE (IPC_TYPE_UNWINDER, unwinder_iface_init))
static gboolean
ipc_unwinder_impl_g_authorize_method (GDBusInterfaceSkeleton *skeleton,
GDBusMethodInvocation *invocation)
{
PolkitAuthorizationResult *res = NULL;
PolkitAuthority *authority = NULL;
PolkitSubject *subject = NULL;
const gchar *peer_name;
gboolean ret = TRUE;
g_assert (IPC_IS_UNWINDER_IMPL (skeleton));
g_assert (G_IS_DBUS_METHOD_INVOCATION (invocation));
peer_name = g_dbus_method_invocation_get_sender (invocation);
if (!(authority = polkit_authority_get_sync (NULL, NULL)) ||
!(subject = polkit_system_bus_name_new (peer_name)) ||
!(res = polkit_authority_check_authorization_sync (authority,
POLKIT_SUBJECT (subject),
"org.gnome.sysprof3.profile",
NULL,
POLKIT_CHECK_AUTHORIZATION_FLAGS_ALLOW_USER_INTERACTION,
NULL,
NULL)) ||
!polkit_authorization_result_get_is_authorized (res))
{
g_dbus_method_invocation_return_error (g_steal_pointer (&invocation),
G_DBUS_ERROR,
G_DBUS_ERROR_ACCESS_DENIED,
"Not authorized to make request");
ret = FALSE;
}
g_clear_object (&authority);
g_clear_object (&subject);
g_clear_object (&res);
return ret;
}
static void
ipc_unwinder_impl_finalize (GObject *object)
{
G_OBJECT_CLASS (ipc_unwinder_impl_parent_class)->finalize (object);
}
static void
ipc_unwinder_impl_class_init (IpcUnwinderImplClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
GDBusInterfaceSkeletonClass *skeleton_class = G_DBUS_INTERFACE_SKELETON_CLASS (klass);
object_class->finalize = ipc_unwinder_impl_finalize;
skeleton_class->g_authorize_method = ipc_unwinder_impl_g_authorize_method;
}
static void
ipc_unwinder_impl_init (IpcUnwinderImpl *self)
{
}
IpcUnwinder *
ipc_unwinder_impl_new (void)
{
return g_object_new (IPC_TYPE_UNWINDER_IMPL, NULL);
}

View File

@ -0,0 +1,34 @@
/*
* ipc-unwinder-impl.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 "ipc-unwinder.h"
G_BEGIN_DECLS
#define IPC_TYPE_UNWINDER_IMPL (ipc_unwinder_impl_get_type())
G_DECLARE_FINAL_TYPE (IpcUnwinderImpl, ipc_unwinder_impl, IPC, UNWINDER_IMPL, IpcUnwinderSkeleton)
IpcUnwinder *ipc_unwinder_impl_new (void);
G_END_DECLS

View File

@ -10,18 +10,24 @@ ipc_service_src = gnome.gdbus_codegen('ipc-service',
namespace: 'Ipc', namespace: 'Ipc',
) )
ipc_unwinder_src = gnome.gdbus_codegen('ipc-unwinder',
sources: 'org.gnome.Sysprof3.Unwinder.xml',
interface_prefix: 'org.gnome.Sysprof3.',
namespace: 'Ipc',
)
sysprofd_sources = [ sysprofd_sources = [
'sysprofd.c', 'sysprofd.c',
'ipc-rapl-profiler.c', 'ipc-rapl-profiler.c',
'ipc-service-impl.c', 'ipc-service-impl.c',
'ipc-unwinder-impl.c',
'sysprof-turbostat.c', 'sysprof-turbostat.c',
'helpers.c', 'helpers.c',
ipc_profiler_src, ipc_profiler_src,
ipc_service_src, ipc_service_src,
ipc_unwinder_src,
] ]
pkglibexecdir = join_paths(get_option('prefix'), get_option('libexecdir'))
sysprofd_deps = [ sysprofd_deps = [
glib_dep, glib_dep,
gio_dep, gio_dep,

View File

@ -0,0 +1,23 @@
<!DOCTYPE node PUBLIC "-//freedesktop//DTD D-BUS Object Introspection 1.0//EN"
"http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd">
<node>
<interface name="org.gnome.Sysprof3.Unwinder">
<!--
Unwind:
@stack_size: the size of stacks that are sampled
@perf_fds: an array of (perf_fd, CPU number)
@event_fd: an event fd to write to for notifying unwinder should exit
@capture_fd: (out): a FD that will be written to containing samples
Unwinding will stop when capture_fd can no longer be written to
such as being closed by the consumer of this API.
-->
<method name="Unwind">
<annotation name="org.gtk.GDBus.C.UnixFD" value="true"/>
<arg type="u" name="stack_size" direction="in"/>
<arg type="a(hi)" name="perf_fds" direction="in"/>
<arg type="h" name="event_fd" direction="in"/>
<arg type="h" name="capture_fd" direction="out"/>
</method>
</interface>
</node>

View File

@ -30,9 +30,11 @@
#include "ipc-rapl-profiler.h" #include "ipc-rapl-profiler.h"
#include "ipc-service-impl.h" #include "ipc-service-impl.h"
#include "ipc-unwinder-impl.h"
#define V3_PATH "/org/gnome/Sysprof3" #define V3_PATH "/org/gnome/Sysprof3"
#define RAPL_PATH "/org/gnome/Sysprof3/RAPL" #define RAPL_PATH "/org/gnome/Sysprof3/RAPL"
#define UNWINDER_PATH "/org/gnome/Sysprof3/Unwinder"
#define NAME_ACQUIRE_DELAY_SECS 3 #define NAME_ACQUIRE_DELAY_SECS 3
#define INACTIVITY_TIMEOUT_SECS 120 #define INACTIVITY_TIMEOUT_SECS 120
@ -126,6 +128,7 @@ main (gint argc,
{ {
g_autoptr(IpcProfiler) rapl = ipc_rapl_profiler_new (); g_autoptr(IpcProfiler) rapl = ipc_rapl_profiler_new ();
g_autoptr(IpcService) v3_service = ipc_service_impl_new (); g_autoptr(IpcService) v3_service = ipc_service_impl_new ();
g_autoptr(IpcUnwinder) unwinder = ipc_unwinder_impl_new ();
g_signal_connect (v3_service, "activity", G_CALLBACK (activity_cb), NULL); g_signal_connect (v3_service, "activity", G_CALLBACK (activity_cb), NULL);
g_signal_connect (rapl, "activity", G_CALLBACK (activity_cb), NULL); g_signal_connect (rapl, "activity", G_CALLBACK (activity_cb), NULL);
@ -133,7 +136,8 @@ main (gint argc,
activity_cb (NULL, NULL); activity_cb (NULL, NULL);
if (g_dbus_interface_skeleton_export (G_DBUS_INTERFACE_SKELETON (v3_service), bus, V3_PATH, &error) && if (g_dbus_interface_skeleton_export (G_DBUS_INTERFACE_SKELETON (v3_service), bus, V3_PATH, &error) &&
g_dbus_interface_skeleton_export (G_DBUS_INTERFACE_SKELETON (rapl), bus, RAPL_PATH, &error)) g_dbus_interface_skeleton_export (G_DBUS_INTERFACE_SKELETON (rapl), bus, RAPL_PATH, &error) &&
g_dbus_interface_skeleton_export (G_DBUS_INTERFACE_SKELETON (unwinder), bus, UNWINDER_PATH, &error))
{ {
for (guint i = 0; i < G_N_ELEMENTS (bus_names); i++) for (guint i = 0; i < G_N_ELEMENTS (bus_names); i++)
{ {