diff --git a/main/asterisk.c b/main/asterisk.c index 637bf1277d0947c88f49dffd823690c99f57b49b..6085e9589e800d04eacb70883b2d4b131f080efb 100644 --- a/main/asterisk.c +++ b/main/asterisk.c @@ -3965,6 +3965,10 @@ int main(int argc, char *argv[]) * an Asterisk instance, and that there isn't one already running. */ multi_thread_safe = 1; +#if defined(__AST_DEBUG_MALLOC) + __ast_mm_init_phase_1(); +#endif /* defined(__AST_DEBUG_MALLOC) */ + /* Spawning of astcanary must happen AFTER the call to daemon(3) */ if (isroot && ast_opt_high_priority) { snprintf(canary_filename, sizeof(canary_filename), "%s/alt.asterisk.canary.tweet.tweet.tweet", ast_config_AST_RUN_DIR); @@ -4034,10 +4038,6 @@ int main(int argc, char *argv[]) ast_el_read_history(filename); } -#if defined(__AST_DEBUG_MALLOC) - __ast_mm_init_phase_1(); -#endif /* defined(__AST_DEBUG_MALLOC) */ - ast_ulaw_init(); ast_alaw_init(); tdd_init(); @@ -4271,9 +4271,9 @@ int main(int argc, char *argv[]) pthread_sigmask(SIG_UNBLOCK, &sigs, NULL); -#ifdef __AST_DEBUG_MALLOC +#if defined(__AST_DEBUG_MALLOC) __ast_mm_init_phase_2(); -#endif +#endif /* defined(__AST_DEBUG_MALLOC) */ ast_lastreloadtime = ast_startuptime = ast_tvnow(); ast_cli_register_multiple(cli_asterisk, ARRAY_LEN(cli_asterisk)); diff --git a/main/astmm.c b/main/astmm.c index d26ba945c933247cb7f5ead7b246b8f03789b3c1..1283de2dfea3d869eacb0e3b12684f9cf141cc3e 100644 --- a/main/astmm.c +++ b/main/astmm.c @@ -43,7 +43,12 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$") #include "asterisk/strings.h" #include "asterisk/unaligned.h" -#define SOME_PRIME 563 +/*! + * The larger the number the faster memory can be freed. + * However, more memory then is used for the regions[] hash + * table. + */ +#define SOME_PRIME 1567 enum func_type { FUNC_CALLOC = 1, @@ -72,7 +77,7 @@ enum func_type { static FILE *mmlog; struct ast_region { - struct ast_region *next; + AST_LIST_ENTRY(ast_region) node; size_t len; unsigned int cache; /* region was allocated as part of a cache pool */ unsigned int lineno; @@ -121,6 +126,22 @@ static struct ast_freed_regions whales; /*! Small memory blocks that have been freed. */ static struct ast_freed_regions minnows; +enum summary_opts { + /*! No summary at exit. */ + SUMMARY_OFF, + /*! Bit set if summary by line at exit. */ + SUMMARY_BY_LINE = (1 << 0), + /*! Bit set if summary by function at exit. */ + SUMMARY_BY_FUNC = (1 << 1), + /*! Bit set if summary by file at exit. */ + SUMMARY_BY_FILE = (1 << 2), +}; + +/*! Summary options of unfreed regions at exit. */ +static enum summary_opts atexit_summary; +/*! Nonzero if the unfreed regions are listed at exit. */ +static int atexit_list; + #define HASH(a) (((unsigned long)(a)) % ARRAY_LEN(regions)) /*! Tracking this mutex will cause infinite recursion, as the mutex tracking @@ -189,7 +210,7 @@ static void *__ast_alloc_region(size_t size, const enum func_type which, const c hash = HASH(reg->data); ast_mutex_lock(®lock); - reg->next = regions[hash]; + AST_LIST_NEXT(reg, node) = regions[hash]; regions[hash] = reg; ast_mutex_unlock(®lock); @@ -322,12 +343,12 @@ static struct ast_region *region_remove(void *ptr) hash = HASH(ptr); ast_mutex_lock(®lock); - for (reg = regions[hash]; reg; reg = reg->next) { + for (reg = regions[hash]; reg; reg = AST_LIST_NEXT(reg, node)) { if (reg->data == ptr) { if (prev) { - prev->next = reg->next; + AST_LIST_NEXT(prev, node) = AST_LIST_NEXT(reg, node); } else { - regions[hash] = reg->next; + regions[hash] = AST_LIST_NEXT(reg, node); } break; } @@ -369,6 +390,26 @@ static void region_check_fences(struct ast_region *reg) } } +/*! + * \internal + * \brief Check the fences of all regions currently allocated. + * + * \return Nothing + */ +static void regions_check_all_fences(void) +{ + int idx; + struct ast_region *reg; + + ast_mutex_lock(®lock); + for (idx = 0; idx < ARRAY_LEN(regions); ++idx) { + for (reg = regions[idx]; reg; reg = AST_LIST_NEXT(reg, node)) { + region_check_fences(reg); + } + } + ast_mutex_unlock(®lock); +} + static void __ast_free_region(void *ptr, const char *file, int lineno, const char *func) { struct ast_region *reg; @@ -449,7 +490,7 @@ static struct ast_region *region_find(void *ptr) struct ast_region *reg; hash = HASH(ptr); - for (reg = regions[hash]; reg; reg = reg->next) { + for (reg = regions[hash]; reg; reg = AST_LIST_NEXT(reg, node)) { if (reg->data == ptr) { break; } @@ -580,15 +621,116 @@ int __ast_vasprintf(char **strp, const char *fmt, va_list ap, const char *file, return size; } -static char *handle_memory_show(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a) +static char *handle_memory_atexit_list(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a) +{ + switch (cmd) { + case CLI_INIT: + e->command = "memory atexit list"; + e->usage = + "Usage: memory atexit list {on|off}\n" + " Enable dumping a list of still allocated memory segments at exit.\n"; + return NULL; + case CLI_GENERATE: + if (a->pos == 3) { + const char * const options[] = { "off", "on", NULL }; + + return ast_cli_complete(a->word, options, a->n); + } + return NULL; + } + + if (a->argc != 4) { + return CLI_SHOWUSAGE; + } + + if (ast_true(a->argv[3])) { + atexit_list = 1; + } else if (ast_false(a->argv[3])) { + atexit_list = 0; + } else { + return CLI_SHOWUSAGE; + } + + ast_cli(a->fd, "The atexit list is: %s\n", atexit_list ? "On" : "Off"); + + return CLI_SUCCESS; +} + +static char *handle_memory_atexit_summary(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a) +{ + char buf[80]; + + switch (cmd) { + case CLI_INIT: + e->command = "memory atexit summary"; + e->usage = + "Usage: memory atexit summary {off|byline|byfunc|byfile}\n" + " Summary of still allocated memory segments at exit options.\n" + " off - Disable at exit summary.\n" + " byline - Enable at exit summary by file line number.\n" + " byfunc - Enable at exit summary by function name.\n" + " byfile - Enable at exit summary by file.\n" + "\n" + " Note: byline, byfunc, and byfile are cumulative enables.\n"; + return NULL; + case CLI_GENERATE: + if (a->pos == 3) { + const char * const options[] = { "off", "byline", "byfunc", "byfile", NULL }; + + return ast_cli_complete(a->word, options, a->n); + } + return NULL; + } + + if (a->argc != 4) { + return CLI_SHOWUSAGE; + } + + if (ast_false(a->argv[3])) { + atexit_summary = SUMMARY_OFF; + } else if (!strcasecmp(a->argv[3], "byline")) { + atexit_summary |= SUMMARY_BY_LINE; + } else if (!strcasecmp(a->argv[3], "byfunc")) { + atexit_summary |= SUMMARY_BY_FUNC; + } else if (!strcasecmp(a->argv[3], "byfile")) { + atexit_summary |= SUMMARY_BY_FILE; + } else { + return CLI_SHOWUSAGE; + } + + if (atexit_summary) { + buf[0] = '\0'; + if (atexit_summary & SUMMARY_BY_LINE) { + strcat(buf, "byline"); + } + if (atexit_summary & SUMMARY_BY_FUNC) { + if (buf[0]) { + strcat(buf, " | "); + } + strcat(buf, "byfunc"); + } + if (atexit_summary & SUMMARY_BY_FILE) { + if (buf[0]) { + strcat(buf, " | "); + } + strcat(buf, "byfile"); + } + } else { + strcpy(buf, "Off"); + } + ast_cli(a->fd, "The atexit summary is: %s\n", buf); + + return CLI_SUCCESS; +} + +static char *handle_memory_show_allocations(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a) { const char *fn = NULL; struct ast_region *reg; - unsigned int x; + unsigned int idx; unsigned int len = 0; unsigned int cache_len = 0; unsigned int count = 0; - int check_anomalies; switch (cmd) { case CLI_INIT: @@ -611,33 +753,35 @@ static char *handle_memory_show(struct ast_cli_entry *e, int cmd, struct ast_cli } /* Look for historical misspelled option as well. */ - check_anomalies = fn && (!strcasecmp(fn, "anomalies") || !strcasecmp(fn, "anomolies")); + if (fn && (!strcasecmp(fn, "anomalies") || !strcasecmp(fn, "anomolies"))) { + regions_check_all_fences(); + ast_cli(a->fd, "Anomaly check complete.\n"); + return CLI_SUCCESS; + } ast_mutex_lock(®lock); - for (x = 0; x < ARRAY_LEN(regions); x++) { - for (reg = regions[x]; reg; reg = reg->next) { - if (check_anomalies) { - region_check_fences(reg); - } else if (!fn || !strcasecmp(fn, reg->file)) { - region_check_fences(reg); - - ast_cli(a->fd, "%10u bytes allocated%s by %20s() line %5u of %s\n", - (unsigned int) reg->len, reg->cache ? " (cache)" : "", - reg->func, reg->lineno, reg->file); - - len += reg->len; - if (reg->cache) { - cache_len += reg->len; - } - count++; + for (idx = 0; idx < ARRAY_LEN(regions); ++idx) { + for (reg = regions[idx]; reg; reg = AST_LIST_NEXT(reg, node)) { + if (fn && strcasecmp(fn, reg->file)) { + continue; + } + + region_check_fences(reg); + + ast_cli(a->fd, "%10u bytes allocated%s by %20s() line %5u of %s\n", + (unsigned int) reg->len, reg->cache ? " (cache)" : "", + reg->func, reg->lineno, reg->file); + + len += reg->len; + if (reg->cache) { + cache_len += reg->len; } + ++count; } } ast_mutex_unlock(®lock); - if (check_anomalies) { - ast_cli(a->fd, "Anomaly check complete.\n"); - } else if (cache_len) { + if (cache_len) { ast_cli(a->fd, "%u bytes allocated (%u in caches) in %u allocations\n", len, cache_len, count); } else { @@ -687,7 +831,7 @@ static char *handle_memory_show_summary(struct ast_cli_entry *e, int cmd, struct ast_mutex_lock(®lock); for (idx = 0; idx < ARRAY_LEN(regions); ++idx) { - for (reg = regions[idx]; reg; reg = reg->next) { + for (reg = regions[idx]; reg; reg = AST_LIST_NEXT(reg, node)) { if (fn) { if (strcasecmp(fn, reg->file)) { continue; @@ -783,10 +927,404 @@ static char *handle_memory_show_summary(struct ast_cli_entry *e, int cmd, struct } static struct ast_cli_entry cli_memory[] = { - AST_CLI_DEFINE(handle_memory_show, "Display outstanding memory allocations"), + AST_CLI_DEFINE(handle_memory_atexit_list, "Enable memory allocations not freed at exit list."), + AST_CLI_DEFINE(handle_memory_atexit_summary, "Enable memory allocations not freed at exit summary."), + AST_CLI_DEFINE(handle_memory_show_allocations, "Display outstanding memory allocations"), AST_CLI_DEFINE(handle_memory_show_summary, "Summarize outstanding memory allocations"), }; +AST_LIST_HEAD_NOLOCK(region_list, ast_region); + +/*! + * \internal + * \brief Convert the allocated regions hash table to a list. + * + * \param list Fill list with the allocated regions. + * + * \details + * Take all allocated regions from the regions[] and put them + * into the list. + * + * \note reglock must be locked before calling. + * + * \note This function is destructive to the regions[] lists. + * + * \return Length of list created. + */ +static size_t mm_atexit_hash_list(struct region_list *list) +{ + struct ast_region *reg; + size_t total_length; + int idx; + + total_length = 0; + for (idx = 0; idx < ARRAY_LEN(regions); ++idx) { + while ((reg = regions[idx])) { + regions[idx] = AST_LIST_NEXT(reg, node); + AST_LIST_NEXT(reg, node) = NULL; + AST_LIST_INSERT_HEAD(list, reg, node); + ++total_length; + } + } + return total_length; +} + +/*! + * \internal + * \brief Put the regions list into the allocated regions hash table. + * + * \param list List to put into the allocated regions hash table. + * + * \note reglock must be locked before calling. + * + * \return Nothing + */ +static void mm_atexit_hash_restore(struct region_list *list) +{ + struct ast_region *reg; + int hash; + + while ((reg = AST_LIST_REMOVE_HEAD(list, node))) { + hash = HASH(reg->data); + AST_LIST_NEXT(reg, node) = regions[hash]; + regions[hash] = reg; + } +} + +/*! + * \internal + * \brief Sort regions comparision. + * + * \param left Region to compare. + * \param right Region to compare. + * + * \retval <0 if left < right + * \retval =0 if left == right + * \retval >0 if left > right + */ +static int mm_atexit_cmp(struct ast_region *left, struct ast_region *right) +{ + int cmp; + ptrdiff_t cmp_ptr; + ssize_t cmp_size; + + /* Sort by filename. */ + cmp = strcmp(left->file, right->file); + if (cmp) { + return cmp; + } + + /* Sort by line number. */ + cmp = left->lineno - right->lineno; + if (cmp) { + return cmp; + } + + /* Sort by allocated size. */ + cmp_size = left->len - right->len; + if (cmp_size) { + if (cmp_size < 0) { + return -1; + } + return 1; + } + + /* Sort by allocated pointers just because. */ + cmp_ptr = left->data - right->data; + if (cmp_ptr) { + if (cmp_ptr < 0) { + return -1; + } + return 1; + } + + return 0; +} + +/*! + * \internal + * \brief Merge the given sorted sublists into sorted order onto the end of the list. + * + * \param list Merge sublists onto this list. + * \param sub1 First sublist to merge. + * \param sub2 Second sublist to merge. + * + * \return Nothing + */ +static void mm_atexit_list_merge(struct region_list *list, struct region_list *sub1, struct region_list *sub2) +{ + struct ast_region *reg; + + for (;;) { + if (AST_LIST_EMPTY(sub1)) { + /* The remaining sublist goes onto the list. */ + AST_LIST_APPEND_LIST(list, sub2, node); + break; + } + if (AST_LIST_EMPTY(sub2)) { + /* The remaining sublist goes onto the list. */ + AST_LIST_APPEND_LIST(list, sub1, node); + break; + } + + if (mm_atexit_cmp(AST_LIST_FIRST(sub1), AST_LIST_FIRST(sub2)) <= 0) { + reg = AST_LIST_REMOVE_HEAD(sub1, node); + } else { + reg = AST_LIST_REMOVE_HEAD(sub2, node); + } + AST_LIST_INSERT_TAIL(list, reg, node); + } +} + +/*! + * \internal + * \brief Take sublists off of the given list. + * + * \param list Source list to remove sublists from the beginning of list. + * \param sub Array of sublists to fill. (Lists are empty on entry.) + * \param num_lists Number of lists to remove from the source list. + * \param size Size of the sublists to remove. + * \param remaining Remaining number of elements on the source list. + * + * \return Nothing + */ +static void mm_atexit_list_split(struct region_list *list, struct region_list sub[], size_t num_lists, size_t size, size_t *remaining) +{ + int idx; + + for (idx = 0; idx < num_lists; ++idx) { + size_t count; + + if (*remaining < size) { + /* The remaining source list goes onto the sublist. */ + AST_LIST_APPEND_LIST(&sub[idx], list, node); + *remaining = 0; + break; + } + + /* Take a sublist off the beginning of the source list. */ + *remaining -= size; + for (count = size; count--;) { + struct ast_region *reg; + + reg = AST_LIST_REMOVE_HEAD(list, node); + AST_LIST_INSERT_TAIL(&sub[idx], reg, node); + } + } +} + +/*! + * \internal + * \brief Sort the regions list using mergesort. + * + * \param list Allocated regions list to sort. + * \param length Length of the list. + * + * \return Nothing + */ +static void mm_atexit_list_sort(struct region_list *list, size_t length) +{ + /*! Semi-sorted merged list. */ + struct region_list merged = AST_LIST_HEAD_NOLOCK_INIT_VALUE; + /*! Sublists to merge. (Can only merge two sublists at this time.) */ + struct region_list sub[2] = { + AST_LIST_HEAD_NOLOCK_INIT_VALUE, + AST_LIST_HEAD_NOLOCK_INIT_VALUE + }; + /*! Sublist size. */ + size_t size = 1; + /*! Remaining elements in the list. */ + size_t remaining; + /*! Number of sublist merge passes to process the list. */ + int passes; + + for (;;) { + remaining = length; + + passes = 0; + while (!AST_LIST_EMPTY(list)) { + mm_atexit_list_split(list, sub, ARRAY_LEN(sub), size, &remaining); + mm_atexit_list_merge(&merged, &sub[0], &sub[1]); + ++passes; + } + AST_LIST_APPEND_LIST(list, &merged, node); + if (passes <= 1) { + /* The list is now sorted. */ + break; + } + + /* Double the sublist size to remove for next round. */ + size <<= 1; + } +} + +/*! + * \internal + * \brief List all regions currently allocated. + * + * \param alloced regions list. + * + * \return Nothing + */ +static void mm_atexit_regions_list(struct region_list *alloced) +{ + struct ast_region *reg; + + AST_LIST_TRAVERSE(alloced, reg, node) { + astmm_log("%s %s() line %u: %u bytes%s at %p\n", + reg->file, reg->func, reg->lineno, + (unsigned int) reg->len, reg->cache ? " (cache)" : "", reg->data); + } +} + +/*! + * \internal + * \brief Summarize all regions currently allocated. + * + * \param alloced Sorted regions list. + * + * \return Nothing + */ +static void mm_atexit_regions_summary(struct region_list *alloced) +{ + struct ast_region *reg; + struct ast_region *next; + struct { + unsigned int count; + unsigned int len; + unsigned int cache_len; + } by_line, by_func, by_file, total; + + by_line.count = 0; + by_line.len = 0; + by_line.cache_len = 0; + + by_func.count = 0; + by_func.len = 0; + by_func.cache_len = 0; + + by_file.count = 0; + by_file.len = 0; + by_file.cache_len = 0; + + total.count = 0; + total.len = 0; + total.cache_len = 0; + + AST_LIST_TRAVERSE(alloced, reg, node) { + next = AST_LIST_NEXT(reg, node); + + ++by_line.count; + by_line.len += reg->len; + if (reg->cache) { + by_line.cache_len += reg->len; + } + if (next && !strcmp(reg->file, next->file) && reg->lineno == next->lineno) { + continue; + } + if (atexit_summary & SUMMARY_BY_LINE) { + if (by_line.cache_len) { + astmm_log("%10u bytes (%u in caches) in %u allocations. %s %s() line %u\n", + by_line.len, by_line.cache_len, by_line.count, reg->file, reg->func, reg->lineno); + } else { + astmm_log("%10u bytes in %5u allocations. %s %s() line %u\n", + by_line.len, by_line.count, reg->file, reg->func, reg->lineno); + } + } + + by_func.count += by_line.count; + by_func.len += by_line.len; + by_func.cache_len += by_line.cache_len; + by_line.count = 0; + by_line.len = 0; + by_line.cache_len = 0; + if (next && !strcmp(reg->file, next->file) && !strcmp(reg->func, next->func)) { + continue; + } + if (atexit_summary & SUMMARY_BY_FUNC) { + if (by_func.cache_len) { + astmm_log("%10u bytes (%u in caches) in %u allocations. %s %s()\n", + by_func.len, by_func.cache_len, by_func.count, reg->file, reg->func); + } else { + astmm_log("%10u bytes in %5u allocations. %s %s()\n", + by_func.len, by_func.count, reg->file, reg->func); + } + } + + by_file.count += by_func.count; + by_file.len += by_func.len; + by_file.cache_len += by_func.cache_len; + by_func.count = 0; + by_func.len = 0; + by_func.cache_len = 0; + if (next && !strcmp(reg->file, next->file)) { + continue; + } + if (atexit_summary & SUMMARY_BY_FILE) { + if (by_file.cache_len) { + astmm_log("%10u bytes (%u in caches) in %u allocations. %s\n", + by_file.len, by_file.cache_len, by_file.count, reg->file); + } else { + astmm_log("%10u bytes in %5u allocations. %s\n", + by_file.len, by_file.count, reg->file); + } + } + + total.count += by_file.count; + total.len += by_file.len; + total.cache_len += by_file.cache_len; + by_file.count = 0; + by_file.len = 0; + by_file.cache_len = 0; + } + + if (total.cache_len) { + astmm_log("%u bytes (%u in caches) in %u allocations.\n", + total.len, total.cache_len, total.count); + } else { + astmm_log("%u bytes in %u allocations.\n", total.len, total.count); + } +} + +/*! + * \internal + * \brief Dump the memory allocations atexit. + * + * \note reglock must be locked before calling. + * + * \return Nothing + */ +static void mm_atexit_dump(void) +{ + struct region_list alloced_atexit = AST_LIST_HEAD_NOLOCK_INIT_VALUE; + size_t length; + + length = mm_atexit_hash_list(&alloced_atexit); + if (!length) { + /* Wow! This is amazing! */ + astmm_log("Exiting with all memory freed.\n"); + return; + } + + mm_atexit_list_sort(&alloced_atexit, length); + + astmm_log("Exiting with the following memory not freed:\n"); + if (atexit_list) { + mm_atexit_regions_list(&alloced_atexit); + } + if (atexit_summary) { + mm_atexit_regions_summary(&alloced_atexit); + } + + /* + * Put the alloced list back into regions[]. + * + * We have do do this because we can get called before all other + * threads have terminated. + */ + mm_atexit_hash_restore(&alloced_atexit); +} + /*! * \internal * \return Nothing @@ -795,10 +1333,22 @@ static void mm_atexit_final(void) { FILE *log; + fprintf(stderr, "Waiting 10 seconds to let other threads die.\n"); + sleep(10); + + regions_check_all_fences(); + /* Flush all delayed memory free circular arrays. */ freed_regions_flush(&whales); freed_regions_flush(&minnows); + /* Peform atexit allocation dumps. */ + if (atexit_list || atexit_summary) { + ast_mutex_lock(®lock); + mm_atexit_dump(); + ast_mutex_unlock(®lock); + } + /* Close the log file. */ log = mmlog; mmlog = NULL;