diff options
Diffstat (limited to 'malloc/memprof.c')
-rw-r--r-- | malloc/memprof.c | 596 |
1 files changed, 596 insertions, 0 deletions
diff --git a/malloc/memprof.c b/malloc/memprof.c new file mode 100644 index 0000000000..1830d33ae6 --- /dev/null +++ b/malloc/memprof.c @@ -0,0 +1,596 @@ +/* Profile heap and stack memory usage of running program. + Copyright (C) 1998, 1999 Free Software Foundation, Inc. + This file is part of the GNU C Library. + Contributed by Ulrich Drepper <drepper@cygnus.com>, 1998. + + The GNU C Library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. + + The GNU C Library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with the GNU C Library; see the file COPYING.LIB. If not, + write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + Boston, MA 02111-1307, USA. */ + +#include <dlfcn.h> +#include <fcntl.h> +#include <inttypes.h> +#include <signal.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <sys/time.h> + +/* Pointer to the real functions. These are determined used `dlsym' + when really needed. */ +static void *(*mallocp) (size_t); +static void *(*reallocp) (void *, size_t); +static void *(*callocp) (size_t, size_t); +static void (*freep) (void *); + +enum +{ + idx_malloc = 0, + idx_realloc, + idx_calloc, + idx_free, + idx_last +}; + + +struct header +{ + size_t length; + size_t magic; +}; + +#define MAGIC 0xfeedbeaf + + +static unsigned long int calls[idx_last]; +static unsigned long int failed[idx_last]; +static unsigned long long int total[idx_last]; +static unsigned long long int grand_total; +static unsigned long int histogram[65536 / 16]; +static unsigned long int large; +static unsigned long int calls_total; +static unsigned long int inplace; +static unsigned long int decreasing; +static long int current_use[2]; +static long int peak_use[3]; +static uintptr_t start_sp; + +/* A few macros to make the source more readable. */ +#define current_heap current_use[0] +#define current_stack current_use[1] +#define peak_heap peak_use[0] +#define peak_stack peak_use[1] +#define peak_total peak_use[2] + +#ifdef __i386__ +# define GETSP() ({ register uintptr_t stack_ptr asm ("esp"); stack_ptr; }) +#endif +#ifdef __alpha__ +# define GETSP() ({ register uintptr_t stack_ptr asm ("$30"); stack_ptr; }) +#endif +#ifdef __sparc__ +# define GETSP() ({ register uintptr_t stack_ptr asm ("%sp"); stack_ptr; }) +#endif + +#ifdef __i386__ +# define GETTIME(low,high) asm ("rdtsc" : "=a" (low), "=d" (high)) +#endif +#ifndef GETTIME +# define GETTIME(low,high) \ + { \ + struct timeval tval; \ + uint64_t usecs; \ + gettimeofday (&tval, NULL); \ + usecs = (uint64_t) tval.tv_usec + (uint64_t) tval_usec * 1000000; \ + low = usecs & 0xffffffff; \ + high = usecs >> 32; \ + } +#endif + +#define DEFAULT_BUFFER_SIZE 1024 +static size_t buffer_size; + +static int fd = -1; + +static int not_me; +extern const char *__progname; + +struct entry +{ + size_t heap; + size_t stack; + uint32_t time_low; + uint32_t time_high; +}; + +static struct entry buffer[DEFAULT_BUFFER_SIZE]; +static size_t buffer_cnt; +static struct entry first; + + +/* Update the global data after a successful function call. */ +static void +update_data (struct header *result, size_t len, size_t old_len) +{ + long int total_use; + + if (result != NULL) + { + /* Record the information we need and mark the block using a + magic number. */ + result->length = len; + result->magic = MAGIC; + } + + /* Compute current heap usage and compare it with the maximum value. */ + current_heap += len - old_len; + if (current_heap > peak_heap) + peak_heap = current_heap; + + /* Compute current stack usage and compare it with the maximum value. */ + current_stack = start_sp - GETSP (); + if (current_stack > peak_stack) + peak_stack = current_stack; + + /* Add up heap and stack usage and compare it with the maximum value. */ + total_use = current_heap + current_stack; + if (total_use > peak_total) + peak_total = total_use; + + /* Store the value only if we are writing to a file. */ + if (fd != -1) + { + buffer[buffer_cnt].heap = current_heap; + buffer[buffer_cnt].stack = current_stack; + GETTIME (buffer[buffer_cnt].time_low, buffer[buffer_cnt].time_high); + ++buffer_cnt; + + /* Write out buffer if it is full. */ + if (buffer_cnt == buffer_size) + { + write (fd, buffer, buffer_cnt * sizeof (struct entry)); + buffer_cnt = 0; + } + } +} + + +/* Interrupt handler. */ +static void +int_handler (int signo) +{ + /* Nothing gets allocated. Just record the stack pointer position. */ + update_data (NULL, 0, 0); +} + + +/* Record the initial stack position. */ +static void +__attribute__ ((constructor)) +init (void) +{ + start_sp = GETSP (); +} + + +/* Find out whether this is the program we are supposed to profile. + For this the name in the variable `__progname' must match the one + given in the environment variable MEMPROF_PROG_NAME. If the variable + is not present every program assumes it should be profiling. + + If this is the program open a file descriptor to the output file. + We will write to it whenever the buffer overflows. The name of the + output file is determined by the environment variable MEMPROF_OUTPUT. + + If the environment variable MEMPROF_BUFFER_SIZE is set its numerical + value determines the size of the internal buffer. The number gives + the number of elements in the buffer. By setting the number to one + one effectively selects unbuffered operation. + + If MEMPROF_NO_TIMER is not present an alarm handler is installed + which at the highest possible frequency records the stack pointer. */ +static void +me (void) +{ + const char *env = getenv ("MEMPROF_PROG_NAME"); + size_t prog_len = strlen (__progname); + if (env != NULL) + { + /* Check for program name. */ + size_t len = strlen (env); + if (len > prog_len || strcmp (env, &__progname[prog_len - len]) != 0 + || (prog_len != len && __progname[prog_len - len - 1] != '/')) + not_me = 1; + } + + /* Only open the file if it's really us. */ + if (!not_me && fd == -1) + { + const char *outname = getenv ("MEMPROF_OUTPUT"); + if (outname != NULL) + { + fd = creat (outname, 0666); + + if (fd == -1) + /* Don't do anything in future calls if we cannot write to + the output file. */ + not_me = 1; + else + { + /* Write the first entry. */ + first.heap = 0; + first.stack = 0; + GETTIME (first.time_low, first.time_high); + /* Write it two times since we need the starting and end time. */ + write (fd, &first, sizeof (first)); + + /* Determine the buffer size. We use the default if the + environment variable is not present. */ + buffer_size = DEFAULT_BUFFER_SIZE; + if (getenv ("MEMPROF_BUFFER_SIZE") != NULL) + { + buffer_size = atoi (getenv ("MEMPROF_BUFFER_SIZE")); + if (buffer_size == 0 || buffer_size > DEFAULT_BUFFER_SIZE) + buffer_size = DEFAULT_BUFFER_SIZE; + } + + /* Possibly enable timer-based stack pointer retrieval. */ + if (getenv ("MEMPROF_NO_TIMER") == NULL) + { + struct sigaction act; + + act.sa_handler = (sighandler_t) &int_handler; + act.sa_flags = SA_RESTART; + sigfillset (&act.sa_mask); + + if (sigaction (SIGPROF, &act, NULL) >= 0) + { + struct itimerval timer; + + timer.it_value.tv_sec = 0; + timer.it_value.tv_usec = 1; + timer.it_interval = timer.it_value; + setitimer (ITIMER_PROF, &timer, NULL); + } + } + } + } + } +} + + +/* `malloc' replacement. We keep track of the memory usage if this is the + correct program. */ +void * +malloc (size_t len) +{ + struct header *result = NULL; + + /* Determine real implementation if not already happened. */ + if (mallocp == NULL) + { + me (); + mallocp = (void *(*) (size_t)) dlsym (RTLD_NEXT, "malloc"); + } + + /* If this is not the correct program just use the normal function. */ + if (not_me) + return (*mallocp) (len); + + /* Keep track of number of calls. */ + ++calls[idx_malloc]; + /* Keep track of total memory consumption for `malloc'. */ + total[idx_malloc] += len; + /* Keep track of total memory requirement. */ + grand_total += len; + /* Remember the size of the request. */ + if (len < 65536) + ++histogram[len / 16]; + else + ++large; + /* Total number of calls of any of the functions. */ + ++calls_total; + + /* Do the real work. */ + result = (struct header *) (*mallocp) (len + sizeof (struct header)); + + if (result == NULL) + ++failed[idx_malloc]; + else + /* Update the allocation data and write out the records if necessary. */ + update_data (result, len, 0); + + /* Return the pointer to the user buffer. */ + return result ? (void *) (result + 1) : NULL; +} + + +/* `realloc' replacement. We keep track of the memory usage if this is the + correct program. */ +void * +realloc (void *old, size_t len) +{ + struct header *result = NULL; + struct header *real; + size_t old_len; + + /* Determine real implementation if not already happened. */ + if (reallocp == NULL) + { + me (); + reallocp = (void *(*) (void *, size_t)) dlsym (RTLD_NEXT, "realloc"); + } + + /* If this is not the correct program just use the normal function. */ + if (not_me) + return (*reallocp) (old, len); + + if (old == NULL) + { + /* This is really a `malloc' call. */ + real = NULL; + old_len = 0; + } + else + { + real = ((struct header *) old) - 1; + if (real->magic != MAGIC) + /* This is no memory allocated here. */ + return (*reallocp) (old, len); + old_len = real->length; + } + + /* Keep track of number of calls. */ + ++calls[idx_realloc]; + /* Keep track of total memory consumption for `realloc'. */ + total[idx_realloc] += len; + /* Keep track of total memory requirement. */ + grand_total += len; + /* Remember the size of the request. */ + if (len < 65536) + ++histogram[len / 16]; + else + ++large; + /* Total number of calls of any of the functions. */ + ++calls_total; + + /* Do the real work. */ + result = (struct header *) (*reallocp) (real, len + sizeof (struct header)); + + if (result == NULL) + ++failed[idx_realloc]; + else + { + /* Record whether the reduction/increase happened in place. */ + if (real == result) + ++inplace; + /* Was the buffer increased? */ + if (old_len > len) + ++decreasing; + + /* Update the allocation data and write out the records if necessary. */ + update_data (result, len, old_len); + } + + /* Return the pointer to the user buffer. */ + return result ? (void *) (result + 1) : NULL; +} + + +/* `calloc' replacement. We keep track of the memory usage if this is the + correct program. */ +void * +calloc (size_t n, size_t len) +{ + struct header *result; + size_t size = n * len; + + /* Determine real implementation if not already happened. We are + searching for the `malloc' implementation since it is not always + efficiently possible to use `calloc' because we have to add a bit + room to the allocation to put the header in. */ + if (mallocp == NULL) + { + me (); + mallocp = (void *(*) (size_t)) dlsym (RTLD_NEXT, "malloc"); + } + + /* If this is not the correct program just use the normal function. */ + if (not_me) + { + callocp = (void *(*) (size_t, size_t)) dlsym (RTLD_NEXT, "calloc"); + + return (*callocp) (n, len); + } + + /* Keep track of number of calls. */ + ++calls[idx_calloc]; + /* Keep track of total memory consumption for `calloc'. */ + total[idx_calloc] += size; + /* Keep track of total memory requirement. */ + grand_total += size; + /* Remember the size of the request. */ + if (size < 65536) + ++histogram[size / 16]; + else + ++large; + /* Total number of calls of any of the functions. */ + ++calls_total; + + /* Do the real work. */ + result = (struct header *) (*mallocp) (size + sizeof (struct header)); + if (result != NULL) + memset (result + 1, '\0', size); + + if (result == NULL) + ++failed[idx_calloc]; + else + /* Update the allocation data and write out the records if necessary. */ + update_data (result, size, 0); + + /* Return the pointer to the user buffer. */ + return result ? (void *) (result + 1) : NULL; +} + + +/* `free' replacement. We keep track of the memory usage if this is the + correct program. */ +void +free (void *ptr) +{ + struct header *real; + + /* `free (NULL)' has no effect. */ + if (ptr == NULL) + { + ++calls[idx_free]; + return; + } + + /* Determine real implementation if not already happened. */ + if (freep == NULL) + { + me (); + freep = (void (*) (void *)) dlsym (RTLD_NEXT, "free"); + } + + /* If this is not the correct program just use the normal function. */ + if (not_me) + { + (*freep) (ptr); + return; + } + + /* Determine the pointer to the header. */ + real = ((struct header *) ptr) - 1; + if (real->magic != MAGIC) + { + /* This block wasn't allocated here. */ + (*freep) (ptr); + return; + } + + /* Keep track of number of calls. */ + ++calls[idx_free]; + /* Keep track of total memory freed using `free'. */ + total[idx_free] += real->length; + + /* Update the allocation data and write out the records if necessary. */ + update_data (NULL, 0, real->length); + + /* Do the real work. */ + (*freep) (real); +} + + +/* Write some statistics to standard error. */ +static void +__attribute__ ((destructor)) +dest (void) +{ + int percent, cnt; + unsigned long int maxcalls; + + /* If we haven't done anything here just return. */ + if (not_me) + return; + /* If we should call any of the memory functions don't do any profiling. */ + not_me = 1; + + /* Finish the output file. */ + if (fd != -1) + { + /* Write the partially filled buffer. */ + write (fd, buffer, buffer_cnt * sizeof (struct entry)); + /* Go back to the beginning of the file. We allocated two records + here when we opened the file. */ + lseek (fd, 0, SEEK_SET); + /* Write out a record containing the total size. */ + first.stack = peak_total; + write (fd, &first, sizeof (struct entry)); + /* Write out another record containing the maximum for heap and + stack. */ + first.heap = peak_heap; + first.stack = peak_stack; + GETTIME (first.time_low, first.time_high); + write (fd, &first, sizeof (struct entry)); + + /* Close the file. */ + close (fd); + fd = -1; + } + + /* Write a colorful statistic. */ + fprintf (stderr, "\n\ +\e[01;32mMemory usage summary:\e[0;0m heap total: %llu, heap peak: %lu, stack peak: %lu\n\ +\e[04;34m total calls total memory failed calls\e[0m\n\ +\e[00;34m malloc|\e[0m %10lu %12llu %s%12lu\e[00;00m\n\ +\e[00;34mrealloc|\e[0m %10lu %12llu %s%12lu\e[00;00m (in place: %ld, dec: %ld)\n\ +\e[00;34m calloc|\e[0m %10lu %12llu %s%12lu\e[00;00m\n\ +\e[00;34m free|\e[0m %10lu %12llu\n", + grand_total, (unsigned long int) peak_heap, + (unsigned long int) peak_stack, + calls[idx_malloc], total[idx_malloc], + failed[idx_malloc] ? "\e[01;41m" : "", failed[idx_malloc], + calls[idx_realloc], total[idx_realloc], + failed[idx_realloc] ? "\e[01;41m" : "", failed[idx_realloc], + inplace, decreasing, + calls[idx_calloc], total[idx_calloc], + failed[idx_calloc] ? "\e[01;41m" : "", failed[idx_calloc], + calls[idx_free], total[idx_free]); + + /* Write out a histoogram of the sizes of the allocations. */ + fprintf (stderr, "\e[01;32mHistogram for block sizes:\e[0;0m\n"); + + /* Determine the maximum of all calls for each size range. */ + maxcalls = large; + for (cnt = 0; cnt < 65536; cnt += 16) + if (histogram[cnt / 16] > maxcalls) + maxcalls = histogram[cnt / 16]; + + for (cnt = 0; cnt < 65536; cnt += 16) + /* Only write out the nonzero entries. */ + if (histogram[cnt / 16] != 0) + { + percent = (histogram[cnt / 16] * 100) / calls_total; + fprintf (stderr, "%5d-%-5d%12lu ", cnt, cnt + 15, + histogram[cnt / 16]); + if (percent == 0) + fputs (" <1% \e[41;37m", stderr); + else + fprintf (stderr, "%3d%% \e[41;37m", percent); + + /* Draw a bar with a length corresponding to the current + percentage. */ + percent = (histogram[cnt / 16] * 50) / maxcalls; + while (percent-- > 0) + fputc ('=', stderr); + fputs ("\e[0;0m\n", stderr); + } + + if (large != 0) + { + percent = (large * 100) / calls_total; + fprintf (stderr, " large %12lu ", large); + if (percent == 0) + fputs (" <1% \e[41;37m", stderr); + else + fprintf (stderr, "%3d%% \e[41;37m", percent); + percent = (large * 50) / maxcalls; + while (percent-- > 0) + fputc ('=', stderr); + fputs ("\e[0;0m\n", stderr); + } +} |