summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorChris Wilson <chris@chris-wilson.co.uk>2007-11-24 20:32:54 +0000
committerChris Wilson <chris@chris-wilson.co.uk>2007-11-24 20:32:54 +0000
commitccb52888d2c9a8584717767353c06f9a59a0109a (patch)
treed6d959d68f652ee84ff642a8b464f609582ecf43 /src
parent8b043b39b92ff29aebcf596ef6508d76097046ad (diff)
Move source files into src/
Diffstat (limited to 'src')
-rw-r--r--src/.gitignore33
-rw-r--r--src/allocators.c726
-rw-r--r--src/app.c878
-rw-r--r--src/blockmap.c681
-rw-r--r--src/callgraph-store.c714
-rw-r--r--src/callgraph-treemap.c954
-rw-r--r--src/callgraph.c544
-rw-r--r--src/callgraph.h61
-rw-r--r--src/frames.c230
-rw-r--r--src/memfault.h212
-rw-r--r--src/timeline.c397
-rw-r--r--src/utils.c21
12 files changed, 5451 insertions, 0 deletions
diff --git a/src/.gitignore b/src/.gitignore
new file mode 100644
index 0000000..b4c252e
--- /dev/null
+++ b/src/.gitignore
@@ -0,0 +1,33 @@
+.deps
+.libs
+memfault
+ChangeLog*
+Makefile
+Makefile.in
+aclocal.m4
+autom4te.cache
+compile
+config.cache
+config.guess
+config.h
+config.h.in
+config.log
+config.status
+config.sub
+configure
+depcomp
+install-sh
+libtool
+ltmain.sh
+missing
+releases
+stamp-h
+stamp-h1
+stamp-h.in
+*~
+.*.sw?
+*.la
+*.lo
+*.orig
+*.rej
+*.o
diff --git a/src/allocators.c b/src/allocators.c
new file mode 100644
index 0000000..5323465
--- /dev/null
+++ b/src/allocators.c
@@ -0,0 +1,726 @@
+#include <gtk/gtk.h>
+#include <math.h>
+#include <unistd.h>
+#include <string.h>
+#include <stdlib.h>
+
+#include "memfault.h"
+
+#define _(x) x
+
+struct _sum_allocator {
+ guint index;
+ const gchar *frame;
+ guint64 size;
+ guint count;
+ guint n_pages;
+ guint min, max;
+ Block **blocks;
+ struct _sum_allocator *next;
+};
+
+typedef struct _allocators_store {
+ GObject object;
+
+ GPtrArray *allocators;
+} AllocatorsStore;
+
+typedef struct _allocators_store_class {
+ GObjectClass parent_class;
+} AllocatorsStoreClass;
+
+
+struct _allocators {
+ GtkTreeView tv;
+
+ Block *blocks;
+ Block **blocks_mem;
+ AllocatorsStore *store;
+ GHashTable *allocators;
+
+ guint reload;
+};
+typedef struct _allocators_class {
+ GtkTreeViewClass parent_class;
+} AllocatorsClass;
+
+G_DEFINE_TYPE (Allocators, allocators, GTK_TYPE_TREE_VIEW)
+
+static void
+allocators_store_tree_model_init (GtkTreeModelIface *iface);
+
+G_DEFINE_TYPE_WITH_CODE (AllocatorsStore, allocators_store, G_TYPE_OBJECT,
+ G_IMPLEMENT_INTERFACE (GTK_TYPE_TREE_MODEL,
+ allocators_store_tree_model_init))
+enum {
+ FRAME,
+ COUNT,
+ NPAGES,
+ SIZE,
+ N_COLUMNS,
+};
+
+enum {
+ PROP_0 = 0,
+ PROP_BLOCKS,
+};
+
+static gint
+block_cmp (gconstpointer A, gconstpointer B)
+{
+ const Block * const *aa = A, * const *bb = B;
+ const Block *a = *aa, *b = *bb;
+ const Allocator *Aa = a->allocator, *Ab = b->allocator;
+ gint cmp;
+
+ cmp = Ab->time_tail->n_allocs - Aa->time_tail->n_allocs;
+ if (cmp)
+ return cmp;
+
+ cmp = b->size - a->size;
+ if (cmp)
+ return cmp;
+
+ return a->addr - b->addr;
+}
+
+static void
+_sum_allocator_destroy (gpointer arg)
+{
+ struct _sum_allocator *sum = arg;
+ g_slice_free (struct _sum_allocator, sum);
+}
+
+static gint
+_sum_allocators_cmp (gconstpointer A, gconstpointer B)
+{
+ const struct _sum_allocator * const *aa = A, * const *bb = B;
+ const struct _sum_allocator *a = *aa, *b = *bb;
+ gint cmp;
+
+ if (b->size == 0)
+ return 1;
+ if (a->size == 0)
+ return -1;
+
+ cmp = b->n_pages * 4096 / b->size * b->count - a->n_pages * 4096 / a->size * a->count;
+ if (cmp)
+ return cmp;
+
+ cmp = b->count - a->count;
+ if (cmp)
+ return cmp;
+
+ cmp = b->n_pages - a->n_pages;
+ if (cmp)
+ return cmp;
+
+ return b->size - a->size;
+}
+
+static void
+allocators_store_finalize (GObject *obj)
+{
+ AllocatorsStore *self = (AllocatorsStore *) obj;
+
+ g_ptr_array_foreach (self->allocators,
+ (GFunc) _sum_allocator_destroy, NULL);
+ g_ptr_array_free (self->allocators, TRUE);
+
+ G_OBJECT_CLASS (allocators_store_parent_class)->finalize (obj);
+}
+
+static void
+allocators_store_class_init (AllocatorsStoreClass *klass)
+{
+ GObjectClass *object_class = (GObjectClass *) klass;
+
+ object_class->finalize = allocators_store_finalize;
+}
+
+
+/* GtkTreeModelIface */
+
+static GtkTreeModelFlags
+allocators_store_get_flags (GtkTreeModel *tree_model)
+{
+ return GTK_TREE_MODEL_ITERS_PERSIST | GTK_TREE_MODEL_LIST_ONLY;
+}
+
+static gint
+allocators_store_get_n_columns (GtkTreeModel *tree_model)
+{
+ return N_COLUMNS;
+}
+
+static GType
+allocators_store_get_column_type (GtkTreeModel *tree_model,
+ gint index)
+{
+ switch (index) {
+ case FRAME: return G_TYPE_STRING;
+ case SIZE: return G_TYPE_UINT64;
+ case COUNT: return G_TYPE_UINT;
+ case NPAGES: return G_TYPE_UINT;
+ default: return G_TYPE_INVALID;
+ }
+}
+
+static gboolean
+allocators_store_get_iter (GtkTreeModel *tree_model,
+ GtkTreeIter *iter,
+ GtkTreePath *path)
+{
+ AllocatorsStore *self = (AllocatorsStore *) tree_model;
+ guint n;
+
+ n = gtk_tree_path_get_depth (path);
+ g_return_val_if_fail (n == 1, FALSE);
+
+ n = gtk_tree_path_get_indices (path)[0];
+ if (n >= self->allocators->len)
+ return FALSE;
+
+ iter->user_data = g_ptr_array_index (self->allocators, n);
+ iter->stamp = 1;
+ return TRUE;
+}
+
+static GtkTreePath *
+allocators_store_get_path (GtkTreeModel *tree_model,
+ GtkTreeIter *iter)
+{
+ GtkTreePath *path = gtk_tree_path_new ();
+ struct _sum_allocator *sum = iter->user_data;
+ gtk_tree_path_append_index (path, sum->index);
+ return path;
+}
+
+static void
+allocators_store_get_value (GtkTreeModel *tree_model,
+ GtkTreeIter *iter,
+ gint column,
+ GValue *value)
+{
+ struct _sum_allocator *sum = iter->user_data;
+ switch (column) {
+ case FRAME:
+ g_value_init (value, G_TYPE_STRING);
+ g_value_set_string (value, sum->frame);
+ break;
+
+ case SIZE:
+ g_value_init (value, G_TYPE_UINT64);
+ g_value_set_uint64 (value, sum->size);
+ break;
+
+ case COUNT:
+ g_value_init (value, G_TYPE_UINT);
+ g_value_set_uint (value, sum->count);
+ break;
+
+ case NPAGES:
+ g_value_init (value, G_TYPE_UINT);
+ g_value_set_uint (value, sum->n_pages);
+ break;
+ }
+}
+
+static gboolean
+allocators_store_iter_next (GtkTreeModel *tree_model,
+ GtkTreeIter *iter)
+{
+ AllocatorsStore *self = (AllocatorsStore *) tree_model;
+ struct _sum_allocator *sum = iter->user_data;
+ GPtrArray *array = self->allocators;
+ guint index = sum->index;
+
+ if (index >= array->len - 1)
+ return FALSE;
+
+ iter->user_data = g_ptr_array_index (array, index + 1);
+ return TRUE;
+}
+
+static gboolean
+allocators_store_iter_children (GtkTreeModel *tree_model,
+ GtkTreeIter *iter,
+ GtkTreeIter *parent)
+{
+ return parent == NULL;
+}
+
+static gboolean
+allocators_store_iter_has_child (GtkTreeModel *tree_model,
+ GtkTreeIter *iter)
+{
+ return FALSE;
+}
+
+static gint
+allocators_store_iter_n_children (GtkTreeModel *tree_model,
+ GtkTreeIter *iter)
+{
+ return 0;
+}
+
+static gboolean
+allocators_store_iter_nth_child (GtkTreeModel *tree_model,
+ GtkTreeIter *iter,
+ GtkTreeIter *parent,
+ gint n)
+{
+ AllocatorsStore *self = (AllocatorsStore *) tree_model;
+ GPtrArray *array;
+
+ if (parent != NULL)
+ return FALSE;
+
+ array = self->allocators;
+ if ((guint) n >= array->len)
+ return FALSE;
+
+ iter->stamp = 1;
+ iter->user_data = g_ptr_array_index (array, n);
+ return TRUE;
+}
+
+static gboolean
+allocators_store_iter_parent (GtkTreeModel *tree_model,
+ GtkTreeIter *iter,
+ GtkTreeIter *child)
+{
+ return FALSE;
+}
+
+static void
+allocators_store_tree_model_init (GtkTreeModelIface *iface)
+{
+ iface->get_flags = allocators_store_get_flags;
+ iface->get_n_columns = allocators_store_get_n_columns;
+ iface->get_column_type = allocators_store_get_column_type;
+ iface->get_iter = allocators_store_get_iter;
+ iface->get_path = allocators_store_get_path;
+ iface->get_value = allocators_store_get_value;
+ iface->iter_next = allocators_store_iter_next;
+ iface->iter_children = allocators_store_iter_children;
+ iface->iter_has_child = allocators_store_iter_has_child;
+ iface->iter_n_children = allocators_store_iter_n_children;
+ iface->iter_nth_child = allocators_store_iter_nth_child;
+ iface->iter_parent = allocators_store_iter_parent;
+}
+
+static void
+allocators_store_init (AllocatorsStore *self)
+{
+ self->allocators = g_ptr_array_new ();
+}
+
+static void
+_sum_allocators_add_to_array (gpointer key, gpointer value, gpointer data)
+{
+ struct _sum_allocator *sum = value;
+ GPtrArray *array = data;
+ guint pagesize = sysconf (_SC_PAGESIZE);
+ guint pages_embedded[1024], *pages;
+ guint n = (sum->max - sum->min + 32) / 32;
+
+ if (n > G_N_ELEMENTS (pages_embedded))
+ pages = g_new0 (guint, n);
+ else
+ pages = pages_embedded;
+
+ for (n = 0; n < sum->count; n++) {
+ Block *block = sum->blocks[n];
+ guint p = (block->addr - sum->min) / pagesize;
+ guint last_page = (block->addr + block->size - sum->min) / pagesize;
+ do {
+ if ((pages[p / 32] & (1 << (p & 31 ))) == 0) {
+ pages [p / 32] |= 1 << (p & 31);
+ sum->n_pages++;
+ }
+ } while (++p < last_page);
+ }
+ if (pages != pages_embedded)
+ g_free (pages);
+
+ qsort (sum->blocks, sum->count, sizeof (Block *), block_cmp);
+
+ g_ptr_array_add (array, sum);
+}
+
+static AllocatorsStore *
+_sum_allocators_ht_to_store (GHashTable *allocators)
+{
+ AllocatorsStore *store = g_object_new (allocators_store_get_type (), NULL);
+ guint n;
+
+ g_hash_table_foreach (allocators,
+ _sum_allocators_add_to_array, store->allocators);
+ g_ptr_array_sort (store->allocators, _sum_allocators_cmp);
+ for (n = 0; n < store->allocators->len; n++) {
+ struct _sum_allocator *sum = g_ptr_array_index (store->allocators, n);
+ sum->index = n;
+ }
+
+ return store;
+}
+
+static void
+allocators_set_blocks (Allocators *self, Block *blocks)
+{
+ Block *b, **block_mem;
+ GHashTable *allocators;
+ struct _sum_allocator *sum, *sums = NULL;
+ guint count;
+
+ if (self->store != NULL)
+ g_object_unref (self->store);
+ if (self->allocators)
+ g_hash_table_destroy (self->allocators);
+
+ self->blocks = blocks;
+
+ count = 0;
+ allocators = g_hash_table_new (g_str_hash, g_str_equal);
+ for (b = blocks; b != NULL; b = b->next) {
+ Allocator *A = b->allocator;
+ sum = g_hash_table_lookup (allocators, A->alloc_fn);
+ if (sum == NULL) {
+ sum = g_slice_new (struct _sum_allocator);
+ sum->size = 0;
+ sum->count = 0;
+ sum->n_pages = 0;
+ sum->min = (guint) -1;
+ sum->max = 0;
+ sum->next = sums;
+ sums = sum;
+
+ sum->frame = A->alloc_fn;
+ g_hash_table_insert (allocators, (gpointer) sum->frame, sum);
+ }
+
+ if (b->addr < sum->min)
+ sum->min = b->addr;
+ if (b->addr + b->size > sum->max)
+ sum->max = b->addr + b->size;
+
+ sum->size += b->size;
+ sum->count++;
+ count++;
+ }
+
+ self->blocks_mem = g_renew (Block *, self->blocks_mem, count);
+ block_mem = self->blocks_mem;
+ for (sum = sums; sum != NULL; sum = sum->next) {
+ sum->blocks = block_mem;
+ block_mem += sum->count;
+ sum->count = 0;
+ }
+
+ for (b = blocks; b != NULL; b = b->next) {
+ Allocator *A = b->allocator;
+ sum = g_hash_table_lookup (allocators, A->alloc_fn);
+ sum->blocks[sum->count++] = b;
+ }
+
+ self->store = _sum_allocators_ht_to_store (allocators);
+ self->allocators = allocators;
+
+ gtk_tree_view_set_model (&self->tv, GTK_TREE_MODEL (self->store));
+}
+
+
+static void
+allocators_set_property (GObject *obj, guint id, const GValue *v, GParamSpec *spec)
+{
+ Allocators *self = (Allocators *) obj;
+ switch (id) {
+ case PROP_BLOCKS:
+ allocators_set_blocks (self, g_value_get_pointer (v));
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (obj, id, spec);
+ break;
+ }
+}
+
+static void
+allocators_get_property (GObject *obj, guint id, GValue *v, GParamSpec *spec)
+{
+ Allocators *self = (Allocators *) obj;
+ switch (id) {
+ case PROP_BLOCKS:
+ g_value_set_pointer (v, self->blocks);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (obj, id, spec);
+ break;
+ }
+}
+
+static gboolean
+allocators_query_tooltip (GtkWidget *widget,
+ gint x,
+ gint y,
+ gboolean keyboard_tip,
+ GtkTooltip *tooltip)
+{
+ Allocators *self = (Allocators *) widget;
+ struct _sum_allocator *sum;
+ Allocator *A;
+ Block *block;
+ GtkTreePath *path;
+ GtkTreeViewColumn *column;
+ GtkTreeIter iter;
+ gint cell_x, cell_y;
+ GString *string;
+ gchar *text;
+ guint n, m;
+
+ gtk_tree_view_convert_widget_to_bin_window_coords (&self->tv, x, y, &x, &y);
+ if (! gtk_tree_view_get_path_at_pos (&self->tv, x, y,
+ &path, &column, &cell_x, &cell_y))
+ return FALSE;
+
+ gtk_tree_view_set_tooltip_row (&self->tv, tooltip, path);
+
+ gtk_tree_model_get_iter ((GtkTreeModel *) self->store, &iter, path);
+ gtk_tree_path_free (path);
+
+ sum = iter.user_data;
+ block = sum->blocks[0];
+ A = block->allocator;
+ string = g_string_new ("Top allocation callsite:");
+ for (n = 0; n < A->n_frames; n++)
+ if (A->alloc_fn == A->functions[n])
+ break;
+ for (m = n; m < MIN (n + 8, A->n_frames); m++) {
+ if (strcmp (A->functions_srcloc[m], A->functions_srcloc[m-1])) {
+ g_string_append_c (string, '\n');
+ g_string_append_c (string, '\t');
+ g_string_append (string, A->functions_srcloc[m]);
+ } else
+ n++;
+ }
+ text = g_string_free (string, FALSE);
+ gtk_tooltip_set_text (tooltip, text);
+ g_free (text);
+ return TRUE;
+}
+
+static gboolean
+reload_blocks (gpointer data)
+{
+ Allocators *self = data;
+ GtkWidget *toplevel = gtk_widget_get_toplevel (data);
+
+ if (toplevel->window != NULL) {
+ GdkCursor *cursor = gdk_cursor_new (GDK_WATCH);
+ gdk_window_set_cursor (toplevel->window, cursor);
+ gdk_cursor_unref (cursor);
+ gdk_flush ();
+ }
+
+ allocators_set_blocks (self, self->blocks);
+
+ if (toplevel->window != NULL)
+ gdk_window_set_cursor (toplevel->window, NULL);
+
+ self->reload = 0;
+ return FALSE;
+}
+
+static gboolean
+allocators_button_press (GtkWidget *widget, GdkEventButton *ev)
+{
+ Allocators *self = (Allocators *) widget;
+ if (ev->button == 3) {
+ GtkTreePath *path;
+ GtkTreeViewColumn *column;
+ GtkTreeIter iter;
+ gint cell_x, cell_y;
+
+ if (gtk_tree_view_get_path_at_pos (&self->tv, ev->x, ev->y,
+ &path, &column, &cell_x, &cell_y)) {
+ struct _sum_allocator *sum;
+ GtkWidget *dialog, *entry;
+
+ gtk_tree_model_get_iter ((GtkTreeModel *) self->store, &iter, path);
+ gtk_tree_path_free (path);
+
+ sum = iter.user_data;
+ dialog = gtk_dialog_new_with_buttons (
+ "Add an allocation function",
+ GTK_WINDOW (gtk_widget_get_toplevel (widget)),
+ GTK_DIALOG_DESTROY_WITH_PARENT |
+ GTK_DIALOG_NO_SEPARATOR,
+ GTK_STOCK_OK, GTK_RESPONSE_ACCEPT,
+ GTK_STOCK_CANCEL, GTK_RESPONSE_REJECT,
+ NULL);
+ entry = gtk_entry_new ();
+
+ gtk_entry_set_activates_default (GTK_ENTRY (entry), TRUE);
+ gtk_dialog_set_default_response (GTK_DIALOG (dialog), GTK_RESPONSE_ACCEPT);
+
+ gtk_entry_set_text (GTK_ENTRY (entry), sum->frame);
+ gtk_container_add (
+ GTK_CONTAINER (GTK_DIALOG (dialog)->vbox),
+ entry);
+ gtk_widget_show (entry);
+
+ if (gtk_dialog_run (GTK_DIALOG (dialog)) == GTK_RESPONSE_ACCEPT) {
+ const char *pattern = gtk_entry_get_text (GTK_ENTRY (entry));
+ GError *error = NULL;
+ if (! app_add_alloc_fn (app_get (widget), pattern, &error)) {
+ GtkWidget *msg = gtk_message_dialog_new (
+ GTK_WINDOW (gtk_widget_get_toplevel (widget)),
+ GTK_DIALOG_DESTROY_WITH_PARENT |
+ GTK_DIALOG_NO_SEPARATOR,
+ GTK_MESSAGE_ERROR,
+ GTK_BUTTONS_OK,
+ "Failed to compile '%s' into a regex: %s",
+ pattern, error->message);
+ g_error_free (error);
+ gtk_dialog_run (GTK_DIALOG (msg));
+ gtk_widget_destroy (msg);
+ } else if (self->reload == 0)
+ self->reload = gdk_threads_add_idle (reload_blocks, self);
+ }
+ gtk_widget_destroy (dialog);
+ }
+
+ return TRUE;
+ }
+
+ if (GTK_WIDGET_CLASS (allocators_parent_class)->button_press_event)
+ return GTK_WIDGET_CLASS (allocators_parent_class)->button_press_event (widget, ev);
+
+ return FALSE;
+}
+
+static void
+allocators_class_init (AllocatorsClass *klass)
+{
+ GObjectClass *object_class = (GObjectClass *) klass;
+ GtkWidgetClass *widget_class = (GtkWidgetClass *) klass;
+
+ object_class->set_property = allocators_set_property;
+ object_class->get_property = allocators_get_property;
+
+ widget_class->query_tooltip = allocators_query_tooltip;
+ widget_class->button_press_event = allocators_button_press;
+
+ g_object_class_install_property (object_class,
+ PROP_BLOCKS,
+ g_param_spec_pointer ("blocks",
+ _("blocks"),
+ _("Allocated blocks"),
+ G_PARAM_STATIC_NICK |
+ G_PARAM_STATIC_BLURB |
+ G_PARAM_STATIC_NAME |
+ G_PARAM_READWRITE));
+}
+
+static void
+allocators_init (Allocators *self)
+{
+ GtkTreeStore *store;
+ GtkTreeViewColumn *column;
+ GtkCellRenderer *renderer;
+
+ renderer = gtk_cell_renderer_text_new ();
+ g_object_set (G_OBJECT (renderer),
+ "ellipsize", PANGO_ELLIPSIZE_END,
+ "ellipsize-set", TRUE,
+ "width-chars", 50,
+ NULL);
+ column = gtk_tree_view_column_new_with_attributes ("Allocator",
+ renderer, "text", FRAME, NULL);
+ gtk_tree_view_append_column (&self->tv, column);
+
+ renderer = gtk_cell_renderer_text_new ();
+ column = gtk_tree_view_column_new_with_attributes ("Count",
+ renderer, "text", COUNT, NULL);
+ gtk_tree_view_append_column (&self->tv, column);
+
+ renderer = gtk_cell_renderer_text_new ();
+ column = gtk_tree_view_column_new_with_attributes ("Size",
+ renderer, "text", SIZE, NULL);
+ gtk_tree_view_append_column (&self->tv, column);
+
+ renderer = gtk_cell_renderer_text_new ();
+ column = gtk_tree_view_column_new_with_attributes ("Pages",
+ renderer, "text", NPAGES, NULL);
+ gtk_tree_view_append_column (&self->tv, column);
+
+ gtk_tree_view_set_grid_lines (&self->tv, GTK_TREE_VIEW_GRID_LINES_VERTICAL);
+
+ gtk_tree_selection_set_mode (gtk_tree_view_get_selection (&self->tv),
+ GTK_SELECTION_MULTIPLE);
+
+ gtk_widget_set_has_tooltip (GTK_WIDGET (self), TRUE);
+
+ /* insert a dummy model */
+ store = gtk_tree_store_new (N_COLUMNS,
+ G_TYPE_STRING, G_TYPE_UINT, G_TYPE_UINT64, G_TYPE_UINT);
+ gtk_tree_view_set_model (&self->tv, GTK_TREE_MODEL (store));
+ g_object_unref (store);
+}
+
+GtkWidget *
+allocators_new (void)
+{
+ return g_object_new (allocators_get_type (), NULL);
+}
+
+GSList *
+allocators_get_blocks_for_iter (Allocators *self,
+ GtkTreeIter *iter,
+ GSList *blocks)
+{
+ struct _sum_allocator *sum = iter->user_data;
+ guint n;
+ for (n = 0; n < sum->count; n++)
+ blocks = g_slist_prepend (blocks, sum->blocks[n]);
+
+ return blocks;
+}
+
+void
+allocators_select_blocks (Allocators *self, GSList *blocks)
+{
+ GtkTreeModel *model = (GtkTreeModel *) self->store;
+ GtkTreeSelection *selection = gtk_tree_view_get_selection (&self->tv);
+ GtkTreePath *first = NULL;
+
+ gtk_tree_selection_unselect_all (selection);
+
+ if (blocks == NULL)
+ return;
+
+ do {
+ Block *b = blocks->data;
+ GtkTreeIter iter;
+ GtkTreePath *path;
+
+ iter.user_data = g_hash_table_lookup (self->allocators,
+ b->allocator->alloc_fn);
+ iter.stamp = 1;
+ path = gtk_tree_model_get_path (model, &iter);
+ gtk_tree_view_expand_to_path (&self->tv, path);
+
+ if (first == NULL) {
+ first = path;
+ } else if (gtk_tree_path_compare (path, first) < 0){
+ gtk_tree_path_free (first);
+ first = path;
+ } else
+ gtk_tree_path_free (path);
+
+ gtk_tree_selection_select_iter (selection, &iter);
+ } while ((blocks = blocks->next) != NULL);
+
+ gtk_tree_view_scroll_to_cell (&self->tv, first, NULL, FALSE, 0., 0.);
+ gtk_tree_path_free (first);
+}
diff --git a/src/app.c b/src/app.c
new file mode 100644
index 0000000..4834fbb
--- /dev/null
+++ b/src/app.c
@@ -0,0 +1,878 @@
+#include <gtk/gtk.h>
+#include <gnet.h>
+
+#include <string.h>
+#include <unistd.h>
+#include <errno.h>
+
+#include "memfault.h"
+#include "callgraph.h"
+
+struct _app {
+ GtkWidget *window;
+ struct {
+ GtkWidget *allocators;
+ GtkWidget *block_map;
+ } allocation_image;
+ struct {
+ GtkWidget *call_graph;
+ GtkWidget *tree_map;
+ } allocations;
+ GtkWidget *refresh_button;
+ GtkWidget *timeline;
+ GtkWidget *statusbar;
+
+ Allocator *allocators;
+ GHashTable *allocators_by_addr;
+
+ Block *blocks;
+ Frames *frames;
+
+ CallGraphStore *call_graph;
+
+ GArray *events;
+
+ GTcpSocket *tcp;
+};
+
+static GQuark app_quark;
+
+static gboolean
+readn (int fd, gpointer data, gsize len)
+{
+ while (len) {
+ int ret = read (fd, data, len);
+ if (ret == 0)
+ return FALSE;
+ if (ret == -1) {
+ switch (errno) {
+ case EINTR:
+ case EAGAIN:
+ continue;
+ default:
+ return FALSE;
+ }
+ }
+ len -= ret;
+ data = (char *) data + ret;
+ }
+
+ return TRUE;
+}
+
+static gboolean
+discardn (int fd, guint len)
+{
+ char buf[1024];
+ do {
+ guint n = len > G_N_ELEMENTS (buf) ? G_N_ELEMENTS (buf) : len;
+ if (! readn (fd, buf, n))
+ return FALSE;
+ len -= n;
+ } while (len);
+
+ return TRUE;
+}
+
+static gboolean
+discard_string (int fd)
+{
+ gint len;
+
+ if (! readn (fd, &len, sizeof (len)))
+ return FALSE;
+
+ return discardn (fd, len);
+}
+
+static gboolean
+read_string (int fd, gchar **bp, guint *rem, gchar **str)
+{
+ guint len;
+
+ if (! readn (fd, &len, sizeof (len)))
+ return FALSE;
+
+ if (len) {
+ if (len >= *rem) {
+ *str = g_malloc (len + 1);
+ } else {
+ *str = *bp;
+ *bp += len + 1;
+ *rem -= len + 1;
+ }
+ if (! readn (fd, *str, len)) {
+ g_free (*str);
+ return FALSE;
+ }
+ (*str)[len] = '\0';
+ } else
+ *str = NULL;
+
+ return TRUE;
+}
+
+static Allocator *
+read_allocator (App *app, int fd, guint time)
+{
+ guint key;
+ Allocator *A;
+ AllocatorTime *At;
+ guint magic;
+ guint tid;
+ guint n;
+
+ if (! readn (fd, &key, sizeof (key)))
+ return NULL;
+
+ if (! readn (fd, &magic, sizeof (magic)) || magic != 0xdeadbeef)
+ return NULL;
+
+ A = g_hash_table_lookup (app->allocators_by_addr, GUINT_TO_POINTER (key));
+ if (A == NULL) {
+ A = g_slice_new (Allocator);
+ At = &A->time[0];
+
+ if (! readn (fd, &A->n_frames, sizeof (A->n_frames))) {
+ g_slice_free (Allocator, A);
+ return NULL;
+ }
+
+ A->ips = g_malloc (sizeof (guint) * A->n_frames +
+ sizeof (const gchar *) * 2 * (A->n_frames + 1));
+ A->functions = (const gchar **) (A->ips + A->n_frames);
+ A->functions_srcloc = A->functions + A->n_frames + 1;
+
+ if (! readn (fd, A->ips, A->n_frames * sizeof (guint))) {
+ g_free (A->ips);
+ g_slice_free (Allocator, A);
+ }
+
+ for (n = 0; n < A->n_frames; n++) {
+ A->functions[n] = frames_get_function (app->frames, A->ips[n]);
+ A->functions_srcloc[n] = frames_get_function_with_srcloc (app->frames, A->ips[n]);
+ }
+ A->functions[n] = NULL;
+ A->functions_srcloc[n] = NULL;
+
+ g_hash_table_insert (app->allocators_by_addr, GUINT_TO_POINTER (key), A);
+ A->next = app->allocators;
+ app->allocators = A;
+ } else {
+ guint n_frames;
+
+ g_assert (A->time_tail->time < time);
+
+ At = g_slice_new (AllocatorTime);
+ A->time_tail->next = At;
+
+ if (! readn (fd, &n_frames, sizeof (n_frames)) ||
+ ! discardn (fd, A->n_frames * sizeof (guint)))
+ {
+ g_slice_free (AllocatorTime, At);
+ return NULL;
+ }
+
+ g_assert (n_frames == A->n_frames);
+ }
+ A->time_tail = At;
+ At->time = time;
+ At->next = NULL;
+
+ if (! readn (fd, &At->bytes, sizeof (At->bytes)) ||
+ ! readn (fd, &At->freed, sizeof (At->freed)) ||
+ ! readn (fd, &At->max_size, sizeof (At->max_size)) ||
+ ! readn (fd, &At->n_allocs, sizeof (At->n_allocs)) ||
+ ! readn (fd, &At->n_reallocs, sizeof (At->n_reallocs)) ||
+ ! readn (fd, &At->n_frees, sizeof (At->n_frees)) ||
+ ! readn (fd, &At->n_realloced_blocks, sizeof (At->n_realloced_blocks)) ||
+ ! readn (fd, &At->peak_blocks, sizeof (At->peak_blocks)) ||
+ ! readn (fd, &At->peak_bytes, sizeof (At->peak_bytes)) ||
+ ! readn (fd, &At->size_allocs, sizeof (At->size_allocs)) ||
+ ! readn (fd, &At->faults.tid, sizeof (At->faults.tid)))
+ {
+ return NULL;
+ }
+
+ At->faults.next = NULL;
+ if (At->faults.tid != 0) {
+ ThreadFaults *f = &At->faults;
+ f->tid = tid;
+ if (! readn (fd, &f->fault_cnt, sizeof (f->fault_cnt)) ||
+ ! readn (fd, &f->n_faults, sizeof (f->n_faults)))
+ {
+ return NULL;
+ }
+
+ do {
+ if (! readn (fd, &tid, sizeof (tid)))
+ return NULL;
+ if (tid == 0)
+ break;
+
+ f->next = g_slice_new (ThreadFaults);
+ f = f->next;
+ f->tid = tid;
+
+ if (! readn (fd, &f->fault_cnt, sizeof (f->fault_cnt)) ||
+ ! readn (fd, &f->n_faults, sizeof (f->n_faults)))
+ {
+ return NULL;
+ }
+ } while (TRUE);
+ }
+
+ return A;
+}
+
+static Block *
+read_block (int fd, GHashTable *allocators, Block *blocks)
+{
+ Block *b;
+ guint addr;
+ gsize size;
+ guint allocator;
+ Allocator *A;
+ guint age;
+
+ if (! readn (fd, &addr, sizeof (addr)) ||
+ ! readn (fd, &size, sizeof (size)) ||
+ ! readn (fd, &age, sizeof (age)) ||
+ ! readn (fd, &allocator, sizeof (allocator)))
+ {
+ return NULL;
+ }
+
+ A = g_hash_table_lookup (allocators, GUINT_TO_POINTER (allocator));
+ g_return_val_if_fail (A != NULL, FALSE);
+
+ b = g_slice_new (Block);
+ b->next = blocks;
+ blocks = b;
+ b->addr = addr;
+ b->size = size;
+ b->age = age;
+ b->allocator = A;
+
+ return blocks;
+}
+
+static void
+app_set_client_name (App *app, const gchar *client)
+{
+ char *str = g_strdup_printf ("%s - %s", client, "memfault");
+ gtk_window_set_title (GTK_WINDOW (app->window), str);
+ g_free (str);
+}
+
+static void
+app_set_status (App *app, const gchar *client, guint time, guint n_allocs, guint n_blocks, guint n_frames)
+{
+ char *txt = g_strdup_printf ("%s: %.1fs, %u allocations, %u active blocks, %u frames.",
+ client, time / 1000., n_allocs, n_blocks, n_frames);
+ gtk_statusbar_push (GTK_STATUSBAR (app->statusbar), 0, txt);
+ g_free (txt);
+}
+
+static void
+app_set_blocks (App *app, Block *blocks)
+{
+ Block *b = app->blocks;
+
+ app->blocks = blocks;
+
+ g_object_set (app->allocation_image.allocators, "blocks", blocks, NULL);
+ g_object_set (app->allocation_image.block_map, "blocks", blocks, NULL);
+
+ while (b != NULL) {
+ Block *next = b->next;
+ g_slice_free (Block, b);
+ b = next;
+ }
+}
+
+static void
+app_update_alloc_fn (App *app)
+{
+ Allocator *A;
+ guint n;
+
+ for (A = app->allocators; A != NULL; A = A->next) {
+ for (n = 0; n < A->n_frames - 1; n++) {
+ if (! frames_is_alloc_fn (app->frames, A->ips[n]))
+ break;
+ }
+ A->alloc_fn = A->functions[n];
+ }
+}
+
+static void
+app_update_allocators (App *app, guint time)
+{
+ app_update_alloc_fn (app);
+
+ if (app->call_graph != NULL)
+ g_object_unref (app->call_graph);
+ app->call_graph = call_graph_store_new (app, app->allocators);
+
+ g_object_set (app->allocations.call_graph, "model", app->call_graph, NULL);
+ g_object_set (app->allocations.tree_map, "model", app->call_graph, NULL);
+
+ timeline_add_datum ((Timeline *) app->timeline, time, app->allocators);
+}
+
+static gboolean
+load_allocators (GIOChannel *io, App *app)
+{
+ gint count;
+ gint len;
+ int fd;
+ Block *blocks;
+ gchar *client;
+ gint pid, time;
+ guint n_allocs, n_blocks;
+
+ fd = g_io_channel_unix_get_fd (io);
+ if (! readn (fd, &len, sizeof (len)))
+ return FALSE;
+ client = g_malloc (len + 1);
+ if (! readn (fd, client, len)) {
+ g_free (client);
+ return FALSE;
+ }
+ client[len] = '\0';
+ app_set_client_name (app, client);
+
+ if (! readn (fd, &pid, sizeof (pid)))
+ return FALSE;
+ if (! readn (fd, &time, sizeof (time)))
+ return FALSE;
+
+ /* frames */
+ if (! readn (fd, &count, sizeof (count)))
+ return FALSE;
+ while (count--) {
+ gchar buf[4096], *bp = buf;
+ guint eip;
+ gchar *function, *object, *file, *directory;
+ gint line;
+ guint len = G_N_ELEMENTS (buf);
+
+ if (! readn (fd, &eip, sizeof (eip)))
+ return FALSE;
+
+ if (frames_has_ip (app->frames, eip)) {
+ if (! discard_string (fd) ||
+ ! discard_string (fd) ||
+ ! discard_string (fd) ||
+ ! discard_string (fd) ||
+ ! discardn (fd, sizeof (line)))
+ {
+ return FALSE;
+ }
+ continue;
+ }
+
+ function = object = file = directory = NULL;
+ if (! read_string (fd, &bp, &len, &function) ||
+ ! read_string (fd, &bp, &len, &object) ||
+ ! read_string (fd, &bp, &len, &file) ||
+ ! read_string (fd, &bp, &len, &directory))
+ {
+ if (function < buf || function > bp)
+ g_free (function);
+ if (object < buf || object > bp)
+ g_free (object);
+ if (file < buf || file > bp)
+ g_free (file);
+ if (directory < buf || directory > bp)
+ g_free (directory);
+ g_free (client);
+ return FALSE;
+ }
+
+ if (! readn (fd, &line, sizeof (line)))
+ return FALSE;
+
+ frames_add (app->frames, eip,
+ function, object, file, directory,
+ line);
+
+ if (function < buf || function > bp)
+ g_free (function);
+ if (object < buf || object > bp)
+ g_free (object);
+ if (file < buf || file > bp)
+ g_free (file);
+ if (directory < buf || directory > bp)
+ g_free (directory);
+ }
+
+ /* allocators */
+ if (! readn (fd, &count, sizeof (count)))
+ return FALSE;
+ n_allocs = 0;
+ while (count--) {
+ Allocator *A = read_allocator (app, fd, time);
+ if (A == NULL) {
+ g_free (client);
+ return FALSE;
+ }
+ n_allocs += A->time_tail->n_allocs;
+ }
+
+ /* blocks */
+ if (! readn (fd, &count, sizeof (count))) {
+ g_free (client);
+ return FALSE;
+ }
+ blocks = NULL;
+ n_blocks = count;
+ while (count--) {
+ Block *new_blocks = read_block (fd, app->allocators_by_addr, blocks);
+ if (new_blocks == NULL) {
+ g_free (client);
+ return FALSE;
+ }
+ blocks = new_blocks;
+ }
+
+
+ /* events */
+ if (! readn (fd, &count, sizeof (count))) {
+ g_free (client);
+ return FALSE;
+ }
+ while (count--) {
+ Event ev;
+ gchar c;
+ guint allocator;
+
+
+ if (! readn (fd, &c, sizeof (c)) ||
+ ! readn (fd, &ev.base.time, sizeof (ev.base.time)) ||
+ ! readn (fd, &ev.base.tid, sizeof (ev.base.tid)) ||
+ ! readn (fd, &allocator, sizeof (allocator)))
+ {
+ g_free (client);
+ return FALSE;
+ }
+
+ ev.base.allocator = g_hash_table_lookup (app->allocators_by_addr,
+ GUINT_TO_POINTER (allocator));
+ if (ev.base.allocator == NULL) {
+ g_free (client);
+ return FALSE;
+ }
+
+ ev.type = c;
+ switch (ev.type) {
+ case ALLOC:
+ if (! readn (fd, &ev.alloc.addr, sizeof (ev.alloc.addr)) ||
+ ! readn (fd, &ev.alloc.size, sizeof (ev.alloc.size)))
+ {
+ g_free (client);
+ return FALSE;
+ }
+
+ break;
+
+ case REALLOC:
+ if (! readn (fd, &ev.realloc.old_addr, sizeof (ev.realloc.old_addr)) ||
+ ! readn (fd, &ev.realloc.old_size, sizeof (ev.realloc.old_size)) ||
+ ! readn (fd, &ev.realloc.new_addr, sizeof (ev.realloc.new_addr)) ||
+ ! readn (fd, &ev.realloc.new_size, sizeof (ev.realloc.new_size)))
+ {
+ g_free (client);
+ return FALSE;
+ }
+ break;
+
+ case DEALLOC:
+ if (! readn (fd, &ev.dealloc.addr, sizeof (ev.dealloc.addr)) ||
+ ! readn (fd, &ev.dealloc.size, sizeof (ev.dealloc.size))) {
+ g_free (client);
+ return FALSE;
+ }
+ break;
+ }
+
+ g_array_append_val (app->events, ev);
+ }
+
+ app_update_allocators (app, time);
+ app_set_blocks (app, blocks);
+
+ app_set_status (app, client, time, n_allocs, n_blocks, app->call_graph->n_frames);
+ g_free (client);
+
+ return TRUE;
+}
+
+static void
+allocators_selection_changed (GtkTreeSelection *selection,
+ gpointer data)
+{
+ GList *rows, *l;
+ GSList *blocks = NULL;
+ GtkTreeModel *model;
+ Allocators *allocators;
+
+ allocators = (Allocators *) gtk_tree_selection_get_tree_view (selection);
+
+ rows = gtk_tree_selection_get_selected_rows (selection, &model);
+ for (l = rows; l != NULL; l = g_list_next (l)) {
+ GtkTreeIter iter;
+ gtk_tree_model_get_iter (model, &iter, l->data);
+ blocks = allocators_get_blocks_for_iter (allocators, &iter, blocks);
+ gtk_tree_path_free (l->data);
+ }
+ g_list_free (rows);
+
+ block_map_set_highlight (data, blocks);
+ g_slist_free (blocks);
+}
+
+static void
+block_map_selection_changed (BlockMap *block_map,
+ GParamSpec *spec,
+ gpointer data)
+{
+ gpointer blocks;
+
+ g_object_get (G_OBJECT (block_map), "selection", &blocks, NULL);
+
+ allocators_select_blocks (data, blocks);
+}
+
+static void
+refresh_clicked (GtkButton *button, App *app)
+{
+ FILE *file = fopen ("/tmp/memfault", "a");
+ if (file != NULL) {
+ GInetAddr *addr = gnet_tcp_socket_get_local_inetaddr (app->tcp);
+ gchar *name = gnet_inetaddr_get_canonical_name (addr);
+ gint port = gnet_inetaddr_get_port (addr);
+ fprintf (file, "A%s:%d\n", name, port);
+ g_free (name);
+ fclose (file);
+ }
+}
+
+static void
+tree_map_selection_changed (GtkWidget *w, GtkTreeIter *iter, GtkTreeView *tv)
+{
+ GtkTreeSelection *selection;
+ GtkTreeModel *model;
+ GtkTreePath *path;
+
+ selection = gtk_tree_view_get_selection (tv);
+ gtk_tree_selection_unselect_all (selection);
+
+ model = gtk_tree_view_get_model (tv);
+ path = gtk_tree_model_get_path (model, iter);
+
+ gtk_tree_view_expand_to_path (tv, path);
+ gtk_tree_view_scroll_to_cell (tv, path, NULL, FALSE, 0., 0.);
+ gtk_tree_path_free (path);
+
+ gtk_tree_selection_select_iter (selection, iter);
+}
+
+static GtkWidget *
+main_window_create (App *app)
+{
+ GtkWidget *hbox, *sw, *vbox, *w, *notebook, *label;
+ GtkTreeSelection *selection;
+
+ app->allocation_image.allocators = allocators_new ();
+ app->allocation_image.block_map = block_map_new ();
+ app->allocations.call_graph = call_graph_new ();
+ app->allocations.tree_map = call_graph_tree_map_new ();
+
+ g_signal_connect (app->allocations.tree_map, "selected",
+ G_CALLBACK (tree_map_selection_changed),
+ app->allocations.call_graph);
+
+ selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (app->allocation_image.allocators));
+ g_signal_connect (selection, "changed",
+ G_CALLBACK (allocators_selection_changed),
+ app->allocation_image.block_map);
+
+ g_signal_connect (app->allocation_image.block_map, "notify::selection",
+ G_CALLBACK (block_map_selection_changed),
+ app->allocation_image.allocators);
+
+ app->window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
+ g_object_set_qdata (G_OBJECT (app->window), app_quark, app);
+
+ vbox = gtk_vbox_new (FALSE, 2);
+ gtk_container_add (GTK_CONTAINER (app->window), vbox);
+ gtk_widget_show (vbox);
+
+ w = gtk_button_new_from_stock (GTK_STOCK_REFRESH);
+ g_signal_connect (w, "clicked", G_CALLBACK (refresh_clicked), app);
+ gtk_box_pack_start (GTK_BOX (vbox), w, FALSE, FALSE, 2);
+ gtk_widget_show (w);
+ app->refresh_button = w;
+
+ notebook = gtk_notebook_new ();
+ gtk_box_pack_start (GTK_BOX (vbox), notebook, TRUE, TRUE, 2);
+ gtk_widget_show (notebook);
+
+ w = timeline_new ();
+ gtk_box_pack_start (GTK_BOX (vbox), w, FALSE, FALSE, 2);
+ gtk_widget_show (w);
+ app->timeline = w;
+
+ app->statusbar = gtk_statusbar_new ();
+ gtk_box_pack_start (GTK_BOX (vbox), app->statusbar, FALSE, FALSE, 2);
+ gtk_widget_show (app->statusbar);
+
+ hbox = gtk_hbox_new (FALSE, 2);
+ label = gtk_label_new ("Allocation Map");
+ gtk_notebook_append_page (GTK_NOTEBOOK (notebook), hbox, label);
+ gtk_widget_show (hbox);
+ gtk_widget_show (label);
+
+ sw = gtk_scrolled_window_new (NULL, NULL);
+ gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (sw),
+ GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
+
+ gtk_container_add (GTK_CONTAINER (sw), app->allocation_image.allocators);
+ gtk_widget_show (app->allocation_image.allocators);
+ gtk_box_pack_start (GTK_BOX (hbox), sw, FALSE, FALSE, 2);
+ gtk_widget_show (sw);
+
+ w = app->allocation_image.block_map;
+ gtk_box_pack_start (GTK_BOX (hbox), w, TRUE, TRUE, 2);
+ gtk_widget_show (w);
+
+ hbox = gtk_hbox_new (FALSE, 2);
+
+ sw = gtk_scrolled_window_new (NULL, NULL);
+ gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (sw),
+ GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
+ gtk_container_add (GTK_CONTAINER (sw), app->allocations.call_graph);
+ gtk_widget_show (app->allocations.call_graph);
+ gtk_box_pack_start (GTK_BOX (hbox), sw, FALSE, TRUE, 2);
+ gtk_widget_show (sw);
+
+ w = app->allocations.tree_map;
+ gtk_box_pack_start (GTK_BOX (hbox), w, TRUE, TRUE, 2);
+ gtk_widget_show (w);
+
+ label = gtk_label_new ("All allocations");
+ gtk_notebook_append_page (GTK_NOTEBOOK (notebook),
+ hbox, label);
+ gtk_widget_show (hbox);
+ gtk_widget_show (label);
+
+ return app->window;
+}
+
+static gboolean
+tcp_server_cb (GIOChannel *source,
+ GIOCondition condition,
+ gpointer data)
+{
+ App *app = data;
+
+ if (condition & G_IO_IN) {
+ GTcpSocket *client = gnet_tcp_socket_server_accept (app->tcp);
+ GdkCursor *cursor = gdk_cursor_new (GDK_WATCH);
+ gdk_window_set_cursor (app->window->window, cursor);
+ gdk_cursor_unref (cursor);
+ gdk_flush ();
+
+ load_allocators (gnet_tcp_socket_get_io_channel (client), app);
+
+ gdk_window_set_cursor (app->window->window, NULL);
+
+ gnet_tcp_socket_delete (client);
+ }
+
+ return TRUE;
+}
+
+gboolean
+app_is_alloc_fn (App *app, guint ip)
+{
+ return frames_is_alloc_fn (app->frames, ip);
+}
+
+gboolean
+app_add_alloc_fn (App *app, const gchar *pattern, GError **error)
+{
+ if (! frames_add_alloc_fn (app->frames, pattern, error))
+ return FALSE;
+
+ app_update_alloc_fn (app);
+
+ /* XXX signal */
+ call_graph_store_filter (app->call_graph, app);
+
+ return TRUE;
+}
+
+guint
+app_get_alloc_fns_serial (App *app)
+{
+ return frames_get_alloc_fns_serial (app->frames);
+}
+
+
+App *
+app_get (GtkWidget *widget)
+{
+ widget = gtk_widget_get_toplevel (widget);
+ return g_object_get_qdata (G_OBJECT (widget), app_quark);
+}
+
+G_CONST_RETURN Event *
+app_find_prev_event_for_addr_range (App *app, guint time, guint min, guint max)
+{
+ guint n;
+
+ if (app->events->len == 0)
+ return NULL;
+
+ for (n = app->events->len; n-- > 0; ) {
+ Event *event = &g_array_index (app->events, Event, n);
+ if (event->base.time > time)
+ continue;
+
+ switch (event->type) {
+ case ALLOC:
+ if (event->alloc.addr + event->alloc.size < min ||
+ event->alloc.addr > max)
+ {
+ continue;
+ }
+ break;
+ case REALLOC:
+ if ((event->realloc.new_addr + event->realloc.new_size < max &&
+ event->realloc.new_addr > min) ||
+ (event->realloc.old_addr + event->realloc.old_size < max &&
+ event->realloc.old_addr > min))
+ {
+ break;
+ }
+ continue;
+
+ case DEALLOC:
+ if (event->dealloc.addr + event->dealloc.size < min ||
+ event->dealloc.addr > max)
+ {
+ continue;
+ }
+ break;
+ }
+
+ return event;
+ }
+
+ return NULL;
+}
+
+static void
+reap_child (GPid pid, gint status, gpointer data)
+{
+ App *app = data;
+ gtk_widget_set_sensitive (app->refresh_button, FALSE);
+ g_spawn_close_pid (pid);
+}
+
+static void
+execute_cmdline (App *app, int argc, char **argv)
+{
+ GInetAddr *addr = gnet_tcp_socket_get_local_inetaddr (app->tcp);
+ gchar *name = gnet_inetaddr_get_canonical_name (addr);
+ gint port = gnet_inetaddr_get_port (addr);
+ GError *error = NULL;
+ GPid child;
+ gchar **argvp = g_new (gchar *, argc + 7);
+ int i, j;
+ const gchar *env;
+
+ j = 0;
+ env = g_getenv ("VALGRIND");
+ if (env == NULL)
+ env = "valgrind";
+ argvp[j++] = g_strdup (env);
+ argvp[j++] = g_strdup ("valgrind");
+ argvp[j++] = g_strdup ("--tool=memfault");
+ argvp[j++] = g_strdup_printf ("--events=%s:%d", name, port);
+ argvp[j++] = g_strdup ("--allow-malloc0=yes");
+ argvp[j++] = g_strdup ("--show-allocators=no");
+ for (i = 0; i < argc; i++)
+ argvp[j++] = g_strdup (argv[i]);
+ argvp[j] = NULL;
+
+ if (! g_spawn_async (NULL, argvp, NULL,
+ G_SPAWN_SEARCH_PATH |
+ //G_SPAWN_STDOUT_TO_DEV_NULL |
+ //G_SPAWN_STDERR_TO_DEV_NULL |
+ G_SPAWN_DO_NOT_REAP_CHILD |
+ G_SPAWN_FILE_AND_ARGV_ZERO,
+ NULL, NULL, &child, &error)) {
+ g_warning ("Failed to execute child: %s", error->message);
+ g_error_free (error);
+
+ gtk_widget_set_sensitive (app->refresh_button, FALSE);
+ } else
+ g_child_watch_add (child, reap_child, app);
+
+ g_strfreev (argvp);
+}
+
+int main
+(int argc, char **argv)
+{
+ App app;
+ GIOChannel *io;
+ GtkWidget *window;
+ GInetAddr *iface;
+
+ memset (&app, 0, sizeof (app));
+
+ gnet_init ();
+ gtk_init (&argc, &argv);
+
+ app_quark = g_quark_from_static_string ("«App»");
+ app.frames = frames_create ();
+ app.events = g_array_new (FALSE, FALSE, sizeof (Event));
+ app.allocators_by_addr = g_hash_table_new (NULL, NULL);
+
+ window = main_window_create (&app);
+ g_signal_connect (window, "delete-event", G_CALLBACK (gtk_main_quit), NULL);
+ gtk_window_present (GTK_WINDOW (window));
+ /* force the realize... */
+ while (gtk_events_pending ())
+ gtk_main_iteration ();
+
+ /* restrict to IPv4 loopback */
+ iface = gnet_inetaddr_new ("127.0.0.1", 0);
+ app.tcp = gnet_tcp_socket_server_new_full (iface, 0);
+
+ io = gnet_tcp_socket_get_io_channel (app.tcp);
+ g_io_add_watch (io, G_IO_IN, tcp_server_cb, &app);
+
+ if (argc > 1) {
+ gboolean have_allocators = FALSE;
+ GIOChannel *io = g_io_channel_new_file (argv[1], "r", NULL);
+ if (io != NULL) {
+ if (! load_allocators (io, &app)) {
+ g_warning ("Failed to load allocations from '%s'", argv[1]);
+ } else
+ have_allocators = TRUE;
+ g_io_channel_unref (io);
+ }
+ if (! have_allocators)
+ execute_cmdline (&app, argc - 1, argv + 1);
+ }
+
+ gtk_main ();
+
+ return 0;
+}
diff --git a/src/blockmap.c b/src/blockmap.c
new file mode 100644
index 0000000..590fc86
--- /dev/null
+++ b/src/blockmap.c
@@ -0,0 +1,681 @@
+#include <gtk/gtk.h>
+#include <math.h>
+#include <unistd.h>
+#include <string.h>
+
+#include "memfault.h"
+
+#define _(x) x
+
+struct _block_map {
+ GtkWidget widget;
+
+ guint width, height;
+ guint min, max, pages;
+ guint pagesize;
+
+ Block *blocks;
+ cairo_pattern_t *pattern;
+
+ GSList **map;
+ GSList *highlight;
+ GSList *selection;
+ guint mouse_over_cell;
+};
+typedef struct _block_map_class {
+ GtkWidgetClass parent_class;
+} BlockMapClass;
+
+G_DEFINE_TYPE (BlockMap, block_map, GTK_TYPE_WIDGET)
+
+enum {
+ PROP_0 = 0,
+ PROP_BLOCKS,
+ PROP_SELECTION,
+};
+
+static gboolean
+block_map_event_to_cell (BlockMap *self, gdouble x, gdouble y, gint *cell_x, gint *cell_y);
+
+static void
+block_map_set_blocks (BlockMap *self, Block *blocks)
+{
+ Block *b;
+ guint min = (guint) -1;
+ guint max = 0;
+ guint pages;
+ guint p;
+ cairo_surface_t *image;
+ guchar *data;
+ guint8 *bitmap;
+ guint x;
+ guint width;
+ guint height;
+ guint stride;
+ guint pagesize;
+ GSList *l;
+
+ pagesize = sysconf (_SC_PAGESIZE);
+
+ gtk_widget_queue_draw (&self->widget);
+ self->blocks = blocks;
+
+ g_slist_free (self->highlight);
+ self->highlight = NULL;
+
+ cairo_pattern_destroy (self->pattern);
+ self->pattern = NULL;
+
+ g_free (self->map);
+ self->map = NULL;
+
+ for (b = blocks; b != NULL; b = b->next) {
+ if (b->addr < min)
+ min = b->addr;
+ if (b->addr + b->size > max)
+ max = b->addr + b->size;
+ }
+
+ if (max < min) {
+ self->mouse_over_cell = -1;
+ return;
+ }
+
+ min &= ~(pagesize - 1);
+ max = (max + pagesize) & ~(pagesize - 1);
+ pages = (max - min) / pagesize;
+
+ /* find the approx. sqrt */
+ for (width = 1; width * width < pages; width++)
+ ;
+ height = (pages + width - 1) / width;
+
+#define ADD(b, v) G_STMT_START { \
+ gint __v = ((v) + (pagesize/256) - 1) / (pagesize/256), __b = *(b); \
+ if (__b > 0xff - __v) \
+ __b = 0xff; \
+ else \
+ __b += __v; \
+ *(b) = __b; \
+} G_STMT_END
+ bitmap = g_new0 (guint8, width * height);
+ for (b = blocks; b != NULL; b = b->next) {
+ p = (b->addr - min) / pagesize;
+ if ((b->addr + b->size) / pagesize == b->addr / pagesize) {
+ ADD (&bitmap[p], b->size);
+ } else {
+ guint last_page = (b->addr + b->size - min) / pagesize;
+ ADD (&bitmap[p], pagesize - (b->addr & (pagesize - 1)));
+ for (++p; p < last_page; p++)
+ bitmap[p] = 0xff;
+ ADD (&bitmap[p], (b->addr + b->size) & (pagesize - 1));
+ }
+ }
+#undef ADD
+
+ image = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, width, height);
+ data = cairo_image_surface_get_data (image);
+ if (data == NULL) {
+ g_free (bitmap);
+ return;
+ }
+
+ stride = cairo_image_surface_get_stride (image);
+ p = 0;
+ do {
+ for (x = 0; x < width; x++) {
+ if (bitmap[p] == 0) {
+ data[4*x + 0] = 128;
+ data[4*x + 1] = 0;
+ data[4*x + 2] = 0;
+ data[4*x + 3] = 255;
+ } else {
+ data[4*x + 0] = bitmap[p];
+ data[4*x + 1] = bitmap[p];
+ data[4*x + 2] = 255;
+ data[4*x + 3] = 255;
+ }
+ if (++p >= pages)
+ break;
+ }
+ data += stride;
+ } while (p < pages);
+ g_free (bitmap);
+ cairo_surface_mark_dirty (image);
+ self->pattern = cairo_pattern_create_for_surface (image);
+ cairo_surface_destroy (image);
+ cairo_pattern_set_filter (self->pattern, CAIRO_FILTER_NEAREST);
+
+ x = 0;
+ for (b = blocks; b != NULL; b = b->next) {
+ guint last_page = (b->addr + b->size - min) / pagesize;
+ for (p = (b->addr - min) / pagesize; p <= last_page; p++)
+ x++;
+ }
+
+ self->map = g_malloc (sizeof (GSList *) * (width * height) +
+ sizeof (GSList) * x);
+ memset (self->map, 0, sizeof (GSList *) * (width * height));
+ l = (GSList *) (self->map + width * height);
+ for (b = blocks; b != NULL; b = b->next) {
+ guint last_page = (b->addr + b->size - min) / pagesize;
+ for (p = (b->addr - min) / pagesize; p <= last_page; p++) {
+ l->data = b;
+ l->next = self->map[p];
+ self->map[p] = l++;
+ }
+ }
+
+ self->min = min;
+ self->max = max;
+ self->pages = pages;
+ self->pagesize = pagesize;
+
+ self->width = width;
+ self->height = height;
+
+ if (self->mouse_over_cell != (guint) -1) {
+ gint x, y;
+
+ gtk_widget_get_pointer (&self->widget, &x, &y);
+
+ if (block_map_event_to_cell (self, x, y, &x, &y))
+ self->mouse_over_cell = y * self->width + x;
+ else
+ self->mouse_over_cell = -1;
+
+ gtk_widget_trigger_tooltip_query (&self->widget);
+ }
+}
+
+static void
+block_map_set_property (GObject *obj, guint id, const GValue *v, GParamSpec *spec)
+{
+ BlockMap *self = (BlockMap *) obj;
+ switch (id) {
+ case PROP_BLOCKS:
+ block_map_set_blocks (self, g_value_get_pointer (v));
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (obj, id, spec);
+ break;
+ }
+}
+
+static void
+block_map_get_property (GObject *obj, guint id, GValue *v, GParamSpec *spec)
+{
+ BlockMap *self = (BlockMap *) obj;
+ switch (id) {
+ case PROP_BLOCKS:
+ g_value_set_pointer (v, self->blocks);
+ break;
+ case PROP_SELECTION:
+ g_value_set_pointer (v, self->selection);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (obj, id, spec);
+ break;
+ }
+}
+
+static void
+block_map_realize (GtkWidget *widget)
+{
+ GdkWindowAttr attributes;
+
+ GTK_WIDGET_SET_FLAGS (widget, GTK_REALIZED);
+
+ attributes.window_type = GDK_WINDOW_CHILD;
+ attributes.x = widget->allocation.x;
+ attributes.y = widget->allocation.y;
+ attributes.width = widget->allocation.width;
+ attributes.height = widget->allocation.height;
+ attributes.wclass = GDK_INPUT_OUTPUT;
+ attributes.visual = gtk_widget_get_visual (widget);
+ attributes.colormap = gtk_widget_get_colormap (widget);
+ attributes.event_mask = GDK_BUTTON_PRESS_MASK |
+ GDK_POINTER_MOTION_MASK |
+ GDK_LEAVE_NOTIFY_MASK |
+ GDK_EXPOSURE_MASK;
+
+ widget->window = gdk_window_new (gtk_widget_get_parent_window (widget),
+ &attributes,
+ GDK_WA_X | GDK_WA_Y |
+ GDK_WA_VISUAL | GDK_WA_COLORMAP);
+ gdk_window_set_user_data (widget->window, widget);
+
+ widget->style = gtk_style_attach (widget->style, widget->window);
+ gtk_style_set_background (widget->style, widget->window, GTK_STATE_NORMAL);
+}
+
+static gboolean
+block_map_expose (GtkWidget *widget, GdkEventExpose *ev)
+{
+ BlockMap *self = (BlockMap *) widget;
+ cairo_t *cr;
+ gint sf_x, sf_y, sf;
+ gdouble tx, ty;
+ GSList *l;
+
+ cr = gdk_cairo_create (widget->window);
+ gdk_cairo_region (cr, ev->region);
+ cairo_clip (cr);
+
+ if (self->pattern != NULL) {
+ sf_x = widget->allocation.width / self->width;
+ sf_y = widget->allocation.height / self->height;
+ sf = MIN (sf_x, sf_y);
+ if (sf == 0)
+ return FALSE;
+
+ tx = floor ((widget->allocation.width - sf * self->width) / 2.);
+ ty = floor ((widget->allocation.height - sf * self->height) / 2.);
+
+ cairo_translate (cr, tx, ty);
+ cairo_scale (cr, sf, sf);
+ cairo_set_source (cr, self->pattern);
+ cairo_paint (cr);
+
+ if (self->highlight != NULL) {
+ guint *pages = g_new0 (guint, (self->pages + 31) / 32);
+ gint cnt = 0;
+ cairo_save (cr);
+ cairo_translate (cr, .5 / sf, .5 / sf);
+ cairo_set_line_width (cr, 1. / sf);
+ cairo_set_source_rgb (cr, 0., 0., 0.);
+ for (l = self->highlight; l != NULL; l = g_slist_next (l)) {
+ Block *b = l->data;
+ guint p;
+ guint x0, y0;
+ guint x1, y1;
+
+ p = (b->addr - self->min) / self->pagesize;
+ x0 = p % self->width;
+ y0 = p / self->width;
+
+ p = (b->addr + b->size - self->min) / self->pagesize;
+ x1 = p % self->width;
+ y1 = p / self->width;
+
+ if (y0 == y1) {
+ if (x0 == x0) {
+ /* discard small allocations to the same page */
+ if ((pages[p / 32] & 1 << (p & 31)))
+ continue;
+ pages[p / 32] |= 1 << (p & 31);
+ }
+ cairo_rectangle (cr,
+ x0, y0,
+ x1 - x0 + 1, 1);
+ } else {
+ cairo_move_to (cr, self->width, y0 + 1);
+ cairo_line_to (cr, x0, y0 + 1);
+ cairo_line_to (cr, x0, y0);
+ cairo_line_to (cr, self->width, y0);
+ for (p = y0 + 1; p < y1; p++) {
+ cairo_move_to (cr, 0, p);
+ cairo_line_to (cr, self->width, p);
+ cairo_move_to (cr, 0, p + 1);
+ cairo_line_to (cr, self->width, p + 1);
+ }
+ cairo_move_to (cr, 0, y1);
+ cairo_line_to (cr, x1 + 1, y1);
+ cairo_line_to (cr, x1 + 1, y1 + 1);
+ cairo_line_to (cr, 0, y1 + 1);
+ }
+
+ if (++cnt == 128) {
+ cairo_stroke (cr);
+ cnt = 0;
+ }
+ }
+ cairo_stroke (cr);
+ cairo_restore (cr);
+ g_free (pages);
+ }
+
+ if (self->mouse_over_cell != (guint) -1) {
+ guint x = self->mouse_over_cell % self->width;
+ guint y = self->mouse_over_cell / self->width;
+ cairo_save (cr);
+ cairo_rectangle (cr, x, y, 1, 1);
+ cairo_set_source_rgb (cr, 1, 1, 0);
+ cairo_fill (cr);
+ cairo_translate (cr, .5 / sf, .5 / sf);
+ cairo_set_line_width (cr, 1. / sf);
+ cairo_rectangle (cr, x, y, 1, 1);
+ cairo_set_source_rgb (cr, 0, 0, 0);
+ cairo_stroke (cr);
+ cairo_restore (cr);
+ }
+ }
+
+ cairo_destroy (cr);
+
+ return FALSE;
+}
+
+static gboolean
+block_map_event_to_cell (BlockMap *self, gdouble x, gdouble y, gint *cell_x, gint *cell_y)
+{
+ gint sf_x, sf_y, sf;
+ gdouble tx, ty;
+
+ if (self->pattern == NULL)
+ return FALSE;
+
+ sf_x = self->widget.allocation.width / self->width;
+ sf_y = self->widget.allocation.height / self->height;
+ sf = MIN (sf_x, sf_y);
+ if (sf == 0)
+ return FALSE;
+
+ tx = floor (.5 * (self->widget.allocation.width - sf * self->width));
+ ty = floor (.5 * (self->widget.allocation.height - sf * self->height));
+
+ tx = (x - tx) / sf;
+ ty = (y - ty) / sf;
+
+ if (tx < 0. || tx >= self->width || ty < 0. || ty >= self->height)
+ return FALSE;
+
+ *cell_x = floor (tx);
+ *cell_y = floor (ty);
+
+ return *cell_x + *cell_y * self->width < self->pages;
+}
+
+static gboolean
+block_map_get_cell_rect (BlockMap *self, gint x, gint y, GdkRectangle *rect)
+{
+ gint sf_x, sf_y, sf;
+ gdouble tx, ty;
+
+ if ((guint) x >= self->width || (guint) y >= self->height)
+ return FALSE;
+
+ sf_x = self->widget.allocation.width / self->width;
+ sf_y = self->widget.allocation.height / self->height;
+ sf = MIN (sf_x, sf_y);
+ if (sf == 0)
+ return FALSE;
+
+ tx = floor (.5 * (self->widget.allocation.width - sf * self->width));
+ ty = floor (.5 * (self->widget.allocation.height - sf * self->height));
+
+ rect->x = tx + x * sf;
+ rect->y = ty + y * sf;
+ rect->width = sf;
+ rect->height = sf;
+
+ return TRUE;
+}
+
+typedef struct _block_call_count {
+ Allocator *allocator;
+ const gchar *function;
+ guint sum;
+ guint cnt;
+} BlockCallCount;
+
+static BlockCallCount *
+_block_call_count_new (Allocator *A)
+{
+ BlockCallCount *count = g_slice_new (BlockCallCount);
+ count->allocator = A;
+ count->function = A->alloc_fn;
+ count->sum = 0;
+ count->cnt = 0;
+ return count;
+}
+
+static void
+_block_call_count_destroy (gpointer data)
+{
+ BlockCallCount *count = data;
+ g_slice_free (BlockCallCount, count);
+}
+
+static gint
+_block_call_count_cmp (gconstpointer A, gconstpointer B)
+{
+ const BlockCallCount * const *a = A, * const *b = B;
+ return (*b)->cnt - (*a)->cnt;
+}
+
+static gboolean
+block_map_query_tooltip (GtkWidget *widget,
+ gint x,
+ gint y,
+ gboolean keyboard_tip,
+ GtkTooltip *tooltip)
+{
+ BlockMap *self = (BlockMap *) widget;
+
+ if (block_map_event_to_cell (self, x, y, &x, &y)) {
+ GSList *blocks = self->map[y * self->width + x];
+ if (blocks != NULL) {
+ GString *string;
+ GdkRectangle rect;
+ gchar *text;
+ guint n;
+ GHashTable *allocators = g_hash_table_new_full (
+ g_str_hash, g_str_equal,
+ NULL, _block_call_count_destroy);
+ GPtrArray *array = g_ptr_array_new ();
+ do {
+ Block *b = blocks->data;
+ Allocator *A = b->allocator;
+ BlockCallCount *count = g_hash_table_lookup (allocators,
+ A->alloc_fn);
+ if (count == NULL) {
+ count = _block_call_count_new (A);
+ g_hash_table_insert (allocators,
+ (gpointer) A->alloc_fn, count);
+ g_ptr_array_add (array, count);
+ }
+ count->sum += b->size;
+ count->cnt++;
+ } while ((blocks = g_slist_next (blocks)) != NULL);
+
+ g_ptr_array_sort (array, _block_call_count_cmp);
+ string = g_string_new ("");
+ if (array->len == 1) {
+ BlockCallCount *count = g_ptr_array_index (array, 0);
+ Allocator *A = count->allocator;
+ guint m;
+
+ g_string_append_printf (string,
+ "%s: %d bytes over %d calls\nCalled from:",
+ count->function, count->sum, count->cnt);
+ for (n = 0; n < A->n_frames - 1; n++) {
+ if (A->functions[n] == A->alloc_fn)
+ break;
+ }
+ for (m = n + 1; m < MIN (n + 9, A->n_frames); m++) {
+ if (strcmp (A->functions_srcloc[m],
+ A->functions_srcloc[m-1]))
+ {
+ g_string_append_c (string, '\n');
+ g_string_append_c (string, '\t');
+ g_string_append (string, A->functions_srcloc[m]);
+ } else
+ n++;
+ }
+ } else for (n = 0; n < array->len; n++) {
+ BlockCallCount *count = g_ptr_array_index (array, n);
+ if (n)
+ g_string_append_c (string, '\n');
+ g_string_append_printf (string, "%s: %d bytes over %d calls",
+ count->function, count->sum, count->cnt);
+ }
+ g_ptr_array_free (array, TRUE);
+ g_hash_table_destroy (allocators);
+
+ text = g_string_free (string, FALSE);
+ gtk_tooltip_set_text (tooltip, text);
+ g_free (text);
+
+ block_map_get_cell_rect (self, x, y, &rect);
+ gtk_tooltip_set_tip_area (tooltip, &rect);
+ return TRUE;
+ } else {
+ /* find the last block that was here */
+ App *app = app_get (&self->widget);
+ guint page = y * self->width + x;
+ const Event *event = app_find_prev_event_for_addr_range (app, -1U,
+ self->min + page * 4096, self->min + (page + 1) * 4096);
+ if (event != NULL) {
+ Allocator *A = event->base.allocator;
+ GString *string;
+ guint n, m;
+ gchar *text;
+ GdkRectangle rect;
+
+ string = g_string_new ( "Last allocated by:");
+ for (n = 0; n < A->n_frames - 1; n++) {
+ if (A->functions[n] == A->alloc_fn)
+ break;
+ }
+ for (m = n + 1; m < MIN (n + 9, A->n_frames); m++) {
+ if (strcmp (A->functions_srcloc[m],
+ A->functions_srcloc[m-1]))
+ {
+ g_string_append_c (string, '\n');
+ g_string_append_c (string, '\t');
+ g_string_append (string, A->functions_srcloc[m]);
+ } else
+ n++;
+ }
+
+ text = g_string_free (string, FALSE);
+ gtk_tooltip_set_text (tooltip, text);
+ g_free (text);
+
+ block_map_get_cell_rect (self, x, y, &rect);
+ gtk_tooltip_set_tip_area (tooltip, &rect);
+ return TRUE;
+ }
+ }
+ }
+
+ return FALSE;
+}
+
+static void
+block_map_set_selection (BlockMap *self, GSList *blocks)
+{
+ if (self->selection == blocks)
+ return;
+
+ self->selection = blocks;
+ g_object_notify (G_OBJECT (self), "selection");
+}
+
+static gboolean
+block_map_button_press (GtkWidget *widget, GdkEventButton *ev)
+{
+ BlockMap *self = (BlockMap *) widget;
+ gint x,y;
+ GSList *blocks;
+
+ blocks = NULL;
+ if (block_map_event_to_cell (self, ev->x, ev->y, &x, &y))
+ blocks = self->map[y * self->width + x];
+ block_map_set_selection (self, blocks);
+
+ return FALSE;
+}
+
+static gboolean
+block_map_motion_notify (GtkWidget *widget, GdkEventMotion *ev)
+{
+ BlockMap *self = (BlockMap *) widget;
+ gint x,y;
+ guint cell;
+
+ cell = -1;
+ if (block_map_event_to_cell (self, ev->x, ev->y, &x, &y))
+ cell = y * self->width + x;
+ if (self->mouse_over_cell != cell) {
+ self->mouse_over_cell = cell;
+ gtk_widget_queue_draw (widget);
+ }
+
+ return FALSE;
+}
+
+static gboolean
+block_map_leave_notify (GtkWidget *widget, GdkEventCrossing *ev)
+{
+ BlockMap *self = (BlockMap *) widget;
+
+ if (self->mouse_over_cell != (guint) -1) {
+ self->mouse_over_cell = -1;
+ gtk_widget_queue_draw (widget);
+ }
+
+ return FALSE;
+}
+
+
+static void
+block_map_class_init (BlockMapClass *klass)
+{
+ GObjectClass *object_class = (GObjectClass *) klass;
+ GtkWidgetClass *widget_class = (GtkWidgetClass *) klass;
+
+ object_class->set_property = block_map_set_property;
+ object_class->get_property = block_map_get_property;
+
+ widget_class->realize = block_map_realize;
+ widget_class->query_tooltip = block_map_query_tooltip;
+ widget_class->expose_event = block_map_expose;
+ widget_class->button_press_event = block_map_button_press;
+ widget_class->motion_notify_event = block_map_motion_notify;
+ widget_class->leave_notify_event = block_map_leave_notify;
+
+ g_object_class_install_property (object_class,
+ PROP_BLOCKS,
+ g_param_spec_pointer ("blocks",
+ _("blocks"),
+ _("Allocated blocks"),
+ G_PARAM_STATIC_NICK |
+ G_PARAM_STATIC_BLURB |
+ G_PARAM_STATIC_NAME |
+ G_PARAM_READWRITE));
+
+ g_object_class_install_property (object_class,
+ PROP_SELECTION,
+ g_param_spec_pointer ("selection",
+ _("selection"),
+ _("Selected blocks"),
+ G_PARAM_STATIC_NICK |
+ G_PARAM_STATIC_BLURB |
+ G_PARAM_STATIC_NAME |
+ G_PARAM_READABLE));
+}
+
+static void
+block_map_init (BlockMap *self)
+{
+ gtk_widget_set_size_request (&self->widget, 256, 256);
+ gtk_widget_set_has_tooltip (&self->widget, TRUE);
+ self->mouse_over_cell = -1;
+}
+
+GtkWidget *
+block_map_new (void)
+{
+ return g_object_new (block_map_get_type(), NULL);
+}
+
+void
+block_map_set_highlight (BlockMap *block_map, GSList *blocks)
+{
+ g_slist_free (block_map->highlight);
+ block_map->highlight = g_slist_copy (blocks);
+
+ gtk_widget_queue_draw (&block_map->widget);
+}
diff --git a/src/callgraph-store.c b/src/callgraph-store.c
new file mode 100644
index 0000000..4e4bea2
--- /dev/null
+++ b/src/callgraph-store.c
@@ -0,0 +1,714 @@
+#include <gtk/gtk.h>
+#include <math.h>
+#include <unistd.h>
+#include <string.h>
+#include <stdlib.h>
+
+#include "memfault.h"
+#include "callgraph.h"
+
+#define _(x) x
+
+/* TODO: autoconf + GPL */
+
+/* TODO: unique frames
+ * do a pass with n^parent_ip^child_ip to count number of Frames and
+ * preallocate. Then use a UniqueFrame for tails.
+ */
+
+/*
+ * TODO: Timeline
+ * selection and reconstruct using Events
+ */
+
+/* TODO: CallGraphView ring.
+ */
+
+static void
+call_graph_store_tree_model_init (GtkTreeModelIface *iface);
+
+G_DEFINE_TYPE_WITH_CODE (CallGraphStore, call_graph_store, G_TYPE_OBJECT,
+ G_IMPLEMENT_INTERFACE (GTK_TYPE_TREE_MODEL,
+ call_graph_store_tree_model_init))
+
+#define HAS_BTREE(ptr) (GPOINTER_TO_UINT (ptr) & 0x1)
+#define GET_BTREE(ptr) ((GTree *) (GPOINTER_TO_UINT (ptr) & ~ 0x1))
+#define SET_BTREE(ptr) ((gpointer) (GPOINTER_TO_UINT (ptr) | 0x1))
+
+enum {
+ FILTERED,
+ LAST_SIGNAL
+};
+
+static guint signals[LAST_SIGNAL];
+
+static void
+_call_graph_frame_destroy (gpointer data)
+{
+ CallGraphFrame *frame = data;
+ guint n;
+
+ for (n = 0; n < frame->n_children; n++)
+ _call_graph_frame_destroy (frame->children[n]);
+
+ g_slice_free (CallGraphFrame, frame);
+}
+
+static void
+_call_graph_frame_accum_size_allocs (CallGraphStore *store, CallGraphFrame *frame, Allocator *A)
+{
+ guint n;
+ for (n = 0; n < G_N_ELEMENTS (frame->size_allocs); n++) {
+ frame->size_allocs[n] += A->time_tail->size_allocs[n];
+ if (A->time_tail->size_allocs[n] && n > store->max_size_alloc)
+ store->max_size_alloc = n + 1;
+ }
+}
+
+static void
+_call_graph_frame_accumulate (CallGraphStore *store, CallGraphFrame *frame, Allocator *A)
+{
+ frame->bytes += A->time_tail->bytes;
+ frame->allocs += A->time_tail->n_allocs;
+ frame->frees += A->time_tail->n_frees;
+ _call_graph_frame_accum_size_allocs (store, frame, A);
+}
+
+
+static gint
+_call_graph_frame_tree_cmp (gconstpointer A, gconstpointer B)
+{
+ const char *a = A, *b = B;
+ return a - b;
+}
+
+static CallGraphFrame *
+_call_graph_frame_new (CallGraphStore *store,
+ CallGraphFrame *parent,
+ guint ip,
+ const gchar *name)
+{
+ CallGraphFrame *frame;
+
+ frame = g_slice_new0 (CallGraphFrame);
+ frame->ip = ip;
+ frame->frame = name;
+ frame->parent = parent;
+
+ if (parent != NULL) {
+ void *ptr = parent->filter;
+ if (ptr == NULL) {
+ parent->filter = (CallGraphFrame **) frame;
+ } else {
+ if (! HAS_BTREE (ptr)) {
+ CallGraphFrame *child = ptr;
+ ptr = g_tree_new (_call_graph_frame_tree_cmp);
+ g_tree_insert (ptr, GUINT_TO_POINTER (child->ip), child);
+ parent->filter = SET_BTREE (ptr);
+ } else
+ ptr = GET_BTREE (ptr);
+
+ g_tree_insert (ptr, GUINT_TO_POINTER (frame->ip), frame);
+ }
+ }
+
+ if (ip) {
+ GSList *frames, *l;
+
+ frames = g_hash_table_lookup (store->frames, GUINT_TO_POINTER (ip));
+ l = store->frames_mem_next++;
+ l->next = frames;
+ l->data = frame;
+ frames = l;
+ g_hash_table_replace (store->frames, GUINT_TO_POINTER (ip), frames);
+ }
+
+ return frame;
+}
+
+static gint
+_call_graph_frame_cmp (gconstpointer A, gconstpointer B)
+{
+ const CallGraphFrame * const *aa = A, * const *bb = B;
+ const CallGraphFrame *a = *aa, *b = *bb;
+ gint cmp;
+
+ cmp = b->allocs - a->allocs;
+ if (cmp)
+ return cmp;
+
+ cmp = b->frees - a->frees;
+ if (cmp)
+ return cmp;
+
+ return b->bytes - a->bytes;
+}
+
+static void
+call_graph_store_finalize (GObject *obj)
+{
+ CallGraphStore *self = (CallGraphStore *) obj;
+
+ _call_graph_frame_destroy (self->root);
+ g_free (self->nodes);
+
+ g_hash_table_destroy (self->frames);
+ g_free (self->frames_mem);
+
+ G_OBJECT_CLASS (call_graph_store_parent_class)->finalize (obj);
+}
+
+static void
+call_graph_store_class_init (CallGraphStoreClass *klass)
+{
+ GObjectClass *object_class = (GObjectClass *) klass;
+
+ object_class->finalize = call_graph_store_finalize;
+
+ signals[FILTERED] = g_signal_new ("filtered",
+ G_OBJECT_CLASS_TYPE (klass),
+ G_SIGNAL_ACTION,
+ 0,
+ NULL, NULL,
+ g_cclosure_marshal_VOID__VOID,
+ G_TYPE_NONE, 0);
+}
+
+
+/* GtkTreeModelIface */
+
+static GtkTreeModelFlags
+call_graph_store_get_flags (GtkTreeModel *tree_model)
+{
+ return GTK_TREE_MODEL_ITERS_PERSIST;
+}
+
+static gint
+call_graph_store_get_n_columns (GtkTreeModel *tree_model)
+{
+ return CG_N_COLUMNS;
+}
+
+static GType
+call_graph_store_get_column_type (GtkTreeModel *tree_model,
+ gint index)
+{
+ switch (index) {
+ case CG_FRAME: return G_TYPE_STRING;
+ case CG_BYTES: return G_TYPE_UINT64;
+ case CG_ALLOCS: return G_TYPE_UINT;
+ case CG_FREES: return G_TYPE_UINT;
+ default: return G_TYPE_INVALID;
+ }
+}
+
+static gboolean
+call_graph_store_get_iter (GtkTreeModel *tree_model,
+ GtkTreeIter *iter,
+ GtkTreePath *path)
+{
+ CallGraphStore *self = (CallGraphStore *) tree_model;
+ CallGraphFrame *frame;
+ gint *i, n, depth;
+
+ depth = gtk_tree_path_get_depth (path);
+ i = gtk_tree_path_get_indices (path);
+ frame = self->root;
+ for (n = 1; n < depth; n++) {
+ if ((guint) i[n] >= frame->n_filter)
+ return FALSE;
+ frame = frame->filter[i[n]];
+ }
+
+ iter->stamp = 1;
+ iter->user_data = frame;
+
+ return TRUE;
+}
+
+static GtkTreePath *
+call_graph_store_get_path (GtkTreeModel *tree_model,
+ GtkTreeIter *iter)
+{
+ CallGraphFrame *frame, *parent;
+ GtkTreePath *path;
+
+ path = gtk_tree_path_new ();
+
+ frame = iter->user_data;
+ while ((parent = frame->filter_parent) != NULL) {
+ gtk_tree_path_prepend_index (path, frame->index);
+ frame = parent;
+ }
+ gtk_tree_path_prepend_index (path, 0);
+
+ return path;
+}
+
+static void
+call_graph_store_get_value (GtkTreeModel *tree_model,
+ GtkTreeIter *iter,
+ gint column,
+ GValue *value)
+{
+ CallGraphFrame *frame = iter->user_data;
+
+ switch (column) {
+ case CG_FRAME:
+ g_value_init (value, G_TYPE_STRING);
+ g_value_set_string (value, frame->frame);
+ break;
+
+ case CG_BYTES:
+ g_value_init (value, G_TYPE_UINT64);
+ g_value_set_uint64 (value, frame->bytes);
+ break;
+
+ case CG_ALLOCS:
+ g_value_init (value, G_TYPE_UINT);
+ g_value_set_uint (value, frame->allocs);
+ break;
+
+ case CG_FREES:
+ g_value_init (value, G_TYPE_UINT);
+ g_value_set_uint (value, frame->frees);
+ break;
+ }
+}
+
+static gboolean
+call_graph_store_iter_next (GtkTreeModel *tree_model,
+ GtkTreeIter *iter)
+{
+ CallGraphFrame *frame = iter->user_data, *parent;
+ guint index;
+
+ parent = frame->filter_parent;
+ if (parent == NULL)
+ return FALSE;
+
+ index = frame->index;
+ if (index >= parent->n_filter - 1)
+ return FALSE;
+
+ iter->user_data = parent->filter[index + 1];
+ return TRUE;
+}
+
+static gboolean
+call_graph_store_iter_children (GtkTreeModel *tree_model,
+ GtkTreeIter *iter,
+ GtkTreeIter *parent)
+{
+ CallGraphStore *self = (CallGraphStore *) tree_model;
+
+ iter->stamp = 1;
+ if (parent) {
+ CallGraphFrame *frame = parent->user_data;
+ if (frame->n_filter == 0)
+ return FALSE;
+
+ iter->user_data = frame->filter[0];
+ } else
+ iter->user_data = self->root;
+
+ return TRUE;
+}
+
+static gboolean
+call_graph_store_iter_has_child (GtkTreeModel *tree_model,
+ GtkTreeIter *iter)
+{
+ CallGraphFrame *frame = iter->user_data;
+ return frame->n_filter;
+}
+
+static gint
+call_graph_store_iter_n_children (GtkTreeModel *tree_model,
+ GtkTreeIter *iter)
+{
+ CallGraphFrame *frame;
+
+ if (iter == NULL)
+ return 1;
+
+ frame = iter->user_data;
+ return frame->n_filter;
+}
+
+static gboolean
+call_graph_store_iter_nth_child (GtkTreeModel *tree_model,
+ GtkTreeIter *iter,
+ GtkTreeIter *parent,
+ gint n)
+{
+ CallGraphStore *self = (CallGraphStore *) tree_model;
+ CallGraphFrame *frame;
+
+ if (parent == NULL) {
+ if (n >= 1)
+ return FALSE;
+ iter->stamp = 1;
+ iter->user_data = self->root;
+ return TRUE;
+ }
+
+ frame = parent->user_data;
+ if ((guint) n >= frame->n_filter)
+ return FALSE;
+
+ iter->stamp = 1;
+ iter->user_data = frame->filter[n];
+ return TRUE;
+}
+
+static gboolean
+call_graph_store_iter_parent (GtkTreeModel *tree_model,
+ GtkTreeIter *iter,
+ GtkTreeIter *child)
+{
+ CallGraphFrame *frame = child->user_data;
+
+ frame = frame->filter_parent;
+ if (frame == NULL)
+ return FALSE;
+
+ iter->stamp = 1;
+ iter->user_data = frame;
+ return TRUE;
+}
+
+static void
+call_graph_store_tree_model_init (GtkTreeModelIface *iface)
+{
+ iface->get_flags = call_graph_store_get_flags;
+ iface->get_n_columns = call_graph_store_get_n_columns;
+ iface->get_column_type = call_graph_store_get_column_type;
+ iface->get_iter = call_graph_store_get_iter;
+ iface->get_path = call_graph_store_get_path;
+ iface->get_value = call_graph_store_get_value;
+ iface->iter_next = call_graph_store_iter_next;
+ iface->iter_children = call_graph_store_iter_children;
+ iface->iter_has_child = call_graph_store_iter_has_child;
+ iface->iter_n_children = call_graph_store_iter_n_children;
+ iface->iter_nth_child = call_graph_store_iter_nth_child;
+ iface->iter_parent = call_graph_store_iter_parent;
+}
+
+static void
+call_graph_store_init (CallGraphStore *self)
+{
+ self->frames = g_hash_table_new (NULL, NULL);
+ self->root = _call_graph_frame_new (self, NULL, 0, "Everything");
+ self->root->is_alloc_fn = TRUE;
+}
+
+static void
+_call_graph_frame_add_to_filter (CallGraphFrame *frame, CallGraphFrame *parent)
+{
+ if (frame->is_alloc_fn) {
+ guint n;
+ for (n = 0; n < frame->n_children; n++) {
+ _call_graph_frame_add_to_filter (frame->children[n], parent);
+ }
+ } else {
+ parent->filter[parent->n_filter++] = frame;
+ }
+}
+
+static void
+_call_graph_frame_filter (CallGraphStore *store, CallGraphFrame *frame, App *app)
+{
+ guint n;
+
+ if (frame->ip != 0) /* protect the root from malicious users */
+ frame->is_alloc_fn = frame->parent->is_alloc_fn &&
+ app_is_alloc_fn (app, frame->ip);
+
+ if (frame->n_children == 0)
+ return;
+
+ for (n = 0; n < frame->n_children; n++)
+ _call_graph_frame_filter (store, frame->children[n], app);
+
+ frame->n_filter = 0;
+ if (frame->parent == NULL || ! frame->is_alloc_fn) {
+ for (n = 0; n < frame->n_children; n++)
+ _call_graph_frame_add_to_filter (frame->children[n], frame);
+
+ qsort (frame->filter,
+ frame->n_filter, sizeof (CallGraphFrame *),
+ _call_graph_frame_cmp);
+ for (n = 0; n < frame->n_filter; n++)
+ frame->filter[n]->index = n;
+ }
+}
+
+static void
+_call_graph_frame_clear_alloc (CallGraphFrame *frame)
+{
+ guint n;
+
+ if (! frame->is_alloc_fn)
+ return;
+
+ for (n = 0; n < frame->n_children; n++)
+ _call_graph_frame_clear_alloc (frame->children[n]);
+}
+
+static void
+_call_graph_frame_is_alloc (CallGraphFrame *frame, App *app)
+{
+ guint n;
+ gboolean was_alloc_fn = frame->is_alloc_fn;
+
+ frame->is_alloc_fn = app_is_alloc_fn (app, frame->ip);
+
+ if (frame->is_alloc_fn) {
+ for (n = 0; n < frame->n_children; n++)
+ _call_graph_frame_is_alloc (frame->children[n], app);
+ } else if (was_alloc_fn) {
+ for (n = 0; n < frame->n_children; n++)
+ _call_graph_frame_clear_alloc (frame->children[n]);
+ }
+}
+
+static void
+_call_graph_frame_add_children (CallGraphFrame *frame, CallGraphFrame *parent)
+{
+ guint n;
+
+ frame->filter_parent = parent;
+
+ frame->filter = frame->children;
+ frame->n_filter = frame->n_children;
+
+ qsort (frame->filter,
+ frame->n_filter, sizeof (CallGraphFrame *),
+ _call_graph_frame_cmp);
+ for (n = 0; n < frame->n_filter; n++) {
+ frame->filter[n]->index = n;
+ _call_graph_frame_add_children (frame->filter[n], frame);
+ }
+}
+static void
+_call_graph_frame_add_filtered (CallGraphFrame *frame, CallGraphFrame *parent)
+{
+ guint n;
+
+ if (frame->is_alloc_fn) {
+ for (n = 0; n < frame->n_children; n++)
+ _call_graph_frame_add_filtered (frame->children[n], parent);
+ } else {
+ parent->filter[parent->n_filter++] = frame;
+ _call_graph_frame_add_children (frame, parent);
+ }
+}
+
+
+struct _call_graph_frame_foreach {
+ GFunc func;
+ gpointer data;
+};
+
+static gboolean
+_call_graph_frame_foreach_internal (gpointer key, gpointer value, gpointer data)
+{
+ CallGraphFrame *frame = value;
+ struct _call_graph_frame_foreach *arg = data;
+
+ arg->func (frame, arg->data);
+ if (frame->filter != NULL) {
+ if (HAS_BTREE (frame->filter))
+ g_tree_foreach (GET_BTREE (frame->filter),
+ _call_graph_frame_foreach_internal, data);
+ else
+ _call_graph_frame_foreach_internal (NULL, frame->filter, data);
+ }
+
+ return FALSE;
+}
+
+static void
+_call_graph_frame_foreach (CallGraphFrame *frame, GFunc func, gpointer data)
+{
+ struct _call_graph_frame_foreach arg;
+
+ arg.func = func;
+ arg.data = data;
+
+ func (frame, data);
+ if (frame->filter != NULL) {
+ if (HAS_BTREE (frame->filter))
+ g_tree_foreach (GET_BTREE (frame->filter),
+ _call_graph_frame_foreach_internal, &arg);
+ else
+ _call_graph_frame_foreach_internal (NULL, frame->filter, data);
+ }
+}
+
+static void
+count_nodes (gpointer _frame, gpointer _data)
+{
+ CallGraphFrame *frame = _frame;
+ guint *count = _data;
+ if (frame->filter != NULL) {
+ if (HAS_BTREE (frame->filter))
+ *count += g_tree_nnodes (GET_BTREE (frame->filter));
+ else
+ *count += 1;
+ }
+}
+
+static void
+add_nodes (gpointer _frame, gpointer _data)
+{
+ CallGraphFrame *frame = _frame;
+ CallGraphFrame ***children = _data;
+
+ if (frame->filter != NULL) {
+ guint n;
+ if (HAS_BTREE (frame->filter))
+ n = g_tree_nnodes (GET_BTREE (frame->filter));
+ else
+ n = 1;
+
+ frame->children = *children;
+ *children += n;
+ }
+
+ if (frame->parent != NULL)
+ frame->parent->children[frame->parent->n_children++] = frame;
+}
+
+static gboolean
+_call_graph_frame_destroy_trees_internal (gpointer key, gpointer value, gpointer data)
+{
+ CallGraphFrame *frame = value;
+
+ if (frame->filter != NULL) {
+ if (HAS_BTREE (frame->filter)) {
+ g_tree_foreach (GET_BTREE (frame->filter),
+ _call_graph_frame_destroy_trees_internal, NULL);
+ g_tree_destroy (GET_BTREE (frame->filter));
+ } else
+ _call_graph_frame_destroy_trees_internal (NULL, frame->filter,NULL);
+ frame->filter = NULL;
+ }
+
+ return FALSE;
+}
+
+static void
+_call_graph_frame_destroy_trees (CallGraphFrame *frame)
+{
+ if (frame->filter != NULL) {
+ if (HAS_BTREE (frame->filter)) {
+ g_tree_foreach (GET_BTREE (frame->filter),
+ _call_graph_frame_destroy_trees_internal, NULL);
+ g_tree_destroy (GET_BTREE (frame->filter));
+ } else
+ _call_graph_frame_destroy_trees_internal (NULL, frame->filter,NULL);
+ frame->filter = NULL;
+ }
+}
+
+CallGraphStore *
+call_graph_store_new (App *app, Allocator *allocators)
+{
+ CallGraphStore *store;
+ Allocator *A;
+ CallGraphFrame *frame, *child, **children;
+ guint n;
+
+ store = g_object_new (call_graph_store_get_type (), NULL);
+
+ n = 0;
+ for (A = allocators; A != NULL; A = A->next)
+ n += A->n_frames;
+ store->frames_mem = g_new (GSList, n);
+ store->frames_mem_next = store->frames_mem;
+
+ for (A = allocators; A != NULL; A = A->next) {
+ frame = store->root;
+ _call_graph_frame_accumulate (store, frame, A);
+ for (n = 0; n < A->n_frames; n++) {
+ child = NULL;
+ if (frame->filter != NULL) {
+ if (HAS_BTREE (frame->filter)) {
+ child = g_tree_lookup (GET_BTREE (frame->filter),
+ GUINT_TO_POINTER (A->ips[n]));
+ } else {
+ child = (CallGraphFrame *) frame->filter;
+ if (child->ip != A->ips[n])
+ child = NULL;
+ }
+ }
+ if (child == NULL)
+ child = _call_graph_frame_new (store,
+ frame,
+ A->ips[n],
+ A->functions_srcloc[n]);
+ frame = child;
+ _call_graph_frame_accumulate (store, frame, A);
+ }
+ }
+
+ n = 0;
+ _call_graph_frame_foreach (store->root, count_nodes, &n);
+ store->n_frames = n;
+
+ store->nodes = g_new (CallGraphFrame *, 2*n);
+
+ children = store->nodes;
+ _call_graph_frame_foreach (store->root, add_nodes, &children);
+ g_assert (children == store->nodes + n);
+
+ _call_graph_frame_destroy_trees (store->root);
+
+ call_graph_store_filter (store, app);
+
+ return store;
+}
+
+void
+call_graph_store_filter (CallGraphStore *store, App *app)
+{
+ CallGraphFrame *root;
+ guint serial = app_get_alloc_fns_serial (app);
+ guint n;
+
+ if (store->alloc_fns_serial == serial)
+ return;
+
+ root = store->root;
+ for (n = 0; n < root->n_children; n++)
+ _call_graph_frame_is_alloc (root->children[n], app);
+
+ root->n_filter = 0;
+ root->filter = store->nodes + store->n_frames;
+ for (n = 0; n < root->n_children; n++)
+ _call_graph_frame_add_filtered (root->children[n], root);
+
+ qsort (root->filter,
+ root->n_filter, sizeof (CallGraphFrame *),
+ _call_graph_frame_cmp);
+
+ for (n = 0; n < root->n_filter; n++)
+ root->filter[n]->index = n;
+
+ g_signal_emit (store, signals[FILTERED], 0);
+
+ store->alloc_fns_serial = serial;
+}
+
+gboolean
+call_graph_frame_get_iter (CallGraphFrame *frame, GtkTreeIter *iter)
+{
+ if (frame->is_alloc_fn)
+ return FALSE;
+
+ iter->stamp = 1;
+ iter->user_data = frame;
+ return TRUE;
+}
diff --git a/src/callgraph-treemap.c b/src/callgraph-treemap.c
new file mode 100644
index 0000000..95abb5c
--- /dev/null
+++ b/src/callgraph-treemap.c
@@ -0,0 +1,954 @@
+#include <gtk/gtk.h>
+#include <math.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "memfault.h"
+#include "callgraph.h"
+
+#define _(x) x
+
+#define BORDER 5
+
+typedef struct _call_graph_tree_map CallGraphTreeMap;
+typedef struct _call_graph_tree_map_layout CallGraphTreeMapLayout;
+
+typedef struct _point {
+ double x,y;
+} point_t;
+
+struct _call_graph_tree_map_layout {
+ gdouble rgb[3];
+ guint allocs;
+ GdkRectangle extents;
+ CallGraphFrame *frame;
+ GArray *children;
+};
+
+struct _call_graph_tree_map {
+ GtkWidget widget;
+
+ CallGraphStore *model;
+
+ CallGraphTreeMapLayout layout;
+ CallGraphTreeMapLayout *highlight;
+};
+typedef struct _call_graph_tree_map_class {
+ GtkWidgetClass parent_class;
+} CallGraphTreeMapClass;
+
+enum {
+ SELECTED,
+ LAST_SIGNAL
+};
+
+static guint signals[LAST_SIGNAL];
+
+
+static gboolean
+call_graph_tree_map_layout_draw_children (CallGraphTreeMap *self,
+ CallGraphTreeMapLayout *layout,
+ cairo_t *cr);
+
+static CallGraphTreeMapLayout *
+call_graph_tree_map_find_layout_for_cursor (CallGraphTreeMap *self,
+ gint x, gint y);
+
+
+G_DEFINE_TYPE (CallGraphTreeMap, call_graph_tree_map, GTK_TYPE_WIDGET)
+
+enum {
+ PROP_0,
+ PROP_MODEL
+};
+
+static void
+hsv_to_rgb (gdouble h, gdouble s, gdouble v, gdouble *rgb)
+{
+ gdouble m, n, f;
+ gint i;
+
+ while (h < 0)
+ h += 6.;
+ while (h > 6.)
+ h -= 6.;
+
+ if (s < 0.)
+ s = 0.;
+ if (s > 1.)
+ s = 1.;
+
+ if (v < 0.)
+ v = 0.;
+ if (v > 1.)
+ v = 1.;
+
+ i = floor (h);
+ f = h - i;
+ if ((i & 1) == 0)
+ f = 1 - f;
+
+ m = v * (1 - s);
+ n = v * (1 - s * f);
+ switch(i){
+ case 6:
+ case 0: rgb[0] = v; rgb[1] = n; rgb[2] = m; break;
+ case 1: rgb[0] = n; rgb[1] = v; rgb[2] = m; break;
+ case 2: rgb[0] = m; rgb[1] = v; rgb[2] = n; break;
+ case 3: rgb[0] = m; rgb[1] = n; rgb[2] = v; break;
+ case 4: rgb[0] = n; rgb[1] = m; rgb[2] = v; break;
+ case 5: rgb[0] = v; rgb[1] = m; rgb[2] = n; break;
+ }
+}
+
+static void
+call_graph_tree_map_layout_subdivide (CallGraphTreeMap *self, CallGraphTreeMapLayout *layout, GdkRectangle *rect, CallGraphFrame *frame)
+{
+ guint n, target, total, last, len;
+ GdkRectangle left, r;
+ gdouble fraction, left_fraction, aspect;
+ guint allocs, m, nn;
+ CallGraphFrame *f;
+ gboolean vertical;
+
+ layout->extents.x = rect->x + BORDER;
+ layout->extents.y = rect->y + BORDER;
+ layout->extents.width = rect->width - 2*BORDER;
+ layout->extents.height = rect->height - 2*BORDER;
+
+ layout->frame = frame;
+ layout->children = NULL;
+
+ if (layout->extents.width < BORDER || layout->extents.height < BORDER)
+ return;
+
+ if (frame->n_filter == 0)
+ return;
+
+ target = 19 * frame->allocs / 20;
+ len = frame->n_filter;
+ total = 0;
+ last = 0;
+
+ for (n = 0; n < len; n++) {
+ f = frame->filter[n];
+ if (total > target && f->allocs < last)
+ break;
+ total += f->allocs;
+ last = f->allocs;
+ }
+
+ if (n > 1) {
+ len = n;
+ n = 0;
+
+ r = layout->extents;
+ do {
+ f = frame->filter[n];
+ allocs = f->allocs;
+ fraction = allocs / (gdouble) total;
+
+ if (r.width > r.height) {
+ left.height = r.height;
+ left.width = r.width * fraction + .5;
+ } else {
+ left.width = r.width;
+ left.height = r.height * fraction + .5;
+ }
+ if (left.width < 3*BORDER || left.height < 3*BORDER)
+ break;
+
+ aspect = left.width / (gdouble) left.height;
+ if (aspect < 1.)
+ aspect = 1. / aspect;
+
+ for (m = n + 1; m < len; m++) {
+ gdouble average_aspect;
+ guint left_allocs;
+
+ f = frame->filter[m];
+ fraction = (allocs + f->allocs) / (gdouble) total;
+
+ if (r.width > r.height) {
+ left.height = r.height;
+ left.width = r.width * fraction + .5;
+ } else {
+ left.width = r.width;
+ left.height = r.height * fraction + .5;
+ }
+ if (left.width < 3*BORDER || left.height < 3*BORDER)
+ break;
+
+ average_aspect = 0;
+ left_allocs = allocs + f->allocs;
+ for (nn = n; nn <= m; nn++) {
+ gint width, height;
+ gdouble a;
+
+ f = frame->filter[nn];
+
+ left_fraction = f->allocs / (gdouble) left_allocs;
+ left_allocs -= f->allocs;
+
+ if (r.width > r.height) {
+ width = left.width;
+ height = left.height * left_fraction + .5;
+ left.height -= height - BORDER;
+ } else {
+ height = left.height;
+ width = left.width * left_fraction + .5;
+ left.width -= width - BORDER;
+ }
+ if (width < 3*BORDER || height < 3*BORDER)
+ goto skip;
+
+ a = width / (gdouble) height;
+ if (a < 1.)
+ a = 1./ a;
+ average_aspect += a * a;
+ }
+ average_aspect /= m - n + 1;
+ average_aspect = sqrt (average_aspect);
+
+ if (average_aspect > aspect)
+ break;
+
+ allocs += f->allocs;
+ aspect = average_aspect;
+ }
+ skip:
+
+ total -= allocs;
+ if (r.width > r.height) {
+ gint dx = r.width * fraction + .5;
+ r.x += dx - BORDER;
+ r.width -= dx - BORDER;
+ } else {
+ gint dy = r.height * fraction + .5;
+ r.y += dy - BORDER;
+ r.height -= dy - BORDER;
+ }
+ n = m;
+ } while (n < len);
+ }
+
+ if (n > 1) {
+ layout->children = g_array_new (FALSE, FALSE,
+ sizeof (CallGraphTreeMapLayout));
+ len = n;
+ n = 0;
+
+ total = 0;
+ for (n = 0; n < len; n++) {
+ f = frame->filter[n];
+ total += f->allocs;
+ }
+
+ n = 0;
+ *rect = layout->extents;
+ do {
+ f = frame->filter[n];
+ allocs = f->allocs;
+ fraction = allocs / (gdouble) total;
+
+ if (rect->width > rect->height) {
+ left.height = rect->height;
+ left.width = rect->width * fraction + .5;
+ } else {
+ left.width = rect->width;
+ left.height = rect->height * fraction + .5;
+ }
+ if (left.width < 3*BORDER || left.height < 3*BORDER)
+ break;
+
+ aspect = left.width / (gdouble) left.height;
+ if (aspect < 1.)
+ aspect = 1. / aspect;
+
+ for (m = n + 1; m < len; m++) {
+ gdouble average_aspect;
+ guint left_allocs;
+
+ f = frame->filter[m];
+ fraction = (allocs + f->allocs) / (gdouble) total;
+
+ if (rect->width > rect->height) {
+ left.height = rect->height;
+ left.width = rect->width * fraction + .5;
+ } else {
+ left.width = rect->width;
+ left.height = rect->height * fraction + .5;
+ }
+ if (left.width < 3*BORDER || left.height < 3*BORDER)
+ break;
+
+ average_aspect = 0;
+ left_allocs = allocs + f->allocs;
+ for (nn = n; nn <= m; nn++) {
+ gint width, height;
+ gdouble a;
+
+ f = frame->filter[nn];
+
+ left_fraction = f->allocs / (gdouble) left_allocs;
+ left_allocs -= f->allocs;
+
+ if (rect->width > rect->height) {
+ width = left.width;
+ height = left.height * left_fraction + .5;
+ left.height -= height - BORDER;
+ } else {
+ height = left.height;
+ width = left.width * left_fraction + .5;
+ left.width -= width - BORDER;
+ }
+ if (width < 3*BORDER || height < 3*BORDER)
+ goto out;
+
+ a = width / (gdouble) height;
+ if (a < 1.)
+ a = 1./ a;
+ average_aspect += a * a;
+ }
+ average_aspect /= m - n + 1;
+ average_aspect = sqrt (average_aspect);
+
+ if (average_aspect > aspect)
+ break;
+
+ allocs += f->allocs;
+ aspect = average_aspect;
+ }
+out:
+
+ left.x = rect->x;
+ left.y = rect->y;
+ fraction = allocs / (gdouble) total;
+ total -= allocs;
+ if (rect->width > rect->height) {
+ gint dx = rect->width * fraction + .5;
+ left.width = dx;
+ left.height = rect->height;
+ rect->x += dx - BORDER;
+ rect->width -= dx - BORDER;
+ if (rect->width < 3*BORDER) {
+ left.width += rect->width - BORDER;
+ rect->width = 0;
+ }
+ vertical = TRUE;
+ } else {
+ gint dy = rect->height * fraction + .5;
+ left.width = rect->width;
+ left.height = dy;
+ rect->y += dy - BORDER;
+ rect->height -= dy - BORDER;
+ if (rect->height < 3*BORDER) {
+ left.height += rect->height - BORDER;
+ rect->height = 0;
+ }
+ vertical = FALSE;
+ }
+
+ do {
+ CallGraphTreeMapLayout child;
+ f = frame->filter[n];
+
+ left_fraction = f->allocs / (gdouble) allocs;
+ allocs -= f->allocs;
+
+ r.x = left.x;
+ r.y = left.y;
+ if (vertical) {
+ gint dy = left.height * left_fraction + .5;
+ r.width = left.width;
+ r.height = dy;
+ left.y += dy - BORDER;
+ left.height -= dy - BORDER;
+ } else {
+ gint dx = left.width * left_fraction + .5;
+ r.width = dx;
+ r.height = left.height;
+ left.x += dx - BORDER;
+ left.width -= dx - BORDER;
+ }
+
+ call_graph_tree_map_layout_subdivide (self, &child, &r, f);
+ g_array_append_val (layout->children, child);
+ } while (++n < m);
+ } while (n < len);
+
+ len = layout->children->len;
+ if (len < 2) {
+ g_array_free (layout->children, TRUE);
+ layout->children = NULL;
+ }
+ } else {
+ guint min_allocs = 19 * frame->allocs / 20;
+ while (frame->n_filter) {
+ CallGraphFrame *f = frame->filter[0];
+ GSList *frames;
+ gint sum;
+
+ if (f->allocs < min_allocs)
+ break;
+ frame = f;
+
+ if (strncmp (f->frame, "main ", 5) == 0)
+ break;
+
+ frames = g_hash_table_lookup (self->model->frames,
+ GUINT_TO_POINTER (f->ip));
+ sum = 20 * f->allocs / 19;
+ while (frames != NULL) {
+ CallGraphFrame *ff = frames->data;
+
+ sum -= ff->allocs;
+ if (sum <= 0)
+ break;
+
+ frames = g_slist_next (frames);
+ }
+ if (sum <= 0)
+ break;
+ }
+ layout->frame = frame;
+ }
+}
+
+static void
+call_graph_tree_map_layout_colour (CallGraphTreeMap *self, CallGraphTreeMapLayout *layout, gdouble h, gdouble s, gdouble v)
+{
+ hsv_to_rgb (h, s, v, layout->rgb);
+
+ if (layout->children != NULL) {
+ guint n, len = layout->children->len;
+ h += 3.;
+ s -= .05;
+ v += .05;
+ for (n = 0; n < len; n++) {
+ CallGraphTreeMapLayout *child = &g_array_index (layout->children,
+ CallGraphTreeMapLayout,
+ n);
+ h += 2.5 - 6. / len;
+ call_graph_tree_map_layout_colour (self, child, h, s, v);
+ }
+ }
+}
+
+static void
+call_graph_tree_map_layout (CallGraphTreeMap *self)
+{
+ GdkRectangle rect;
+
+ rect.x = -BORDER;
+ rect.y = -BORDER;
+ rect.width = self->widget.allocation.width + 2*BORDER;
+ rect.height = self->widget.allocation.height + 2*BORDER;
+
+ call_graph_tree_map_layout_subdivide (self,
+ &self->layout, &rect, self->model->root);
+
+ call_graph_tree_map_layout_colour (self, &self->layout, 3., .6, .6);
+}
+
+
+static void
+call_graph_tree_map_update_layout (CallGraphTreeMap *self)
+{
+ call_graph_tree_map_layout (self);
+
+ if (self->highlight != NULL) {
+ CallGraphTreeMapLayout *layout;
+ gint x, y;
+
+ gtk_widget_get_pointer (&self->widget, &x, &y);
+
+ layout = call_graph_tree_map_find_layout_for_cursor (self, x, y);
+ if (layout != NULL &&
+ (layout->frame == NULL || layout->frame->parent == NULL))
+ {
+ layout = NULL;
+ }
+ self->highlight = layout;
+
+ gtk_widget_trigger_tooltip_query (&self->widget);
+ }
+
+ gtk_widget_queue_draw (&self->widget);
+}
+
+static void
+call_graph_tree_map_set_model (CallGraphTreeMap *self, CallGraphStore *model)
+{
+ if (self->model != NULL) {
+ g_signal_handlers_disconnect_matched (self->model,
+ G_SIGNAL_MATCH_DATA,
+ 0, 0, NULL, NULL, self);
+ g_object_unref (self->model);
+ }
+ self->model = g_object_ref (model);
+ g_signal_connect_swapped (model, "filtered",
+ G_CALLBACK (call_graph_tree_map_update_layout),
+ self);
+
+ call_graph_tree_map_update_layout (self);
+}
+
+static void
+call_graph_tree_map_set_property (GObject *obj, guint id, const GValue *v, GParamSpec *spec)
+{
+ CallGraphTreeMap *self = (CallGraphTreeMap *) obj;
+ switch (id) {
+ case PROP_MODEL:
+ call_graph_tree_map_set_model (self, g_value_get_object (v));
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (obj, id, spec);
+ break;
+ }
+}
+
+static void
+call_graph_tree_map_get_property (GObject *obj, guint id, GValue *v, GParamSpec *spec)
+{
+ CallGraphTreeMap *self = (CallGraphTreeMap *) obj;
+ switch (id) {
+ case PROP_MODEL:
+ g_value_set_object (v, self->model);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (obj, id, spec);
+ break;
+ }
+}
+
+static void
+call_graph_tree_map_realize (GtkWidget *widget)
+{
+ GdkWindowAttr attributes;
+
+ GTK_WIDGET_SET_FLAGS (widget, GTK_REALIZED);
+
+ attributes.window_type = GDK_WINDOW_CHILD;
+ attributes.x = widget->allocation.x;
+ attributes.y = widget->allocation.y;
+ attributes.width = widget->allocation.width;
+ attributes.height = widget->allocation.height;
+ attributes.wclass = GDK_INPUT_OUTPUT;
+ attributes.visual = gtk_widget_get_visual (widget);
+ attributes.colormap = gtk_widget_get_colormap (widget);
+ attributes.event_mask = GDK_BUTTON_PRESS_MASK |
+ GDK_POINTER_MOTION_MASK |
+ GDK_ENTER_NOTIFY_MASK |
+ GDK_LEAVE_NOTIFY_MASK |
+ GDK_EXPOSURE_MASK;
+
+ widget->window = gdk_window_new (gtk_widget_get_parent_window (widget),
+ &attributes,
+ GDK_WA_X | GDK_WA_Y |
+ GDK_WA_VISUAL | GDK_WA_COLORMAP);
+ gdk_window_set_user_data (widget->window, widget);
+
+ widget->style = gtk_style_attach (widget->style, widget->window);
+ gtk_style_set_background (widget->style, widget->window, GTK_STATE_NORMAL);
+}
+
+static void
+call_graph_tree_map_size_allocate (GtkWidget *widget, GdkRectangle *allocation)
+{
+ CallGraphTreeMap *self = (CallGraphTreeMap *) widget;
+ GTK_WIDGET_CLASS (call_graph_tree_map_parent_class)->size_allocate (widget, allocation);
+
+ if (self->model != NULL) {
+ call_graph_tree_map_layout (self);
+ gtk_widget_queue_draw (widget);
+ }
+}
+
+static void
+call_graph_tree_map_draw_distribution (CallGraphTreeMap *self,
+ CallGraphTreeMapLayout *layout,
+ cairo_t *cr)
+{
+ CallGraphFrame *frame = layout->frame;
+ const gint width = self->model->max_size_alloc, height = 10;
+ gint n;
+ guint max_y;
+
+ if (layout->extents.width < width + BORDER ||
+ layout->extents.height < height + BORDER)
+ {
+ return;
+ }
+
+ max_y = 0;
+ for (n = 0; n < width; n++) {
+ if (frame->size_allocs[n] > max_y)
+ max_y = frame->size_allocs[n];
+ }
+
+ cairo_save (cr);
+ cairo_translate (cr,
+ layout->extents.x + layout->extents.width - width - BORDER,
+ layout->extents.y + layout->extents.height - height - BORDER);
+
+ cairo_save (cr);
+ cairo_scale (cr, 1., height / (gdouble) max_y);
+
+ for (n = 0; n < width; n++)
+ cairo_rectangle (cr, n, max_y, 1, 0. - frame->size_allocs[n]);
+ cairo_set_source_rgb (cr, 0.75, 0.75, 0.75);
+ cairo_fill (cr);
+
+ cairo_restore (cr);
+
+ cairo_set_line_width (cr, 1.0);
+ cairo_move_to (cr, 0., height - .5);
+ cairo_line_to (cr, width, height - .5);
+ cairo_set_source_rgb (cr, 0., 0., 0.);
+ cairo_stroke (cr);
+ cairo_restore (cr);
+}
+
+static void
+call_graph_tree_map_draw_label (CallGraphTreeMap *self,
+ CallGraphTreeMapLayout *layout,
+ cairo_t *cr)
+{
+ PangoContext *ctx;
+ PangoLayout *text;
+ PangoRectangle logical;
+ PangoFontDescription *merge, *desc;
+
+ ctx = gtk_widget_create_pango_context (&self->widget);
+ merge = pango_context_get_font_description (ctx);
+ desc = pango_font_description_new ();
+ pango_font_description_set_family (desc, "sans");
+ pango_font_description_set_size (desc, PANGO_SCALE * 8);
+ pango_font_description_set_weight (desc, PANGO_WEIGHT_NORMAL);
+ pango_font_description_merge (desc, merge, FALSE);
+ pango_context_set_font_description (ctx, desc);
+ //g_object_unref (desc);
+
+ text = pango_layout_new (ctx);
+ pango_layout_set_width (text, layout->extents.width - 2*BORDER);
+ pango_layout_set_text (text, layout->frame->frame, -1);
+ pango_layout_get_pixel_extents (text, NULL, &logical);
+
+ if (logical.width > layout->extents.width - 2*BORDER ||
+ logical.height > layout->extents.height)
+ {
+ pango_layout_set_ellipsize (text, PANGO_ELLIPSIZE_END);
+ pango_layout_get_pixel_extents (text, NULL, &logical);
+ }
+
+ if (logical.width <= layout->extents.width - 2*BORDER &&
+ logical.height <= layout->extents.height)
+ {
+ cairo_move_to (cr,
+ layout->extents.x +
+ .5 * layout->extents.width -
+ .5 * logical.width,
+ layout->extents.y +
+ .5 * layout->extents.height -
+ .5 * logical.height);
+ cairo_set_source_rgb (cr, 0., 0., 0.);
+ pango_cairo_show_layout (cr, text);
+ }
+
+ g_object_unref (text);
+ g_object_unref (ctx);
+}
+
+static void
+call_graph_tree_map_layout_draw (CallGraphTreeMap *self,
+ CallGraphTreeMapLayout *layout,
+ cairo_t *cr)
+{
+ cairo_set_source_rgb (cr, layout->rgb[0], layout->rgb[1], layout->rgb[2]);
+ gdk_cairo_rectangle (cr, &layout->extents);
+ cairo_fill (cr);
+
+ if (! call_graph_tree_map_layout_draw_children (self, layout, cr)) {
+ //call_graph_tree_map_draw_distribution (self, layout, cr);
+ call_graph_tree_map_draw_label (self, layout, cr);
+ }
+
+ if (self->highlight == layout)
+ cairo_set_source_rgb (cr, 1., 1., 0.);
+ else
+ cairo_set_source_rgba (cr, 0., 0., 0., .5);
+ gdk_cairo_rectangle (cr, &layout->extents);
+ cairo_stroke (cr);
+}
+
+static gboolean
+call_graph_tree_map_layout_draw_children (CallGraphTreeMap *self,
+ CallGraphTreeMapLayout *layout,
+ cairo_t *cr)
+{
+ if (layout->children != NULL) {
+ guint n;
+
+ for (n = 0; n < layout->children->len; n++) {
+ CallGraphTreeMapLayout *child = &g_array_index (layout->children,
+ CallGraphTreeMapLayout,
+ n);
+ call_graph_tree_map_layout_draw (self, child, cr);
+ }
+ }
+
+ return layout->children != NULL;
+}
+
+static gboolean
+call_graph_tree_map_expose (GtkWidget *widget, GdkEventExpose *ev)
+{
+ CallGraphTreeMap *self = (CallGraphTreeMap *) widget;
+ cairo_t *cr;
+
+ cr = gdk_cairo_create (widget->window);
+ gdk_cairo_region (cr, ev->region);
+ cairo_clip (cr);
+
+ call_graph_tree_map_layout_draw_children (self, &self->layout, cr);
+
+ cairo_destroy (cr);
+
+ return FALSE;
+}
+
+static CallGraphTreeMapLayout *
+call_graph_tree_map_find_layout_for_cursor (CallGraphTreeMap *self,
+ gint x, gint y)
+{
+ CallGraphTreeMapLayout *layout;
+ gboolean found;
+
+ layout = &self->layout;
+ do {
+ found = FALSE;
+ if (layout->children) {
+ guint n;
+ for (n = 0; n < layout->children->len; n++) {
+ CallGraphTreeMapLayout *child = &g_array_index (
+ layout->children,
+ CallGraphTreeMapLayout,
+ n);
+ if (x >= child->extents.x &&
+ x < child->extents.x + child->extents.width &&
+ y >= child->extents.y &&
+ y < child->extents.y + child->extents.height) {
+ found = TRUE;
+ layout = child;
+ break;
+ }
+ }
+ }
+ } while (found);
+
+ return layout;
+}
+
+static gboolean
+call_graph_tree_map_button_press (GtkWidget *widget, GdkEventButton *ev)
+{
+ CallGraphTreeMap *self = (CallGraphTreeMap *) widget;
+ CallGraphTreeMapLayout *layout;
+ GtkTreeIter iter;
+
+ if (ev->x < 0 || ev->x > widget->allocation.width)
+ return FALSE;
+ if (ev->y < 0 || ev->y > widget->allocation.height)
+ return FALSE;
+
+ layout = call_graph_tree_map_find_layout_for_cursor (self, ev->x, ev->y);
+ if (layout == NULL)
+ return FALSE;
+
+ if (call_graph_frame_get_iter (layout->frame, &iter)) {
+ g_signal_emit (widget, signals[SELECTED], 0, &iter);
+ }
+
+ return FALSE;
+}
+
+static gboolean
+call_graph_tree_map_query_tooltip (GtkWidget *widget,
+ gint x,
+ gint y,
+ gboolean keyboard_tip,
+ GtkTooltip *tooltip)
+{
+ CallGraphTreeMap *self = (CallGraphTreeMap *) widget;
+ CallGraphTreeMapLayout *layout;
+ CallGraphFrame *frame;
+ GSList *list = NULL, *l;
+ GString *string;
+ guint n;
+ gchar *text;
+ const gchar *last;
+ gchar bytes[160];
+ gchar blocks[80];
+
+ if (x < 0 || x > widget->allocation.width)
+ return FALSE;
+ if (y < 0 || y > widget->allocation.height)
+ return FALSE;
+
+ layout = call_graph_tree_map_find_layout_for_cursor (self, x, y);
+ if (layout == NULL)
+ return FALSE;
+
+ frame = layout->frame;
+ if (frame == NULL || frame->parent == NULL)
+ return FALSE;
+
+ n = g_snprintf (bytes + 80, 80, "%" G_GUINT64_FORMAT, frame->bytes);
+ pretty_print_number (bytes + 80, n, bytes);
+
+ n = g_snprintf (blocks + 40, 40, "%d", frame->allocs);
+ pretty_print_number (blocks + 40, n, blocks);
+
+ string = g_string_new ("");
+ g_string_append_printf (string,
+ "%s bytes over %s allocations at:",
+ bytes, blocks);
+
+ while (frame->parent != NULL) {
+ list = g_slist_prepend (list, frame);
+ frame = frame->parent;
+ }
+
+ n = 0;
+ last = NULL;
+ for (l = list; l != NULL; l = g_slist_next (l)) {
+ CallGraphFrame *frame = l->data;
+
+ if (last != NULL && strcmp (frame->frame, last) == 0)
+ break;
+
+ g_string_append_c (string, '\n');
+ g_string_append_c (string, '\t');
+ g_string_append (string, frame->frame);
+ last = frame->frame;
+
+ if (++n == 16)
+ break;
+ }
+ g_slist_free (list);
+
+ text = g_string_free (string, FALSE);
+ gtk_tooltip_set_text (tooltip, text);
+ g_free (text);
+
+ gtk_tooltip_set_tip_area (tooltip, &layout->extents);
+ return TRUE;
+}
+
+static gboolean
+call_graph_tree_map_enter_notify (GtkWidget *widget, GdkEventCrossing *ev)
+{
+ CallGraphTreeMap *self = (CallGraphTreeMap *) widget;
+ CallGraphTreeMapLayout *layout;
+
+ /* catch the spontaneous appearance of the cursor within a cell */
+ layout = call_graph_tree_map_find_layout_for_cursor (self, ev->x, ev->y);
+ if (layout != NULL &&
+ (layout->frame == NULL || layout->frame->parent == NULL))
+ layout = NULL;
+ if (layout != self->highlight) {
+ self->highlight = layout;
+ gtk_widget_queue_draw (widget);
+ }
+
+ return FALSE;
+}
+
+static gboolean
+call_graph_tree_map_leave_notify (GtkWidget *widget, GdkEventCrossing *ev)
+{
+ CallGraphTreeMap *self = (CallGraphTreeMap *) widget;
+
+ if (self->highlight != NULL) {
+ self->highlight = NULL;
+ gtk_widget_queue_draw (widget);
+ }
+
+ return FALSE;
+}
+
+static gboolean
+call_graph_tree_map_motion_notify (GtkWidget *widget, GdkEventMotion *ev)
+{
+ CallGraphTreeMap *self = (CallGraphTreeMap *) widget;
+ CallGraphTreeMapLayout *layout;
+
+ if (ev->x < 0 || ev->x > widget->allocation.width)
+ return FALSE;
+ if (ev->y < 0 || ev->y > widget->allocation.height)
+ return FALSE;
+
+ layout = call_graph_tree_map_find_layout_for_cursor (self, ev->x, ev->y);
+ if (layout != NULL &&
+ (layout->frame == NULL || layout->frame->parent == NULL))
+ layout = NULL;
+ if (layout != self->highlight) {
+ self->highlight = layout;
+ gtk_widget_queue_draw (widget);
+ }
+
+ return FALSE;
+}
+
+static void
+call_graph_tree_map_class_init (CallGraphTreeMapClass *klass)
+{
+ GObjectClass *object_class = (GObjectClass *) klass;
+ GtkWidgetClass *widget_class = (GtkWidgetClass *) klass;
+
+ object_class->set_property = call_graph_tree_map_set_property;
+ object_class->get_property = call_graph_tree_map_get_property;
+
+ widget_class->realize = call_graph_tree_map_realize;
+ widget_class->size_allocate = call_graph_tree_map_size_allocate;
+ widget_class->expose_event = call_graph_tree_map_expose;
+ widget_class->button_press_event = call_graph_tree_map_button_press;
+ widget_class->motion_notify_event = call_graph_tree_map_motion_notify;
+ widget_class->enter_notify_event = call_graph_tree_map_enter_notify;
+ widget_class->leave_notify_event = call_graph_tree_map_leave_notify;
+ widget_class->query_tooltip = call_graph_tree_map_query_tooltip;
+
+ g_object_class_install_property (object_class,
+ PROP_MODEL,
+ g_param_spec_object ("model",
+ _("model"),
+ _("Model"),
+ G_TYPE_OBJECT,
+ //call_graph_store_get_type (),
+ G_PARAM_STATIC_NICK |
+ G_PARAM_STATIC_BLURB |
+ G_PARAM_STATIC_NAME |
+ G_PARAM_READWRITE));
+
+ signals[SELECTED] = g_signal_new ("selected",
+ G_OBJECT_CLASS_TYPE (klass),
+ G_SIGNAL_ACTION,
+ 0,
+ NULL, NULL,
+ g_cclosure_marshal_VOID__BOXED,
+ G_TYPE_NONE, 1, GTK_TYPE_TREE_ITER);
+}
+
+static void
+call_graph_tree_map_init (CallGraphTreeMap *self)
+{
+ gtk_widget_set_has_tooltip (&self->widget, TRUE);
+ gtk_widget_set_size_request (&self->widget, 256, 256);
+}
+
+GtkWidget *
+call_graph_tree_map_new (void)
+{
+ return g_object_new (call_graph_tree_map_get_type(), NULL);
+}
diff --git a/src/callgraph.c b/src/callgraph.c
new file mode 100644
index 0000000..c6b00cd
--- /dev/null
+++ b/src/callgraph.c
@@ -0,0 +1,544 @@
+#include <gtk/gtk.h>
+#include <math.h>
+#include <unistd.h>
+#include <string.h>
+
+#include "memfault.h"
+#include "callgraph.h"
+
+#define _(x) x
+
+struct _call_graph {
+ GtkTreeView tv;
+};
+typedef struct _call_graph_class {
+ GtkTreeViewClass parent_class;
+} CallGraphClass;
+
+G_DEFINE_TYPE (CallGraph, call_graph, GTK_TYPE_TREE_VIEW)
+
+typedef struct _size_histogram {
+ GtkWidget widget;
+
+ guint *distribution;
+ guint count;
+ guint mean;
+} SizeHistogram;
+typedef struct _size_histogram_class {
+ GtkTreeViewClass parent_class;
+} SizeHistogramClass;
+
+G_DEFINE_TYPE (SizeHistogram, size_histogram, GTK_TYPE_WIDGET)
+
+enum {
+ PROP_0 = 0,
+ PROP_MODEL
+};
+
+static void
+call_graph_set_model (CallGraph *self, CallGraphStore *store)
+{
+ GtkTreeModel *model = (GtkTreeModel *) store;
+ GtkTreePath *path;
+ GtkTreeIter iter;
+
+ g_object_ref (model);
+ gtk_tree_view_set_model (&self->tv, NULL);
+ gtk_tree_view_set_model (&self->tv, model);
+
+ iter.user_data = store->root;
+ iter.stamp = 1;
+ path = gtk_tree_model_get_path (model, &iter);
+ gtk_tree_view_expand_to_path (&self->tv, path);
+
+ gtk_tree_path_free (path);
+ g_object_unref (model);
+}
+
+static void
+call_graph_set_property (GObject *obj, guint id, const GValue *v, GParamSpec *spec)
+{
+ CallGraph *self = (CallGraph *) obj;
+ switch (id) {
+ case PROP_MODEL:
+ call_graph_set_model (self, g_value_get_object (v));
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (obj, id, spec);
+ break;
+ }
+}
+
+static void
+call_graph_get_property (GObject *obj, guint id, GValue *v, GParamSpec *spec)
+{
+ CallGraph *self = (CallGraph *) obj;
+ switch (id) {
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (obj, id, spec);
+ break;
+ }
+}
+
+static gboolean
+size_histogram_expose (GtkWidget *widget, GdkEventExpose *ev)
+{
+ SizeHistogram *self = (SizeHistogram *) widget;
+ cairo_t *cr;
+ guint n;
+ guint max_y = 0, max_x = 0, total = 0, median;
+ PangoLayout *layout;
+ gchar *text;
+ PangoRectangle logical;
+ gint target;
+ double x, y;
+
+ for (n = 0; n < self->count; n++) {
+ if (self->distribution[n] > max_y)
+ max_y = self->distribution[n], max_x = n;
+ total += self->distribution[n];
+ }
+
+ target = total / 2;
+ for (n = 0; n < self->count; n++) {
+ target -= self->distribution[n];
+ if (target < 0)
+ break;
+ }
+ median = n;
+
+ cr = gdk_cairo_create (widget->window);
+ gdk_cairo_region (cr, ev->region);
+ cairo_clip (cr);
+
+ cairo_translate (cr, widget->allocation.x, widget->allocation.y);
+ cairo_save (cr);
+ cairo_scale (cr,
+ widget->allocation.width / (double) self->count,
+ widget->allocation.height / (double) max_y);
+ for (n = 0; n < self->count; n++)
+ cairo_rectangle (cr, n, max_y, 1, 0.-self->distribution[n]);
+ cairo_set_source_rgb (cr, 0.75, 0.75, 0.75);
+ cairo_fill (cr);
+
+ cairo_restore (cr);
+
+ text = g_strdup_printf ("Mode: %d bytes, %.1f%%",
+ 1 << max_x, max_y * 100. / total);
+ layout = gtk_widget_create_pango_layout (widget, text);
+ g_free (text);
+ cairo_move_to (cr, 4, 4);
+ pango_cairo_show_layout (cr, layout);
+
+ cairo_get_current_point (cr, &x, &y);
+ text = g_strdup_printf ("Median: %d bytes", 1 << median);
+ pango_layout_set_text (layout, text, -1);
+ pango_layout_get_pixel_extents (layout, NULL, &logical);
+ g_free (text);
+ cairo_move_to (cr, 4, y + logical.height + 4);
+ pango_cairo_show_layout (cr, layout);
+
+ cairo_get_current_point (cr, &x, &y);
+ text = g_strdup_printf ("Mean: %d bytes", self->mean);
+ pango_layout_set_text (layout, text, -1);
+ pango_layout_get_pixel_extents (layout, NULL, &logical);
+ g_free (text);
+ cairo_move_to (cr, 4, y + logical.height + 4);
+ pango_cairo_show_layout (cr, layout);
+
+ cairo_set_line_width (cr, 1.0);
+ for (n = 0; n < self->count; n += 5) {
+ text = g_strdup_printf ("%d", n);
+ pango_layout_set_text (layout, text, -1);
+ pango_layout_get_pixel_extents (layout, NULL, &logical);
+ g_free (text);
+
+ cairo_move_to (cr,
+ (n + .5) * widget->allocation.width / (double) self->count - logical.width / 2.,
+ widget->allocation.height - logical.height);
+ pango_cairo_show_layout (cr, layout);
+
+ cairo_move_to (cr,
+ (n + .5) * widget->allocation.width / (double) self->count,
+ widget->allocation.height - 2);
+ cairo_line_to (cr,
+ (n + .5) * widget->allocation.width / (double) self->count,
+ widget->allocation.height);
+ cairo_stroke (cr);
+ }
+
+ g_object_unref (layout);
+
+ cairo_rectangle (cr, 0, widget->allocation.height-1, widget->allocation.width, 1);
+ cairo_fill (cr);
+ cairo_destroy (cr);
+
+
+ return FALSE;
+}
+
+static void
+size_histogram_class_init (SizeHistogramClass *klass)
+{
+ GtkWidgetClass *widget_class = (GtkWidgetClass *) klass;
+
+ widget_class->expose_event = size_histogram_expose;
+}
+
+static void
+size_histogram_init (SizeHistogram *self)
+{
+ GTK_WIDGET_SET_FLAGS (self, GTK_NO_WINDOW);
+}
+
+static gboolean
+call_graph_query_tooltip (GtkWidget *widget,
+ gint x,
+ gint y,
+ gboolean keyboard_tip,
+ GtkTooltip *tooltip)
+{
+ CallGraph * self = (CallGraph *) widget;
+ GtkTreeModel *model = gtk_tree_view_get_model (&self->tv);
+ GtkTreePath *path;
+ GtkTreeViewColumn *column;
+ GtkTreeIter iter;
+ const gchar *title;
+ gint cell_x, cell_y;
+ CallGraphFrame *frame;
+ SizeHistogram *w;
+
+ gtk_tree_view_convert_widget_to_bin_window_coords (&self->tv, x, y, &x, &y);
+ if (! gtk_tree_view_get_path_at_pos (&self->tv, x, y,
+ &path, &column, &cell_x, &cell_y))
+ return FALSE;
+
+ gtk_tree_view_set_tooltip_cell (&self->tv, tooltip, path, column, NULL);
+
+ gtk_tree_model_get_iter (model, &iter, path);
+ frame = iter.user_data;
+ gtk_tree_path_free (path);
+
+ title = gtk_tree_view_column_get_title (column); /* XXX !!! */
+ if (strcmp (title, "Sizes") == 0) {
+ CallGraphStore *store = (CallGraphStore *) model;
+ w = g_object_new (size_histogram_get_type (), NULL, NULL);
+ gtk_widget_set_size_request (GTK_WIDGET (w), 180, 120);
+ w->distribution = frame->size_allocs;
+ w->count = store->max_size_alloc;
+ w->mean = frame->bytes / frame->allocs;
+ gtk_widget_show (GTK_WIDGET (w));
+ gtk_tooltip_set_custom (tooltip, GTK_WIDGET (w));
+ } else if (strcmp (title, "Frame") == 0) {
+ GString *string;
+ guint n = 0;
+ gchar *text;
+
+ if (frame->n_children == 0)
+ return FALSE;
+
+ frame = frame->children[0];
+
+ string = g_string_new ("Top allocation site ");
+ g_string_append_printf (string, "(%.0f%%):",
+ frame->allocs * 100. / frame->parent->allocs);
+ do {
+ if (strcmp (frame->frame, frame->parent->frame)) {
+ g_string_append_c (string, '\n');
+ g_string_append_c (string, '\t');
+ g_string_append (string, frame->frame);
+ if (++n == 8)
+ break;
+ }
+ frame = frame->children[0];
+ } while (frame->n_children);
+ text = g_string_free (string, FALSE);
+ gtk_tooltip_set_text (tooltip, text);
+ g_free (text);
+ } else
+ return FALSE;
+
+ return TRUE;
+}
+
+static void
+call_graph_class_init (CallGraphClass *klass)
+{
+ GObjectClass *object_class = (GObjectClass *) klass;
+ GtkWidgetClass *widget_class = (GtkWidgetClass *) klass;
+
+ object_class->set_property = call_graph_set_property;
+ object_class->get_property = call_graph_get_property;
+
+ widget_class->query_tooltip = call_graph_query_tooltip;
+
+ g_object_class_install_property (object_class,
+ PROP_MODEL,
+ g_param_spec_object ("model",
+ _("model"),
+ _("Model"),
+ GTK_TYPE_TREE_MODEL,
+ G_PARAM_STATIC_NICK |
+ G_PARAM_STATIC_BLURB |
+ G_PARAM_STATIC_NAME |
+ G_PARAM_WRITABLE));
+}
+
+typedef struct _CellRendererDistribution {
+ GtkCellRenderer renderer;
+
+ guint count;
+ guint *distribution;
+} CellRendererDistribution;
+typedef struct _CellRendererDistributionClass {
+ GtkCellRendererClass parent_class;
+} CellRendererDistributionClass;
+
+G_DEFINE_TYPE (CellRendererDistribution, cell_renderer_distribution, GTK_TYPE_CELL_RENDERER)
+
+static void
+cell_renderer_distribution_get_size (GtkCellRenderer *cell,
+ GtkWidget *widget,
+ GdkRectangle *rectangle,
+ gint *x_offset,
+ gint *y_offset,
+ gint *width,
+ gint *height)
+{
+ if (x_offset)
+ *x_offset = 0;
+ if (y_offset)
+ *y_offset = 0;
+ if (width)
+ *width = 32;
+ if (height)
+ *height = 20;
+}
+
+static void
+cell_renderer_distribution_render (GtkCellRenderer *cell,
+ GdkDrawable *window,
+ GtkWidget *widget,
+ GdkRectangle *background_area,
+ GdkRectangle *cell_area,
+ GdkRectangle *expose_area,
+ GtkCellRendererState flags)
+{
+ CellRendererDistribution *self = (CellRendererDistribution *) cell;
+ cairo_t *cr = gdk_cairo_create (window);
+ guint n;
+ guint max_y = 0;
+
+ for (n = 0; n < self->count; n++) {
+ if (self->distribution[n] > max_y)
+ max_y = self->distribution[n];
+ }
+
+ cairo_translate (cr, cell_area->x, cell_area->y);
+ cairo_save (cr);
+ cairo_scale (cr, 32. / self->count, 20. / max_y);
+ for (n = 0; n < self->count; n++)
+ cairo_rectangle (cr, n, max_y, 1, 0.-self->distribution[n]);
+ cairo_fill (cr);
+ cairo_restore (cr);
+
+ cairo_rectangle (cr, 0, 19, 32, 1);
+ cairo_set_source_rgba (cr, 0.4, 0.4, 0.4, .4);
+ cairo_fill (cr);
+ cairo_destroy (cr);
+}
+
+enum {
+ CR_PROP_0 = 0,
+ CR_PROP_COUNT,
+ CR_PROP_DISTRIBUTION,
+};
+
+static void
+cell_renderer_distribution_set_property (GObject *obj, guint id, const GValue *v, GParamSpec *spec)
+{
+ CellRendererDistribution *self = (CellRendererDistribution *) obj;
+ switch (id) {
+ case CR_PROP_COUNT:
+ self->count = g_value_get_uint (v);
+ break;
+ case CR_PROP_DISTRIBUTION:
+ self->distribution = g_value_get_pointer (v);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (obj, id, spec);
+ break;
+ }
+}
+
+static void
+cell_renderer_distribution_class_init (CellRendererDistributionClass *klass)
+{
+ GObjectClass *object_class = (GObjectClass *) klass;
+ GtkCellRendererClass *cell_class = (GtkCellRendererClass *) klass;
+
+ cell_class->get_size = cell_renderer_distribution_get_size;
+ cell_class->render = cell_renderer_distribution_render;
+
+ object_class->set_property = cell_renderer_distribution_set_property;
+
+ g_object_class_install_property (object_class,
+ CR_PROP_COUNT,
+ g_param_spec_uint ("count",
+ _("count"),
+ _("Count"),
+ 0, G_MAXUINT, 32,
+ G_PARAM_STATIC_NICK |
+ G_PARAM_STATIC_BLURB |
+ G_PARAM_STATIC_NAME |
+ G_PARAM_WRITABLE));
+ g_object_class_install_property (object_class,
+ CR_PROP_DISTRIBUTION,
+ g_param_spec_pointer ("distribution",
+ _("distribution"),
+ _("Distribution"),
+ G_PARAM_STATIC_NICK |
+ G_PARAM_STATIC_BLURB |
+ G_PARAM_STATIC_NAME |
+ G_PARAM_WRITABLE));
+}
+
+static void
+cell_renderer_distribution_init (CellRendererDistribution *self)
+{
+}
+
+
+static void
+render_size_distribution (GtkCellLayout *layout,
+ GtkCellRenderer *cell,
+ GtkTreeModel *model,
+ GtkTreeIter *iter,
+ gpointer data)
+{
+ CallGraphStore *store = (CallGraphStore *) model;
+ CallGraphFrame *frame = iter->user_data;
+ g_object_set (G_OBJECT (cell),
+ "count", store->max_size_alloc,
+ "distribution", frame->size_allocs,
+ NULL);
+}
+
+static gboolean
+call_graph_search_equal_func (GtkTreeModel *model,
+ gint column,
+ const gchar *key,
+ GtkTreeIter *iter,
+ gpointer data)
+{
+ CallGraphFrame *frame = iter->user_data;
+ return strncmp (frame->frame, key, strlen (key)) != 0;
+}
+
+static void
+pretty_print_uint (GtkCellLayout *layout,
+ GtkCellRenderer *cell,
+ GtkTreeModel *model,
+ GtkTreeIter *iter,
+ gpointer data)
+{
+ guint v;
+ char buf[80];
+ gint len;
+
+ gtk_tree_model_get (model, iter, GPOINTER_TO_UINT (data), &v, -1);
+
+ len = g_snprintf (buf + 40, 40, "%u", v);
+ pretty_print_number (buf + 40, len, buf);
+
+ g_object_set (G_OBJECT (cell), "text", buf, NULL);
+}
+
+static void
+pretty_print_uint64 (GtkCellLayout *layout,
+ GtkCellRenderer *cell,
+ GtkTreeModel *model,
+ GtkTreeIter *iter,
+ gpointer data)
+{
+ guint64 v;
+ char buf[160];
+ gint len;
+
+ gtk_tree_model_get (model, iter, GPOINTER_TO_UINT (data), &v, -1);
+
+ len = g_snprintf (buf + 80, 80, "%" G_GUINT64_FORMAT, v);
+ pretty_print_number (buf + 80, len, buf);
+
+ g_object_set (G_OBJECT (cell), "text", buf, NULL);
+}
+
+static void
+call_graph_init (CallGraph *self)
+{
+ GtkTreeStore *store;
+ GtkTreeViewColumn *column;
+ GtkCellRenderer *renderer;
+
+ renderer = gtk_cell_renderer_text_new ();
+ g_object_set (G_OBJECT (renderer),
+ "ellipsize", PANGO_ELLIPSIZE_END,
+ "ellipsize-set", TRUE,
+ "width-chars", 50,
+ NULL);
+ column = gtk_tree_view_column_new_with_attributes ("Frame",
+ renderer, "text", CG_FRAME, NULL);
+ gtk_tree_view_column_set_expand (column, TRUE);
+ gtk_tree_view_append_column (&self->tv, column);
+
+ renderer = gtk_cell_renderer_text_new ();
+ column = gtk_tree_view_column_new_with_attributes ("Allocs",
+ renderer, NULL);
+ gtk_cell_layout_set_cell_data_func (GTK_CELL_LAYOUT (column), renderer,
+ pretty_print_uint, GUINT_TO_POINTER (CG_ALLOCS), NULL);
+ gtk_tree_view_append_column (&self->tv, column);
+
+ renderer = gtk_cell_renderer_text_new ();
+ column = gtk_tree_view_column_new_with_attributes ("Frees",
+ renderer, NULL);
+ gtk_cell_layout_set_cell_data_func (GTK_CELL_LAYOUT (column), renderer,
+ pretty_print_uint, GUINT_TO_POINTER (CG_FREES), NULL);
+ gtk_tree_view_append_column (&self->tv, column);
+
+ renderer = gtk_cell_renderer_text_new ();
+ column = gtk_tree_view_column_new_with_attributes ("Bytes",
+ renderer, NULL);
+ gtk_cell_layout_set_cell_data_func (GTK_CELL_LAYOUT (column), renderer,
+ pretty_print_uint64, GUINT_TO_POINTER (CG_BYTES), NULL);
+ gtk_tree_view_append_column (&self->tv, column);
+
+ renderer = g_object_new (cell_renderer_distribution_get_type (), NULL);
+ column = gtk_tree_view_column_new_with_attributes ("Sizes",
+ renderer, NULL, NULL);
+ gtk_cell_layout_set_cell_data_func (GTK_CELL_LAYOUT (column), renderer,
+ render_size_distribution, NULL, NULL);
+ gtk_tree_view_append_column (&self->tv, column);
+
+ gtk_tree_view_set_search_column (&self->tv, CG_FRAME);
+ gtk_tree_view_set_enable_search (&self->tv, TRUE);
+ gtk_tree_view_set_search_equal_func (&self->tv,
+ call_graph_search_equal_func,
+ NULL, NULL);
+
+ gtk_tree_view_set_enable_tree_lines (&self->tv, TRUE);
+ gtk_tree_view_set_rules_hint (&self->tv, TRUE);
+
+ gtk_widget_set_has_tooltip (GTK_WIDGET (self), TRUE);
+
+ /* insert a dummy model */
+ store = gtk_tree_store_new (CG_N_COLUMNS,
+ G_TYPE_STRING, G_TYPE_UINT64, G_TYPE_UINT, G_TYPE_UINT);
+ gtk_tree_view_set_model (&self->tv, GTK_TREE_MODEL (store));
+ g_object_unref (store);
+}
+
+GtkWidget *
+call_graph_new (void)
+{
+ return g_object_new (call_graph_get_type (), NULL);
+}
diff --git a/src/callgraph.h b/src/callgraph.h
new file mode 100644
index 0000000..89563b0
--- /dev/null
+++ b/src/callgraph.h
@@ -0,0 +1,61 @@
+#ifndef CALLGRAPH_H
+#define CALLGRAPH_H
+
+struct _call_graph_frame {
+ guint ip;
+ const gchar *frame;
+ gboolean is_alloc_fn;
+ guint64 bytes;
+ guint allocs;
+ guint frees;
+ guint size_allocs[32];
+
+ CallGraphFrame *parent;
+ CallGraphFrame **children;
+ guint n_children;
+
+ guint index;
+ CallGraphFrame **filter;
+ guint n_filter;
+ CallGraphFrame *filter_parent;
+};
+
+struct _call_graph_store {
+ GObject object;
+
+ guint max_size_alloc;
+
+ CallGraphFrame *root;
+ GHashTable *frames;
+ GSList *frames_mem, *frames_mem_next;
+
+ guint alloc_fns_serial;
+
+ CallGraphFrame **nodes;
+ guint n_frames;
+};
+struct _call_graph_store_class {
+ GObjectClass parent_class;
+};
+
+enum {
+ CG_FRAME,
+ CG_BYTES,
+ CG_ALLOCS,
+ CG_FREES,
+ CG_N_COLUMNS,
+};
+
+CallGraphStore *
+call_graph_store_new (App *app, Allocator *allocators);
+
+void
+call_graph_store_sort (CallGraphStore *store);
+
+void
+call_graph_store_filter (CallGraphStore *store, App *app);
+
+gboolean
+call_graph_frame_get_iter (CallGraphFrame *frame, GtkTreeIter *iter);
+
+#endif /* CALLGRAPH_H */
diff --git a/src/frames.c b/src/frames.c
new file mode 100644
index 0000000..6f311cb
--- /dev/null
+++ b/src/frames.c
@@ -0,0 +1,230 @@
+#include "memfault.h"
+
+typedef struct _frame {
+ guint ip;
+ const gchar *function;
+ const gchar *object;
+ const gchar *file;
+ const gchar *directory;
+ gint line;
+ gchar *function_srcloc;
+ gboolean is_alloc_fn;
+ guint alloc_fns_serial;
+} Frame;
+
+struct _frames {
+ GHashTable *frames;
+
+ GList *alloc_fns;
+ guint alloc_fns_serial;
+};
+
+static void
+_frame_destroy (gpointer data)
+{
+ Frame *f = data;
+
+ g_free (f->function_srcloc);
+ g_free (f);
+}
+
+const gchar *
+frames_get_function (Frames *frames, guint ip)
+{
+ Frame *f = g_hash_table_lookup (frames->frames, GUINT_TO_POINTER (ip));
+ return f ? f->function : NULL;
+}
+
+const gchar *
+frames_get_function_with_srcloc (Frames *frames, guint ip)
+{
+ Frame *f = g_hash_table_lookup (frames->frames, GUINT_TO_POINTER (ip));
+ return f ? f->function_srcloc : NULL;
+}
+
+gchar *
+frames_get_source_path (Frames *frames, guint ip)
+{
+ Frame *f;
+
+ f = g_hash_table_lookup (frames->frames, GUINT_TO_POINTER (ip));
+ if (f == NULL)
+ return NULL;
+
+ if (f->file == NULL)
+ return NULL;
+ if (f->directory == NULL)
+ return g_strdup (f->file);
+
+ return g_strconcat (f->directory, G_DIR_SEPARATOR_S, f->file, NULL);
+}
+
+void
+frames_add (Frames *frames,
+ guint ip,
+ const gchar *function,
+ const gchar *object,
+ const gchar *file,
+ const gchar *directory,
+ gint line)
+{
+ Frame *f;
+ GString *string;
+ gchar *tmp;
+
+ f = g_hash_table_lookup (frames->frames, GUINT_TO_POINTER (ip));
+ if (f != NULL)
+ return;
+
+ f = g_new (Frame, 1);
+ f->ip = ip;
+ tmp = NULL;
+ if (function == NULL && object != NULL) {
+ tmp = g_strdup_printf ("(within %s)", object);
+ }
+ f->function = function ? g_intern_string (function) :
+ object ? g_intern_string (tmp) :
+ g_intern_string ("???");
+ if (tmp != NULL)
+ g_free (tmp);
+ f->object = object ? g_intern_string (object) : NULL;
+ f->file = file ? g_intern_string (file) : NULL;
+ f->directory = directory ? g_intern_string (directory) : NULL;
+ f->line = line;
+
+ string = g_string_new ("");
+ if (function) {
+ g_string_append (string, function);
+ if (file == NULL && object != NULL) {
+ g_string_append_printf (string, " (in %s)", object);
+ }
+ } else if (file == NULL && object != NULL) {
+ g_string_append_printf (string, "(within %s)", object);
+ } else {
+ g_string_append (string, "???");
+ }
+
+ if (file != NULL)
+ g_string_append_printf (string, " (%s:%d)", file, line);
+ f->function_srcloc = g_string_free (string, FALSE);
+
+ f->alloc_fns_serial = 0;
+ f->is_alloc_fn = FALSE;
+
+ g_hash_table_insert (frames->frames, GUINT_TO_POINTER (ip), f);
+}
+
+gboolean
+frames_has_ip (Frames *frames, guint ip)
+{
+ return g_hash_table_lookup (frames->frames, GUINT_TO_POINTER (ip)) != NULL;
+}
+
+gboolean
+frames_is_alloc_fn (Frames *frames, guint ip)
+{
+ GList *l;
+ Frame *f;
+
+ f = g_hash_table_lookup (frames->frames, GUINT_TO_POINTER (ip));
+ if (f == NULL)
+ return FALSE;
+
+ if (f->function == NULL)
+ return FALSE; /* XXX or TRUE? */
+
+ if (f->alloc_fns_serial == frames->alloc_fns_serial)
+ return f->is_alloc_fn;
+
+ for (l = frames->alloc_fns; l != NULL; l = g_list_next (l))
+ if (g_regex_match (l->data, f->function, 0, NULL))
+ break;
+ f->is_alloc_fn = l != NULL;
+ f->alloc_fns_serial = frames->alloc_fns_serial;
+
+ return f->is_alloc_fn;
+}
+
+gboolean
+frames_add_alloc_fn (Frames *frames, const gchar *pattern, GError **error)
+{
+ GRegex *regex = g_regex_new (pattern, G_REGEX_OPTIMIZE, 0, error);
+ if (regex == NULL)
+ return FALSE;
+
+ frames->alloc_fns = g_list_prepend (frames->alloc_fns, regex);
+
+ if (++frames->alloc_fns_serial == 0)
+ frames->alloc_fns_serial = 1;
+
+ return TRUE;
+}
+
+guint
+frames_get_alloc_fns_serial (Frames *frames)
+{
+ return frames->alloc_fns_serial;
+}
+
+static void
+_frames_init_alloc_fns (Frames *frames)
+{
+ const gchar *patterns[] = {
+ "malloc",
+ "operator new\\(unsigned\\)",
+ "operator new\\[\\]\\(unsigned\\)",
+ "operator new\\(unsigned, std::nothrow_t const&\\)",
+ "operator new\\[\\]\\(unsigned, std::nothrow_t const&\\)",
+ "__builtin_new",
+ "__builtin_vec_new",
+ "calloc",
+ "realloc",
+ "memalign",
+
+ "g_.*alloc",
+ "(strn?|mem)dup",
+ "ft_.*alloc", /* FreeType */
+ "_hb_.*alloc" /* HarfBuzz */
+
+ };
+ guint n;
+
+ for (n = 0; n < G_N_ELEMENTS (patterns); n++) {
+ GError *error = NULL;
+ GRegex *regex = g_regex_new (patterns[n], G_REGEX_OPTIMIZE, 0, &error);
+ if (regex != NULL) {
+ frames->alloc_fns = g_list_prepend (frames->alloc_fns, regex);
+ } else {
+ g_warning ("Failed to compile builtin regex '%s': %s",
+ patterns[n], error->message);
+ g_error_free (error);
+ }
+ }
+
+ frames->alloc_fns_serial = 1;
+}
+
+Frames *
+frames_create (void)
+{
+ Frames *frames;
+
+ frames = g_new (Frames, 1);
+ frames->frames = g_hash_table_new_full (NULL, NULL, NULL, _frame_destroy);
+ frames->alloc_fns = NULL;
+ frames->alloc_fns_serial = 0;
+
+ _frames_init_alloc_fns (frames);
+
+ return frames;
+}
+
+void
+frames_destroy (Frames *frames)
+{
+ g_list_foreach (frames->alloc_fns, (GFunc) g_regex_unref, NULL);
+ g_list_free (frames->alloc_fns);
+
+ g_hash_table_destroy (frames->frames);
+ g_free (frames);
+}
diff --git a/src/memfault.h b/src/memfault.h
new file mode 100644
index 0000000..9b925ca
--- /dev/null
+++ b/src/memfault.h
@@ -0,0 +1,212 @@
+#ifndef MEMFAULT_H
+#define MEMFAULT_H
+
+#include <gtk/gtk.h>
+
+G_BEGIN_DECLS
+
+typedef struct _app App;
+typedef struct _allocator Allocator;
+typedef struct _allocator_time AllocatorTime;
+typedef struct _block Block;
+typedef struct _thread_faults ThreadFaults;
+typedef struct _frames Frames;
+
+typedef struct _allocators Allocators;
+typedef struct _block_map BlockMap;
+typedef struct _call_graph CallGraph;
+typedef struct _timeline Timeline;
+
+typedef struct _call_graph_frame CallGraphFrame;
+typedef struct _call_graph_store CallGraphStore;
+typedef struct _call_graph_store_class CallGraphStoreClass;
+
+
+struct _block {
+ Block *next;
+ guint addr;
+ gsize size;
+ guint age;
+ Allocator *allocator;
+};
+
+struct _thread_faults {
+ guint tid;
+ guint fault_cnt;
+ guint n_faults;
+ ThreadFaults *next;
+};
+
+struct _allocator {
+ Allocator *next;
+ guint n_frames;
+ const gchar **functions;
+ const gchar **functions_srcloc;
+ const gchar *alloc_fn;
+ guint *ips;
+ struct _allocator_time {
+ guint time;
+ guint64 bytes;
+ guint64 freed;
+ gsize max_size;
+ guint size_allocs[32];
+ guint n_allocs;
+ guint n_reallocs;
+ guint n_frees;
+ guint n_realloced_blocks;
+ guint peak_blocks;
+ gsize peak_bytes;
+ ThreadFaults faults;
+ AllocatorTime *next;
+ } time[1];
+ AllocatorTime *time_tail;
+};
+
+typedef enum {
+ ALLOC,
+ REALLOC,
+ DEALLOC
+} EventType;
+
+typedef struct _event_base {
+ EventType type;
+ guint time;
+ guint tid;
+ Allocator *allocator;
+} EventBase;
+
+typedef struct _event_alloc {
+ EventType type;
+ guint time;
+ guint tid;
+ Allocator *allocator;
+
+ guint addr;
+ gsize size;
+} EventAlloc;
+
+typedef struct _event_realloc {
+ EventType type;
+ guint time;
+ guint tid;
+ Allocator *allocator;
+
+ guint old_addr;
+ gsize old_size;
+ guint new_addr;
+ gsize new_size;
+} EventRealloc;
+
+typedef struct _event_dealloc {
+ EventType type;
+ guint time;
+ guint tid;
+ Allocator *allocator;
+
+ guint addr;
+ gsize size;
+} EventDealloc;
+
+typedef union _event {
+ EventType type;
+ EventBase base;
+ EventAlloc alloc;
+ EventRealloc realloc;
+ EventDealloc dealloc;
+} Event;
+
+
+App *
+app_get (GtkWidget *widget);
+
+gboolean
+app_add_alloc_fn (App *app, const gchar *pattern, GError **error);
+
+gboolean
+app_is_alloc_fn (App *app, guint ip);
+
+guint
+app_get_alloc_fns_serial (App *app);
+
+void
+frames_add (Frames *frames,
+ guint ip,
+ const gchar *function,
+ const gchar *object,
+ const gchar *file,
+ const gchar *directory,
+ gint line);
+gboolean
+frames_has_ip (Frames *frames, guint ip);
+
+gboolean
+frames_is_alloc_fn (Frames *frames, guint ip);
+
+const gchar *
+frames_get_function (Frames *frames, guint ip);
+
+const gchar *
+frames_get_function_with_srcloc (Frames *frames, guint ip);
+
+gchar *
+frames_get_source_path (Frames *frames, guint ip);
+
+gboolean
+frames_add_alloc_fn (Frames *frames, const gchar *pattern, GError **error);
+
+guint
+frames_get_alloc_fns_serial (Frames *frames);
+
+Frames *
+frames_create (void);
+
+void
+frames_destroy (Frames *frames);
+
+
+GtkWidget *
+block_map_new (void);
+
+void
+block_map_set_highlight (BlockMap *block_map, GSList *blocks);
+
+GtkWidget *
+timeline_new (void);
+
+void
+timeline_add_datum (Timeline *tl, guint time, Allocator *A);
+
+GtkWidget *
+allocators_new (void);
+
+GSList *
+allocators_get_blocks_for_iter (Allocators *self,
+ GtkTreeIter *iter,
+ GSList *blocks);
+
+void
+allocators_select_blocks (Allocators *self, GSList *blocks);
+
+gboolean
+allocators_add_alloc_fn (Allocators *self,
+ const gchar *pattern,
+ GError **error);
+
+GtkWidget *
+call_graph_new (void);
+
+void
+call_graph_update_view (CallGraph *self);
+
+GtkWidget *
+call_graph_tree_map_new (void);
+
+G_CONST_RETURN Event *
+app_find_prev_event_for_addr_range (App *app, guint time, guint min, guint max);
+
+void
+pretty_print_number (const char *number, gint len, char *output);
+
+G_END_DECLS
+
+#endif /* MEMFAULT_H */
diff --git a/src/timeline.c b/src/timeline.c
new file mode 100644
index 0000000..abfa248
--- /dev/null
+++ b/src/timeline.c
@@ -0,0 +1,397 @@
+#include <gtk/gtk.h>
+#include <math.h>
+#include <unistd.h>
+#include <string.h>
+
+#include "memfault.h"
+
+#define _(x) x
+
+typedef struct _timeline_data TimelineData;
+
+struct _timeline_data {
+ guint time;
+ guint n_allocs;
+ guint n_frees;
+ guint64 bytes_allocated;
+ guint64 bytes_freed;
+ TimelineData *next, *prev;
+};
+
+struct _timeline {
+ GtkWidget widget;
+
+ TimelineData *data, *tail;
+ guint max_time;
+ guint64 max_bytes;
+ guint64 max_active;
+ guint max_blocks;
+};
+
+typedef struct _timeline_class {
+ GtkWidgetClass parent_class;
+} TimelineClass;
+
+G_DEFINE_TYPE (Timeline, timeline, GTK_TYPE_WIDGET)
+
+static void
+timeline_set_property (GObject *obj, guint id, const GValue *v, GParamSpec *spec)
+{
+ Timeline *self = (Timeline *) obj;
+ switch (id) {
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (obj, id, spec);
+ break;
+ }
+}
+
+static void
+timeline_get_property (GObject *obj, guint id, GValue *v, GParamSpec *spec)
+{
+ Timeline *self = (Timeline *) obj;
+ switch (id) {
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (obj, id, spec);
+ break;
+ }
+}
+
+static void
+timeline_realize (GtkWidget *widget)
+{
+ GdkWindowAttr attributes;
+
+ GTK_WIDGET_SET_FLAGS (widget, GTK_REALIZED);
+
+ attributes.window_type = GDK_WINDOW_CHILD;
+ attributes.x = widget->allocation.x;
+ attributes.y = widget->allocation.y;
+ attributes.width = widget->allocation.width;
+ attributes.height = widget->allocation.height;
+ attributes.wclass = GDK_INPUT_OUTPUT;
+ attributes.visual = gtk_widget_get_visual (widget);
+ attributes.colormap = gtk_widget_get_colormap (widget);
+ attributes.event_mask = GDK_BUTTON_PRESS_MASK |
+ GDK_POINTER_MOTION_MASK |
+ GDK_LEAVE_NOTIFY_MASK |
+ GDK_EXPOSURE_MASK;
+
+ widget->window = gdk_window_new (gtk_widget_get_parent_window (widget),
+ &attributes,
+ GDK_WA_X | GDK_WA_Y |
+ GDK_WA_VISUAL | GDK_WA_COLORMAP);
+ gdk_window_set_user_data (widget->window, widget);
+
+ widget->style = gtk_style_attach (widget->style, widget->window);
+ gtk_style_set_background (widget->style, widget->window, GTK_STATE_NORMAL);
+}
+
+#define BORDER 4
+static gboolean
+timeline_expose (GtkWidget *widget, GdkEventExpose *ev)
+{
+ Timeline *self = (Timeline *) widget;
+ TimelineData *data;
+ cairo_t *cr;
+ double dashes[1] = {4.};
+ cairo_pattern_t *pattern;
+ guint last_time;
+ guint64 last_value;
+ gdouble last_x;
+
+ cr = gdk_cairo_create (widget->window);
+ gdk_cairo_region (cr, ev->region);
+ cairo_clip (cr);
+
+ if (self->data != NULL) {
+ cairo_save (cr);
+ cairo_translate (cr, BORDER, widget->allocation.height - BORDER);
+ cairo_scale (cr,
+ (widget->allocation.width - 2*BORDER) / (gdouble) self->max_time,
+ -(widget->allocation.height - 2*BORDER) / (gdouble) (2 * self->max_bytes));
+ cairo_translate (cr, 0, self->max_bytes);
+
+ /* bytes allocated */
+ cairo_move_to (cr, 0, 0);
+ last_time = last_value = 0;
+ for (data = self->data; data != NULL; data = data->next) {
+ guint64 bytes = data->prev ? data->prev->bytes_allocated : 0;
+ guint64 value = data->bytes_allocated - bytes;
+ cairo_curve_to (cr,
+ .5 * (data->time - last_time) + last_time, last_value,
+ .5 * (data->time - last_time) + last_time, value,
+ data->time, value);
+ last_time = data->time;
+ last_value = value;
+ }
+ cairo_line_to (cr, self->max_time, 0);
+ cairo_close_path (cr);
+
+ cairo_save (cr);
+ cairo_identity_matrix (cr);
+ pattern = cairo_pattern_create_linear (
+ 0, .5 * self->widget.allocation.height,
+ 0, 0);
+ cairo_pattern_add_color_stop_rgba (pattern, 0, 0, 0, 0, 1);
+ cairo_pattern_add_color_stop_rgba (pattern, 1, 1, 0, 0, 1);
+ cairo_set_source (cr, pattern);
+ cairo_pattern_destroy (pattern);
+ cairo_fill (cr);
+ cairo_restore (cr);
+
+ cairo_scale (cr, 1, -1);
+
+ /* bytes freed */
+ cairo_move_to (cr, 0, 0);
+ last_time = last_value = 0;
+ for (data = self->data; data != NULL; data = data->next) {
+ guint64 bytes = data->prev ? data->prev->bytes_freed : 0;
+ guint64 value = data->bytes_freed - bytes;
+ cairo_curve_to (cr,
+ .5 * (data->time - last_time) + last_time, last_value,
+ .5 * (data->time - last_time) + last_time, value,
+ data->time, value);
+ last_time = data->time;
+ last_value = value;
+ }
+ cairo_line_to (cr, self->max_time, 0);
+ cairo_close_path (cr);
+
+ cairo_save (cr);
+ cairo_identity_matrix (cr);
+ pattern = cairo_pattern_create_linear (
+ 0, .5 * widget->allocation.height,
+ 0, widget->allocation.height);
+ cairo_pattern_add_color_stop_rgb (pattern, 0., 0, 0, 0);
+ cairo_pattern_add_color_stop_rgb (pattern, 1., 0, 1, 0);
+ cairo_set_source (cr, pattern);
+ cairo_pattern_destroy (pattern);
+ cairo_fill (cr);
+ cairo_restore (cr);
+
+
+ /* bytes delta */
+ cairo_move_to (cr, 0, 0);
+ last_time = last_value = 0;
+ for (data = self->data; data != NULL; data = data->next) {
+ gint bytes = data->prev ? data->prev->bytes_allocated - data->prev->bytes_freed : 0;
+ gint value = data->bytes_allocated - data->bytes_freed - bytes;
+ if (value < 0)
+ value = 0;
+ cairo_curve_to (cr,
+ .5 * (data->time - last_time) + last_time, last_value,
+ .5 * (data->time - last_time) + last_time, value,
+ data->time, value);
+ last_time = data->time;
+ last_value = value;
+ }
+ cairo_line_to (cr, last_time, 0);
+
+ cairo_scale (cr, 1, -1);
+
+ cairo_line_to (cr, 0, 0);
+ last_time = last_value = 0;
+ for (data = self->data; data != NULL; data = data->next) {
+ gint bytes = data->prev ? data->prev->bytes_allocated - data->prev->bytes_freed : 0;
+ gint value = data->bytes_allocated - data->bytes_freed - bytes;
+ if (value < 0)
+ value = 0;
+ cairo_curve_to (cr,
+ .5 * (data->time - last_time) + last_time, last_value,
+ .5 * (data->time - last_time) + last_time, value,
+ data->time, value);
+ last_time = data->time;
+ last_value = value;
+ }
+ cairo_line_to (cr, last_time, 0);
+ cairo_close_path (cr);
+
+ cairo_save (cr);
+ cairo_identity_matrix (cr);
+ //cairo_set_source_rgb (cr, 1, 1, 1);
+ pattern = cairo_pattern_create_linear (
+ 0, 0,
+ 0, self->widget.allocation.height);
+ cairo_pattern_add_color_stop_rgba (pattern, 0, 1, 1, 1, 0);
+ cairo_pattern_add_color_stop_rgba (pattern, 0.5, 1, 1, 1, 1);
+ cairo_pattern_add_color_stop_rgba (pattern, 1, 1, 1, 1, 0);
+ cairo_set_source (cr, pattern);
+ cairo_pattern_destroy (pattern);
+ cairo_fill (cr);
+ cairo_restore (cr);
+
+ cairo_restore (cr);
+
+
+ pattern = cairo_pattern_create_linear (
+ 0, 0,
+ 0, self->widget.allocation.height);
+ cairo_pattern_add_color_stop_rgb (pattern, 0., 0.66, 0.1, 0);
+ cairo_pattern_add_color_stop_rgb (pattern, .5, .33, .33, .33);
+ cairo_pattern_add_color_stop_rgb (pattern, 1., 0, 0.66, 0.1);
+ cairo_set_source (cr, pattern);
+ cairo_pattern_destroy (pattern);
+
+ cairo_save (cr);
+ cairo_translate (cr, BORDER, widget->allocation.height - BORDER);
+ cairo_scale (cr,
+ (widget->allocation.width - 2*BORDER) / (gdouble) self->max_time,
+ -(widget->allocation.height - 2*BORDER) / (gdouble) (2 * self->max_blocks));
+ cairo_translate (cr, 0, self->max_blocks);
+
+ /* struts */
+ last_x = 0;
+ for (data = self->data; data->next != NULL; data = data->next) {
+ guint last_frees = data->prev ? data->prev->n_frees : 0;
+ guint last_allocs = data->prev ? data->prev->n_allocs : 0;
+ gdouble x;
+
+ x = floor ((widget->allocation.width - 2*BORDER) / (gdouble) self->max_time * data->time) - .5;
+ /* maintain a minimum of 10 pixels between struts */
+ if (x - last_x < 10.)
+ continue;
+
+ last_x = x;
+ x *= self->max_time / (gdouble) (widget->allocation.width - 2*BORDER);
+
+ cairo_move_to (cr, x, 0. - (data->n_frees - last_frees));
+ cairo_line_to (cr, x, data->n_allocs - last_allocs);
+
+ cairo_save (cr);
+ cairo_identity_matrix (cr);
+ cairo_set_dash (cr, dashes, 1, 0.);
+ cairo_set_line_width (cr, 1.);
+ cairo_stroke (cr);
+ cairo_restore (cr);
+ }
+
+
+ /* blocks allocated */
+ cairo_move_to (cr, 0, 0);
+ for (data = self->data; data != NULL; data = data->next) {
+ guint last_allocs = data->prev ? data->prev->n_allocs : 0;
+ cairo_line_to (cr, data->time, data->n_allocs - last_allocs);
+ }
+ cairo_set_source_rgb (cr, .66, .1, 0);
+ cairo_save (cr);
+ cairo_identity_matrix (cr);
+ cairo_stroke (cr);
+ cairo_restore (cr);
+
+ /* blocks freed */
+ cairo_scale (cr, 1, -1);
+ cairo_move_to (cr, 0, 0);
+ for (data = self->data; data != NULL; data = data->next) {
+ guint last_frees = data->prev ? data->prev->n_frees : 0;
+ cairo_line_to (cr, data->time, data->n_frees - last_frees);
+ }
+ cairo_set_source_rgb (cr, 0, .66, .1);
+ cairo_save (cr);
+ cairo_identity_matrix (cr);
+ cairo_stroke (cr);
+ cairo_restore (cr);
+
+ cairo_restore (cr);
+
+ /* active bytes */
+ cairo_save (cr);
+ cairo_translate (cr, BORDER, widget->allocation.height - BORDER);
+ cairo_scale (cr,
+ (widget->allocation.width - 2*BORDER) / (gdouble) self->max_time,
+ -(widget->allocation.height - 2*BORDER) / (gdouble) (2 * self->max_active));
+ cairo_translate (cr, 0, self->max_active);
+
+ /* bytes allocated */
+ cairo_move_to (cr, 0, 0);
+ for (data = self->data; data != NULL; data = data->next) {
+ cairo_line_to (cr, data->time, data->bytes_allocated - data->bytes_freed);
+ }
+ cairo_set_source_rgb (cr, 1, 1, 1);
+ cairo_identity_matrix (cr);
+ cairo_stroke (cr);
+ cairo_restore (cr);
+ }
+
+ cairo_destroy (cr);
+
+ return FALSE;
+}
+
+static void
+timeline_class_init (TimelineClass *klass)
+{
+ GObjectClass *object_class = (GObjectClass *) klass;
+ GtkWidgetClass *widget_class = (GtkWidgetClass *) klass;
+
+ object_class->set_property = timeline_set_property;
+ object_class->get_property = timeline_get_property;
+
+ widget_class->realize = timeline_realize;
+ widget_class->expose_event = timeline_expose;
+}
+
+static void
+timeline_init (Timeline *self)
+{
+ gtk_widget_set_size_request (&self->widget, 256, 256);
+}
+
+GtkWidget *
+timeline_new (void)
+{
+ return g_object_new (timeline_get_type(), NULL);
+}
+
+void
+timeline_add_datum (Timeline *tl, guint time, Allocator *A)
+{
+ TimelineData *data = g_slice_new (TimelineData);
+ TimelineData *prev;
+ guint64 bytes;
+ guint blocks;
+
+ prev = tl->tail;
+ if (tl->tail != NULL) {
+ g_assert (time > tl->tail->time);
+ tl->tail->next = data;
+ } else
+ tl->data = data;
+ data->prev = tl->tail;
+ tl->tail = data;
+
+ tl->max_time = time;
+
+ data->time = time;
+ data->next = NULL;
+ data->n_allocs = 0;
+ data->n_frees = 0;
+ data->bytes_allocated = 0;
+ data->bytes_freed = 0;
+
+ while (A != NULL) {
+ if (A->time_tail->time == time) {
+ data->n_allocs += A->time_tail->n_allocs;
+ data->n_frees += A->time_tail->n_frees;
+ data->bytes_allocated += A->time_tail->bytes;
+ data->bytes_freed += A->time_tail->freed;
+ }
+
+ A = A->next;
+ }
+ g_assert (data->bytes_allocated >= data->bytes_freed);
+ g_assert (data->n_allocs >= data->n_frees);
+
+ bytes = data->bytes_allocated - data->bytes_freed;
+ if (bytes > tl->max_active)
+ tl->max_active = bytes;
+
+ bytes = prev ? prev->bytes_allocated : 0;
+ if (data->bytes_allocated - bytes > tl->max_bytes)
+ tl->max_bytes = data->bytes_allocated - bytes;
+
+ blocks = prev ? prev->n_allocs : 0;
+ if (data->n_allocs - blocks > tl->max_blocks)
+ tl->max_blocks = data->n_allocs - blocks;
+
+ gtk_widget_queue_draw (&tl->widget);
+}
diff --git a/src/utils.c b/src/utils.c
new file mode 100644
index 0000000..a815563
--- /dev/null
+++ b/src/utils.c
@@ -0,0 +1,21 @@
+#include "memfault.h"
+
+void
+pretty_print_number (const char *number, gint len, char *output)
+{
+ gint i;
+
+ output += len + (len - 1) / 3;
+ number += len;
+ *output = '\0';
+
+ i = -1;
+ while (len--) {
+ if (++i == 3) {
+ *--output = ',';
+ i = 0;
+ }
+
+ *--output = *--number;
+ }
+}