[sysprof/wip/chergert/mem-preload: 34/43] memprof: start on visualizer



commit fae0644662db2381b3671c594ac8103e54adcbb0
Author: Christian Hergert <chergert redhat com>
Date:   Thu Feb 6 12:26:58 2020 -0800

    memprof: start on visualizer
    
    This just gets some mechanics in place so that we can draw asynchronously
    and scale the results until our next sized draw comes in (such as from
    changing zoom level).

 src/libsysprof-ui/sysprof-memprof-aid.c        |  12 +-
 src/libsysprof-ui/sysprof-memprof-visualizer.c | 340 ++++++++++++++++++++-----
 2 files changed, 285 insertions(+), 67 deletions(-)
---
diff --git a/src/libsysprof-ui/sysprof-memprof-aid.c b/src/libsysprof-ui/sysprof-memprof-aid.c
index a0f9866..5b9fbda 100644
--- a/src/libsysprof-ui/sysprof-memprof-aid.c
+++ b/src/libsysprof-ui/sysprof-memprof-aid.c
@@ -27,6 +27,7 @@
 #include "sysprof-memprof-aid.h"
 #include "sysprof-memprof-page.h"
 #include "sysprof-memprof-source.h"
+#include "sysprof-memprof-visualizer.h"
 
 struct _SysprofMemprofAid
 {
@@ -171,6 +172,7 @@ sysprof_memprof_aid_present_finish (SysprofAid    *aid,
   if (p->has_allocs)
     {
       SysprofVisualizerGroup *group;
+      SysprofVisualizer *row;
       SysprofPage *page;
 
       group = g_object_new (SYSPROF_TYPE_VISUALIZER_GROUP,
@@ -180,7 +182,13 @@ sysprof_memprof_aid_present_finish (SysprofAid    *aid,
                             "title", _("Memory"),
                             "visible", TRUE,
                             NULL);
-      sysprof_display_add_group (p->display, group);
+
+      row = g_object_new (SYSPROF_TYPE_MEMPROF_VISUALIZER,
+                          "title", _("Memory Allocations"),
+                          "height-request", 35,
+                          "visible", TRUE,
+                          NULL);
+      sysprof_visualizer_group_insert (group, row, 0, FALSE);
 
       page = g_object_new (SYSPROF_TYPE_MEMPROF_PAGE,
                            "title", _("Memory Allocations"),
@@ -195,6 +203,8 @@ sysprof_memprof_aid_present_finish (SysprofAid    *aid,
                                G_CALLBACK (on_group_activated_cb),
                                page,
                                0);
+
+      sysprof_display_add_group (p->display, group);
     }
 
   return g_task_propagate_boolean (G_TASK (result), error);
diff --git a/src/libsysprof-ui/sysprof-memprof-visualizer.c b/src/libsysprof-ui/sysprof-memprof-visualizer.c
index cf97d85..1c2209d 100644
--- a/src/libsysprof-ui/sysprof-memprof-visualizer.c
+++ b/src/libsysprof-ui/sysprof-memprof-visualizer.c
@@ -26,15 +26,27 @@
 
 typedef struct
 {
-  guint64 time;
-  guint64 size;
-} Memstat;
+  cairo_surface_t      *surface;
+  SysprofCaptureReader *reader;
+  GtkAllocation         alloc;
+  gint64                begin_time;
+  gint64                duration;
+  GdkRGBA               fg;
+  GdkRGBA               bg;
+} DrawContext;
 
 struct _SysprofMemprofVisualizer
 {
   SysprofVisualizer     parent_instance;
 
   SysprofCaptureReader *reader;
+  GCancellable         *cancellable;
+
+  cairo_surface_t      *surface;
+  gint                  surface_w;
+  gint                  surface_h;
+
+  guint                 queued_draw;
 
   gint64                begin_time;
   gint64                duration;
@@ -43,114 +55,310 @@ struct _SysprofMemprofVisualizer
 G_DEFINE_TYPE (SysprofMemprofVisualizer, sysprof_memprof_visualizer, SYSPROF_TYPE_VISUALIZER)
 
 static void
-load_data_cb (GObject      *object,
-              GAsyncResult *result,
-              gpointer      user_data)
+draw_context_finalize (DrawContext *draw)
+{
+  g_clear_pointer (&draw->surface, cairo_surface_destroy);
+}
+
+static void
+draw_context_unref (DrawContext *draw)
+{
+  g_atomic_rc_box_release_full (draw, (GDestroyNotify)draw_context_finalize);
+}
+
+static DrawContext *
+draw_context_ref (DrawContext *draw)
+{
+  return g_atomic_rc_box_acquire (draw);
+}
+
+static void
+sysprof_memprof_visualizer_set_reader (SysprofVisualizer    *visualizer,
+                                       SysprofCaptureReader *reader)
 {
-  SysprofMemprofVisualizer *self = (SysprofMemprofVisualizer *)object;
+  SysprofMemprofVisualizer *self = (SysprofMemprofVisualizer *)visualizer;
 
   g_assert (SYSPROF_IS_MEMPROF_VISUALIZER (self));
-  g_assert (G_IS_TASK (result));
 
-#if 0
-  if ((pc = g_task_propagate_pointer (G_TASK (result), NULL)))
-    {
-      g_clear_pointer (&self->cache, point_cache_unref);
-      self->cache = g_steal_pointer (&pc);
-    }
-#endif
+  if (reader == self->reader)
+    return;
+
+  g_clear_pointer (&self->reader, sysprof_capture_reader_unref);
+
+  self->reader = sysprof_capture_reader_ref (reader);
+  self->begin_time = sysprof_capture_reader_get_start_time (reader);
+  self->duration = sysprof_capture_reader_get_end_time (reader)
+                 - sysprof_capture_reader_get_start_time (reader);
 
   gtk_widget_queue_draw (GTK_WIDGET (self));
 }
 
-static gboolean
-collect_allocs_cb (const SysprofCaptureFrame *frame,
-                   gpointer                   user_data)
+SysprofMemprofVisualizer *
+sysprof_memprof_visualizer_new (void)
 {
-  GArray *ar = user_data;
+  return g_object_new (SYSPROF_TYPE_MEMPROF_VISUALIZER, NULL);
+}
 
-  g_assert (frame != NULL);
-  g_assert (ar != NULL);
+static void
+draw_context_add (cairo_t                        *cr,
+                  DrawContext                    *draw,
+                  const SysprofCaptureAllocation *ev)
+{
+  gint x;
+  gint y;
+
+  g_assert (draw != NULL);
+  g_assert (ev != NULL);
+
+  x = (ev->frame.time - draw->begin_time) / (gdouble)draw->duration * draw->alloc.width;
+  y = 0;
 
-  return TRUE;
+  cairo_rectangle (cr, x, y, 1, 1);
 }
 
 static void
-sysprof_memprof_visualizer_worker (GTask        *task,
-                                   gpointer      source_object,
-                                   gpointer      task_data,
-                                   GCancellable *cancellable)
+sysprof_memprof_visualizer_draw_worker (GTask        *task,
+                                        gpointer      source_object,
+                                        gpointer      task_data,
+                                        GCancellable *cancellable)
 {
-  SysprofMemprofVisualizer *self = source_object;
-  SysprofCaptureCursor *cursor = task_data;
-  GArray *ar;
+  DrawContext *draw = task_data;
+  SysprofCaptureFrameType type;
+  cairo_t *cr;
+  guint counter = 0;
 
   g_assert (G_IS_TASK (task));
-  g_assert (SYSPROF_IS_MEMPROF_VISUALIZER (self));
-  g_assert (cursor != NULL);
+  g_assert (draw != NULL);
+  g_assert (draw->surface != NULL);
+  g_assert (draw->reader != NULL);
   g_assert (!cancellable || G_IS_CANCELLABLE (cancellable));
 
-  ar = g_array_new (FALSE, FALSE, sizeof (Memstat));
-  sysprof_capture_cursor_foreach (cursor, collect_allocs_cb, ar);
+  cr = cairo_create (draw->surface);
+
+  /* Fill background first */
+  gdk_cairo_rectangle (cr, &draw->alloc);
+  gdk_cairo_set_source_rgba (cr, &draw->bg);
+  cairo_fill (cr);
 
-  g_task_return_pointer (task,
-                         g_steal_pointer (&ar),
-                         (GDestroyNotify) g_array_unref);
+  /* Now draw data points */
+  while (sysprof_capture_reader_peek_type (draw->reader, &type))
+    {
+      const SysprofCaptureAllocation *ev;
+
+      /* Cancellation check every 1000 frames */
+      if G_UNLIKELY (++counter == 1000)
+        {
+          if (g_task_return_error_if_cancelled (task))
+            {
+              cairo_destroy (cr);
+              return;
+            }
+
+          counter = 0;
+        }
+
+      /* We only care about memory frames here */
+      if (type != SYSPROF_CAPTURE_FRAME_MEMORY_ALLOC &&
+          type != SYSPROF_CAPTURE_FRAME_MEMORY_FREE)
+        {
+          if (sysprof_capture_reader_skip (draw->reader))
+            continue;
+          break;
+        }
+
+      if (type == SYSPROF_CAPTURE_FRAME_MEMORY_ALLOC)
+        ev = sysprof_capture_reader_read_memory_alloc (draw->reader);
+      else
+        ev = sysprof_capture_reader_read_memory_free (draw->reader);
+
+      if (ev == NULL)
+        break;
+
+      draw_context_add (cr, draw, ev);
+    }
+
+  /* Now fill our draw paths */
+  gdk_cairo_set_source_rgba (cr, &draw->fg);
+  cairo_fill (cr);
+  cairo_destroy (cr);
+
+  g_task_return_boolean (task, TRUE);
 }
 
 static void
-sysprof_memprof_visualizer_set_reader (SysprofVisualizer    *visualizer,
-                                       SysprofCaptureReader *reader)
+draw_finished (GObject      *object,
+               GAsyncResult *result,
+               gpointer      user_data)
+{
+  g_autoptr(SysprofMemprofVisualizer) self = user_data;
+  g_autoptr(GError) error = NULL;
+
+  g_assert (object == NULL);
+  g_assert (G_IS_TASK (result));
+  g_assert (SYSPROF_IS_MEMPROF_VISUALIZER (self));
+
+  if (g_task_propagate_boolean (G_TASK (result), &error))
+    {
+      DrawContext *draw = g_task_get_task_data (G_TASK (result));
+
+      g_clear_pointer (&self->surface, cairo_surface_destroy);
+
+      self->surface = g_steal_pointer (&draw->surface);
+      self->surface_w = draw->alloc.width;
+      self->surface_h = draw->alloc.height;
+
+      gtk_widget_queue_draw (GTK_WIDGET (self));
+    }
+}
+
+static gboolean
+sysprof_memprof_visualizer_begin_draw (SysprofMemprofVisualizer *self)
 {
-  SysprofMemprofVisualizer *self = (SysprofMemprofVisualizer *)visualizer;
-  static const SysprofCaptureFrameType types[] = {
-    SYSPROF_CAPTURE_FRAME_MEMORY_ALLOC,
-    SYSPROF_CAPTURE_FRAME_MEMORY_FREE,
-  };
-  g_autoptr(SysprofCaptureCursor) cursor = NULL;
   g_autoptr(GTask) task = NULL;
-  SysprofCaptureCondition *c;
+  GtkStyleContext *style_context;
+  GtkAllocation alloc;
+  GtkStateFlags state;
+  DrawContext *draw;
+  GdkWindow *window;
+  gint scale;
 
   g_assert (SYSPROF_IS_MEMPROF_VISUALIZER (self));
-  g_assert (reader != NULL);
 
-  self->begin_time = sysprof_capture_reader_get_start_time (reader);
-  self->duration = sysprof_capture_reader_get_end_time (reader)
-                 - sysprof_capture_reader_get_start_time (reader);
+  self->queued_draw = 0;
+
+  /* Make sure we even need to draw */
+  gtk_widget_get_allocation (GTK_WIDGET (self), &alloc);
+  if (self->reader == NULL ||
+      !gtk_widget_get_visible (GTK_WIDGET (self)) ||
+      !gtk_widget_get_mapped (GTK_WIDGET (self)) ||
+      alloc.width == 0 || alloc.height == 0)
+    return G_SOURCE_REMOVE;
+
+  draw = g_atomic_rc_box_new0 (DrawContext);
+  draw->alloc.width = alloc.width;
+  draw->alloc.height = alloc.height;
+  draw->reader = sysprof_capture_reader_copy (self->reader);
+  draw->begin_time = self->begin_time;
+  draw->duration = self->duration;
+
+  /* Create a surface to draw to */
+  window = gtk_widget_get_window (GTK_WIDGET (self));
+  scale = gtk_widget_get_scale_factor (GTK_WIDGET (self));
+  draw->surface = gdk_window_create_similar_image_surface (window, CAIRO_FORMAT_RGB24, alloc.width, 
alloc.height, scale);
 
-  cursor = sysprof_capture_cursor_new (reader);
-  c = sysprof_capture_condition_new_where_type_in (G_N_ELEMENTS (types), types);
-  sysprof_capture_cursor_add_condition (cursor, g_steal_pointer (&c));
+  /* Get our styles to render with so we can do off-main-thread */
+  state = gtk_widget_get_state_flags (GTK_WIDGET (self));
+  style_context = gtk_widget_get_style_context (GTK_WIDGET (self));
+  gtk_style_context_get_color (style_context, state, &draw->fg);
+  /* Takin the render cost for render_background() isn't worth it on the
+   * main thread. The main thing we want here is an opaque surface that we
+   * can quickly draw as a texture when rendering.
+   */
+  G_GNUC_BEGIN_IGNORE_DEPRECATIONS
+  gtk_style_context_get_background_color (style_context, state, &draw->bg);
+  G_GNUC_END_IGNORE_DEPRECATIONS
 
-  task = g_task_new (self, NULL, load_data_cb, NULL);
-  g_task_set_source_tag (task, sysprof_memprof_visualizer_set_reader);
-  g_task_set_task_data (task,
-                        g_steal_pointer (&cursor),
-                        (GDestroyNotify)sysprof_capture_cursor_unref);
-  g_task_run_in_thread (task, sysprof_memprof_visualizer_worker);
+  g_cancellable_cancel (self->cancellable);
+  g_clear_object (&self->cancellable);
+  self->cancellable = g_cancellable_new ();
+
+  task = g_task_new (NULL, self->cancellable, draw_finished, g_object_ref (self));
+  g_task_set_source_tag (task, sysprof_memprof_visualizer_begin_draw);
+  g_task_set_task_data (task, g_steal_pointer (&draw), (GDestroyNotify)draw_context_unref);
+  g_task_run_in_thread (task, sysprof_memprof_visualizer_draw_worker);
+
+  return G_SOURCE_REMOVE;
 }
 
-SysprofMemprofVisualizer *
-sysprof_memprof_visualizer_new (void)
+static void
+sysprof_memprof_visualizer_queue_redraw (SysprofMemprofVisualizer *self)
 {
-  return g_object_new (SYSPROF_TYPE_MEMPROF_VISUALIZER, NULL);
+  g_assert (SYSPROF_IS_MEMPROF_VISUALIZER (self));
+
+  if (self->queued_draw == 0)
+    self->queued_draw = g_idle_add_full (G_PRIORITY_HIGH_IDLE,
+                                         (GSourceFunc) sysprof_memprof_visualizer_begin_draw,
+                                         g_object_ref (self),
+                                         g_object_unref);
 }
 
 static void
-sysprof_memprof_visualizer_finalize (GObject *object)
+sysprof_memprof_visualizer_size_allocate (GtkWidget     *widget,
+                                          GtkAllocation *alloc)
 {
-  SysprofMemprofVisualizer *self = (SysprofMemprofVisualizer *)object;
+  SysprofMemprofVisualizer *self = (SysprofMemprofVisualizer *)widget;
+
+  g_assert (GTK_IS_WIDGET (widget));
+  g_assert (alloc != NULL);
+
+  GTK_WIDGET_CLASS (sysprof_memprof_visualizer_parent_class)->size_allocate (widget, alloc);
+
+  sysprof_memprof_visualizer_queue_redraw (self);
+}
+
+static void
+sysprof_memprof_visualizer_destroy (GtkWidget *widget)
+{
+  SysprofMemprofVisualizer *self = (SysprofMemprofVisualizer *)widget;
+
+  g_clear_pointer (&self->reader, sysprof_capture_reader_unref);
+  g_clear_pointer (&self->surface, cairo_surface_destroy);
+  g_clear_handle_id (&self->queued_draw, g_source_remove);
+
+  GTK_WIDGET_CLASS (sysprof_memprof_visualizer_parent_class)->destroy (widget);
+}
+
+static gboolean
+sysprof_memprof_visualizer_draw (GtkWidget *widget,
+                                 cairo_t   *cr)
+{
+  SysprofMemprofVisualizer *self = (SysprofMemprofVisualizer *)widget;
+
+  g_assert (SYSPROF_IS_MEMPROF_VISUALIZER (self));
+  g_assert (cr != NULL);
+
+  if (self->surface != NULL)
+    {
+      GtkAllocation alloc;
+
+      gtk_widget_get_allocation (widget, &alloc);
+
+      cairo_save (cr);
+      cairo_set_operator (cr, CAIRO_OPERATOR_SOURCE);
+      cairo_rectangle (cr, 0, 0, alloc.width, alloc.height);
+
+      /* We might be drawing an updated image in the background, and this
+       * will take our current surface (which is the wrong size) and draw
+       * it stretched to fit the allocation. That gives us *something* that
+       * represents the end result even if it is a bit blurry in the mean
+       * time. Allocators take a while to render anyway.
+       */
+      if (self->surface_w != alloc.width || self->surface_h != alloc.height)
+        {
+          cairo_scale (cr,
+                       (gdouble)alloc.width / (gdouble)self->surface_w,
+                       (gdouble)alloc.height / (gdouble)self->surface_h);
+        }
+
+      cairo_set_source_surface (cr, self->surface, 0, 0);
+      cairo_fill (cr);
+      cairo_restore (cr);
+
+      return GDK_EVENT_PROPAGATE;
+    }
 
-  G_OBJECT_CLASS (sysprof_memprof_visualizer_parent_class)->finalize (object);
+  return GTK_WIDGET_CLASS (sysprof_memprof_visualizer_parent_class)->draw (widget, cr);
 }
 
 static void
 sysprof_memprof_visualizer_class_init (SysprofMemprofVisualizerClass *klass)
 {
-  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+  GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
   SysprofVisualizerClass *visualizer_class = SYSPROF_VISUALIZER_CLASS (klass);
 
-  object_class->finalize = sysprof_memprof_visualizer_finalize;
+  widget_class->destroy = sysprof_memprof_visualizer_destroy;
+  widget_class->draw = sysprof_memprof_visualizer_draw;
+  widget_class->size_allocate = sysprof_memprof_visualizer_size_allocate;
 
   visualizer_class->set_reader = sysprof_memprof_visualizer_set_reader;
 }


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]