mirror of
https://repo.dactyloidae.xyz/Dactyloidae/UXP.git
synced 2026-10-25 16:47:29 +09:00
parent
64602172c2
commit
8d1e73fc3c
25 changed files with 0 additions and 3212 deletions
|
|
@ -1006,110 +1006,6 @@ nsJSContext::AddSupportsPrimitiveTojsvals(nsISupports *aArg, JS::Value *aArgv)
|
||||||
return NS_OK;
|
return NS_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifdef MOZ_JPROF
|
|
||||||
|
|
||||||
#include <signal.h>
|
|
||||||
|
|
||||||
inline bool
|
|
||||||
IsJProfAction(struct sigaction *action)
|
|
||||||
{
|
|
||||||
return (action->sa_sigaction &&
|
|
||||||
(action->sa_flags & (SA_RESTART | SA_SIGINFO)) == (SA_RESTART | SA_SIGINFO));
|
|
||||||
}
|
|
||||||
|
|
||||||
void NS_JProfStartProfiling();
|
|
||||||
void NS_JProfStopProfiling();
|
|
||||||
void NS_JProfClearCircular();
|
|
||||||
|
|
||||||
static bool
|
|
||||||
JProfStartProfilingJS(JSContext *cx, unsigned argc, JS::Value *vp)
|
|
||||||
{
|
|
||||||
NS_JProfStartProfiling();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void NS_JProfStartProfiling()
|
|
||||||
{
|
|
||||||
// Figure out whether we're dealing with SIGPROF, SIGALRM, or
|
|
||||||
// SIGPOLL profiling (SIGALRM for JP_REALTIME, SIGPOLL for
|
|
||||||
// JP_RTC_HZ)
|
|
||||||
struct sigaction action;
|
|
||||||
|
|
||||||
// Must check ALRM before PROF since both are enabled for real-time
|
|
||||||
sigaction(SIGALRM, nullptr, &action);
|
|
||||||
//printf("SIGALRM: %p, flags = %x\n",action.sa_sigaction,action.sa_flags);
|
|
||||||
if (IsJProfAction(&action)) {
|
|
||||||
//printf("Beginning real-time jprof profiling.\n");
|
|
||||||
raise(SIGALRM);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
sigaction(SIGPROF, nullptr, &action);
|
|
||||||
//printf("SIGPROF: %p, flags = %x\n",action.sa_sigaction,action.sa_flags);
|
|
||||||
if (IsJProfAction(&action)) {
|
|
||||||
//printf("Beginning process-time jprof profiling.\n");
|
|
||||||
raise(SIGPROF);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
sigaction(SIGPOLL, nullptr, &action);
|
|
||||||
//printf("SIGPOLL: %p, flags = %x\n",action.sa_sigaction,action.sa_flags);
|
|
||||||
if (IsJProfAction(&action)) {
|
|
||||||
//printf("Beginning rtc-based jprof profiling.\n");
|
|
||||||
raise(SIGPOLL);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
printf("Could not start jprof-profiling since JPROF_FLAGS was not set.\n");
|
|
||||||
}
|
|
||||||
|
|
||||||
static bool
|
|
||||||
JProfStopProfilingJS(JSContext *cx, unsigned argc, JS::Value *vp)
|
|
||||||
{
|
|
||||||
NS_JProfStopProfiling();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void
|
|
||||||
NS_JProfStopProfiling()
|
|
||||||
{
|
|
||||||
raise(SIGUSR1);
|
|
||||||
//printf("Stopped jprof profiling.\n");
|
|
||||||
}
|
|
||||||
|
|
||||||
static bool
|
|
||||||
JProfClearCircularJS(JSContext *cx, unsigned argc, JS::Value *vp)
|
|
||||||
{
|
|
||||||
NS_JProfClearCircular();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void
|
|
||||||
NS_JProfClearCircular()
|
|
||||||
{
|
|
||||||
raise(SIGUSR2);
|
|
||||||
//printf("cleared jprof buffer\n");
|
|
||||||
}
|
|
||||||
|
|
||||||
static bool
|
|
||||||
JProfSaveCircularJS(JSContext *cx, unsigned argc, JS::Value *vp)
|
|
||||||
{
|
|
||||||
// Not ideal...
|
|
||||||
NS_JProfStopProfiling();
|
|
||||||
NS_JProfStartProfiling();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
static const JSFunctionSpec JProfFunctions[] = {
|
|
||||||
JS_FS("JProfStartProfiling", JProfStartProfilingJS, 0, 0),
|
|
||||||
JS_FS("JProfStopProfiling", JProfStopProfilingJS, 0, 0),
|
|
||||||
JS_FS("JProfClearCircular", JProfClearCircularJS, 0, 0),
|
|
||||||
JS_FS("JProfSaveCircular", JProfSaveCircularJS, 0, 0),
|
|
||||||
JS_FS_END
|
|
||||||
};
|
|
||||||
|
|
||||||
#endif /* defined(MOZ_JPROF) */
|
|
||||||
|
|
||||||
nsresult
|
nsresult
|
||||||
nsJSContext::InitClasses(JS::Handle<JSObject*> aGlobalObj)
|
nsJSContext::InitClasses(JS::Handle<JSObject*> aGlobalObj)
|
||||||
{
|
{
|
||||||
|
|
@ -1121,11 +1017,6 @@ nsJSContext::InitClasses(JS::Handle<JSObject*> aGlobalObj)
|
||||||
// Attempt to initialize profiling functions
|
// Attempt to initialize profiling functions
|
||||||
::JS_DefineProfilingFunctions(cx, aGlobalObj);
|
::JS_DefineProfilingFunctions(cx, aGlobalObj);
|
||||||
|
|
||||||
#ifdef MOZ_JPROF
|
|
||||||
// Attempt to initialize JProf functions
|
|
||||||
::JS_DefineFunctions(cx, aGlobalObj, JProfFunctions);
|
|
||||||
#endif
|
|
||||||
|
|
||||||
return NS_OK;
|
return NS_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -135,11 +135,6 @@ if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'gtk3':
|
||||||
'mozgtk_stub',
|
'mozgtk_stub',
|
||||||
]
|
]
|
||||||
|
|
||||||
if CONFIG['MOZ_JPROF']:
|
|
||||||
USE_LIBS += [
|
|
||||||
'jprof',
|
|
||||||
]
|
|
||||||
|
|
||||||
if 'gtk' in CONFIG['MOZ_WIDGET_TOOLKIT'] or \
|
if 'gtk' in CONFIG['MOZ_WIDGET_TOOLKIT'] or \
|
||||||
CONFIG['MOZ_TREE_FREETYPE']:
|
CONFIG['MOZ_TREE_FREETYPE']:
|
||||||
USE_LIBS += [
|
USE_LIBS += [
|
||||||
|
|
|
||||||
|
|
@ -22,19 +22,6 @@ def systrace(value, target):
|
||||||
|
|
||||||
set_define('MOZ_USE_SYSTRACE', systrace)
|
set_define('MOZ_USE_SYSTRACE', systrace)
|
||||||
|
|
||||||
|
|
||||||
option('--enable-jprof', env='MOZ_JPROF',
|
|
||||||
help='Enable jprof profiling tool (needs mozilla/tools/jprof)')
|
|
||||||
|
|
||||||
@depends('--enable-jprof')
|
|
||||||
def jprof(value):
|
|
||||||
if value:
|
|
||||||
return True
|
|
||||||
|
|
||||||
set_config('MOZ_JPROF', jprof)
|
|
||||||
set_define('MOZ_JPROF', jprof)
|
|
||||||
imply_option('--enable-profiling', jprof)
|
|
||||||
|
|
||||||
@depends(target)
|
@depends(target)
|
||||||
def sps_profiler(target):
|
def sps_profiler(target):
|
||||||
if target.os == 'Android':
|
if target.os == 'Android':
|
||||||
|
|
|
||||||
|
|
@ -103,10 +103,6 @@ else:
|
||||||
|
|
||||||
# toolkit
|
# toolkit
|
||||||
|
|
||||||
# This must precede xpfe.
|
|
||||||
if CONFIG['MOZ_JPROF']:
|
|
||||||
DIRS += ['/tools/jprof']
|
|
||||||
|
|
||||||
DIRS += [
|
DIRS += [
|
||||||
'/tools/power',
|
'/tools/power',
|
||||||
'/tools/profiler',
|
'/tools/profiler',
|
||||||
|
|
|
||||||
|
|
@ -183,10 +183,6 @@
|
||||||
#include "mozilla/Logging.h"
|
#include "mozilla/Logging.h"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifdef MOZ_JPROF
|
|
||||||
#include "jprof.h"
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#include "base/command_line.h"
|
#include "base/command_line.h"
|
||||||
#include "GTestRunner.h"
|
#include "GTestRunner.h"
|
||||||
|
|
||||||
|
|
@ -1514,11 +1510,6 @@ static nsresult LaunchChild(nsINativeAppSupport* aNative,
|
||||||
// Restart this process by exec'ing it into the current process
|
// Restart this process by exec'ing it into the current process
|
||||||
// if supported by the platform. Otherwise, use NSPR.
|
// if supported by the platform. Otherwise, use NSPR.
|
||||||
|
|
||||||
#ifdef MOZ_JPROF
|
|
||||||
// make sure JPROF doesn't think we're E10s
|
|
||||||
unsetenv("JPROF_SLAVE");
|
|
||||||
#endif
|
|
||||||
|
|
||||||
if (aBlankCommandLine) {
|
if (aBlankCommandLine) {
|
||||||
gRestartArgc = 1;
|
gRestartArgc = 1;
|
||||||
gRestartArgv[gRestartArgc] = nullptr;
|
gRestartArgv[gRestartArgc] = nullptr;
|
||||||
|
|
@ -3397,11 +3388,6 @@ XREMain::XRE_mainStartup(bool* aExitFlag)
|
||||||
XRE_InstallX11ErrorHandler();
|
XRE_InstallX11ErrorHandler();
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// Call the code to install our handler
|
|
||||||
#ifdef MOZ_JPROF
|
|
||||||
setupProfilingStuff();
|
|
||||||
#endif
|
|
||||||
|
|
||||||
rv = NS_CreateNativeAppSupport(getter_AddRefs(mNativeApp));
|
rv = NS_CreateNativeAppSupport(getter_AddRefs(mNativeApp));
|
||||||
if (NS_FAILED(rv))
|
if (NS_FAILED(rv))
|
||||||
return 1;
|
return 1;
|
||||||
|
|
|
||||||
|
|
@ -81,10 +81,6 @@
|
||||||
using mozilla::_ipdltest::IPDLUnitTestProcessChild;
|
using mozilla::_ipdltest::IPDLUnitTestProcessChild;
|
||||||
#endif // ifdef MOZ_IPDL_TESTS
|
#endif // ifdef MOZ_IPDL_TESTS
|
||||||
|
|
||||||
#ifdef MOZ_JPROF
|
|
||||||
#include "jprof.h"
|
|
||||||
#endif
|
|
||||||
|
|
||||||
using namespace mozilla;
|
using namespace mozilla;
|
||||||
|
|
||||||
using mozilla::ipc::BrowserProcessSubThread;
|
using mozilla::ipc::BrowserProcessSubThread;
|
||||||
|
|
@ -270,11 +266,6 @@ XRE_InitChildProcess(int aArgc,
|
||||||
NS_ENSURE_ARG_POINTER(aArgv[0]);
|
NS_ENSURE_ARG_POINTER(aArgv[0]);
|
||||||
MOZ_ASSERT(aChildData);
|
MOZ_ASSERT(aChildData);
|
||||||
|
|
||||||
#ifdef MOZ_JPROF
|
|
||||||
// Call the code to install our handler
|
|
||||||
setupProfilingStuff();
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#if !defined(MOZ_WIDGET_ANDROID)
|
#if !defined(MOZ_WIDGET_ANDROID)
|
||||||
// On non-Fennec Gecko, the GMPLoader code resides in plugin-container,
|
// On non-Fennec Gecko, the GMPLoader code resides in plugin-container,
|
||||||
// and we must forward it through to the GMP code here.
|
// and we must forward it through to the GMP code here.
|
||||||
|
|
|
||||||
|
|
@ -1,330 +0,0 @@
|
||||||
<!-- This Source Code Form is subject to the terms of the Mozilla Public
|
|
||||||
- License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
||||||
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
|
|
||||||
|
|
||||||
<html>
|
|
||||||
<head><title>The Jprof Profiler</title></head>
|
|
||||||
|
|
||||||
<body bgcolor="#FFFFFF" text="#000000"
|
|
||||||
link="#0000EE" vlink="#551A8B" alink="#FF0000">
|
|
||||||
<center>
|
|
||||||
<h1>The Jprof Profiler</h1>
|
|
||||||
<font size="-1">
|
|
||||||
<a href="mailto:jim_nance%yahoo.com">jim_nance@yahoo.com</a><p>
|
|
||||||
Recent (4/2011) updates Randell Jesup (see bugzilla for contact info)
|
|
||||||
</font>
|
|
||||||
<hr>
|
|
||||||
|
|
||||||
<a href="#introduction">Introduction</a> | <a href="#operation">Operation</a> |
|
|
||||||
<a href="#setup">Setup</a> | <a href="#usage">Usage</a> |
|
|
||||||
<a href="#interpretation">Interpretation</a>
|
|
||||||
|
|
||||||
</center>
|
|
||||||
<hr>
|
|
||||||
|
|
||||||
<h3><a name="introduction">Introduction</a></h3>
|
|
||||||
|
|
||||||
Jprof is a profiling tool. I am writing it because I need to find out
|
|
||||||
where mozilla is spending its time, and there do not seem to be any
|
|
||||||
profilers for Linux that can handle threads and/or shared libraries.
|
|
||||||
This code is based heavily on Kipp Hickman's leaky.
|
|
||||||
|
|
||||||
<h3><a name="operation">Operation</a></h3>
|
|
||||||
|
|
||||||
Jprof operates by installing a timer which periodically interrupts mozilla.
|
|
||||||
When this timer goes off, the jprof code inside mozilla walks the function call
|
|
||||||
stack to determine which code was executing and saves the results into the
|
|
||||||
<code>jprof-log</code> and <code>jprof-map</code> files. By collecting a large
|
|
||||||
number of these call stacks, it is possible to deduce where mozilla is spending
|
|
||||||
its time.
|
|
||||||
|
|
||||||
<h3><a name="setup">Setup</a></h3>
|
|
||||||
|
|
||||||
<p>Configure your mozilla with jprof support by adding
|
|
||||||
<code>--enable-jprof</code> to your configure options (eg adding
|
|
||||||
<code>ac_add_options --enable-jprof</code> to your <code>.mozconfig</code>) and
|
|
||||||
making sure that you do <strong>not</strong> have the
|
|
||||||
<code>--enable-strip</code> configure option set -- jprof needs symbols to
|
|
||||||
operate. On many architectures with GCC, you'll need to add
|
|
||||||
<code>--enable-optimize="-O3 -fno-omit-frame-pointer"</code> or the
|
|
||||||
equivalent to ensure frame pointer generation in the compiler you're using.</p>
|
|
||||||
|
|
||||||
<p>Finally, build mozilla with your new configuration. Now you can run jprof.</p>
|
|
||||||
|
|
||||||
<h3><a name="usage">Usage</a></h3>
|
|
||||||
<pre> jprof [-v] [-t] [-e exclude] [-i include] [-s stackdepth] [--last] [--all] [--start n [--end m]] [--output-dir dir] prog log [log2 ...]</pre>
|
|
||||||
Options:
|
|
||||||
<ul>
|
|
||||||
<li><b>-s depth</b> : Limit depth looked at from captured stack
|
|
||||||
frames</li>
|
|
||||||
<li><b>-v</b> : Output some information about the symbols, memory map, etc.</li>
|
|
||||||
<li><b>-t or --threads</b> : Group output according to thread. May require external
|
|
||||||
LD_PRELOAD library to help force sampling of spawned threads; jprof
|
|
||||||
may capture the main thread only. See <a
|
|
||||||
href="http://sam.zoy.org/writings/programming/gprof.html">gprof-helper</a>;
|
|
||||||
it may need adaption for jprof.</li>
|
|
||||||
<li><b>--only-thread id</b> : Only output data for thread 'id'</li>
|
|
||||||
<li><b>-e exclusion</b> : Allows excluding specific stack frames</li>
|
|
||||||
<li><b>-i inclusion</b> : Allows including specific stack frames</li>
|
|
||||||
<li><b>--last</b> : Only process data from the last 'section' of sampling
|
|
||||||
(starting at the last PROF)</li>
|
|
||||||
<li><b>--start N</b> : Start processing data at 'section' N </li>
|
|
||||||
<li><b>--end N</b> : Stop processing data at 'section' N </li>
|
|
||||||
<li><b>--output-dir dir</b> : Store generated .html files in the given directory </li>
|
|
||||||
</ul>
|
|
||||||
The behavior of jprof is determined by the value of the JPROF_FLAGS environment
|
|
||||||
variable. This environment variable can be composed of several substrings
|
|
||||||
which have the following meanings:
|
|
||||||
<ul>
|
|
||||||
<li> <b>JP_START</b> : Install the signal handler, and start sending the
|
|
||||||
timer signals.
|
|
||||||
|
|
||||||
<li> <b>JP_DEFER</b> : Install the signal handler, but don't start sending
|
|
||||||
the timer signals. The user must start the signals by sending the first
|
|
||||||
one (with <code>kill -PROF</code>, or with <code>kill -ALRM</code> if
|
|
||||||
JP_REALTIME is used, or with <code>kill -POLL</code> (also known as <code>kill -IO</code>) if JP_RTC_HZ is used).
|
|
||||||
|
|
||||||
<li> <b>JP_FIRST=x</b> : Wait x seconds before starting the timer
|
|
||||||
|
|
||||||
<li> <b>JP_PERIOD=y</b> : Set timer to interrupt every y seconds. Only
|
|
||||||
values of y greater than or equal to 0.001 are supported. Default is
|
|
||||||
0.050 (50ms).
|
|
||||||
|
|
||||||
<li> <b>JP_REALTIME</b> : Do the profiling in intervals of real time rather
|
|
||||||
than intervals of time used by the mozilla process (and the kernel
|
|
||||||
when doing work for mozilla). This could probably lead to weird
|
|
||||||
results (you'll see whatever runs when mozilla is waiting for events),
|
|
||||||
but is needed to see time spent in the X server.
|
|
||||||
|
|
||||||
<li> <b>JP_RTC_HZ=freq</b> : This option, only available on Linux if the
|
|
||||||
kernel is built with RTC support, makes jprof use the RTC timer instead of
|
|
||||||
using its own timer. This option, like JP_REALTIME, uses intervals of real
|
|
||||||
time. This option overrides JP_PERIOD. <code>freq</code> is the frequency
|
|
||||||
at which the timer should fire, measured in Hz. It must be a power of 2.
|
|
||||||
The maximal frequency allowed by the kernel can be changed by writing to
|
|
||||||
<code>/proc/sys/dev/rtc/max-user-freq</code>; the maximum value it can be
|
|
||||||
set to is 8192. Note that <code>/dev/rtc</code> will need to be readable
|
|
||||||
by the Firefox process; making that file world-readable is a simple way to
|
|
||||||
accomplish that.
|
|
||||||
|
|
||||||
<li> <b>JP_CIRCULAR=size</b> : This tells jprof to store samples in a
|
|
||||||
circular buffer of the given size, which then will be saved (appended)
|
|
||||||
to disk when SIGUSR1 is received or JProfStopProfiling is done. If the
|
|
||||||
buffer overflows, the oldest entries will be evicted until there's
|
|
||||||
space for the new entry.<p>
|
|
||||||
|
|
||||||
SIGUSR2 will cause the circular buffer to be cleared.
|
|
||||||
|
|
||||||
<li> <b>JP_FILENAME=basefilename</b> : This is the filename used for
|
|
||||||
saving the log files to; the default is "jprof-log". If Electrolysis
|
|
||||||
is used, each process after the first will have the process ID
|
|
||||||
added ("jprof-log-3212");
|
|
||||||
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
<h4>Starting and stopping jprof from JavaScript</h4>
|
|
||||||
<p>
|
|
||||||
A build with jprof enabled adds four functions to the Window object:<p>
|
|
||||||
<code>JProfStartProfiling()</code> and <code>JProfStopProfiling()</code>: When used with JP_DEFER, these
|
|
||||||
allow one to start and stop the timer just around whatever critical section is
|
|
||||||
being profiled.</p><p>
|
|
||||||
<code>JProfClearCircular()</code> and <code>JProfSaveCircular()</code>:
|
|
||||||
These clear the circular buffer and save the buffer (without stopping), respectively.</p>
|
|
||||||
|
|
||||||
<h4>Examples of JPROF_FLAGS usage</h4>
|
|
||||||
<ul>
|
|
||||||
|
|
||||||
<li>To make the timer start firing 3 seconds after the program is started and
|
|
||||||
fire every 25 milliseconds of program time use:
|
|
||||||
<pre>
|
|
||||||
setenv JPROF_FLAGS "JP_START JP_FIRST=3 JP_PERIOD=0.025" </pre>
|
|
||||||
|
|
||||||
<li>To make the timer start on your signal and fire every 1 millisecond of
|
|
||||||
program time use:
|
|
||||||
<pre>
|
|
||||||
setenv JPROF_FLAGS "JP_DEFER JP_PERIOD=0.001" </pre>
|
|
||||||
|
|
||||||
<li>To make the timer start on your signal and fire every 10 milliseconds of
|
|
||||||
wall-clock time use:
|
|
||||||
<pre>
|
|
||||||
setenv JPROF_FLAGS "JP_DEFER JP_PERIOD=0.010 JP_REALTIME" </pre>
|
|
||||||
|
|
||||||
<li>To make the timer start on your signal and fire at 8192 Hz in wall-clock
|
|
||||||
time use:
|
|
||||||
<pre>
|
|
||||||
setenv JPROF_FLAGS "JP_DEFER JP_RTC_HZ=8192" </pre>
|
|
||||||
|
|
||||||
<li>To make the timer start on JProfStartProfiling() and run continously
|
|
||||||
with a 1ms sample rate until told to stop, then save the last 1MB of
|
|
||||||
data:
|
|
||||||
<pre>
|
|
||||||
setenv JPROF_FLAGS "JP_DEFER JP_CIRCULAR=1048576 JP_PERIOD=0.001" </pre>
|
|
||||||
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
<h4>Pausing profiles</h4>
|
|
||||||
|
|
||||||
<P>jprof can be paused at any time by sending a SIGUSR1 to mozilla (<code>kill
|
|
||||||
-USR1</code>). This will cause the timer signals to stop and jprof-map to be
|
|
||||||
written, but it will not close jprof-log. Combining SIGUSR1 with the JP_DEFER
|
|
||||||
option allows profiling of one sequence of actions by starting the timer right
|
|
||||||
before starting the actions and stopping the timer right afterward.
|
|
||||||
|
|
||||||
<P>After a SIGUSR1, sending another timer signal (SIGPROF, SIGALRM, or SIGPOLL (aka SIGIO),
|
|
||||||
depending on the mode) can be used to continue writing data to the same
|
|
||||||
output.
|
|
||||||
|
|
||||||
<P>SIGUSR2 will cause the circular buffer to be cleared, if it's in use.
|
|
||||||
This is useful right before running a test when you're using a large,
|
|
||||||
continuous circular buffer, or programmatically at the start of an action
|
|
||||||
which might take too long (JProfClearCircular()).
|
|
||||||
|
|
||||||
<h4>Looking at the results</h4>
|
|
||||||
|
|
||||||
Now that we have <code>jprof-log</code> and <code>jprof-map</code> files, we
|
|
||||||
can use the jprof executable is used to turn them into readable output. To do
|
|
||||||
this jprof needs the name of the mozilla binary and the log file. It deduces
|
|
||||||
the name of the map file:
|
|
||||||
|
|
||||||
<pre>
|
|
||||||
./jprof /home/user/mozilla/objdir/dist/bin/firefox ./jprof-log > tmp.html
|
|
||||||
</pre>
|
|
||||||
|
|
||||||
This will generate the file <code>tmp.html</code> which you should view in a
|
|
||||||
web browser.
|
|
||||||
|
|
||||||
<pre>
|
|
||||||
./jprof --output-dir=/tmp /home/user/mozilla/objdir/dist/bin/firefox ./jprof-log*
|
|
||||||
</pre>
|
|
||||||
|
|
||||||
This will generate a set of files in /tmp for each process.
|
|
||||||
|
|
||||||
|
|
||||||
<h3><a name="interpretation">Interpretation</a></h3>
|
|
||||||
|
|
||||||
|
|
||||||
The Jprof output is split into a flat portion and a hierarchical portion.
|
|
||||||
There are links to each section at the top of the page. It is typically
|
|
||||||
easier to analyze the profile by starting with the flat output and following
|
|
||||||
the links contained in the flat output up to the hierarchical output.
|
|
||||||
|
|
||||||
<h4><a name="flat">Flat output</a></h3>
|
|
||||||
|
|
||||||
The flat portion of the profile indicates which functions were executing
|
|
||||||
when the timer was going off. It is displayed as a list of functions names
|
|
||||||
on the right and the number of times that function was interrupted on the
|
|
||||||
left. The list is sorted by decreasing interrupt count. For example:
|
|
||||||
|
|
||||||
<blockquote> <pre>
|
|
||||||
Total hit count: 151603
|
|
||||||
Count %Total Function Name
|
|
||||||
|
|
||||||
<a href="#23081">8806 5.8 __libc_poll</a>
|
|
||||||
<a href="#40008">2254 1.5 __i686.get_pc_thunk.bx</a>
|
|
||||||
<a href="#21390">2053 1.4 _int_malloc</a>
|
|
||||||
<a href="#49013">1777 1.2 nsStyleContext::GetStyleData(nsStyleStructID)</a>
|
|
||||||
<a href="#21380">1600 1.1 __libc_malloc</a>
|
|
||||||
<a href="#603">1552 1.0 nsCOMPtr_base::~nsCOMPtr_base()</a>
|
|
||||||
</pre> </blockquote>
|
|
||||||
|
|
||||||
This shows that of the 151603 times the timer fired, 1777 (1.2% of the total) were inside nsStyleContext::GetStyleData() and 1552 (1.0% of the total) were in the nsCOMPtr_base destructor.
|
|
||||||
|
|
||||||
<p>
|
|
||||||
In general, the functions with the highest count are the functions which
|
|
||||||
are taking the most time.
|
|
||||||
|
|
||||||
<P>
|
|
||||||
The function names are linked to the entry for that function in the
|
|
||||||
hierarchical profile, which is described in the next section.
|
|
||||||
|
|
||||||
<h4><a name="hier">Hierarchical output</a></h4>
|
|
||||||
|
|
||||||
The hierarchical output is divided up into sections, with each section
|
|
||||||
corresponding to one function. A typical section looks something like
|
|
||||||
this:
|
|
||||||
|
|
||||||
<blockquote><pre>
|
|
||||||
index Count Hits Function Name
|
|
||||||
<A href="#72871"> 545 (46.4%) nsBlockFrame::ReflowInlineFrames(nsBlockReflowState&, nsLineList_iterator, int*)</A>
|
|
||||||
<A href="#72873"> 100 (8.5%) nsBlockFrame::ReflowDirtyLines(nsBlockReflowState&)</A>
|
|
||||||
72870 4 (0.3%) <a name=72870> 645 (54.9%)</a> <b>nsBlockFrame::DoReflowInlineFrames(nsBlockReflowState&, nsLineLayout&, nsLineList_iterator, nsFlowAreaRect&, int&, nsFloatManager::SavedState*, int*, LineReflowStatus*, int)</b>
|
|
||||||
<A href="#72821"> 545 (46.4%) nsBlockFrame::ReflowInlineFrame(nsBlockReflowState&, nsLineLayout&, nsLineList_iterator, nsIFrame*, LineReflowStatus*)</A>
|
|
||||||
<A href="#72853"> 83 (7.1%) nsBlockFrame::PlaceLine(nsBlockReflowState&, nsLineLayout&, nsLineList_iterator, nsFloatManager::SavedState*, nsRect&, int&, int*)</A>
|
|
||||||
<A href="#74150"> 9 (0.8%) nsLineLayout::BeginLineReflow(int, int, int, int, int, int)</A>
|
|
||||||
<A href="#74897"> 1 (0.1%) nsTextFrame::GetType() const</A>
|
|
||||||
<A href="#74131"> 1 (0.1%) nsLineLayout::RelativePositionFrames(nsOverflowAreas&)</A>
|
|
||||||
<A href="#58320"> 1 (0.1%) __i686.get_pc_thunk.bx</A>
|
|
||||||
<A href="#53077"> 1 (0.1%) PL_ArenaAllocate</A>
|
|
||||||
</pre></blockquote>
|
|
||||||
|
|
||||||
The information this block tells us is:
|
|
||||||
|
|
||||||
<ul>
|
|
||||||
<li>There were 4 profiler hits <em>in</em> <code>nsBlockFrame::DoReflowInlineFrames</code>
|
|
||||||
<li>There were 645 profiler hits <em>in or under</em> <code>nsBlockFrame::DoReflowInlineFrames</code>. Of these:
|
|
||||||
<ul>
|
|
||||||
<li>545 were in or under <code>nsBlockFrame::ReflowInlineFrame</code>
|
|
||||||
<li>83 were in or under <code>nsBlockFrame::PlaceLine</code>
|
|
||||||
<li>9 were in or under <code>nsLineLayout::BeginLineReflow</code>
|
|
||||||
<li>1 was in or under <code>nsTextFrame::GetType</code>
|
|
||||||
<li>1 was in or under <code>nsLineLayout::RelativePositionFrames</code>
|
|
||||||
<li>1 was in or under <code>__i686.get_pc_thunk.bx</code>
|
|
||||||
<li>1 was in or under <code>PL_ArenaAllocate</code>
|
|
||||||
</ul>
|
|
||||||
<li>Of these 645 calls into <code>nsBlockFrame::DoReflowInlineFrames</code>:
|
|
||||||
<ul>
|
|
||||||
<li>545 came from <code>nsBlockFrame::ReflowInlineFrames</code>
|
|
||||||
<li>100 came from <code>nsBlockFrame::ReflowDirtyLines</code>
|
|
||||||
</ul>
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
|
|
||||||
The rest of this section explains how to read this information off from the jprof output.
|
|
||||||
|
|
||||||
<p>This block corresponds to the function <code>nsBlockFrame::DoReflowInlineFrames</code>, which is
|
|
||||||
therefore bolded and not a link. The name of this function is preceded by
|
|
||||||
five numbers which have the following meaning. The number on the left (72870)
|
|
||||||
is the index number, and is not important. The next number (4) and the
|
|
||||||
percentage following (0.3%) are the number
|
|
||||||
of times this function was interrupted by the timer and the percentage of
|
|
||||||
the total hits that is. The last number pair ("645 (54.9%)")
|
|
||||||
are the number of times this function was in the call stack when the timer went
|
|
||||||
off. That is, the timer went off while we were in code that was ultimately
|
|
||||||
called from <code>nsBlockFrame::DoReflowInlineFrames</code>.
|
|
||||||
<p>For our example we can see that our function was in the call stack for
|
|
||||||
645 interrupt ticks, but we were only the function that was running when
|
|
||||||
the interrupt arrived 4 times.
|
|
||||||
<P>
|
|
||||||
The functions listed above the line for <code>nsBlockFrame::DoReflowInlineFrames</code> are its
|
|
||||||
callers. The numbers to the left of these function names are the numbers of
|
|
||||||
times these functions were in the call stack as callers of
|
|
||||||
<code>nsBlockFrame::DoReflowInlineFrames</code>. In our example, we were called 545 times by
|
|
||||||
<code>nsBlockFrame::ReflowInlineFrames</code> and 100 times by
|
|
||||||
<code>nsBlockFrame::ReflowDirtyLines</code>.
|
|
||||||
<P>
|
|
||||||
The functions listed below the line for <code>nsBlockFrame::DoReflowInlineFrames</code> are its
|
|
||||||
callees. The numbers to the left of the function names are the numbers of
|
|
||||||
times these functions were in the callstack as callees of
|
|
||||||
<code>nsBlockFrame::DoReflowInlineFrames</code> and the corresponding percentages. In our example, of the 645 profiler hits under <code>nsBlockFrame::DoReflowInlineFrames</code> 545 were under <code>nsBlockFrame::ReflowInlineFrame</code>, 83 were under <code>nsBlockFrame::PlaceLine</code>, and so forth.<p>
|
|
||||||
|
|
||||||
<b>NOTE:</b> If there are loops of execution or recursion, the numbers will
|
|
||||||
not add up and percentages can exceed 100%. If a function directly calls
|
|
||||||
itself "(self)" will be appended to the line, but indirect recursion will
|
|
||||||
not be marked.
|
|
||||||
|
|
||||||
<h3>Bugs</h3>
|
|
||||||
The current build of Jprof has only been tested under Ubuntu 8.04 LTS, but
|
|
||||||
should work under any fairly modern linux distribution using GCC/GLIBC.
|
|
||||||
Please update this document with any known compatibilities/incompatibilities.
|
|
||||||
<p>
|
|
||||||
If you get an error:<p><code>Inconsistency detected by ld.so: dl-open.c: 260: dl_open_worker: Assertion `_dl_debug_initialize (0, args->nsid)->r_state == RT_CONSISTENT' failed!
|
|
||||||
</code><p>that means you've hit a timing hole in the version of glibc you're
|
|
||||||
running. See <a
|
|
||||||
href="http://sources.redhat.com/bugzilla/show_bug.cgi?id=4578">Redhat bug 4578</a>.
|
|
||||||
<!-- <h3>Update</h3>
|
|
||||||
<ul>
|
|
||||||
</ul>
|
|
||||||
-->
|
|
||||||
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
|
|
@ -1,231 +0,0 @@
|
||||||
// vim:ts=8:sw=2:et:
|
|
||||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
|
||||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
||||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
|
||||||
|
|
||||||
#include "leaky.h"
|
|
||||||
|
|
||||||
#ifdef USE_BFD
|
|
||||||
#include <stdio.h>
|
|
||||||
#include <string.h>
|
|
||||||
#include <fcntl.h>
|
|
||||||
#include <unistd.h>
|
|
||||||
#include <libgen.h>
|
|
||||||
#include <bfd.h>
|
|
||||||
#include <cxxabi.h>
|
|
||||||
|
|
||||||
static bfd *try_debug_file(const char *filename, unsigned long crc32)
|
|
||||||
{
|
|
||||||
int fd = open(filename, O_RDONLY);
|
|
||||||
if (fd < 0)
|
|
||||||
return nullptr;
|
|
||||||
|
|
||||||
unsigned char buf[4*1024];
|
|
||||||
unsigned long crc = 0;
|
|
||||||
|
|
||||||
while (1) {
|
|
||||||
ssize_t count = read(fd, buf, sizeof(buf));
|
|
||||||
if (count <= 0)
|
|
||||||
break;
|
|
||||||
|
|
||||||
crc = bfd_calc_gnu_debuglink_crc32(crc, buf, count);
|
|
||||||
}
|
|
||||||
|
|
||||||
close(fd);
|
|
||||||
|
|
||||||
if (crc != crc32)
|
|
||||||
return nullptr;
|
|
||||||
|
|
||||||
bfd *object = bfd_openr(filename, nullptr);
|
|
||||||
if (!bfd_check_format(object, bfd_object)) {
|
|
||||||
bfd_close(object);
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
return object;
|
|
||||||
}
|
|
||||||
|
|
||||||
static bfd *find_debug_file(bfd *lib, const char *aFileName)
|
|
||||||
{
|
|
||||||
// check for a separate debug file with symbols
|
|
||||||
asection *sect = bfd_get_section_by_name(lib, ".gnu_debuglink");
|
|
||||||
|
|
||||||
if (!sect)
|
|
||||||
return nullptr;
|
|
||||||
|
|
||||||
bfd_size_type debuglinkSize = bfd_section_size (objfile->obfd, sect);
|
|
||||||
|
|
||||||
char *debuglink = new char[debuglinkSize];
|
|
||||||
bfd_get_section_contents(lib, sect, debuglink, 0, debuglinkSize);
|
|
||||||
|
|
||||||
// crc checksum is aligned to 4 bytes, and after the NUL.
|
|
||||||
int crc_offset = (int(strlen(debuglink)) & ~3) + 4;
|
|
||||||
unsigned long crc32 = bfd_get_32(lib, debuglink + crc_offset);
|
|
||||||
|
|
||||||
// directory component
|
|
||||||
char *dirbuf = strdup(aFileName);
|
|
||||||
const char *dir = dirname(dirbuf);
|
|
||||||
|
|
||||||
static const char debug_subdir[] = ".debug";
|
|
||||||
// This is gdb's default global debugging info directory, but gdb can
|
|
||||||
// be instructed to use a different directory.
|
|
||||||
static const char global_debug_dir[] = "/usr/lib/debug";
|
|
||||||
|
|
||||||
char *filename =
|
|
||||||
new char[strlen(global_debug_dir) + strlen(dir) + crc_offset + 3];
|
|
||||||
|
|
||||||
// /path/debuglink
|
|
||||||
sprintf(filename, "%s/%s", dir, debuglink);
|
|
||||||
bfd *debugFile = try_debug_file(filename, crc32);
|
|
||||||
if (!debugFile) {
|
|
||||||
|
|
||||||
// /path/.debug/debuglink
|
|
||||||
sprintf(filename, "%s/%s/%s", dir, debug_subdir, debuglink);
|
|
||||||
debugFile = try_debug_file(filename, crc32);
|
|
||||||
if (!debugFile) {
|
|
||||||
|
|
||||||
// /usr/lib/debug/path/debuglink
|
|
||||||
sprintf(filename, "%s/%s/%s", global_debug_dir, dir, debuglink);
|
|
||||||
debugFile = try_debug_file(filename, crc32);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
delete[] filename;
|
|
||||||
free(dirbuf);
|
|
||||||
delete[] debuglink;
|
|
||||||
|
|
||||||
return debugFile;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// Use an indirect array to avoid copying tons of objects
|
|
||||||
Symbol ** leaky::ExtendSymbols(int num)
|
|
||||||
{
|
|
||||||
long n = numExternalSymbols + num;
|
|
||||||
|
|
||||||
externalSymbols = (Symbol**)
|
|
||||||
realloc(externalSymbols,
|
|
||||||
(size_t) (sizeof(externalSymbols[0]) * n));
|
|
||||||
Symbol *new_array = new Symbol[n];
|
|
||||||
for (int i = 0; i < num; i++) {
|
|
||||||
externalSymbols[i + numExternalSymbols] = &new_array[i];
|
|
||||||
}
|
|
||||||
lastSymbol = externalSymbols + n;
|
|
||||||
Symbol **sp = externalSymbols + numExternalSymbols;
|
|
||||||
numExternalSymbols = n;
|
|
||||||
return sp;
|
|
||||||
}
|
|
||||||
|
|
||||||
#define NEXT_SYMBOL do { sp++; \
|
|
||||||
if (sp >= lastSymbol) { \
|
|
||||||
sp = ExtendSymbols(16384); \
|
|
||||||
} \
|
|
||||||
} while (0)
|
|
||||||
|
|
||||||
void leaky::ReadSymbols(const char *aFileName, u_long aBaseAddress)
|
|
||||||
{
|
|
||||||
int initialSymbols = usefulSymbols;
|
|
||||||
if (nullptr == externalSymbols) {
|
|
||||||
externalSymbols = (Symbol**) calloc(sizeof(Symbol*),10000);
|
|
||||||
Symbol *new_array = new Symbol[10000];
|
|
||||||
for (int i = 0; i < 10000; i++) {
|
|
||||||
externalSymbols[i] = &new_array[i];
|
|
||||||
}
|
|
||||||
numExternalSymbols = 10000;
|
|
||||||
}
|
|
||||||
Symbol** sp = externalSymbols + usefulSymbols;
|
|
||||||
lastSymbol = externalSymbols + numExternalSymbols;
|
|
||||||
|
|
||||||
// Create a dummy symbol for the library so, if it doesn't have any
|
|
||||||
// symbols, we show it by library.
|
|
||||||
(*sp)->Init(aFileName, aBaseAddress);
|
|
||||||
NEXT_SYMBOL;
|
|
||||||
|
|
||||||
bfd_boolean kDynamic = (bfd_boolean) false;
|
|
||||||
|
|
||||||
static int firstTime = 1;
|
|
||||||
if (firstTime) {
|
|
||||||
firstTime = 0;
|
|
||||||
bfd_init ();
|
|
||||||
}
|
|
||||||
|
|
||||||
bfd* lib = bfd_openr(aFileName, nullptr);
|
|
||||||
if (nullptr == lib) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (!bfd_check_format(lib, bfd_object)) {
|
|
||||||
bfd_close(lib);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
bfd *symbolFile = find_debug_file(lib, aFileName);
|
|
||||||
|
|
||||||
// read mini symbols
|
|
||||||
PTR minisyms;
|
|
||||||
unsigned int size;
|
|
||||||
long symcount = 0;
|
|
||||||
|
|
||||||
if (symbolFile) {
|
|
||||||
symcount = bfd_read_minisymbols(symbolFile, kDynamic, &minisyms, &size);
|
|
||||||
if (symcount == 0) {
|
|
||||||
bfd_close(symbolFile);
|
|
||||||
} else {
|
|
||||||
bfd_close(lib);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (symcount == 0) {
|
|
||||||
symcount = bfd_read_minisymbols(lib, kDynamic, &minisyms, &size);
|
|
||||||
if (symcount == 0) {
|
|
||||||
// symtab is empty; try dynamic symbols
|
|
||||||
kDynamic = (bfd_boolean) true;
|
|
||||||
symcount = bfd_read_minisymbols(lib, kDynamic, &minisyms, &size);
|
|
||||||
}
|
|
||||||
symbolFile = lib;
|
|
||||||
}
|
|
||||||
|
|
||||||
asymbol* store;
|
|
||||||
store = bfd_make_empty_symbol(symbolFile);
|
|
||||||
|
|
||||||
// Scan symbols
|
|
||||||
size_t demangle_buffer_size = 128;
|
|
||||||
char *demangle_buffer = (char*) malloc(demangle_buffer_size);
|
|
||||||
bfd_byte* from = (bfd_byte *) minisyms;
|
|
||||||
bfd_byte* fromend = from + symcount * size;
|
|
||||||
for (; from < fromend; from += size) {
|
|
||||||
asymbol *sym;
|
|
||||||
sym = bfd_minisymbol_to_symbol(symbolFile, kDynamic, (const PTR) from, store);
|
|
||||||
|
|
||||||
symbol_info syminfo;
|
|
||||||
bfd_get_symbol_info (symbolFile, sym, &syminfo);
|
|
||||||
|
|
||||||
// if ((syminfo.type == 'T') || (syminfo.type == 't')) {
|
|
||||||
const char* nm = bfd_asymbol_name(sym);
|
|
||||||
if (nm && nm[0]) {
|
|
||||||
char* dnm = nullptr;
|
|
||||||
if (strncmp("__thunk", nm, 7)) {
|
|
||||||
dnm =
|
|
||||||
abi::__cxa_demangle(nm, demangle_buffer, &demangle_buffer_size, 0);
|
|
||||||
if (dnm) {
|
|
||||||
demangle_buffer = dnm;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
(*sp)->Init(dnm ? dnm : nm, syminfo.value + aBaseAddress);
|
|
||||||
NEXT_SYMBOL;
|
|
||||||
}
|
|
||||||
// }
|
|
||||||
}
|
|
||||||
|
|
||||||
free(demangle_buffer);
|
|
||||||
demangle_buffer = nullptr;
|
|
||||||
|
|
||||||
bfd_close(symbolFile);
|
|
||||||
|
|
||||||
int interesting = sp - externalSymbols;
|
|
||||||
if (!quiet) {
|
|
||||||
printf("%s provided %d symbols\n", aFileName,
|
|
||||||
interesting - initialSymbols);
|
|
||||||
}
|
|
||||||
usefulSymbols = interesting;
|
|
||||||
}
|
|
||||||
|
|
||||||
#endif /* USE_BFD */
|
|
||||||
|
|
@ -1,99 +0,0 @@
|
||||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
|
||||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
||||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
|
||||||
|
|
||||||
#include "leaky.h"
|
|
||||||
|
|
||||||
#ifdef USE_COFF
|
|
||||||
|
|
||||||
#define LANGUAGE_C
|
|
||||||
#include <sym.h>
|
|
||||||
#include <cmplrs/stsupport.h>
|
|
||||||
#include <symconst.h>
|
|
||||||
#include <filehdr.h>
|
|
||||||
#include <ldfcn.h>
|
|
||||||
#include <string.h>
|
|
||||||
#include <stdlib.h>
|
|
||||||
|
|
||||||
#ifdef IRIX4
|
|
||||||
extern "C" {
|
|
||||||
extern char *demangle(char const* in);
|
|
||||||
};
|
|
||||||
#else
|
|
||||||
#include <dem.h>
|
|
||||||
#endif
|
|
||||||
|
|
||||||
static char *Demangle(char *rawName)
|
|
||||||
{
|
|
||||||
#ifdef IRIX4
|
|
||||||
return strdup(demangle(rawName));
|
|
||||||
#else
|
|
||||||
char namebuf[4000];
|
|
||||||
demangle(rawName, namebuf);
|
|
||||||
return strdup(namebuf);
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
void leaky::readSymbols(const char *fileName)
|
|
||||||
{
|
|
||||||
LDFILE *ldptr;
|
|
||||||
|
|
||||||
ldptr = ldopen(fileName, nullptr);
|
|
||||||
if (!ldptr) {
|
|
||||||
fprintf(stderr, "%s: unable to open \"%s\"\n", applicationName,
|
|
||||||
fileName);
|
|
||||||
exit(-1);
|
|
||||||
}
|
|
||||||
if (PSYMTAB(ldptr) == 0) {
|
|
||||||
fprintf(stderr, "%s: \"%s\": has no symbol table\n", applicationName,
|
|
||||||
fileName);
|
|
||||||
exit(-1);
|
|
||||||
}
|
|
||||||
|
|
||||||
long isymMax = SYMHEADER(ldptr).isymMax;
|
|
||||||
long iextMax = SYMHEADER(ldptr).iextMax;
|
|
||||||
long iMax = isymMax + iextMax;
|
|
||||||
|
|
||||||
long alloced = 10000;
|
|
||||||
Symbol* syms = (Symbol*) malloc(sizeof(Symbol) * 10000);
|
|
||||||
Symbol* sp = syms;
|
|
||||||
Symbol* last = syms + alloced;
|
|
||||||
SYMR symr;
|
|
||||||
|
|
||||||
for (long isym = 0; isym < iMax; isym++) {
|
|
||||||
if (ldtbread(ldptr, isym, &symr) != SUCCESS) {
|
|
||||||
fprintf(stderr, "%s: can't read symbol #%d\n", applicationName,
|
|
||||||
isym);
|
|
||||||
exit(-1);
|
|
||||||
}
|
|
||||||
if (isym < isymMax) {
|
|
||||||
if ((symr.st == stStaticProc)
|
|
||||||
|| ((symr.st == stProc) &&
|
|
||||||
((symr.sc == scText) || (symr.sc == scAbs)))
|
|
||||||
|| ((symr.st == stBlock) &&
|
|
||||||
(symr.sc == scText))) {
|
|
||||||
// Text symbol. Set name field to point to the symbol name
|
|
||||||
sp->name = Demangle(ldgetname(ldptr, &symr));
|
|
||||||
sp->address = symr.value;
|
|
||||||
sp++;
|
|
||||||
if (sp >= last) {
|
|
||||||
long n = alloced + 10000;
|
|
||||||
syms = (Symbol*)
|
|
||||||
realloc(syms, (size_t) (sizeof(Symbol) * n));
|
|
||||||
last = syms + n;
|
|
||||||
sp = syms + alloced;
|
|
||||||
alloced = n;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
int interesting = sp - syms;
|
|
||||||
if (!quiet) {
|
|
||||||
printf("Total of %d symbols\n", interesting);
|
|
||||||
}
|
|
||||||
usefulSymbols = interesting;
|
|
||||||
externalSymbols = syms;
|
|
||||||
}
|
|
||||||
|
|
||||||
#endif /* USE_COFF */
|
|
||||||
|
|
@ -1,133 +0,0 @@
|
||||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
|
||||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
||||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
|
||||||
|
|
||||||
#include "leaky.h"
|
|
||||||
|
|
||||||
#ifdef USE_ELF
|
|
||||||
|
|
||||||
#include "leaky.h"
|
|
||||||
#include <stdio.h>
|
|
||||||
#include <malloc.h>
|
|
||||||
#include <libelf/libelf.h>
|
|
||||||
#include <unistd.h>
|
|
||||||
#include <fcntl.h>
|
|
||||||
#include <string.h>
|
|
||||||
|
|
||||||
void leaky::readSymbols(const char *fileName)
|
|
||||||
{
|
|
||||||
int fd = ::open(fileName, O_RDONLY);
|
|
||||||
if (fd < 0) {
|
|
||||||
fprintf(stderr, "%s: unable to open \"%s\"\n", applicationName,
|
|
||||||
fileName);
|
|
||||||
exit(-1);
|
|
||||||
}
|
|
||||||
|
|
||||||
elf_version(EV_CURRENT);
|
|
||||||
Elf *elf = elf_begin(fd, ELF_C_READ, 0);
|
|
||||||
if (!elf) {
|
|
||||||
fprintf(stderr, "%s: \"%s\": has no symbol table\n", applicationName,
|
|
||||||
fileName);
|
|
||||||
exit(-1);
|
|
||||||
}
|
|
||||||
|
|
||||||
long alloced = 10000;
|
|
||||||
Symbol* syms = (Symbol*) malloc(sizeof(Symbol) * 10000);
|
|
||||||
Symbol* sp = syms;
|
|
||||||
Symbol* last = syms + alloced;
|
|
||||||
|
|
||||||
// Get each of the relevant sections and add them to the list of
|
|
||||||
// symbols.
|
|
||||||
Elf32_Ehdr *ehdr = elf32_getehdr(elf);
|
|
||||||
if (!ehdr) {
|
|
||||||
fprintf(stderr, "%s: elf library lossage\n", applicationName);
|
|
||||||
exit(-1);
|
|
||||||
}
|
|
||||||
#if 0
|
|
||||||
Elf32_Half ndx = ehdr->e_shstrndx;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
Elf_Scn *scn = 0;
|
|
||||||
int strtabndx = -1;
|
|
||||||
for (int i = 1; (scn = elf_nextscn(elf, scn)) != 0; i++) {
|
|
||||||
Elf32_Shdr *shdr = elf32_getshdr(scn);
|
|
||||||
#if 0
|
|
||||||
char *name = elf_strptr(elf, ndx, (size_t) shdr->sh_name);
|
|
||||||
printf("Section %s (%d 0x%x)\n", name ? name : "(null)",
|
|
||||||
shdr->sh_type, shdr->sh_type);
|
|
||||||
#endif
|
|
||||||
if (shdr->sh_type == SHT_STRTAB) {
|
|
||||||
/* We assume here that string tables preceed symbol tables... */
|
|
||||||
strtabndx = i;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
#if 0
|
|
||||||
if (shdr->sh_type == SHT_DYNAMIC) {
|
|
||||||
/* Dynamic */
|
|
||||||
Elf_Data *data = elf_getdata(scn, 0);
|
|
||||||
if (!data || !data->d_size) {
|
|
||||||
printf("No data...");
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
Elf32_Dyn *dyn = (Elf32_Dyn*) data->d_buf;
|
|
||||||
Elf32_Dyn *lastdyn =
|
|
||||||
(Elf32_Dyn*) ((char*) data->d_buf + data->d_size);
|
|
||||||
for (; dyn < lastdyn; dyn++) {
|
|
||||||
printf("tag=%d value=0x%x\n", dyn->d_tag, dyn->d_un.d_val);
|
|
||||||
}
|
|
||||||
} else
|
|
||||||
#endif
|
|
||||||
if ((shdr->sh_type == SHT_SYMTAB) ||
|
|
||||||
(shdr->sh_type == SHT_DYNSYM)) {
|
|
||||||
/* Symbol table */
|
|
||||||
Elf_Data *data = elf_getdata(scn, 0);
|
|
||||||
if (!data || !data->d_size) {
|
|
||||||
printf("No data...");
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* In theory we now have the symbols... */
|
|
||||||
Elf32_Sym *esym = (Elf32_Sym*) data->d_buf;
|
|
||||||
Elf32_Sym *lastsym =
|
|
||||||
(Elf32_Sym*) ((char*) data->d_buf + data->d_size);
|
|
||||||
for (; esym < lastsym; esym++) {
|
|
||||||
#if 0
|
|
||||||
char *nm = elf_strptr(elf, strtabndx, (size_t)esym->st_name);
|
|
||||||
printf("%20s 0x%08x %02x %02x\n",
|
|
||||||
nm, esym->st_value, ELF32_ST_BIND(esym->st_info),
|
|
||||||
ELF32_ST_TYPE(esym->st_info));
|
|
||||||
#endif
|
|
||||||
if ((esym->st_value == 0) ||
|
|
||||||
(ELF32_ST_BIND(esym->st_info) == STB_WEAK) ||
|
|
||||||
(ELF32_ST_BIND(esym->st_info) == STB_NUM) ||
|
|
||||||
(ELF32_ST_TYPE(esym->st_info) != STT_FUNC)) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
#if 1
|
|
||||||
char *nm = elf_strptr(elf, strtabndx, (size_t)esym->st_name);
|
|
||||||
#endif
|
|
||||||
sp->name = nm ? strdup(nm) : "(no name)";
|
|
||||||
sp->address = esym->st_value;
|
|
||||||
sp++;
|
|
||||||
if (sp >= last) {
|
|
||||||
long n = alloced + 10000;
|
|
||||||
syms = (Symbol*)
|
|
||||||
realloc(syms, (size_t) (sizeof(Symbol) * n));
|
|
||||||
last = syms + n;
|
|
||||||
sp = syms + alloced;
|
|
||||||
alloced = n;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
int interesting = sp - syms;
|
|
||||||
if (!quiet) {
|
|
||||||
printf("Total of %d symbols\n", interesting);
|
|
||||||
}
|
|
||||||
usefulSymbols = interesting;
|
|
||||||
externalSymbols = syms;
|
|
||||||
}
|
|
||||||
|
|
||||||
#endif /* USE_ELF */
|
|
||||||
|
|
@ -1,71 +0,0 @@
|
||||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
|
||||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
||||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
|
||||||
|
|
||||||
#include "intcnt.h"
|
|
||||||
|
|
||||||
IntCount::IntCount() : numInts(0), iPair(nullptr) { }
|
|
||||||
IntCount::~IntCount() { delete [] iPair;}
|
|
||||||
int IntCount::getSize() {return numInts;}
|
|
||||||
int IntCount::getCount(int pos) {return iPair[pos].cnt;}
|
|
||||||
int IntCount::getIndex(int pos) {return iPair[pos].idx;}
|
|
||||||
|
|
||||||
void IntCount::clear()
|
|
||||||
{
|
|
||||||
delete[] iPair;
|
|
||||||
iPair = new IntPair[0];
|
|
||||||
numInts = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
int IntCount::countAdd(int index, int increment)
|
|
||||||
{
|
|
||||||
if(numInts) {
|
|
||||||
// Do a binary search to find the element
|
|
||||||
int divPoint = 0;
|
|
||||||
|
|
||||||
if(index>iPair[numInts-1].idx) {
|
|
||||||
divPoint = numInts;
|
|
||||||
} else if(index<iPair[0].idx) {
|
|
||||||
divPoint = 0;
|
|
||||||
} else {
|
|
||||||
int low=0, high=numInts-1;
|
|
||||||
int mid = (low+high)/2;
|
|
||||||
while(1) {
|
|
||||||
mid = (low+high)/2;
|
|
||||||
|
|
||||||
if(index<iPair[mid].idx) {
|
|
||||||
high = mid;
|
|
||||||
} else if(index>iPair[mid].idx) {
|
|
||||||
if(mid<numInts-1 && index<iPair[mid+1].idx) {
|
|
||||||
divPoint = mid+1;
|
|
||||||
break;
|
|
||||||
} else {
|
|
||||||
low = mid+1;
|
|
||||||
}
|
|
||||||
} else if(index==iPair[mid].idx) {
|
|
||||||
return iPair[mid].cnt += increment;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
int i;
|
|
||||||
IntPair *tpair = new IntPair[numInts+1];
|
|
||||||
for(i=0; i<divPoint; i++) {
|
|
||||||
tpair[i] = iPair[i];
|
|
||||||
}
|
|
||||||
for(i=divPoint; i<numInts; i++) {
|
|
||||||
tpair[i+1] = iPair[i];
|
|
||||||
}
|
|
||||||
++numInts;
|
|
||||||
delete [] iPair;
|
|
||||||
iPair = tpair;
|
|
||||||
iPair[divPoint].idx = index;
|
|
||||||
iPair[divPoint].cnt = increment;
|
|
||||||
return increment;
|
|
||||||
} else {
|
|
||||||
iPair = new IntPair[1];
|
|
||||||
numInts = 1;
|
|
||||||
iPair[0].idx = index;
|
|
||||||
return iPair[0].cnt = increment;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,38 +0,0 @@
|
||||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
|
||||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
||||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
|
||||||
|
|
||||||
#ifndef INTCNT_H
|
|
||||||
#define INTCNT_H
|
|
||||||
|
|
||||||
class IntCount
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
IntCount();
|
|
||||||
~IntCount();
|
|
||||||
void clear();
|
|
||||||
int countAdd(int index, int increment=1);
|
|
||||||
int countGet(int index);
|
|
||||||
int getSize();
|
|
||||||
int getCount(int pos);
|
|
||||||
int getIndex(int pos);
|
|
||||||
|
|
||||||
IntCount(const IntCount&old)
|
|
||||||
{
|
|
||||||
numInts = old.numInts;
|
|
||||||
if (numInts > 0) {
|
|
||||||
iPair = new IntPair[numInts];
|
|
||||||
for (int i = 0; i < numInts; i++) {
|
|
||||||
iPair[i] = old.iPair[i];
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
iPair = nullptr;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
private:
|
|
||||||
|
|
||||||
int numInts;
|
|
||||||
struct IntPair{int idx; int cnt;} *iPair;
|
|
||||||
};
|
|
||||||
|
|
||||||
#endif
|
|
||||||
|
|
@ -1,46 +0,0 @@
|
||||||
#!/bin/sh
|
|
||||||
#
|
|
||||||
# This Source Code Form is subject to the terms of the Mozilla Public
|
|
||||||
# License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
||||||
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
|
||||||
|
|
||||||
#
|
|
||||||
# Find Mozilla PID and send it a signal, to be used
|
|
||||||
# with the jprof tool.
|
|
||||||
#
|
|
||||||
|
|
||||||
jpsignal_usage() {
|
|
||||||
echo "Usage: jprofsig [start|stop]"
|
|
||||||
exit 1
|
|
||||||
}
|
|
||||||
|
|
||||||
if [ $# != 1 ]; then
|
|
||||||
echo "Wrong number of arguments."
|
|
||||||
jpsignal_usage
|
|
||||||
fi
|
|
||||||
|
|
||||||
jpsignal_arg="$1"
|
|
||||||
|
|
||||||
# Find & print mozilla PID
|
|
||||||
tmpmoz=`ps aux | grep mozilla-bin | head -1 | awk '{ print $2 }'`
|
|
||||||
echo "Mozilla PID = $tmpmoz"
|
|
||||||
|
|
||||||
# See how we were called.
|
|
||||||
case "$jpsignal_arg" in
|
|
||||||
start)
|
|
||||||
if [ "$JP_REALTIME" = 1 ]; then
|
|
||||||
kill -ALRM $tmpmoz
|
|
||||||
else
|
|
||||||
# Normal, non-realtime mode.
|
|
||||||
kill -PROF $tmpmoz
|
|
||||||
fi
|
|
||||||
;;
|
|
||||||
stop)
|
|
||||||
kill -USR1 $tmpmoz
|
|
||||||
;;
|
|
||||||
*)
|
|
||||||
jpsignal_usage
|
|
||||||
exit 1
|
|
||||||
esac
|
|
||||||
|
|
||||||
exit 0
|
|
||||||
|
|
@ -1,863 +0,0 @@
|
||||||
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
|
||||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
|
||||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
||||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
|
||||||
|
|
||||||
#include "leaky.h"
|
|
||||||
#include "intcnt.h"
|
|
||||||
|
|
||||||
#include <sys/types.h>
|
|
||||||
#include <sys/mman.h>
|
|
||||||
#include <sys/stat.h>
|
|
||||||
#include <fcntl.h>
|
|
||||||
#include <unistd.h>
|
|
||||||
#include <string.h>
|
|
||||||
#ifndef NTO
|
|
||||||
#include <getopt.h>
|
|
||||||
#endif
|
|
||||||
#include <assert.h>
|
|
||||||
#include <stdlib.h>
|
|
||||||
#include <stdio.h>
|
|
||||||
|
|
||||||
#ifdef NTO
|
|
||||||
#include <mem.h>
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#ifndef FALSE
|
|
||||||
#define FALSE 0
|
|
||||||
#endif
|
|
||||||
#ifndef TRUE
|
|
||||||
#define TRUE 1
|
|
||||||
#endif
|
|
||||||
|
|
||||||
static const u_int DefaultBuckets = 10007; // arbitrary, but prime
|
|
||||||
static const u_int MaxBuckets = 1000003; // arbitrary, but prime
|
|
||||||
|
|
||||||
//----------------------------------------------------------------------
|
|
||||||
|
|
||||||
int main(int argc, char** argv)
|
|
||||||
{
|
|
||||||
leaky* l = new leaky;
|
|
||||||
|
|
||||||
l->initialize(argc, argv);
|
|
||||||
l->outputfd = stdout;
|
|
||||||
|
|
||||||
for (int i = 0; i < l->numLogFiles; i++) {
|
|
||||||
if (l->output_dir || l->numLogFiles > 1) {
|
|
||||||
char name[2048]; // XXX fix
|
|
||||||
if (l->output_dir)
|
|
||||||
snprintf(name,sizeof(name),"%s/%s.html",l->output_dir,argv[l->logFileIndex + i]);
|
|
||||||
else
|
|
||||||
snprintf(name,sizeof(name),"%s.html",argv[l->logFileIndex + i]);
|
|
||||||
|
|
||||||
fprintf(stderr,"opening %s\n",name);
|
|
||||||
l->outputfd = fopen(name,"w");
|
|
||||||
// if an error we won't process the file
|
|
||||||
}
|
|
||||||
if (l->outputfd) { // paranoia
|
|
||||||
l->open(argv[l->logFileIndex + i]);
|
|
||||||
|
|
||||||
if (l->outputfd != stderr) {
|
|
||||||
fclose(l->outputfd);
|
|
||||||
l->outputfd = nullptr;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
char *
|
|
||||||
htmlify(const char *in)
|
|
||||||
{
|
|
||||||
const char *p = in;
|
|
||||||
char *out, *q;
|
|
||||||
int n = 0;
|
|
||||||
size_t newlen;
|
|
||||||
|
|
||||||
// Count the number of '<' and '>' in the input.
|
|
||||||
while ((p = strpbrk(p, "<>")))
|
|
||||||
{
|
|
||||||
++n;
|
|
||||||
++p;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Knowing the number of '<' and '>', we can calculate the space
|
|
||||||
// needed for the output string.
|
|
||||||
newlen = strlen(in) + n * 3 + 1;
|
|
||||||
out = new char[newlen];
|
|
||||||
|
|
||||||
// Copy the input to the output, with substitutions.
|
|
||||||
p = in;
|
|
||||||
q = out;
|
|
||||||
do
|
|
||||||
{
|
|
||||||
if (*p == '<')
|
|
||||||
{
|
|
||||||
strcpy(q, "<");
|
|
||||||
q += 4;
|
|
||||||
}
|
|
||||||
else if (*p == '>')
|
|
||||||
{
|
|
||||||
strcpy(q, ">");
|
|
||||||
q += 4;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
*q++ = *p;
|
|
||||||
}
|
|
||||||
p++;
|
|
||||||
} while (*p);
|
|
||||||
*q = '\0';
|
|
||||||
|
|
||||||
return out;
|
|
||||||
}
|
|
||||||
|
|
||||||
leaky::leaky()
|
|
||||||
{
|
|
||||||
applicationName = nullptr;
|
|
||||||
progFile = nullptr;
|
|
||||||
|
|
||||||
quiet = true;
|
|
||||||
showAddress = false;
|
|
||||||
showThreads = false;
|
|
||||||
stackDepth = 100000;
|
|
||||||
onlyThread = 0;
|
|
||||||
cleo = false;
|
|
||||||
|
|
||||||
mappedLogFile = -1;
|
|
||||||
firstLogEntry = lastLogEntry = 0;
|
|
||||||
|
|
||||||
sfd = -1;
|
|
||||||
externalSymbols = 0;
|
|
||||||
usefulSymbols = 0;
|
|
||||||
numExternalSymbols = 0;
|
|
||||||
lowestSymbolAddr = 0;
|
|
||||||
highestSymbolAddr = 0;
|
|
||||||
|
|
||||||
loadMap = nullptr;
|
|
||||||
|
|
||||||
collect_last = false;
|
|
||||||
collect_start = -1;
|
|
||||||
collect_end = -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
leaky::~leaky()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
void leaky::usageError()
|
|
||||||
{
|
|
||||||
fprintf(stderr, "Usage: %s [-v] [-t] [-e exclude] [-i include] [-s stackdepth] [--last] [--all] [--start n [--end m]] [--cleo] [--output-dir dir] prog log [log2 ...]\n", (char*) applicationName);
|
|
||||||
fprintf(stderr,
|
|
||||||
"\t-v: verbose\n"
|
|
||||||
"\t-t | --threads: split threads\n"
|
|
||||||
"\t--only-thread n: only profile thread N\n"
|
|
||||||
"\t-i include-id: stack must include specified id\n"
|
|
||||||
"\t-e exclude-id: stack must NOT include specified id\n"
|
|
||||||
"\t-s stackdepth: Limit depth looked at from captured stack frames\n"
|
|
||||||
"\t--last: only profile the last capture section\n"
|
|
||||||
"\t--start n [--end m]: profile n to m (or end) capture sections\n"
|
|
||||||
"\t--cleo: format output for 'cleopatra' display\n"
|
|
||||||
"\t--output-dir dir: write output files to dir\n"
|
|
||||||
"\tIf there's one log, output goes to stdout unless --output-dir is set\n"
|
|
||||||
"\tIf there are more than one log, output files will be named with .html added\n"
|
|
||||||
);
|
|
||||||
exit(-1);
|
|
||||||
}
|
|
||||||
|
|
||||||
static struct option longopts[] = {
|
|
||||||
{ "threads", 0, nullptr, 't' },
|
|
||||||
{ "only-thread", 1, nullptr, 'T' },
|
|
||||||
{ "last", 0, nullptr, 'l' },
|
|
||||||
{ "start", 1, nullptr, 'x' },
|
|
||||||
{ "end", 1, nullptr, 'n' },
|
|
||||||
{ "cleo",0, nullptr, 'c' },
|
|
||||||
{ "output-dir", 1, nullptr, 'd' },
|
|
||||||
{ nullptr, 0, nullptr, 0 },
|
|
||||||
};
|
|
||||||
|
|
||||||
void leaky::initialize(int argc, char** argv)
|
|
||||||
{
|
|
||||||
applicationName = argv[0];
|
|
||||||
applicationName = strrchr(applicationName, '/');
|
|
||||||
if (!applicationName) {
|
|
||||||
applicationName = argv[0];
|
|
||||||
} else {
|
|
||||||
applicationName++;
|
|
||||||
}
|
|
||||||
|
|
||||||
int arg;
|
|
||||||
int errflg = 0;
|
|
||||||
int longindex = 0;
|
|
||||||
|
|
||||||
onlyThread = 0;
|
|
||||||
output_dir = nullptr;
|
|
||||||
cleo = false;
|
|
||||||
|
|
||||||
// XXX tons of cruft here left over from tracemalloc
|
|
||||||
// XXX The -- options shouldn't need short versions, or they should be documented
|
|
||||||
while (((arg = getopt_long(argc, argv, "adEe:gh:i:r:Rs:tT:qvx:ln:",longopts,&longindex)) != -1)) {
|
|
||||||
switch (arg) {
|
|
||||||
case '?':
|
|
||||||
default:
|
|
||||||
fprintf(stderr,"error: unknown option %c\n",optopt);
|
|
||||||
errflg++;
|
|
||||||
break;
|
|
||||||
case 'a':
|
|
||||||
break;
|
|
||||||
case 'A': // not implemented
|
|
||||||
showAddress = true;
|
|
||||||
break;
|
|
||||||
case 'c':
|
|
||||||
cleo = true;
|
|
||||||
break;
|
|
||||||
case 'd':
|
|
||||||
output_dir = optarg; // reference to an argv pointer
|
|
||||||
break;
|
|
||||||
case 'R':
|
|
||||||
break;
|
|
||||||
case 'e':
|
|
||||||
exclusions.add(optarg);
|
|
||||||
break;
|
|
||||||
case 'g':
|
|
||||||
break;
|
|
||||||
case 'r': // not implemented
|
|
||||||
roots.add(optarg);
|
|
||||||
if (!includes.IsEmpty()) {
|
|
||||||
errflg++;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case 'i':
|
|
||||||
includes.add(optarg);
|
|
||||||
if (!roots.IsEmpty()) {
|
|
||||||
errflg++;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case 'h':
|
|
||||||
break;
|
|
||||||
case 's':
|
|
||||||
stackDepth = atoi(optarg);
|
|
||||||
if (stackDepth < 2) {
|
|
||||||
stackDepth = 2;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case 'x':
|
|
||||||
// --start
|
|
||||||
collect_start = atoi(optarg);
|
|
||||||
break;
|
|
||||||
case 'n':
|
|
||||||
// --end
|
|
||||||
collect_end = atoi(optarg);
|
|
||||||
break;
|
|
||||||
case 'l':
|
|
||||||
// --last
|
|
||||||
collect_last = true;
|
|
||||||
break;
|
|
||||||
case 'q':
|
|
||||||
break;
|
|
||||||
case 'v':
|
|
||||||
quiet = !quiet;
|
|
||||||
break;
|
|
||||||
case 't':
|
|
||||||
showThreads = true;
|
|
||||||
break;
|
|
||||||
case 'T':
|
|
||||||
showThreads = true;
|
|
||||||
onlyThread = atoi(optarg);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (errflg || ((argc - optind) < 2)) {
|
|
||||||
usageError();
|
|
||||||
}
|
|
||||||
progFile = argv[optind++];
|
|
||||||
logFileIndex = optind;
|
|
||||||
numLogFiles = argc - optind;
|
|
||||||
if (!quiet)
|
|
||||||
fprintf(stderr,"numlogfiles = %d\n",numLogFiles);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void* mapFile(int fd, u_int flags, off_t* sz)
|
|
||||||
{
|
|
||||||
struct stat sb;
|
|
||||||
if (fstat(fd, &sb) < 0) {
|
|
||||||
perror("fstat");
|
|
||||||
exit(-1);
|
|
||||||
}
|
|
||||||
void* base = mmap(0, (int)sb.st_size, flags, MAP_PRIVATE, fd, 0);
|
|
||||||
if (!base) {
|
|
||||||
perror("mmap");
|
|
||||||
exit(-1);
|
|
||||||
}
|
|
||||||
*sz = sb.st_size;
|
|
||||||
return base;
|
|
||||||
}
|
|
||||||
|
|
||||||
void leaky::LoadMap()
|
|
||||||
{
|
|
||||||
malloc_map_entry mme;
|
|
||||||
char name[1000];
|
|
||||||
|
|
||||||
if (!loadMap) {
|
|
||||||
// all files use the same map
|
|
||||||
int fd = ::open(M_MAPFILE, O_RDONLY);
|
|
||||||
if (fd < 0) {
|
|
||||||
perror("open: " M_MAPFILE);
|
|
||||||
exit(-1);
|
|
||||||
}
|
|
||||||
for (;;) {
|
|
||||||
int nb = read(fd, &mme, sizeof(mme));
|
|
||||||
if (nb != sizeof(mme)) break;
|
|
||||||
nb = read(fd, name, mme.nameLen);
|
|
||||||
if (nb != (int)mme.nameLen) break;
|
|
||||||
name[mme.nameLen] = 0;
|
|
||||||
if (!quiet) {
|
|
||||||
fprintf(stderr,"%s @ %lx\n", name, mme.address);
|
|
||||||
}
|
|
||||||
|
|
||||||
LoadMapEntry* lme = new LoadMapEntry;
|
|
||||||
lme->address = mme.address;
|
|
||||||
lme->name = strdup(name);
|
|
||||||
lme->next = loadMap;
|
|
||||||
loadMap = lme;
|
|
||||||
}
|
|
||||||
close(fd);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void leaky::open(char *logFile)
|
|
||||||
{
|
|
||||||
int threadArray[100]; // should auto-expand
|
|
||||||
int last_thread = -1;
|
|
||||||
int numThreads = 0;
|
|
||||||
int section = -1;
|
|
||||||
bool collecting = false;
|
|
||||||
|
|
||||||
LoadMap();
|
|
||||||
|
|
||||||
setupSymbols(progFile);
|
|
||||||
|
|
||||||
// open up the log file
|
|
||||||
if (mappedLogFile)
|
|
||||||
::close(mappedLogFile);
|
|
||||||
|
|
||||||
mappedLogFile = ::open(logFile, O_RDONLY);
|
|
||||||
if (mappedLogFile < 0) {
|
|
||||||
perror("open");
|
|
||||||
exit(-1);
|
|
||||||
}
|
|
||||||
off_t size;
|
|
||||||
firstLogEntry = (malloc_log_entry*) mapFile(mappedLogFile, PROT_READ, &size);
|
|
||||||
lastLogEntry = (malloc_log_entry*)((char*)firstLogEntry + size);
|
|
||||||
|
|
||||||
if (!collect_last || collect_start < 0) {
|
|
||||||
collecting = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// First, restrict it to the capture sections specified (all, last, start/end)
|
|
||||||
// This loop walks through all the call stacks we recorded
|
|
||||||
for (malloc_log_entry* lep=firstLogEntry;
|
|
||||||
lep < lastLogEntry;
|
|
||||||
lep = reinterpret_cast<malloc_log_entry*>(&lep->pcs[lep->numpcs])) {
|
|
||||||
|
|
||||||
if (lep->flags & JP_FIRST_AFTER_PAUSE) {
|
|
||||||
section++;
|
|
||||||
if (collect_last) {
|
|
||||||
firstLogEntry = lep;
|
|
||||||
numThreads = 0;
|
|
||||||
collecting = true;
|
|
||||||
}
|
|
||||||
if (collect_start == section) {
|
|
||||||
collecting = true;
|
|
||||||
firstLogEntry = lep;
|
|
||||||
}
|
|
||||||
if (collect_end == section) {
|
|
||||||
collecting = false;
|
|
||||||
lastLogEntry = lep;
|
|
||||||
}
|
|
||||||
if (!quiet)
|
|
||||||
fprintf(stderr,"New section %d: first=%p, last=%p, collecting=%d\n",
|
|
||||||
section,(void*)firstLogEntry,(void*)lastLogEntry,collecting);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Capture thread info at the same time
|
|
||||||
|
|
||||||
// Find all the threads captured
|
|
||||||
|
|
||||||
// pthread/linux docs say the signal can be delivered to any thread in
|
|
||||||
// the process. In practice, it appears in Linux that it's always
|
|
||||||
// delivered to the thread that called setitimer(), and each thread can
|
|
||||||
// have a separate itimer. There's a support library for gprof that
|
|
||||||
// overlays pthread_create() to set timers in any threads you spawn.
|
|
||||||
if (showThreads && collecting) {
|
|
||||||
if (lep->thread != last_thread)
|
|
||||||
{
|
|
||||||
int i;
|
|
||||||
for (i=0; i<numThreads; i++)
|
|
||||||
{
|
|
||||||
if (lep->thread == threadArray[i])
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
if (i == numThreads &&
|
|
||||||
i < (int) (sizeof(threadArray)/sizeof(threadArray[0])))
|
|
||||||
{
|
|
||||||
threadArray[i] = lep->thread;
|
|
||||||
numThreads++;
|
|
||||||
if (!quiet)
|
|
||||||
fprintf(stderr,"new thread %d\n",lep->thread);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!quiet)
|
|
||||||
fprintf(stderr,"Done collecting: sections %d: first=%p, last=%p, numThreads=%d\n",
|
|
||||||
section,(void*)firstLogEntry,(void*)lastLogEntry,numThreads);
|
|
||||||
|
|
||||||
if (!cleo) {
|
|
||||||
fprintf(outputfd,"<html><head><title>Jprof Profile Report</title></head><body>\n");
|
|
||||||
fprintf(outputfd,"<h1><center>Jprof Profile Report</center></h1>\n");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (showThreads)
|
|
||||||
{
|
|
||||||
fprintf(stderr,"Num threads %d\n",numThreads);
|
|
||||||
|
|
||||||
if (!cleo) {
|
|
||||||
fprintf(outputfd,"<hr>Threads:<p><pre>\n");
|
|
||||||
for (int i=0; i<numThreads; i++)
|
|
||||||
{
|
|
||||||
fprintf(outputfd," <a href=\"#thread_%d\">%d</a> ",
|
|
||||||
threadArray[i],threadArray[i]);
|
|
||||||
if ((i+1)%10 == 0)
|
|
||||||
fprintf(outputfd,"<br>\n");
|
|
||||||
}
|
|
||||||
fprintf(outputfd,"</pre>");
|
|
||||||
}
|
|
||||||
|
|
||||||
for (int i=0; i<numThreads; i++)
|
|
||||||
{
|
|
||||||
if (!onlyThread || onlyThread == threadArray[i])
|
|
||||||
analyze(threadArray[i]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
analyze(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!cleo)
|
|
||||||
fprintf(outputfd,"</pre></body></html>\n");
|
|
||||||
}
|
|
||||||
|
|
||||||
//----------------------------------------------------------------------
|
|
||||||
|
|
||||||
|
|
||||||
static int symbolOrder(void const* a, void const* b)
|
|
||||||
{
|
|
||||||
Symbol const** ap = (Symbol const **)a;
|
|
||||||
Symbol const** bp = (Symbol const **)b;
|
|
||||||
return (*ap)->address == (*bp)->address ? 0 :
|
|
||||||
((*ap)->address > (*bp)->address ? 1 : -1);
|
|
||||||
}
|
|
||||||
|
|
||||||
void leaky::ReadSharedLibrarySymbols()
|
|
||||||
{
|
|
||||||
LoadMapEntry* lme = loadMap;
|
|
||||||
while (nullptr != lme) {
|
|
||||||
ReadSymbols(lme->name, lme->address);
|
|
||||||
lme = lme->next;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void leaky::setupSymbols(const char *fileName)
|
|
||||||
{
|
|
||||||
if (usefulSymbols == 0) {
|
|
||||||
// only read once!
|
|
||||||
|
|
||||||
// Read in symbols from the program
|
|
||||||
ReadSymbols(fileName, 0);
|
|
||||||
|
|
||||||
// Read in symbols from the .so's
|
|
||||||
ReadSharedLibrarySymbols();
|
|
||||||
|
|
||||||
if (!quiet) {
|
|
||||||
fprintf(stderr,"A total of %d symbols were loaded\n", usefulSymbols);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Now sort them
|
|
||||||
qsort(externalSymbols, usefulSymbols, sizeof(Symbol *), symbolOrder);
|
|
||||||
lowestSymbolAddr = externalSymbols[0]->address;
|
|
||||||
highestSymbolAddr = externalSymbols[usefulSymbols-1]->address;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Binary search the table, looking for a symbol that covers this
|
|
||||||
// address.
|
|
||||||
int leaky::findSymbolIndex(u_long addr)
|
|
||||||
{
|
|
||||||
u_int base = 0;
|
|
||||||
u_int limit = usefulSymbols - 1;
|
|
||||||
Symbol** end = &externalSymbols[limit];
|
|
||||||
while (base <= limit) {
|
|
||||||
u_int midPoint = (base + limit)>>1;
|
|
||||||
Symbol** sp = &externalSymbols[midPoint];
|
|
||||||
if (addr < (*sp)->address) {
|
|
||||||
if (midPoint == 0) {
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
limit = midPoint - 1;
|
|
||||||
} else {
|
|
||||||
if (sp+1 < end) {
|
|
||||||
if (addr < (*(sp+1))->address) {
|
|
||||||
return midPoint;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return midPoint;
|
|
||||||
}
|
|
||||||
base = midPoint + 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
Symbol* leaky::findSymbol(u_long addr)
|
|
||||||
{
|
|
||||||
int idx = findSymbolIndex(addr);
|
|
||||||
|
|
||||||
if(idx<0) {
|
|
||||||
return nullptr;
|
|
||||||
} else {
|
|
||||||
return externalSymbols[idx];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//----------------------------------------------------------------------
|
|
||||||
|
|
||||||
bool leaky::excluded(malloc_log_entry* lep)
|
|
||||||
{
|
|
||||||
if (exclusions.IsEmpty()) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
char** pcp = &lep->pcs[0];
|
|
||||||
u_int n = lep->numpcs;
|
|
||||||
for (u_int i = 0; i < n; i++, pcp++) {
|
|
||||||
Symbol* sp = findSymbol((u_long) *pcp);
|
|
||||||
if (sp && exclusions.contains(sp->name)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool leaky::included(malloc_log_entry* lep)
|
|
||||||
{
|
|
||||||
if (includes.IsEmpty()) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
char** pcp = &lep->pcs[0];
|
|
||||||
u_int n = lep->numpcs;
|
|
||||||
for (u_int i = 0; i < n; i++, pcp++) {
|
|
||||||
Symbol* sp = findSymbol((u_long) *pcp);
|
|
||||||
if (sp && includes.contains(sp->name)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
//----------------------------------------------------------------------
|
|
||||||
|
|
||||||
void leaky::displayStackTrace(FILE* out, malloc_log_entry* lep)
|
|
||||||
{
|
|
||||||
char** pcp = &lep->pcs[0];
|
|
||||||
u_int n = (lep->numpcs < stackDepth) ? lep->numpcs : stackDepth;
|
|
||||||
for (u_int i = 0; i < n; i++, pcp++) {
|
|
||||||
u_long addr = (u_long) *pcp;
|
|
||||||
Symbol* sp = findSymbol(addr);
|
|
||||||
if (sp) {
|
|
||||||
fputs(sp->name, out);
|
|
||||||
if (showAddress) {
|
|
||||||
fprintf(out, "[%p]", (char*)addr);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
fprintf(out, "<%p>", (char*)addr);
|
|
||||||
}
|
|
||||||
fputc(' ', out);
|
|
||||||
}
|
|
||||||
fputc('\n', out);
|
|
||||||
}
|
|
||||||
|
|
||||||
void leaky::dumpEntryToLog(malloc_log_entry* lep)
|
|
||||||
{
|
|
||||||
printf("%ld\t", lep->delTime);
|
|
||||||
printf(" --> ");
|
|
||||||
displayStackTrace(outputfd, lep);
|
|
||||||
}
|
|
||||||
|
|
||||||
void leaky::generateReportHTML(FILE *fp, int *countArray, int count, int thread)
|
|
||||||
{
|
|
||||||
fprintf(fp,"<center>");
|
|
||||||
if (showThreads)
|
|
||||||
{
|
|
||||||
fprintf(fp,"<hr><A NAME=thread_%d><b>Thread: %d</b></A><p>",
|
|
||||||
thread,thread);
|
|
||||||
}
|
|
||||||
fprintf(fp,"<A href=#flat_%d>flat</A><b> | </b><A href=#hier_%d>hierarchical</A>",
|
|
||||||
thread,thread);
|
|
||||||
fprintf(fp,"</center><P><P><P>\n");
|
|
||||||
|
|
||||||
int totalTimerHits = count;
|
|
||||||
int *rankingTable = new int[usefulSymbols];
|
|
||||||
|
|
||||||
for(int cnt=usefulSymbols; --cnt>=0; rankingTable[cnt]=cnt);
|
|
||||||
|
|
||||||
// Drat. I would use ::qsort() but I would need a global variable and my
|
|
||||||
// intro-pascal professor threatened to flunk anyone who used globals.
|
|
||||||
// She damaged me for life :-) (That was 1986. See how much influence
|
|
||||||
// she had. I don't remember her name but I always feel guilty about globals)
|
|
||||||
|
|
||||||
// Shell Sort. 581130733 is the max 31 bit value of h = 3h+1
|
|
||||||
int mx, i, h;
|
|
||||||
for(mx=usefulSymbols/9, h=581130733; h>0; h/=3) {
|
|
||||||
if(h<mx) {
|
|
||||||
for(i = h-1; i<usefulSymbols; i++) {
|
|
||||||
int j, tmp=rankingTable[i], val = countArray[tmp];
|
|
||||||
for(j = i; (j>=h) && (countArray[rankingTable[j-h]]<val); j-=h) {
|
|
||||||
rankingTable[j] = rankingTable[j-h];
|
|
||||||
}
|
|
||||||
rankingTable[j] = tmp;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Ok, We are sorted now. Let's go through the table until we get to
|
|
||||||
// functions that were never called. Right now we don't do much inside
|
|
||||||
// this loop. Later we can get callers and callees into it like gprof
|
|
||||||
// does
|
|
||||||
fprintf(fp,
|
|
||||||
"<h2><A NAME=hier_%d></A><center><a href=\"http://dxr.mozilla.org/mozilla-central/source/tools/jprof/README.html#hier\">Hierarchical Profile</a></center></h2><hr>\n",
|
|
||||||
thread);
|
|
||||||
fprintf(fp, "<pre>\n");
|
|
||||||
fprintf(fp, "%6s %6s %4s %s\n",
|
|
||||||
"index", "Count", "Hits", "Function Name");
|
|
||||||
|
|
||||||
for(i=0; i<usefulSymbols && countArray[rankingTable[i]]>0; i++) {
|
|
||||||
Symbol **sp=&externalSymbols[rankingTable[i]];
|
|
||||||
|
|
||||||
(*sp)->cntP.printReport(fp, this, rankingTable[i], totalTimerHits);
|
|
||||||
|
|
||||||
char *symname = htmlify((*sp)->name);
|
|
||||||
fprintf(fp, "%6d %6d (%3.1f%%)%s <a name=%d>%8d (%3.1f%%)</a>%s <b>%s</b>\n",
|
|
||||||
rankingTable[i],
|
|
||||||
(*sp)->timerHit, ((*sp)->timerHit*1000/totalTimerHits)/10.0,
|
|
||||||
((*sp)->timerHit*1000/totalTimerHits)/10.0 >= 10.0 ? "" : " ",
|
|
||||||
rankingTable[i], countArray[rankingTable[i]],
|
|
||||||
(countArray[rankingTable[i]]*1000/totalTimerHits)/10.0,
|
|
||||||
(countArray[rankingTable[i]]*1000/totalTimerHits)/10.0 >= 10.0 ? "" : " ",
|
|
||||||
symname);
|
|
||||||
delete [] symname;
|
|
||||||
|
|
||||||
(*sp)->cntC.printReport(fp, this, rankingTable[i], totalTimerHits);
|
|
||||||
|
|
||||||
fprintf(fp, "<hr>\n");
|
|
||||||
}
|
|
||||||
fprintf(fp,"</pre>\n");
|
|
||||||
|
|
||||||
// OK, Now we want to print the flat profile. To do this we resort on
|
|
||||||
// the hit count.
|
|
||||||
|
|
||||||
// Cut-N-Paste Shell sort from above. The Ranking Table has already been
|
|
||||||
// populated, so we do not have to reinitialize it.
|
|
||||||
for(mx=usefulSymbols/9, h=581130733; h>0; h/=3) {
|
|
||||||
if(h<mx) {
|
|
||||||
for(i = h-1; i<usefulSymbols; i++) {
|
|
||||||
int j, tmp=rankingTable[i], val = externalSymbols[tmp]->timerHit;
|
|
||||||
for(j = i;
|
|
||||||
(j>=h) && (externalSymbols[rankingTable[j-h]]->timerHit<val); j-=h) {
|
|
||||||
rankingTable[j] = rankingTable[j-h];
|
|
||||||
}
|
|
||||||
rankingTable[j] = tmp;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Pre-count up total counter hits, to get a percentage.
|
|
||||||
// I wanted the total before walking the list, if this
|
|
||||||
// double-pass over externalSymbols gets slow we can
|
|
||||||
// do single-pass and print this out after the loop finishes.
|
|
||||||
totalTimerHits = 0;
|
|
||||||
for(i=0;
|
|
||||||
i<usefulSymbols && externalSymbols[rankingTable[i]]->timerHit>0; i++) {
|
|
||||||
Symbol **sp=&externalSymbols[rankingTable[i]];
|
|
||||||
totalTimerHits += (*sp)->timerHit;
|
|
||||||
}
|
|
||||||
if (totalTimerHits == 0)
|
|
||||||
totalTimerHits = 1;
|
|
||||||
|
|
||||||
if (totalTimerHits != count)
|
|
||||||
fprintf(stderr,"Hit count mismatch: count=%d; totalTimerHits=%d",
|
|
||||||
count,totalTimerHits);
|
|
||||||
|
|
||||||
fprintf(fp,"<h2><A NAME=flat_%d></A><center><a href=\"http://dxr.mozilla.org/mozilla-central/source/tools/jprof/README.html#flat\">Flat Profile</a></center></h2><br>\n",
|
|
||||||
thread);
|
|
||||||
fprintf(fp, "<pre>\n");
|
|
||||||
|
|
||||||
fprintf(fp, "Total hit count: %d\n", totalTimerHits);
|
|
||||||
fprintf(fp, "Count %%Total Function Name\n");
|
|
||||||
// Now loop for as long as we have timer hits
|
|
||||||
for(i=0;
|
|
||||||
i<usefulSymbols && externalSymbols[rankingTable[i]]->timerHit>0; i++) {
|
|
||||||
|
|
||||||
Symbol **sp=&externalSymbols[rankingTable[i]];
|
|
||||||
|
|
||||||
char *symname = htmlify((*sp)->name);
|
|
||||||
fprintf(fp, "<a href=\"#%d\">%3d %-2.1f %s</a>\n",
|
|
||||||
rankingTable[i], (*sp)->timerHit,
|
|
||||||
((float)(*sp)->timerHit/(float)totalTimerHits)*100.0, symname);
|
|
||||||
delete [] symname;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void leaky::analyze(int thread)
|
|
||||||
{
|
|
||||||
int *countArray = new int[usefulSymbols];
|
|
||||||
int *flagArray = new int[usefulSymbols];
|
|
||||||
|
|
||||||
//Zero our function call counter
|
|
||||||
memset(countArray, 0, sizeof(countArray[0])*usefulSymbols);
|
|
||||||
|
|
||||||
// reset hit counts
|
|
||||||
for(int i=0; i<usefulSymbols; i++) {
|
|
||||||
externalSymbols[i]->timerHit = 0;
|
|
||||||
externalSymbols[i]->regClear();
|
|
||||||
}
|
|
||||||
|
|
||||||
// The flag array is used to prevent counting symbols multiple times
|
|
||||||
// if functions are called recursively. In order to keep from having
|
|
||||||
// to zero it on each pass through the loop, we mark it with the value
|
|
||||||
// of stacks on each trip through the loop. This means we can determine
|
|
||||||
// if we have seen this symbol for this stack trace w/o having to reset
|
|
||||||
// from the prior stacktrace.
|
|
||||||
memset(flagArray, -1, sizeof(flagArray[0])*usefulSymbols);
|
|
||||||
|
|
||||||
if (cleo)
|
|
||||||
fprintf(outputfd,"m-Start\n");
|
|
||||||
|
|
||||||
// This loop walks through all the call stacks we recorded
|
|
||||||
// --last, --start and --end can restrict it, as can excludes/includes
|
|
||||||
stacks = 0;
|
|
||||||
for(malloc_log_entry* lep=firstLogEntry;
|
|
||||||
lep < lastLogEntry;
|
|
||||||
lep = reinterpret_cast<malloc_log_entry*>(&lep->pcs[lep->numpcs])) {
|
|
||||||
|
|
||||||
if ((thread != 0 && lep->thread != thread) ||
|
|
||||||
excluded(lep) || !included(lep))
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
++stacks; // How many stack frames did we collect
|
|
||||||
|
|
||||||
u_int n = (lep->numpcs < stackDepth) ? lep->numpcs : stackDepth;
|
|
||||||
char** pcp = &lep->pcs[n-1];
|
|
||||||
int idx=-1, parrentIdx=-1; // Init idx incase n==0
|
|
||||||
if (cleo) {
|
|
||||||
// This loop walks through every symbol in the call stack. By walking it
|
|
||||||
// backwards we know who called the function when we get there.
|
|
||||||
char type = 's';
|
|
||||||
for (int i=n-1; i>=0; --i, --pcp) {
|
|
||||||
idx = findSymbolIndex(reinterpret_cast<u_long>(*pcp));
|
|
||||||
|
|
||||||
if(idx>=0) {
|
|
||||||
// Skip over bogus __restore_rt frames that realtime profiling
|
|
||||||
// can introduce.
|
|
||||||
if (i > 0 && !strcmp(externalSymbols[idx]->name, "__restore_rt")) {
|
|
||||||
--pcp;
|
|
||||||
--i;
|
|
||||||
idx = findSymbolIndex(reinterpret_cast<u_long>(*pcp));
|
|
||||||
if (idx < 0) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Symbol **sp=&externalSymbols[idx];
|
|
||||||
char *symname = htmlify((*sp)->name);
|
|
||||||
fprintf(outputfd,"%c-%s\n",type,symname);
|
|
||||||
delete [] symname;
|
|
||||||
}
|
|
||||||
// else can't find symbol - ignore
|
|
||||||
type = 'c';
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// This loop walks through every symbol in the call stack. By walking it
|
|
||||||
// backwards we know who called the function when we get there.
|
|
||||||
for (int i=n-1; i>=0; --i, --pcp) {
|
|
||||||
idx = findSymbolIndex(reinterpret_cast<u_long>(*pcp));
|
|
||||||
|
|
||||||
if(idx>=0) {
|
|
||||||
// Skip over bogus __restore_rt frames that realtime profiling
|
|
||||||
// can introduce.
|
|
||||||
if (i > 0 && !strcmp(externalSymbols[idx]->name, "__restore_rt")) {
|
|
||||||
--pcp;
|
|
||||||
--i;
|
|
||||||
idx = findSymbolIndex(reinterpret_cast<u_long>(*pcp));
|
|
||||||
if (idx < 0) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// If we have not seen this symbol before count it and mark it as seen
|
|
||||||
if(flagArray[idx]!=stacks && ((flagArray[idx]=stacks) || true)) {
|
|
||||||
++countArray[idx];
|
|
||||||
}
|
|
||||||
|
|
||||||
// We know who we are and we know who our parrent is. Count this
|
|
||||||
if(parrentIdx>=0) {
|
|
||||||
externalSymbols[parrentIdx]->regChild(idx);
|
|
||||||
externalSymbols[idx]->regParrent(parrentIdx);
|
|
||||||
}
|
|
||||||
// inside if() so an unknown in the middle of a stack won't break
|
|
||||||
// the link!
|
|
||||||
parrentIdx=idx;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// idx should be the function that we were in when we received the signal.
|
|
||||||
if(idx>=0) {
|
|
||||||
++externalSymbols[idx]->timerHit;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!cleo)
|
|
||||||
generateReportHTML(outputfd, countArray, stacks, thread);
|
|
||||||
}
|
|
||||||
|
|
||||||
void FunctionCount::printReport(FILE *fp, leaky *lk, int parent, int total)
|
|
||||||
{
|
|
||||||
const char *fmt = " <A href=\"#%d\">%8d (%3.1f%%)%s %s</A>%s\n";
|
|
||||||
|
|
||||||
int nmax, tmax=((~0U)>>1);
|
|
||||||
|
|
||||||
do {
|
|
||||||
nmax=0;
|
|
||||||
for(int j=getSize(); --j>=0;) {
|
|
||||||
int cnt = getCount(j);
|
|
||||||
if(cnt==tmax) {
|
|
||||||
int idx = getIndex(j);
|
|
||||||
char *symname = htmlify(lk->indexToName(idx));
|
|
||||||
fprintf(fp, fmt, idx, getCount(j),
|
|
||||||
getCount(j)*100.0/total,
|
|
||||||
getCount(j)*100.0/total >= 10.0 ? "" : " ",
|
|
||||||
symname,
|
|
||||||
parent == idx ? " (self)" : "");
|
|
||||||
delete [] symname;
|
|
||||||
} else if(cnt<tmax && cnt>nmax) {
|
|
||||||
nmax=cnt;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} while((tmax=nmax)>0);
|
|
||||||
}
|
|
||||||
|
|
@ -1,122 +0,0 @@
|
||||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
|
||||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
||||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
|
||||||
|
|
||||||
#ifndef __leaky_h_
|
|
||||||
#define __leaky_h_
|
|
||||||
|
|
||||||
#include "config.h"
|
|
||||||
#include <stdio.h>
|
|
||||||
#include <string.h>
|
|
||||||
#include <sys/types.h>
|
|
||||||
#include "libmalloc.h"
|
|
||||||
#include "strset.h"
|
|
||||||
#include "intcnt.h"
|
|
||||||
|
|
||||||
typedef unsigned int u_int;
|
|
||||||
|
|
||||||
struct Symbol;
|
|
||||||
struct leaky;
|
|
||||||
|
|
||||||
class FunctionCount : public IntCount
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
void printReport(FILE *fp, leaky *lk, int parent, int total);
|
|
||||||
};
|
|
||||||
|
|
||||||
struct Symbol {
|
|
||||||
char* name;
|
|
||||||
u_long address;
|
|
||||||
int timerHit;
|
|
||||||
FunctionCount cntP, cntC;
|
|
||||||
|
|
||||||
int regChild(int id) {return cntC.countAdd(id, 1);}
|
|
||||||
int regParrent(int id) {return cntP.countAdd(id, 1);}
|
|
||||||
void regClear() {cntC.clear(); cntP.clear();}
|
|
||||||
|
|
||||||
Symbol() : timerHit(0) {}
|
|
||||||
void Init(const char* aName, u_long aAddress) {
|
|
||||||
name = aName ? strdup(aName) : (char *)"";
|
|
||||||
address = aAddress;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
struct LoadMapEntry {
|
|
||||||
char* name; // name of .so
|
|
||||||
u_long address; // base address where it was mapped in
|
|
||||||
LoadMapEntry* next;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct leaky {
|
|
||||||
leaky();
|
|
||||||
~leaky();
|
|
||||||
|
|
||||||
void initialize(int argc, char** argv);
|
|
||||||
void open(char *arg);
|
|
||||||
|
|
||||||
char* applicationName;
|
|
||||||
int logFileIndex;
|
|
||||||
int numLogFiles;
|
|
||||||
char* progFile;
|
|
||||||
FILE* outputfd;
|
|
||||||
|
|
||||||
bool quiet;
|
|
||||||
bool showAddress;
|
|
||||||
bool showThreads;
|
|
||||||
bool cleo;
|
|
||||||
u_int stackDepth;
|
|
||||||
int onlyThread;
|
|
||||||
char* output_dir;
|
|
||||||
|
|
||||||
int mappedLogFile;
|
|
||||||
malloc_log_entry* firstLogEntry;
|
|
||||||
malloc_log_entry* lastLogEntry;
|
|
||||||
|
|
||||||
int stacks;
|
|
||||||
|
|
||||||
int sfd;
|
|
||||||
Symbol** externalSymbols;
|
|
||||||
Symbol** lastSymbol;
|
|
||||||
int usefulSymbols;
|
|
||||||
int numExternalSymbols;
|
|
||||||
StrSet exclusions;
|
|
||||||
u_long lowestSymbolAddr;
|
|
||||||
u_long highestSymbolAddr;
|
|
||||||
|
|
||||||
LoadMapEntry* loadMap;
|
|
||||||
|
|
||||||
bool collect_last;
|
|
||||||
int collect_start;
|
|
||||||
int collect_end;
|
|
||||||
|
|
||||||
StrSet roots;
|
|
||||||
StrSet includes;
|
|
||||||
|
|
||||||
void usageError();
|
|
||||||
|
|
||||||
void LoadMap();
|
|
||||||
|
|
||||||
void analyze(int thread);
|
|
||||||
|
|
||||||
void dumpEntryToLog(malloc_log_entry* lep);
|
|
||||||
|
|
||||||
void insertAddress(u_long address, malloc_log_entry* lep);
|
|
||||||
void removeAddress(u_long address, malloc_log_entry* lep);
|
|
||||||
|
|
||||||
void displayStackTrace(FILE* out, malloc_log_entry* lep);
|
|
||||||
|
|
||||||
Symbol ** ExtendSymbols(int num);
|
|
||||||
void ReadSymbols(const char* fileName, u_long aBaseAddress);
|
|
||||||
void ReadSharedLibrarySymbols();
|
|
||||||
void setupSymbols(const char* fileName);
|
|
||||||
Symbol* findSymbol(u_long address);
|
|
||||||
bool excluded(malloc_log_entry* lep);
|
|
||||||
bool included(malloc_log_entry* lep);
|
|
||||||
const char* indexToName(int idx) {return externalSymbols[idx]->name;}
|
|
||||||
|
|
||||||
private:
|
|
||||||
void generateReportHTML(FILE *fp, int *countArray, int count, int thread);
|
|
||||||
int findSymbolIndex(u_long address);
|
|
||||||
};
|
|
||||||
|
|
||||||
#endif /* __leaky_h_ */
|
|
||||||
|
|
@ -1,28 +0,0 @@
|
||||||
# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
|
|
||||||
# vim: set filetype=python:
|
|
||||||
# This Source Code Form is subject to the terms of the Mozilla Public
|
|
||||||
# License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
||||||
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
|
||||||
|
|
||||||
DIRS += ['stub']
|
|
||||||
|
|
||||||
Program('jprof')
|
|
||||||
|
|
||||||
SOURCES += [
|
|
||||||
'bfd.cpp',
|
|
||||||
'coff.cpp',
|
|
||||||
'elf.cpp',
|
|
||||||
'intcnt.cpp',
|
|
||||||
'leaky.cpp',
|
|
||||||
'strset.cpp',
|
|
||||||
]
|
|
||||||
|
|
||||||
LOCAL_INCLUDES += [
|
|
||||||
'stub',
|
|
||||||
]
|
|
||||||
|
|
||||||
OS_LIBS += [
|
|
||||||
'dl',
|
|
||||||
'bfd',
|
|
||||||
'iberty',
|
|
||||||
]
|
|
||||||
|
|
@ -1,143 +0,0 @@
|
||||||
#!/usr/bin/python
|
|
||||||
# This Source Code Form is subject to the terms of the Mozilla Public
|
|
||||||
# License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
||||||
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
|
||||||
|
|
||||||
# This program splits up a jprof profile into multiple files based on a
|
|
||||||
# list of functions in a text file. First, a complete profile is
|
|
||||||
# generated. Then, for each line in the text file, a profile is
|
|
||||||
# generated containing only stacks that go through that line, and also
|
|
||||||
# excluding all stacks in earlier lines in the text file. This means
|
|
||||||
# that the text file, from start to end, is splitting out pieces of the
|
|
||||||
# profile in their own file. Finally, a final profile containing the
|
|
||||||
# remainder is produced.
|
|
||||||
|
|
||||||
# The program takes four arguments:
|
|
||||||
# (1) The path to jprof.
|
|
||||||
# (2) The path to the text file describing the splits. The output
|
|
||||||
# will be placed in the same directory as this file.
|
|
||||||
# (3) The program that was profiled.
|
|
||||||
# (4) The jprof-log file generated by the profile, to be split up.
|
|
||||||
# (Really, all arguments from (3) and later are passed through to
|
|
||||||
# jprof, so additional arguments could be provided if you want to pass
|
|
||||||
# additional arguments to jprof.)
|
|
||||||
|
|
||||||
# In slightly more detail:
|
|
||||||
#
|
|
||||||
# This script uses jprof's includes (-i) and excludes (-e) options to
|
|
||||||
# split profiles into segments. It takes as input a single text file,
|
|
||||||
# and from that text file creates a series of jprof profiles in the
|
|
||||||
# directory the text file is in.
|
|
||||||
#
|
|
||||||
# The input file format looks like the following:
|
|
||||||
#
|
|
||||||
# poll g_main_poll
|
|
||||||
# GetRuleCascade CSSRuleProcessor::GetRuleCascade(nsPresContext *, nsIAtom *)
|
|
||||||
# RuleProcessorData RuleProcessorData::RuleProcessorData(nsPresContext *, nsIContent *, nsRuleWalker *, nsCompatibility *)
|
|
||||||
#
|
|
||||||
# From this input file, the script will construct a profile called
|
|
||||||
# jprof-0.html that contains the whole profile, a profile called
|
|
||||||
# jprof-1-poll.html that includes only stacks with g_main_poll, a
|
|
||||||
# profile called jprof-2-GetRuleCascade.html that includes only stacks
|
|
||||||
# that have GetRuleCascade and do not have g_main_poll, a profile called
|
|
||||||
# jprof-3-RuleProcessorData.html that includes only stacks that have the
|
|
||||||
# RuleProcessorData constructor and do not have GetRuleCascade or
|
|
||||||
# g_main_poll, and a profile called jprof-4.html that includes only
|
|
||||||
# stacks that do not have any of the three functions in them.
|
|
||||||
#
|
|
||||||
# This means that all of the segments of the profile, except
|
|
||||||
# jprof-0.html, are mutually exclusive. Thus clever ordering of the
|
|
||||||
# functions in the input file can lead to a logical splitting of the
|
|
||||||
# profile into segments.
|
|
||||||
|
|
||||||
import sys
|
|
||||||
import subprocess
|
|
||||||
import os.path
|
|
||||||
|
|
||||||
if len(sys.argv) < 5:
|
|
||||||
sys.stderr.write("Expected arguments: <jprof> <split-file> <program> <jprof-log>\n")
|
|
||||||
sys.exit(1)
|
|
||||||
|
|
||||||
jprof = sys.argv[1]
|
|
||||||
splitfile = sys.argv[2]
|
|
||||||
passthrough = sys.argv[3:]
|
|
||||||
|
|
||||||
for f in [jprof, splitfile]:
|
|
||||||
if not os.path.isfile(f):
|
|
||||||
sys.stderr.write("could not find file: {0}\n".format(f))
|
|
||||||
sys.exit(1)
|
|
||||||
|
|
||||||
def read_splits(splitfile):
|
|
||||||
"""
|
|
||||||
Read splitfile (each line of which contains a name, a space, and
|
|
||||||
then a function name to split on), and return a list of pairs
|
|
||||||
representing exactly that. (Note that the name cannot contain
|
|
||||||
spaces, but the function name can, and often does.)
|
|
||||||
"""
|
|
||||||
def line_to_split(line):
|
|
||||||
line = line.strip("\r\n")
|
|
||||||
idx = line.index(" ")
|
|
||||||
return (line[0:idx], line[idx+1:])
|
|
||||||
|
|
||||||
io = open(splitfile, "r")
|
|
||||||
result = [line_to_split(line) for line in io]
|
|
||||||
io.close()
|
|
||||||
return result
|
|
||||||
|
|
||||||
splits = read_splits(splitfile)
|
|
||||||
|
|
||||||
def generate_profile(options, destfile):
|
|
||||||
"""
|
|
||||||
Run jprof to generate one split of the profile.
|
|
||||||
"""
|
|
||||||
args = [jprof] + options + passthrough
|
|
||||||
print "Generating {0}".format(destfile)
|
|
||||||
destio = open(destfile, "w")
|
|
||||||
# jprof expects the "jprof-map" file to be in its current working directory
|
|
||||||
cwd = None
|
|
||||||
for option in passthrough:
|
|
||||||
if option.find("jprof-log"):
|
|
||||||
cwd = os.path.dirname(option)
|
|
||||||
if cwd is None:
|
|
||||||
raise StandardError("no jprof-log option given")
|
|
||||||
process = subprocess.Popen(args, stdout=destio, cwd=cwd)
|
|
||||||
process.wait()
|
|
||||||
destio.close()
|
|
||||||
if process.returncode != 0:
|
|
||||||
os.remove(destfile)
|
|
||||||
sys.stderr.write("Error {0} from command:\n {1}\n".format(process.returncode, " ".join(args)))
|
|
||||||
sys.exit(process.returncode)
|
|
||||||
|
|
||||||
def output_filename(number, splitname):
|
|
||||||
"""
|
|
||||||
Return the filename (absolute path) we should use to output the
|
|
||||||
profile segment with the given number and splitname. Splitname
|
|
||||||
should be None for the complete profile and the remainder.
|
|
||||||
"""
|
|
||||||
def pad_count(i):
|
|
||||||
result = str(i)
|
|
||||||
# 0-pad to the same length
|
|
||||||
result = "0" * (len(str(len(splits) + 1)) - len(result)) + result
|
|
||||||
return result
|
|
||||||
|
|
||||||
name = pad_count(number)
|
|
||||||
if splitname is not None:
|
|
||||||
name += "-" + splitname
|
|
||||||
|
|
||||||
return os.path.join(os.path.dirname(splitfile),
|
|
||||||
"jprof-{0}.html".format(name))
|
|
||||||
|
|
||||||
# generate the complete profile
|
|
||||||
generate_profile([], output_filename(0, None))
|
|
||||||
|
|
||||||
# generate the listed splits
|
|
||||||
count = 1
|
|
||||||
excludes = []
|
|
||||||
for (splitname, splitfunction) in splits:
|
|
||||||
generate_profile(excludes + ["-i" + splitfunction],
|
|
||||||
output_filename(count, splitname))
|
|
||||||
excludes += ["-e" + splitfunction]
|
|
||||||
count = count + 1
|
|
||||||
|
|
||||||
# generate the remainder after the splits
|
|
||||||
generate_profile(excludes, output_filename(count, None))
|
|
||||||
|
|
@ -1,40 +0,0 @@
|
||||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
|
||||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
||||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
|
||||||
|
|
||||||
#include "strset.h"
|
|
||||||
#include <malloc.h>
|
|
||||||
#include <string.h>
|
|
||||||
|
|
||||||
StrSet::StrSet()
|
|
||||||
{
|
|
||||||
strings = 0;
|
|
||||||
numstrings = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
void StrSet::add(const char* s)
|
|
||||||
{
|
|
||||||
if (strings) {
|
|
||||||
strings = (char**) realloc(strings, (numstrings + 1) * sizeof(char*));
|
|
||||||
} else {
|
|
||||||
strings = (char**) malloc(sizeof(char*));
|
|
||||||
}
|
|
||||||
strings[numstrings] = strdup(s);
|
|
||||||
numstrings++;
|
|
||||||
}
|
|
||||||
|
|
||||||
int StrSet::contains(const char* s)
|
|
||||||
{
|
|
||||||
char** sp = strings;
|
|
||||||
int i = numstrings;
|
|
||||||
|
|
||||||
while (--i >= 0) {
|
|
||||||
char *ss = *sp++;
|
|
||||||
if (ss[0] == s[0]) {
|
|
||||||
if (strcmp(ss, s) == 0) {
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
@ -1,19 +0,0 @@
|
||||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
|
||||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
||||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
|
||||||
|
|
||||||
#ifndef __strset_h_
|
|
||||||
#define __strset_h_
|
|
||||||
|
|
||||||
struct StrSet {
|
|
||||||
StrSet();
|
|
||||||
|
|
||||||
void add(const char* string);
|
|
||||||
int contains(const char* string);
|
|
||||||
bool IsEmpty() const { return 0 == numstrings; }
|
|
||||||
|
|
||||||
char** strings;
|
|
||||||
int numstrings;
|
|
||||||
};
|
|
||||||
|
|
||||||
#endif /* __strset_h_ */
|
|
||||||
|
|
@ -1,8 +0,0 @@
|
||||||
#! gmake
|
|
||||||
#
|
|
||||||
# This Source Code Form is subject to the terms of the Mozilla Public
|
|
||||||
# License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
||||||
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
|
||||||
|
|
||||||
# override optimization
|
|
||||||
MOZ_OPTIMIZE_FLAGS = -fno-omit-frame-pointer
|
|
||||||
|
|
@ -1,18 +0,0 @@
|
||||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
|
||||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
||||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
|
||||||
|
|
||||||
#ifndef config_h___
|
|
||||||
#define config_h___
|
|
||||||
|
|
||||||
#define MAX_STACK_CRAWL 500
|
|
||||||
#define M_LOGFILE "jprof-log"
|
|
||||||
#define M_MAPFILE "jprof-map"
|
|
||||||
|
|
||||||
#if defined(linux) || defined(NTO)
|
|
||||||
#define USE_BFD
|
|
||||||
#undef NEED_WRAPPERS
|
|
||||||
|
|
||||||
#endif /* linux */
|
|
||||||
|
|
||||||
#endif /* config_h___ */
|
|
||||||
|
|
@ -1,17 +0,0 @@
|
||||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
|
||||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
||||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
|
||||||
|
|
||||||
#ifndef jprof_h___
|
|
||||||
#define jprof_h___
|
|
||||||
#include "nscore.h"
|
|
||||||
|
|
||||||
#ifdef _IMPL_JPPROF_API
|
|
||||||
#define JPROF_API(type) NS_EXPORT_(type)
|
|
||||||
#else
|
|
||||||
#define JPROF_API(type) NS_IMPORT_(type)
|
|
||||||
#endif
|
|
||||||
|
|
||||||
JPROF_API(void) setupProfilingStuff(void);
|
|
||||||
|
|
||||||
#endif /* jprof_h___ */
|
|
||||||
|
|
@ -1,790 +0,0 @@
|
||||||
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
|
|
||||||
// vim:cindent:sw=4:et:ts=8:
|
|
||||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
|
||||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
||||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
|
||||||
|
|
||||||
// The linux glibc hides part of sigaction if _POSIX_SOURCE is defined
|
|
||||||
#if defined(linux)
|
|
||||||
#undef _POSIX_SOURCE
|
|
||||||
#undef _SVID_SOURCE
|
|
||||||
#ifndef _GNU_SOURCE
|
|
||||||
#define _GNU_SOURCE
|
|
||||||
#endif
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#include <errno.h>
|
|
||||||
#if defined(linux)
|
|
||||||
#include <linux/rtc.h>
|
|
||||||
#include <pthread.h>
|
|
||||||
#endif
|
|
||||||
#include <unistd.h>
|
|
||||||
#include <fcntl.h>
|
|
||||||
#include <stdio.h>
|
|
||||||
#include <stdlib.h>
|
|
||||||
#include <signal.h>
|
|
||||||
#include <sys/time.h>
|
|
||||||
#include <sys/types.h>
|
|
||||||
#include <sys/ioctl.h>
|
|
||||||
#include <sys/stat.h>
|
|
||||||
#include <sys/syscall.h>
|
|
||||||
#include <ucontext.h>
|
|
||||||
#include <execinfo.h>
|
|
||||||
|
|
||||||
#include "libmalloc.h"
|
|
||||||
#include "jprof.h"
|
|
||||||
#include <string.h>
|
|
||||||
#include <errno.h>
|
|
||||||
#include <dlfcn.h>
|
|
||||||
|
|
||||||
// Must define before including jprof.h
|
|
||||||
void *moz_xmalloc(size_t size)
|
|
||||||
{
|
|
||||||
return malloc(size);
|
|
||||||
}
|
|
||||||
|
|
||||||
void moz_xfree(void *mem)
|
|
||||||
{
|
|
||||||
free(mem);
|
|
||||||
}
|
|
||||||
|
|
||||||
#ifdef NTO
|
|
||||||
#include <sys/link.h>
|
|
||||||
extern r_debug _r_debug;
|
|
||||||
#else
|
|
||||||
#include <link.h>
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#define USE_GLIBC_BACKTRACE 1
|
|
||||||
// To debug, use #define JPROF_STATIC
|
|
||||||
#define JPROF_STATIC static
|
|
||||||
|
|
||||||
static int gLogFD = -1;
|
|
||||||
static pthread_t main_thread;
|
|
||||||
|
|
||||||
static bool gIsSlave = false;
|
|
||||||
static int gFilenamePID;
|
|
||||||
|
|
||||||
static void startSignalCounter(unsigned long millisec);
|
|
||||||
static int enableRTCSignals(bool enable);
|
|
||||||
|
|
||||||
|
|
||||||
//----------------------------------------------------------------------
|
|
||||||
// replace use of atexit()
|
|
||||||
|
|
||||||
static void DumpAddressMap();
|
|
||||||
|
|
||||||
struct JprofShutdown {
|
|
||||||
JprofShutdown() {}
|
|
||||||
~JprofShutdown() {
|
|
||||||
DumpAddressMap();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
static void RegisterJprofShutdown() {
|
|
||||||
// This instanciates the dummy class above, and will trigger the class
|
|
||||||
// destructor when libxul is unloaded. This is equivalent to atexit(),
|
|
||||||
// but gracefully handles dlclose().
|
|
||||||
static JprofShutdown t;
|
|
||||||
}
|
|
||||||
|
|
||||||
#if defined(i386) || defined(_i386) || defined(__x86_64__)
|
|
||||||
JPROF_STATIC void CrawlStack(malloc_log_entry* me,
|
|
||||||
void* stack_top, void* top_instr_ptr)
|
|
||||||
{
|
|
||||||
#if USE_GLIBC_BACKTRACE
|
|
||||||
// This probably works on more than x86! But we need a way to get the
|
|
||||||
// top instruction pointer, which is kindof arch-specific
|
|
||||||
void *array[500];
|
|
||||||
int cnt, i;
|
|
||||||
u_long numpcs = 0;
|
|
||||||
|
|
||||||
// This is from glibc. A more generic version might use
|
|
||||||
// libunwind and/or CaptureStackBackTrace() on Windows
|
|
||||||
cnt = backtrace(&array[0],sizeof(array)/sizeof(array[0]));
|
|
||||||
|
|
||||||
// StackHook->JprofLog->CrawlStack
|
|
||||||
// Then we have sigaction, which replaced top_instr_ptr
|
|
||||||
array[3] = top_instr_ptr;
|
|
||||||
for (i = 3; i < cnt; i++)
|
|
||||||
{
|
|
||||||
me->pcs[numpcs++] = (char *) array[i];
|
|
||||||
}
|
|
||||||
me->numpcs = numpcs;
|
|
||||||
|
|
||||||
#else
|
|
||||||
// original code - this breaks on many platforms
|
|
||||||
void **bp;
|
|
||||||
#if defined(__i386)
|
|
||||||
__asm__( "movl %%ebp, %0" : "=g"(bp));
|
|
||||||
#elif defined(__x86_64__)
|
|
||||||
__asm__( "movq %%rbp, %0" : "=g"(bp));
|
|
||||||
#else
|
|
||||||
// It would be nice if this worked uniformly, but at least on i386 and
|
|
||||||
// x86_64, it stopped working with gcc 4.1, because it points to the
|
|
||||||
// end of the saved registers instead of the start.
|
|
||||||
bp = __builtin_frame_address(0);
|
|
||||||
#endif
|
|
||||||
u_long numpcs = 0;
|
|
||||||
bool tracing = false;
|
|
||||||
|
|
||||||
me->pcs[numpcs++] = (char*) top_instr_ptr;
|
|
||||||
|
|
||||||
while (numpcs < MAX_STACK_CRAWL) {
|
|
||||||
void** nextbp = (void**) *bp++;
|
|
||||||
void* pc = *bp;
|
|
||||||
if (nextbp < bp) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
if (tracing) {
|
|
||||||
// Skip the signal handling.
|
|
||||||
me->pcs[numpcs++] = (char*) pc;
|
|
||||||
}
|
|
||||||
else if (pc == top_instr_ptr) {
|
|
||||||
tracing = true;
|
|
||||||
}
|
|
||||||
bp = nextbp;
|
|
||||||
}
|
|
||||||
me->numpcs = numpcs;
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
//----------------------------------------------------------------------
|
|
||||||
|
|
||||||
static int rtcHz;
|
|
||||||
static int rtcFD = -1;
|
|
||||||
static bool circular = false;
|
|
||||||
|
|
||||||
#if defined(linux) || defined(NTO)
|
|
||||||
static void DumpAddressMap()
|
|
||||||
{
|
|
||||||
// Turn off the timer so we don't get interrupts during shutdown
|
|
||||||
#if defined(linux)
|
|
||||||
if (rtcHz) {
|
|
||||||
enableRTCSignals(false);
|
|
||||||
} else
|
|
||||||
#endif
|
|
||||||
{
|
|
||||||
startSignalCounter(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
char filename[2048];
|
|
||||||
if (gIsSlave)
|
|
||||||
snprintf(filename, sizeof(filename), "%s-%d", M_MAPFILE, gFilenamePID);
|
|
||||||
else
|
|
||||||
snprintf(filename, sizeof(filename), "%s", M_MAPFILE);
|
|
||||||
|
|
||||||
int mfd = open(filename, O_CREAT|O_WRONLY|O_TRUNC, 0666);
|
|
||||||
if (mfd >= 0) {
|
|
||||||
malloc_map_entry mme;
|
|
||||||
link_map* map = _r_debug.r_map;
|
|
||||||
while (nullptr != map) {
|
|
||||||
if (map->l_name && *map->l_name) {
|
|
||||||
mme.nameLen = strlen(map->l_name);
|
|
||||||
mme.address = map->l_addr;
|
|
||||||
write(mfd, &mme, sizeof(mme));
|
|
||||||
write(mfd, map->l_name, mme.nameLen);
|
|
||||||
#if 0
|
|
||||||
write(1, map->l_name, mme.nameLen);
|
|
||||||
write(1, "\n", 1);
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
map = map->l_next;
|
|
||||||
}
|
|
||||||
close(mfd);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
static bool was_paused = true;
|
|
||||||
|
|
||||||
JPROF_STATIC void JprofBufferDump();
|
|
||||||
JPROF_STATIC void JprofBufferClear();
|
|
||||||
|
|
||||||
static void ClearProfilingHook(int signum)
|
|
||||||
{
|
|
||||||
if (circular) {
|
|
||||||
JprofBufferClear();
|
|
||||||
puts("Jprof: cleared circular buffer.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static void EndProfilingHook(int signum)
|
|
||||||
{
|
|
||||||
if (circular)
|
|
||||||
JprofBufferDump();
|
|
||||||
|
|
||||||
DumpAddressMap();
|
|
||||||
was_paused = true;
|
|
||||||
puts("Jprof: profiling paused.");
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
//----------------------------------------------------------------------
|
|
||||||
// proper usage would be a template, including the function to find the
|
|
||||||
// size of an entry, or include a size header explicitly to each entry.
|
|
||||||
#if defined(linux)
|
|
||||||
#define DUMB_LOCK() pthread_mutex_lock(&mutex);
|
|
||||||
#define DUMB_UNLOCK() pthread_mutex_unlock(&mutex);
|
|
||||||
#else
|
|
||||||
#define DUMB_LOCK() FIXME()
|
|
||||||
#define DUMB_UNLOCK() FIXME()
|
|
||||||
#endif
|
|
||||||
|
|
||||||
|
|
||||||
class DumbCircularBuffer
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
DumbCircularBuffer(size_t init_buffer_size) {
|
|
||||||
used = 0;
|
|
||||||
buffer_size = init_buffer_size;
|
|
||||||
buffer = (unsigned char *) malloc(buffer_size);
|
|
||||||
head = tail = buffer;
|
|
||||||
|
|
||||||
#if defined(linux)
|
|
||||||
pthread_mutexattr_t mAttr;
|
|
||||||
pthread_mutexattr_settype(&mAttr, PTHREAD_MUTEX_RECURSIVE_NP);
|
|
||||||
pthread_mutex_init(&mutex, &mAttr);
|
|
||||||
pthread_mutexattr_destroy(&mAttr);
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
~DumbCircularBuffer() {
|
|
||||||
free(buffer);
|
|
||||||
#if defined(linux)
|
|
||||||
pthread_mutex_destroy (&mutex);
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
void clear() {
|
|
||||||
DUMB_LOCK();
|
|
||||||
head = tail;
|
|
||||||
used = 0;
|
|
||||||
DUMB_UNLOCK();
|
|
||||||
}
|
|
||||||
|
|
||||||
bool empty() {
|
|
||||||
return head == tail;
|
|
||||||
}
|
|
||||||
|
|
||||||
size_t space_available() {
|
|
||||||
size_t result;
|
|
||||||
DUMB_LOCK();
|
|
||||||
if (tail > head)
|
|
||||||
result = buffer_size - (tail-head) - 1;
|
|
||||||
else
|
|
||||||
result = head-tail - 1;
|
|
||||||
DUMB_UNLOCK();
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
void drop(size_t size) {
|
|
||||||
// assumes correctness!
|
|
||||||
DUMB_LOCK();
|
|
||||||
head += size;
|
|
||||||
if (head >= &buffer[buffer_size])
|
|
||||||
head -= buffer_size;
|
|
||||||
used--;
|
|
||||||
DUMB_UNLOCK();
|
|
||||||
}
|
|
||||||
|
|
||||||
bool insert(void *data, size_t size) {
|
|
||||||
// can fail if not enough space in the entire buffer
|
|
||||||
DUMB_LOCK();
|
|
||||||
if (space_available() < size)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
size_t max_without_wrap = &buffer[buffer_size] - tail;
|
|
||||||
size_t initial = size > max_without_wrap ? max_without_wrap : size;
|
|
||||||
#if DEBUG_CIRCULAR
|
|
||||||
fprintf(stderr,"insert(%d): max_without_wrap %d, size %d, initial %d\n",used,max_without_wrap,size,initial);
|
|
||||||
#endif
|
|
||||||
memcpy(tail,data,initial);
|
|
||||||
tail += initial;
|
|
||||||
data = ((char *)data)+initial;
|
|
||||||
size -= initial;
|
|
||||||
if (size != 0) {
|
|
||||||
#if DEBUG_CIRCULAR
|
|
||||||
fprintf(stderr,"wrapping by %d bytes\n",size);
|
|
||||||
#endif
|
|
||||||
memcpy(buffer,data,size);
|
|
||||||
tail = &(((unsigned char *)buffer)[size]);
|
|
||||||
}
|
|
||||||
|
|
||||||
used++;
|
|
||||||
DUMB_UNLOCK();
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// for external access to the buffer (saving)
|
|
||||||
void lock() {
|
|
||||||
DUMB_LOCK();
|
|
||||||
}
|
|
||||||
|
|
||||||
void unlock() {
|
|
||||||
DUMB_UNLOCK();
|
|
||||||
}
|
|
||||||
|
|
||||||
// XXX These really shouldn't be public...
|
|
||||||
unsigned char *head;
|
|
||||||
unsigned char *tail;
|
|
||||||
unsigned int used;
|
|
||||||
unsigned char *buffer;
|
|
||||||
size_t buffer_size;
|
|
||||||
|
|
||||||
private:
|
|
||||||
pthread_mutex_t mutex;
|
|
||||||
};
|
|
||||||
|
|
||||||
class DumbCircularBuffer *JprofBuffer;
|
|
||||||
|
|
||||||
JPROF_STATIC void
|
|
||||||
JprofBufferInit(size_t size)
|
|
||||||
{
|
|
||||||
JprofBuffer = new DumbCircularBuffer(size);
|
|
||||||
}
|
|
||||||
|
|
||||||
JPROF_STATIC void
|
|
||||||
JprofBufferClear()
|
|
||||||
{
|
|
||||||
fprintf(stderr,"Told to clear JPROF circular buffer\n");
|
|
||||||
JprofBuffer->clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
JPROF_STATIC size_t
|
|
||||||
JprofEntrySizeof(malloc_log_entry *me)
|
|
||||||
{
|
|
||||||
return offsetof(malloc_log_entry, pcs) + me->numpcs*sizeof(char*);
|
|
||||||
}
|
|
||||||
|
|
||||||
JPROF_STATIC void
|
|
||||||
JprofBufferAppend(malloc_log_entry *me)
|
|
||||||
{
|
|
||||||
size_t size = JprofEntrySizeof(me);
|
|
||||||
|
|
||||||
do {
|
|
||||||
while (JprofBuffer->space_available() < size &&
|
|
||||||
JprofBuffer->used > 0) {
|
|
||||||
#if DEBUG_CIRCULAR
|
|
||||||
fprintf(stderr,"dropping entry: %d in use, %d free, need %d, size_to_free = %d\n",
|
|
||||||
JprofBuffer->used,JprofBuffer->space_available(),size,JprofEntrySizeof((malloc_log_entry *) JprofBuffer->head));
|
|
||||||
#endif
|
|
||||||
JprofBuffer->drop(JprofEntrySizeof((malloc_log_entry *) JprofBuffer->head));
|
|
||||||
}
|
|
||||||
if (JprofBuffer->space_available() < size)
|
|
||||||
return;
|
|
||||||
|
|
||||||
} while (!JprofBuffer->insert(me,size));
|
|
||||||
}
|
|
||||||
|
|
||||||
JPROF_STATIC void
|
|
||||||
JprofBufferDump()
|
|
||||||
{
|
|
||||||
JprofBuffer->lock();
|
|
||||||
#if DEBUG_CIRCULAR
|
|
||||||
fprintf(stderr,"dumping JP_CIRCULAR buffer, %d of %d bytes\n",
|
|
||||||
JprofBuffer->tail > JprofBuffer->head ?
|
|
||||||
JprofBuffer->tail - JprofBuffer->head :
|
|
||||||
JprofBuffer->buffer_size + JprofBuffer->tail - JprofBuffer->head,
|
|
||||||
JprofBuffer->buffer_size);
|
|
||||||
#endif
|
|
||||||
if (JprofBuffer->tail >= JprofBuffer->head) {
|
|
||||||
write(gLogFD, JprofBuffer->head, JprofBuffer->tail - JprofBuffer->head);
|
|
||||||
} else {
|
|
||||||
write(gLogFD, JprofBuffer->head, &(JprofBuffer->buffer[JprofBuffer->buffer_size]) - JprofBuffer->head);
|
|
||||||
write(gLogFD, JprofBuffer->buffer, JprofBuffer->tail - JprofBuffer->buffer);
|
|
||||||
}
|
|
||||||
JprofBuffer->clear();
|
|
||||||
JprofBuffer->unlock();
|
|
||||||
}
|
|
||||||
|
|
||||||
//----------------------------------------------------------------------
|
|
||||||
|
|
||||||
JPROF_STATIC void
|
|
||||||
JprofLog(u_long aTime, void* stack_top, void* top_instr_ptr)
|
|
||||||
{
|
|
||||||
// Static is simply to make debugging tolerable
|
|
||||||
static malloc_log_entry me;
|
|
||||||
|
|
||||||
me.delTime = aTime;
|
|
||||||
me.thread = syscall(SYS_gettid); //gettid();
|
|
||||||
if (was_paused) {
|
|
||||||
me.flags = JP_FIRST_AFTER_PAUSE;
|
|
||||||
was_paused = 0;
|
|
||||||
} else {
|
|
||||||
me.flags = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
CrawlStack(&me, stack_top, top_instr_ptr);
|
|
||||||
|
|
||||||
#ifndef NTO
|
|
||||||
if (circular) {
|
|
||||||
JprofBufferAppend(&me);
|
|
||||||
} else {
|
|
||||||
write(gLogFD, &me, JprofEntrySizeof(&me));
|
|
||||||
}
|
|
||||||
#else
|
|
||||||
printf("Neutrino is missing the pcs member of malloc_log_entry!! \n");
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
static int realTime;
|
|
||||||
|
|
||||||
/* Lets interrupt at 10 Hz. This is so my log files don't get too large.
|
|
||||||
* This can be changed to a faster value latter. This timer is not
|
|
||||||
* programmed to reset, even though it is capable of doing so. This is
|
|
||||||
* to keep from getting interrupts from inside of the handler.
|
|
||||||
*/
|
|
||||||
static void startSignalCounter(unsigned long millisec)
|
|
||||||
{
|
|
||||||
struct itimerval tvalue;
|
|
||||||
|
|
||||||
tvalue.it_interval.tv_sec = 0;
|
|
||||||
tvalue.it_interval.tv_usec = 0;
|
|
||||||
tvalue.it_value.tv_sec = millisec/1000;
|
|
||||||
tvalue.it_value.tv_usec = (millisec%1000)*1000;
|
|
||||||
|
|
||||||
if (realTime) {
|
|
||||||
setitimer(ITIMER_REAL, &tvalue, nullptr);
|
|
||||||
} else {
|
|
||||||
setitimer(ITIMER_PROF, &tvalue, nullptr);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static long timerMilliSec = 50;
|
|
||||||
|
|
||||||
#if defined(linux)
|
|
||||||
static int setupRTCSignals(int hz, struct sigaction *sap)
|
|
||||||
{
|
|
||||||
/* global */ rtcFD = open("/dev/rtc", O_RDONLY);
|
|
||||||
if (rtcFD < 0) {
|
|
||||||
perror("JPROF_RTC setup: open(\"/dev/rtc\", O_RDONLY)");
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (sigaction(SIGIO, sap, nullptr) == -1) {
|
|
||||||
perror("JPROF_RTC setup: sigaction(SIGIO)");
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (ioctl(rtcFD, RTC_IRQP_SET, hz) == -1) {
|
|
||||||
perror("JPROF_RTC setup: ioctl(/dev/rtc, RTC_IRQP_SET, $JPROF_RTC_HZ)");
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (ioctl(rtcFD, RTC_PIE_ON, 0) == -1) {
|
|
||||||
perror("JPROF_RTC setup: ioctl(/dev/rtc, RTC_PIE_ON)");
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (fcntl(rtcFD, F_SETSIG, 0) == -1) {
|
|
||||||
perror("JPROF_RTC setup: fcntl(/dev/rtc, F_SETSIG, 0)");
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (fcntl(rtcFD, F_SETOWN, getpid()) == -1) {
|
|
||||||
perror("JPROF_RTC setup: fcntl(/dev/rtc, F_SETOWN, getpid())");
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int enableRTCSignals(bool enable)
|
|
||||||
{
|
|
||||||
static bool enabled = false;
|
|
||||||
if (enabled == enable) {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
enabled = enable;
|
|
||||||
|
|
||||||
int flags = fcntl(rtcFD, F_GETFL);
|
|
||||||
if (flags < 0) {
|
|
||||||
perror("JPROF_RTC setup: fcntl(/dev/rtc, F_GETFL)");
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (enable) {
|
|
||||||
flags |= FASYNC;
|
|
||||||
} else {
|
|
||||||
flags &= ~FASYNC;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (fcntl(rtcFD, F_SETFL, flags) == -1) {
|
|
||||||
if (enable) {
|
|
||||||
perror("JPROF_RTC setup: fcntl(/dev/rtc, F_SETFL, flags | FASYNC)");
|
|
||||||
} else {
|
|
||||||
perror("JPROF_RTC setup: fcntl(/dev/rtc, F_SETFL, flags & ~FASYNC)");
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
JPROF_STATIC void StackHook(
|
|
||||||
int signum,
|
|
||||||
siginfo_t *info,
|
|
||||||
void *ucontext)
|
|
||||||
{
|
|
||||||
static struct timeval tFirst;
|
|
||||||
static int first=1;
|
|
||||||
size_t millisec = 0;
|
|
||||||
|
|
||||||
#if defined(linux)
|
|
||||||
if (rtcHz && pthread_self() != main_thread) {
|
|
||||||
// Only collect stack data on the main thread, for now.
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
if(first && !(first=0)) {
|
|
||||||
puts("Jprof: received first signal");
|
|
||||||
#if defined(linux)
|
|
||||||
if (rtcHz) {
|
|
||||||
enableRTCSignals(true);
|
|
||||||
} else
|
|
||||||
#endif
|
|
||||||
{
|
|
||||||
gettimeofday(&tFirst, 0);
|
|
||||||
millisec = 0;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
#if defined(linux)
|
|
||||||
if (rtcHz) {
|
|
||||||
enableRTCSignals(true);
|
|
||||||
} else
|
|
||||||
#endif
|
|
||||||
{
|
|
||||||
struct timeval tNow;
|
|
||||||
gettimeofday(&tNow, 0);
|
|
||||||
double usec = 1e6*(tNow.tv_sec - tFirst.tv_sec);
|
|
||||||
usec += (tNow.tv_usec - tFirst.tv_usec);
|
|
||||||
millisec = static_cast<size_t>(usec*1e-3);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
gregset_t &gregs = ((ucontext_t*)ucontext)->uc_mcontext.gregs;
|
|
||||||
#ifdef __x86_64__
|
|
||||||
JprofLog(millisec, (void*)gregs[REG_RSP], (void*)gregs[REG_RIP]);
|
|
||||||
#else
|
|
||||||
JprofLog(millisec, (void*)gregs[REG_ESP], (void*)gregs[REG_EIP]);
|
|
||||||
#endif
|
|
||||||
|
|
||||||
if (!rtcHz)
|
|
||||||
startSignalCounter(timerMilliSec);
|
|
||||||
}
|
|
||||||
|
|
||||||
NS_EXPORT_(void) setupProfilingStuff(void)
|
|
||||||
{
|
|
||||||
static int gFirstTime = 1;
|
|
||||||
char filename[2048]; // XXX fix
|
|
||||||
|
|
||||||
if(gFirstTime && !(gFirstTime=0)) {
|
|
||||||
int startTimer = 1;
|
|
||||||
int doNotStart = 1;
|
|
||||||
int firstDelay = 0;
|
|
||||||
int append = O_TRUNC;
|
|
||||||
char *tst = getenv("JPROF_FLAGS");
|
|
||||||
|
|
||||||
/* Options from JPROF_FLAGS environment variable:
|
|
||||||
* JP_DEFER -> Wait for a SIGPROF (or SIGALRM, if JP_REALTIME
|
|
||||||
* is set) from userland before starting
|
|
||||||
* to generate them internally
|
|
||||||
* JP_START -> Install the signal handler
|
|
||||||
* JP_PERIOD -> Time between profiler ticks
|
|
||||||
* JP_FIRST -> Extra delay before starting
|
|
||||||
* JP_REALTIME -> Take stack traces in intervals of real time
|
|
||||||
* rather than time used by the process (and the
|
|
||||||
* system for the process). This is useful for
|
|
||||||
* finding time spent by the X server.
|
|
||||||
* JP_APPEND -> Append to jprof-log rather than overwriting it.
|
|
||||||
* This is somewhat risky since it depends on the
|
|
||||||
* address map staying constant across multiple runs.
|
|
||||||
* JP_FILENAME -> base filename to use when saving logs. Note that
|
|
||||||
* this does not affect the mapfile.
|
|
||||||
* JP_CIRCULAR -> use a circular buffer of size N, write/clear on SIGUSR1
|
|
||||||
*
|
|
||||||
* JPROF_SLAVE is set if this is not the first process.
|
|
||||||
*/
|
|
||||||
|
|
||||||
circular = false;
|
|
||||||
|
|
||||||
if(tst) {
|
|
||||||
if(strstr(tst, "JP_DEFER"))
|
|
||||||
{
|
|
||||||
doNotStart = 0;
|
|
||||||
startTimer = 0;
|
|
||||||
}
|
|
||||||
if(strstr(tst, "JP_START")) doNotStart = 0;
|
|
||||||
if(strstr(tst, "JP_REALTIME")) realTime = 1;
|
|
||||||
if(strstr(tst, "JP_APPEND")) append = O_APPEND;
|
|
||||||
|
|
||||||
char *delay = strstr(tst,"JP_PERIOD=");
|
|
||||||
if(delay) {
|
|
||||||
double tmp = strtod(delay+strlen("JP_PERIOD="), nullptr);
|
|
||||||
if (tmp>=1e-3) {
|
|
||||||
timerMilliSec = static_cast<unsigned long>(1000 * tmp);
|
|
||||||
} else {
|
|
||||||
fprintf(stderr,
|
|
||||||
"JP_PERIOD of %g less than 0.001 (1ms), using 1ms\n",
|
|
||||||
tmp);
|
|
||||||
timerMilliSec = 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
char *circular_op = strstr(tst,"JP_CIRCULAR=");
|
|
||||||
if(circular_op) {
|
|
||||||
size_t size = atol(circular_op+strlen("JP_CIRCULAR="));
|
|
||||||
if (size < 1000) {
|
|
||||||
fprintf(stderr,
|
|
||||||
"JP_CIRCULAR of %lu less than 1000, using 10000\n",
|
|
||||||
(unsigned long) size);
|
|
||||||
size = 10000;
|
|
||||||
}
|
|
||||||
JprofBufferInit(size);
|
|
||||||
fprintf(stderr,"JP_CIRCULAR buffer of %lu bytes\n", (unsigned long) size);
|
|
||||||
circular = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
char *first = strstr(tst, "JP_FIRST=");
|
|
||||||
if(first) {
|
|
||||||
firstDelay = atol(first+strlen("JP_FIRST="));
|
|
||||||
}
|
|
||||||
|
|
||||||
char *rtc = strstr(tst, "JP_RTC_HZ=");
|
|
||||||
if (rtc) {
|
|
||||||
#if defined(linux)
|
|
||||||
rtcHz = atol(rtc+strlen("JP_RTC_HZ="));
|
|
||||||
timerMilliSec = 0; /* This makes JP_FIRST work right. */
|
|
||||||
realTime = 1; /* It's the _R_TC and all. ;) */
|
|
||||||
|
|
||||||
#define IS_POWER_OF_TWO(x) (((x) & ((x) - 1)) == 0)
|
|
||||||
|
|
||||||
if (!IS_POWER_OF_TWO(rtcHz) || rtcHz < 2) {
|
|
||||||
fprintf(stderr, "JP_RTC_HZ must be power of two and >= 2, "
|
|
||||||
"but %d was provided; using default of 2048\n",
|
|
||||||
rtcHz);
|
|
||||||
rtcHz = 2048;
|
|
||||||
}
|
|
||||||
#else
|
|
||||||
fputs("JP_RTC_HZ found, but RTC profiling only supported on "
|
|
||||||
"Linux!\n", stderr);
|
|
||||||
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
const char *f = strstr(tst,"JP_FILENAME=");
|
|
||||||
if (f)
|
|
||||||
f = f + strlen("JP_FILENAME=");
|
|
||||||
else
|
|
||||||
f = M_LOGFILE;
|
|
||||||
|
|
||||||
char *is_slave = getenv("JPROF_SLAVE");
|
|
||||||
if (!is_slave)
|
|
||||||
setenv("JPROF_SLAVE","", 0);
|
|
||||||
gIsSlave = !!is_slave;
|
|
||||||
|
|
||||||
gFilenamePID = syscall(SYS_gettid); //gettid();
|
|
||||||
if (is_slave)
|
|
||||||
snprintf(filename,sizeof(filename),"%s-%d",f,gFilenamePID);
|
|
||||||
else
|
|
||||||
snprintf(filename,sizeof(filename),"%s",f);
|
|
||||||
|
|
||||||
// XXX FIX! inherit current capture state!
|
|
||||||
}
|
|
||||||
|
|
||||||
if(!doNotStart) {
|
|
||||||
|
|
||||||
if(gLogFD<0) {
|
|
||||||
gLogFD = open(filename, O_CREAT | O_WRONLY | append, 0666);
|
|
||||||
if(gLogFD<0) {
|
|
||||||
fprintf(stderr, "Unable to create " M_LOGFILE);
|
|
||||||
perror(":");
|
|
||||||
} else {
|
|
||||||
struct sigaction action;
|
|
||||||
sigset_t mset;
|
|
||||||
|
|
||||||
// Dump out the address map when we terminate
|
|
||||||
RegisterJprofShutdown();
|
|
||||||
|
|
||||||
main_thread = pthread_self();
|
|
||||||
//fprintf(stderr,"jprof: main_thread = %u\n",
|
|
||||||
// (unsigned int)main_thread);
|
|
||||||
|
|
||||||
// FIX! probably should block these against each other
|
|
||||||
// Very unlikely.
|
|
||||||
sigemptyset(&mset);
|
|
||||||
action.sa_handler = nullptr;
|
|
||||||
action.sa_sigaction = StackHook;
|
|
||||||
action.sa_mask = mset;
|
|
||||||
action.sa_flags = SA_RESTART | SA_SIGINFO;
|
|
||||||
#if defined(linux)
|
|
||||||
if (rtcHz) {
|
|
||||||
if (!setupRTCSignals(rtcHz, &action)) {
|
|
||||||
fputs("jprof: Error initializing RTC, NOT "
|
|
||||||
"profiling\n", stderr);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!rtcHz || firstDelay != 0)
|
|
||||||
#endif
|
|
||||||
{
|
|
||||||
if (realTime) {
|
|
||||||
sigaction(SIGALRM, &action, nullptr);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// enable PROF in all cases to simplify JP_DEFER/pause/restart
|
|
||||||
sigaction(SIGPROF, &action, nullptr);
|
|
||||||
|
|
||||||
// make it so a SIGUSR1 will stop the profiling
|
|
||||||
// Note: It currently does not close the logfile.
|
|
||||||
// This could be configurable (so that it could
|
|
||||||
// later be reopened).
|
|
||||||
|
|
||||||
struct sigaction stop_action;
|
|
||||||
stop_action.sa_handler = EndProfilingHook;
|
|
||||||
stop_action.sa_mask = mset;
|
|
||||||
stop_action.sa_flags = SA_RESTART;
|
|
||||||
sigaction(SIGUSR1, &stop_action, nullptr);
|
|
||||||
|
|
||||||
// make it so a SIGUSR2 will clear the circular buffer
|
|
||||||
|
|
||||||
stop_action.sa_handler = ClearProfilingHook;
|
|
||||||
stop_action.sa_mask = mset;
|
|
||||||
stop_action.sa_flags = SA_RESTART;
|
|
||||||
sigaction(SIGUSR2, &stop_action, nullptr);
|
|
||||||
|
|
||||||
printf("Jprof: Initialized signal handler and set "
|
|
||||||
"timer for %lu %s, %d s "
|
|
||||||
"initial delay\n",
|
|
||||||
rtcHz ? rtcHz : timerMilliSec,
|
|
||||||
rtcHz ? "Hz" : "ms",
|
|
||||||
firstDelay);
|
|
||||||
|
|
||||||
if(startTimer) {
|
|
||||||
#if defined(linux)
|
|
||||||
/* If we have an initial delay we can just use
|
|
||||||
startSignalCounter to set up a timer to fire the
|
|
||||||
first stackHook after that delay. When that happens
|
|
||||||
we'll go and switch to RTC profiling. */
|
|
||||||
if (rtcHz && firstDelay == 0) {
|
|
||||||
puts("Jprof: enabled RTC signals");
|
|
||||||
enableRTCSignals(true);
|
|
||||||
} else
|
|
||||||
#endif
|
|
||||||
{
|
|
||||||
puts("Jprof: started timer");
|
|
||||||
startSignalCounter(firstDelay*1000 + timerMilliSec);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
printf("setupProfilingStuff() called multiple times\n");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,45 +0,0 @@
|
||||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
|
||||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
||||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
|
||||||
|
|
||||||
#ifndef libmalloc_h___
|
|
||||||
#define libmalloc_h___
|
|
||||||
|
|
||||||
#include <sys/types.h>
|
|
||||||
#include <malloc.h>
|
|
||||||
|
|
||||||
#ifdef __cplusplus
|
|
||||||
extern "C" {
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#include "config.h"
|
|
||||||
|
|
||||||
typedef unsigned long u_long;
|
|
||||||
|
|
||||||
// For me->flags
|
|
||||||
#define JP_FIRST_AFTER_PAUSE 1
|
|
||||||
|
|
||||||
// Format of a jprof log entry. This is what's written out to the
|
|
||||||
// "jprof-log" file.
|
|
||||||
// It's called malloc_log_entry because the history of jprof is that
|
|
||||||
// it's a modified version of tracemalloc.
|
|
||||||
struct malloc_log_entry {
|
|
||||||
u_long delTime;
|
|
||||||
u_long numpcs;
|
|
||||||
unsigned int flags;
|
|
||||||
int thread;
|
|
||||||
char* pcs[MAX_STACK_CRAWL];
|
|
||||||
};
|
|
||||||
|
|
||||||
// Format of a malloc map entry; after this struct is nameLen+1 bytes of
|
|
||||||
// name data.
|
|
||||||
struct malloc_map_entry {
|
|
||||||
u_long nameLen;
|
|
||||||
u_long address; // base address
|
|
||||||
};
|
|
||||||
|
|
||||||
#ifdef __cplusplus
|
|
||||||
} /* end of extern "C" */
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#endif /* libmalloc_h___ */
|
|
||||||
|
|
@ -1,17 +0,0 @@
|
||||||
# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
|
|
||||||
# vim: set filetype=python:
|
|
||||||
# This Source Code Form is subject to the terms of the Mozilla Public
|
|
||||||
# License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
||||||
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
|
||||||
|
|
||||||
EXPORTS += [
|
|
||||||
'jprof.h',
|
|
||||||
]
|
|
||||||
|
|
||||||
SOURCES += [
|
|
||||||
'libmalloc.cpp',
|
|
||||||
]
|
|
||||||
|
|
||||||
SharedLibrary('jprof')
|
|
||||||
|
|
||||||
DEFINES['_IMPL_JPROF_API'] = True
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue