libsysprof: join libsysprof-analyze and libsysprof-profile

This brings together the two libraries back into one now that the whole
design is pretty well sorted out. They depend on roughly the same libraries
anyway and it's way easier of the single library can both read and write
the capture files (along with bringing in libsysprof-capture symbols in
a single place).
This commit is contained in:
Christian Hergert
2023-07-19 17:40:41 -07:00
parent a990fc6b3c
commit dbb7833cbf
217 changed files with 187 additions and 316 deletions

View File

@ -0,0 +1,577 @@
/* sysprof-perf-event-stream.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
*/
/* Sysprof -- Sampling, systemwide CPU profiler
* Copyright 2004, Red Hat, Inc.
* Copyright 2004, 2005, Soeren Sandmann
*
* 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 2 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, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*/
#include "config.h"
#include <errno.h>
#include <glib/gstdio.h>
#include <gio/gio.h>
#include <gio/gunixfdlist.h>
#ifdef HAVE_STDATOMIC_H
# include <stdatomic.h>
#endif
#include <string.h>
#include <sys/ioctl.h>
#include <sys/mman.h>
#include <sys/syscall.h>
#include <unistd.h>
#include "sysprof-perf-event-stream-private.h"
/*
* Number of pages to map for the ring buffer. We map one additional buffer
* at the beginning for header information to communicate with perf.
*/
#define N_PAGES 32
struct _SysprofPerfEventStream
{
GObject parent_instance;
GDBusConnection *connection;
GSource *source;
struct perf_event_attr attr;
int self_pid;
int cpu;
int group_fd;
gulong flags;
SysprofPerfEventCallback callback;
gpointer callback_data;
GDestroyNotify callback_data_destroy;
DexPromise *promise;
int perf_fd;
struct perf_event_mmap_page *map;
guint8 *map_data;
guint64 tail;
guint active : 1;
};
typedef struct _SysprofPerfEventSource
{
GSource source;
SysprofPerfEventStream *stream;
gint64 next_ready_time;
int timeout_msec;
} SysprofPerfEventSource;
enum {
PROP_0,
PROP_ACTIVE,
N_PROPS
};
G_DEFINE_FINAL_TYPE (SysprofPerfEventStream, sysprof_perf_event_stream, G_TYPE_OBJECT)
static GParamSpec *properties [N_PROPS];
static GVariant *
build_options_dict (const struct perf_event_attr *attr)
{
return g_variant_take_ref (
g_variant_new_parsed ("["
"{'comm', <%b>},"
#ifdef HAVE_PERF_CLOCKID
"{'clockid', <%i>},"
"{'use_clockid', <%b>},"
#endif
"{'config', <%t>},"
"{'disabled', <%b>},"
"{'exclude_idle', <%b>},"
"{'mmap', <%b>},"
"{'wakeup_events', <%u>},"
"{'sample_id_all', <%b>},"
"{'sample_period', <%t>},"
"{'sample_type', <%t>},"
"{'task', <%b>},"
"{'type', <%u>}"
"]",
(gboolean)!!attr->comm,
#ifdef HAVE_PERF_CLOCKID
(gint32)attr->clockid,
(gboolean)!!attr->use_clockid,
#endif
(guint64)attr->config,
(gboolean)!!attr->disabled,
(gboolean)!!attr->exclude_idle,
(gboolean)!!attr->mmap,
(guint32)attr->wakeup_events,
(gboolean)!!attr->sample_id_all,
(guint64)attr->sample_period,
(guint64)attr->sample_type,
(gboolean)!!attr->task,
(guint32)attr->type));
}
static void
sysprof_perf_event_stream_flush (SysprofPerfEventStream *self)
{
SysprofPerfEventSource *source = (SysprofPerfEventSource *)self->source;
guint64 n_bytes = N_PAGES * sysprof_getpagesize ();
guint64 mask = n_bytes - 1;
guint64 head;
guint64 tail;
gboolean lost_records = FALSE;
guint us = 0;
guint them = 0;
g_assert (SYSPROF_IS_PERF_EVENT_STREAM (self));
tail = self->tail;
head = self->map->data_head;
#ifdef HAVE_STDATOMIC_H
atomic_thread_fence (memory_order_acquire);
#elif G_GNUC_CHECK_VERSION(3, 0)
__sync_synchronize ();
#endif
if (head < tail)
tail = head;
while ((head - tail) >= sizeof (struct perf_event_header))
{
g_autofree guint8 *free_me = NULL;
const SysprofPerfEvent *event;
struct perf_event_header *header;
guint8 buffer[4096];
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)(self->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.
*/
break;
}
if (self->map_data + (tail & mask) + header->size > self->map_data + n_bytes)
{
gint n_before;
gint n_after;
guint8 *b;
if (header->size > sizeof buffer)
free_me = b = g_malloc (header->size);
else
b = buffer;
n_after = (tail & mask) + header->size - n_bytes;
n_before = header->size - n_after;
memcpy (b, self->map_data + (tail & mask), n_before);
memcpy (b + n_before, self->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:
break;
case PERF_RECORD_SAMPLE:
is_self = event->callchain.pid == self->self_pid;
break;
case PERF_RECORD_READ:
case PERF_RECORD_THROTTLE:
case PERF_RECORD_UNTHROTTLE:
goto skip_callback;
case PERF_RECORD_LOST:
lost_records = TRUE;
g_debug ("Lost records from perf");
break;
}
if (self->callback != NULL)
self->callback (event, self->cpu, self->callback_data);
us += is_self;
them += !is_self;
skip_callback:
tail += header->size;
}
self->tail = tail;
#ifdef HAVE_STDATOMIC_H
atomic_thread_fence (memory_order_seq_cst);
#elif G_GNUC_CHECK_VERSION(3, 0)
__sync_synchronize ();
#endif
self->map->data_tail = tail;
/* If we lost records them we took too long to process events and
* need to speed up how often we process incoming records. However,
* if we are the cause of that (due to running to frequently), then
* we need to back-off.
*/
if (lost_records && us < them/3)
{
if (source->timeout_msec > 1)
source->timeout_msec -= 5;
}
else
{
if (source->timeout_msec < 500)
{
if (us < 5 && them < 5)
source->timeout_msec += 100;
else
source->timeout_msec += 10;
}
}
}
static gboolean
sysprof_perf_event_source_dispatch (GSource *gsource,
GSourceFunc callback,
gpointer user_data)
{
SysprofPerfEventSource *source = (SysprofPerfEventSource *)gsource;
SysprofPerfEventStream *self = source->stream;
if (source->next_ready_time <= g_source_get_time (gsource) &&
self != NULL &&
self->active &&
self->map != NULL &&
self->tail != self->map->data_head)
sysprof_perf_event_stream_flush (self);
else
source->timeout_msec = MIN (500, source->timeout_msec + 50);
source->next_ready_time = g_get_monotonic_time () + (source->timeout_msec * 1000);
g_source_set_ready_time (self->source, source->next_ready_time);
return G_SOURCE_CONTINUE;
}
static GSourceFuncs source_funcs = {
.dispatch = sysprof_perf_event_source_dispatch,
};
static void
sysprof_perf_event_stream_finalize (GObject *object)
{
SysprofPerfEventStream *self = (SysprofPerfEventStream *)object;
if (self->callback_data_destroy)
{
self->callback_data_destroy (self->callback_data);
self->callback_data_destroy = NULL;
self->callback_data = NULL;
}
self->callback = NULL;
dex_clear (&self->promise);
g_clear_object (&self->connection);
if (self->source != NULL)
{
g_source_destroy (self->source);
g_source_unref (self->source);
self->source = NULL;
}
g_clear_fd (&self->perf_fd, NULL);
G_OBJECT_CLASS (sysprof_perf_event_stream_parent_class)->finalize (object);
}
static void
sysprof_perf_event_stream_get_property (GObject *object,
guint prop_id,
GValue *value,
GParamSpec *pspec)
{
SysprofPerfEventStream *self = SYSPROF_PERF_EVENT_STREAM (object);
switch (prop_id)
{
case PROP_ACTIVE:
g_value_set_boolean (value, self->active);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
}
}
static void
sysprof_perf_event_stream_class_init (SysprofPerfEventStreamClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
object_class->finalize = sysprof_perf_event_stream_finalize;
object_class->get_property = sysprof_perf_event_stream_get_property;
properties[PROP_ACTIVE] =
g_param_spec_boolean ("active", NULL, NULL,
FALSE,
(G_PARAM_READABLE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS));
g_object_class_install_properties (object_class, N_PROPS, properties);
}
static void
sysprof_perf_event_stream_init (SysprofPerfEventStream *self)
{
self->perf_fd = -1;
self->self_pid = getpid ();
}
static void
sysprof_perf_event_stream_new_cb (GObject *object,
GAsyncResult *result,
gpointer user_data)
{
GDBusConnection *connection = (GDBusConnection *)object;
g_autoptr(SysprofPerfEventStream) self = 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 (SYSPROF_IS_PERF_EVENT_STREAM (self));
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)))
{
gsize map_size;
guint8 *map;
self->perf_fd = fd;
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_IO_ERROR,
g_io_error_from_errno (errsv),
g_strerror (errsv));
}
else
{
self->map = (gpointer)map;
self->map_data = map + sysprof_getpagesize ();
self->tail = 0;
}
}
}
if (error != NULL)
dex_promise_reject (self->promise, g_steal_pointer (&error));
else
dex_promise_resolve_object (self->promise, g_object_ref (self));
dex_clear (&self->promise);
}
DexFuture *
sysprof_perf_event_stream_new (GDBusConnection *connection,
struct perf_event_attr *attr,
int cpu,
int group_fd,
guint64 flags,
SysprofPerfEventCallback callback,
gpointer callback_data,
GDestroyNotify callback_data_destroy)
{
SysprofPerfEventSource *source;
g_autoptr(SysprofPerfEventStream) self = NULL;
g_autoptr(GUnixFDList) fd_list = NULL;
g_autoptr(DexPromise) promise = NULL;
g_autoptr(GVariant) options = NULL;
g_autofree char *name = NULL;
int group_fd_handle = -1;
g_return_val_if_fail (G_IS_DBUS_CONNECTION (connection), NULL);
g_return_val_if_fail (attr != NULL, NULL);
g_return_val_if_fail (cpu > -1, NULL);
g_return_val_if_fail (group_fd >= -1, NULL);
promise = dex_promise_new ();
self = g_object_new (SYSPROF_TYPE_PERF_EVENT_STREAM, NULL);
self->connection = g_object_ref (connection);
self->attr = *attr;
self->cpu = cpu;
self->group_fd = group_fd;
self->flags = flags;
self->callback = callback;
self->callback_data = callback_data;
self->callback_data_destroy = callback_data_destroy;
self->promise = dex_ref (promise);
source = (SysprofPerfEventSource *)
g_source_new (&source_funcs, sizeof (SysprofPerfEventSource));
source->stream = self;
source->timeout_msec = 5;
source->next_ready_time = g_get_monotonic_time () + (source->timeout_msec * 1000);
self->source = (GSource *)source;
name = g_strdup_printf ("[perf cpu%d]", cpu);
g_source_set_ready_time (self->source, source->next_ready_time);
g_source_set_name (self->source, name);
g_source_attach (self->source, NULL);
if (group_fd > -1)
{
fd_list = g_unix_fd_list_new ();
group_fd_handle = g_unix_fd_list_append (fd_list, group_fd, NULL);
}
options = build_options_dict (attr);
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,
group_fd_handle,
flags),
G_VARIANT_TYPE ("(h)"),
G_DBUS_CALL_FLAGS_NONE,
G_MAXUINT,
fd_list,
dex_promise_get_cancellable (promise),
sysprof_perf_event_stream_new_cb,
g_object_ref (self));
return DEX_FUTURE (g_steal_pointer (&promise));
}
gboolean
sysprof_perf_event_stream_enable (SysprofPerfEventStream *self,
GError **error)
{
g_return_val_if_fail (SYSPROF_IS_PERF_EVENT_STREAM (self), FALSE);
if (self->active)
return TRUE;
if (0 != ioctl (self->perf_fd, PERF_EVENT_IOC_ENABLE))
{
int errsv = errno;
g_set_error_literal (error,
G_IO_ERROR,
g_io_error_from_errno (errsv),
g_strerror (errsv));
return FALSE;
}
self->active = TRUE;
g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_ACTIVE]);
return TRUE;
}
gboolean
sysprof_perf_event_stream_disable (SysprofPerfEventStream *self,
GError **error)
{
g_return_val_if_fail (SYSPROF_IS_PERF_EVENT_STREAM (self), FALSE);
if (!self->active)
return TRUE;
if (0 != ioctl (self->perf_fd, PERF_EVENT_IOC_DISABLE))
{
int errsv = errno;
g_set_error_literal (error,
G_IO_ERROR,
g_io_error_from_errno (errsv),
g_strerror (errsv));
return FALSE;
}
self->active = FALSE;
sysprof_perf_event_stream_flush (self);
g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_ACTIVE]);
return TRUE;
}