Add MALLOC_DEBUG atexit unreleased malloc memory summary.
authorRichard Mudgett <rmudgett@digium.com>
Thu, 29 Nov 2012 00:48:12 +0000 (00:48 +0000)
committerRichard Mudgett <rmudgett@digium.com>
Thu, 29 Nov 2012 00:48:12 +0000 (00:48 +0000)
* Adds the following CLI commands to control MALLOC_DEBUG reporting of
unreleased malloc memory when Asterisk is shut down.
memory atexit list on
memory atexit list off
memory atexit summary byline
memory atexit summary byfunc
memory atexit summary byfile
memory atexit summary off

* Made check all remaining allocated region blocks atexit for fence
violations.

* Increased the allocated region hash table size by about three times.  It
still isn't large enough considering the number of malloced blocks
Asterisk uses.

* Made CLI "memory show allocations anomalies" use
regions_check_all_fences().

Review: https://reviewboard.asterisk.org/r/2196/
........

Merged revisions 376788 from http://svn.asterisk.org/svn/asterisk/branches/1.8
........

Merged revisions 376789 from http://svn.asterisk.org/svn/asterisk/branches/10
........

Merged revisions 376790 from http://svn.asterisk.org/svn/asterisk/branches/11

git-svn-id: https://origsvn.digium.com/svn/asterisk/trunk@376791 65c4cc65-6c06-0410-ace0-fbb531ad65f3

main/asterisk.c
main/astmm.c

index 637bf12..6085e95 100644 (file)
@@ -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));
index d26ba94..1283de2 100644 (file)
@@ -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(&reglock);
-       reg->next = regions[hash];
+       AST_LIST_NEXT(reg, node) = regions[hash];
        regions[hash] = reg;
        ast_mutex_unlock(&reglock);
 
@@ -322,12 +343,12 @@ static struct ast_region *region_remove(void *ptr)
        hash = HASH(ptr);
 
        ast_mutex_lock(&reglock);
-       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(&reglock);
+       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(&reglock);
+}
+
 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(&reglock);
-       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(&reglock);
 
-       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(&reglock);
        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(&reglock);
+               mm_atexit_dump();
+               ast_mutex_unlock(&reglock);
+       }
+
        /* Close the log file. */
        log = mmlog;
        mmlog = NULL;