diff options
author | Chris Wilson <chris@chris-wilson.co.uk> | 2007-11-24 20:32:54 +0000 |
---|---|---|
committer | Chris Wilson <chris@chris-wilson.co.uk> | 2007-11-24 20:32:54 +0000 |
commit | ccb52888d2c9a8584717767353c06f9a59a0109a (patch) | |
tree | d6d959d68f652ee84ff642a8b464f609582ecf43 /src | |
parent | 8b043b39b92ff29aebcf596ef6508d76097046ad (diff) |
Move source files into src/
Diffstat (limited to 'src')
-rw-r--r-- | src/.gitignore | 33 | ||||
-rw-r--r-- | src/allocators.c | 726 | ||||
-rw-r--r-- | src/app.c | 878 | ||||
-rw-r--r-- | src/blockmap.c | 681 | ||||
-rw-r--r-- | src/callgraph-store.c | 714 | ||||
-rw-r--r-- | src/callgraph-treemap.c | 954 | ||||
-rw-r--r-- | src/callgraph.c | 544 | ||||
-rw-r--r-- | src/callgraph.h | 61 | ||||
-rw-r--r-- | src/frames.c | 230 | ||||
-rw-r--r-- | src/memfault.h | 212 | ||||
-rw-r--r-- | src/timeline.c | 397 | ||||
-rw-r--r-- | src/utils.c | 21 |
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; + } +} |