@ -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 id x;
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 ;