[sysprof/wip/chergert/mem-preload] libsysprof-ui: start on memory page and profile



commit 74308ac430c3c3e39b9d28377064f0681ba25492
Author: Christian Hergert <chergert redhat com>
Date:   Mon Feb 3 20:19:13 2020 -0800

    libsysprof-ui: start on memory page and profile
    
    This starts to generate a stack stash with the allocation sizes so that
    we can create a view somewhat like memprof.

 src/libsysprof-ui/meson.build            |    1 +
 src/libsysprof-ui/sysprof-memory-page.c  | 1294 ++++++++++++++++++++++++++++++
 src/libsysprof-ui/sysprof-memory-page.h  |   51 ++
 src/libsysprof-ui/sysprof-memory-page.ui |  235 ++++++
 src/libsysprof-ui/sysprof-ui-private.h   |    3 +
 src/libsysprof/sysprof-memory-profile.c  |  113 ++-
 src/libsysprof/sysprof-memory-profile.h  |   14 +-
 7 files changed, 1707 insertions(+), 4 deletions(-)
---
diff --git a/src/libsysprof-ui/meson.build b/src/libsysprof-ui/meson.build
index 52c9121..af88ff4 100644
--- a/src/libsysprof-ui/meson.build
+++ b/src/libsysprof-ui/meson.build
@@ -45,6 +45,7 @@ libsysprof_ui_private_sources = [
   'sysprof-marks-page.c',
   'sysprof-mark-visualizer.c',
   'sysprof-memory-aid.c',
+  'sysprof-memory-page.c',
   'sysprof-netdev-aid.c',
   'sysprof-procs-visualizer.c',
   'sysprof-profiler-assistant.c',
diff --git a/src/libsysprof-ui/sysprof-memory-page.c b/src/libsysprof-ui/sysprof-memory-page.c
new file mode 100644
index 0000000..10b4058
--- /dev/null
+++ b/src/libsysprof-ui/sysprof-memory-page.c
@@ -0,0 +1,1294 @@
+/* sysprof-memory-page.c
+ *
+ * Copyright 2016-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
+ */
+
+/* Sysprof -- Sampling, systemwide CPU profiler
+ * Copyright 2004, Red Hat, Inc.
+ * Copyright 2004, 2005, 2006, 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 <dazzle.h>
+#include <glib/gi18n.h>
+
+#include "../stackstash.h"
+
+#include "sysprof-cell-renderer-percent.h"
+#include "sysprof-memory-page.h"
+#include "sysprof-profile.h"
+
+typedef struct
+{
+  SysprofMemoryProfile  *profile;
+
+  GtkTreeView              *callers_view;
+  GtkTreeView              *functions_view;
+  GtkTreeView              *descendants_view;
+  GtkTreeViewColumn        *descendants_name_column;
+  GtkStack                 *stack;
+
+  GQueue                   *history;
+
+  guint                     profile_size;
+  guint                     loading;
+} SysprofMemoryPagePrivate;
+
+G_DEFINE_TYPE_WITH_PRIVATE (SysprofMemoryPage, sysprof_memory_page, SYSPROF_TYPE_PAGE)
+
+enum {
+  PROP_0,
+  PROP_PROFILE,
+  N_PROPS
+};
+
+enum {
+  GO_PREVIOUS,
+  N_SIGNALS
+};
+
+enum {
+  COLUMN_NAME,
+  COLUMN_SELF,
+  COLUMN_TOTAL,
+  COLUMN_POINTER,
+  COLUMN_HITS,
+};
+
+static void sysprof_memory_page_update_descendants (SysprofMemoryPage *self,
+                                                    StackNode         *node);
+
+static GParamSpec *properties [N_PROPS];
+static guint signals [N_SIGNALS];
+
+static guint
+sysprof_memory_page_get_profile_size (SysprofMemoryPage *self)
+{
+  SysprofMemoryPagePrivate *priv = sysprof_memory_page_get_instance_private (self);
+  StackStash *stash;
+  StackNode *node;
+  guint size = 0;
+
+  g_assert (SYSPROF_IS_MEMORY_PAGE (self));
+
+  if (priv->profile_size != 0)
+    return priv->profile_size;
+
+  if (priv->profile == NULL)
+    return 0;
+
+  if (NULL == (stash = sysprof_memory_profile_get_stash (priv->profile)))
+    return 0;
+
+  for (node = stack_stash_get_root (stash); node != NULL; node = node->siblings)
+    size += node->total;
+
+  priv->profile_size = size;
+
+  return size;
+}
+
+static void
+build_functions_store (StackNode *node,
+                       gpointer   user_data)
+{
+  struct {
+    GtkListStore *store;
+    gdouble profile_size;
+  } *state = user_data;
+  GtkTreeIter iter;
+  const StackNode *n;
+  guint size = 0;
+  guint total = 0;
+
+  g_assert (state != NULL);
+  g_assert (GTK_IS_LIST_STORE (state->store));
+
+  for (n = node; n != NULL; n = n->next)
+    {
+      size += n->size;
+      if (n->toplevel)
+        total += n->total;
+    }
+
+  gtk_list_store_append (state->store, &iter);
+  gtk_list_store_set (state->store, &iter,
+                      COLUMN_NAME, U64_TO_POINTER(node->data),
+                      COLUMN_SELF, 100.0 * size / state->profile_size,
+                      COLUMN_TOTAL, 100.0 * total / state->profile_size,
+                      COLUMN_POINTER, node,
+                      -1);
+
+}
+
+static void
+sysprof_memory_page_load (SysprofMemoryPage    *self,
+                          SysprofMemoryProfile *profile)
+{
+  SysprofMemoryPagePrivate *priv = sysprof_memory_page_get_instance_private (self);
+  GtkListStore *functions;
+  StackStash *stash;
+  StackNode *n;
+  GtkTreeIter iter;
+  struct {
+    GtkListStore *store;
+    gdouble profile_size;
+  } state = { 0 };
+
+  g_assert (SYSPROF_IS_MEMORY_PAGE (self));
+  g_assert (SYSPROF_IS_MEMORY_PROFILE (profile));
+
+  /*
+   * TODO: This is probably the type of thing we want to do off the main
+   *       thread. We should be able to build the tree models off thread
+   *       and then simply apply them on the main thread.
+   *
+   *       In the mean time, we should set the state of the widget to
+   *       insensitive and give some indication of loading progress.
+   */
+
+  if (!g_set_object (&priv->profile, profile))
+    return;
+
+  if (sysprof_memory_profile_is_empty (profile))
+    return;
+
+  stash = sysprof_memory_profile_get_stash (profile);
+
+  for (n = stack_stash_get_root (stash); n; n = n->siblings)
+    state.profile_size += n->total;
+
+  functions = gtk_list_store_new (4, G_TYPE_STRING, G_TYPE_DOUBLE, G_TYPE_DOUBLE, G_TYPE_POINTER);
+
+  state.store = functions;
+  stack_stash_foreach_by_address (stash, build_functions_store, &state);
+
+  gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE (functions),
+                                        COLUMN_TOTAL,
+                                        GTK_SORT_DESCENDING);
+
+  gtk_tree_view_set_model (priv->functions_view, GTK_TREE_MODEL (functions));
+  gtk_tree_view_set_model (priv->callers_view, NULL);
+  gtk_tree_view_set_model (priv->descendants_view, NULL);
+
+  if (gtk_tree_model_get_iter_first (GTK_TREE_MODEL (functions), &iter))
+    {
+      GtkTreeSelection *selection;
+
+      selection = gtk_tree_view_get_selection (priv->functions_view);
+      gtk_tree_selection_select_iter (selection, &iter);
+    }
+
+  gtk_stack_set_visible_child_name (priv->stack, "callgraph");
+
+  g_clear_object (&functions);
+}
+
+void
+_sysprof_memory_page_set_failed (SysprofMemoryPage *self)
+{
+  SysprofMemoryPagePrivate *priv = sysprof_memory_page_get_instance_private (self);
+
+  g_return_if_fail (SYSPROF_IS_MEMORY_PAGE (self));
+
+  gtk_stack_set_visible_child_name (priv->stack, "empty-state");
+}
+
+static void
+sysprof_memory_page_unload (SysprofMemoryPage *self)
+{
+  SysprofMemoryPagePrivate *priv = sysprof_memory_page_get_instance_private (self);
+
+  g_assert (SYSPROF_IS_MEMORY_PAGE (self));
+  g_assert (SYSPROF_IS_MEMORY_PROFILE (priv->profile));
+
+  g_queue_clear (priv->history);
+  g_clear_object (&priv->profile);
+  priv->profile_size = 0;
+
+  gtk_tree_view_set_model (priv->callers_view, NULL);
+  gtk_tree_view_set_model (priv->functions_view, NULL);
+  gtk_tree_view_set_model (priv->descendants_view, NULL);
+
+  gtk_stack_set_visible_child_name (priv->stack, "empty-state");
+}
+
+/**
+ * sysprof_memory_page_get_profile:
+ *
+ * Returns: (transfer none): An #SysprofMemoryProfile.
+ */
+SysprofMemoryProfile *
+sysprof_memory_page_get_profile (SysprofMemoryPage *self)
+{
+  SysprofMemoryPagePrivate *priv = sysprof_memory_page_get_instance_private (self);
+
+  g_return_val_if_fail (SYSPROF_IS_MEMORY_PAGE (self), NULL);
+
+  return priv->profile;
+}
+
+void
+sysprof_memory_page_set_profile (SysprofMemoryPage    *self,
+                                 SysprofMemoryProfile *profile)
+{
+  SysprofMemoryPagePrivate *priv = sysprof_memory_page_get_instance_private (self);
+
+  g_return_if_fail (SYSPROF_IS_MEMORY_PAGE (self));
+  g_return_if_fail (!profile || SYSPROF_IS_MEMORY_PROFILE (profile));
+
+  if (profile != priv->profile)
+    {
+      if (priv->profile)
+        sysprof_memory_page_unload (self);
+
+      if (profile)
+        sysprof_memory_page_load (self, profile);
+
+      g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_PROFILE]);
+    }
+}
+
+static void
+sysprof_memory_page_expand_descendants (SysprofMemoryPage *self)
+{
+  SysprofMemoryPagePrivate *priv = sysprof_memory_page_get_instance_private (self);
+  GtkTreeModel *model;
+  GList *all_paths = NULL;
+  GtkTreePath *first_path;
+  GtkTreeIter iter;
+  gdouble top_value = 0;
+  gint max_rows = 40; /* FIXME */
+  gint n_rows;
+
+  g_assert (SYSPROF_IS_MEMORY_PAGE (self));
+
+  model = gtk_tree_view_get_model (priv->descendants_view);
+  first_path = gtk_tree_path_new_first ();
+  all_paths = g_list_prepend (all_paths, first_path);
+  n_rows = 1;
+
+  gtk_tree_model_get_iter (model, &iter, first_path);
+  gtk_tree_model_get (model, &iter,
+                      COLUMN_TOTAL, &top_value,
+                      -1);
+
+  while ((all_paths != NULL) && (n_rows < max_rows))
+    {
+      GtkTreeIter best_iter;
+      GtkTreePath *best_path = NULL;
+      GList *list;
+      gdouble best_value = 0.0;
+      gint n_children;
+      gint i;
+
+      for (list = all_paths; list != NULL; list = list->next)
+        {
+          GtkTreePath *path = list->data;
+
+          g_assert (path != NULL);
+
+          if (gtk_tree_model_get_iter (model, &iter, path))
+            {
+              gdouble value;
+
+              gtk_tree_model_get (model, &iter,
+                                  COLUMN_TOTAL, &value,
+                                  -1);
+
+              if (value >= best_value)
+                {
+                  best_value = value;
+                  best_path = path;
+                  best_iter = iter;
+                }
+            }
+        }
+
+      n_children = gtk_tree_model_iter_n_children (model, &best_iter);
+
+      if ((n_children > 0) &&
+          ((best_value / top_value) > 0.04) &&
+          ((n_children + gtk_tree_path_get_depth (best_path)) / (gdouble)max_rows) < (best_value / 
top_value))
+        {
+          gtk_tree_view_expand_row (priv->descendants_view, best_path, FALSE);
+          n_rows += n_children;
+
+          if (gtk_tree_path_get_depth (best_path) < 4)
+            {
+              GtkTreePath *path;
+
+              path = gtk_tree_path_copy (best_path);
+              gtk_tree_path_down (path);
+
+              for (i = 0; i < n_children; i++)
+                {
+                  all_paths = g_list_prepend (all_paths, path);
+
+                  path = gtk_tree_path_copy (path);
+                  gtk_tree_path_next (path);
+                }
+
+              gtk_tree_path_free (path);
+            }
+        }
+
+      all_paths = g_list_remove (all_paths, best_path);
+
+      /* Always expand at least once */
+      if ((all_paths == NULL) && (n_rows == 1))
+        gtk_tree_view_expand_row (priv->descendants_view, best_path, FALSE);
+
+      gtk_tree_path_free (best_path);
+    }
+
+  g_list_free_full (all_paths, (GDestroyNotify)gtk_tree_path_free);
+}
+
+typedef struct
+{
+  StackNode   *node;
+  const gchar *name;
+  guint        self;
+  guint        total;
+} Caller;
+
+static Caller *
+caller_new (StackNode *node)
+{
+  Caller *c;
+
+  c = g_slice_new (Caller);
+  c->name = U64_TO_POINTER (node->data);
+  c->self = 0;
+  c->total = 0;
+  c->node = node;
+
+  return c;
+}
+
+static void
+caller_free (gpointer data)
+{
+  Caller *c = data;
+  g_slice_free (Caller, c);
+}
+
+static void
+sysprof_memory_page_function_selection_changed (SysprofMemoryPage *self,
+                                                GtkTreeSelection  *selection)
+{
+  SysprofMemoryPagePrivate *priv = sysprof_memory_page_get_instance_private (self);
+  GtkTreeModel *model = NULL;
+  GtkTreeIter iter;
+  GtkListStore *callers_store;
+  g_autoptr(GHashTable) callers = NULL;
+  g_autoptr(GHashTable) processed = NULL;
+  StackNode *callees = NULL;
+  StackNode *node;
+
+  g_assert (SYSPROF_IS_MEMORY_PAGE (self));
+  g_assert (GTK_IS_TREE_SELECTION (selection));
+
+  if (!gtk_tree_selection_get_selected (selection, &model, &iter))
+    {
+      gtk_tree_view_set_model (priv->callers_view, NULL);
+      gtk_tree_view_set_model (priv->descendants_view, NULL);
+      return;
+    }
+
+  gtk_tree_model_get (model, &iter,
+                      COLUMN_POINTER, &callees,
+                      -1);
+
+  sysprof_memory_page_update_descendants (self, callees);
+
+  callers_store = gtk_list_store_new (4,
+                                      G_TYPE_STRING,
+                                      G_TYPE_DOUBLE,
+                                      G_TYPE_DOUBLE,
+                                      G_TYPE_POINTER);
+
+  callers = g_hash_table_new_full (NULL, NULL, NULL, caller_free);
+  processed = g_hash_table_new (NULL, NULL);
+
+  for (node = callees; node != NULL; node = node->next)
+    {
+      Caller *c;
+
+      if (!node->parent)
+        continue;
+
+      c = g_hash_table_lookup (callers, U64_TO_POINTER (node->parent->data));
+
+      if (c == NULL)
+        {
+          c = caller_new (node->parent);
+          g_hash_table_insert (callers, (gpointer)c->name, c);
+        }
+    }
+
+  for (node = callees; node != NULL; node = node->next)
+    {
+      StackNode *top_caller = node->parent;
+      StackNode *top_callee = node;
+      StackNode *n;
+      Caller *c;
+
+      if (!node->parent)
+        continue;
+
+      /*
+       * We could have a situation where the function was called in a
+       * reentrant fashion, so we want to take the top-most match in the
+       * stack.
+       */
+      for (n = node; n && n->parent; n = n->parent)
+        {
+          if (n->data == node->data && n->parent->data == node->parent->data)
+            {
+              top_caller = n->parent;
+              top_callee = n;
+            }
+        }
+
+      c = g_hash_table_lookup (callers, U64_TO_POINTER (node->parent->data));
+
+      g_assert (c != NULL);
+
+      if (!g_hash_table_lookup (processed, top_caller))
+        {
+          c->total += top_callee->total;
+          g_hash_table_insert (processed, top_caller, top_caller);
+        }
+
+      c->self += node->size;
+    }
+
+  {
+    GHashTableIter hiter;
+    gpointer key, value;
+    guint size = 0;
+
+    size = MAX (1, sysprof_memory_page_get_profile_size (self));
+
+    g_hash_table_iter_init (&hiter, callers);
+
+    while (g_hash_table_iter_next (&hiter, &key, &value))
+      {
+        Caller *c = value;
+
+        gtk_list_store_append (callers_store, &iter);
+        gtk_list_store_set (callers_store, &iter,
+                            COLUMN_NAME, c->name,
+                            COLUMN_SELF, c->self * 100.0 / size,
+                            COLUMN_TOTAL, c->total * 100.0 / size,
+                            COLUMN_POINTER, c->node,
+                            -1);
+      }
+  }
+
+  gtk_tree_view_set_model (priv->callers_view, GTK_TREE_MODEL (callers_store));
+  gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE (callers_store),
+                                        COLUMN_TOTAL,
+                                        GTK_SORT_DESCENDING);
+
+  g_clear_object (&callers_store);
+}
+
+static void
+sysprof_memory_page_set_node (SysprofMemoryPage *self,
+                              StackNode         *node)
+{
+  SysprofMemoryPagePrivate *priv = sysprof_memory_page_get_instance_private (self);
+  GtkTreeModel *model;
+  GtkTreeIter iter;
+
+  g_assert (SYSPROF_IS_MEMORY_PAGE (self));
+  g_assert (node != NULL);
+
+  if (priv->profile == NULL)
+    return;
+
+  model = gtk_tree_view_get_model (priv->functions_view);
+
+  if (gtk_tree_model_get_iter_first (model, &iter))
+    {
+      do
+        {
+          StackNode *item = NULL;
+
+          gtk_tree_model_get (model, &iter,
+                              COLUMN_POINTER, &item,
+                              -1);
+
+          if (item != NULL && item->data == node->data)
+            {
+              GtkTreeSelection *selection;
+
+              selection = gtk_tree_view_get_selection (priv->functions_view);
+              gtk_tree_selection_select_iter (selection, &iter);
+
+              break;
+            }
+        }
+      while (gtk_tree_model_iter_next (model, &iter));
+    }
+}
+
+static void
+sysprof_memory_page_descendant_activated (SysprofMemoryPage *self,
+                                          GtkTreePath       *path,
+                                          GtkTreeViewColumn *column,
+                                          GtkTreeView       *tree_view)
+{
+  GtkTreeModel *model;
+  StackNode *node = NULL;
+  GtkTreeIter iter;
+
+  g_assert (SYSPROF_IS_MEMORY_PAGE (self));
+  g_assert (GTK_IS_TREE_VIEW (tree_view));
+  g_assert (path != NULL);
+  g_assert (GTK_IS_TREE_VIEW_COLUMN (column));
+
+  model = gtk_tree_view_get_model (tree_view);
+
+  if (!gtk_tree_model_get_iter (model, &iter, path))
+    return;
+
+  gtk_tree_model_get (model, &iter,
+                      COLUMN_POINTER, &node,
+                      -1);
+
+  if (node != NULL)
+    sysprof_memory_page_set_node (self, node);
+}
+
+static void
+sysprof_memory_page_caller_activated (SysprofMemoryPage *self,
+                                      GtkTreePath       *path,
+                                      GtkTreeViewColumn *column,
+                                      GtkTreeView       *tree_view)
+{
+  GtkTreeModel *model;
+  StackNode *node = NULL;
+  GtkTreeIter iter;
+
+  g_assert (SYSPROF_IS_MEMORY_PAGE (self));
+  g_assert (GTK_IS_TREE_VIEW (tree_view));
+  g_assert (path != NULL);
+  g_assert (GTK_IS_TREE_VIEW_COLUMN (column));
+
+  model = gtk_tree_view_get_model (tree_view);
+
+  if (!gtk_tree_model_get_iter (model, &iter, path))
+    return;
+
+  gtk_tree_model_get (model, &iter,
+                      COLUMN_POINTER, &node,
+                      -1);
+
+  if (node != NULL)
+    sysprof_memory_page_set_node (self, node);
+}
+
+static void
+sysprof_memory_page_tag_data_func (GtkTreeViewColumn *column,
+                                   GtkCellRenderer   *cell,
+                                   GtkTreeModel      *model,
+                                   GtkTreeIter       *iter,
+                                   gpointer           data)
+{
+  SysprofMemoryPage *self = data;
+  SysprofMemoryPagePrivate *priv = sysprof_memory_page_get_instance_private (self);
+  StackNode *node = NULL;
+  const gchar *str = NULL;
+
+  if (priv->profile == NULL)
+    return;
+
+  gtk_tree_model_get (model, iter, COLUMN_POINTER, &node, -1);
+
+  if (node && node->data)
+    {
+      GQuark tag;
+
+      tag = sysprof_memory_profile_get_tag (priv->profile, GSIZE_TO_POINTER (node->data));
+      if (tag != 0)
+        str = g_quark_to_string (tag);
+    }
+
+  g_object_set (cell, "text", str, NULL);
+}
+
+static void
+sysprof_memory_page_real_go_previous (SysprofMemoryPage *self)
+{
+  SysprofMemoryPagePrivate *priv = sysprof_memory_page_get_instance_private (self);
+  StackNode *node;
+
+  g_assert (SYSPROF_IS_MEMORY_PAGE (self));
+
+  node = g_queue_pop_head (priv->history);
+
+  if (NULL != (node = g_queue_peek_head (priv->history)))
+    sysprof_memory_page_set_node (self, node);
+}
+
+static void
+descendants_view_move_cursor_cb (GtkTreeView     *descendants_view,
+                                 GtkMovementStep  step,
+                                 int              direction,
+                                 gpointer         user_data)
+{
+  if (step == GTK_MOVEMENT_VISUAL_POSITIONS)
+    {
+      GtkTreePath *path;
+
+      gtk_tree_view_get_cursor (descendants_view, &path, NULL);
+
+      if (direction == 1)
+        {
+          gtk_tree_view_expand_row (descendants_view, path, FALSE);
+          g_signal_stop_emission_by_name (descendants_view, "move-cursor");
+        }
+      else if (direction == -1)
+        {
+          gtk_tree_view_collapse_row (descendants_view, path);
+          g_signal_stop_emission_by_name (descendants_view, "move-cursor");
+        }
+
+      gtk_tree_path_free (path);
+    }
+}
+
+static void
+copy_tree_view_selection_cb (GtkTreeModel *model,
+                             GtkTreePath  *path,
+                             GtkTreeIter  *iter,
+                             gpointer      data)
+{
+  g_autofree gchar *name = NULL;
+  gchar sstr[16];
+  gchar tstr[16];
+  GString *str = data;
+  gdouble self;
+  gdouble total;
+  gint depth;
+
+  g_assert (GTK_IS_TREE_MODEL (model));
+  g_assert (path != NULL);
+  g_assert (iter != NULL);
+  g_assert (str != NULL);
+
+  depth = gtk_tree_path_get_depth (path);
+  gtk_tree_model_get (model, iter,
+                      COLUMN_NAME, &name,
+                      COLUMN_SELF, &self,
+                      COLUMN_TOTAL, &total,
+                      -1);
+
+  g_snprintf (sstr, sizeof sstr, "%.2lf%%", self);
+  g_snprintf (tstr, sizeof tstr, "%.2lf%%", total);
+
+  g_string_append_printf (str, "[%8s] [%8s]    ", sstr, tstr);
+
+  for (gint i = 1; i < depth; i++)
+    g_string_append (str, "  ");
+  g_string_append (str, name);
+  g_string_append_c (str, '\n');
+}
+
+static void
+copy_tree_view_selection (GtkTreeView *tree_view)
+{
+  g_autoptr(GString) str = NULL;
+  GtkClipboard *clipboard;
+
+  g_assert (GTK_IS_TREE_VIEW (tree_view));
+
+  str = g_string_new ("      SELF      TOTAL    FUNCTION\n");
+  gtk_tree_selection_selected_foreach (gtk_tree_view_get_selection (tree_view),
+                                       copy_tree_view_selection_cb,
+                                       str);
+
+  clipboard = gtk_widget_get_clipboard (GTK_WIDGET (tree_view), GDK_SELECTION_CLIPBOARD);
+  gtk_clipboard_set_text (clipboard, str->str, str->len);
+}
+
+static void
+sysprof_memory_page_copy_cb (GtkWidget         *widget,
+                             SysprofMemoryPage *self)
+{
+  SysprofMemoryPagePrivate *priv = sysprof_memory_page_get_instance_private (self);
+  GtkWidget *toplevel;
+  GtkWidget *focus;
+
+  g_assert (GTK_IS_WIDGET (widget));
+  g_assert (SYSPROF_IS_MEMORY_PAGE (self));
+
+  if (!(toplevel = gtk_widget_get_toplevel (widget)) ||
+      !GTK_IS_WINDOW (toplevel) ||
+      !(focus = gtk_window_get_focus (GTK_WINDOW (toplevel))))
+    return;
+
+  if (focus == GTK_WIDGET (priv->descendants_view))
+    copy_tree_view_selection (priv->descendants_view);
+  else if (focus == GTK_WIDGET (priv->callers_view))
+    copy_tree_view_selection (priv->callers_view);
+  else if (focus == GTK_WIDGET (priv->functions_view))
+    copy_tree_view_selection (priv->functions_view);
+}
+
+static void
+sysprof_memory_page_generate_cb (GObject      *object,
+                                 GAsyncResult *result,
+                                 gpointer      user_data)
+{
+  SysprofProfile *profile = (SysprofProfile *)object;
+  SysprofMemoryPage *self;
+  g_autoptr(GTask) task = user_data;
+  g_autoptr(GError) error = NULL;
+
+  g_assert (SYSPROF_IS_PROFILE (profile));
+  g_assert (G_IS_ASYNC_RESULT (result));
+  g_assert (G_IS_TASK (task));
+
+  self = g_task_get_source_object (task);
+
+  if (!sysprof_profile_generate_finish (profile, result, &error))
+    g_task_return_error (task, g_steal_pointer (&error));
+  else
+    sysprof_memory_page_set_profile (self, SYSPROF_MEMORY_PROFILE (profile));
+}
+
+static void
+sysprof_memory_page_load_async (SysprofPage             *page,
+                                SysprofCaptureReader    *reader,
+                                SysprofSelection        *selection,
+                                SysprofCaptureCondition *filter,
+                                GCancellable            *cancellable,
+                                GAsyncReadyCallback      callback,
+                                gpointer                 user_data)
+{
+  SysprofMemoryPage *self = (SysprofMemoryPage *)page;
+  g_autoptr(SysprofCaptureReader) copy = NULL;
+  g_autoptr(SysprofProfile) profile = NULL;
+  g_autoptr(GTask) task = NULL;
+
+  g_assert (SYSPROF_IS_MEMORY_PAGE (self));
+  g_assert (reader != NULL);
+  g_assert (SYSPROF_IS_SELECTION (selection));
+  g_assert (!cancellable || G_IS_CANCELLABLE (cancellable));
+
+  task = g_task_new (self, cancellable, callback, user_data);
+  g_task_set_source_tag (task, sysprof_memory_page_load_async);
+
+  copy = sysprof_capture_reader_copy (reader);
+
+  profile = sysprof_memory_profile_new_with_selection (selection);
+  sysprof_profile_set_reader (profile, reader);
+  sysprof_profile_generate (profile,
+                            cancellable,
+                            sysprof_memory_page_generate_cb,
+                            g_steal_pointer (&task));
+}
+
+static gboolean
+sysprof_memory_page_load_finish (SysprofPage   *page,
+                                 GAsyncResult  *result,
+                                 GError       **error)
+{
+  g_return_val_if_fail (SYSPROF_IS_MEMORY_PAGE (page), FALSE);
+  g_return_val_if_fail (G_IS_TASK (result), FALSE);
+
+  return g_task_propagate_boolean (G_TASK (result), error);
+}
+
+static void
+sysprof_memory_page_finalize (GObject *object)
+{
+  SysprofMemoryPage *self = (SysprofMemoryPage *)object;
+  SysprofMemoryPagePrivate *priv = sysprof_memory_page_get_instance_private (self);
+
+  g_clear_pointer (&priv->history, g_queue_free);
+  g_clear_object (&priv->profile);
+
+  G_OBJECT_CLASS (sysprof_memory_page_parent_class)->finalize (object);
+}
+
+static void
+sysprof_memory_page_get_property (GObject    *object,
+                                  guint       prop_id,
+                                  GValue     *value,
+                                  GParamSpec *pspec)
+{
+  SysprofMemoryPage *self = SYSPROF_MEMORY_PAGE (object);
+  SysprofMemoryPagePrivate *priv = sysprof_memory_page_get_instance_private (self);
+
+  switch (prop_id)
+    {
+    case PROP_PROFILE:
+      g_value_set_object (value, priv->profile);
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+    }
+}
+
+static void
+sysprof_memory_page_set_property (GObject      *object,
+                                  guint         prop_id,
+                                  const GValue *value,
+                                  GParamSpec   *pspec)
+{
+  SysprofMemoryPage *self = SYSPROF_MEMORY_PAGE (object);
+
+  switch (prop_id)
+    {
+    case PROP_PROFILE:
+      sysprof_memory_page_set_profile (self, g_value_get_object (value));
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+    }
+}
+
+static void
+sysprof_memory_page_class_init (SysprofMemoryPageClass *klass)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+  GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+  SysprofPageClass *page_class = SYSPROF_PAGE_CLASS (klass);
+  GtkBindingSet *bindings;
+
+  object_class->finalize = sysprof_memory_page_finalize;
+  object_class->get_property = sysprof_memory_page_get_property;
+  object_class->set_property = sysprof_memory_page_set_property;
+
+  page_class->load_async = sysprof_memory_page_load_async;
+  page_class->load_finish = sysprof_memory_page_load_finish;
+
+  klass->go_previous = sysprof_memory_page_real_go_previous;
+
+  properties [PROP_PROFILE] =
+    g_param_spec_object ("profile",
+                         "Profile",
+                         "The callgraph profile to view",
+                         SYSPROF_TYPE_MEMORY_PROFILE,
+                         (G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS));
+
+  g_object_class_install_properties (object_class, N_PROPS, properties);
+
+  signals [GO_PREVIOUS] =
+    g_signal_new ("go-previous",
+                  G_TYPE_FROM_CLASS (klass),
+                  G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
+                  G_STRUCT_OFFSET (SysprofMemoryPageClass, go_previous),
+                  NULL, NULL, NULL, G_TYPE_NONE, 0);
+
+  gtk_widget_class_set_template_from_resource (widget_class,
+                                               "/org/gnome/sysprof/ui/sysprof-memory-page.ui");
+
+  gtk_widget_class_bind_template_child_private (widget_class, SysprofMemoryPage, callers_view);
+  gtk_widget_class_bind_template_child_private (widget_class, SysprofMemoryPage, functions_view);
+  gtk_widget_class_bind_template_child_private (widget_class, SysprofMemoryPage, descendants_view);
+  gtk_widget_class_bind_template_child_private (widget_class, SysprofMemoryPage, descendants_name_column);
+  gtk_widget_class_bind_template_child_private (widget_class, SysprofMemoryPage, stack);
+
+  bindings = gtk_binding_set_by_class (klass);
+  gtk_binding_entry_add_signal (bindings, GDK_KEY_Left, GDK_MOD1_MASK, "go-previous", 0);
+
+  g_type_ensure (SYSPROF_TYPE_CELL_RENDERER_PERCENT);
+}
+
+static void
+sysprof_memory_page_init (SysprofMemoryPage *self)
+{
+  SysprofMemoryPagePrivate *priv = sysprof_memory_page_get_instance_private (self);
+  DzlShortcutController *controller;
+  GtkTreeSelection *selection;
+  GtkCellRenderer *cell;
+
+  priv->history = g_queue_new ();
+
+  gtk_widget_init_template (GTK_WIDGET (self));
+
+  gtk_stack_set_visible_child_name (priv->stack, "empty-state");
+
+  selection = gtk_tree_view_get_selection (priv->functions_view);
+
+  g_signal_connect_object (selection,
+                           "changed",
+                           G_CALLBACK (sysprof_memory_page_function_selection_changed),
+                           self,
+                           G_CONNECT_SWAPPED);
+
+  g_signal_connect_object (priv->descendants_view,
+                           "row-activated",
+                           G_CALLBACK (sysprof_memory_page_descendant_activated),
+                           self,
+                           G_CONNECT_SWAPPED);
+
+  g_signal_connect_object (priv->callers_view,
+                           "row-activated",
+                           G_CALLBACK (sysprof_memory_page_caller_activated),
+                           self,
+                           G_CONNECT_SWAPPED);
+
+  g_signal_connect (priv->descendants_view,
+                    "move-cursor",
+                    G_CALLBACK (descendants_view_move_cursor_cb),
+                    NULL);
+
+  cell = g_object_new (GTK_TYPE_CELL_RENDERER_TEXT,
+                       "ellipsize", PANGO_ELLIPSIZE_MIDDLE,
+                       "xalign", 0.0f,
+                       NULL);
+  gtk_tree_view_column_pack_start (priv->descendants_name_column, cell, TRUE);
+  gtk_tree_view_column_add_attribute (priv->descendants_name_column, cell, "text", 0);
+
+  cell = g_object_new (GTK_TYPE_CELL_RENDERER_TEXT,
+                       "foreground", "#666666",
+                       "scale", PANGO_SCALE_SMALL,
+                       "xalign", 1.0f,
+                       NULL);
+  gtk_tree_view_column_pack_start (priv->descendants_name_column, cell, FALSE);
+  gtk_tree_view_column_set_cell_data_func (priv->descendants_name_column, cell,
+                                           sysprof_memory_page_tag_data_func,
+                                           self, NULL);
+
+  gtk_tree_selection_set_mode (gtk_tree_view_get_selection (priv->descendants_view),
+                               GTK_SELECTION_MULTIPLE);
+
+  controller = dzl_shortcut_controller_find (GTK_WIDGET (self));
+
+  dzl_shortcut_controller_add_command_callback (controller,
+                                                "org.gnome.sysprof3.capture.copy",
+                                                "<Control>c",
+                                                DZL_SHORTCUT_PHASE_BUBBLE,
+                                                (GtkCallback) sysprof_memory_page_copy_cb,
+                                                self,
+                                                NULL);
+}
+
+typedef struct _Descendant Descendant;
+
+struct _Descendant
+{
+  const gchar *name;
+  guint        self;
+  guint        cumulative;
+  Descendant  *parent;
+  Descendant  *siblings;
+  Descendant  *children;
+};
+
+static void
+build_tree_cb (StackLink *trace,
+               gint       size,
+               gpointer   user_data)
+{
+  Descendant **tree = user_data;
+  Descendant *parent = NULL;
+  StackLink *link;
+
+  g_assert (trace != NULL);
+  g_assert (tree != NULL);
+
+  /* Get last item */
+  link = trace;
+  while (link->next)
+    link = link->next;
+
+  for (; link != NULL; link = link->prev)
+    {
+      const gchar *address = U64_TO_POINTER (link->data);
+      Descendant *prev = NULL;
+      Descendant *match = NULL;
+
+      for (match = *tree; match != NULL; match = match->siblings)
+        {
+          if (match->name == address)
+            {
+              if (prev != NULL)
+                {
+                  /* Move to front */
+                  prev->siblings = match->siblings;
+                  match->siblings = *tree;
+                  *tree = match;
+                }
+              break;
+            }
+        }
+
+      if (match == NULL)
+        {
+          /* Have we seen this object further up the tree? */
+          for (match = parent; match != NULL; match = match->parent)
+            {
+              if (match->name == address)
+                break;
+            }
+        }
+
+      if (match == NULL)
+        {
+          match = g_slice_new (Descendant);
+          match->name = address;
+          match->cumulative = 0;
+          match->self = 0;
+          match->children = NULL;
+          match->parent = parent;
+          match->siblings = *tree;
+          *tree = match;
+        }
+
+      tree = &match->children;
+      parent = match;
+    }
+
+  parent->self += size;
+
+  for (; parent != NULL; parent = parent->parent)
+    parent->cumulative += size;
+}
+
+static Descendant *
+build_tree (StackNode *node)
+{
+  Descendant *tree = NULL;
+
+  for (; node != NULL; node = node->next)
+    {
+      if (node->toplevel)
+        stack_node_foreach_trace (node, build_tree_cb, &tree);
+    }
+
+  return tree;
+}
+
+static void
+append_to_tree_and_free (SysprofMemoryPage *self,
+                         StackStash           *stash,
+                         GtkTreeStore         *store,
+                         Descendant           *item,
+                         GtkTreeIter          *parent)
+{
+  StackNode *node = NULL;
+  GtkTreeIter iter;
+  guint profile_size;
+
+  g_assert (GTK_IS_TREE_STORE (store));
+  g_assert (item != NULL);
+
+  profile_size = MAX (1, sysprof_memory_page_get_profile_size (self));
+
+  gtk_tree_store_append (store, &iter, parent);
+
+  node = stack_stash_find_node (stash, (gpointer)item->name);
+
+  gtk_tree_store_set (store, &iter,
+                      COLUMN_NAME, item->name,
+                      COLUMN_SELF, item->self * 100.0 / (gdouble)profile_size,
+                      COLUMN_TOTAL, item->cumulative * 100.0 / (gdouble)profile_size,
+                      COLUMN_POINTER, node,
+                      COLUMN_HITS, (guint)item->cumulative,
+                      -1);
+
+  if (item->siblings != NULL)
+    append_to_tree_and_free (self, stash, store, item->siblings, parent);
+
+  if (item->children != NULL)
+    append_to_tree_and_free (self, stash, store, item->children, &iter);
+
+  g_slice_free (Descendant, item);
+}
+
+static void
+sysprof_memory_page_update_descendants (SysprofMemoryPage *self,
+                                        StackNode         *node)
+{
+  SysprofMemoryPagePrivate *priv = sysprof_memory_page_get_instance_private (self);
+  GtkTreeStore *store;
+
+  g_assert (SYSPROF_IS_MEMORY_PAGE (self));
+
+  if (g_queue_peek_head (priv->history) != node)
+    g_queue_push_head (priv->history, node);
+
+  store = gtk_tree_store_new (5,
+                              G_TYPE_STRING,
+                              G_TYPE_DOUBLE,
+                              G_TYPE_DOUBLE,
+                              G_TYPE_POINTER,
+                              G_TYPE_UINT);
+
+  if (priv->profile != NULL)
+  {
+    StackStash *stash;
+
+    stash = sysprof_memory_profile_get_stash (priv->profile);
+    if (stash != NULL)
+      {
+        Descendant *tree;
+
+        tree = build_tree (node);
+        if (tree != NULL)
+          append_to_tree_and_free (self, stash, store, tree, NULL);
+      }
+  }
+
+  gtk_tree_view_set_model (priv->descendants_view, GTK_TREE_MODEL (store));
+  gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE (store),
+                                        COLUMN_TOTAL, GTK_SORT_DESCENDING);
+  sysprof_memory_page_expand_descendants (self);
+
+  g_clear_object (&store);
+}
+
+/**
+ * sysprof_memory_page_screenshot:
+ * @self: A #SysprofMemoryPage.
+ *
+ * This function will generate a text representation of the descendants tree.
+ * This is useful if you want to include various profiling information in a
+ * commit message or email.
+ *
+ * The text generated will match the current row expansion in the tree view.
+ *
+ * Returns: (nullable) (transfer full): A newly allocated string that should be freed
+ *   with g_free().
+ */
+gchar *
+sysprof_memory_page_screenshot (SysprofMemoryPage *self)
+{
+  SysprofMemoryPagePrivate *priv = sysprof_memory_page_get_instance_private (self);
+  GtkTreeView *tree_view;
+  GtkTreeModel *model;
+  GtkTreePath *tree_path;
+  GString *str;
+  GtkTreeIter iter;
+
+  g_return_val_if_fail (SYSPROF_IS_MEMORY_PAGE (self), NULL);
+
+  tree_view = priv->descendants_view;
+
+  if (NULL == (model = gtk_tree_view_get_model (tree_view)))
+    return NULL;
+
+  /*
+   * To avoid having to precalculate the deepest visible row, we
+   * put the timing information at the beginning of the line.
+   */
+
+  str = g_string_new ("      SELF CUMULATIVE    FUNCTION\n");
+  tree_path = gtk_tree_path_new_first ();
+
+  for (;;)
+    {
+      if (gtk_tree_model_get_iter (model, &iter, tree_path))
+        {
+          guint depth = gtk_tree_path_get_depth (tree_path);
+          StackNode *node;
+          gdouble in_self;
+          gdouble total;
+          guint i;
+
+          gtk_tree_model_get (model, &iter,
+                              COLUMN_SELF, &in_self,
+                              COLUMN_TOTAL, &total,
+                              COLUMN_POINTER, &node,
+                              -1);
+
+          g_string_append_printf (str, "[% 7.2lf%%] [% 7.2lf%%]  ", in_self, total);
+
+          for (i = 0; i < depth; i++)
+            g_string_append (str, "  ");
+          g_string_append (str, GSIZE_TO_POINTER (node->data));
+          g_string_append_c (str, '\n');
+
+          if (gtk_tree_view_row_expanded (tree_view, tree_path))
+            gtk_tree_path_down (tree_path);
+          else
+            gtk_tree_path_next (tree_path);
+
+          continue;
+        }
+
+      if (!gtk_tree_path_up (tree_path) || !gtk_tree_path_get_depth (tree_path))
+        break;
+
+      gtk_tree_path_next (tree_path);
+    }
+
+  gtk_tree_path_free (tree_path);
+
+  return g_string_free (str, FALSE);
+}
+
+guint
+sysprof_memory_page_get_n_functions (SysprofMemoryPage *self)
+{
+  SysprofMemoryPagePrivate *priv = sysprof_memory_page_get_instance_private (self);
+  GtkTreeModel *model;
+  guint ret = 0;
+
+  g_return_val_if_fail (SYSPROF_IS_MEMORY_PAGE (self), 0);
+
+  if (NULL != (model = gtk_tree_view_get_model (priv->functions_view)))
+    ret = gtk_tree_model_iter_n_children (model, NULL);
+
+  return ret;
+}
+
+void
+_sysprof_memory_page_set_loading (SysprofMemoryPage *self,
+                                  gboolean           loading)
+{
+  SysprofMemoryPagePrivate *priv = sysprof_memory_page_get_instance_private (self);
+
+  g_return_if_fail (SYSPROF_IS_MEMORY_PAGE (self));
+
+  if (loading)
+    priv->loading++;
+  else
+    priv->loading--;
+
+  if (priv->loading)
+    gtk_stack_set_visible_child_name (priv->stack, "loading");
+  else
+    gtk_stack_set_visible_child_name (priv->stack, "callgraph");
+}
diff --git a/src/libsysprof-ui/sysprof-memory-page.h b/src/libsysprof-ui/sysprof-memory-page.h
new file mode 100644
index 0000000..4eb50ec
--- /dev/null
+++ b/src/libsysprof-ui/sysprof-memory-page.h
@@ -0,0 +1,51 @@
+/* sysprof-memory-page.h
+ *
+ * Copyright 2016-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
+ */
+
+#pragma once
+
+#include <gtk/gtk.h>
+#include <sysprof.h>
+
+#include "sysprof-page.h"
+
+G_BEGIN_DECLS
+
+#define SYSPROF_TYPE_MEMORY_PAGE (sysprof_memory_page_get_type())
+
+G_DECLARE_DERIVABLE_TYPE (SysprofMemoryPage, sysprof_memory_page, SYSPROF, MEMORY_PAGE, SysprofPage)
+
+struct _SysprofMemoryPageClass
+{
+  SysprofPageClass parent_class;
+
+  void (*go_previous) (SysprofMemoryPage *self);
+
+  /*< private >*/
+  gpointer _reserved[16];
+};
+
+GtkWidget            *sysprof_memory_page_new             (void);
+SysprofMemoryProfile *sysprof_memory_page_get_profile     (SysprofMemoryPage    *self);
+void                  sysprof_memory_page_set_profile     (SysprofMemoryPage    *self,
+                                                           SysprofMemoryProfile *profile);
+gchar                *sysprof_memory_page_screenshot      (SysprofMemoryPage    *self);
+guint                 sysprof_memory_page_get_n_functions (SysprofMemoryPage    *self);
+
+G_END_DECLS
diff --git a/src/libsysprof-ui/sysprof-memory-page.ui b/src/libsysprof-ui/sysprof-memory-page.ui
new file mode 100644
index 0000000..51dc089
--- /dev/null
+++ b/src/libsysprof-ui/sysprof-memory-page.ui
@@ -0,0 +1,235 @@
+<interface>
+  <template class="SysprofMemoryPage" parent="SysprofPage">
+    <child>
+      <object class="GtkStack" id="stack">
+        <property name="visible">true</property>
+        <child>
+          <object class="GtkPaned">
+            <property name="orientation">horizontal</property>
+            <property name="position">450</property>
+            <property name="visible">true</property>
+            <child>
+              <object class="GtkPaned">
+                <property name="orientation">vertical</property>
+                <property name="visible">true</property>
+                <child>
+                  <object class="GtkScrolledWindow">
+                    <property name="visible">true</property>
+                    <child>
+                      <object class="GtkTreeView" id="functions_view">
+                        <property name="fixed-height-mode">true</property>
+                        <property name="visible">true</property>
+                        <child>
+                          <object class="GtkTreeViewColumn" id="function_name_column">
+                            <property name="expand">true</property>
+                            <property name="sizing">fixed</property>
+                            <property name="sort-column-id">0</property>
+                            <property name="title" translatable="yes">Functions</property>
+                            <child>
+                              <object class="GtkCellRendererText">
+                                <property name="ellipsize">middle</property>
+                              </object>
+                              <attributes>
+                                <attribute name="text">0</attribute>
+                              </attributes>
+                            </child>
+                          </object>
+                        </child>
+                        <child>
+                          <object class="GtkTreeViewColumn" id="function_self_column">
+                            <property name="expand">false</property>
+                            <property name="sizing">fixed</property>
+                            <property name="sort-column-id">1</property>
+                            <property name="title" translatable="yes">Self</property>
+                            <child>
+                              <object class="SysprofCellRendererPercent">
+                                <property name="width">65</property>
+                              </object>
+                              <attributes>
+                                <attribute name="percent">1</attribute>
+                              </attributes>
+                            </child>
+                          </object>
+                        </child>
+                        <child>
+                          <object class="GtkTreeViewColumn" id="function_total_column">
+                            <property name="expand">false</property>
+                            <property name="sizing">fixed</property>
+                            <property name="sort-column-id">2</property>
+                            <property name="title" translatable="yes">Total</property>
+                            <child>
+                              <object class="SysprofCellRendererPercent">
+                                <property name="width">65</property>
+                              </object>
+                              <attributes>
+                                <attribute name="percent">2</attribute>
+                              </attributes>
+                            </child>
+                          </object>
+                        </child>
+                      </object>
+                    </child>
+                  </object>
+                  <packing>
+                    <property name="resize">true</property>
+                  </packing>
+                </child>
+                <child>
+                  <object class="GtkScrolledWindow">
+                    <property name="visible">true</property>
+                    <child>
+                      <object class="GtkTreeView" id="callers_view">
+                        <property name="visible">true</property>
+                        <child>
+                          <object class="GtkTreeViewColumn" id="callers_name_column">
+                            <property name="expand">true</property>
+                            <property name="sizing">fixed</property>
+                            <property name="sort-column-id">0</property>
+                            <property name="title" translatable="yes">Callers</property>
+                            <child>
+                              <object class="GtkCellRendererText">
+                                <property name="ellipsize">middle</property>
+                              </object>
+                              <attributes>
+                                <attribute name="text">0</attribute>
+                              </attributes>
+                            </child>
+                          </object>
+                        </child>
+                        <child>
+                          <object class="GtkTreeViewColumn" id="callers_self_column">
+                            <property name="expand">false</property>
+                            <property name="sizing">fixed</property>
+                            <property name="sort-column-id">1</property>
+                            <property name="title" translatable="yes">Self</property>
+                            <child>
+                              <object class="SysprofCellRendererPercent">
+                                <property name="width">65</property>
+                              </object>
+                              <attributes>
+                                <attribute name="percent">1</attribute>
+                              </attributes>
+                            </child>
+                          </object>
+                        </child>
+                        <child>
+                          <object class="GtkTreeViewColumn" id="callers_total_column">
+                            <property name="expand">false</property>
+                            <property name="sizing">fixed</property>
+                            <property name="sort-column-id">2</property>
+                            <property name="title" translatable="yes">Total</property>
+                            <child>
+                              <object class="SysprofCellRendererPercent">
+                                <property name="width">65</property>
+                              </object>
+                              <attributes>
+                                <attribute name="percent">2</attribute>
+                              </attributes>
+                            </child>
+                          </object>
+                        </child>
+                      </object>
+                    </child>
+                  </object>
+                  <packing>
+                    <property name="resize">true</property>
+                  </packing>
+                </child>
+              </object>
+            </child>
+            <child>
+              <object class="GtkScrolledWindow">
+                <property name="visible">true</property>
+                <child>
+                  <object class="GtkTreeView" id="descendants_view">
+                    <property name="visible">true</property>
+                    <child>
+                      <object class="GtkTreeViewColumn" id="descendants_name_column">
+                        <property name="expand">true</property>
+                        <property name="sizing">autosize</property>
+                        <property name="sort-column-id">0</property>
+                        <property name="title" translatable="yes">Descendants</property>
+                      </object>
+                    </child>
+                    <child>
+                      <object class="GtkTreeViewColumn" id="descendants_self_column">
+                        <property name="expand">false</property>
+                        <property name="sizing">fixed</property>
+                        <property name="sort-column-id">1</property>
+                        <property name="title" translatable="yes">Self</property>
+                        <child>
+                          <object class="SysprofCellRendererPercent">
+                            <property name="width">65</property>
+                          </object>
+                          <attributes>
+                            <attribute name="percent">1</attribute>
+                          </attributes>
+                        </child>
+                      </object>
+                    </child>
+                    <child>
+                      <object class="GtkTreeViewColumn" id="descendants_total_column">
+                        <property name="expand">false</property>
+                        <property name="sizing">fixed</property>
+                        <property name="sort-column-id">2</property>
+                        <property name="title" translatable="yes">Total</property>
+                        <child>
+                          <object class="SysprofCellRendererPercent">
+                            <property name="width">65</property>
+                          </object>
+                          <attributes>
+                            <attribute name="percent">2</attribute>
+                          </attributes>
+                        </child>
+                      </object>
+                    </child>
+                    <child>
+                      <object class="GtkTreeViewColumn" id="function_hits_column">
+                        <property name="expand">false</property>
+                        <property name="sizing">fixed</property>
+                        <property name="title" translatable="yes">Hits</property>
+                        <child>
+                          <object class="GtkCellRendererText">
+                            <property name="xalign">1.0</property>
+                          </object>
+                          <attributes>
+                            <attribute name="text">4</attribute>
+                          </attributes>
+                        </child>
+                      </object>
+                    </child>
+                  </object>
+                </child>
+              </object>
+            </child>
+          </object>
+          <packing>
+            <property name="name">callgraph</property>
+          </packing>
+        </child>
+        <child>
+          <object class="DzlEmptyState">
+            <property name="icon-name">content-loading-symbolic</property>
+            <property name="title" translatable="yes">Generating Callgraph</property>
+            <property name="subtitle" translatable="yes">Sysprof is busy creating the selected 
callgraph.</property>
+            <property name="visible">true</property>
+          </object>
+          <packing>
+            <property name="name">loading</property>
+          </packing>
+        </child>
+        <child>
+          <object class="DzlEmptyState">
+            <property name="icon-name">computer-fail-symbolic</property>
+            <property name="title" translatable="yes">Not Enough Samples</property>
+            <property name="subtitle" translatable="yes">More samples are necessary to display a 
callgraph.</property>
+            <property name="visible">false</property>
+          </object>
+          <packing>
+            <property name="name">empty-state</property>
+          </packing>
+        </child>
+      </object>
+    </child>
+  </template>
+</interface>
diff --git a/src/libsysprof-ui/sysprof-ui-private.h b/src/libsysprof-ui/sysprof-ui-private.h
index aef6e8a..a0a08fd 100644
--- a/src/libsysprof-ui/sysprof-ui-private.h
+++ b/src/libsysprof-ui/sysprof-ui-private.h
@@ -29,6 +29,9 @@ G_BEGIN_DECLS
 void   _sysprof_callgraph_page_set_failed       (SysprofCallgraphPage     *self);
 void   _sysprof_callgraph_page_set_loading      (SysprofCallgraphPage     *self,
                                                  gboolean                  loading);
