Profiling support, tools and documentation.
git-svn-id: svn://svn.rockbox.org/rockbox/trunk@8375 a1c6a512-1295-4272-9138-f99709370657
diff --git a/apps/codecs.c b/apps/codecs.c
index d8ad714..88b2ea4 100644
--- a/apps/codecs.c
+++ b/apps/codecs.c
@@ -218,6 +218,12 @@
/* 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 320431f..70799f7 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 @@
/* 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 7fd5de8..cec9797 100644
--- a/apps/codecs/Tremor/Makefile
+++ b/apps/codecs/Tremor/Makefile
@@ -16,7 +16,7 @@
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 fa647f3..d2a0452 100644
--- a/apps/codecs/dumb/Makefile
+++ b/apps/codecs/dumb/Makefile
@@ -178,7 +178,7 @@
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 cad8f53..1f070e8 100644
--- a/apps/codecs/lib/codeclib.c
+++ b/apps/codecs/lib/codeclib.c
@@ -149,3 +149,14 @@
{
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 e112112..c2e7869 100644
--- a/apps/codecs/lib/codeclib.h
+++ b/apps/codecs/lib/codeclib.h
@@ -58,3 +58,9 @@
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 4008479..d1e78bd 100644
--- a/apps/codecs/libFLAC/Makefile
+++ b/apps/codecs/libFLAC/Makefile
@@ -27,7 +27,7 @@
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 8e05017..ea3c01c 100644
--- a/apps/codecs/liba52/Makefile
+++ b/apps/codecs/liba52/Makefile
@@ -16,7 +16,7 @@
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 60da5ae..7d9030e 100644
--- a/apps/codecs/libffmpegFLAC/Makefile
+++ b/apps/codecs/libffmpegFLAC/Makefile
@@ -16,7 +16,7 @@
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 7f870c9..fcbc100 100644
--- a/apps/codecs/libm4a/Makefile
+++ b/apps/codecs/libm4a/Makefile
@@ -16,7 +16,7 @@
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 e2f2643..5eaf9f1 100644
--- a/apps/codecs/libmad/Makefile
+++ b/apps/codecs/libmad/Makefile
@@ -17,7 +17,7 @@
# 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 8e65915..6bfa2f9 100644
--- a/apps/codecs/libmusepack/Makefile
+++ b/apps/codecs/libmusepack/Makefile
@@ -16,7 +16,7 @@
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 3947879..eba67fa 100644
--- a/apps/codecs/libwavpack/Makefile
+++ b/apps/codecs/libwavpack/Makefile
@@ -16,7 +16,7 @@
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 44eb0dc..b907604 100644
--- a/apps/plugin.c
+++ b/apps/plugin.c
@@ -363,6 +363,12 @@
/* 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 69a2a79..157831b 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 @@
/* 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 4d4247e..0f6e13e 100644
--- a/apps/plugins/lib/SOURCES
+++ b/apps/plugins/lib/SOURCES
@@ -12,3 +12,6 @@
#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 0000000..3318476
--- /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 0000000..71cff37
--- /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 20a4589..812ed17 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 <codecs/libwavpack/wavpack.h>
@@ -289,6 +292,15 @@
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 f1054ea..21f2faf 100644
--- a/docs/FILES
+++ b/docs/FILES
@@ -8,6 +8,7 @@
FAQ
FILES
HISTORY
+LICENSES
NODO
README
TECH
diff --git a/docs/LICENSES b/docs/LICENSES
new file mode 100644
index 0000000..652a276
--- /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 4532af3..b22a8c4 100644
--- a/docs/TECH
+++ b/docs/TECH
@@ -172,3 +172,29 @@
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 4fe5fc9..3a5f551 100644
--- a/firmware/SOURCES
+++ b/firmware/SOURCES
@@ -123,6 +123,9 @@
#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 5e3bbeb..8df5966 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 0000000..cb75132
--- /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 <sys/types.h>
+
+/* 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 fe1612d..da61d1a 100644
--- a/firmware/export/thread.h
+++ b/firmware/export/thread.h
@@ -37,5 +37,8 @@
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 0000000..8ad4651
--- /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 <file.h>
+#include <sprintf.h>
+#include <system.h>
+#include <string.h>
+#include <timer.h>
+#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 2818014..d828211 100644
--- a/firmware/thread.c
+++ b/firmware/thread.c
@@ -78,6 +78,13 @@
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 <profile.h>
+void profile_thread(void) {
+ profstart(current_thread);
+}
+#endif
+
#if defined(CPU_ARM)
/*---------------------------------------------------------------------------
* Store non-volatile context.
@@ -245,6 +252,9 @@
*/
void switch_thread(void)
{
+#ifdef RB_PROFILE
+ profile_thread_stopped(current_thread);
+#endif
int current;
unsigned int *stackptr;
@@ -284,6 +294,9 @@
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 19d3a55..f4b1c6d 100644
--- a/tools/FILES
+++ b/tools/FILES
@@ -23,3 +23,4 @@
ucl/src/*.[ch]
ucl/src/Makefile
ucl/include/ucl/*.h
+profile_reader/*.pl
diff --git a/tools/buildzip.pl b/tools/buildzip.pl
index eabf739..934b003 100755
--- a/tools/buildzip.pl
+++ b/tools/buildzip.pl
@@ -199,6 +199,7 @@
"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 2cd855e..2bea201 100755
--- a/tools/configure
+++ b/tools/configure
@@ -240,7 +240,7 @@
#
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 @@
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 @@
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 @@
-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 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 0000000..da5e300
--- /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 = <FILE1>;
+close(FILE1);
+open(FILE2,shift) || error("Couldn't open file2");
+my @file2 = <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 0000000..088ba71
--- /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 (<MAP_FILE>) {
+ 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 (<OBJECT_FILE>) {
+ 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 (<PROFILE_FILE>) {
+ 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]));
+}