diff --git a/apps/codecs.c b/apps/codecs.c index d8ad7146df..88b2ea4e07 100644 --- a/apps/codecs.c +++ b/apps/codecs.c @@ -218,6 +218,12 @@ struct codec_api ci = { /* new stuff at the end, sort into place next time the API gets incompatible */ +#ifdef RB_PROFILE + profile_thread, + profstop, + profile_func_enter, + profile_func_exit, +#endif }; diff --git a/apps/codecs.h b/apps/codecs.h index 320431f3bf..70799f790e 100644 --- a/apps/codecs.h +++ b/apps/codecs.h @@ -43,6 +43,9 @@ #include "mpeg.h" #include "audio.h" #include "mp3_playback.h" +#ifdef RB_PROFILE +#include "profile.h" +#endif #if (CONFIG_CODEC == SWCODEC) #include "dsp.h" #include "pcm_playback.h" @@ -83,7 +86,7 @@ #define CODEC_MAGIC 0x52434F44 /* RCOD */ /* increase this every time the api struct changes */ -#define CODEC_API_VERSION 1 +#define CODEC_API_VERSION 2 /* update this to latest version if a change to the api struct breaks backwards compatibility (and please take the opportunity to sort in any @@ -289,6 +292,12 @@ struct codec_api { /* new stuff at the end, sort into place next time the API gets incompatible */ +#ifdef RB_PROFILE + void (*profile_thread)(void); + void (*profstop)(void); + void (*profile_func_enter)(void *this_fn, void *call_site); + void (*profile_func_exit)(void *this_fn, void *call_site); +#endif }; diff --git a/apps/codecs/Tremor/Makefile b/apps/codecs/Tremor/Makefile index 7fd5de852b..cec9797f63 100644 --- a/apps/codecs/Tremor/Makefile +++ b/apps/codecs/Tremor/Makefile @@ -16,7 +16,7 @@ endif TREMOROPTS = -O2 CFLAGS = $(GCCOPTS) $(TREMOROPTS) $(INCLUDES) $(TARGET) $(EXTRA_DEFINES) \ - -DMEM=${MEMORYSIZE} + -DMEM=${MEMORYSIZE} ${PROFILE_OPTS} # This sets up 'SRC' based on the files mentioned in SOURCES include $(TOOLSDIR)/makesrc.inc diff --git a/apps/codecs/dumb/Makefile b/apps/codecs/dumb/Makefile index fa647f3924..d2a045285e 100644 --- a/apps/codecs/dumb/Makefile +++ b/apps/codecs/dumb/Makefile @@ -178,7 +178,7 @@ WFLAGS_ALLEGRO := -Wno-missing-declarations OFLAGS := -O2 -ffast-math -fomit-frame-pointer DBGFLAGS := -DDEBUGMODE=1 -g3 -CFLAGS_RELEASE := -Iinclude $(WFLAGS) $(OFLAGS) +CFLAGS_RELEASE := -Iinclude $(WFLAGS) $(OFLAGS) $(PROFILE_OPTS) CFLAGS_DEBUG := -Iinclude $(WFLAGS) $(DBGFLAGS) LDFLAGS := -s diff --git a/apps/codecs/lib/codeclib.c b/apps/codecs/lib/codeclib.c index cad8f53deb..1f070e8eac 100644 --- a/apps/codecs/lib/codeclib.c +++ b/apps/codecs/lib/codeclib.c @@ -149,3 +149,14 @@ void qsort(void *base, size_t nmemb, size_t size, { local_rb->qsort(base,nmemb,size,compar); } + +#ifdef RB_PROFILE +void __cyg_profile_func_enter(void *this_fn, void *call_site) { + (void)call_site; + local_rb->profile_func_enter(this_fn, __builtin_return_address(1)); +} + +void __cyg_profile_func_exit(void *this_fn, void *call_site) { + local_rb->profile_func_exit(this_fn,call_site); +} +#endif diff --git a/apps/codecs/lib/codeclib.h b/apps/codecs/lib/codeclib.h index e112112756..c2e7869aa4 100644 --- a/apps/codecs/lib/codeclib.h +++ b/apps/codecs/lib/codeclib.h @@ -58,3 +58,9 @@ void qsort(void *base, size_t nmemb, size_t size, int(*compar)(const void *, con int codec_init(struct codec_api* rb); void codec_set_replaygain(struct mp3entry* id3); +#ifdef RB_PROFILE +void __cyg_profile_func_enter(void *this_fn, void *call_site) + NO_PROF_ATTR ICODE_ATTR; +void __cyg_profile_func_exit(void *this_fn, void *call_site) + NO_PROF_ATTR ICODE_ATTR; +#endif diff --git a/apps/codecs/libFLAC/Makefile b/apps/codecs/libFLAC/Makefile index 4008479649..d1e78bd229 100644 --- a/apps/codecs/libFLAC/Makefile +++ b/apps/codecs/libFLAC/Makefile @@ -27,7 +27,7 @@ ifeq ($(NEWGCC), 1) FLACOPTS += --param large-function-insns=10000 endif -CFLAGS = $(GCCOPTS) $(FLACOPTS)\ +CFLAGS = $(GCCOPTS) $(PROFILE_OPTS) $(FLACOPTS)\ $(INCLUDES) $(TARGET) $(EXTRA_DEFINES) -DMEM=${MEMORYSIZE} # This sets up 'SRC' based on the files mentioned in SOURCES diff --git a/apps/codecs/liba52/Makefile b/apps/codecs/liba52/Makefile index 8e0501798d..ea3c01c931 100644 --- a/apps/codecs/liba52/Makefile +++ b/apps/codecs/liba52/Makefile @@ -16,7 +16,7 @@ endif A52OPTS = -O2 CFLAGS = $(GCCOPTS) $(A52OPTS) $(INCLUDES) $(TARGET) $(EXTRA_DEFINES) \ - -DMEM=${MEMORYSIZE} + -DMEM=${MEMORYSIZE} $(PROFILE_OPTS) # This sets up 'SRC' based on the files mentioned in SOURCES include $(TOOLSDIR)/makesrc.inc diff --git a/apps/codecs/libffmpegFLAC/Makefile b/apps/codecs/libffmpegFLAC/Makefile index 60da5ae7ff..7d9030e648 100644 --- a/apps/codecs/libffmpegFLAC/Makefile +++ b/apps/codecs/libffmpegFLAC/Makefile @@ -16,7 +16,7 @@ endif FLACOPTS = -O2 CFLAGS = $(GCCOPTS) $(FLACOPTS) $(INCLUDES) $(TARGET) $(EXTRA_DEFINES) \ - -DMEM=${MEMORYSIZE} + -DMEM=${MEMORYSIZE} $(PROFILE_OPTS) # This sets up 'SRC' based on the files mentioned in SOURCES include $(TOOLSDIR)/makesrc.inc diff --git a/apps/codecs/libm4a/Makefile b/apps/codecs/libm4a/Makefile index 7f870c9407..fcbc10045e 100644 --- a/apps/codecs/libm4a/Makefile +++ b/apps/codecs/libm4a/Makefile @@ -16,7 +16,7 @@ endif M4AOPTS = -O3 CFLAGS = $(GCCOPTS) $(M4AOPTS) $(INCLUDES) $(TARGET) $(EXTRA_DEFINES) \ - -DMEM=${MEMORYSIZE} + -DMEM=${MEMORYSIZE} $(PROFILE_OPTS) # This sets up 'SRC' based on the files mentioned in SOURCES include $(TOOLSDIR)/makesrc.inc diff --git a/apps/codecs/libmad/Makefile b/apps/codecs/libmad/Makefile index e2f2643b27..5eaf9f1517 100644 --- a/apps/codecs/libmad/Makefile +++ b/apps/codecs/libmad/Makefile @@ -17,7 +17,7 @@ endif # NOTE: FPM_ define has been moved to global.h MADOPTS = -DNDEBUG -O2 CFLAGS = $(GCCOPTS) $(MADOPTS) $(INCLUDES) $(TARGET) $(EXTRA_DEFINES) \ - -DMEM=${MEMORYSIZE} + -DMEM=${MEMORYSIZE} $(PROFILE_OPTS) # This sets up 'SRC' based on the files mentioned in SOURCES include $(TOOLSDIR)/makesrc.inc diff --git a/apps/codecs/libmusepack/Makefile b/apps/codecs/libmusepack/Makefile index 8e65915feb..6bfa2f9462 100644 --- a/apps/codecs/libmusepack/Makefile +++ b/apps/codecs/libmusepack/Makefile @@ -16,7 +16,7 @@ endif MUSEPACKOPTS = -O2 CFLAGS = $(GCCOPTS) $(MUSEPACKOPTS) $(INCLUDES) $(TARGET) $(EXTRA_DEFINES) \ - -DMEM=${MEMORYSIZE} + -DMEM=${MEMORYSIZE} $(PROFILE_OPTS) # This sets up 'SRC' based on the files mentioned in SOURCES include $(TOOLSDIR)/makesrc.inc diff --git a/apps/codecs/libwavpack/Makefile b/apps/codecs/libwavpack/Makefile index 39478799bf..eba67fa7fc 100644 --- a/apps/codecs/libwavpack/Makefile +++ b/apps/codecs/libwavpack/Makefile @@ -16,7 +16,7 @@ endif WAVPACKOPTS = -O2 CFLAGS = $(GCCOPTS) $(WAVPACKOPTS) $(INCLUDES) $(TARGET) $(EXTRA_DEFINES) \ - -DMEM=${MEMORYSIZE} + -DMEM=${MEMORYSIZE} $(PROFILE_OPTS) # This sets up 'SRC' based on the files mentioned in SOURCES include $(TOOLSDIR)/makesrc.inc diff --git a/apps/plugin.c b/apps/plugin.c index 44eb0dc04c..b907604528 100644 --- a/apps/plugin.c +++ b/apps/plugin.c @@ -363,6 +363,12 @@ static const struct plugin_api rockbox_api = { /* new stuff at the end, sort into place next time the API gets incompatible */ +#ifdef RB_PROFILE + profile_thread, + profstop, + profile_func_enter, + profile_func_exit, +#endif }; diff --git a/apps/plugin.h b/apps/plugin.h index 69a2a79ba8..157831bf19 100644 --- a/apps/plugin.h +++ b/apps/plugin.h @@ -44,6 +44,9 @@ #include "mpeg.h" #include "audio.h" #include "mp3_playback.h" +#ifdef RB_PROFILE +#include "profile.h" +#endif #include "misc.h" #if (HWCODEC == SWCODEC) #include "pcm_playback.h" @@ -93,7 +96,7 @@ #define PLUGIN_MAGIC 0x526F634B /* RocK */ /* increase this every time the api struct changes */ -#define PLUGIN_API_VERSION 1 +#define PLUGIN_API_VERSION 2 /* update this to latest version if a change to the api struct breaks backwards compatibility (and please take the opportunity to sort in any @@ -424,6 +427,12 @@ struct plugin_api { /* new stuff at the end, sort into place next time the API gets incompatible */ +#ifdef RB_PROFILE + void (*profile_thread)(void); + void (*profstop)(void); + void (*profile_func_enter)(void *this_fn, void *call_site); + void (*profile_func_exit)(void *this_fn, void *call_site); +#endif }; diff --git a/apps/plugins/lib/SOURCES b/apps/plugins/lib/SOURCES index 4d4247e3bb..0f6e13ebb4 100644 --- a/apps/plugins/lib/SOURCES +++ b/apps/plugins/lib/SOURCES @@ -12,3 +12,6 @@ xlcd.c #ifdef HAVE_LCD_CHARCELLS playergfx.c #endif +#ifdef RB_PROFILE +profile_plugin.c +#endif diff --git a/apps/plugins/lib/profile_plugin.c b/apps/plugins/lib/profile_plugin.c new file mode 100644 index 0000000000..3318476a89 --- /dev/null +++ b/apps/plugins/lib/profile_plugin.c @@ -0,0 +1,38 @@ +/*************************************************************************** +* __________ __ ___. +* Open \______ \ ____ ____ | | _\_ |__ _______ ___ +* Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / +* Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < +* Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ +* \/ \/ \/ \/ \/ +* $Id$ +* +* Passthrough routines for plugin profiling +* +* Copyright (C) 2006 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. +* +****************************************************************************/ + +#include "plugin.h" + +static struct plugin_api *local_rb = NULL; /* global api struct pointer */ + +void profile_init(struct plugin_api* pa) +{ + local_rb = pa; +} + +void __cyg_profile_func_enter(void *this_fn, void *call_site) { + (void)call_site; + local_rb->profile_func_enter(this_fn, __builtin_return_address(1)); +} + +void __cyg_profile_func_exit(void *this_fn, void *call_site) { + local_rb->profile_func_exit(this_fn,call_site); +} diff --git a/apps/plugins/lib/profile_plugin.h b/apps/plugins/lib/profile_plugin.h new file mode 100644 index 0000000000..71cff37033 --- /dev/null +++ b/apps/plugins/lib/profile_plugin.h @@ -0,0 +1,35 @@ +/*************************************************************************** +* __________ __ ___. +* Open \______ \ ____ ____ | | _\_ |__ _______ ___ +* Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / +* Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < +* Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ +* \/ \/ \/ \/ \/ +* $Id$ +* +* Passthrough routines for plugin profiling. +* +* Copyright (C) 2005 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. +* +****************************************************************************/ + +#ifndef __PROFILE_PLUGIN_H__ +#define __PROFILE_PLUGIN_H__ + +#include "plugin.h" + +void profile_init(struct plugin_api* pa); + +void __cyg_profile_func_enter(void *this_fn, void *call_site) + NO_PROF_ATTR ICODE_ATTR; +void __cyg_profile_func_exit(void *this_fn, void *call_site) + NO_PROF_ATTR ICODE_ATTR; + +#endif /* __PROFILE_PLUGIN_H__ */ + diff --git a/apps/plugins/wav2wv.c b/apps/plugins/wav2wv.c index 20a458960f..812ed176c4 100644 --- a/apps/plugins/wav2wv.c +++ b/apps/plugins/wav2wv.c @@ -17,6 +17,9 @@ * ****************************************************************************/ #include "plugin.h" +#ifdef RB_PROFILE +#include "lib/profile_plugin.h" +#endif #include @@ -289,6 +292,15 @@ static int wav2wv (char *filename) enum plugin_status plugin_start(struct plugin_api* api, void *parameter) { +#ifdef RB_PROFILE + /* This doesn't start profiling or anything, it just gives the + * profiling functions that are compiled in someplace to call, + * this is needed here to let this compile with profiling support + * since it calls code from a codec that is compiled with profiling + * support */ + profile_init(api); +#endif + rb = api; if (!parameter) diff --git a/docs/FILES b/docs/FILES index f1054eadd9..21f2faf0a8 100644 --- a/docs/FILES +++ b/docs/FILES @@ -8,6 +8,7 @@ CUSTOM_WPS_FORMAT FAQ FILES HISTORY +LICENSES NODO README TECH diff --git a/docs/LICENSES b/docs/LICENSES new file mode 100644 index 0000000000..652a276eed --- /dev/null +++ b/docs/LICENSES @@ -0,0 +1,36 @@ +This file contains license for software imported into Rockbox but governed by +a previous license. Each section begins by identifying the code in Rockbox +and the source from of those code, and is followed by the license text. + +************************************************************************* +In: profile.c, profile_func_enter +From: gcc - gmon.c, mcount +************************************************************************* +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 diff --git a/docs/TECH b/docs/TECH index 4532af3c42..b22a8c4c97 100644 --- a/docs/TECH +++ b/docs/TECH @@ -172,3 +172,29 @@ Charging controlled charging that Rockbox can't affect.) ... + +Profiling + + Rockbox contains a profiling system which can be used to monitor call count + and time in function for a specific set of functions on a single thread. + + To use this functionality: + 1) Configure a developer build with profiling support. + 2) Make sure that the functions of interest will be compiled with the + PROFILE_OPTS added to their CFLAGS + 3) On the same thread as these functions will be run, surround the relevent + running time with calls to profile_thread and profstop. (For codecs, + this can be done in the codec.c file for example) + 4) Compile and run the code on the target, after the section to be profiled + exits (when profstop is called) a profile.out file will be written to + the player's root. + 5) Use the tools/profile_reader/profile_reader.pl script to convert the + profile.out into a human readable format. This script requires the + relevent map files and object (or library) files created in the build. + (ex: ./profile_reader.pl profile.out vorbis.map libTremor.a 0) + + There is also a profile_comparator.pl script which can compare two profile + runs as output by the above script to show percent change from optimization + + profile_reader.pl requires a recent binutils that can automatically handle + target object files, or objdump in path to be the target-objdump. diff --git a/firmware/SOURCES b/firmware/SOURCES index 4fe5fc91f5..3a5f551585 100644 --- a/firmware/SOURCES +++ b/firmware/SOURCES @@ -123,6 +123,9 @@ font.c #endif id3.c #ifndef SIMULATOR +#ifdef RB_PROFILE +profile.c +#endif /* RB_PROFILE */ hwcompat.c kernel.c rolo.c diff --git a/firmware/export/config.h b/firmware/export/config.h index 5e3bbeb645..8df596665f 100644 --- a/firmware/export/config.h +++ b/firmware/export/config.h @@ -174,6 +174,14 @@ #define CODEC_SIZE 0 #endif +/* This attribute can be used to ensure that certain symbols are never profiled + * which can be important as profiling a function de-inlines it */ +#ifdef RB_PROFILE +#define NO_PROF_ATTR __attribute__ ((no_instrument_function)) +#else +#define NO_PROF_ATTR +#endif + /* IRAM usage */ #if !defined(SIMULATOR) && /* Not for simulators */ \ (((CONFIG_CPU == SH7034) && !defined(PLUGIN)) || /* SH1 archos: core only */ \ diff --git a/firmware/export/profile.h b/firmware/export/profile.h new file mode 100644 index 0000000000..cb751328ae --- /dev/null +++ b/firmware/export/profile.h @@ -0,0 +1,80 @@ +/*************************************************************************** + * __________ __ ___. + * 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. + ****************************************************************************/ + +#ifndef _SYS_PROFILE_H +#define _SYS_PROFILE_H 1 + +#include + +/* PFD is Profiled Function Data */ + +/* Indices are shorts which means that we use 4k of RAM */ +#define INDEX_BITS 11 /* What is a reasonable size for this? */ +#define INDEX_SIZE 2048 /* 2 ^ INDEX_BITS */ +#define INDEX_MASK 0x7FF /* lower INDEX_BITS 1 */ + +/* + * In the current setup (pfd has 4 longs and 2 shorts) this uses 20k of RAM + * for profiling, and allows for profiling sections of code with up-to + * 1024 function caller->callee pairs + */ +#define NUMPFDS 1024 + +struct pfd_struct { + void *self_pc; + unsigned long count; + unsigned long time; + unsigned short link; + struct pfd_struct *caller; +}; + +/* Possible states of profiling */ +#define PROF_ON 0x00 +#define PROF_BUSY 0x01 +#define PROF_ERROR 0x02 +#define PROF_OFF 0x03 +/* Masks for thread switches */ +#define PROF_OFF_THREAD 0x10 +#define PROF_ON_THREAD 0x0F + +extern int current_thread; + +/* Initialize and start profiling */ +void profstart(int current_thread) + NO_PROF_ATTR; + +/* Clean up and write profile data */ +void profstop (void) + NO_PROF_ATTR; + +/* Called every time a thread stops, we check if it's our thread and store + * temporary timing data if it is */ +void profile_thread_stopped(int current_thread) + NO_PROF_ATTR; +/* Called when a thread starts, we check if it's our thread and resume timing */ +void profile_thread_started(int current_thread) + NO_PROF_ATTR; + +void profile_func_exit(void *this_fn, void *call_site) + NO_PROF_ATTR ICODE_ATTR; +void profile_func_enter(void *this_fn, void *call_site) + NO_PROF_ATTR ICODE_ATTR; + +#endif /*_SYS_PROFILE_H*/ diff --git a/firmware/export/thread.h b/firmware/export/thread.h index fe1612d2f4..da61d1a632 100644 --- a/firmware/export/thread.h +++ b/firmware/export/thread.h @@ -37,5 +37,8 @@ void sleep_thread(void); void wake_up_thread(void); void init_threads(void); int thread_stack_usage(int threadnum); +#ifdef RB_PROFILE +void profile_thread(void); +#endif #endif diff --git a/firmware/profile.c b/firmware/profile.c new file mode 100644 index 0000000000..8ad46515f8 --- /dev/null +++ b/firmware/profile.c @@ -0,0 +1,303 @@ +/*************************************************************************** + * __________ __ ___. + * 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 */ +#define ADDQI_L(_var,_value) \ + asm ("addq.l %[value],%[var];" \ + : [var] "+g" (_var) \ + : [value] "I" (_value) ) + +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, + CPU_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, CPU_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; + } + /* this is equivalent to 'profiling = PROF_BUSY;' but it's faster */ + 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; +} diff --git a/firmware/thread.c b/firmware/thread.c index 281801418f..d8282111b5 100644 --- a/firmware/thread.c +++ b/firmware/thread.c @@ -78,6 +78,13 @@ void switch_thread(void) ICODE_ATTR; static inline void store_context(void* addr) __attribute__ ((always_inline)); static inline void load_context(const void* addr) __attribute__ ((always_inline)); +#ifdef RB_PROFILE +#include +void profile_thread(void) { + profstart(current_thread); +} +#endif + #if defined(CPU_ARM) /*--------------------------------------------------------------------------- * Store non-volatile context. @@ -245,6 +252,9 @@ static inline void load_context(const void* addr) */ void switch_thread(void) { +#ifdef RB_PROFILE + profile_thread_stopped(current_thread); +#endif int current; unsigned int *stackptr; @@ -284,6 +294,9 @@ void switch_thread(void) current_thread = current; load_context(&thread_contexts[current]); +#ifdef RB_PROFILE + profile_thread_started(current_thread); +#endif } void sleep_thread(void) diff --git a/tools/FILES b/tools/FILES index 19d3a5576f..f4b1c6dca7 100644 --- a/tools/FILES +++ b/tools/FILES @@ -23,3 +23,4 @@ ucl/*.[ch] ucl/src/*.[ch] ucl/src/Makefile ucl/include/ucl/*.h +profile_reader/*.pl diff --git a/tools/buildzip.pl b/tools/buildzip.pl index eabf739d0d..934b0035e4 100755 --- a/tools/buildzip.pl +++ b/tools/buildzip.pl @@ -199,6 +199,7 @@ sub buildzip { "CUSTOM_CFG_FORMAT", "CUSTOM_WPS_FORMAT", "FAQ", + "LICENSES", "NODO", "TECH")) { `cp $ROOT/docs/$_ .rockbox/docs/$_.txt`; diff --git a/tools/configure b/tools/configure index 2cd855e0c5..2bea201324 100755 --- a/tools/configure +++ b/tools/configure @@ -240,7 +240,7 @@ whichdevel () { # echo "" echo "Enter your developer options (press enter when done)" - echo "(D)EBUG, (L)ogf, (S)imulator" + echo "(D)EBUG, (L)ogf, (S)imulator, (P)rofiling" cont=1 while [ $cont = "1" ]; do @@ -249,19 +249,29 @@ whichdevel () { case $option in [Dd]) - echo "define DEBUG" - debug="-DDEBUG" - GCCOPTS="$GCCOPTS -g -DDEBUG" + if [ "yes" = "$profile" ]; then + echo "Debug is incompatible with profiling" + else + echo "define DEBUG" + use_debug="yes" + fi ;; [Ll]) - logf="yes" echo "logf() support enabled" - use_logf="#define ROCKBOX_HAS_LOGF 1" + logf="yes" ;; [Ss]) echo "Simulator build enabled" simulator="yes" ;; + [Pp]) + if [ "yes" = "$use_debug" ]; then + echo "Profiling is incompatible with debug" + else + echo "Profiling support is enabled" + profile="yes" + fi + ;; *) echo "done" cont=0 @@ -269,11 +279,23 @@ whichdevel () { esac done + if [ "yes" = "$use_debug" ]; then + debug="-DDEBUG" + GCCOPTS="$GCCOPTS -g -DDEBUG" + fi + if [ "yes" = "$logf" ]; then + use_logf="#define ROCKBOX_HAS_LOGF 1" + fi if [ "yes" = "$simulator" ]; then debug="-DDEBUG" - extradefines="-DSIMULATOR" + extradefines="$extradefines -DSIMULATOR" whichsim fi + if [ "yes" = "$profile" ]; then + extradefines="$extradefines -DRB_PROFILE" + PROFILE_OPTS="-finstrument-functions" + GCCOPTS="$GCCOPTS $GCCOPTIMIZE" + fi } whichsim () { @@ -902,6 +924,7 @@ sed > Makefile \ -e "s,@FLASHFILE@,${flash},g" \ -e "s,@PLUGINS@,${plugins},g" \ -e "s,@CODECS@,${codecs},g" \ + -e "s,@PROFILE_OPTS@,${PROFILE_OPTS},g" \ -e "s,@GCCOPTS@,${GCCOPTS},g" \ -e "s!@LDOPTS@!${LDOPTS}!g" \ -e "s,@LOADADDRESS@,${loadaddress},g" \ @@ -952,6 +975,7 @@ export WINDRES=@WINDRES@ export DLLTOOL=@DLLTOOL@ export DLLWRAP=@DLLWRAP@ export RANLIB=@RANLIB@ +export PROFILE_OPTS=@PROFILE_OPTS@ export GCCOPTS=@GCCOPTS@ export LOADADDRESS=@LOADADDRESS@ export SIMVER=@SIMVER@ diff --git a/tools/profile_reader/profile_comparator.pl b/tools/profile_reader/profile_comparator.pl new file mode 100755 index 0000000000..da5e3004c9 --- /dev/null +++ b/tools/profile_reader/profile_comparator.pl @@ -0,0 +1,104 @@ +#!/usr/bin/perl +sub error { + print("Error: @_\n"); + exit(1); +} +sub usage { + if (@_) { + print STDERR ("Error: @_\n"); + } + print STDERR ("USAGE:\n"); + print STDERR ("$0 file1 file2 [showcalldiff]\n"); + print STDERR + ("\tfile[12] output from profile_reader.pl to compare\n"); + print STDERR + ("\tshowcalldiff show the percent change in calls instead of ticks\n"); + exit(1); +} +if ($ARGV[0] =~ m/-(h|help|-help)/) { + usage(); +} +if (@ARGV < 2) { + usage("Requires at least 2 arguments"); +} +open(FILE1,shift) || error("Couldn't open file1"); +my @file1 = ; +close(FILE1); +open(FILE2,shift) || error("Couldn't open file2"); +my @file2 = ; +close(FILE2); +my $showcalldiff = shift; +my %calls1; +my %calls2; +my @calls = (\%calls1,\%calls2); +my $start = 0; +my @files = (\@file1,\@file2); +my @allcalls = (0,0); +my @allticks = (0,0); +for ( $i=0; $i <= $#files; $i++ ) { + my $file = $files[$i]; + foreach $line(@$file) { + chomp($line); + if ( $line =~ m/By calls/ ) { + $start = 1; + next; + } + if ( $line =~ m/By ticks/ ) { + $start = 0; + last; + } + if ( $start == 1) { + my @line = split(/[[:space:]]+/,$line); + $allcalls[$i] += $line[1]; + $allticks[$i] += $line[3]; + $calls[$i]{$line[5]} = [($line[1],$line[3])]; + } + } +} +printf("File one calls: %08ld, ticks: %08ld\n",$allcalls[0],$allticks[0]); +printf("File two calls: %08ld, ticks: %08ld\n",$allcalls[1],$allticks[1]); +printf("Percent change: %+7.2f%%, ticks: %+7.2f%%\n", + ($allcalls[1]-$allcalls[0])/$allcalls[0]*100, + ($allticks[1]-$allticks[0])/$allticks[0]*100); +my @allkeys = keys(%calls1); +push(@allkeys,keys(%calls2)); +my %u = (); +my @keys = grep {defined} map { + if (exists $u{$_}) { undef; } else { $u{$_}=undef;$_; } +} @allkeys; +undef %u; +my %byticks; +my %bycalls; +foreach $key(@keys) { + my $values1 = $calls1{$key}; + my $values2 = $calls2{$key}; + my $calldiff = @$values2[0]-@$values1[0]; + my $totalcalls = @$values2[0]+@$values1[0]; + my $tickdiff = @$values2[1]-@$values1[1]; + my $totalticks = @$values2[1]+@$values1[1]; + my $pdiff; + my $result; + if ($showcalldiff) { + $pdiff = $calldiff/(@$values1[0]>0?@$values1[0]:1)*100; + $result = sprintf("%+7.2f%% Calls: %+09d Symbol: %s$key\n", + $pdiff, $calldiff, + (exists $calls1{$key} && exists $calls2{$key})?"":"LONE "); + } else { + $pdiff = $tickdiff/(@$values1[1]>0?@$values1[1]:1)*100; + $result = sprintf("%+7.2f%% Ticks: %+09d Symbol: %s$key\n", + $pdiff, $tickdiff, + (exists $calls1{$key} && exists $calls2{$key})?"":"LONE "); + } + $bycalls{sprintf("%08X$key",$totalcalls)} = $result; + $byticks{sprintf("%08X$key",$totalticks)} = $result; +} +my @calls = sort(keys(%bycalls)); +print("By calls\n"); +foreach $call(@calls) { + print($bycalls{$call}); +} +my @ticks = sort(keys(%byticks)); +print("By ticks\n"); +foreach $tick(@ticks) { + print($byticks{$tick}); +} diff --git a/tools/profile_reader/profile_reader.pl b/tools/profile_reader/profile_reader.pl new file mode 100755 index 0000000000..088ba7186e --- /dev/null +++ b/tools/profile_reader/profile_reader.pl @@ -0,0 +1,236 @@ +#!/usr/bin/perl + +sub error { + print STDERR ("Error: @_\n"); + exit(1); +} + +sub warning { + print STDERR ("Warning: @_\n"); +} + +# string (filename.map) +# return hash(string:hash(string:number)) +sub read_map { + open(MAP_FILE,$_[0]) || error("Couldn't open a map $_[0]"); + my %retval; + while () { + chomp; + my @parts = split(/[[:space:]]+/); + if (@parts != 5) { + next; + } + if ($parts[1] =~ m/\.(text|data|rodata|bss|icode|idata|irodata|ibss)/) { + my $region = $parts[1]; + my $number = $parts[2]; + @parts = split(/\//,$parts[4]); + @parts = split(/[\(\)]/,$parts[$#parts]); + my $library = $retval{$parts[0]}; + my %library = %$library; + my $object = $parts[$#parts]; + $library{$object . $region} = $number; + $retval{$parts[0]} = \%library; + } + } + close(MAP_FILE); + return %retval; +} + +# string (filename.[ao]), hash(string:number) +# return hash(number:string) +sub read_library { + open(OBJECT_FILE,"objdump -t $_[0] |") || + error("Couldn't pipe objdump for $_[0]"); + my $library = $_[1]; + my %library = %$library; + my %retval; + my $object; + while () { + chomp; + my @parts = split(/[[:space:]]+/); + if ($parts[0] =~ m/:$/) { + $object = $parts[0]; + $object =~ s/:$//; + next; + } + if (@parts != 6) { + next; + } + if ($parts[0] eq "") { + next; + } + if ($parts[3] eq $parts[5]) { + next; + } + if ($parts[3] =~ m/\.(text|data|rodata|bss|icode|idata|irodata|ibss)/) { + my $region = $parts[3]; + my $symbolOffset = hex("0x" . $parts[0]); + my $sectionOffset = hex($library{$object . $region}); + my $location = $symbolOffset + $sectionOffset; + $retval{$location} = $parts[5] . "(" . $object . ")"; + } + } + close(OBJECT_FILE); + return %retval; +} + +# string (0xFFFFFFFF), hash(number:string) +# return string +sub get_name { + my $location = hex($_[0]); + my $offsets = $_[1]; + my %offsets = %$offsets; + if (exists $offsets{$location}) { + return $offsets{$location}; + } else { + my $retval = $_[0]; + $retval =~ y/[A-Z]/a-z/; + warning("No symbol found for $retval"); + return $retval; + } +} + +# string (filename), hash(number:string) +# return array(array(number,number,string)) +sub create_list { + open(PROFILE_FILE,$_[0]) || + error("Could not open profile file: $profile_file"); + my $offsets = $_[1]; + my $started = 0; + my %pfds; +# my $totalCalls = 0; +# my $totalTicks = 0; +# my $pfds = 0; + while () { + if ($started == 0) { + if (m/^0x/) { + $started = 1; + } else { + next; + } + } + my @parts = split(/[[:space:]]+/); + if ($parts[0] =~ m/^0x/) { + my $callName = get_name($parts[0],$offsets); + my $calls = $parts[1]; + my $ticks = $parts[2]; + my @pfd = ($calls,$ticks,$callName); + if (exists $pfds{$callName}) { + my $old_pfd = $pfds{$callName}; + $pfd[0]+=@$old_pfd[0]; + $pfd[1]+=@$old_pfd[1]; + } + $pfds{$callName} = \@pfd; +# $pfds++; +# $totalCalls+=$calls; +# $totalTicks+=$ticks; + } else { + last; + } + + } + close(PROFILE_FILE); +# print("FUNCTIONS\tTOTAL_CALLS\tTOTAL_TICKS\n"); +# printf(" %4d\t %8d\t %8d\n",$pfds,$totalCalls,$totalTicks); + return values(%pfds); +} + +# array(array(number,number,string)), number (sort element) +sub print_sorted { + my $pfds = $_[0]; + my @pfds = @$pfds; + my $sort_index = $_[1]; + my $percent = $_[2]; + my %elements; + my $totalCalls = 0; + my $totalTicks = 0; + $pfds = 0; + foreach $element(@pfds) { + $elements{@$element[$sort_index] . @$element[2]} = $element; + $pfds++; + $totalCalls += @$element[0]; + $totalTicks += @$element[1]; + } + my @keys = sort(keys(%elements)); + print("FUNCTIONS\tTOTAL_CALLS\tTOTAL_TICKS\n"); + printf(" %4d\t %8d\t %8d\n",$pfds,$totalCalls,$totalTicks); + foreach $key(@keys) { + my $element = $elements{$key}; + if ($percent) { + printf("Calls: %7.2f%% Ticks: %7.2f%% Symbol: %s\n", + @$element[0]/$totalCalls*100, + @$element[1]/$totalTicks*100, + @$element[2]); + } else { + printf("Calls: %08d Ticks: %08d Symbol: %s\n", + @$element); + } + } +} + +# merges two hashes +sub merge_hashes { + my $hash1 = $_[0]; + my $hash2 = $_[1]; + return (%$hash1,%$hash2); +} + +sub usage { + if (@_) { + print STDERR ("Error: @_\n"); + } + print STDERR ("USAGE:\n"); + print STDERR ("$0 profile.out map obj[...] [map obj[...]...] sort[...]\n"); + print STDERR + ("\tprofile.out output from the profiler, extension is .out\n"); + print STDERR + ("\tmap map file, extension is .map\n"); + print STDERR + ("\tobj library or object file, extension is .a or .o\n"); + print STDERR + ("\tformat 0-2[_p] 0: by calls, 1: by ticks, 2: by name\n"); + print STDERR + ("\t _p shows percents instead of counts\n"); + print STDERR ("NOTES:\n"); + print STDERR + ("\tmaps and objects come in sets, one map then many objects\n"); + exit(1); +} + + +if ($ARGV[0] =~ m/-(h|help|-help)/) { + usage(); +} +if (@ARGV < 2) { + usage("Requires at least 2 arguments"); +} +if ($ARGV[0] !~ m/\.out$/) { + usage("Profile file must end in .out"); +} +my $i = 1; +my %symbols; +{ + my %map; + for (; $i < @ARGV; $i++) { + my $file = $ARGV[$i]; + if ($file =~ m/\.map$/) { + %map = read_map($file); + } elsif ($file =~ m/\.[ao]$/) { + if (!%map) { + usage("No map file found before first object file"); + } + my @parts = split(/\//,$file); + my %new_symbols = read_library($file,$map{$parts[$#parts]}); + %symbols = merge_hashes(\%symbols,\%new_symbols); + } else { + last; + } + } +} +if (!%symbols) { + warning("No symbols found"); +} +my @pfds = create_list($ARGV[0],\%symbols); +for (; $i < @ARGV; $i++) { + print_sorted(\@pfds,split("_",$ARGV[$i])); +}