Files
sysprof/src/libsysprof-ui/sysprof-depth-visualizer.c
Christian Hergert db160cf536 ui: avoid concurrent reloads of stack depths
This ensures that we only have one thread doing reloads of stack frame
depths at a time. While we only ref the reader in the state, it should
still be fine because cursors *always* make a copy of the reader for their
internal use. I don't think this should fix #23, but it may reduce the
chances of it happening.

It's unclear to me what could cause #23 to happen, unless for some reason
multiple threads were sharing the reader's internal buffer causing the
frame-> dereferences to be junk. But as stated with reader copies, that
should not be able to happen.

Another possible avenue is that the task is cancelled and for some reason
the task is clearing the task data while the thread is running. Again,
that is not supposed to be possible given the design of GTask as it
should not release task data until finalized.
2020-01-08 11:13:24 -08:00

453 lines
13 KiB
C

/* sysprof-depth-visualizer.c
*
* Copyright 2019 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 "sysprof-depth-visualizer"
#include "config.h"
#include <glib/gi18n.h>
#include "pointcache.h"
#include "sysprof-depth-visualizer.h"
struct _SysprofDepthVisualizer
{
SysprofVisualizer parent_instance;
SysprofCaptureReader *reader;
PointCache *points;
guint reload_source;
guint mode;
GtkAllocation last_alloc;
guint reloading : 1;
guint needs_reload : 1;
};
typedef struct
{
SysprofCaptureReader *reader;
PointCache *pc;
gint64 begin_time;
gint64 end_time;
gint64 duration;
guint max_n_addrs;
guint mode;
} State;
static void sysprof_depth_visualizer_reload (SysprofDepthVisualizer *self);
G_DEFINE_TYPE (SysprofDepthVisualizer, sysprof_depth_visualizer, SYSPROF_TYPE_VISUALIZER)
static void
state_clear (State *st)
{
g_clear_pointer (&st->reader, sysprof_capture_reader_unref);
g_clear_pointer (&st->pc, point_cache_unref);
}
static void
state_unref (State *st)
{
g_atomic_rc_box_release_full (st, (GDestroyNotify) state_clear);
}
static gboolean
discover_max_n_addr (const SysprofCaptureFrame *frame,
gpointer user_data)
{
const SysprofCaptureSample *sample = (const SysprofCaptureSample *)frame;
State *st = user_data;
g_assert (frame != NULL);
g_assert (frame->type == SYSPROF_CAPTURE_FRAME_SAMPLE);
g_assert (st != NULL);
st->max_n_addrs = MAX (st->max_n_addrs, sample->n_addrs);
return TRUE;
}
static gboolean
build_point_cache_cb (const SysprofCaptureFrame *frame,
gpointer user_data)
{
const SysprofCaptureSample *sample = (const SysprofCaptureSample *)frame;
State *st = user_data;
gdouble x, y;
gboolean has_kernel = FALSE;
g_assert (frame != NULL);
g_assert (frame->type == SYSPROF_CAPTURE_FRAME_SAMPLE);
g_assert (st != NULL);
x = (frame->time - st->begin_time) / (gdouble)st->duration;
y = sample->n_addrs / (gdouble)st->max_n_addrs;
/* If this contains a context-switch (meaning we're going into the kernel
* to do some work, use a negative value for Y so that we know later on
* that we should draw it with a different color (after removing the negation
* on the value.
*
* We skip past the first index, which is always a context switch as it is
* our perf handler.
*/
for (guint i = 1; i < sample->n_addrs; i++)
{
SysprofAddressContext kind;
if (sysprof_address_is_context_switch (sample->addrs[i], &kind))
{
has_kernel = TRUE;
y = -y;
break;
}
}
if (!has_kernel)
point_cache_add_point_to_set (st->pc, 1, x, y);
else
point_cache_add_point_to_set (st->pc, 2, x, y);
return TRUE;
}
static void
sysprof_depth_visualizer_worker (GTask *task,
gpointer source_object,
gpointer task_data,
GCancellable *cancellable)
{
static const SysprofCaptureFrameType types[] = { SYSPROF_CAPTURE_FRAME_SAMPLE, };
g_autoptr(SysprofCaptureCursor) cursor = NULL;
SysprofCaptureCondition *condition;
State *st = task_data;
g_assert (G_IS_TASK (task));
g_assert (SYSPROF_IS_DEPTH_VISUALIZER (source_object));
g_assert (!cancellable || G_IS_CANCELLABLE (cancellable));
if (st->duration != 0)
{
cursor = sysprof_capture_cursor_new (st->reader);
condition = sysprof_capture_condition_new_where_type_in (G_N_ELEMENTS (types), types);
sysprof_capture_cursor_add_condition (cursor, g_steal_pointer (&condition));
sysprof_capture_cursor_foreach (cursor, discover_max_n_addr, st);
sysprof_capture_cursor_reset (cursor);
sysprof_capture_cursor_foreach (cursor, build_point_cache_cb, st);
}
g_task_return_pointer (task,
g_steal_pointer (&st->pc),
(GDestroyNotify) point_cache_unref);
}
static void
apply_point_cache_cb (GObject *object,
GAsyncResult *result,
gpointer user_data)
{
SysprofDepthVisualizer *self = (SysprofDepthVisualizer *)object;
PointCache *pc;
g_assert (SYSPROF_IS_DEPTH_VISUALIZER (self));
g_assert (G_IS_TASK (result));
self->reloading = FALSE;
if ((pc = g_task_propagate_pointer (G_TASK (result), NULL)))
{
g_clear_pointer (&self->points, point_cache_unref);
self->points = g_steal_pointer (&pc);
gtk_widget_queue_draw (GTK_WIDGET (self));
}
if (self->needs_reload)
sysprof_depth_visualizer_reload (self);
}
static void
sysprof_depth_visualizer_reload (SysprofDepthVisualizer *self)
{
g_autoptr(GTask) task = NULL;
GtkAllocation alloc;
State *st;
g_assert (SYSPROF_IS_DEPTH_VISUALIZER (self));
self->needs_reload = TRUE;
if (self->reloading)
return;
self->reloading = TRUE;
self->needs_reload = FALSE;
gtk_widget_get_allocation (GTK_WIDGET (self), &alloc);
st = g_atomic_rc_box_new0 (State);
st->reader = sysprof_capture_reader_ref (self->reader);
st->pc = point_cache_new ();
st->max_n_addrs = 0;
st->begin_time = sysprof_capture_reader_get_start_time (self->reader);
st->end_time = sysprof_capture_reader_get_end_time (self->reader);
st->duration = st->end_time - st->begin_time;
st->mode = self->mode;
point_cache_add_set (st->pc, 1);
point_cache_add_set (st->pc, 2);
task = g_task_new (self, NULL, apply_point_cache_cb, NULL);
g_task_set_source_tag (task, sysprof_depth_visualizer_reload);
g_task_set_task_data (task, st, (GDestroyNotify) state_unref);
g_task_run_in_thread (task, sysprof_depth_visualizer_worker);
}
static void
sysprof_depth_visualizer_set_reader (SysprofVisualizer *row,
SysprofCaptureReader *reader)
{
SysprofDepthVisualizer *self = (SysprofDepthVisualizer *)row;
g_assert (SYSPROF_IS_DEPTH_VISUALIZER (self));
if (self->reader != reader)
{
if (self->reader != NULL)
{
sysprof_capture_reader_unref (self->reader);
self->reader = NULL;
}
if (reader != NULL)
{
self->reader = sysprof_capture_reader_ref (reader);
sysprof_depth_visualizer_reload (self);
}
}
}
static gboolean
sysprof_depth_visualizer_draw (GtkWidget *widget,
cairo_t *cr)
{
SysprofDepthVisualizer *self = (SysprofDepthVisualizer *)widget;
GtkAllocation alloc;
GdkRectangle clip;
const Point *points;
gboolean ret;
guint n_points = 0;
GdkRGBA user;
GdkRGBA system;
g_assert (SYSPROF_IS_DEPTH_VISUALIZER (self));
g_assert (cr != NULL);
ret = GTK_WIDGET_CLASS (sysprof_depth_visualizer_parent_class)->draw (widget, cr);
if (self->points == NULL)
return ret;
gdk_rgba_parse (&user, "#1a5fb4");
gdk_rgba_parse (&system, "#3584e4");
gtk_widget_get_allocation (widget, &alloc);
if (!gdk_cairo_get_clip_rectangle (cr, &clip))
return ret;
/* Draw user-space stacks */
if (self->mode != SYSPROF_DEPTH_VISUALIZER_KERNEL_ONLY &&
(points = point_cache_get_points (self->points, 1, &n_points)))
{
g_autofree SysprofVisualizerAbsolutePoint *out_points = NULL;
out_points = g_new (SysprofVisualizerAbsolutePoint, n_points);
sysprof_visualizer_translate_points (SYSPROF_VISUALIZER (widget),
(const SysprofVisualizerRelativePoint *)points,
n_points, out_points, n_points);
cairo_set_line_width (cr, 1.0);
gdk_cairo_set_source_rgba (cr, &user);
for (guint i = 0; i < n_points; i++)
{
gdouble x, y;
x = out_points[i].x;
y = out_points[i].y;
if (x < clip.x)
continue;
if (x > clip.x + clip.width)
break;
for (guint j = i + 1; j < n_points; j++)
{
if (out_points[j].x != x)
break;
y = MIN (y, out_points[j].y);
}
x += alloc.x;
cairo_move_to (cr, (guint)x + .5, alloc.height);
cairo_line_to (cr, (guint)x + .5, y);
}
cairo_stroke (cr);
}
/* Draw kernel-space stacks */
if (self->mode != SYSPROF_DEPTH_VISUALIZER_USER_ONLY &&
(points = point_cache_get_points (self->points, 2, &n_points)))
{
g_autofree SysprofVisualizerAbsolutePoint *out_points = NULL;
out_points = g_new (SysprofVisualizerAbsolutePoint, n_points);
sysprof_visualizer_translate_points (SYSPROF_VISUALIZER (widget),
(const SysprofVisualizerRelativePoint *)points,
n_points, out_points, n_points);
cairo_set_line_width (cr, 1.0);
gdk_cairo_set_source_rgba (cr, &system);
for (guint i = 0; i < n_points; i++)
{
gdouble x, y;
x = out_points[i].x;
y = out_points[i].y;
if (x < clip.x)
continue;
if (x > clip.x + clip.width)
break;
for (guint j = i + 1; j < n_points; j++)
{
if (out_points[j].x != x)
break;
y = MIN (y, out_points[j].y);
}
x += alloc.x;
cairo_move_to (cr, (guint)x + .5, alloc.height);
cairo_line_to (cr, (guint)x + .5, y);
}
cairo_stroke (cr);
}
return ret;
}
static gboolean
sysprof_depth_visualizer_do_reload (gpointer data)
{
SysprofDepthVisualizer *self = data;
self->reload_source = 0;
sysprof_depth_visualizer_reload (self);
return G_SOURCE_REMOVE;
}
static void
sysprof_depth_visualizer_queue_reload (SysprofDepthVisualizer *self)
{
g_assert (SYSPROF_IS_DEPTH_VISUALIZER (self));
if (self->reload_source)
g_source_remove (self->reload_source);
self->reload_source = gdk_threads_add_idle (sysprof_depth_visualizer_do_reload, self);
}
static void
sysprof_depth_visualizer_size_allocate (GtkWidget *widget,
GtkAllocation *alloc)
{
SysprofDepthVisualizer *self = (SysprofDepthVisualizer *)widget;
GTK_WIDGET_CLASS (sysprof_depth_visualizer_parent_class)->size_allocate (widget, alloc);
if (alloc->width != self->last_alloc.x ||
alloc->height != self->last_alloc.height)
{
sysprof_depth_visualizer_queue_reload (SYSPROF_DEPTH_VISUALIZER (widget));
self->last_alloc = *alloc;
}
}
static void
sysprof_depth_visualizer_finalize (GObject *object)
{
SysprofDepthVisualizer *self = (SysprofDepthVisualizer *)object;
g_clear_pointer (&self->reader, sysprof_capture_reader_unref);
if (self->reload_source)
{
g_source_remove (self->reload_source);
self->reload_source = 0;
}
G_OBJECT_CLASS (sysprof_depth_visualizer_parent_class)->finalize (object);
}
static void
sysprof_depth_visualizer_class_init (SysprofDepthVisualizerClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
SysprofVisualizerClass *row_class = SYSPROF_VISUALIZER_CLASS (klass);
object_class->finalize = sysprof_depth_visualizer_finalize;
widget_class->draw = sysprof_depth_visualizer_draw;
widget_class->size_allocate = sysprof_depth_visualizer_size_allocate;
row_class->set_reader = sysprof_depth_visualizer_set_reader;
}
static void
sysprof_depth_visualizer_init (SysprofDepthVisualizer *self)
{
}
SysprofVisualizer *
sysprof_depth_visualizer_new (SysprofDepthVisualizerMode mode)
{
SysprofDepthVisualizer *self;
g_return_val_if_fail (mode == SYSPROF_DEPTH_VISUALIZER_COMBINED ||
mode == SYSPROF_DEPTH_VISUALIZER_KERNEL_ONLY ||
mode == SYSPROF_DEPTH_VISUALIZER_USER_ONLY,
NULL);
self = g_object_new (SYSPROF_TYPE_DEPTH_VISUALIZER, NULL);
self->mode = mode;
return SYSPROF_VISUALIZER (g_steal_pointer (&self));
}