diff options
Diffstat (limited to 'src/allocators.c')
-rw-r--r-- | src/allocators.c | 726 |
1 files changed, 726 insertions, 0 deletions
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); +} |