Profiling support, tools and documentation.

git-svn-id: svn://svn.rockbox.org/rockbox/trunk@8375 a1c6a512-1295-4272-9138-f99709370657
This commit is contained in:
Brandon Low 2006-01-18 20:54:13 +00:00
parent 1060e447f8
commit 05dccc3551
33 changed files with 992 additions and 18 deletions

View file

@ -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
};

View file

@ -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
};

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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
};

View file

@ -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
};

View file

@ -12,3 +12,6 @@ xlcd.c
#ifdef HAVE_LCD_CHARCELLS
playergfx.c
#endif
#ifdef RB_PROFILE
profile_plugin.c
#endif

View file

@ -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);
}

View file

@ -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__ */

View file

@ -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 @@ 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)

View file

@ -8,6 +8,7 @@ CUSTOM_WPS_FORMAT
FAQ
FILES
HISTORY
LICENSES
NODO
README
TECH

36
docs/LICENSES Normal file
View file

@ -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

View file

@ -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.

View file

@ -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

View file

@ -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 */ \

80
firmware/export/profile.h Normal file
View file

@ -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*/

View file

@ -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

303
firmware/profile.c Normal file
View file

@ -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;
}

View file

@ -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 <profile.h>
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)

View file

@ -23,3 +23,4 @@ ucl/*.[ch]
ucl/src/*.[ch]
ucl/src/Makefile
ucl/include/ucl/*.h
profile_reader/*.pl

View file

@ -199,6 +199,7 @@ sub buildzip {
"CUSTOM_CFG_FORMAT",
"CUSTOM_WPS_FORMAT",
"FAQ",
"LICENSES",
"NODO",
"TECH")) {
`cp $ROOT/docs/$_ .rockbox/docs/$_.txt`;

36
tools/configure vendored
View file

@ -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])
if [ "yes" = "$profile" ]; then
echo "Debug is incompatible with profiling"
else
echo "define DEBUG"
debug="-DDEBUG"
GCCOPTS="$GCCOPTS -g -DDEBUG"
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@

View file

@ -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});
}

View file

@ -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]));
}