diff --git a/src/lighttpd.c b/src/lighttpd.c index 68c6837..89df1b2 100644 --- a/src/lighttpd.c +++ b/src/lighttpd.c @@ -1,6 +1,7 @@ #include "base.h" #include "config_parser.h" +#include "profiler.h" #ifdef HAVE_LUA_H #include "config_lua.h" @@ -33,7 +34,8 @@ int main(int argc, char *argv[]) { /* check for environment variable LIGHTY_PROFILE_MEM */ gchar *profile_mem = getenv("LIGHTY_PROFILE_MEM"); if (profile_mem != NULL && g_str_equal(profile_mem, "true")) { - g_mem_set_vtable(glib_mem_profiler_table); + /*g_mem_set_vtable(glib_mem_profiler_table);*/ + profiler_enable(); } /* parse commandline options */ @@ -132,5 +134,8 @@ int main(int argc, char *argv[]) { if (free_config_path) g_free(config_path); + if (profile_mem) + profiler_dump(); + return 0; } diff --git a/src/plugin_core.c b/src/plugin_core.c index 110539c..e68d461 100644 --- a/src/plugin_core.c +++ b/src/plugin_core.c @@ -2,6 +2,7 @@ #include "base.h" #include "plugin_core.h" #include "utils.h" +#include "profiler.h" @@ -287,7 +288,8 @@ static action_result core_handle_profile_mem(connection *con, gpointer param) { UNUSED(con); UNUSED(param); - g_mem_profile(); + /*g_mem_profile();*/ + profiler_dump(); return ACTION_GO_ON; } diff --git a/src/profiler.c b/src/profiler.c new file mode 100644 index 0000000..8be27c7 --- /dev/null +++ b/src/profiler.c @@ -0,0 +1,231 @@ + +/* lighty memory profiler + * counts how many times malloc/realloc/free have been called and the amounts of bytes allocated/freed + * TODO: move hashtable to utils.c, optimize hashtable? implementation is very basic + */ + + +#include "base.h" +#include "profiler.h" + +#define PROFILER_HASHTABLE_SIZE 1024 + + +static profiler_mem stats_mem = { 0, 0, 0, 0, 0, 0, 0, 0, 0 }; +static GStaticMutex profiler_mutex = G_STATIC_MUTEX_INIT; +static gboolean profiler_enabled = FALSE; + + +struct profiler_entry { + gpointer addr; + gsize len; + struct profiler_entry *next; +}; +typedef struct profiler_entry profiler_entry; + +static struct { + profiler_entry **nodes; +} profiler_hashtable; + +static void profiler_hashtable_init() { + profiler_hashtable.nodes = calloc(1, sizeof(profiler_entry*) * PROFILER_HASHTABLE_SIZE); +} + +static profiler_entry *profiler_hashtable_find(gpointer addr) { + guint h = (gsize)addr % PROFILER_HASHTABLE_SIZE; + + for (profiler_entry *e = profiler_hashtable.nodes[h]; e != NULL; e = e->next) { + if (e->addr == addr) + return e; + } + assert(NULL); + return NULL; +} + +static void profiler_hashtable_insert(gpointer addr, gsize len) { + profiler_entry *e = malloc(sizeof(profiler_entry)); + + e->addr = addr; + e->len = len; + e->next = NULL; + + guint h = (gsize)addr % PROFILER_HASHTABLE_SIZE; + + if (profiler_hashtable.nodes[h] == NULL) { + profiler_hashtable.nodes[h] = e; + return; + } + + for (profiler_entry *ec = profiler_hashtable.nodes[h];; ec = ec->next) { + if (ec->next == NULL) { + ec->next = e; + return; + } + } +} + +static void profiler_hashtable_remove(gpointer addr) { + guint h = (gsize)addr % PROFILER_HASHTABLE_SIZE; + profiler_entry *prev = profiler_hashtable.nodes[h]; + + if (!prev) + return; + + if (prev->addr == addr) { + if (prev->next) + profiler_hashtable.nodes[h] = prev->next; + else + profiler_hashtable.nodes[h] = NULL; + free(prev); + return; + } + + for (profiler_entry *e = prev->next; e != NULL; e = e->next) { + if (e->addr == addr) { + prev->next = e->next; + free(e); + return; + } + prev = e; + } +} + +static gpointer profiler_try_malloc(gsize n_bytes) { + /* we alloc sizeof(gsize) bytes more to hold n_bytes */ + gsize *p; + + p = malloc(n_bytes); + + if (p) { + g_static_mutex_lock(&profiler_mutex); + profiler_hashtable_insert(p, n_bytes); + stats_mem.alloc_times++; + stats_mem.alloc_bytes += n_bytes; + stats_mem.inuse_bytes += n_bytes; + g_static_mutex_unlock(&profiler_mutex); + } + + 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 l; + gsize *p = mem; + + if (!mem) { + p = malloc(n_bytes); + g_static_mutex_lock(&profiler_mutex); + stats_mem.alloc_times++; + g_static_mutex_unlock(&profiler_mutex); + l = 0; + } + else { + p = realloc(p, n_bytes); + g_static_mutex_lock(&profiler_mutex); + profiler_entry *e = profiler_hashtable_find(mem); + l = e->len; + profiler_hashtable_remove(mem); + g_static_mutex_unlock(&profiler_mutex); + } + + if (p) { + g_static_mutex_lock(&profiler_mutex); + profiler_hashtable_insert(p, n_bytes); + stats_mem.realloc_times++; + stats_mem.realloc_bytes += n_bytes; + stats_mem.inuse_bytes += n_bytes - l; + g_static_mutex_unlock(&profiler_mutex); + } + + 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) { + /* we alloc sizeof(gsize) bytes more to hold n_blocks*n_bytes */ + gsize *p; + + gsize l = n_blocks * n_bytes; + + p = calloc(1, l); + + if (p) { + g_static_mutex_lock(&profiler_mutex); + profiler_hashtable_insert(p, l); + stats_mem.calloc_times++; + stats_mem.calloc_bytes += l; + stats_mem.inuse_bytes += l; + g_static_mutex_unlock(&profiler_mutex); + } + + assert(p); + + return p; +} + +static void profiler_free(gpointer mem) { + gsize *p = mem; + + assert(p); + profiler_entry *e = profiler_hashtable_find(mem); + g_static_mutex_lock(&profiler_mutex); + stats_mem.free_times++; + stats_mem.free_bytes += e->len; + stats_mem.inuse_bytes -= e->len; + g_static_mutex_unlock(&profiler_mutex); + free(p); + profiler_hashtable_remove(mem); +} + +/* public functions */ +void profiler_enable() { + GMemVTable t; + + if (profiler_enabled) + return; + + profiler_enabled = TRUE; + + profiler_hashtable_init(); + + 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); +} + +void profiler_dump() { + if (!profiler_enabled) + return; + + g_static_mutex_lock(&profiler_mutex); + profiler_mem s = stats_mem; + g_static_mutex_unlock(&profiler_mutex); + + g_print("--- memory profiler stats ---\n"); + g_print("malloc(): called %" G_GUINT64_FORMAT " times, %" G_GUINT64_FORMAT " bytes total\n", s.alloc_times, s.alloc_bytes); + g_print("calloc(): called %" G_GUINT64_FORMAT " times, %" G_GUINT64_FORMAT " bytes total\n", s.calloc_times, s.calloc_bytes); + g_print("realloc(): called %" G_GUINT64_FORMAT " times, %" G_GUINT64_FORMAT " bytes total\n", s.realloc_times, s.realloc_bytes); + g_print("free(): called %" G_GUINT64_FORMAT " times, %" G_GUINT64_FORMAT " bytes total\n", s.free_times, s.free_bytes); + g_print("memory remaining: %" G_GUINT64_FORMAT " bytes, %" G_GUINT64_FORMAT " calls to free()\n", s.inuse_bytes, s.alloc_times + s.calloc_times - s.free_times); +} diff --git a/src/profiler.h b/src/profiler.h new file mode 100644 index 0000000..d8ca1db --- /dev/null +++ b/src/profiler.h @@ -0,0 +1,22 @@ +#ifndef _LIGHTTPD_PROFILER_H_ +#define _LIGHTTPD_PROFILER_H_ + +struct profiler_mem; +typedef struct profiler_mem profiler_mem; + +struct profiler_mem { + guint64 inuse_bytes; + guint64 alloc_times; + guint64 alloc_bytes; + guint64 calloc_times; + guint64 calloc_bytes; + guint64 realloc_times; + guint64 realloc_bytes; + guint64 free_times; + guint64 free_bytes; +}; + +void profiler_enable(); /* enables the profiler */ +void profiler_dump(); /* dumps memory statistics to stdout */ + +#endif diff --git a/src/wscript b/src/wscript index 13d1524..90542e8 100644 --- a/src/wscript +++ b/src/wscript @@ -25,6 +25,7 @@ common_source=''' network_linux_sendfile.c options.c plugin.c + profiler.c request.c response.c server.c