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:
parent
1060e447f8
commit
05dccc3551
33 changed files with 992 additions and 18 deletions
|
@ -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
|
||||
|
||||
};
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
};
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
||||
};
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
};
|
||||
|
||||
|
|
|
@ -12,3 +12,6 @@ xlcd.c
|
|||
#ifdef HAVE_LCD_CHARCELLS
|
||||
playergfx.c
|
||||
#endif
|
||||
#ifdef RB_PROFILE
|
||||
profile_plugin.c
|
||||
#endif
|
||||
|
|
38
apps/plugins/lib/profile_plugin.c
Normal file
38
apps/plugins/lib/profile_plugin.c
Normal 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);
|
||||
}
|
35
apps/plugins/lib/profile_plugin.h
Normal file
35
apps/plugins/lib/profile_plugin.h
Normal 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__ */
|
||||
|
|
@ -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)
|
||||
|
|
|
@ -8,6 +8,7 @@ CUSTOM_WPS_FORMAT
|
|||
FAQ
|
||||
FILES
|
||||
HISTORY
|
||||
LICENSES
|
||||
NODO
|
||||
README
|
||||
TECH
|
||||
|
|
36
docs/LICENSES
Normal file
36
docs/LICENSES
Normal 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
|
26
docs/TECH
26
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.
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
80
firmware/export/profile.h
Normal 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*/
|
|
@ -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
303
firmware/profile.c
Normal 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;
|
||||
}
|
|
@ -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)
|
||||
|
|
|
@ -23,3 +23,4 @@ ucl/*.[ch]
|
|||
ucl/src/*.[ch]
|
||||
ucl/src/Makefile
|
||||
ucl/include/ucl/*.h
|
||||
profile_reader/*.pl
|
||||
|
|
|
@ -199,6 +199,7 @@ sub buildzip {
|
|||
"CUSTOM_CFG_FORMAT",
|
||||
"CUSTOM_WPS_FORMAT",
|
||||
"FAQ",
|
||||
"LICENSES",
|
||||
"NODO",
|
||||
"TECH")) {
|
||||
`cp $ROOT/docs/$_ .rockbox/docs/$_.txt`;
|
||||
|
|
38
tools/configure
vendored
38
tools/configure
vendored
|
@ -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@
|
||||
|
|
104
tools/profile_reader/profile_comparator.pl
Executable file
104
tools/profile_reader/profile_comparator.pl
Executable 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});
|
||||
}
|
236
tools/profile_reader/profile_reader.pl
Executable file
236
tools/profile_reader/profile_reader.pl
Executable 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]));
|
||||
}
|
Loading…
Reference in a new issue