+void   _sysprof_memory_page_set_failed          (SysprofCallgraphPage     *self);
+void   _sysprof_memory_page_set_loading         (SysprofCallgraphPage     *self,
+                                                 gboolean                  loading);
 void   _sysprof_display_focus_record            (SysprofDisplay           *self);
 void   _sysprof_profiler_assistant_focus_record (SysprofProfilerAssistant *self);
 gchar *_sysprof_format_duration                 (gint64                    duration);
diff --git a/src/libsysprof/sysprof-memory-profile.c b/src/libsysprof/sysprof-memory-profile.c
index 5caf3df..406752c 100644
--- a/src/libsysprof/sysprof-memory-profile.c
+++ b/src/libsysprof/sysprof-memory-profile.c
@@ -27,17 +27,21 @@
 #include "sysprof-memory-profile.h"
 
 #include "rax.h"
+#include "../stackstash.h"
 
 struct _SysprofMemoryProfile
 {
   GObject               parent_instance;
+  SysprofSelection     *selection;
   SysprofCaptureReader *reader;
-  rax *rax;
+  StackStash           *stash;
+  rax                  *rax;
 };
 
 typedef struct
 {
   SysprofCaptureReader *reader;
+  StackStash *stash;
   rax *rax;
 } Generate;
 
@@ -46,11 +50,20 @@ static void profile_iface_init (SysprofProfileInterface *iface);
 G_DEFINE_TYPE_WITH_CODE (SysprofMemoryProfile, sysprof_memory_profile, G_TYPE_OBJECT,
                          G_IMPLEMENT_INTERFACE (SYSPROF_TYPE_PROFILE, profile_iface_init))
 
