diff options
author | Chris Hall <chris.hall@highwayman.com> | 2011-07-21 19:53:02 +0100 |
---|---|---|
committer | Chris Hall <chris.hall@highwayman.com> | 2011-07-21 19:53:02 +0100 |
commit | 56da2a1c9b6361e302b7a39fe2740561a9012d88 (patch) | |
tree | 6b6543532133a0c618d0f4ec70a87cf3f96caf30 /lib/memory.c | |
parent | e535bc959729262480a9702e71334002edee3f8c (diff) | |
download | quagga-56da2a1c9b6361e302b7a39fe2740561a9012d88.tar.bz2 quagga-56da2a1c9b6361e302b7a39fe2740561a9012d88.tar.xz |
Update pipework and improve memory reporting.
Improve error handling for all new pipework inputs and outputs.
Change behaviour of ^C from VTY Terminal, so that will interrupt
output and terminate all running pipes -- including running
shell commands.
In pipe commands, recognise "~/..." and "~user/..." home directory
forms.
Changed "~/" to mean the usual home for the current user. "~~/"
now means the configuration file directory.
Introduced "shdir DIR" command to show what is (currently) what.
Changed "<|" so that if the command has a path, it is expanded
using Quagga's rules (including "~~/" and "~./") and the
"here" directory is set to that path.
Fixed collection of stderr output from all pipes so that is
separate from stdout output, and is always sent to the base
output (eg VTY Terminal).
Increase amount of information about the heap that "show mem"
shows -- particularly if the "memory_tracker" is enabled.
Tested and applied resulting fixes.
Diffstat (limited to 'lib/memory.c')
-rw-r--r-- | lib/memory.c | 3470 |
1 files changed, 3111 insertions, 359 deletions
diff --git a/lib/memory.c b/lib/memory.c index ef8551cd..efd3a83f 100644 --- a/lib/memory.c +++ b/lib/memory.c @@ -26,12 +26,145 @@ #include <malloc.h> #endif /* !HAVE_STDLIB_H || HAVE_MALLINFO */ +#include "qlib_init.h" #include "log.h" #include "memory.h" #include "qpthreads.h" -/* Needs to be qpthread safe. The system malloc etc are already - * thread safe, but we need to protect the stats +#include "vty.h" +#include "command.h" +#include "qfstring.h" + +/* HAVE_MMAP specifies that can do mmap() and mmunmap() -- below */ +#define HAVE_MMAP 1 + +/* HAVE_MEM_REGIONS -- pro tem ! */ +#if defined(GNU_LINUX) && !defined(NO_MEM_REGIONS) +#define HAVE_MEM_REGIONS +#endif + +#ifdef HAVE_MMAP +#include <sys/mman.h> +#endif + +#ifdef HAVE_MEM_REGIONS + +#undef HAVE_MEM_REGIONS +#define HAVE_MEM_REGIONS 1 + + enum { memory_regions = HAVE_MEM_REGIONS } ; + +#include <stdio.h> +#include <ctype.h> +#include <string.h> +#include <unistd.h> + +#else + enum { memory_regions = 0 } ; +#endif + + +/*============================================================================== + * Here we wrap the usual malloc()/calloc()/realloc()/free()/strdup() functions + * with various amounts of statics gathering. + * + * When memory is allocated/deallocated the type of that memory must be + * specified -- see memtypes.c. + * + * The basic statistics gathering counts, for each memory type, the number of + * objects which have been allocated and not yet freed -- so knows the current + * number of allocated objects. (The counts depend on realloc() and free() + * being given the same memory type as the original allocation.) + * + *------------------------------------------------------------------------------ + * The "memory_tracker" + * + * The (debug) "memory_tracker" statistics keep the address, size and type + * for every allocated object, and the name of the function which allocated + * the object (or last realloc()'d) it. This adds 24..32 bytes per object, + * so is not cheap ! The number of alloc/realloc/free operations for each + * type of memory is also recorded, as are the peak number of objects and the + * peak memory allocation. These statistics allow: + * + * * tracking of size of allocated memory + * + * * the malloc() overhead for allocated memory + * (assuming the mem_tracker_start() function can establish this. + * + * * checking that realloc() and free() + * + * a. specify the address of a previously allocated object. + * + * b. specify the same type as the original allocation + * + * * reporting of the total activity -- alloc/realloc/free -- by memory type + * + * * reporting of peak object counts and size -- by memory type. + * + * The extra data saved by the memory_tracker is put (if at all possible) into + * lumps of memory separate from malloc() -- allocated by mmap(). + * + *------------------------------------------------------------------------------ + * The "memory_logger" + * + * The (debug) "memory_logger" outputs a logging message for every memory + * operation. + * + * The "memory_logger" overrides the "memory tracker". + * + *------------------------------------------------------------------------------ + * mallinfo() + * + * This is a GNU extension in glibc. Sadly, the mallinfo data structure is + * defined in terms of "int", and there seem to be no plans to extend that to + * support larger amounts of memory -- beyond 4G. + * + *------------------------------------------------------------------------------ + * mmap() and munmap() + * + * These are Posix facilities. Most implementations support (MAP_ANON or + * MAP_ANONYMOUS), though that is not Posix. The Posix posix_typed+mem_open() + * can be used if MAP_ANON or MAP_ANONYMOUS is not supported. + * + * Late model malloc() uses mmap(), so memory may be allocated across a number + * of distinct regions. + * + * The "memory_tracker" will use mmap() allocated regions, if at all possible, + * for all the tracked data. + * + *------------------------------------------------------------------------------ + * Memory Regions + * + * With Linux /proc/self/maps provides a list of all regions of memory. Also, + * /proc/self/pagemap can be used to discover which pages in each region are + * in memory and which are swapped out (and which are neither). + * + * Using these it is possible to provide some information about total memory + * usage -- partly replacing mallinfo(). + * + * With memory_tracker, can work out which regions form part of the heap, and + * how much of each region is in use, taken up by overhead, or currently + * free. + * + * Without memory_tracker there is no way to tell which regions are part of + * the heap, apart from the initial heap region. So the summary information + * may show a number of "Anon" regions, some of which are, in fact, heap. + * + * The extra data saved by the memory_regions is put (if at all possible) into + * lumps of memory separate from malloc() -- allocated by mmap(). + * + *------------------------------------------------------------------------------ + * malloc() overhead + * + * There is an attempt to establish the amount of redtape on each malloc() + * allocation, the minimum size of a malloc() block and the unit of allocation + * after that. This is used by the memory_tracker to identify the space in + * the heap which is "overhead". + */ + +/*------------------------------------------------------------------------------ + * Need to be qpthread safe. The system malloc etc are already thread safe, + * but we need to protect the statistics. */ static qpt_mutex_t memory_mutex; @@ -40,57 +173,380 @@ static qpt_mutex_t memory_mutex; static void log_memstats(int log_priority); -static const struct message mstr [] = +/*------------------------------------------------------------------------------ + * Basic statistics + */ +static mem_stats_t mstats ; /* zeroised in memory_start() */ + +static size_t mem_pagesize = 0 ; /* set in memory_start() */ + +/*------------------------------------------------------------------------------ + * Showing stuff + */ +QFB_T(num_str, 30) ; + +/*------------------------------------------------------------------------------ + * Read in the mem_type_map array: MTYPE_XXX -> "MTYPE_XXX" + */ +#define MEM_MTYPE_MAP_REQUIRED 1 +#include "memtypes.h" +CONFIRM((sizeof(mem_mtype_map) / sizeof(char*)) == MTYPE_MAX) ; + +static inline const char* +mem_mtype_name(mtype_t mtype) { - { MTYPE_THREAD, "thread" }, - { MTYPE_THREAD_MASTER, "thread_master" }, - { MTYPE_VECTOR, "vector" }, - { MTYPE_VECTOR_BODY, "vector_index" }, - { MTYPE_IF, "interface" }, - { 0, NULL }, -}; + return (mtype < MTYPE_MAX) ? mem_mtype_map[mtype] : "*unknown mtype*" ; +} /*------------------------------------------------------------------------------ - * Include the memory tracker, if required. + * Include the optional memory tracker -- MEMORY_TRACKER. * - * Makes sure that the tracker is always part of the source -- so kept up to - * date -- depending on dead code removal to eliminate overhead when not - * used. + * The memory tracker is always part of the source -- so kept up to date. + * We depend on dead code removal to eliminate overhead when the tracker is + * not used. */ -typedef struct mem_tracker* mem_tracker ; -struct mem_tracker +typedef struct mem_tracker_item* mem_tracker_item ; +typedef struct mem_tracker_item mem_tracker_item_t ; + +struct mem_tracker_item { - uint64_t malloc_count ; - uint64_t realloc_count ; - uint64_t free_count ; + ulong malloc_count ; /* number of mallocs */ + ulong realloc_count ; /* number of reallocs */ + ulong free_count ; /* number of frees */ - uint32_t tracked_count ; - size_t tracked_size ; + ulong item_count ; /* number of existing items */ + size_t total_size ; /* total size of existing items */ - uint32_t tracked_max_count ; - size_t tracked_max_size ; + ulong peak_item_count ; /* peak count of items */ + size_t peak_total_size ; /* peak size of items */ } ; -static void -mem_tracker_zeroise(struct mem_tracker* mem) +/*------------------------------------------------------------------------------ + * Memory Region Handling -- based on /proc/self/maps + * and /proc/self/pagemap + */ +enum mem_region_flags { - memset(mem, 0, sizeof(struct mem_tracker)) ; + mrf_read = BIT(0), + mrf_write = BIT(1), + mrf_execute = BIT(2), + mrf_private = BIT(3), + mrf_shared = BIT(4), } ; +typedef enum mem_region_flags mem_region_flags_t ; -#include "mem_tracker.c" +enum mem_region_type +{ + mrt_anon, + mrt_named, + mrt_heap, + mrt_stack, + mrt_vdso, + mrt_vsyscall, + mrt_other, + + mrt_type_count, +} ; +typedef enum mem_region_type mem_region_type_t ; -/*============================================================================== - * Keeping track of number of allocated objects of given type +static const char mrf_type_chars[mrt_type_count] = +{ + [mrt_anon] = 'A', + [mrt_named] = '=', + [mrt_heap] = 'H', + [mrt_stack] = 'S', + [mrt_vdso] = 'v', + [mrt_vsyscall] = 'w', + [mrt_other] = '~', +} ; + +enum mem_region_sex +{ + mrx_code = 0, /* mrt_named & mrf_read & !mrf_write & mrf_execute */ + /* mrt_vdso & mrf_read & !mrf_write & mrf_execute */ + /* mrt_vsyscall & mrf_read & !mrf_write & mrf_execute */ + mrx_const, /* mrt_named & mrf_read & !mrf_write & !mrf_execute */ + mrx_data, /* mrt_named & mrf_read & mrf_write & !mrf_execute */ + mrx_reserved, /* mrt_name & !mrf_read & !mrf_write & !mrf_execute */ + mrx_gap, /* mrt_anon & !mrf_read & !mrf_write & !mrf_execute */ + mrx_exotic, /* anything else */ + + /* Note that the stack and anon regions immediately precede the heap stuff */ + + mrx_stack, /* mrt_stack & mrf_read & mrf_write & !mrf_execute */ + mrx_anon, /* mrt_anon & mrf_read & mrf_write & !mrf_execute */ + + mrx_rest_total, /* subtotal of all not heap */ + + mrx_heap, /* start of heap information */ + + mrx_heap_main = mrx_heap, + /* mrt_heap & mrf_read & mrf_write & !mrf_execute */ + mrx_heap_extra, /* mrx_anon & seen by memory_tracker */ + + mrx_heap_total, /* subtotal of heap_main and heap extra */ + + mrx_grand_total, /* total of everything */ + + mrx_sex_count, /* number of mrx items */ + + mrx_heap_count = mrx_heap_total - mrx_heap, + /* number of heap type items */ +} ; + +typedef enum mem_region_sex mem_region_sex_t ; + +static const char* mrx_sex_names[mrx_sex_count] = +{ + [mrx_heap_main] = "Heap ", + [mrx_heap_extra] = "Extra ", + + [mrx_code] = "Code ", + [mrx_const] = "Const ", + [mrx_data] = "Data ", + [mrx_reserved] = "Resrvd", + [mrx_gap] = "Gap ", + [mrx_exotic] = "Exotic", + [mrx_stack] = "Stack ", + [mrx_anon] = "Anon ", + + [mrx_heap_total] = "++++++", + [mrx_rest_total] = "++++++", + [mrx_grand_total] = " Total", +} ; + +enum { mem_region_max_name_len = 127 } ; + +typedef struct mem_region mem_region_t ; +typedef struct mem_region* mem_region ; +typedef const struct mem_region* mem_region_c ; + +struct mem_region +{ + uintptr_t base ; + uintptr_t limit ; + + mem_region_flags_t flags ; + mem_region_type_t type ; + mem_region_sex_t sex ; + + size_t offset ; + + uint dev_major ; + uint dev_minor ; + ullong inode ; + + char name[mem_region_max_name_len + 1] ; + + size_t present ; + size_t swapped ; + + uintptr_t min_address ; + uintptr_t max_address ; + + size_t count ; + size_t used ; + size_t overhead ; +} ; + +typedef struct mem_region_summary mem_region_summary_t ; +typedef struct mem_region_summary* mem_region_summary ; + +struct mem_region_summary +{ + mem_region_sex_t sex ; + + size_t size ; + size_t present ; + size_t swapped ; + + size_t count ; + size_t used ; + size_t overhead ; + size_t unused ; + size_t data ; +} ; + +/*------------------------------------------------------------------------------ + * memory_tracker definitions + */ +typedef struct mem_descriptor mem_descriptor_t ; +typedef struct mem_descriptor* mem_descriptor ; +struct mem_descriptor +{ + void* addr ; + const char* name ; + + uint32_t next ; /* MS Type is encoded as MS 4 bits */ + uint32_t size ; /* LS Type is encoded as MS 4 bits */ +} ; + +typedef uint32_t md_index_t ; + +enum +{ + md_next_bits = 28, /* up to 256M allocated objects */ + md_next_mask = (1 << md_next_bits) - 1, + + md_index_max = md_next_mask + 1, + + md_size_bits = 28, /* up to 256M individual item */ + md_size_mask = (1 << md_size_bits) - 1, + + md_size_max = md_size_mask, + + md_next_type_bits = 32 - md_next_bits, + md_next_type_mask = (1 << md_next_type_bits) - 1, + md_size_type_bits = 32 - md_size_bits, + md_size_type_mask = (1 << md_size_type_bits) - 1, + + md_i_index_bits = 16, + md_i_index_count = 1 << md_i_index_bits, + md_i_index_mask = md_i_index_count - 1, + + md_page_bits = md_next_bits - md_i_index_bits, + md_page_count = 1 << md_page_bits, + md_page_mask = md_page_count - 1, +} ; + +CONFIRM(MTYPE_MAX < (1 << (md_next_type_bits + md_size_type_bits))) ; + +/* The structure in which all mem_tracker stuff is held. + * + * Note that for the total memory, does not bother to keep: + * + * malloc_count + * realloc_count + * free_count + * + * up to date on every alloc/free -- those can be produced by adding up the + * typ items. + * + * Does keep: + * + * item_count + * total_size + * + * up to date on every alloc/free, so that can maintain: + * + * peak_item_count + * peak_total_size + */ +typedef struct mem_tracker_data mem_tracker_data_t ; +typedef struct mem_tracker_data* mem_tracker_data ; + +struct mem_tracker_data +{ + mem_tracker_item_t tot[1] ; /* Total memory */ + mem_tracker_item_t typ[MTYPE_MAX] ; /* Per memory type */ + + size_t tracker_overhead ; /* Bytes used by tracker */ +} ; + +/* All the mem_tracker data is kept together to facilitate copying of same + * to take snapshot to output from. */ +static mem_tracker_data_t mem_mt_data ; + +static mem_descriptor* mem_page_table = NULL ; +static mem_descriptor mem_free_descriptors = NULL ; +static md_index_t mem_next_index = 0 ; + +static uint32_t mem_base_count = 0 ; +static md_index_t* mem_bases = NULL ; + +/*------------------------------------------------------------------------------ + * In order to measure heap usage, we try to establish the overhead and + * padding in the blocks returned by malloc() -- see mem_alloc_discover(). + * + * Have to make a number of assumptions: + * + * a. overhead comprises: + * + * * a fixed amount of "red-tape" + * + * * a minimum allocation + * + * * a fixed unit of allocation for anything above minimum + * + * Note that this assumes that malloc() does *not* for larger blocks + * allocate larger amounts of unused space at the end of a block. + * (Which would be a way of avoiding moving memory around on some + * realloc() requests -- but those are rare.) + * + * b. at start time: + * + * * malloc() will allocate blocks at the "edge" of the heap. + * + * * free() will release blocks, which will be immediately be + * reused. + * + * This appears reasonable, but... who can tell ? The code that tries to + * establish these parameters is quite careful to check that its conclusions + * are consistent -- but there is limited intelligence here. + */ +bool mem_alloc_known = false ; /* set true when think know what + the overhead & padding is. */ + +uintptr_t mem_alloc_first = 0 ; /* address of first allocation */ +size_t mem_alloc_min_size = 0 ; /* minimum size block */ +size_t mem_alloc_redtape = 0 ; /* redtape per block */ +size_t mem_alloc_min_max = 0 ; /* maximum allocation in minimum + size block */ +size_t mem_alloc_unit = 0 ; /* unit beyond minimum size */ + +size_t mem_alloc_add = 0 ; /* see mem_alloc_padding() */ + +char* mem_alloc_trial_addr = NULL ; /* final trial results */ +size_t mem_alloc_trial_size = 0 ; +size_t mem_alloc_trial_alloc = 0 ; + +enum +{ + ma_not_tried, + + ma_not_a_before_b, + ma_not_increasing_size, + ma_unit_not_power_2, + ma_address_not_n_unit, + ma_size_min_not_n_unit, + ma_trial_failed, + +} mem_alloc_failed = ma_not_tried ; -static struct mstat +/*------------------------------------------------------------------------------ + * Extra counting performed by optional logger -- MEMORY_LOGGER + * + * The memory logger is always part of the source -- so kept up to date. + * We depend on dead code removal to eliminate overhead when the tracker is + * not used. + */ +static struct /* TODO: establish what this is for ! */ { - struct - { - char *name ; - long alloc ; - } mt[MTYPE_MAX] ; -} mstat ; + const char *name; + ulong alloc; + ulong t_malloc; + ulong c_malloc; + ulong t_calloc; + ulong c_calloc; + ulong t_realloc; + ulong t_free; + ulong c_strdup; +} mlog_stat [MTYPE_MAX]; + +/*============================================================================== + * Function definitions. + */ +inline static void mem_mt_malloc(mtype_t mtype, void* address, + size_t size, const char* name) ; +inline static void mem_mt_free(mtype_t mtype, void* address) ; +inline static void mem_mt_realloc(mtype_t mtype, void* old_address, + void* new_address, + size_t new_size, const char* name) ; +static void mem_mt_get_stats(mem_tracker_data mtd, bool locked) ; + +static void mtype_log (const char *func, void *memory, mtype_t mtype, + const char *file, int line) ; /*============================================================================== * Memory allocation functions. @@ -98,12 +554,15 @@ static struct mstat * NB: failure to allocate is FATAL -- so no need to test return value. */ -/* Fatal memory allocation error occured. */ +/*------------------------------------------------------------------------------ + * Fatal memory allocation error occured. + */ static void __attribute__ ((noreturn)) -zerror (const char *fname, int type, size_t size) +zerror (const char *fname, mtype_t mtype, size_t size) { - zlog_err ("%s : can't allocate memory for `%s' size %d: %s\n", - fname, lookup (mstr, type), (int) size, errtoa(errno, 0).str); + zlog_err ("%s : can't allocate memory for '%s'(%d) size %zd: %s\n", + fname, mem_mtype_name(mtype), mtype, size, errtoa(errno, 0).str) ; + log_memstats(LOG_WARNING); /* N.B. It might be preferable to call zlog_backtrace_sigsafe here, since that function should definitely be safe in an OOM condition. But @@ -111,7 +570,7 @@ zerror (const char *fname, int type, size_t size) this time... */ zlog_backtrace(LOG_WARNING); zabort_abort(); -} +} ; /*------------------------------------------------------------------------------ * Memory allocation. @@ -122,8 +581,8 @@ zerror (const char *fname, int type, size_t size) * * NB: If memory cannot be allocated, aborts execution. */ -void * -zmalloc (enum MTYPE mtype, size_t size MEMORY_TRACKER_NAME) +extern void * +zmalloc (mtype_t mtype, size_t size MEMORY_EXTRA_ARGS) { void *memory; @@ -138,23 +597,33 @@ zmalloc (enum MTYPE mtype, size_t size MEMORY_TRACKER_NAME) } else { - mstat.mt[mtype].alloc++; + mstats.alloc[mtype]++; + + if (memory_logger) + { + mlog_stat[mtype].c_malloc++; + mlog_stat[mtype].t_malloc++; + } ; + if (memory_tracker) - mem_md_malloc(mtype, memory, size, MEMORY_TRACKER_NAME_ARG) ; + mem_mt_malloc(mtype, memory, size, MEMORY_TRACKER_ARG) ; UNLOCK ; + + if (memory_logger) + mtype_log("zmalloc", memory, mtype, MEMORY_LOGGING_ARGS) ; } ; - return memory; -} + return memory ; +} ; /*------------------------------------------------------------------------------ * Memory allocation zeroising the allocated area. * * As zmalloc, plus zeroises the allocated memory. */ -void * -zcalloc (enum MTYPE mtype, size_t size MEMORY_TRACKER_NAME) +extern void * +zcalloc (mtype_t mtype, size_t size MEMORY_EXTRA_ARGS) { void *memory; @@ -169,10 +638,21 @@ zcalloc (enum MTYPE mtype, size_t size MEMORY_TRACKER_NAME) } else { - mstat.mt[mtype].alloc++; + mstats.alloc[mtype]++; + + if (memory_logger) + { + mlog_stat[mtype].c_calloc++; + mlog_stat[mtype].t_calloc++; + } ; + if (memory_tracker) - mem_md_malloc(mtype, memory, size, MEMORY_TRACKER_NAME_ARG) ; + mem_mt_malloc(mtype, memory, size, MEMORY_TRACKER_ARG) ; + UNLOCK ; + + if (memory_logger) + mtype_log("zcalloc", memory, mtype, MEMORY_LOGGING_ARGS) ; } ; return memory; @@ -192,7 +672,7 @@ zcalloc (enum MTYPE mtype, size_t size MEMORY_TRACKER_NAME) * NB: If memory cannot be allocated, aborts execution. */ void * -zrealloc (enum MTYPE mtype, void *ptr, size_t size MEMORY_TRACKER_NAME) +zrealloc (mtype_t mtype, void *ptr, size_t size MEMORY_EXTRA_ARGS) { void *memory; @@ -207,10 +687,20 @@ zrealloc (enum MTYPE mtype, void *ptr, size_t size MEMORY_TRACKER_NAME) else { if (ptr == NULL) - mstat.mt[mtype].alloc++; + mstats.alloc[mtype]++; + + if (memory_logger) + { + mlog_stat[mtype].t_realloc++; + } ; + if (memory_tracker) - mem_md_realloc(mtype, ptr, memory, size, MEMORY_TRACKER_NAME_ARG) ; + mem_mt_realloc(mtype, ptr, memory, size, MEMORY_TRACKER_ARG) ; + UNLOCK ; + + if (memory_logger) + mtype_log("zrealloc", memory, mtype, MEMORY_LOGGING_ARGS) ; } ; return memory; @@ -225,21 +715,27 @@ zrealloc (enum MTYPE mtype, void *ptr, size_t size MEMORY_TRACKER_NAME) * Does nothing if the given pointer is NULL. */ void -zfree (enum MTYPE mtype, void *ptr) +zfree (mtype_t mtype, void *ptr MEMORY_EXTRA_ARGS) { if (ptr != NULL) { LOCK ; - assert(mstat.mt[mtype].alloc > 0) ; + assert(mstats.alloc[mtype] > 0) ; + mstats.alloc[mtype]--; + + if (memory_logger) + mlog_stat[mtype].t_free++; - mstat.mt[mtype].alloc--; if (memory_tracker) - mem_md_free(mtype, ptr) ; + mem_mt_free(mtype, ptr) ; free (ptr); UNLOCK ; + + if (memory_logger) + mtype_log("zfree", ptr, mtype, MEMORY_LOGGING_ARGS) ; } ; } ; @@ -251,7 +747,7 @@ zfree (enum MTYPE mtype, void *ptr) * NB: If memory cannot be allocated, aborts execution. */ char * -zstrdup (enum MTYPE mtype, const char *str MEMORY_TRACKER_NAME) +zstrdup (mtype_t mtype, const char *str MEMORY_EXTRA_ARGS) { void *dup; @@ -265,303 +761,1913 @@ zstrdup (enum MTYPE mtype, const char *str MEMORY_TRACKER_NAME) } else { - mstat.mt[mtype].alloc++; + mstats.alloc[mtype]++; + + if (memory_logger) + mlog_stat[mtype].c_strdup++; + if (memory_tracker) - mem_md_malloc(mtype, dup, strlen(str)+1, MEMORY_TRACKER_NAME_ARG) ; + mem_mt_malloc(mtype, dup, strlen(str)+1, MEMORY_TRACKER_ARG) ; UNLOCK ; + + if (memory_logger) + mtype_log("zstrdup", dup, mtype, MEMORY_LOGGING_ARGS) ; } ; return dup; } /*============================================================================== - * Memory allocation with built in logging + * Memory logging function -- called if MEMORY_LOGGER enabled */ +static void +mtype_log (const char *func, void *memory, mtype_t mtype, + const char *file, int line) +{ + zlog_debug ("%s: %s(%d) %p %s %d", func, mem_mtype_name(mtype), mtype, + memory, file, line) ; +} ; -#ifdef MEMORY_LOG +/*============================================================================== + * Support for memory reporting functions + */ -static struct +/*------------------------------------------------------------------------------ + * Pick up copy of the current basic statistics + * + * Done under lock in order to get a single, consistent snap shot. + */ +extern void +mem_get_stats(mem_stats_t* mst) { - const char *name; - long alloc; - unsigned long t_malloc; - unsigned long c_malloc; - unsigned long t_calloc; - unsigned long c_calloc; - unsigned long t_realloc; - unsigned long t_free; - unsigned long c_strdup; -} mlog_stat [MTYPE_MAX]; + LOCK ; + *mst = mstats ; + UNLOCK ; +} ; -static void -mtype_log (char *func, void *memory, const char *file, int line, - enum MTYPE type) +/*------------------------------------------------------------------------------ + * Get value of alloc count for given mtype + * + * For use with a copy of statistics picjed up already. + */ +extern ulong +mem_get_alloc(mem_stats_t* mst, mtype_t mtype) { - zlog_debug ("%s: %s %p %s %d", func, lookup (mstr, type), memory, file, line); -} + return mst->alloc[mtype] ; +} ; -void * -mtype_zmalloc (const char *file, int line, int type, size_t size) +/*------------------------------------------------------------------------------ + * Get individual allocation counter. + * + * To get all the counters at once, see mem_get_stats(). + */ +extern ulong +mtype_stats_alloc(mtype_t mtype) { - void *memory; + ulong alloc ; + LOCK ; + alloc = mstats.alloc[mtype] ; + UNLOCK ; + return alloc ; +} ; + +/*------------------------------------------------------------------------------ + * Convert given byte count to 4 figures + scale + * + * "0 bytes", "2048 bytes", "110KiB", "500MiB", "11GiB", etc. + * + * The pointer returned may be NULL (indicating an error) + * or point to the given buffer, or point to static storage. + */ +extern const char * +mtype_memstr(char *buf, size_t len, ulong bytes) +{ + uint t, g, m, k; - LOCK - mlog_stat[type].c_malloc++; - mlog_stat[type].t_malloc++; - UNLOCK + /* easy cases */ + if (!bytes) + return "0 bytes"; + if (bytes == 1) + return "1 byte"; - memory = zmalloc (type, size); - mtype_log ("zmalloc", memory, file, line, type); + if (sizeof (ulong) >= 8) + /* Hacked to make it not warn on ILP32 machines + * Shift will always be 40 at runtime. See below too */ + t = bytes >> (sizeof (ulong) >= 8 ? 40 : 0); + else + t = 0; - return memory; -} + g = bytes >> 30; + m = bytes >> 20; + k = bytes >> 10; -void * -mtype_zcalloc (const char *file, int line, enum MTYPE type, size_t size) -{ - void *memory; + if (t > 10) + { + /* The shift will always be 39 at runtime. + * Just hacked to make it not warn on 'smaller' machines. + * Static compiler analysis should mean no extra code + */ + if (bytes & (1UL << (sizeof (ulong) >= 8 ? 39 : 0))) + t++; + snprintf (buf, len, "%4u TiB", t); + } + else if (g > 10) + { + if (bytes & (1 << 29)) + g++; + snprintf (buf, len, "%u GiB", g); + } + else if (m > 10) + { + if (bytes & (1 << 19)) + m++; + snprintf (buf, len, "%u MiB", m); + } + else if (k > 10) + { + if (bytes & (1 << 9)) + k++; + snprintf (buf, len, "%d KiB", k); + } + else + snprintf (buf, len, "%lu bytes", bytes); - LOCK - mlog_stat[type].c_calloc++; - mlog_stat[type].t_calloc++; - UNLOCK + return buf; +} ; - memory = zcalloc (type, size); - mtype_log ("xcalloc", memory, file, line, type); +/*============================================================================== + * Establishing (if can) the malloc() parameters. + */ - return memory; -} +static qfb_gen_t mem_alloc_unknown_message(void) ; -void * -mtype_zrealloc (const char *file, int line, enum MTYPE type, void *ptr, - size_t size) +static inline size_t +mem_alloc_padding(size_t s) { - void *memory; + size_t sp ; + sp = (s <= mem_alloc_min_max) ? 0 : ((s - mem_alloc_min_max + mem_alloc_add) + & ~mem_alloc_add) ; + return sp + mem_alloc_min_max - s ; +} ; - /* Realloc need before allocated pointer. */ - LOCK - mlog_stat[type].t_realloc++; - UNLOCK +/*------------------------------------------------------------------------------ + * This tries to find out how malloc() system manages memory, overhead-wise. + */ +static void +mem_alloc_discover(void) +{ + enum { show = false } ; + + size_t alloc ; + FILE* out = show ? stdout : NULL ; + + /* Make sure the statics are initialised -- cleared down to zero */ + mem_page_table = NULL ; + mem_free_descriptors = NULL ; + mem_next_index = 0 ; + + mem_base_count = 0 ; + mem_bases = NULL ; + + /* Try to establish the memory allocation parameters */ + mem_alloc_known = false ; + mem_alloc_failed = ma_not_tried ; + + mem_alloc_first = (uintptr_t)((char*)malloc(1)) ; + free((void*)mem_alloc_first) ; + + mem_alloc_min_size = 0 ; + mem_alloc_redtape = 0 ; + mem_alloc_min_max = 0 ; + mem_alloc_unit = 0 ; + + if (show) + fprintf(out, "first malloc(1):%p\n", (void*)mem_alloc_first) ; + + /* In this loop we allocate blocks 'a' and then 'b' of the same size, + * increasing from 1 by 1 each time round the loop. + * + * We assume (at a minimum) that at the edge of the heap 'b' will immediately + * follow 'a'. + * + * So, we expect to find the actual size of each allocation (including + * redtape and padding) as 'b - a'. After allocating 1 byte, expect the + * size to remain the same until we allocate the minimum allocation + 1, + * at which point we expect the size to increase by the allocation unit. + * + * Note that we do not require 'a' to be allocated at the same address + * each time -- only that if one is allocated using reclaimed space, then + * both are. [A common approach is for released space to remain in the units + * it was originally allocated in -- so, not coalesce free space, at least + * not until there is pressure to do so. If malloc() does this, allocating + * 'a' and 'b' the same size means that 'a' and 'b' will fit in their + * first locations until the minimum allocation is exceeded, at which point + * they will neither of them fit there.] + * + * We leave this loop: + * + * * with mem_alloc_unit != 0 if all has gone well so far. + * + * or mem_alloc_unit == 0 if got something unexpected + * mem_alloc_failed == indication of what went wrong. + * + * * mem_alloc_min_size + * + * * mem_alloc_min_max + */ + alloc = 0 ; + + while (1) + { + char* a, * b ; + size_t size ; + uint i ; - memory = zrealloc (type, ptr, size); + ++alloc ; - mtype_log ("xrealloc", memory, file, line, type); + a = malloc(alloc) ; + b = malloc(alloc) ; - return memory; -} + if (b < a) + { + mem_alloc_failed = ma_not_a_before_b ; + break ; + } ; -/* Important function. */ -void -mtype_zfree (const char *file, int line, enum MTYPE type, void *ptr) + size = b - a ; + for (i = 0 ; i < alloc ; ++i) + *(a + i) = i ; + + free(b) ; + free(a) ; + + if (show) + { + fprintf(out, "alloc:%3zd a:%p b:%p size=%zd\n", + alloc, a, b, size) ; + a = (char*)mem_alloc_first ; + while (a < b) + { + fprintf(out, " %p: %016llX\n", a, (ullong)(*(uint64_t*)a)) ; + a += 8 ; + } ; + } ; + + if (mem_alloc_min_size == 0) + mem_alloc_min_size = size ; /* capture first size */ + + if (mem_alloc_min_size == size) + mem_alloc_min_max = alloc ; /* increases until size changes */ + else + { + /* As soon as the size changes we stop increasing the allocation + * request and exit the loop. + * + * If the size increased, we assume it has done so by the minimum + * allocation unit. + */ + if (mem_alloc_min_size < size) + mem_alloc_unit = size - mem_alloc_min_size ; + else + mem_alloc_failed = ma_not_increasing_size ; + break ; + } ; + } ; + + if (show) + fprintf(out, "size_min=%zd alloc_min=%zd alloc_unit=%zd\n", + mem_alloc_min_size, mem_alloc_min_max, mem_alloc_unit) ; + + if (mem_alloc_unit != 0) + { + size_t x ; + + /* Assume all is well. */ + mem_alloc_redtape = mem_alloc_min_size - mem_alloc_min_max ; + mem_alloc_add = mem_alloc_unit - 1 ; + + x = ((mem_alloc_unit ^ (mem_alloc_unit - 1)) >> 1) + 1 ; + if (x != mem_alloc_unit) + mem_alloc_failed = ma_unit_not_power_2 ; + else if ((mem_alloc_first % mem_alloc_unit) != 0) + mem_alloc_failed = ma_address_not_n_unit ; + else if ((mem_alloc_min_size % mem_alloc_unit) != 0) + mem_alloc_failed = ma_size_min_not_n_unit ; + else + { + char* b ; + + mem_alloc_trial_alloc = 31415 ; + mem_alloc_trial_addr = malloc(mem_alloc_trial_alloc) ; + b = malloc(mem_alloc_trial_alloc) ; + free(b) ; + free(mem_alloc_trial_addr) ; + + mem_alloc_trial_size = b - mem_alloc_trial_addr ; + + if (mem_alloc_trial_size != + (mem_alloc_redtape + mem_alloc_trial_alloc + + mem_alloc_padding(mem_alloc_trial_alloc))) + mem_alloc_failed = ma_trial_failed ; + else if (((uintptr_t)mem_alloc_trial_addr % mem_alloc_unit) != 0) + mem_alloc_failed = ma_trial_failed ; + else if ((mem_alloc_trial_size % mem_alloc_unit) != 0) + mem_alloc_failed = ma_trial_failed ; + else + mem_alloc_known = true ; + } ; + } ; + + if (!mem_alloc_known) + mem_alloc_add = 0 ; + + if (show) + { + if (mem_alloc_known) + fprintf(out, + "Allocation parameters: redtape=%zu min_max_alloc=%zu alloc unit=%zu\n", + mem_alloc_redtape, mem_alloc_min_max, mem_alloc_unit) ; + else + fprintf(out, "Allocation parameters not known: %s\n", + mem_alloc_unknown_message().str) ; + } ; +} ; + +/*------------------------------------------------------------------------------ + * Message explaining why allocation parameters are not known + * + * Message expected to follow something like: "allocation parameters unknown: " + */ +static qfb_gen_t +mem_alloc_unknown_message(void) { - LOCK - mlog_stat[type].t_free++; - UNLOCK + QFB_QFS(qfb_gen, qfs) ; - mtype_log ("xfree", ptr, file, line, type); + switch (mem_alloc_failed) + { + case ma_not_tried: + qfs_append(qfs, "not yet tried to establish parameters") ; + break ; + + case ma_not_a_before_b: + qfs_append(qfs, "malloc() allocated 'a' at higher address than 'b'") ; + break ; + + case ma_not_increasing_size: + qfs_append(qfs, + "malloc() apparently allocated less space for larger request") ; + break ; + + case ma_unit_not_power_2: + qfs_printf(qfs, "mem_alloc_unit=%zd is not power of 2\n", + mem_alloc_unit) ; + break ; + + case ma_address_not_n_unit: + qfs_printf(qfs, + "first address in heap (%p) is not multiple of alloc_unit=%zd", + (void*)mem_alloc_first, mem_alloc_unit) ; + break ; + + case ma_size_min_not_n_unit: + qfs_printf(qfs, "minimum size (%zd) is not multiple of alloc_unit=%zd", + mem_alloc_min_size, mem_alloc_unit) ; + break ; + + case ma_trial_failed: + qfs_printf(qfs, + "parameters trial failed: malloc(%zu) gave %zu bytes at %p", + mem_alloc_trial_alloc, mem_alloc_trial_size, mem_alloc_trial_addr) ; + break ; + + default: + qfs_printf(qfs, "for unknown reason %d", mem_alloc_failed) ; + break ; + } ; - zfree (type, ptr); -} + qfs_term(qfs) ; -char * -mtype_zstrdup (const char *file, int line, enum MTYPE type, const char *str) + return qfb_gen ; +} ; + +/*============================================================================== + * Memory Region Mapping + * + * Uses a mmaped area to hold: + * + * 1. array of mem_region_t objects, one for each region in /proc/self/maps + * + * 2. array of mem_region pointers, for all regions in ascending address + * order. + * + * 3. array of mem_region pointers, for all "malloc" regions in ascending + * address order -- this is used when memory tracker maps current + * allocations to the memory map. + */ +static void* mem_region_map_base = NULL ; /* mmaped area base */ +static size_t mem_region_map_size = 0 ; /* mmaped area size */ + +static uint mem_region_count = 0 ; /* current count */ +static uint mem_region_heap_count = 0 ; /* current count, heap */ +static uint mem_region_max_count = 0 ; /* current limit */ + +static mem_region_t* mem_regions = NULL ; /* known regions */ +static mem_region* mem_region_v = NULL ; /* all regions */ +static mem_region* mem_region_h = NULL ; /* heap regions */ + +static mem_region mem_seek_region(mem_region vec[], uint count, uintptr_t addr); +static FILE* mem_open_maps(void) ; +static bool mem_read_region(mem_region reg, FILE* maps) ; +static int mem_open_pagemap(void) ; +static uint mem_read_pagemap(uint64_t* pm, int pagemap, uintptr_t v, + uint count) ; +static qfb_gen_t mem_form_region(mem_region reg) ; + +static void mem_sort_regions(mem_region* base, uint count) ; +static int mem_cmp_region(const cvp* a, const cvp* b) ; +static int mem_in_region(cvp k, const cvp* v) ; + +/*------------------------------------------------------------------------------ + * Get an up to date map of all regions -- assumes lock held. + * + * Reads the /proc/self/maps file and fills in the mem_regions array, if + * required, will create a new mmaped area if runs out of space. + * + * Fill in and sort the mem_region_v. + * + * Empty the mem_region_h. + */ +static void +mem_map_regions(void) { - char *memory; + FILE* maps ; + pid_t euid ; - LOCK - mlog_stat[type].c_strdup++; - UNLOCK + euid = geteuid() ; + seteuid(getuid()) ; - memory = zstrdup (type, str); + maps = mem_open_maps() ; - mtype_log ("xstrdup", memory, file, line, type); + mem_region_count = 0 ; /* current count */ + mem_region_heap_count = 0 ; /* current count, heap */ - return memory; -} -#endif + if (maps != NULL) + { + bool alloc = (mem_region_max_count == 0) ; -/*============================================================================== - * Showing memory allocation + while (1) + { + size_t map_size, vec_size, tot_size ; + mem_region_t reg[1] ; + + if (alloc) + { + if (mem_region_map_base != NULL) + mem_munmap(mem_region_map_base, mem_region_map_size) ; + + mem_region_max_count = ((mem_region_max_count + 64) | 0xFF) + 1 ; + } ; + + map_size = ((mem_region_max_count * sizeof(mem_region_t)) | 0xFF) + 1; + vec_size = ((mem_region_max_count * sizeof(mem_region)) | 0xFF) + 1; + + tot_size = map_size + (vec_size * 2) ; + + if (alloc) + { + mem_region_map_base = mem_mmap(tot_size) ; /* zero fills */ + mem_region_map_size = tot_size ; + } + else + { + assert(mem_region_map_size == tot_size) ; + + memset(mem_region_map_base, 0, mem_region_map_size) ; + } ; + + mem_regions = (mem_region)mem_region_map_base ; + mem_region_v = (mem_region*)((char*)mem_region_map_base + map_size) ; + mem_region_h = (mem_region*)((char*)mem_region_map_base + map_size + + vec_size) ; + + while (mem_read_region(reg, maps)) + { + if (mem_region_count < mem_region_max_count) + { + mem_regions[mem_region_count] = *reg ; + mem_region_v[mem_region_count] = + &mem_regions[mem_region_count] ; + } ; + ++mem_region_count ; + } ; + + /* OK if had enough memory for complete map */ + if (mem_region_count <= mem_region_max_count) + break ; + + /* Page map did not fit -- alloc new memory according to what we + * now know is required. + */ + mem_region_max_count = mem_region_count ; + mem_region_count = 0 ; /* reset count */ + + fseek(maps, 0, SEEK_SET) ; + alloc = true ; + } ; + + fclose(maps) ; + } ; + + seteuid(euid) ; + + mem_sort_regions(mem_region_v, mem_region_count) ; +} ; + +/*------------------------------------------------------------------------------ + * Find how much is in memory and how much is swapped out -- assumes lock held. + * + * Assumes the regions have just been updated. */ +static void +mem_weigh_regions(void) +{ + enum { show = false } ; -/* Looking up memory status from vty interface. */ -#include "vector.h" -#include "vty.h" -#include "command.h" + int pagemap ; + pid_t euid ; + + euid = geteuid() ; + seteuid(getuid()) ; + + pagemap = mem_open_pagemap() ; + if (pagemap >= 0) + { + enum { pm_entries = 4096 / sizeof(uint64_t) } ; + + uint64_t pm[pm_entries] ; + uint i ; + + for (i = 0 ; i < mem_region_count ; ++i) + { + mem_region reg ; + uintptr_t v ; + uint n, j ; + + reg = mem_region_v[i] ; + + v = reg->base ; + assert((v % mem_pagesize) == 0) ; + + reg->present = 0 ; + reg->swapped = 0 ; + + n = 0 ; + j = 0 ; + while (v < reg->limit) + { + uint64_t pme ; + + if (j == n) + { + n = mem_read_pagemap(pm, pagemap, v, pm_entries) ; + j = 0 ; + if (n == 0) + break ; + } ; + + pme = pm[j] ; + + if (((pme & (3llu << 62)) != 0)) + { + assert(mem_pagesize == (1llu << ((pme >> 55) & 0x3F))) ; + + if ((pme & (1llu << 63)) != 0) + reg->present += mem_pagesize ; + else + reg->swapped += mem_pagesize ; + } ; + + j += 1 ; + v += mem_pagesize ; + } ; + + if (show) + fputs(mem_form_region(reg).str, stdout) ; + } ; + + close(pagemap) ; + } ; + + seteuid(euid) ; +} ; + +/*------------------------------------------------------------------------------ + * Account for tracked memory object in the map of regions -- assume lock held. + */ static void -log_memstats(int pri) +mem_account_region(uintptr_t address, size_t size) { - struct mstat mst ; - struct mlist *ml; + mem_region reg ; - LOCK ; - mst = mstat ; - UNLOCK ; + reg = mem_seek_region(mem_region_h, mem_region_heap_count, address) ; - for (ml = mlists; ml->list; ml++) + if (reg == NULL) { - struct memory_list *m; + reg = mem_seek_region(mem_region_v, mem_region_count, address) ; + assert(reg != NULL) ; - zlog (NULL, pri, "Memory utilization in module %s:", ml->name); - for (m = ml->list; m->index >= 0; m++) + mem_region_h[mem_region_heap_count++] = reg ; + mem_sort_regions(mem_region_h, mem_region_heap_count) ; + } ; + + reg->count += 1 ; + reg->used += size ; + reg->overhead += mem_alloc_redtape + mem_alloc_padding(size) ; +} ; + +/*------------------------------------------------------------------------------ + * Count maximum number of summary regions + */ +static uint +mem_count_summary_regions(void) +{ + uint i, h ; + + h = 0 ; + for (i = 0 ; i < mem_region_heap_count ; ++i) + { + if (mem_region_h[i]->sex == mrx_anon) { - unsigned long alloc = mst.mt[m->index].alloc ; - if (m->index && alloc) - zlog (NULL, pri, " %-30s: %10ld", m->format, alloc); - } - } -} + mem_region_h[i]->sex = mrx_heap_extra ; + ++h ; + } ; + } ; -void -log_memstats_stderr (const char *prefix) + for (i = 0 ; i < mem_region_count ; ++i) + { + if (mem_region_v[i]->sex == mrx_heap_main) + ++h ; + } ; + + return (mrx_sex_count - mrx_heap_count) + h ; +} ; + +/*------------------------------------------------------------------------------ + * Fill in the region summary + * + * Constructs an individual entry for each mrx_heap_main or mrx_heap_extra, and + * summary entries for each non-heap sex. + * + * If there is only one heap entry -- which is most likely if memory_tracker is + * not enabled -- then does not construct a heap subtotal entry. + * + * Note that for heap entries, the count is the count is the number of known + * items allocated in that heap region -- this is only known with mem_tracker. + * + * For all other entries it is the number of regions. For the grand total, + * the count is returned zero -- cannot add count of regions to count of + * items and get anything sensible. + */ +static void +mem_summarize_regions(mem_region_summary_t sum[], uint n) { - struct mstat mst ; - struct mlist *ml; - struct memory_list *m; - int i; - int j = 0; + uint i, h, nh, it ; - LOCK ; - mst = mstat ; - UNLOCK ; + memset(sum, 0, sizeof(mem_region_summary_t) * n) ; + nh = n - (mrx_sex_count - mrx_heap_count) ; + h = 0 ; - for (ml = mlists; ml->list; ml++) + for (i = 0 ; i < mem_region_count ; ++i) { - i = 0; - for (m = ml->list; m->index >= 0; m++) + mem_region reg ; + uint is, ist ; + + reg = mem_region_v[i] ; + + is = reg->sex ; + + if ((is == mrx_heap_main) || (is == mrx_heap_extra)) { - unsigned long alloc = mst.mt[m->index].alloc ; - if (m->index && alloc) + assert(h < nh) ; + + is = mrx_heap + h++ ; + ist = n - 2 ; + sum[ist].sex = mrx_heap_total ; + + if (memory_tracker) { - if (!i) - fprintf (stderr, - "%s: memstats: Current memory utilization in module %s:\n", - prefix, - ml->name); - fprintf (stderr, - "%s: memstats: %-30s: %10ld%s\n", - prefix, - m->format, - alloc, - alloc < 0 ? " (REPORT THIS BUG!)" : ""); - i = j = 1; - } + sum[is].count = reg->count ; + sum[is].used = reg->used ; + sum[is].overhead = reg->overhead ; + sum[is].unused = (reg->limit - reg->base) + - (reg->used + reg->overhead) ; + + sum[ist].count += sum[is].count ; + sum[ist].used += sum[is].used; + sum[ist].overhead += sum[is].overhead ; + sum[ist].unused += sum[is].unused ; + } ; + + if ((mem_alloc_first >= reg->base) && (mem_alloc_first <= reg->limit)) + { + sum[is].data = mem_alloc_first - reg->base ; + sum[ist].data = sum[is].data ; + } ; } + else + { + ist = mrx_rest_total ; + sum[ist].sex = mrx_rest_total ; + + sum[is].count += 1 ; + sum[ist].count += 1 ; + } ; + + sum[is].sex = reg->sex ; + sum[is].size += (reg->limit - reg->base) ; + sum[is].present += reg->present ; + sum[is].swapped += reg->swapped ; + + sum[ist].size += (reg->limit - reg->base) ; + sum[ist].present += reg->present ; + sum[ist].swapped += reg->swapped ; + } ; + + /* Construct the grand totals by copying in the heap subtotal and then + * adding the rest. Setting the count to be the count of regions. + */ + + it = n - 1 ; + sum[it] = sum[n - 2] ; /* copy heap subtotals to total */ + sum[it].sex = mrx_grand_total ; + + if (nh == 1) /* wipe out heap subtotals if only one */ + memset(&sum[n - 2], 0, sizeof(mem_region_summary_t)) ; + + sum[it].count = 0 ; /* grand total count is meaningless */ + sum[it].size += sum[mrx_rest_total].size ; + sum[it].present += sum[mrx_rest_total].present ; + sum[it].swapped += sum[mrx_rest_total].swapped ; +} ; + +/*------------------------------------------------------------------------------ + * Seek region for given address amongst the known regions. + * + * Returns known region, or NULL. + */ +static mem_region +mem_seek_region(mem_region vec[], uint count, uintptr_t addr) +{ + mem_region* p_reg ; + + if (count <= 4) + { + uint i ; + for (i = 0 ; i < count ; ++i) + { + mem_region reg = vec[i] ; + if ((reg->base <= addr) && (addr < reg->limit)) + return reg ; + } ; + + return NULL ; + } ; + + typedef int bsearch_cmp(const void*, const void*) ; + p_reg = bsearch(&addr, vec, count, sizeof(mem_region), + (bsearch_cmp*)mem_in_region) ; + return (p_reg != NULL) ? *p_reg : NULL ; +} ; + +/*------------------------------------------------------------------------------ + * Open /proc/self/maps if we can + */ +static FILE* +mem_open_maps(void) +{ + FILE* maps = fopen("/proc/self/maps", "r") ; + + if (maps == NULL) + zlog_err("%s: failed to open '/proc/self/maps': %s", + __func__, errtoa(errno, 0).str) ; + return maps ; +} ; + +/*------------------------------------------------------------------------------ + * Read next entry from /proc/<pid>/maps + * + * Each entry takes the form: + * + * base limit perm offset dev inode name + * 00400000-0040b000 r-xp 00000000 08:03 262262 /bin/cat + * + * It is assumed that all values are hex except the inode. + * + * Zeroises the mem_region object and sets: + * + * base - base of region + * limit - last address + 1 of region + * flags - as permissions and the name of the region + * offset - offset within device (if any) + * dev_major + * dev_minor + * inode + * name - which may be [heap], [stack] etc. or path to mapped file. + */ +static bool +mem_read_region(mem_region reg, FILE* maps) +{ + int ch, i ; + size_t a, b ; + + memset(reg, 0, sizeof(mem_region_t)) ; + + /* Chew up leading white-space -- spaces/tabs/newlines etc. */ + while (isspace(ch = fgetc(maps))) ; + if (ch == EOF) + return false ; + ungetc(ch, maps) ; + + /* Base and limit */ + i = fscanf(maps, "%zx-%zx", &a, &b) ; + + if ((i != 2) || (a > b)) + { + zlog_err("%s: invalid base or limit (%d)", __func__, i) ; + return false ; + } ; + + confirm(sizeof(size_t) >= sizeof(uintptr_t)) ; + + reg->base = a ; + reg->limit = b ; + + /* Flags */ + while (isblank(ch = fgetc(maps))) ; + + do + { + switch (ch) + { + case '-': + break ; + + case 'r': + reg->flags |= mrf_read ; + break ; + + case 'w': + reg->flags |= mrf_write ; + break ; + + case 'x': + reg->flags |= mrf_execute ; + break ; + + case 'p': + reg->flags |= mrf_private ; + break ; + + case 's': + reg->flags |= mrf_shared ; + break ; + + default: + zlog_err("%s: invalid permission '%c'", __func__, ch) ; + return false ; + } ; } + while (!isblank(ch = fgetc(maps))) ; - if (j) - fprintf (stderr, - "%s: memstats: NOTE: If configuration exists, utilization may be " - "expected.\n", - prefix); + /* offset, device and inode */ + i = fscanf(maps, "%zx %x:%x %llu", ®->offset, + ®->dev_major, ®->dev_minor, + ®->inode) ; + if (i != 4) + { + zlog_err("%s: invalid offset, device or inode (%d)", __func__, i) ; + return false ; + } ; + + /* Skip spaces and then pick up the name (if any) */ + while (isblank(ch = fgetc(maps))) ; + + i = 0 ; + while ((ch != '\n') && (ch != EOF)) + { + if (i < (mem_region_max_name_len + 1)) + reg->name[i++] = ch ; + + ch = fgetc(maps) ; + } ; + + if (i == (mem_region_max_name_len + 1)) + { + i -= 4 ; + while (i < mem_region_max_name_len) + reg->name[i++] = '.' ; + } ; + + reg->name[i] = '\0' ; + + /* Establish type */ + if (i == 0) + reg->type = mrt_anon ; + else if (reg->name[0] == '[') + { + if (strcmp(reg->name, "[heap]") == 0) + reg->type = mrt_heap ; + else if (strcmp(reg->name, "[stack]") == 0) + reg->type = mrt_stack ; + else if (strcmp(reg->name, "[vdso]") == 0) + reg->type = mrt_vdso ; + else if (strcmp(reg->name, "[vsyscall]") == 0) + reg->type = mrt_vsyscall ; + else + reg->type = mrt_other ; + } else - fprintf (stderr, - "%s: memstats: No remaining tracked memory utilization.\n", - prefix); -} + reg->type = mrt_named ; + + /* Establish sex + * + * Note that mrx_heap_extra is only discovered if/when the mem_tracker data + * is scanned to see if anything has been malloced in the region. + */ + reg->sex = mrx_exotic ; + + switch (reg->flags & (mrf_read | mrf_write | mrf_execute)) + { + case mrf_read | 0 | mrf_execute: + if ((reg->type == mrt_named) || (reg->type == mrt_vdso) + || (reg->type == mrt_vsyscall)) + reg->sex = mrx_code ; + break ; + + case mrf_read: + if (reg->type == mrt_named) + reg->sex = mrx_const ; + break ; + + case mrf_read | mrf_write: + if (reg->type == mrt_named) + reg->sex = mrx_data ; + else if (reg->type == mrt_heap) + reg->sex = mrx_heap_main ; + else if (reg->type == mrt_anon) + reg->sex = mrx_anon ; + else if (reg->type == mrt_stack) + reg->sex = mrx_stack ; + break ; + + case 0: + if (reg->type == mrt_named) + reg->sex = mrx_reserved ; + else if (reg->type == mrt_anon) + reg->sex = mrx_gap ; + break ; + + default: /* mrf_read | mrf_write | mrf_execute + 0 | mrf_write | mrf_execute + 0 | mrf_write | 0 + 0 | 0 | mrf_execute */ + break ; + } ; + + return true ; +} ; + +/*------------------------------------------------------------------------------ + * Open /proc/self/pagemap if we can + */ +static int +mem_open_pagemap(void) +{ + int pagemap = open("/proc/self/pagemap", O_RDONLY) ; + + if (pagemap < 0) + zlog_err("%s: failed to open '/proc/self/pagemap': %s", + __func__, errtoa(errno, 0).str) ; + return pagemap ; +} ; + +/*------------------------------------------------------------------------------ + * Read set of entries from /proc/self/pagemap. + */ +static uint +mem_read_pagemap(uint64_t* pm, int pagemap, uintptr_t v, uint count) +{ + off_t off ; + int n ; + + off = (v / mem_pagesize) * sizeof(uint64_t) ; + if (lseek(pagemap, off, SEEK_SET) < 0) + { + zlog_err("%s: failed to seek to %llX in '/proc/self/pagemap': %s", + __func__, (long long)off, errtoa(errno, 0).str) ; + return 0 ; + } ; + + n = read(pagemap, pm, sizeof(uint64_t) * count) ; + + if (n < 0) + { + zlog_err("%s: failed reading at %llX in '/proc/self/pagemap': %s", + __func__, (long long)off, errtoa(errno, 0).str) ; + n = 0 ; + } ; + + assert((n % sizeof(uint64_t)) == 0) ; + + return n / sizeof(uint64_t) ; +} ; + +/*------------------------------------------------------------------------------ + * Construct string describing a memory region + */ +static qfb_gen_t +mem_form_region(mem_region reg) +{ + QFB_QFS(qfb_gen, qfs) ; + + size_t a, b, s ; + int ch ; + + confirm(sizeof(size_t) >= sizeof(uintptr_t)) ; + + a = reg->base >> 32 ; + b = reg->base & 0xFFFFFFFF ; + s = reg->limit - reg->base ; + + qfs_printf(qfs, "%08zX_%08zX %010zX %010zX %010zX ", a, b, s, + reg->present, reg->swapped) ; + + qfs_printf(qfs, "%c", ((reg->flags & mrf_read) != 0) ? 'r' : '-') ; + qfs_printf(qfs, "%c", ((reg->flags & mrf_write) != 0) ? 'w' : '-') ; + qfs_printf(qfs, "%c", ((reg->flags & mrf_execute) != 0) ? 'x' : '-') ; + qfs_printf(qfs, "%c", ((reg->flags & mrf_private) != 0) + ? ( ((reg->flags & mrf_shared) != 0) ? 'Q' : 'p' ) + : ( ((reg->flags & mrf_shared) != 0) ? 's' : '-' )) ; + + ch = (reg->type < mrt_type_count) ? mrf_type_chars[reg->type] : '\0' ; + if (ch == '\0') + ch = '?' ; + + qfs_printf(qfs, " %08zX %02X:%02X %8lld %c %s\n", reg->offset, + reg->dev_major, reg->dev_minor, + reg->inode, ch, reg->name) ; + qfs_term(qfs) ; + + return qfb_gen ; +} ; + +/*------------------------------------------------------------------------------ + * Sort given vector of pointers to regions, and then make sure they are + * in order (!) and that the limit >= base for each one. + */ static void -show_memory_type_vty (struct vty *vty, const char* name, - struct mem_tracker* mt, long int alloc, int sep) +mem_sort_regions(mem_region* base, uint count) { - if (sep) - vty_out (vty, "-----------------------------%s", VTY_NEWLINE) ; + uint i ; + uintptr_t ll ; + + typedef int qsort_cmp(const void*, const void*) ; + qsort(base, count, sizeof(mem_region), (qsort_cmp*)mem_cmp_region) ; + + ll = (uintptr_t)NULL ; + for (i = 0 ; i < count ; ++i) + { + assert(base[i]->base >= ll) ; + ll = base[i]->limit ; + assert(base[i]->base <= ll) ; + } ; +} ; + +/*------------------------------------------------------------------------------ + * Compare two regions for qsort + * + * Don't think it likely, but if bases are equal, sorts by limit. Allows for + * empty region(s) ? + */ +static int +mem_cmp_region(const cvp* a, const cvp* b) +{ + mem_region_c ra = *(const mem_region_c*)a ; + mem_region_c rb = *(const mem_region_c*)b ; - vty_out (vty, "%-30s:", name) ; + if (ra->base != rb->base) + return (ra->base < rb->base) ? -1 : +1 ; - if (memory_tracker) - show_memory_tracker_detail(vty, mt, alloc) ; - else - vty_out (vty, " %10ld", alloc) ; + if (ra->limit != rb->limit) + return (ra->limit < rb->limit) ? -1 : +1 ; - vty_out (vty, "%s", VTY_NEWLINE); + return 0 ; } ; +/*------------------------------------------------------------------------------ + * Compare two regions for bsearch + */ static int -show_memory_vty (struct vty *vty, struct memory_list *m, struct mlist* ml, - int needsep) +mem_in_region(cvp k, const cvp* v) { - int notempty = 0 ; + const uintptr_t kv = *(const uintptr_t*)k ; + const mem_region_c rv = *(const mem_region_c*)v ; - long int alloc ; + if (rv->base > kv) + return -1 ; - struct mstat mst ; - struct mem_tracker mem_tot ; - struct mem_tracker mem_one ; - struct mem_tracker* mt ; + if (kv >= rv->limit) + return +1 ; - struct mem_type_tracker mem_tt ; + return 0 ; +} ; - LOCK ; +/*============================================================================== + * Manipulation of the tracking data structures. + */ +static void mem_mt_make_bases(void) ; + +/*------------------------------------------------------------------------------ + * Set memory type in the given mem_descriptor + */ +inline static void +mem_mt_set_type(mem_descriptor md, enum MTYPE mtype) +{ + uint32_t t_ms ; + uint32_t t_ls ; - mst = mstat ; - mem_tt = mem_type_tracker ; + t_ms = mtype >> md_size_type_bits ; + t_ls = mtype ; - UNLOCK ; + t_ms = (t_ms & md_next_type_mask) << md_next_bits ; + t_ls = (t_ls & md_size_type_mask) << md_size_bits ; + + md->next = (md->next & md_next_mask) | t_ms ; + md->size = (md->size & md_size_mask) | t_ls ; +} ; + +/*------------------------------------------------------------------------------ + * Set the next index in the given mem_descriptor + */ +inline static void +mem_mt_set_next(mem_descriptor md, md_index_t next) +{ + md->next = (md->next & ~md_next_mask) | (next & md_next_mask) ; +} ; - mem_tracker_zeroise(&mem_tot) ; - mem_tracker_zeroise(&mem_one) ; +/*------------------------------------------------------------------------------ + * Set the item size in the given mem_descriptor + */ +inline static void +mem_mt_set_size(mem_descriptor md, size_t size) +{ + md->size = (md->size & ~md_size_mask) | (size & md_size_mask) ; +} ; + +/*------------------------------------------------------------------------------ + * Get the item type from the given mem_descriptor + */ +inline static uint8_t +mem_mt_type(mem_descriptor md) +{ + return ( (md->next >> (md_next_bits - md_size_type_bits)) + & (md_next_type_mask << md_size_type_bits) ) + | ( (md->size >> md_size_bits) & md_size_type_mask ) ; +} ; + +/*------------------------------------------------------------------------------ + * Get the next index from the given mem_descriptor + */ +inline static md_index_t +mem_mt_next(mem_descriptor md) +{ + return md->next & md_next_mask ; +} ; + +/*------------------------------------------------------------------------------ + * Get the item size from the given mem_descriptor + */ +inline static size_t +mem_mt_size(mem_descriptor md) +{ + return md->size & md_size_mask ; +} ; + +/*------------------------------------------------------------------------------ + * Map item index to mem_descriptor + */ +inline static mem_descriptor +mem_mt_ptr(md_index_t mdi) +{ + mem_descriptor page ; + + if (mdi == 0) + return NULL ; + + page = mem_page_table[(mdi >> md_i_index_bits) & md_page_mask] ; + passert(page != NULL) ; + return page + (mdi & md_i_index_mask) ; +} ; + +/*------------------------------------------------------------------------------ + * Map item address to address of base entry in the hash table. + */ +inline static md_index_t* +mem_mt_base(void* address) +{ + if (mem_bases == NULL) + mem_mt_make_bases() ; + + return mem_bases + ((uintptr_t)address % mem_base_count) ; +} ; + +/*------------------------------------------------------------------------------ + * Make new set of bases for the hash table -- if a set already exists, + * rearrange the hash table and then discard the old bases. + */ +static void +mem_mt_make_bases(void) +{ + md_index_t* bases_was = mem_bases ; + uint32_t count_was = mem_base_count ; + + size_t size_was = count_was * sizeof(md_index_t) ; + size_t size_now ; - if ((m == NULL) && (ml != NULL)) - m = (ml++)->list ; + mem_base_count += 256 * 1024 ; + mem_base_count |= 1 ; - while (m != NULL) + size_now = mem_base_count * sizeof(md_index_t) ; + + mem_bases = mem_mmap(size_now) ; /* zero fills */ + + mem_mt_data.tracker_overhead += size_now - size_was ; + + if (bases_was == NULL) + passert(count_was == 0) ; + else { - if (m->index <= 0) + md_index_t* base = bases_was ; + md_index_t* new_base ; + md_index_t this ; + md_index_t next ; + mem_descriptor md ; + + while (count_was) { - needsep = notempty ; - if (m->index < 0) + next = *base++ ; + while (next != 0) { - if (ml == NULL) - m = NULL ; - else - m = (ml++)->list ; - } + this = next ; + md = mem_mt_ptr(this) ; + next = mem_mt_next(md) ; + + new_base = mem_mt_base(md->addr) ; + mem_mt_set_next(md, *new_base) ; + *new_base = this ; + } ; + --count_was ; + } ; + + mem_munmap(bases_was, size_was) ; + } ; +} ; + +/*------------------------------------------------------------------------------ + * Make new page full of empty memory descriptors, and place them all on the + * free descriptors list. + */ +static void +mem_mt_make_descriptors(void) +{ + mem_descriptor md ; + md_index_t mdi ; + size_t desc_size ; + size_t total_size ; + + mdi = mem_next_index ; + passert(mdi < md_index_max) ; + + desc_size = md_i_index_count * sizeof(mem_descriptor_t) ; + total_size = desc_size ; + + if (mem_page_table == NULL) + { + desc_size = (desc_size | 0xFF) + 1 ; + total_size = desc_size + (md_page_count * sizeof(mem_descriptor)) ; + } ; + + mem_mt_data.tracker_overhead += total_size ; + + mem_free_descriptors = mem_mmap(total_size) ; /* zero fills */ + + if (mem_page_table == NULL) + mem_page_table = (mem_descriptor*)((char*)mem_free_descriptors + desc_size); + + mem_page_table[(mdi >> md_i_index_bits) & md_page_mask] + = mem_free_descriptors ; + mem_next_index += md_i_index_count ; + + if (mdi == 0) + { + ++mem_free_descriptors ; /* don't use index == 0 */ + ++mdi ; + } ; + + md = mem_free_descriptors ; + while (mdi < mem_next_index) + { + md->addr = md + 1 ; /* point at next entry */ + md->next = mdi ; /* set to point at self */ + ++md ; + ++mdi ; + } ; + (md-1)->addr = NULL ; /* set end of list */ +} ; + +/*------------------------------------------------------------------------------ + * Walk the memory_tracker descriptors, to account for all tracked memory + * by region -- assumes lock held. + */ +static void +mem_mt_region_accounting(void) +{ + uint i ; + + for (i = 0 ; i < mem_base_count ; ++i) + { + md_index_t this ; + this = mem_bases[i] ; + while (this != 0) + { + mem_descriptor md ; + md = mem_mt_ptr(this) ; + mem_account_region((uintptr_t)md->addr, mem_mt_size(md)) ; + this = mem_mt_next(md) ; + } ; + } ; +} ; + +/*============================================================================== + * Tracking of malloc/realloc/free -- calloc treated as malloc. + */ + +/*------------------------------------------------------------------------------ + * memory_tracker malloc() extension. + */ +inline static void +mem_mt_malloc(mtype_t mtype, void* address, size_t size, const char* name) +{ + mem_tracker_item typ ; + md_index_t* base ; + mem_descriptor md ; + md_index_t mdi ; + + passert(size <= md_size_max) ; + passert(mtype < MTYPE_MAX) ; + + if (mem_free_descriptors == NULL) + mem_mt_make_descriptors() ; + + md = mem_free_descriptors ; + mem_free_descriptors = md->addr ; + mdi = md->next ; + + if (mem_mt_data.tot->item_count >= (mem_base_count * 4)) + mem_mt_make_bases() ; + + base = mem_mt_base(address) ; + + md->addr = address ; + md->name = name ; + md->size = size ; + md->next = *base ; + mem_mt_set_type(md, mtype) ; + + *base = mdi ; + + /* Keep track of total number of all items and the total size of same, + * and the peak value of those. + */ + mem_mt_data.tot->item_count += 1 ; + mem_mt_data.tot->total_size += size ; + + if (mem_mt_data.tot->peak_item_count < mem_mt_data.tot->item_count) + mem_mt_data.tot->peak_item_count = mem_mt_data.tot->item_count ; + + if (mem_mt_data.tot->peak_total_size < mem_mt_data.tot->total_size) + mem_mt_data.tot->peak_total_size = mem_mt_data.tot->total_size ; + + /* Now all data for items of this type. + */ + typ = &(mem_mt_data.typ[mtype]) ; + + typ->malloc_count += 1 ; + typ->item_count += 1 ; + typ->total_size += size ; + + if (typ->peak_item_count < typ->item_count) + typ->peak_item_count = typ->item_count ; + + if (typ->peak_total_size < typ->total_size) + typ->peak_total_size = typ->total_size ; +} ; + +/*------------------------------------------------------------------------------ + * memory_tracker free() extension. + */ +inline static void +mem_mt_free(mtype_t mtype, void* address) +{ + mem_tracker_item typ ; + md_index_t* base ; + mem_descriptor md, prev_md ; + md_index_t this, next ; + + passert(mtype < MTYPE_MAX) ; + + if (address == NULL) + return ; + + base = mem_mt_base(address) ; + + prev_md = NULL ; + this = *base ; + while (this != 0) + { + md = mem_mt_ptr(this) ; + next = mem_mt_next(md) ; + + if (md->addr == address) + { + size_t size = mem_mt_size(md) ; + + if (mem_mt_type(md) != mtype) + zabort("memory type mismatch in free") ; + + mem_mt_data.tot->item_count -= 1 ; + mem_mt_data.tot->total_size -= size ; + + typ = &(mem_mt_data.typ[mtype]) ; + + typ->free_count += 1 ; + typ->item_count -= 1 ; + typ->total_size -= size ; + + if (prev_md == NULL) + *base = next ; else - ++m ; + mem_mt_set_next(prev_md, next) ; + + md->addr = mem_free_descriptors ; + mem_free_descriptors = md ; + md->next = this ; + + return ; } else { - alloc = mst.mt[m->index].alloc ; - if (memory_tracker) - mt = &(mem_tt.mt[m->index]) ; + prev_md = md ; + this = next ; + } ; + } ; + + zabort("Failed to find memory being freed") ; +} ; + +/*------------------------------------------------------------------------------ + * memory_tracker realloc() extension. + */ +inline static void +mem_mt_realloc(mtype_t mtype, void* old_address, void* new_address, + size_t new_size, const char* name) +{ + mem_tracker_item typ ; + md_index_t* base ; + mem_descriptor md, prev_md ; + md_index_t this, next ; + + if (old_address == NULL) + { + mem_mt_malloc(mtype, new_address, new_size, name) ; + return ; + } ; + + passert(new_size <= md_size_max) ; + passert(mtype < MTYPE_MAX) ; + + base = mem_mt_base(old_address) ; + + prev_md = NULL ; + this = *base ; + while (this != 0) + { + md = mem_mt_ptr(this) ; + next = mem_mt_next(md) ; + + if (md->addr == old_address) + { + size_t old_size = mem_mt_size(md) ; + + if (mem_mt_type(md) != mtype) + zabort("memory type mismatch in realloc") ; + + mem_mt_data.tot->total_size += new_size - old_size ; + + if (mem_mt_data.tot->peak_total_size + < mem_mt_data.tot->total_size) + mem_mt_data.tot->peak_total_size + = mem_mt_data.tot->total_size ; + + typ = &(mem_mt_data.typ[mtype]) ; + + typ->realloc_count += 1 ; + typ->total_size += new_size - old_size ; + + if (typ->peak_total_size < typ->total_size) + typ->peak_total_size = typ->total_size ; + + md->name = name ; + mem_mt_set_size(md, new_size) ; + + if (old_address == new_address) + return ; + + if (prev_md == NULL) + *base = next ; + else + mem_mt_set_next(prev_md, next) ; + + base = mem_mt_base(new_address) ; + mem_mt_set_next(md, *base) ; + *base = this ; + + md->addr = new_address ; + + return ; + } + else + { + prev_md = md ; + this = next ; + } ; + } ; + + zabort("Failed to find memory being realloced") ; +} ; + +/*============================================================================== + * Memory Tracker Display + */ + +/*------------------------------------------------------------------------------ + * Get copy of current statistics in mem_tracker_data form. + * + * This acquires the lock (if required), copies the data and releases the + * lock. + * + * If not mem_tracker enabled, then takes the standard mstat stuff and + * creates a (largely empty) mem_tracker data set. + * + * If mem_tracker enabled, takes the mem_tracker_data and the mstat stuff. + * Then checks that the counts are the same, and constructs the totals for: + * + * malloc_count + * realloc_count + * free_count + */ +static void +mem_mt_get_stats(mem_tracker_data mtd, bool locked) +{ + mem_stats_t mst ; + mtype_t mtype ; + ulong item_count ; + size_t total_size ; + + if (!locked) + LOCK ; + + mst = mstats ; + if (memory_tracker) + *mtd = mem_mt_data ; + + UNLOCK ; + + if (!memory_tracker) + { + memset(mtd, 0, sizeof(mem_tracker_data_t)) ; + + item_count = 0 ; + + for (mtype = 0 ; mtype < MTYPE_MAX ; ++mtype) + item_count += (mtd->typ[mtype].item_count = mst.alloc[mtype]) ; + + mtd->tot->item_count = item_count ; + } ; + + mtd->tot->malloc_count = 0 ; + mtd->tot->realloc_count = 0 ; + mtd->tot->free_count = 0 ; + + item_count = 0 ; + total_size = 0 ; + + for (mtype = 0 ; mtype < MTYPE_MAX ; ++mtype) + { + mtd->tot->malloc_count += mtd->typ[mtype].malloc_count ; + mtd->tot->realloc_count += mtd->typ[mtype].realloc_count ; + mtd->tot->free_count += mtd->typ[mtype].free_count ; + + item_count += mtd->typ[mtype].item_count ; + total_size += mtd->typ[mtype].total_size ; + } ; + + assert(item_count == mtd->tot->item_count) ; + + if (memory_tracker) + { + assert(total_size == mtd->tot->total_size) ; + assert(item_count == mtd->tot->malloc_count - mtd->tot->free_count) ; + } ; +} ; + +/*------------------------------------------------------------------------------ + * Creation of "friendly" counts and sizes display. + */ + +enum { scale_max = 4 } ; + +static const char* scale_d_tags [] = +{ + [0] = " " , + [1] = "'k", + [2] = "'m", + [3] = "'g", + [4] = "'t", +} ; + +static const char* scale_b_tags [] = +{ + [0] = " " , + [1] = "'K", + [2] = "'M", + [3] = "'G", + [4] = "'T", +} ; + +static const ulong p10 [] = +{ + [ 0] = 1ul, + [ 1] = 10ul, + [ 2] = 100ul, + [ 3] = 1000ul, + [ 4] = 10000ul, + [ 5] = 100000ul, + [ 6] = 1000000ul, + [ 7] = 10000000ul, + [ 8] = 100000000ul, + [ 9] = 1000000000ul, + [10] = 10000000000ul, + [11] = 100000000000ul, + [12] = 1000000000000ul, + [13] = 10000000000000ul, + [14] = 100000000000000ul, + [15] = 1000000000000000ul /* 10^((4*3) + 3) */ +} ; + +static const ulong q10 [] = +{ + [ 0] = 1ul / 2, + [ 1] = 10ul / 2, + [ 2] = 100ul / 2, + [ 3] = 1000ul / 2, + [ 4] = 10000ul / 2, + [ 5] = 100000ul / 2, + [ 6] = 1000000ul / 2, + [ 7] = 10000000ul / 2, + [ 8] = 100000000ul / 2, + [ 9] = 1000000000ul / 2, + [10] = 10000000000ul / 2, + [11] = 100000000000ul / 2, + [12] = 1000000000000ul / 2, + [13] = 10000000000000ul / 2, + [14] = 100000000000000ul / 2, + [15] = 1000000000000000ul / 2, /* 10^((4*3) + 3) / 2 */ +} ; + +/*------------------------------------------------------------------------------ + * Form string for number, with commas and "d" decimal digits, followed by + * the given tag -- where d = 0..4 + * + * So: val=1234567, d=2, tag="k" -> "12,345.67k". + * val=1234, d=0, tag="" -> "1,234" + */ +static num_str_t +mem_form_commas(ulong v, const char* tag, uint d, uint f) +{ + QFB_QFS(num_str, qfs) ; + + if (d == 0) + qfs_unsigned(qfs, v, pf_commas, 0, 0) ; + else + { + qfs_unsigned(qfs, v, 0, 0, 0) ; + qfs_append(qfs, ".") ; + qfs_unsigned(qfs, f, pf_zeros, d, 0) ; + } ; + + qfs_append(qfs, tag) ; + qfs_term(qfs) ; + + return num_str ; +} ; + +/*------------------------------------------------------------------------------ + * Form count with 4 significant digits, up to multiples of trillions. + */ +static num_str_t +mem_form_count(ulong val) +{ + uint i, d ; + ldiv_t r ; + + i = 0 ; + d = 0 ; + + while ((val >= p10[i + 4]) && (i < (scale_max * 3))) + i += 3 ; + + if (i == 0) + { + r.quot = val ; + r.rem = 0 ; + } + else + { + if (val < p10[i + 1]) + d = 3 ; + else if (val < p10[i + 2]) + d = 2 ; + else if (val < p10[i + 3]) + d = 1 ; + + /* Scale down to required number of decimals and round. + * + * If is thousands, then i = 3, if value = 10,000 (smallest possible) + * then d == 2. So divide by 5 (q10[3 - 2]) to make ls bit the rounding + * bit, add one and shift off the rounding bit. + * + * The result should be 1000..9999, unless value is greater than our + * ability to scale, or has rounded up one decade. + */ + val = ((val / q10[i - d]) + 1) >> 1 ; + + qassert(val >= 1000) ; + + if (val > 9999) + { + if (d == 0) + { + if (i < (scale_max * 3)) + { + qassert(val == 10000) ; + + val = 1000 ; + d = 2 ; + i += 3 ; + } ; + } else - mt = &mem_one ; - mt->tracked_count = alloc ; - - mem_tot.malloc_count += mt->malloc_count ; - mem_tot.free_count += mt->free_count ; - mem_tot.realloc_count += mt->realloc_count ; - mem_tot.tracked_count += mt->tracked_count ; - mem_tot.tracked_max_count += mt->tracked_max_count ; - mem_tot.tracked_size += mt->tracked_size ; - mem_tot.tracked_max_size += mt->tracked_max_size ; - - if (alloc || mt->tracked_count) { - show_memory_type_vty(vty, m->format, mt, alloc, needsep) ; - needsep = 0 ; - notempty = 1 ; + qassert(val == 10000) ; + + val = 1000 ; + d -= 1 ; } ; + } ; - ++m ; - } ; + r = ldiv(val, p10[d]) ; } ; - show_memory_type_vty(vty, "Total", &mem_tot, mem_tot.tracked_count, notempty); + return mem_form_commas(r.quot, scale_d_tags[i / 3], d, r.rem) ; +} ; - return 1 ; +/*------------------------------------------------------------------------------ + * Form count with 4 significant digits, up to multiples of Terabytes. + * + * So: 1023 + */ +static num_str_t +mem_form_byte_count(ulong val) +{ + ulong v ; + uint i, d, f ; + + i = 0 ; + d = 0 ; + v = val ; + f = 0 ; + + while ((v >= 1024) && (i < scale_max)) + { + v >>= 10 ; /* find power of 1024 scale */ + i += 1 ; + } ; + + if (i > 0) + { + if (v < 10) + d = 3 ; /* number of decimals expected */ + else if (v < 100) + d = 2 ; + else if (v < 1000) + d = 1 ; + + /* Scale up to the required number of decimals, shift down so that + * only ms bit of fraction is left, round and shift off rounding bit. + * + * If d != 0, then will scale up by 10, 100 or 1000. The largest value + * to be scaled up is 1000 * (2^(scale_max * 10)) - 1, which will be + * multiplied by 10. The maximum scaled value is, therefore, bound to + * be < 2^(((scale_max + 1) * 10) + 4) + */ + confirm((sizeof(ulong) * 8) >= (((scale_max + 1) * 10) + 4)) ; + + v = (((val * p10[d]) >> ((i * 10) - 1)) + 1) >> 1 ; + + qassert(v >= 1000) ; + + if (d == 0) + { + if ((v == 1024) && (i < scale_max)) + { + v = 1000 ; /* rounded up to next power of 1024 */ + d = 3 ; + i += 1 ; + } + } + else + { + if (v >= 10000) + { + qassert(v == 10000) ; + + v = 1000 ; /* rounded up to one less decimals */ + d -= 1 ; + } ; + } ; + + f = v % p10[d] ; + v = v / p10[d] ; + } ; + + return mem_form_commas(v, scale_b_tags[i], d, f) ; +} ; + +/*------------------------------------------------------------------------------ + * Show summary of memory_tracker on vty. + */ +static void +show_memory_tracker_summary(vty vty, mem_tracker_data mtd) +{ + vty_out (vty, "Memory Tracker Statistics:\n"); + vty_out (vty, " Current memory allocated: %13s\n", + mem_form_byte_count(mtd->tot->total_size).str) ; + vty_out (vty, " Current allocated objects: %'11lu\n", + mtd->tot->item_count); + vty_out (vty, " Maximum memory allocated: %13s\n", + mem_form_byte_count(mtd->tot->peak_total_size).str) ; + vty_out (vty, " Maximum allocated objects: %'11lu\n", + mtd->tot->peak_item_count) ; + vty_out (vty, " malloc/calloc call count: %13s\n", + mem_form_count (mtd->tot->malloc_count).str); + vty_out (vty, " realloc_call_count: %13s\n", + mem_form_count (mtd->tot->realloc_count).str); + vty_out (vty, " free call count: %13s\n", + mem_form_count (mtd->tot->free_count).str); + vty_out (vty, " Memory Tracker overhead: %13s\n", + mem_form_byte_count(mtd->tracker_overhead).str); } ; +/*============================================================================== + * Showing memory allocation + */ #ifdef HAVE_MALLINFO -static int -show_memory_mallinfo (struct vty *vty) +static void +show_memory_mallinfo (struct vty *vty, mem_tracker_data mtd) { - struct mallinfo minfo = mallinfo(); + struct mallinfo minfo ; char buf[MTYPE_MEMSTR_LEN]; + LOCK ; + + minfo = mallinfo() ; + mem_mt_get_stats(mtd, true) ; /* Get a snapshot of all data & UNLOCK */ + + vty_out (vty, "System allocator statistics:%s", VTY_NEWLINE); vty_out (vty, " Total heap allocated: %s%s", mtype_memstr (buf, MTYPE_MEMSTR_LEN, minfo.arena), @@ -581,22 +2687,353 @@ show_memory_mallinfo (struct vty *vty) vty_out (vty, " Free ordinary blocks: %s%s", mtype_memstr (buf, MTYPE_MEMSTR_LEN, minfo.fordblks), VTY_NEWLINE); - vty_out (vty, " Ordinary blocks: %ld%s", - (unsigned long)minfo.ordblks, + vty_out (vty, " Ordinary blocks: %lu%s", + (ulong)minfo.ordblks, VTY_NEWLINE); - vty_out (vty, " Small blocks: %ld%s", - (unsigned long)minfo.smblks, + vty_out (vty, " Small blocks: %lu%s", + (ulong)minfo.smblks, VTY_NEWLINE); - vty_out (vty, " Holding blocks: %ld%s", - (unsigned long)minfo.hblks, + vty_out (vty, " Holding blocks: %lu%s", + (ulong)minfo.hblks, VTY_NEWLINE); vty_out (vty, "(see system documentation for 'mallinfo' for meaning)%s", VTY_NEWLINE); +} +#endif /* HAVE_MALLINFO */ + +/*------------------------------------------------------------------------------ + * Log the number of each type of memory currently thought to be allocated. + */ +static void +log_memstats(int pri) +{ + mem_stats_t mst ; + struct mlist *ml; + + mem_get_stats(&mst) ; + + for (ml = mlists; ml->list; ml++) + { + struct memory_list *m; - return 1; + zlog (NULL, pri, "Memory utilization in module %s:", ml->name); + for (m = ml->list; m->index >= 0; m++) + { + if ((m->index != 0) && (mst.alloc[m->index] != 0)) + zlog (NULL, pri, " %-30s: %10lu", m->format, mst.alloc[m->index]) ; + } ; + } ; +} ; + +/*------------------------------------------------------------------------------ + * Output the number of each type of memory currently thought to be allocated, + * to stderr. + */ +extern void +log_memstats_stderr (const char *prefix) +{ + mem_stats_t mst ; + struct mlist *ml; + struct memory_list *m; + bool seen_something ; + + mem_get_stats(&mst) ; + + seen_something = false ; + for (ml = mlists; ml->list; ml++) + { + bool first = true ; + for (m = ml->list; m->index >= 0; m++) + { + if ((m->index != 0) && (mst.alloc[m->index] != 0)) + { + if (first) + { + fprintf (stderr, + "%s: memstats: Current memory utilization in module %s:\n", + prefix, + ml->name) ; + first = false ; + } ; + fprintf (stderr, + "%s: memstats: %-30s: %10lu\n", + prefix, + m->format, + mst.alloc[m->index]) ; + seen_something = true ; + } ; + } ; + } ; + + if (seen_something) + fprintf (stderr, + "%s: memstats: NOTE: If configuration exists, utilization may be " + "expected.\n", + prefix); + else + fprintf (stderr, + "%s: memstats: No remaining tracked memory utilization.\n", + prefix); } + +/*------------------------------------------------------------------------------ + * Show all regions -- to vty. + * + * Acquires the lock while making the region map, and returns the other + * statistics, gathered while the lock is held. + */ +static void +mem_show_region_info(vty vty, mem_tracker_data mtd) +{ + uint i, n ; + + /* Start by making sure known regions are up to date, and then taking a + * local copy of them. + * + * This uses C99 variable length arrays ! + */ + LOCK ; + + mem_map_regions() ; + mem_weigh_regions() ; + + if (memory_tracker) + mem_mt_region_accounting() ; + + n = mem_count_summary_regions() ; + + mem_region_summary_t sum[n] ; + mem_summarize_regions(sum, n) ; + + mem_mt_get_stats(mtd, true) ; /* Get a snapshot of all data and UNLOCK */ + + /* Now can show what we have + */ + /* 12345678901234567890123456789012345678901234 */ + vty_out(vty, "Region Size Present Swapped Count ") ; + if (memory_tracker) + /* 567890123456789012345678901 */ + vty_out(vty, " Used Overhd Unused ") ; + /* 234567890 */ + vty_out(vty, " Static\n") ; + + for (i = 0 ; i < n ; i++) + { + mem_region_summary rs = &sum[i] ; + const char* name ; + + if (rs->size == 0) + continue ; /* Ignore absent or trivial entries */ + + name = (rs->sex < mrx_sex_count) ? mrx_sex_names[rs->sex] : NULL ; + if (name == NULL) + name = "????" ; + + /* 12--7:90-789-678-5 */ + vty_out(vty, " %-6s: %8s %8s %8s", + name, + mem_form_byte_count(rs->size).str, + mem_form_byte_count(rs->present).str, + mem_form_byte_count(rs->swapped).str + ) ; + + if (rs->sex != mrx_grand_total) + { + /* 67-4 */ + vty_out(vty, " %8s", ((rs->count != 0) || memory_tracker) + ? mem_form_count(rs->count).str + : "- ") ; + + if (memory_tracker && (rs->sex >= mrx_heap)) + /* 56-345-234-1 */ + vty_out (vty, " %8s %8s %8s", + mem_form_byte_count(rs->used).str, + mem_form_byte_count(rs->overhead).str, + mem_form_byte_count(rs->unused).str) ; + + if (rs->data != 0) + /* 23-0 */ + vty_out(vty, " %8s", mem_form_byte_count(rs->data).str) ; + } ; + + vty_out(vty, "\n") ; + } ; +} ; + +/*------------------------------------------------------------------------------ + * Show memory summary, if any is available: + * + * If have "memory_regions", show that. Otherwise, if HAVE_MALLINFO, show + * that (for whatever it is worth. + * + * If we have memory_tracker, show that. + * + * Show what we know about the memory overhead, if anything. + * + * Returns true if has output any form of summary. + * + * Copies the memory_tracker or standard statistics to the given + * mem_tracker_data structure -- so all information is gathered under the lock, + * in one go. + */ +static bool +show_memory_summary_info(vty vty, mem_tracker_data mtd) +{ + bool needsep = false ; + + if (memory_regions) + { + /* Show what we know about all memory regions & get other statistics */ + mem_show_region_info(vty, mtd) ; + needsep = true ; + } + else + { + /* If we have mallinfo() show that & get other statistics. + * + * In any event, get other statistics. + */ +#ifdef HAVE_MALLINFO + show_memory_mallinfo(vty, mtd) ; + needsep = true ; +#else + mem_mt_get_stats(mtd) ; /* Get a snapshot of all data */ #endif /* HAVE_MALLINFO */ + } ; + + if (memory_tracker) + { + show_memory_tracker_summary(vty, mtd) ; + needsep = true ; + + if (mem_alloc_known) + vty_out(vty, + "malloc() minimum block %zu with %zu redtape and allocation unit %zu\n", + mem_alloc_min_size, mem_alloc_redtape, mem_alloc_unit) ; + else + vty_out(vty, "malloc() parameters unknown: %s\n", + mem_alloc_unknown_message().str) ; + } ; + + return needsep ; +} ; + +/*------------------------------------------------------------------------------ + * Show counts for for given type of memory. + * + * Shows basic allocation count or full memory_tracker statistics. + */ +static void +show_memory_type_vty (vty vty, const char* name, mem_tracker_item mt, bool sep) +{ + if (sep) + { + /* 123456789012345678901234567890123456789012 */ + vty_out (vty, "------------------------------:------Count") ; + + if (memory_tracker) + { + /* 123456789012 */ + vty_out(vty, "---Size--" + "--Peak-Count" + "-P-Size--" + "-malloc--" + "realloc--" + "---free--") ; + } ; + + vty_out (vty, "\n") ; + } ; + + vty_out (vty, "%-30s:%'11lu", name, mt->item_count) ; + + if (memory_tracker) + { + vty_out(vty, "%9s", mem_form_byte_count(mt->total_size).str) ; + vty_out(vty, "%'12lu", mt->peak_item_count) ; + vty_out(vty, "%9s", mem_form_byte_count(mt->peak_total_size).str) ; + vty_out(vty, "%9s", mem_form_count(mt->malloc_count).str) ; + vty_out(vty, "%9s", mem_form_count(mt->realloc_count).str) ; + vty_out(vty, "%9s", mem_form_count(mt->free_count).str) ; + + } ; + + vty_out (vty, "\n") ; +} ; + +/*------------------------------------------------------------------------------ + * Show counts for all memory of types in the given memory_list. + * + * Shows everything for NULL memory_list, preceeded by summary. + * + * For basic statistics shows anything with non-zero count. For memory_tracker + * shows anything with non-zero count or peak count. + */ +static int +show_memory_vty (vty vty, struct memory_list *ml) +{ + mem_tracker_data_t mtd[1] ; + mem_tracker_item mt ; + + struct mlist* mll = NULL ; + + bool needsep = false ; + bool notempty = false ; + + /* ml == NULL => "show memory all" */ + if (ml == NULL) + { + needsep = show_memory_summary_info(vty, mtd) ; + + mll = mlists ; + } + else + mem_mt_get_stats(mtd, false) ; /* Get a snapshot of all data */ + + /* Work along the current list, when reach end, step to next or fin */ + while (1) + { + if (ml == NULL) /* Step to next list */ + { + if (mll != NULL) + ml = (mll++)->list ; + if (ml == NULL) + break ; + } ; + + if (ml->index <= 0) /* Deal with separator/terminator */ + { + needsep |= notempty ; + notempty = false ; + + if (ml->index < 0) + ml = NULL ; + else + ++ml ; + + continue ; + } ; + + mt = &(mtd->typ[ml->index]) ; + + if ((mt->item_count != 0) || (mt->peak_item_count != 0)) + { + show_memory_type_vty(vty, ml->format, mt, needsep) ; + needsep = false ; + notempty = true ; + } ; + ++ml ; + } ; + + show_memory_type_vty(vty, "Total", mtd->tot, needsep || notempty) ; + + return 1 ; +} ; + +/*============================================================================== + * The memory CLI commands + */ DEFUN_CALL (show_memory_summary, show_memory_summary_cmd, @@ -605,23 +3042,12 @@ DEFUN_CALL (show_memory_summary, "Memory statistics\n" "Summary memory statistics\n") { - if (memory_tracker) - show_memory_tracker_summary(vty) ; - else - { - long alloc = 0 ; - int mtype ; + mem_tracker_data_t mtd[1] ; -# ifdef HAVE_MALLINFO - show_memory_mallinfo (vty); -# endif /* HAVE_MALLINFO */ + show_memory_summary_info(vty, mtd) ; - LOCK ; - for (mtype = 1 ; mtype < MTYPE_MAX ; ++mtype) - alloc += mstat.mt[mtype].alloc ; - UNLOCK - vty_out(vty, "%ld items allocated%s", alloc, VTY_NEWLINE) ; - } ; + if (!memory_tracker) + vty_out(vty, "%'lu items allocated\n", mtd->tot->item_count) ; return CMD_SUCCESS; } @@ -633,16 +3059,7 @@ DEFUN_CALL (show_memory_all, "Memory statistics\n" "All memory statistics\n") { - int needsep = 0; - -#ifdef HAVE_MALLINFO - needsep |= show_memory_mallinfo (vty); -#endif /* HAVE_MALLINFO */ - - if (memory_tracker) - needsep |= show_memory_tracker_summary(vty) ; - - show_memory_vty (vty, NULL, mlists, needsep); + show_memory_vty (vty, NULL) ; return CMD_SUCCESS; } @@ -660,7 +3077,7 @@ DEFUN_CALL (show_memory_lib, "Memory statistics\n" "Library memory\n") { - show_memory_vty (vty, memory_list_lib, NULL, 0); + show_memory_vty (vty, memory_list_lib); return CMD_SUCCESS; } @@ -671,7 +3088,7 @@ DEFUN_CALL (show_memory_zebra, "Memory statistics\n" "Zebra memory\n") { - show_memory_vty (vty, memory_list_zebra, NULL, 0); + show_memory_vty (vty, memory_list_zebra); return CMD_SUCCESS; } @@ -682,7 +3099,7 @@ DEFUN_CALL (show_memory_rip, "Memory statistics\n" "RIP memory\n") { - show_memory_vty (vty, memory_list_rip, NULL, 0); + show_memory_vty (vty, memory_list_rip); return CMD_SUCCESS; } @@ -693,7 +3110,7 @@ DEFUN_CALL (show_memory_ripng, "Memory statistics\n" "RIPng memory\n") { - show_memory_vty (vty, memory_list_ripng, NULL, 0); + show_memory_vty (vty, memory_list_ripng); return CMD_SUCCESS; } @@ -704,7 +3121,7 @@ DEFUN_CALL (show_memory_bgp, "Memory statistics\n" "BGP memory\n") { - show_memory_vty (vty, memory_list_bgp, NULL, 0); + show_memory_vty (vty, memory_list_bgp); return CMD_SUCCESS; } @@ -715,7 +3132,7 @@ DEFUN_CALL (show_memory_ospf, "Memory statistics\n" "OSPF memory\n") { - show_memory_vty (vty, memory_list_ospf, NULL, 0); + show_memory_vty (vty, memory_list_ospf); return CMD_SUCCESS; } @@ -726,7 +3143,7 @@ DEFUN_CALL (show_memory_ospf6, "Memory statistics\n" "OSPF6 memory\n") { - show_memory_vty (vty, memory_list_ospf6, NULL, 0); + show_memory_vty (vty, memory_list_ospf6); return CMD_SUCCESS; } @@ -737,24 +3154,60 @@ DEFUN_CALL (show_memory_isis, "Memory statistics\n" "ISIS memory\n") { - show_memory_vty (vty, memory_list_isis, NULL, 0); + show_memory_vty (vty, memory_list_isis); return CMD_SUCCESS; } -/* Second state initialisation if qpthreaded */ -void +/*============================================================================== + * Initialisation and close down + */ + +static void bd_form_test(void) ; + +/*------------------------------------------------------------------------------ + * First stage initialisation + */ +extern void +memory_start(void) +{ +#ifdef MCHECK + #include <mcheck.h> + mcheck(NULL) ; /* start now !! */ +#endif + + memset(&mstats, 0, sizeof(mem_stats_t)) ; + memset(&mem_mt_data, 0, sizeof(mem_tracker_data_t)) ; + memset(mlog_stat, 0, sizeof(mlog_stat)) ; + + mem_pagesize = qlib_pagesize ; + + mem_alloc_discover() ; + + if (false) + bd_form_test() ; +} ; + +/*------------------------------------------------------------------------------ + * Second stage initialisation if qpthreaded + */ +extern void memory_init_r (void) { qpt_mutex_init(memory_mutex, qpt_mutex_quagga); } -/* Finished with module */ -void +/*------------------------------------------------------------------------------ + * Finished with module + */ +extern void memory_finish (void) { qpt_mutex_destroy(memory_mutex, 0); } +/*------------------------------------------------------------------------------ + * Set up of memory reporting commands + */ void memory_init (void) { @@ -793,78 +3246,377 @@ memory_init (void) install_element (ENABLE_NODE, &show_memory_isis_cmd); } -/* Stats querying from users */ -/* Return a pointer to a human friendly string describing - * the byte count passed in. E.g: - * "0 bytes", "2048 bytes", "110kB", "500MiB", "11GiB", etc. - * Up to 4 significant figures will be given. - * The pointer returned may be NULL (indicating an error) - * or point to the given buffer, or point to static storage. +#undef UNLOCK +#undef LOCK + +/*============================================================================== + * Support for mmap of anonymous memory regions + * + * These wrappers allow for the (perhaps obscure, now) absence of MAP_ANON/ + * MAP_ANONYMOUS. They also check for failure returns, and crash with + * suitable messages. + * + * In the (extremely) unlikely event of there being no mmap, uses malloc(). + * + * Note that the memory allocated is *not* included in the memory statistics. + */ + +/*------------------------------------------------------------------------------ + * mmap allocation of memory. + * + * Rounds allocation up to multiple of page size (unless using malloc()) and + * zeroises. */ -const char * -mtype_memstr (char *buf, size_t len, unsigned long bytes) +extern void* +mem_mmap(size_t size) { - unsigned int t, g, m, k; + void* p ; + int err = 0 ; - /* easy cases */ - if (!bytes) - return "0 bytes"; - if (bytes == 1) - return "1 byte"; +#if HAVE_MMAP - if (sizeof (unsigned long) >= 8) - /* Hacked to make it not warn on ILP32 machines - * Shift will always be 40 at runtime. See below too */ - t = bytes >> (sizeof (unsigned long) >= 8 ? 40 : 0); - else - t = 0; - g = bytes >> 30; - m = bytes >> 20; - k = bytes >> 10; + size = ((size + mem_pagesize - 1) / mem_pagesize) * mem_pagesize ; - if (t > 10) +# if (defined(MAP_ANON) || defined(MAP_ANONYMOUS)) && 0 + p = mmap(NULL, size, PROT_READ | PROT_WRITE, + MAP_PRIVATE | (MAP_ANONYMOUS |0) | (MAP_ANON |0), -1, 0) ; + err = errno ; +# else + int fd ; + + fd = open("/dev/zero", O_RDWR) ; + + if (fd >= 0) { - /* The shift will always be 39 at runtime. - * Just hacked to make it not warn on 'smaller' machines. - * Static compiler analysis should mean no extra code - */ - if (bytes & (1UL << (sizeof (unsigned long) >= 8 ? 39 : 0))) - t++; - snprintf (buf, len, "%4d TiB", t); + p = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_PRIVATE, fd, 0) ; + err = errno ; } - else if (g > 10) + else { - if (bytes & (1 << 29)) - g++; - snprintf (buf, len, "%d GiB", g); - } - else if (m > 10) + p = MAP_FAILED ; + err = errno ; + } ; + + close(fd) ; + + if (p == MAP_FAILED) { - if (bytes & (1 << 19)) - m++; - snprintf (buf, len, "%d MiB", m); - } - else if (k > 10) + zlog_err ("%s : failed to mmap %zd bytes: %s\n", + __func__, size, errtoa(err, 0).str) ; + zabort_abort(); + } ; +# endif + +#else + p = malloc(size) ; + err = errno ; + + if (p == NULL) { - if (bytes & (1 << 9)) - k++; - snprintf (buf, len, "%d KiB", k); - } + zlog_err ("%s : failed to malloc %zd bytes: %s\n", + __func__, size, errtoa(err, 0).str) ; + zabort_abort(); + } ; +#endif + + memset(p, 0, size) ; + + return p ; +} ; + +/*------------------------------------------------------------------------------ + * mmap allocation of memory. + * + * Rounds allocation up to multiple of page sizes and zeroises. + */ +extern void +mem_munmap(void* p, size_t size) +{ +#if HAVE_MMAP + int rc ; + + size = ((size + mem_pagesize - 1) / mem_pagesize) * mem_pagesize ; + rc = munmap(p, size) ; + + if (rc < 0) + { + zlog_err ("%s : failed to munmap %zd bytes @ %p: %s\n", + __func__, size, p, errtoa(errno, 0).str) ; + zabort_abort(); + } ; +#else + free(p) ; +#endif +} ; + +/*============================================================================== + * Testing the number formatting + */ +static ulong t_val(double v, ulong s) ; +static void b_test(ulong v) ; +static void d_test(ulong v) ; +static void c_val(char* e, ulong u, uint d, const char* t) ; + +static void +bd_form_test(void) +{ + ulong s ; + int g = 0 ; + + fprintf(stderr, "Group %d\n", g) ; + b_test(0 ) ; + b_test(1 ) ; + b_test(9 ) ; + b_test(10 ) ; + b_test(99 ) ; + b_test(100 ) ; + b_test(999 ) ; + b_test(1023 ) ; + b_test(1024 ) ; + b_test(1025 ) ; + b_test(9999 ) ; + b_test(10000) ; + b_test(10007) ; + + s = 1 ; + for (g = 1 ; g <= 5 ; ++g) + { + fprintf(stderr, "Group %d\n", g) ; + s *= 1024ul ; + b_test(t_val(.31414, s) ) ; + b_test(t_val(.31415, s) ) ; + b_test(t_val(.31416, s) ) ; + b_test(t_val(.99995, s) - 1) ; + b_test(t_val(.99995, s) ) ; + b_test(t_val(.99995, s) + 1) ; + b_test(t_val(1.2345, s) ) ; + b_test(t_val(9.9995, s) - 1) ; + b_test(t_val(9.9995, s) ) ; + b_test(t_val(9.9995, s) + 1) ; + b_test(t_val(3.1415, s) - 1) ; + b_test(t_val(3.1415, s) ) ; + b_test(t_val(3.1416, s) ) ; + b_test(t_val(99.995, s) - 1) ; + b_test(t_val(99.995, s) ) ; + b_test(t_val(99.995, s) + 1) ; + b_test(t_val(314.15, s) - 1) ; + b_test(t_val(314.15, s) ) ; + b_test(t_val(314.16, s) ) ; + b_test(t_val(999.95, s) - 1) ; + b_test(t_val(999.95, s) ) ; + b_test(t_val(999.95, s) + 1) ; + b_test(t_val(1023.5, s) - 1) ; + b_test(t_val(1023.5, s) ) ; + b_test(t_val(1023.5, s) + 1) ; + b_test(t_val(3141.5, s) - 1) ; + b_test(t_val(3141.5, s) ) ; + b_test(t_val(3141.6, s) ) ; + b_test(t_val(9999.5, s) - 1) ; + b_test(t_val(9999.5, s) ) ; + b_test(t_val(9999.5, s) + 1) ; + } ; + + g = 0 ; + fprintf(stderr, "Group %d\n", g) ; + d_test(0 ) ; + d_test(1 ) ; + d_test(9 ) ; + d_test(10 ) ; + d_test(11 ) ; + d_test(99 ) ; + d_test(100 ) ; + d_test(999 ) ; + d_test(1000 ) ; + d_test(1001 ) ; + d_test(9999 ) ; + d_test(10000) ; + d_test(10001) ; + + s = 1 ; + for (g = 1 ; g <= 4 ; ++g) + { + fprintf(stderr, "Group %d\n", g) ; + s *= 1000ul ; + d_test(t_val(1.2345, s) - 1) ; + d_test(t_val(1.2345, s) ) ; + d_test(t_val(9.9995, s) - 1) ; + d_test(t_val(9.9995, s) ) ; + d_test(t_val(21.235, s) - 1) ; + d_test(t_val(21.235, s) ) ; + d_test(t_val(99.995, s) - 1) ; + d_test(t_val(99.995, s) ) ; + d_test(t_val(312.35, s) - 1) ; + d_test(t_val(312.35, s) ) ; + d_test(t_val(999.95, s) - 1) ; + d_test(t_val(999.95, s) ) ; + d_test(t_val(4321.5, s) - 1) ; + d_test(t_val(4321.5, s) ) ; + d_test(t_val(9999.5, s) - 1) ; + d_test(t_val(9999.5, s) ) ; + } ; + + g = 5 ; + fprintf(stderr, "Group %d\n", g) ; + s *= 1000ul ; + d_test(t_val(1.2345, s) - 1) ; + d_test(t_val(1.2345, s) ) ; + d_test(t_val(9.9995, s) - 1) ; + d_test(t_val(9.9995, s) ) ; + d_test(t_val(21.2345, s) - 1) ; + d_test(t_val(21.2345, s) ) ; + d_test(t_val(99.9995, s) - 1) ; + d_test(t_val(99.9995, s) ) ; + d_test(t_val(312.6785, s) - 1) ; + d_test(t_val(312.6785, s) ) ; + d_test(t_val(999.9995, s) - 1) ; + d_test(t_val(999.9995, s) ) ; + d_test(t_val(4321.5675, s) - 1) ; + d_test(t_val(4321.5675, s) ) ; + d_test(t_val(9999.9995, s) - 1) ; + d_test(t_val(9999.9995, s) ) ; +} ; + +static ulong +t_val(double v, ulong s) +{ + return v * s ; +} ; + +static void +b_test(ulong v) +{ + num_str_t g ; + char e[50] ; + ulong u ; + uint i ; + uint d ; + + d = 0 ; + u = v ; + i = 0 ; + if (u >= 1024) + { + uint n ; + + while ((u >= 1024) && (i < scale_max)) + { + u >>= 10 ; + i += 1 ; + } + n = i * 10 ; + + u = v ; + while (u < (1000ul << n)) + { + u *= 10 ; + d += 1 ; + } ; + + u = ((u >> (n - 1)) + 1) >> 1 ; + + if (d == 0) + { + if ((u == 1024) && (i < scale_max)) + { + u = 1000 ; + i += 1 ; + d = 3 ; + } ; + } + else + { + assert(u <= 10000) ; + if (u == 10000) + { + u = 1000 ; + d -= 1 ; + } ; + } ; + } ; + + c_val(e, u, d, scale_b_tags[i]) ; + + g = mem_form_byte_count(v) ; + + fprintf(stderr, "%16lx %'20lu -> '%7s'", v, v, g.str) ; + + if (strcmp(e, g.str) == 0) + fputs(" -- OK\n", stderr) ; else - snprintf (buf, len, "%ld bytes", bytes); + fprintf(stderr, " *** expect: '%7s'\n", e) ; +} ; - return buf; -} +static void +d_test(ulong v) +{ + num_str_t g ; + char e[50] ; + ulong u ; + uint i ; + uint n ; + uint d ; + + d = 0 ; + i = 0 ; + u = v ; + n = (v == 0) ? 0 : 1 ; + + while (u >= 10) + { + u /= 10 ; + n += 1 ; + } ; -unsigned long -mtype_stats_alloc (enum MTYPE type) + u = v ; + + if (n > 4) + { + if (n > ((scale_max * 3) + 3)) + { + u = (u + q10[scale_max * 3]) / p10[scale_max * 3] ; + i = scale_max ; + } + else + { + u = (u + q10[n - 4]) / p10[n - 4] ; + + if (u > 9999) + { + u /= 10 ; + n += 1 ; + } ; + + i = (n - 2) / 3 ; + d = (i * 3) + 4 - n ; + } ; + } ; + + c_val(e, u, d, scale_d_tags[i]) ; + + g = mem_form_count(v) ; + + fprintf(stderr, "%'20lu -> '%7s'", v, g.str) ; + + if (strcmp(e, g.str) == 0) + fputs(" -- OK\n", stderr) ; + else + fprintf(stderr, " *** expect: '%7s'\n", e) ; +} ; + +static void +c_val(char* e, ulong u, uint d, const char* t) { - unsigned long result; - LOCK - result = mstat.mt[type].alloc; - UNLOCK - return result; -} + if (d != 0) + sprintf(e, "%lu.%0*lu%s", u / p10[d], (int)d, u % p10[d], t) ; + else + { + if (u < 1000) + sprintf(e, "%lu%s", u, t) ; + else if (u < 1000000) + sprintf(e, "%lu,%03lu%s", u / 1000, u % 1000, t) ; + else + sprintf(e, "%lu,%03lu,%03lu%s", u / 1000000, (u / 1000) % 1000, + u % 1000, t) ; + } ; +} ; -#undef UNLOCK -#undef LOCK |