/* * Copyright (c) 1999-2001 * The Regents of the University of California. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. Neither the name of the University nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * */ #include #include #include #include #include #include #include #undef REGION_PROFILE #include "regions.h" #include "profile.h" typedef struct Alloc_info { struct Alloc_info *next; char *file; int line; unsigned long size; unsigned long calls; } *ainfo; static ainfo ainfos = NULL; static region profile_region = NULL; /* perror(s) then exit */ void pfail(const char *s) { perror(s); exit(EXIT_FAILURE); } /************************************************************************** * * * Log information about an allocation -- generic * * * **************************************************************************/ static int registered_exit = 0; static ainfo find_ainfo(char *file, int line) { ainfo ai; for (ai = ainfos; ai; ai = ai->next) if (line == ai->line && !strcmp(file, ai->file)) return ai; if (!registered_exit) { if (atexit(profile)) fprintf(stderr, "Registration of profile at exit failed\n"); registered_exit = 1; } if (!profile_region) profile_region = newregion(); ai = ralloc(profile_region, struct Alloc_info); ai->file = file; ai->line = line; ai->size = 0; ai->calls = 0; ai->next = ainfos; ainfos = ai; return ai; } /************************************************************************** * * * Log information about an allocation -- GCC * * * * WARNING: This code uses __builtin_return_address, a non-portable * * feature of gcc, to trace the call chain back. You'll also get ugly * * output unless the addr2line (in GNU binutils) is installed. * * * * ANOTHER WARNING: The depths hard-coded in find_cinfo are only correct * * if find_cinfo is inlined. Ack! * * * **************************************************************************/ #define REGION_PROFILE_DEPTH 2 #undef TRACE_STACK #if defined(__GNUC__) && defined(__OPTIMIZE__) && REGION_PROFILE_DEPTH > 1 #define TRACE_STACK #endif #ifdef TRACE_STACK #if REGION_PROFILE_DEPTH > 6 #error "REGION_PROFILE_DEPTH must be less than 6. See find_cinfo()." #endif typedef struct Call_info { struct Call_info *next; void **stack; /* Array holding the call chain */ unsigned long size; unsigned long calls; } *cinfo; static cinfo cinfos = NULL; /* Find the current call chain and return a pointer to our status for it, or allocate a new entry if there is none. */ static cinfo find_cinfo(void) { void *calls[REGION_PROFILE_DEPTH]; int i; cinfo ci; /* Compute the call chain. This is an awful hack. */ i = 0; if (i < REGION_PROFILE_DEPTH) calls[i++] = __builtin_return_address(1); if (i < REGION_PROFILE_DEPTH) calls[i++] = __builtin_return_address(2); if (i < REGION_PROFILE_DEPTH) calls[i++] = __builtin_return_address(3); if (i < REGION_PROFILE_DEPTH) calls[i++] = __builtin_return_address(4); if (i < REGION_PROFILE_DEPTH) calls[i++] = __builtin_return_address(5); if (i < REGION_PROFILE_DEPTH) calls[i++] = __builtin_return_address(6); /* Add more if you want a higher call-depth (why would you?) */ /* Find it */ for (ci = cinfos; ci; ci = ci->next) if (!memcmp(calls, ci->stack, REGION_PROFILE_DEPTH*sizeof(void *))) return ci; if (!profile_region) profile_region = newregion(); ci = ralloc(profile_region, struct Call_info); ci->stack = rarrayalloc(profile_region, REGION_PROFILE_DEPTH, void *); memcpy(ci->stack, calls, REGION_PROFILE_DEPTH*sizeof(void *)); ci->size = 0; ci->calls = 0; ci->next = cinfos; cinfos = ci; return ci; } #endif static void add_alloc(char *file, int line, int size) { ainfo ai = find_ainfo(file, line); ai->calls++; ai->size += size; #ifdef TRACE_STACK { cinfo ci; ci = find_cinfo(); ci->calls++; ci->size += size; } #endif } /************************************************************************** * * * Intercept and log calls to region library * * * **************************************************************************/ void *profile_typed_ralloc(region r, size_t size, type_t type, char *file, int line) { add_alloc(file, line, size); return typed_ralloc(r, size, type); } void *profile_typed_rarrayalloc(region r, size_t n, size_t size, type_t type, char *file, int line) { add_alloc(file, line, n*size); return typed_rarrayalloc(r, n, size, type); } void *profile_typed_rarrayextend(region r, void *old, size_t n, size_t size, type_t type, char *file, int line) { add_alloc(file, line, n*size); /* XXX: Fix */ return typed_rarrayextend(r, old, n, size, type); } char *profile_rstralloc(region r, size_t size, char *file, int line) { add_alloc(file, line, size); return rstralloc(r, size); } char *profile_rstralloc0(region r, size_t size, char *file, int line) { add_alloc(file, line, size); return rstralloc0(r, size); } char *profile_rstrdup(region r, const char *s, char *file, int line) { add_alloc(file, line, strlen(s)); return rstrdup(r, s); } char *profile_rstrextend(region r, const char *old, size_t newsize, char *file, int line) { add_alloc(file, line, newsize); /* XXX: Fix */ return rstrextend(r, old, newsize); } char *profile_rstrextend0(region r, const char *old, size_t newsize, char *file, int line) { add_alloc(file, line, newsize); /* XXX: Fix */ return rstrextend0(r, old, newsize); } /************************************************************************** * * * Display results -- generic * * * **************************************************************************/ static FILE *out = NULL; /* Generic list -- used for generic sorting. Note that next field is at the top. */ typedef struct List { struct List *next; } *list; /* Sort a list. cmp should sort in reverse order. */ static list sort_list(list l, int (*cmp)(const void *, const void *)) { list cur, result; list *sorted; int i, length; region temp_region; /* Compute length of list */ for (cur = l, length = 0; cur; cur = cur->next, length++); temp_region = newregion(); sorted = rarrayalloc(temp_region, length, list *); for (cur = l, i = 0; cur; cur = cur->next) sorted[i++] = cur; qsort(sorted, length, sizeof(list *), cmp); result = NULL; for (i = 0; i < length; i++) { cur = result; result = sorted[i]; result->next = cur; } deleteregion(temp_region); return result; } typedef struct File_info { struct File_info *next; char *file; unsigned long size; unsigned long calls; unsigned long sites; } *finfo; static finfo finfos = NULL; static int finfo_cmp(const void *a, const void *b) { finfo *afi = (finfo *) a; finfo *bfi = (finfo *) b; return (*afi)->size - (*bfi)->size; /* Reverse order */ } static void print_finfos(void) { finfo fi; unsigned long size, sites, calls; finfos = (finfo) sort_list((list) finfos, finfo_cmp); size = sites = calls = 0; fprintf(out, " Bytes | Sites | Calls | File\n"); fprintf(out, " ------------+-------+----------+---------------------\n"); for (fi = finfos; fi; fi = fi->next) { size += fi->size; sites += fi->sites; calls += fi->calls; fprintf(out, " %12lu | %5lu | %8lu | %s\n", fi->size, fi->sites, fi->calls, fi->file); } fprintf(out, " ------------+-------+----------+---------------------\n"); fprintf(out, " %12lu | %5lu | %8lu | Total\n", size, sites, calls); } static int ainfo_cmp(const void *a, const void *b) { ainfo *afi = (ainfo *) a; ainfo *bfi = (ainfo *) b; return (*afi)->size - (*bfi)->size; /* Reverse order */ } static void print_ainfos(void) { ainfo ai; unsigned long size, calls; ainfos = (ainfo) sort_list((list) ainfos, ainfo_cmp); size = calls = 0; fprintf(out, " Bytes | Calls | Site\n"); fprintf(out, " ------------+----------+---------------------\n"); for (ai = ainfos; ai; ai = ai->next) { size += ai->size; calls += ai->calls; fprintf(out, " %12lu | %8lu | %s:%d\n", ai->size, ai->calls, ai->file, ai->line); } fprintf(out, " ------------+----------+---------------------\n"); fprintf(out, " %12lu | %8lu | Total\n", size, calls); } static finfo find_finfo(char *file) { finfo fi; for (fi = finfos; fi; fi = fi->next) if (!strcmp(file, fi->file)) return fi; fi = ralloc(profile_region, struct File_info); fi->file = file; fi->size = 0; fi->calls = 0; fi->sites = 0; fi->next = finfos; finfos = fi; return fi; } static void gather_finfo(void) { ainfo ai; for (ai = ainfos; ai; ai = ai->next) { finfo fi = find_finfo(ai->file); fi->size += ai->size; fi->calls += ai->calls; fi->sites++; } } /************************************************************************** * * * Display results -- GCC * * * **************************************************************************/ #ifdef TRACE_STACK pid_t child_pid = 0; int child_in[2], child_out[2]; /* pipes to child process */ static void start_prettiness(void) { if (pipe(child_in) || pipe(child_out)) pfail("Unable to open pipe to child process"); if (!(child_pid = fork())) { /* Child process */ pid_t parent_pid; char filename[64]; if (dup2(child_in[0], STDIN_FILENO) == -1) pfail("Unable to open pipe from parent"); close(child_in[0]); close(child_in[1]); if (dup2(child_out[1], STDOUT_FILENO) == -1) pfail("Unable to open pipe to parent"); close(child_out[0]); close(child_out[1]); parent_pid = getppid(); snprintf(filename, 64, "/proc/%d/exe", parent_pid); filename[63] = '\0'; execlp("addr2line", "addr2line", "-s", "-e", filename, 0); fprintf(stderr, "Unable to fork addr2line\n"); exit(EXIT_FAILURE); } else { close(child_in[0]); close(child_out[1]); } } /* Turn p into a file:line string */ static char *prettify(void *p) { #define BUFSIZE 1024 static char buf[BUFSIZE]; int size; /*printf("To child: %p\n", p);*/ size = snprintf(buf, BUFSIZE, "%p\n", p); write(child_in[1], buf, size); size = read(child_out[0], buf, BUFSIZE - 1); if (!size) pfail("Unable to read from child process"); buf[size-1] = '\0'; /* Kill \n */ /*printf("Read: [%s]\n", buf);*/ return buf; } static void end_prettiness(void) { if (child_pid) kill(child_pid, SIGHUP); } static int cinfo_cmp(const void *a, const void *b) { cinfo *aci = (cinfo *) a; cinfo *bci = (cinfo *) b; return (*aci)->size - (*bci)->size; /* Reverse order */ } /* Print the call chain information out to a file. */ static void print_cinfos(void) { cinfo ci; unsigned long size, calls; int i; cinfos = (cinfo) sort_list((list) cinfos, cinfo_cmp); size = calls = 0; start_prettiness(); fprintf(out, " Bytes | Calls | Call Stack\n"); fprintf(out, " ------------+----------+---------------------\n"); for (ci = cinfos; ci; ci = ci->next) { size += ci->size; calls += ci->calls; fprintf(out, " %12lu | %8lu | ", ci->size, ci->calls); for (i = 0; i < REGION_PROFILE_DEPTH; i++) fprintf(out, "%s ", prettify(ci->stack[i])); fprintf(out, "\n"); } fprintf(out, " ------------+----------+---------------------\n"); fprintf(out, " %12lu | %8lu | Total\n", size, calls); end_prettiness(); } #endif void profile(void) { if (profile_region == NULL) return; gather_finfo(); if (!(out = fopen("profile.out", "w"))) pfail("Unable to open profile.out"); fprintf(out, "---------------------------\n"); fprintf(out, "Region Library Memory Usage\n"); fprintf(out, "---------------------------\n\n"); print_finfos(); fprintf(out, "\n"); print_ainfos(); #ifdef TRACE_STACK fprintf(out, "\n"); print_cinfos(); #endif fclose(out); }