+enum {
+  PROP_0,
+  PROP_SELECTION,
+  N_PROPS
+};
+
+static GParamSpec *properties[N_PROPS];
+
 static void
 generate_free (Generate *g)
 {
   g_clear_pointer (&g->reader, sysprof_capture_reader_unref);
   g_clear_pointer (&g->rax, raxFree);
+  g_clear_pointer (&g->stash, stack_stash_unref);
   g_slice_free (Generate, g);
 }
 
@@ -61,16 +74,67 @@ sysprof_memory_profile_finalize (GObject *object)
 
   g_clear_pointer (&self->reader, sysprof_capture_reader_unref);
   g_clear_pointer (&self->rax, raxFree);
+  g_clear_pointer (&self->stash, stack_stash_unref);
+  g_clear_object (&self->selection);
 
   G_OBJECT_CLASS (sysprof_memory_profile_parent_class)->finalize (object);
 }
 
+static void
+sysprof_memory_profile_get_property (GObject    *object,
+                                     guint       prop_id,
+                                     GValue     *value,
+                                     GParamSpec *pspec)
+{
+  SysprofMemoryProfile *self = SYSPROF_MEMORY_PROFILE (object);
+
+  switch (prop_id)
+    {
+    case PROP_SELECTION:
+      g_value_set_object (value, self->selection);
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+    }
+}
+
+static void
+sysprof_memory_profile_set_property (GObject      *object,
+                                     guint         prop_id,
+                                     const GValue *value,
+                                     GParamSpec   *pspec)
+{
+  SysprofMemoryProfile *self = SYSPROF_MEMORY_PROFILE (object);
+
+  switch (prop_id)
+    {
+    case PROP_SELECTION:
+      self->selection = g_value_dup_object (value);
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+    }
+}
+
 static void
 sysprof_memory_profile_class_init (SysprofMemoryProfileClass *klass)
 {
   GObjectClass *object_class = G_OBJECT_CLASS (klass);
 
   object_class->finalize = sysprof_memory_profile_finalize;
+  object_class->get_property = sysprof_memory_profile_get_property;
+  object_class->set_property = sysprof_memory_profile_set_property;
+
+  properties [PROP_SELECTION] =
+    g_param_spec_object ("selection",
+                         "Selection",
+                         "The selection for filtering the callgraph",
+                         SYSPROF_TYPE_SELECTION,
+                         (G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
+
+  g_object_class_install_properties (object_class, N_PROPS, properties);
 }
 
 static void
@@ -136,6 +200,11 @@ cursor_foreach_cb (const SysprofCaptureFrame *frame,
                  sizeof ev->alloc_addr,
                  (gpointer)ev->alloc_size,
                  NULL);
+
+      stack_stash_add_trace (g->stash,
+                             ev->addrs,
+                             ev->n_addrs,
+                             ev->alloc_size);
     }
   else if (frame->type == SYSPROF_CAPTURE_FRAME_MEMORY_FREE)
     {
@@ -199,6 +268,7 @@ sysprof_memory_profile_generate (SysprofProfile      *profile,
   g = g_slice_new0 (Generate);
   g->reader = sysprof_capture_reader_copy (self->reader);
   g->rax = raxNew ();
+  g->stash = stack_stash_new (NULL);
 
   g_task_set_task_data (task, g, (GDestroyNotify) generate_free);
   g_task_run_in_thread (task, sysprof_memory_profile_generate_worker);
@@ -216,7 +286,13 @@ sysprof_memory_profile_generate_finish (SysprofProfile  *profile,
   g_assert (G_IS_TASK (result));
 
   if ((g = g_task_get_task_data (G_TASK (result))))
-    self->rax = g_steal_pointer (&g->rax);
+    {
+      g_clear_pointer (&self->rax, raxFree);
+      g_clear_pointer (&self->stash, stack_stash_unref);
+
+      self->rax = g_steal_pointer (&g->rax);
+      self->stash = g_steal_pointer (&g->stash);
+    }
 
   return g_task_propagate_boolean (G_TASK (result), error);
 }
@@ -234,3 +310,36 @@ sysprof_memory_profile_get_native (SysprofMemoryProfile *self)
 {
   return self->rax;
 }
+
+gpointer
+sysprof_memory_profile_get_stash (SysprofMemoryProfile *self)
+{
+  return self->stash;
+}
+
+gboolean
+sysprof_memory_profile_is_empty (SysprofMemoryProfile *self)
+{
+  StackNode *root;
+
+  g_return_val_if_fail (SYSPROF_IS_MEMORY_PROFILE (self), FALSE);
+
+  return (self->stash == NULL ||
+          !(root = stack_stash_get_root (self->stash)) ||
+          !root->total);
+}
+
+GQuark
+sysprof_memory_profile_get_tag (SysprofMemoryProfile *self,
+                                const gchar          *symbol)
+{
+  return 0;
+}
+
+SysprofProfile *
+sysprof_memory_profile_new_with_selection (SysprofSelection *selection)
+{
+  return g_object_new (SYSPROF_TYPE_MEMORY_PROFILE,
+                       "selection", selection,
+                       NULL);
+}
diff --git a/src/libsysprof/sysprof-memory-profile.h b/src/libsysprof/sysprof-memory-profile.h
index 0f9334f..2ab0767 100644
--- a/src/libsysprof/sysprof-memory-profile.h
+++ b/src/libsysprof/sysprof-memory-profile.h
@@ -27,6 +27,7 @@
 #include "sysprof-version-macros.h"
 
 #include "sysprof-profile.h"
+#include "sysprof-selection.h"
 
 G_BEGIN_DECLS
 
@@ -36,8 +37,17 @@ SYSPROF_AVAILABLE_IN_ALL
 G_DECLARE_FINAL_TYPE (SysprofMemoryProfile, sysprof_memory_profile, SYSPROF, MEMORY_PROFILE, GObject)
 
 SYSPROF_AVAILABLE_IN_3_36
-SysprofProfile *sysprof_memory_profile_new        (void);
+SysprofProfile *sysprof_memory_profile_new                (void);
 SYSPROF_AVAILABLE_IN_3_36
-gpointer        sysprof_memory_profile_get_native (SysprofMemoryProfile *self);
+SysprofProfile *sysprof_memory_profile_new_with_selection (SysprofSelection *selection);
+SYSPROF_AVAILABLE_IN_3_36
+gpointer        sysprof_memory_profile_get_native         (SysprofMemoryProfile *self);
+SYSPROF_AVAILABLE_IN_3_36
+gpointer        sysprof_memory_profile_get_stash          (SysprofMemoryProfile *self);
+SYSPROF_AVAILABLE_IN_3_36
+gboolean        sysprof_memory_profile_is_empty           (SysprofMemoryProfile *self);
+SYSPROF_AVAILABLE_IN_3_36
+GQuark          sysprof_memory_profile_get_tag            (SysprofMemoryProfile *self,
+                                                           const gchar          *symbol);
 
 G_END_DECLS


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