summaryrefslogtreecommitdiff
path: root/src/allocators.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/allocators.c')
-rw-r--r--src/allocators.c726
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);
+}