/* * lighty memory profiler * prints a backtrace for every object not free()d at exit() * */ #include #include #include #ifdef HAVE_EXECINFO_H # include #endif #if defined(LIGHTY_OS_MACOSX) # include #elif defined(LIGHTY_OS_LINUX) # include #endif #define PROFILER_HASHTABLE_SIZE 65521 #define PROFILER_STACKFRAMES 36 typedef struct profiler_block profiler_block; struct profiler_block { gpointer addr; gsize size; profiler_block *next; void *stackframes[PROFILER_STACKFRAMES]; gint stackframes_num; }; typedef struct profiler_stackframe profiler_stackframe; struct profiler_stackframe { gpointer addr; gsize size; guint blocks; gchar *symbol; profiler_stackframe *next; profiler_stackframe *children; }; static void profiler_free(gpointer addr); static GStaticMutex profiler_mutex = G_STATIC_MUTEX_INIT; static profiler_block *block_free_list = NULL; static profiler_block **profiler_hashtable = NULL; static gint profiler_output_fd = 0; static gpointer profiler_heap_base = NULL; gboolean li_profiler_enabled = FALSE; static guint profiler_hash(gpointer addr) { return ((uintptr_t)addr * 2654435761); /* ~ golden ratio of 2^32 */ } static profiler_block *profiler_block_new(void) { profiler_block *block; if (!block_free_list) { /* page_free_list exhausted */ block = malloc(sizeof(profiler_block)); } else { block = block_free_list; block_free_list = block_free_list->next; } block->addr = NULL; block->size = 0; block->next = NULL; block->stackframes_num = 0; return block; } static void profiler_block_free(profiler_block *block) { /* push onto free list */ block->next = block_free_list; block_free_list = block; } static gpointer profiler_try_malloc(gsize n_bytes) { gsize *p; p = malloc(n_bytes); if (p) { #if defined(LIGHTY_OS_MACOSX) n_bytes = malloc_size(p); #elif defined(LIGHTY_OS_LINUX) n_bytes = malloc_usable_size(p); #endif li_profiler_hashtable_insert(p, n_bytes); } return p; } static gpointer profiler_malloc(gsize n_bytes) { gpointer p = profiler_try_malloc(n_bytes); assert(p); return p; } static gpointer profiler_try_realloc(gpointer mem, gsize n_bytes) { gsize *p; if (!mem) return profiler_try_malloc(n_bytes); if (!n_bytes) { profiler_free(mem); return NULL; } p = realloc(mem, n_bytes); if (p) { li_profiler_hashtable_remove(mem); #if defined(LIGHTY_OS_MACOSX) n_bytes = malloc_size(p); #elif defined(LIGHTY_OS_LINUX) n_bytes = malloc_usable_size(p); #endif li_profiler_hashtable_insert(p, n_bytes); } return p; } static gpointer profiler_realloc(gpointer mem, gsize n_bytes) { gpointer p = profiler_try_realloc(mem, n_bytes); assert(p); return p; } static gpointer profiler_calloc(gsize n_blocks, gsize n_bytes) { gsize *p; gsize size = n_blocks * n_bytes; p = calloc(1, size); if (p) { #if defined(LIGHTY_OS_MACOSX) n_bytes = malloc_size(p); #elif defined(LIGHTY_OS_LINUX) n_bytes = malloc_usable_size(p); #endif li_profiler_hashtable_insert(p, n_bytes); } assert(p); return p; } static void profiler_free(gpointer mem) { assert(mem); li_profiler_hashtable_remove(mem); free(mem); } static void profiler_write(gchar *str, gint len) { gint res; gint written = 0; while (len) { res = write(profiler_output_fd, str + written, len); if (-1 == res) { fputs("error writing to profiler output file\n", stderr); fflush(stderr); abort(); } written += res; len -= res; } } #ifdef HAVE_EXECINFO_H static void profiler_dump_frame(guint level, profiler_stackframe *frame, gsize minsize) { gchar str[1024]; gint len; gboolean swapped; profiler_stackframe *f; /* sort this tree level according to total allocated size. yes, bubblesort. */ do { profiler_stackframe *f1, *f2; swapped = FALSE; for (f1 = frame, f2 = frame->next; f1 != NULL && f2 != NULL; f1 = f2, f2 = f2->next) { if (f2->size > f1->size) { profiler_stackframe tmp = *f1; f1->addr = f2->addr; f1->blocks = f2->blocks; f1->size = f2->size; f1->children = f2->children; f1->symbol = f2->symbol; f2->addr = tmp.addr; f2->blocks = tmp.blocks; f2->size = tmp.size; f2->children = tmp.children; f2->symbol = tmp.symbol; swapped = TRUE; } } } while (swapped); while (frame) { if (frame->size >= minsize) { /* indention */ memset(str, ' ', level*4); profiler_write(str, level*4); len = sprintf(str, "%"G_GSIZE_FORMAT" %s in %u blocks @ %p %s\n", (frame->size > 1024) ? frame->size / 1024 : frame->size, (frame->size > 1024) ? "kilobytes" : "bytes", frame->blocks, frame->addr, frame->symbol ); profiler_write(str, len); } if (frame->children) profiler_dump_frame(level+1, frame->children, minsize); f = frame->next; free(frame->symbol); free(frame); frame = f; } } #endif /* public functions */ void li_profiler_enable(gchar *output_path) { GMemVTable t; /* force allocation of mutex data; newer glib versions allocate extra data */ g_static_mutex_lock(&profiler_mutex); g_static_mutex_unlock(&profiler_mutex); profiler_heap_base = sbrk(0); if (g_str_equal(output_path, "stdout")) { profiler_output_fd = STDOUT_FILENO; } else if (g_str_equal(output_path, "stderr")) { profiler_output_fd = STDERR_FILENO; } else { profiler_output_fd = open(output_path, O_WRONLY | O_CREAT | O_TRUNC, 0600); if (-1 == profiler_output_fd) { fputs("error opening profiler output file\n", stderr); fflush(stderr); abort(); } } block_free_list = profiler_block_new(); profiler_hashtable = calloc(sizeof(profiler_block), PROFILER_HASHTABLE_SIZE); t.malloc = profiler_malloc; t.realloc = profiler_realloc; t.free = profiler_free; t.calloc = profiler_calloc; t.try_malloc = profiler_try_malloc; t.try_realloc = profiler_try_realloc; g_mem_set_vtable(&t); li_profiler_enabled = TRUE; } void li_profiler_finish(void) { guint i; profiler_block *block, *block_tmp; for (i = 0; i < PROFILER_HASHTABLE_SIZE; i++) { for (block = profiler_hashtable[i]; block != NULL;) { block_tmp = block->next; free(block); block = block_tmp; } } for (block = block_free_list; block != NULL;) { block_tmp = block->next; free(block); block = block_tmp; } free(profiler_hashtable); } void li_profiler_dump(gint minsize) { profiler_stackframe *tree_cur, *frame; gchar **symbols; gint i, j, len; profiler_block *block; struct stat st; gchar str[1024]; gsize total_size = 0; guint total_blocks = 0; profiler_stackframe *tree = calloc(1, sizeof(profiler_stackframe)); g_static_mutex_lock(&profiler_mutex); fstat(profiler_output_fd, &st); lseek(profiler_output_fd, st.st_size, SEEK_SET); len = sprintf(str, "--------------- memory profiler dump @ %ju ---------------\n", time(NULL)); profiler_write(str, len); for (i = 0; i < PROFILER_HASHTABLE_SIZE; i++) { for (block = profiler_hashtable[i]; block != NULL; block = block->next) { total_size += block->size; total_blocks++; #ifdef HAVE_EXECINFO_H /* resolve all symbols */ symbols = backtrace_symbols(block->stackframes, block->stackframes_num); tree_cur = tree; for (j = block->stackframes_num-1; j >= 0; j--) { for (frame = tree_cur->children; frame != NULL; frame = frame->next) { if (block->stackframes[j] == frame->addr) { frame->blocks++; frame->size += block->size; break; } } if (!frame) { frame = malloc(sizeof(profiler_stackframe)); frame->addr = block->stackframes[j]; frame->blocks = 1; frame->size = block->size; frame->symbol = strdup(symbols[j]); frame->children = NULL; frame->next = tree_cur->children; tree_cur->children = frame; } tree_cur = frame; } free(symbols); #endif } } #ifdef HAVE_EXECINFO_H profiler_dump_frame(0, tree->children, minsize); #endif len = sprintf(str, "--------------- memory profiler summary ---------------\n" "total blocks: %u\n" "total size: %"G_GSIZE_FORMAT" %s\n" "heap base / break / size: %p / %p / %"G_GSIZE_FORMAT"\n", total_blocks, (total_size > 1024) ? total_size / 1024 : total_size, (total_size > 1024) ? "kilobytes" : "bytes", profiler_heap_base, sbrk(0), (guintptr)sbrk(0) - (guintptr)profiler_heap_base ); profiler_write(str, len); len = sprintf(str, "--------------- memory profiler dump end ---------------\n"); free(tree); profiler_write(str, len); g_static_mutex_unlock(&profiler_mutex); } void li_profiler_hashtable_insert(const gpointer addr, gsize size) { profiler_block *block; guint hash; g_static_mutex_lock(&profiler_mutex); hash = profiler_hash(addr); block = profiler_block_new(); block->addr = addr; block->size = size; #ifdef HAVE_EXECINFO_H block->stackframes_num = backtrace(block->stackframes, PROFILER_STACKFRAMES); #endif block->next = profiler_hashtable[hash % PROFILER_HASHTABLE_SIZE]; profiler_hashtable[hash % PROFILER_HASHTABLE_SIZE] = block; g_static_mutex_unlock(&profiler_mutex); } void li_profiler_hashtable_remove(const gpointer addr) { profiler_block *block, *block_prev; guint hash; g_static_mutex_lock(&profiler_mutex); hash = profiler_hash(addr); block = profiler_hashtable[hash % PROFILER_HASHTABLE_SIZE]; if (block->addr == addr) { profiler_hashtable[hash % PROFILER_HASHTABLE_SIZE] = block->next; profiler_block_free(block); g_static_mutex_unlock(&profiler_mutex); return; } block_prev = block; for (block = block->next; block != NULL; block = block->next) { if (block->addr == addr) { block_prev->next = block->next; profiler_block_free(block); g_static_mutex_unlock(&profiler_mutex); return; } block_prev = block; } g_static_mutex_unlock(&profiler_mutex); }