/*************************************************************************** * __________ __ ___. * Open \______ \ ____ ____ | | _\_ |__ _______ ___ * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ * \/ \/ \/ \/ \/ * $Id$ * * Profiling routines counts ticks and calls to each profiled function. * * Copyright (C) 2005 by Brandon Low * * All files in this archive are subject to the GNU General Public License. * See the file COPYING in the source tree root for full license agreement. * * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY * KIND, either express or implied. * **************************************************************************** * * profile_func_enter() based on mcount found in gmon.c: * *************************************************************************** * Copyright (c) 1991, 1998 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. [rescinded 22 July 1999] * 4. 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. * @(#)gmon.c 5.3 (Berkeley) 5/22/91 */ #include #include #include #include #include #include "profile.h" static unsigned short profiling = PROF_OFF; static size_t recursion_level; static unsigned short indices[INDEX_SIZE]; static struct pfd_struct pfds[NUMPFDS]; /* This holds a pointer to the last pfd effected for time tracking */ static struct pfd_struct *last_pfd; /* These are used to track the time when we've lost the CPU so it doesn't count * against any of the profiled functions */ static int profiling_thread = -1; /* internal function prototypes */ static void profile_timer_tick(void); static void profile_timer_unregister(void); static void write_function_recursive(int fd, struct pfd_struct *pfd, int depth); /* Be careful to use the right one for the size of your variable */ #ifdef CPU_COLDFIRE #define ADDQI_L(_var,_value) \ asm ("addq.l %[value],%[var];" \ : [var] "+g" (_var) \ : [value] "I" (_value) ) #else #define ADDQI_L(var, value) var += value #endif void profile_thread_stopped(int current_thread) { if (current_thread == profiling_thread) { /* If profiling is busy or idle */ if (profiling < PROF_ERROR) { /* Unregister the timer so that other threads aren't interrupted */ timer_unregister(); } /* Make sure we don't waste time profiling when we're running the * wrong thread */ profiling |= PROF_OFF_THREAD; } } void profile_thread_started(int current_thread) { if (current_thread == profiling_thread) { /* Now we are allowed to profile again */ profiling &= PROF_ON_THREAD; /* if profiling was busy or idle */ if (profiling < PROF_ERROR) { /* After we de-mask, if profiling is active, reactivate the timer */ timer_register(0, profile_timer_unregister, TIMER_FREQ/10000, 0, profile_timer_tick); } } } static void profile_timer_tick(void) { if (!profiling) { register struct pfd_struct *my_last_pfd = last_pfd; if (my_last_pfd) { ADDQI_L(my_last_pfd->time,1); } } } static void profile_timer_unregister(void) { profiling = PROF_ERROR; profstop(); } /* This function clears the links on top level linkers, and clears the needed * parts of memory in the index array */ void profstart(int current_thread) { recursion_level = 0; profiling_thread = current_thread; last_pfd = (struct pfd_struct*)0; pfds[0].link = 0; pfds[0].self_pc = 0; memset(&indices,0,INDEX_SIZE * sizeof(unsigned short)); timer_register( 0, profile_timer_unregister, TIMER_FREQ/10000, 0, profile_timer_tick); profiling = PROF_ON; } static void write_function_recursive(int fd, struct pfd_struct *pfd, int depth){ unsigned short link = pfd->link; fdprintf(fd,"0x%08lX\t%08ld\t%08ld\t%04d\n", (size_t)pfd->self_pc, pfd->count, pfd->time, depth); if (link > 0 && link < NUMPFDS) { write_function_recursive(fd, &pfds[link], depth++); } } void profstop() { int profiling_exit = profiling; int fd = 0; int i; unsigned short current_index; timer_unregister(); profiling = PROF_OFF; fd = open("/profile.out", O_WRONLY|O_CREAT|O_TRUNC); if (profiling_exit == PROF_ERROR) { fdprintf(fd,"Profiling exited with an error.\n"); fdprintf(fd,"Overflow or timer stolen most likely.\n"); } fdprintf(fd,"PROFILE_THREAD\tPFDS_USED\n"); fdprintf(fd,"%08d\t%08d\n", profiling_thread, pfds[0].link); fdprintf(fd,"FUNCTION_PC\tCALL_COUNT\tTICKS\t\tDEPTH\n"); for (i = 0; i < INDEX_SIZE; i++) { current_index = indices[i]; if (current_index != 0) { write_function_recursive(fd, &pfds[current_index], 0); } } fdprintf(fd,"DEBUG PROFILE DATA FOLLOWS\n"); fdprintf(fd,"INDEX\tLOCATION\tSELF_PC\t\tCOUNT\t\tTIME\t\tLINK\tCALLER\n"); for (i = 0; i < NUMPFDS; i++) { struct pfd_struct *my_last_pfd = &pfds[i]; if (my_last_pfd->self_pc != 0) { fdprintf(fd, "%04d\t0x%08lX\t0x%08lX\t0x%08lX\t0x%08lX\t%04d\t0x%08lX\n", i, (size_t)my_last_pfd, (size_t)my_last_pfd->self_pc, my_last_pfd->count, my_last_pfd->time, my_last_pfd->link, (size_t)my_last_pfd->caller); } } fdprintf(fd,"INDEX_ADDRESS=INDEX\n"); for (i=0; i < INDEX_SIZE; i++) { fdprintf(fd,"%08lX=%04d\n",(size_t)&indices[i],indices[i]); } close(fd); } void profile_func_exit(void *self_pc, void *call_site) { (void)call_site; (void)self_pc; /* When we started timing, we set the time to the tick at that time * less the time already used in function */ if (profiling) { return; } profiling = PROF_BUSY; { register unsigned short my_recursion_level = recursion_level; if (my_recursion_level) { my_recursion_level--; recursion_level = my_recursion_level; } else { /* This shouldn't be necessary, maybe exit could be called first */ register struct pfd_struct *my_last_pfd = last_pfd; if (my_last_pfd) { last_pfd = my_last_pfd->caller; } } } profiling = PROF_ON; } #define ALLOCATE_PFD(temp) \ temp = ++pfds[0].link;\ if (temp >= NUMPFDS) goto overflow; \ pfd = &pfds[temp];\ pfd->self_pc = self_pc; pfd->count = 1; pfd->time = 0 void profile_func_enter(void *self_pc, void *from_pc) { struct pfd_struct *pfd; struct pfd_struct *prev_pfd; unsigned short *pfd_index_pointer; unsigned short pfd_index; /* check that we are profiling and that we aren't recursively invoked * this is equivalent to 'if (profiling != PROF_ON)' but it's faster */ if (profiling) { return; } profiling = PROF_BUSY; /* A check that the PC is in the code range here wouldn't hurt, but this is * logically guaranteed to be a valid address unless the constants are * breaking the rules. */ pfd_index_pointer = &indices[((size_t)from_pc)&INDEX_MASK]; pfd_index = *pfd_index_pointer; if (pfd_index == 0) { /* new caller, allocate new storage */ ALLOCATE_PFD(pfd_index); pfd->link = 0; *pfd_index_pointer = pfd_index; goto done; } pfd = &pfds[pfd_index]; if (pfd->self_pc == self_pc) { /* only / most recent function called by this caller, usual case */ /* increment count, start timing and exit */ goto found; } /* collision, bad for performance, look down the list of functions called by * colliding PCs */ for (; /* goto done */; ) { pfd_index = pfd->link; if (pfd_index == 0) { /* no more previously called functions, allocate a new one */ ALLOCATE_PFD(pfd_index); /* this function becomes the new head, link to the old head */ pfd->link = *pfd_index_pointer; /* and set the index to point to this function */ *pfd_index_pointer = pfd_index; /* start timing and exit */ goto done; } /* move along the chain */ prev_pfd = pfd; pfd = &pfds[pfd_index]; if (pfd->self_pc == self_pc) { /* found ourself */ /* Remove me from my old spot */ prev_pfd->link = pfd->link; /* Link to the old head */ pfd->link = *pfd_index_pointer; /* Make me head */ *pfd_index_pointer = pfd_index; /* increment count, start timing and exit */ goto found; } } /* We've found a pfd, increment it */ found: ADDQI_L(pfd->count,1); /* We've (found or created) and updated our pfd, save it and start timing */ done: { register struct pfd_struct *my_last_pfd = last_pfd; if (pfd != my_last_pfd) { /* If we are not recursing */ pfd->caller = my_last_pfd; last_pfd = pfd; } else { ADDQI_L(recursion_level,1); } } /* Start timing this function */ profiling = PROF_ON; return; /* normal return restores saved registers */ overflow: /* this is the same as 'profiling = PROF_ERROR' */ profiling = PROF_ERROR; return; }