summaryrefslogtreecommitdiff
path: root/stackstash.c
diff options
context:
space:
mode:
Diffstat (limited to 'stackstash.c')
-rw-r--r--stackstash.c427
1 files changed, 298 insertions, 129 deletions
diff --git a/stackstash.c b/stackstash.c
index 9f66f15..44912e4 100644
--- a/stackstash.c
+++ b/stackstash.c
@@ -19,200 +19,369 @@
#include "stackstash.h"
-typedef struct StackNode StackNode;
-
-struct StackNode
+struct StackStash
{
- StackNode * parent;
- gpointer address;
- StackNode * siblings;
- StackNode * children;
- StackNode * next; /* next leaf with the same pid */
- int size;
+ int ref_count;
+ StackNode * root;
+ GHashTable * nodes_by_data;
+ GDestroyNotify destroy;
+
+ StackNode * cached_nodes;
+ GPtrArray * blocks;
};
-struct StackStash
+static void
+decorate_node (StackNode *node,
+ StackStash *stash)
{
- StackNode *root;
- GHashTable *leaves_by_process;
-};
+ StackNode *n;
+
+ if (!node)
+ return;
+
+ decorate_node (node->siblings, stash);
+ decorate_node (node->children, stash);
+
+ node->next = g_hash_table_lookup (stash->nodes_by_data, &node->data);
+ g_hash_table_insert (stash->nodes_by_data, &node->data, node);
+
+ /* FIXME: This could be done more efficiently
+ * by keeping track of the ancestors we have seen.
+ */
+ node->toplevel = TRUE;
+ for (n = node->parent; n != NULL; n = n->parent)
+ {
+ if (n->data == node->data)
+ {
+ node->toplevel = FALSE;
+ break;
+ }
+ }
+}
+
+static unsigned int
+address_hash (gconstpointer key)
+{
+ const uint64_t *addr = key;
+
+ return *addr;
+}
+
+static gboolean
+address_equal (gconstpointer key1, gconstpointer key2)
+{
+ const uint64_t *addr1 = key1;
+ const uint64_t *addr2 = key2;
+
+ return *addr1 == *addr2;
+}
+
+static void
+stack_stash_decorate (StackStash *stash)
+{
+ if (stash->nodes_by_data)
+ return;
+
+ stash->nodes_by_data = g_hash_table_new (address_hash, address_equal);
+
+ decorate_node (stash->root, stash);
+}
+
+static void
+free_key (gpointer key,
+ gpointer value,
+ gpointer data)
+{
+ GDestroyNotify destroy = data;
+ uint64_t u64 = *(uint64_t *)key;
+
+ destroy (U64_TO_POINTER (u64));
+}
-static StackNode *
-stack_node_new (void)
+static void
+stack_stash_undecorate (StackStash *stash)
+{
+ if (stash->nodes_by_data)
+ {
+ if (stash->destroy)
+ {
+ g_hash_table_foreach (
+ stash->nodes_by_data, free_key, stash->destroy);
+ }
+
+ g_hash_table_destroy (stash->nodes_by_data);
+ stash->nodes_by_data = NULL;
+ }
+}
+
+static GHashTable *
+get_nodes_by_data (StackStash *stash)
{
- StackNode *node = g_new (StackNode, 1);
+ if (!stash->nodes_by_data)
+ stack_stash_decorate (stash);
+
+ return stash->nodes_by_data;
+}
+
+StackNode *
+stack_node_new (StackStash *stash)
+{
+ StackNode *node;
+
+ if (!stash->cached_nodes)
+ {
+#define BLOCK_SIZE 32768
+#define N_NODES (BLOCK_SIZE / sizeof (StackNode))
+
+ StackNode *block = g_malloc (BLOCK_SIZE);
+ int i;
+
+ for (i = 0; i < N_NODES; ++i)
+ {
+ block[i].next = stash->cached_nodes;
+ stash->cached_nodes = &(block[i]);
+ }
+
+ g_ptr_array_add (stash->blocks, block);
+ }
+
+ node = stash->cached_nodes;
+ stash->cached_nodes = node->next;
+
node->siblings = NULL;
node->children = NULL;
- node->address = NULL;
+ node->data = 0;
node->parent = NULL;
- node->next = NULL;
node->size = 0;
+ node->next = NULL;
+ node->total = 0;
+
return node;
}
-static void
-stack_node_destroy (gpointer p)
+/* "destroy", if non-NULL, is called once on every address */
+static StackStash *
+create_stack_stash (GDestroyNotify destroy)
{
- StackNode *node = p;
- if (node)
- {
- stack_node_destroy (node->siblings);
- stack_node_destroy (node->children);
- g_free (node);
- }
+ StackStash *stash = g_new (StackStash, 1);
+
+ stash->root = NULL;
+ stash->nodes_by_data = NULL;
+ stash->ref_count = 1;
+ stash->destroy = destroy;
+
+ stash->cached_nodes = NULL;
+ stash->blocks = g_ptr_array_new ();
+
+ return stash;
}
/* Stach */
StackStash *
-stack_stash_new (void)
+stack_stash_new (GDestroyNotify destroy)
{
- StackStash *stash = g_new (StackStash, 1);
+ return create_stack_stash (destroy);
+}
- stash->leaves_by_process =
- g_hash_table_new (g_direct_hash, g_direct_equal);
- stash->root = NULL;
- return stash;
+
+static void
+stack_stash_free (StackStash *stash)
+{
+ int i;
+
+ stack_stash_undecorate (stash);
+
+ for (i = 0; i < stash->blocks->len; ++i)
+ g_free (stash->blocks->pdata[i]);
+
+ g_ptr_array_free (stash->blocks, TRUE);
+
+ g_free (stash);
}
-static StackNode *
-stack_node_add_trace (StackNode *node,
- GList *bottom,
- gint size,
- StackNode **leaf)
+StackNode *
+stack_stash_add_trace (StackStash *stash,
+ uint64_t *addrs,
+ int n_addrs,
+ int size)
{
- StackNode *match;
- StackNode *n;
+ StackNode **location = &(stash->root);
+ StackNode *parent = NULL;
+ int i;
- if (!bottom)
- {
- *leaf = NULL;
- return node;
- }
+ if (!n_addrs)
+ return NULL;
- if (!bottom->next)
+ if (stash->nodes_by_data)
+ stack_stash_undecorate (stash);
+
+ for (i = n_addrs - 1; i >= 0; --i)
{
- /* A leaf must always be separate, so pids can
- * point to them
+ StackNode *match = NULL;
+ StackNode *prev;
+
+ /* FIXME: On x86-64 we don't get proper stacktraces which means
+ * each node can have tons of children. That makes this loop
+ * here show up on profiles.
+ *
+ * Not sure what can be done about it aside from actually fixing
+ * x86-64 to get stacktraces.
*/
- match = NULL;
- }
- else
- {
- for (match = node; match != NULL; match = match->siblings)
+ prev = NULL;
+ for (match = *location; match; prev = match, match = match->siblings)
{
- if (match->address == bottom->data)
+ if (match->data == addrs[i])
+ {
+ if (prev)
+ {
+ /* move to front */
+ prev->siblings = match->siblings;
+ match->siblings = *location;
+ *location = match;
+ }
+
break;
+ }
}
- }
- if (!match)
- {
- match = stack_node_new ();
- match->address = bottom->data;
- match->siblings = node;
- node = match;
+ if (!match)
+ {
+ match = stack_node_new (stash);
+ match->data = addrs[i];
+ match->siblings = *location;
+ match->parent = parent;
+ *location = match;
+ }
+
+ match->total += size;
+
+ location = &(match->children);
+ parent = match;
}
- match->children =
- stack_node_add_trace (match->children, bottom->next, size, leaf);
+ parent->size += size;
- for (n = match->children; n; n = n->siblings)
- n->parent = match;
+ return parent;
+}
+
+static void
+do_callback (StackNode *node,
+ StackLink *trace,
+ StackFunction func,
+ gpointer data)
+{
+ StackLink link;
+
+ if (trace)
+ trace->prev = &link;
+
+ link.next = trace;
+ link.prev = NULL;
- if (!bottom->next)
+ while (node)
{
- match->size += size;
- *leaf = match;
+ link.data = node->data;
+
+ if (node->size)
+ func (&link, node->size, data);
+
+ do_callback (node->children, &link, func, data);
+
+ node = node->siblings;
}
- return node;
+ if (trace)
+ trace->prev = NULL;
}
void
-stack_stash_add_trace (StackStash *stash,
- Process *process,
- gulong *addrs,
- int n_addrs,
- int size)
+stack_stash_foreach (StackStash *stash,
+ StackFunction stack_func,
+ gpointer data)
{
- GList *trace;
- StackNode *leaf;
- int i;
+ do_callback (stash->root, NULL, stack_func, data);
+}
- if (!n_addrs)
- return;
-
- trace = NULL;
- for (i = 0; i < n_addrs; ++i)
- trace = g_list_prepend (trace, GINT_TO_POINTER (addrs[i]));
+void
+stack_node_foreach_trace (StackNode *node,
+ StackFunction func,
+ gpointer data)
+{
+ StackLink link;
- stash->root = stack_node_add_trace (stash->root, trace, size, &leaf);
+ link.next = NULL;
+ link.data = node->data;
+ link.prev = NULL;
- leaf->next = g_hash_table_lookup (
- stash->leaves_by_process, process);
- g_hash_table_insert (
- stash->leaves_by_process, process, leaf);
+ if (node->size)
+ func (&link, node->size, data);
+
+ do_callback (node->children, &link, func, data);
+}
- g_list_free (trace);
+void
+stack_stash_unref (StackStash *stash)
+{
+ stash->ref_count--;
+ if (stash->ref_count == 0)
+ stack_stash_free (stash);
}
-typedef struct CallbackInfo
+StackStash *
+stack_stash_ref (StackStash *stash)
{
- StackFunction func;
- gpointer data;
-} CallbackInfo;
+ stash->ref_count++;
+ return stash;
+}
-static void
-do_callback (gpointer key, gpointer value, gpointer data)
+StackNode *
+stack_stash_find_node (StackStash *stash,
+ gpointer data)
{
- CallbackInfo *info = data;
- Process *process = key;
- StackNode *n;
- StackNode *leaf = value;
- while (leaf)
- {
- GSList *trace;
+ uint64_t u64 = POINTER_TO_U64 (data);
+
+ g_return_val_if_fail (stash != NULL, NULL);
+
+ return g_hash_table_lookup (get_nodes_by_data (stash), &u64);
+}
- trace = NULL;
- for (n = leaf; n; n = n->parent)
- trace = g_slist_prepend (trace, n->address);
+typedef struct
+{
+ StackNodeFunc func;
+ gpointer data;
+} Info;
- info->func (process, trace, leaf->size, info->data);
+static void
+do_foreach (gpointer key, gpointer value, gpointer data)
+{
+ Info *info = data;
- g_slist_free (trace);
-
- leaf = leaf->next;
- }
+ info->func (value, info->data);
}
void
-stack_stash_foreach (StackStash *stash,
- StackFunction stack_func,
- gpointer data)
+stack_stash_foreach_by_address (StackStash *stash,
+ StackNodeFunc func,
+ gpointer data)
{
- CallbackInfo info;
- info.func = stack_func;
+ Info info;
+ info.func = func;
info.data = data;
- g_hash_table_foreach (stash->leaves_by_process, do_callback, &info);
+ g_hash_table_foreach (get_nodes_by_data (stash), do_foreach, &info);
}
-static void
-stack_node_free (StackNode *node)
+StackNode *
+stack_stash_get_root (StackStash *stash)
{
- if (!node)
- return;
-
- stack_node_free (node->siblings);
- stack_node_free (node->children);
-
- g_free (node);
+ return stash->root;
}
void
-stack_stash_free (StackStash *stash)
+stack_stash_set_root (StackStash *stash,
+ StackNode *root)
{
- stack_node_free (stash->root);
- g_hash_table_destroy (stash->leaves_by_process);
- g_free (stash);
+ g_return_if_fail (stash->root == NULL);
+
+ stash->root = root;
}