Bug 1014341 (part 1) - Remove trace-malloc. r=dbaron,glandium.

--HG--
extra : rebase_source : 771710c5427141d738eef112fab00951eb8e20e3
This commit is contained in:
Nicholas Nethercote 2015-01-07 16:13:03 -08:00
parent e9f49ca06a
commit 97b5d348cc
60 changed files with 18 additions and 25852 deletions

View File

@ -492,7 +492,6 @@ def run_app(harness_root_dir, manifest_rdf, harness_options,
env['MOZ_DISABLE_NONLOCAL_CONNECTIONS'] = '1'
env['MOZ_NO_REMOTE'] = '1'
env['XPCOM_DEBUG_BREAK'] = 'stack'
env['NS_TRACE_MALLOC_DISABLE_STACKS'] = '1'
env.update(extra_environment)
if norun:
cmdargs.append("-no-remote")

View File

@ -504,7 +504,6 @@ class Automation(object):
env['GNOME_DISABLE_CRASH_DIALOG'] = '1'
env['XRE_NO_WINDOWS_CRASH_DIALOG'] = '1'
env['NS_TRACE_MALLOC_DISABLE_STACKS'] = '1'
# Set WebRTC logging in case it is not set yet
env.setdefault('NSPR_LOG_MODULES', 'signaling:5,mtransport:5,datachannel:5,jsep:5,MediaPipelineFactory:5')

View File

@ -330,7 +330,6 @@ def environment(xrePath, env=None, crashreporter=True, debugger=False, dmdPath=N
# crashreporter
env['GNOME_DISABLE_CRASH_DIALOG'] = '1'
env['XRE_NO_WINDOWS_CRASH_DIALOG'] = '1'
env['NS_TRACE_MALLOC_DISABLE_STACKS'] = '1'
if crashreporter and not debugger:
env['MOZ_CRASHREPORTER_NO_REPORT'] = '1'

View File

@ -228,17 +228,17 @@ endif
endif
#
# Handle trace-malloc and DMD in optimized builds.
# Handle DMD in optimized builds.
# No opt to give sane callstacks.
#
ifneq (,$(NS_TRACE_MALLOC)$(MOZ_DMD))
ifdef MOZ_DMD
MOZ_OPTIMIZE_FLAGS=-Zi -Od -UDEBUG -DNDEBUG
ifdef HAVE_64BIT_BUILD
OS_LDFLAGS = -DEBUG -OPT:REF,ICF
else
OS_LDFLAGS = -DEBUG -OPT:REF
endif
endif # NS_TRACE_MALLOC || MOZ_DMD
endif # MOZ_DMD
endif # MOZ_DEBUG
@ -421,20 +421,20 @@ ifeq ($(OS_ARCH)_$(GNU_CC),WINNT_)
#//------------------------------------------------------------------------
ifdef USE_STATIC_LIBS
RTL_FLAGS=-MT # Statically linked multithreaded RTL
ifneq (,$(MOZ_DEBUG)$(NS_TRACE_MALLOC))
ifdef MOZ_DEBUG
ifndef MOZ_NO_DEBUG_RTL
RTL_FLAGS=-MTd # Statically linked multithreaded MSVC4.0 debug RTL
endif
endif # MOZ_DEBUG || NS_TRACE_MALLOC
endif # MOZ_DEBUG
else # !USE_STATIC_LIBS
RTL_FLAGS=-MD # Dynamically linked, multithreaded RTL
ifneq (,$(MOZ_DEBUG)$(NS_TRACE_MALLOC))
ifdef MOZ_DEBUG
ifndef MOZ_NO_DEBUG_RTL
RTL_FLAGS=-MDd # Dynamically linked, multithreaded MSVC4.0 debug RTL
endif
endif # MOZ_DEBUG || NS_TRACE_MALLOC
endif # MOZ_DEBUG
endif # USE_STATIC_LIBS
endif # WINNT && !GNU_CC

View File

@ -7046,21 +7046,6 @@ if test -n "$MOZ_DEBUG"; then
AC_DEFINE(MOZ_DUMP_PAINTING)
fi
dnl ========================================================
dnl = Enable trace malloc
dnl ========================================================
NS_TRACE_MALLOC=${MOZ_TRACE_MALLOC}
MOZ_ARG_ENABLE_BOOL(trace-malloc,
[ --enable-trace-malloc Enable malloc tracing; also disables DMD and jemalloc],
NS_TRACE_MALLOC=1,
NS_TRACE_MALLOC= )
if test "$NS_TRACE_MALLOC"; then
# Please, Mr. Linker Man, don't take away our symbol names
MOZ_COMPONENTS_VERSION_SCRIPT_LDFLAGS=
AC_DEFINE(NS_TRACE_MALLOC)
fi
AC_SUBST(NS_TRACE_MALLOC)
dnl ========================================================
dnl = Enable DMD
dnl ========================================================
@ -7070,11 +7055,6 @@ MOZ_ARG_ENABLE_BOOL(dmd,
MOZ_DMD=1,
MOZ_DMD= )
dnl The two options are conflicting. Fails the configure to alert the user.
if test "$NS_TRACE_MALLOC" -a "$MOZ_DMD"; then
AC_MSG_ERROR([--enable-trace-malloc and --enable-dmd are conflicting options])
fi
if test "$MOZ_DMD"; then
AC_DEFINE(MOZ_DMD)
@ -7096,10 +7076,6 @@ MOZ_ARG_ENABLE_BOOL(jemalloc,
MOZ_MEMORY=1,
MOZ_MEMORY=)
if test "$NS_TRACE_MALLOC"; then
MOZ_MEMORY=
fi
case "${OS_TARGET}" in
Android|WINNT|Darwin)
MOZ_GLUE_IN_PROGRAM=
@ -7122,11 +7098,6 @@ MOZ_ARG_ENABLE_BOOL(replace-malloc,
MOZ_REPLACE_MALLOC=1,
MOZ_REPLACE_MALLOC= )
dnl The two options are conflicting. Fails the configure to alert the user.
if test "$NS_TRACE_MALLOC" -a "$MOZ_REPLACE_MALLOC"; then
AC_MSG_ERROR([--enable-trace-malloc and --enable-replace-malloc are conflicting options])
fi
if test -n "$MOZ_REPLACE_MALLOC" -a -z "$MOZ_MEMORY"; then
dnl We don't want to enable jemalloc unconditionally because it may be a
dnl deliberate choice not to enable it (bug 702250, for instance)
@ -7601,9 +7572,9 @@ if test -z "$SKIP_LIBRARY_CHECKS"; then
AC_LANG_RESTORE
fi
# Demangle only for debug or trace-malloc or DMD builds
# Demangle only for debug or DMD builds
MOZ_DEMANGLE_SYMBOLS=
if test "$HAVE_DEMANGLE" && test "$MOZ_DEBUG" -o "$NS_TRACE_MALLOC" -o "$MOZ_DMD"; then
if test "$HAVE_DEMANGLE" && test "$MOZ_DEBUG" -o "$MOZ_DMD"; then
MOZ_DEMANGLE_SYMBOLS=1
AC_DEFINE(MOZ_DEMANGLE_SYMBOLS)
fi
@ -8423,7 +8394,7 @@ if test -n "$MOZ_DEVICES"; then
fi
dnl ========================================================
if test "$MOZ_DEBUG" -o "$NS_TRACE_MALLOC" -o "$MOZ_DMD"; then
if test "$MOZ_DEBUG" -o "$MOZ_DMD"; then
MOZ_COMPONENTS_VERSION_SCRIPT_LDFLAGS=
fi

View File

@ -1087,177 +1087,6 @@ nsJSContext::AddSupportsPrimitiveTojsvals(nsISupports *aArg, JS::Value *aArgv)
return NS_OK;
}
#ifdef NS_TRACE_MALLOC
#include <errno.h> // XXX assume Linux if NS_TRACE_MALLOC
#include <fcntl.h>
#ifdef XP_UNIX
#include <unistd.h>
#endif
#ifdef XP_WIN32
#include <io.h>
#endif
#include "nsTraceMalloc.h"
static bool
CheckUniversalXPConnectForTraceMalloc(JSContext *cx)
{
if (nsContentUtils::IsCallerChrome())
return true;
JS_ReportError(cx, "trace-malloc functions require UniversalXPConnect");
return false;
}
static bool
TraceMallocDisable(JSContext *cx, unsigned argc, JS::Value *vp)
{
JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
if (!CheckUniversalXPConnectForTraceMalloc(cx))
return false;
NS_TraceMallocDisable();
args.rval().setUndefined();
return true;
}
static bool
TraceMallocEnable(JSContext *cx, unsigned argc, JS::Value *vp)
{
JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
if (!CheckUniversalXPConnectForTraceMalloc(cx))
return false;
NS_TraceMallocEnable();
args.rval().setUndefined();
return true;
}
static bool
TraceMallocOpenLogFile(JSContext *cx, unsigned argc, JS::Value *vp)
{
JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
if (!CheckUniversalXPConnectForTraceMalloc(cx))
return false;
int fd;
if (argc == 0) {
fd = -1;
} else {
JSString *str = JS::ToString(cx, args[0]);
if (!str)
return false;
JSAutoByteString filename(cx, str);
if (!filename)
return false;
fd = open(filename.ptr(), O_CREAT | O_WRONLY | O_TRUNC, 0644);
if (fd < 0) {
JS_ReportError(cx, "can't open %s: %s", filename.ptr(), strerror(errno));
return false;
}
}
args.rval().setInt32(fd);
return true;
}
static bool
TraceMallocChangeLogFD(JSContext *cx, unsigned argc, JS::Value *vp)
{
JS::CallArgs args = CallArgsFromVp(argc, vp);
if (!CheckUniversalXPConnectForTraceMalloc(cx))
return false;
int32_t fd, oldfd;
if (args.length() == 0) {
oldfd = -1;
} else {
if (!JS::ToInt32(cx, args[0], &fd))
return false;
oldfd = NS_TraceMallocChangeLogFD(fd);
if (oldfd == -2) {
JS_ReportOutOfMemory(cx);
return false;
}
}
args.rval().setInt32(oldfd);
return true;
}
static bool
TraceMallocCloseLogFD(JSContext *cx, unsigned argc, JS::Value *vp)
{
JS::CallArgs args = CallArgsFromVp(argc, vp);
if (!CheckUniversalXPConnectForTraceMalloc(cx))
return false;
int32_t fd;
if (args.length() == 0) {
args.rval().setUndefined();
return true;
}
if (!JS::ToInt32(cx, args[0], &fd))
return false;
NS_TraceMallocCloseLogFD((int) fd);
args.rval().setInt32(fd);
return true;
}
static bool
TraceMallocLogTimestamp(JSContext *cx, unsigned argc, JS::Value *vp)
{
JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
if (!CheckUniversalXPConnectForTraceMalloc(cx))
return false;
JSString *str = JS::ToString(cx, args.get(0));
if (!str)
return false;
JSAutoByteString caption(cx, str);
if (!caption)
return false;
NS_TraceMallocLogTimestamp(caption.ptr());
args.rval().setUndefined();
return true;
}
static bool
TraceMallocDumpAllocations(JSContext *cx, unsigned argc, JS::Value *vp)
{
JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
if (!CheckUniversalXPConnectForTraceMalloc(cx))
return false;
JSString *str = JS::ToString(cx, args.get(0));
if (!str)
return false;
JSAutoByteString pathname(cx, str);
if (!pathname)
return false;
if (NS_TraceMallocDumpAllocations(pathname.ptr()) < 0) {
JS_ReportError(cx, "can't dump to %s: %s", pathname.ptr(), strerror(errno));
return false;
}
args.rval().setUndefined();
return true;
}
static const JSFunctionSpec TraceMallocFunctions[] = {
JS_FS("TraceMallocDisable", TraceMallocDisable, 0, 0),
JS_FS("TraceMallocEnable", TraceMallocEnable, 0, 0),
JS_FS("TraceMallocOpenLogFile", TraceMallocOpenLogFile, 1, 0),
JS_FS("TraceMallocChangeLogFD", TraceMallocChangeLogFD, 1, 0),
JS_FS("TraceMallocCloseLogFD", TraceMallocCloseLogFD, 1, 0),
JS_FS("TraceMallocLogTimestamp", TraceMallocLogTimestamp, 1, 0),
JS_FS("TraceMallocDumpAllocations", TraceMallocDumpAllocations, 1, 0),
JS_FS_END
};
#endif /* NS_TRACE_MALLOC */
#ifdef MOZ_JPROF
#include <signal.h>
@ -1374,13 +1203,6 @@ nsJSContext::InitClasses(JS::Handle<JSObject*> aGlobalObj)
// Attempt to initialize profiling functions
::JS_DefineProfilingFunctions(cx, aGlobalObj);
#ifdef NS_TRACE_MALLOC
if (nsContentUtils::IsCallerChrome()) {
// Attempt to initialize TraceMalloc functions
::JS_DefineFunctions(cx, aGlobalObj, TraceMallocFunctions);
}
#endif
#ifdef MOZ_JPROF
// Attempt to initialize JProf functions
::JS_DefineFunctions(cx, aGlobalObj, JProfFunctions);

View File

@ -702,7 +702,7 @@ gfxPlatform::~gfxPlatform()
// cairo_debug_* function unconditionally.
//
// because cairo can assert and thus crash on shutdown, don't do this in release builds
#if defined(DEBUG) || defined(NS_BUILD_REFCNT_LOGGING) || defined(NS_TRACE_MALLOC) || defined(MOZ_VALGRIND)
#if defined(DEBUG) || defined(NS_BUILD_REFCNT_LOGGING) || defined(MOZ_VALGRIND)
#ifdef USE_SKIA
// must do Skia cleanup before Cairo cleanup, because Skia may be referencing
// Cairo objects e.g. through SkCairoFTTypeface

View File

@ -2988,21 +2988,6 @@ elif test "$GNU_CC"; then
fi
fi
dnl ========================================================
dnl = Enable trace malloc
dnl ========================================================
NS_TRACE_MALLOC=${MOZ_TRACE_MALLOC}
MOZ_ARG_ENABLE_BOOL(trace-malloc,
[ --enable-trace-malloc Enable malloc tracing],
NS_TRACE_MALLOC=1,
NS_TRACE_MALLOC= )
if test "$NS_TRACE_MALLOC"; then
# Please, Mr. Linker Man, don't take away our symbol names
MOZ_COMPONENTS_VERSION_SCRIPT_LDFLAGS=
AC_DEFINE(NS_TRACE_MALLOC)
fi
AC_SUBST(NS_TRACE_MALLOC)
dnl ========================================================
dnl = Enable DMD
dnl ========================================================
@ -3012,9 +2997,6 @@ MOZ_ARG_ENABLE_BOOL(dmd,
MOZ_DMD=1,
MOZ_DMD= )
if test "$NS_TRACE_MALLOC"; then # trace-malloc disables DMD
MOZ_DMD=
fi
if test "$MOZ_DMD"; then
AC_DEFINE(MOZ_DMD)
@ -3032,10 +3014,6 @@ MOZ_ARG_ENABLE_BOOL(jemalloc,
MOZ_MEMORY=1,
MOZ_MEMORY=)
if test "$NS_TRACE_MALLOC"; then
MOZ_MEMORY=
fi
if test "$MOZ_MEMORY"; then
AC_DEFINE(MOZ_MEMORY)
if test "x$MOZ_DEBUG" = "x1"; then
@ -3429,9 +3407,9 @@ if test -z "$SKIP_LIBRARY_CHECKS"; then
AC_LANG_RESTORE
fi
# Demangle only for debug or trace-malloc or DMD builds
# Demangle only for debug or DMD builds
MOZ_DEMANGLE_SYMBOLS=
if test "$HAVE_DEMANGLE" && test "$MOZ_DEBUG" -o "$NS_TRACE_MALLOC" -o "$MOZ_DMD"; then
if test "$HAVE_DEMANGLE" && test "$MOZ_DEBUG" -o "$MOZ_DMD"; then
MOZ_DEMANGLE_SYMBOLS=1
AC_DEFINE(MOZ_DEMANGLE_SYMBOLS)
fi
@ -3671,7 +3649,7 @@ if test "$JS_HAS_CTYPES"; then
AC_DEFINE(JS_HAS_CTYPES)
fi
if test "$MOZ_DEBUG" -o "$NS_TRACE_MALLOC" -o "$MOZ_DMD"; then
if test "$MOZ_DEBUG" -o "$MOZ_DMD"; then
MOZ_COMPONENTS_VERSION_SCRIPT_LDFLAGS=
fi

View File

@ -836,9 +836,6 @@ class XPCShellTests(object):
self.env["MOZ_CRASHREPORTER"] = "1"
# Don't launch the crash reporter client
self.env["MOZ_CRASHREPORTER_NO_REPORT"] = "1"
# Capturing backtraces is very slow on some platforms, and it's
# disabled by automation.py too
self.env["NS_TRACE_MALLOC_DISABLE_STACKS"] = "1"
# Don't permit remote connections by default.
# MOZ_DISABLE_NONLOCAL_CONNECTIONS can be set to "0" to temporarily
# enable non-local connections for the purposes of local testing.

View File

@ -126,7 +126,7 @@
// and subsequent processes.
//
// Nb: mgr.explicit will throw NS_ERROR_NOT_AVAILABLE if this is a
// --enable-trace-malloc build. Allow for that exception, but *only* that
// --disable-jemalloc build. Allow for that exception, but *only* that
// exception.
try {
is(mgr.explicit, 500*MB + (100 + 13 + 10)*MB + 599*KB, "mgr.explicit");

View File

@ -139,7 +139,7 @@
// non-deterministic.
//
// Nb: mgr.explicit will throw NS_ERROR_NOT_AVAILABLE if this is a
// --enable-trace-malloc build. Allow for that exception, but *only* that
// --disable-jemalloc build. Allow for that exception, but *only* that
// exception.
let dummy;
let haveExplicit = true;

View File

@ -21,9 +21,6 @@ DIRS += [
if CONFIG['MOZ_UPDATER']:
DIRS += ['/modules/libmar']
if CONFIG['NS_TRACE_MALLOC']:
DIRS += ['/tools/trace-malloc/lib']
DIRS += [
'/config/external/freetype2',
'/xpcom',
@ -163,9 +160,6 @@ DIRS += [
'/toolkit/library',
]
if CONFIG['NS_TRACE_MALLOC']:
DIRS += ['/tools/trace-malloc']
if CONFIG['MOZ_ENABLE_GNOME_COMPONENT']:
DIRS += ['/toolkit/system/gnome']

View File

@ -160,10 +160,6 @@
#include "nsIRemoteService.h"
#endif
#ifdef NS_TRACE_MALLOC
#include "nsTraceMalloc.h"
#endif
#if defined(DEBUG) && defined(XP_WIN32)
#include <malloc.h>
#endif
@ -224,8 +220,7 @@ static char **gQtOnlyArgv;
#endif
#if defined(MOZ_WIDGET_GTK)
#if defined(DEBUG) || defined(NS_BUILD_REFCNT_LOGGING) \
|| defined(NS_TRACE_MALLOC)
#if defined(DEBUG) || defined(NS_BUILD_REFCNT_LOGGING)
#define CLEANUP_MEMORY 1
#define PANGO_ENABLE_BACKEND
#include <pango/pangofc-fontmap.h>
@ -3328,10 +3323,6 @@ XREMain::XRE_mainInit(bool* aExitFlag)
return 0;
}
#ifdef NS_TRACE_MALLOC
gArgc = NS_TraceMallocStartupArgs(gArgc, gArgv);
#endif
rv = XRE_InitCommandLine(gArgc, gArgv);
NS_ENSURE_SUCCESS(rv, 1);

View File

@ -1,39 +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/.
EXTRACSRCS = tmreader.c
EXTRACPPSRCS = adreader.cpp
ifndef MOZ_PROFILE_GENERATE
PROGCSRCS = \
spacetrace.c \
spacecategory.c \
formdata.c \
$(NULL)
PROGOBJS = $(PROGCSRCS:.c=.$(OBJ_SUFFIX))
endif
CPPSRCS += $(EXTRACPPSRCS)
include $(topsrcdir)/config/config.mk
# This is the last use of EXTRA_LIBS, and it would be sad to not have an
# error if it's used in other Makefiles just because of it. So hack around
# the check.
_DEPRECATED_VARIABLES := $(filter-out EXTRA_LIBS,$(_DEPRECATED_VARIABLES))
EXTRA_LIBS += \
tmreader.$(OBJ_SUFFIX) \
adreader.$(OBJ_SUFFIX) \
$(NULL)
EXTRA_DEPS = $(EXTRACSRCS:.c=.$(OBJ_SUFFIX)) $(EXTRACPPSRCS:.cpp=.$(OBJ_SUFFIX))
include $(topsrcdir)/config/rules.mk
# install rules.txt along with spacetrace executable
libs:: rules.txt
$(INSTALL) $< $(DIST)/bin

View File

@ -1,177 +0,0 @@
Trace Malloc Tools
Chris Waterson <waterson@netscape.com>
November 27, 2000
This is a short primer on how to use the `trace malloc' tools
contained in this directory.
WHAT IS TRACE MALLOC?
=====================
Trace malloc is an optional facility that is built in to XPCOM. It
uses `weak linking' to intercept all calls to malloc(), calloc(),
realloc() and free(). It does two things:
1. Writes information about allocations to a filehandle that you
specify. As each call to malloc(), et. al. is made, a record is logged
to the filehandle.
2. Maintains a table of all `live objects' -- that is, objects that
have been allocated by malloc(), calloc() or realloc(), but have not
yet been free()'d. The contents of this table can be called by making
a `secret' call to JavaScript.
MAKING A TRACE MALLOC BUILD
===========================
As of this writing, trace malloc only works on Linux, but work is
underway to port it to Windows.
On Linux, start with a clean tree, and configure your build with the
following flags:
--enable-trace-malloc
--enable-cpp-rtti
Be sure that `--enable-boehm' is *not* set. I don't think that the
values for `--enable-debug' and `--enable-optimize' matter, but I've
typically had debug on and optimize off.
COLLECTING LIVE OBJECT DATA
===========================
To collect `live object' data from `mozilla' using a build that has
trace malloc enabled,
1. Run `mozilla' as follows:
% mozilla --trace-malloc /dev/null
2. Do whatever operations in mozilla you'd like to test.
3. Open the `live-bloat.html' file contained in this directory.
4. Press the button that says `Dump to /tmp/dump.log'
An enormous file (typically 300MB) called `dump.log' will be dropped
in your `/tmp' directory.
To collect live object data from `gtkEmbed' using a build that has
trace malloc enabled:
1. Run `gtkEmbed' as follows:
% gtkEmbed --trace-malloc /dev/null
2. Do whatever operations in gtkEmbed that you'd like to test.
3. Press the `Dump Memory' button at the bottom of gtkEmbed.
The enormous file will be dropped in the current directory, and is
called `allocations.log'.
About Live Object Logs
----------------------
A typical entry from the `live object' dump file will look like:
Address Type Size
| | |
v v v
0x40008080 <nsFooBar> 16
0x00000001 <- Fields
0x40008084
0x80004001
0x00000036
__builtin_new[./libxpcom.so +0x10E9DC] <- Stack at allocation time
nsFooBar::CreateFooBar(nsFooBar **)[./libfoobar.so +0x408C]
main+C7E5AFB5[(null) +0xC7E5AFB5]
One will be printed for each object that was allocated.
TOOLS TO PARSE LIVE OBJECT LOGS
===============================
This directory is meant to house the tools that you can use to parse
live-object logs.
Object Histograms - histogram.pl
--------------------------------
This program parses a `live object' dump and produces a histogram of
the objects, sorted from objects that take the most memory to objects
that take the least. The output of this program is rather spartan: on
each line, it prints the object type, the number of objects of that
type, and the total number of bytes that the objects consume.
There are a two simple programs to `pretty print' the output from
histogram.pl:
1. histogram-pretty.sh takes a single histogram and produces a table
of objects.
Type Count Bytes %Total
TOTAL 67348 4458127 100.00
nsImageGTK 76 679092 15.23
void* 8956 563572 12.64
...
PRLock 732 61488 1.38
OTHER 24419 940235 21.09
2. histogram-diff.sh takes two histograms and computes the difference
between them.
---- Base ---- ---- Incr ---- ----- Difference ----
Type Count Bytes Count Bytes Count Bytes %Total
TOTAL 40241 1940945 73545 5315142 33304 3374197 100.00
nsImageGTK 16 106824 151 832816 135 725992 21.52
PresShell 16 51088 198 340706 182 289618 8.58
...
OTHER 27334 1147033 38623 1493385 11289 346352 10.26
Both of these scripts accept `-c' parameter that specifies how many
rows you'd like to see (by default, twenty). Any rows past the first
`n' rows are lumped into a single `OTHER' row. This allows you to keep
your reports short n' sweet.
Stack-based Type Inference - types.dat
--------------------------------------
Trace malloc uses `speculative RTTI' to determine the types of objects
as it dumps them. Unfortunately, RTTI can only deduce the type name
for C++ objects with a virtual destructor.
This leaves:
. C++ object without a virtual destructor
. array allocated C++ objects, and
. objects allocated with the C runtime function (malloc
and friends)
out in the cold. Trace malloc reports objects allocated this was as
having type `void*'.
The good news is that you can almost always determine the object's
type by looking at the stack trace that's taken at the time the object
is allocated.
The file `types.dat' consists of rules to classify objects based on
stack trace.
Uncategorized Objects - uncategorized.pl
----------------------------------------
Categorizing objects in `types.dat' is sweaty work, and the
`uncategorized.pl' script is a tool that makes it a bit
easier. Specifically, it reads a `live object' dump file and sorts the
stack traces. Stack traces that account for the most uncategorized
objects are placed first.
Using this tool, you can add the `most effective' rules to
`types.dat': rules that account for most of the uncategorized data.

View File

@ -1,156 +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/.
package TraceMalloc;
use strict;
# Read in the type inference file and construct a network that we can
# use to match stack prefixes to types.
sub init_type_inference($) {
my ($file) = @_;
$::Fingerprints = { };
open(TYPES, "<$file") || die "unable to open $::opt_types, $!";
TYPE: while (<TYPES>) {
next TYPE unless /<(.*)>/;
my $type = $1;
my $link = \%::Fingerprints;
FRAME: while (<TYPES>) {
chomp;
last FRAME if /^$/;
my $next = $link->{$_};
if (! $next) {
$next = $link->{$_} = {};
}
$link = $next;
}
$link->{'#type#'} = $type;
last TYPE if eof;
}
}
# Infer the type, trying to find the most specific type possible.
sub infer_type($) {
my ($stack) = @_;
my $link = \%::Fingerprints;
my $last;
my $type = 'void*';
FRAME: foreach my $frame (@$stack) {
last FRAME unless $link;
$frame =~ s/\[.*\]$//; # ignore exact addresses, as they'll drift
$last = $link;
#
# Remember this type, but keep going. We use the longest match
# we find, but substacks of longer matches will also match.
#
if ($last->{'#type#'}) {
$type = $last->{'#type#'};
}
$link = $link->{$frame};
if (! $link) {
CHILD: foreach my $child (keys %$last) {
next CHILD unless $child =~ /^~/;
$child =~ s/^~//;
if ($frame =~ $child) {
$link = $last->{'~' . $child};
last CHILD;
}
}
}
}
return $type;
}
#----------------------------------------------------------------------
#
# Read in the output a trace malloc's dump.
#
sub read {
my ($callback, $noslop) = @_;
OBJECT: while (<>) {
# e.g., 0x0832FBD0 <void*> (80)
next OBJECT unless /^0x(\S+) <(.*)> \((\d+)\)/;
my ($addr, $type, $size) = (hex $1, $2, $3);
my $object = { 'type' => $type, 'size' => $size };
# Record the object's slots
my @slots;
SLOT: while (<>) {
# e.g., 0x00000000
last SLOT unless /^\t0x(\S+)/;
my $value = hex $1;
# Ignore low bits, unless they've specified --noslop
$value &= ~0x7 unless $noslop;
$slots[$#slots + 1] = $value;
}
$object->{'slots'} = \@slots;
# Record the stack by which the object was allocated
my @stack;
while (/^(.*)\[(.*) \+0x(\S+)\]$/) {
# e.g., _dl_debug_message[/lib/ld-linux.so.2 +0x0000B858]
my ($func, $lib, $off) = ($1, $2, hex $3);
chomp;
$stack[$#stack + 1] = $_;
$_ = <>;
}
$object->{'stack'} = \@stack;
$object->{'type'} = infer_type(\@stack)
if $object->{'type'} eq 'void*';
&$callback($object) if $callback;
# Gotta check EOF explicitly...
last OBJECT if eof;
}
}
1;
__END__
=head1 NAME
TraceMalloc - Perl routines to deal with output from ``trace malloc''
and the Boehm GC
=head1 SYNOPSIS
use TraceMalloc;
TraceMalloc::init_type_inference("types.dat");
TraceMalloc::read(0);
=head1 DESCRIPTION
=head1 EXAMPLES
=cut

View File

@ -1,132 +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 "adreader.h"
#include <stdio.h>
#include <stdint.h>
#include <string.h>
ADLog::ADLog()
: mEntryCount(0)
{
mEntries.mNext = static_cast<EntryBlock*>(&mEntries);
mEntries.mPrev = static_cast<EntryBlock*>(&mEntries);
}
ADLog::~ADLog()
{
for (const_iterator entry = begin(), entry_end = end();
entry != entry_end; ++entry) {
free((void*) (*entry)->type);
free((char*) (*entry)->data);
free((char*) (*entry)->allocation_stack);
}
for (EntryBlock *b = mEntries.mNext, *next; b != &mEntries; b = next) {
next = b->mNext;
delete b;
}
}
bool
ADLog::Read(const char* aFileName)
{
FILE *in = fopen(aFileName, "r");
if (!in) {
return false;
}
while (!feof(in)) {
unsigned int ptr;
char typebuf[256];
int datasize;
int res = fscanf(in, "%x %s (%d)\n", &ptr, typebuf, &datasize);
if (res == EOF)
break;
if (res != 3) {
return false;
}
size_t data_mem_size = ((datasize + sizeof(unsigned long) - 1) /
sizeof(unsigned long)) * sizeof(unsigned long);
char *data = (char*)malloc(data_mem_size);
for (size_t *cur_data = (size_t*) data,
*cur_data_end = (size_t*) ((char*)data + data_mem_size);
cur_data != cur_data_end; ++cur_data) {
res = fscanf(in, " %zX\n", cur_data);
if (res != 1) {
return false;
}
}
char stackbuf[100000];
stackbuf[0] = '\0';
char *stack = stackbuf;
int len;
do {
fgets(stack, sizeof(stackbuf) - (stack - stackbuf), in);
len = strlen(stack);
stack += len;
} while (len > 1);
if (mEntryCount % ADLOG_ENTRY_BLOCK_SIZE == 0) {
EntryBlock *new_block = new EntryBlock();
new_block->mNext = static_cast<EntryBlock*>(&mEntries);
new_block->mPrev = mEntries.mPrev;
mEntries.mPrev->mNext = new_block;
mEntries.mPrev = new_block;
}
size_t typelen = strlen(typebuf);
char *type = (char*)malloc(typelen-1);
strncpy(type, typebuf+1, typelen-2);
type[typelen-2] = '\0';
Entry *entry =
&mEntries.mPrev->entries[mEntryCount % ADLOG_ENTRY_BLOCK_SIZE];
entry->address = (Pointer) (uintptr_t) ptr;
entry->type = type;
entry->datasize = datasize;
entry->data = data;
entry->allocation_stack = strdup(stackbuf);
++mEntryCount;
}
return true;
}
ADLog::const_iterator::const_iterator(ADLog::EntryBlock *aBlock,
size_t aOffset)
{
SetBlock(aBlock);
mCur = mBlockStart + aOffset;
}
ADLog::const_iterator&
ADLog::const_iterator::operator++()
{
++mCur;
if (mCur == mBlockEnd) {
SetBlock(mBlock->mNext);
mCur = mBlockStart;
}
return *this;
}
ADLog::const_iterator&
ADLog::const_iterator::operator--()
{
if (mCur == mBlockStart) {
SetBlock(mBlock->mPrev);
mCur = mBlockEnd;
}
--mCur;
return *this;
}

View File

@ -1,97 +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 <stdlib.h>
#define ADLOG_ENTRY_BLOCK_SIZE 4096
class ADLog {
public:
// Use typedef in case somebody wants to process 64-bit output on a
// 32-bit machine someday.
typedef const char* Pointer;
struct Entry {
Pointer address;
const char *type;
const char *data; // The contents of the memory.
size_t datasize;
const char *allocation_stack;
};
ADLog();
~ADLog();
/*
* Returns false on failure and true on success.
*/
bool Read(const char *aFilename);
private:
// Link structure for a circularly linked list.
struct EntryBlock;
struct EntryBlockLink {
EntryBlock *mPrev;
EntryBlock *mNext;
};
struct EntryBlock : public EntryBlockLink {
Entry entries[ADLOG_ENTRY_BLOCK_SIZE];
};
size_t mEntryCount;
EntryBlockLink mEntries;
public:
class const_iterator {
private:
// Only |ADLog| member functions can construct iterators.
friend class ADLog;
const_iterator(EntryBlock *aBlock, size_t aOffset);
public:
const Entry* operator*() { return mCur; }
const Entry* operator->() { return mCur; }
const_iterator& operator++();
const_iterator& operator--();
bool operator==(const const_iterator& aOther) const {
return mCur == aOther.mCur;
}
bool operator!=(const const_iterator& aOther) const {
return mCur != aOther.mCur;
}
private:
void SetBlock(EntryBlock *aBlock) {
mBlock = aBlock;
mBlockStart = aBlock->entries;
mBlockEnd = aBlock->entries + ADLOG_ENTRY_BLOCK_SIZE;
}
EntryBlock *mBlock;
Entry *mCur, *mBlockStart, *mBlockEnd;
// Not to be implemented.
const_iterator operator++(int);
const_iterator operator--(int);
};
const_iterator begin() {
return const_iterator(mEntries.mNext, 0);
}
const_iterator end() {
return const_iterator(mEntries.mPrev,
mEntryCount % ADLOG_ENTRY_BLOCK_SIZE);
}
size_t count() { return mEntryCount; }
};

View File

@ -1,92 +0,0 @@
/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
*
* 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 <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#ifdef HAVE_GETOPT_H
#include <getopt.h>
#else
extern int getopt(int argc, char *const *argv, const char *shortopts);
extern char *optarg;
extern int optind;
#ifdef XP_WIN32
int optind=1;
#endif
#endif
#include <time.h>
#include "nsTraceMalloc.h"
#include "tmreader.h"
static char *program;
static void my_tmevent_handler(tmreader *tmr, tmevent *event)
{
tmcallsite *callsite;
switch (event->type) {
case TM_EVENT_REALLOC:
case TM_EVENT_MALLOC:
case TM_EVENT_CALLOC:
for (callsite = tmreader_callsite(tmr, event->serial);
callsite != &tmr->calltree_root; callsite = callsite->parent) {
fprintf(stdout, "%s +%08X (%s:%d)\n",
(const char*)callsite->method->graphnode.entry.value,
callsite->offset,
callsite->method->sourcefile,
callsite->method->linenumber);
}
fprintf(stdout, "\n");
}
}
int main(int argc, char **argv)
{
int i, j, rv;
tmreader *tmr;
FILE *fp;
time_t start;
program = *argv;
tmr = tmreader_new(program, NULL);
if (!tmr) {
perror(program);
exit(1);
}
start = time(NULL);
fprintf(stdout, "%s starting at %s", program, ctime(&start));
fflush(stdout);
argc -= optind;
argv += optind;
if (argc == 0) {
if (tmreader_eventloop(tmr, "-", my_tmevent_handler) <= 0)
exit(1);
} else {
for (i = j = 0; i < argc; i++) {
fp = fopen(argv[i], "r");
if (!fp) {
fprintf(stderr, "%s: can't open %s: %s\n",
program, argv[i], strerror(errno));
exit(1);
}
rv = tmreader_eventloop(tmr, argv[i], my_tmevent_handler);
if (rv < 0)
exit(1);
if (rv > 0)
j++;
fclose(fp);
}
if (j == 0)
exit(1);
}
tmreader_destroy(tmr);
exit(0);
}

View File

@ -1,14 +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/. */
td {
font: x-small sans-serif;
vertical-align: top;
}
thead td {
font-weight: bold;
}

View File

@ -1,219 +0,0 @@
#!/usr/bin/perl -w
#
# 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/.
#
# Process output of TraceMallocDumpAllocations() to produce a table
# that attributes memory to the allocators using call stack.
#
use 5.004;
use strict;
# A table of all ancestors. Key is function name, value is an
# array of ancestors, each attributed with a number of calls and
# the amount of memory allocated.
my %Ancestors;
# Ibid, for descendants.
my %Descendants;
# A table that keeps the total amount of memory allocated by each
# function
my %Totals;
$Totals{".root"} = { "#memory#" => 0, "#calls#" => 0 };
# A table that maps the long ugly function name to a unique number so
# that the HTML we generate isn't too fat
my %Ids;
my $NextId = 0;
$Ids{".root"} = ++$NextId;
LINE: while (<>) {
# The line'll look like:
#
# 0x4000a008 16 PR_Malloc+16; nsMemoryImpl::Alloc(unsigned int)+12; ...
# Ignore any lines that don't start with an address
next LINE unless /^0x/;
# Parse it
my ($address, $size, $rest) = /^(0x\S*)\s*(\d+)\s*(.*)$/;
my @stack = reverse(split /; /, $rest);
# Accumulate at the root
$Totals{".root"}->{"#memory#"} += $size;
++$Totals{".root"}->{"#calls#"};
my $caller = ".root";
foreach my $callee (@stack) {
# Strip the offset from the callsite information. I don't
# think we care.
$callee =~ s/\+\d+$//g;
# Accumulate the total for the callee
if (! $Totals{$callee}) {
$Totals{$callee} = { "#memory#" => 0, "#calls#" => 0 };
}
$Totals{$callee}->{"#memory#"} += $size;
++$Totals{$callee}->{"#calls#"};
# Descendants
my $descendants = $Descendants{$caller};
if (! $descendants) {
$descendants = $Descendants{$caller} = [ ];
}
# Manage the list of descendants
{
my $wasInserted = 0;
DESCENDANT: foreach my $item (@$descendants) {
if ($item->{"#name#"} eq $callee) {
$item->{"#memory#"} += $size;
++$item->{"#calls#"};
$wasInserted = 1;
last DESCENDANT;
}
}
if (! $wasInserted) {
$descendants->[@$descendants] = {
"#name#" => $callee,
"#memory#" => $size,
"#calls#" => 1
};
}
}
# Ancestors
my $ancestors = $Ancestors{$callee};
if (! $ancestors) {
$ancestors = $Ancestors{$callee} = [ ];
}
# Manage the list of ancestors
{
my $wasInserted = 0;
ANCESTOR: foreach my $item (@$ancestors) {
if ($item->{"#name#"} eq $caller) {
$item->{"#memory#"} += $size;
++$item->{"#calls#"};
$wasInserted = 1;
last ANCESTOR;
}
}
if (! $wasInserted) {
$ancestors->[@$ancestors] = {
"#name#" => $caller,
"#memory#" => $size,
"#calls#" => 1
};
}
}
# Make a new "id", if necessary
if (! $Ids{$callee}) {
$Ids{$callee} = ++$NextId;
}
# On to the next one...
$caller = $callee;
}
}
# Change the manky looking callsite into a pretty function; strip argument
# types and offset information.
sub pretty($) {
$_ = $_[0];
s/&/&amp;/g;
s/</&lt;/g;
s/>/&gt;/g;
if (/([^\(]*)(\(.*\))/) {
return $1 . "()";
}
else {
return $_[0];
}
}
# Dump a web page!
print "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.0//EN\">\n";
print "<html><head>\n";
print "<title>Live Bloat Blame</title>\n";
print "<link rel=\"stylesheet\" type=\"text/css\" href=\"blame.css\">\n";
print "</head>\n";
print "<body>\n";
# At most 100 rows per table so as not to kill the browser.
my $maxrows = 100;
print "<table>\n";
print "<thead><tr><td>Function</td><td>Ancestors</td><td>Descendants</td></tr></thead>\n";
foreach my $node (sort(keys(%Ids))) {
print "<tr>\n";
# Print the current node
{
my ($memory, $calls) =
($Totals{$node}->{"#memory#"},
$Totals{$node}->{"#calls#"});
my $pretty = pretty($node);
print " <td><a name=\"$Ids{$node}\">$pretty&nbsp;$memory&nbsp;($calls)</a></td>\n";
}
# Ancestors, sorted descending by amount of memory allocated
print " <td>\n";
my $ancestors = $Ancestors{$node};
if ($ancestors) {
foreach my $ancestor (sort { $b->{"#memory#"} <=> $a->{"#memory#"} } @$ancestors) {
my ($name, $memory, $calls) =
($ancestor->{"#name#"},
$ancestor->{"#memory#"},
$ancestor->{"#calls#"});
my $pretty = pretty($name);
print " <a href=\"#$Ids{$name}\">$pretty</a>&nbsp;$memory&nbsp;($calls)<br>\n";
}
}
print " </td>\n";
# Descendants, sorted descending by amount of memory allocated
print " <td>\n";
my $descendants = $Descendants{$node};
if ($descendants) {
foreach my $descendant (sort { $b->{"#memory#"} <=> $a->{"#memory#"} } @$descendants) {
my ($name, $memory, $calls) =
($descendant->{"#name#"},
$descendant->{"#memory#"},
$descendant->{"#calls#"});
my $pretty = pretty($name);
print " <a href=\"#$Ids{$name}\">$pretty</a>&nbsp;$memory&nbsp;($calls)<br>\n";
}
}
print " </td></tr>\n";
if (--$maxrows == 0) {
print "</table>\n";
print "<table>\n";
print "<thead><tr><td>Function</td><td>Ancestors</td><td>Descendants</td></tr></thead>\n";
$maxrows = 100;
}
}
# Footer
print "</table>\n";
print "</body></html>\n";

View File

@ -1,722 +0,0 @@
/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
*
* 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 <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <errno.h>
#ifdef HAVE_GETOPT_H
#include <getopt.h>
#else
#ifdef XP_WIN
#include "getopt.c"
#else
extern int getopt(int argc, char *const *argv, const char *shortopts);
extern char *optarg;
extern int optind;
#endif
#endif
#include <math.h>
#include <time.h>
#include <sys/stat.h>
#include "prlog.h"
#include "prprf.h"
#include "plhash.h"
#include "pldhash.h"
#include "nsTraceMalloc.h"
#include "tmreader.h"
static char *program;
static int sort_by_direct = 0;
static int js_mode = 0;
static int do_tree_dump = 0;
static int unified_output = 0;
static char *function_dump = NULL;
static uint32_t min_subtotal = 0;
static void compute_callsite_totals(tmcallsite *site)
{
tmcallsite *kid;
site->allocs.bytes.total += site->allocs.bytes.direct;
site->allocs.calls.total += site->allocs.calls.direct;
for (kid = site->kids; kid; kid = kid->siblings) {
compute_callsite_totals(kid);
site->allocs.bytes.total += kid->allocs.bytes.total;
site->allocs.calls.total += kid->allocs.calls.total;
}
}
static void walk_callsite_tree(tmcallsite *site, int level, int kidnum, FILE *fp)
{
tmcallsite *parent;
tmgraphnode *comp, *pcomp, *lib, *plib;
tmmethodnode *meth, *pmeth;
int old_meth_low, old_comp_low, old_lib_low, nkids;
tmcallsite *kid;
parent = site->parent;
comp = lib = NULL;
meth = NULL;
if (parent) {
meth = site->method;
if (meth) {
pmeth = parent->method;
if (pmeth && pmeth != meth) {
if (!meth->graphnode.low) {
meth->graphnode.allocs.bytes.total += site->allocs.bytes.total;
meth->graphnode.allocs.calls.total += site->allocs.calls.total;
}
if (!tmgraphnode_connect(&(pmeth->graphnode), &(meth->graphnode), site))
goto bad;
comp = meth->graphnode.up;
if (comp) {
pcomp = pmeth->graphnode.up;
if (pcomp && pcomp != comp) {
if (!comp->low) {
comp->allocs.bytes.total
+= site->allocs.bytes.total;
comp->allocs.calls.total
+= site->allocs.calls.total;
}
if (!tmgraphnode_connect(pcomp, comp, site))
goto bad;
lib = comp->up;
if (lib) {
plib = pcomp->up;
if (plib && plib != lib) {
if (!lib->low) {
lib->allocs.bytes.total
+= site->allocs.bytes.total;
lib->allocs.calls.total
+= site->allocs.calls.total;
}
if (!tmgraphnode_connect(plib, lib, site))
goto bad;
}
old_lib_low = lib->low;
if (!old_lib_low)
lib->low = level;
}
}
old_comp_low = comp->low;
if (!old_comp_low)
comp->low = level;
}
}
old_meth_low = meth->graphnode.low;
if (!old_meth_low)
meth->graphnode.low = level;
}
}
if (do_tree_dump) {
fprintf(fp, "%c%*s%3d %3d %s %lu %ld\n",
site->kids ? '+' : '-', level, "", level, kidnum,
meth ? tmmethodnode_name(meth) : "???",
(unsigned long)site->allocs.bytes.direct,
(long)site->allocs.bytes.total);
}
nkids = 0;
level++;
for (kid = site->kids; kid; kid = kid->siblings) {
walk_callsite_tree(kid, level, nkids, fp);
nkids++;
}
if (meth) {
if (!old_meth_low)
meth->graphnode.low = 0;
if (comp) {
if (!old_comp_low)
comp->low = 0;
if (lib) {
if (!old_lib_low)
lib->low = 0;
}
}
}
return;
bad:
perror(program);
exit(1);
}
/*
* Linked list bubble-sort (waterson and brendan went bald hacking this).
*
* Sort the list in non-increasing order, using the expression passed as the
* 'lessthan' formal macro parameter. This expression should use 'curr' as
* the pointer to the current node (of type nodetype) and 'next' as the next
* node pointer. It should return true if curr is less than next, and false
* otherwise.
*/
#define BUBBLE_SORT_LINKED_LIST(listp, nodetype, lessthan) \
PR_BEGIN_MACRO \
nodetype *curr, **currp, *next, **nextp, *tmp; \
\
currp = listp; \
while ((curr = *currp) != NULL && curr->next) { \
nextp = &curr->next; \
while ((next = *nextp) != NULL) { \
if (lessthan) { \
tmp = curr->next; \
*currp = tmp; \
if (tmp == next) { \
PR_ASSERT(nextp == &curr->next); \
curr->next = next->next; \
next->next = curr; \
} else { \
*nextp = next->next; \
curr->next = next->next; \
next->next = tmp; \
*currp = next; \
*nextp = curr; \
nextp = &curr->next; \
} \
curr = next; \
continue; \
} \
nextp = &next->next; \
} \
currp = &curr->next; \
} \
PR_END_MACRO
static int tabulate_node(PLHashEntry *he, int i, void *arg)
{
tmgraphnode *node = (tmgraphnode*) he;
tmgraphnode **table = (tmgraphnode**) arg;
table[i] = node;
BUBBLE_SORT_LINKED_LIST(&node->down, tmgraphnode,
(curr->allocs.bytes.total < next->allocs.bytes.total));
return HT_ENUMERATE_NEXT;
}
/* Sort in reverse size order, so biggest node comes first. */
static int node_table_compare(const void *p1, const void *p2)
{
const tmgraphnode *node1, *node2;
uint32_t key1, key2;
node1 = *(const tmgraphnode**) p1;
node2 = *(const tmgraphnode**) p2;
if (sort_by_direct) {
key1 = node1->allocs.bytes.direct;
key2 = node2->allocs.bytes.direct;
} else {
key1 = node1->allocs.bytes.total;
key2 = node2->allocs.bytes.total;
}
return (key2 < key1) ? -1 : (key2 > key1) ? 1 : 0;
}
static int mean_size_compare(const void *p1, const void *p2)
{
const tmgraphnode *node1, *node2;
double div1, div2, key1, key2;
node1 = *(const tmgraphnode**) p1;
node2 = *(const tmgraphnode**) p2;
div1 = (double)node1->allocs.calls.direct;
div2 = (double)node2->allocs.calls.direct;
if (div1 == 0 || div2 == 0)
return (int)(div2 - div1);
key1 = (double)node1->allocs.bytes.direct / div1;
key2 = (double)node2->allocs.bytes.direct / div2;
if (key1 < key2)
return 1;
if (key1 > key2)
return -1;
return 0;
}
static const char *prettybig(uint32_t num, char *buf, size_t limit)
{
if (num >= 1000000000)
PR_snprintf(buf, limit, "%1.2fG", (double) num / 1e9);
else if (num >= 1000000)
PR_snprintf(buf, limit, "%1.2fM", (double) num / 1e6);
else if (num >= 1000)
PR_snprintf(buf, limit, "%1.2fK", (double) num / 1e3);
else
PR_snprintf(buf, limit, "%lu", (unsigned long) num);
return buf;
}
static double percent(uint32_t num, uint32_t total)
{
if (num == 0)
return 0.0;
return ((double) num * 100) / (double) total;
}
static void sort_graphlink_list(tmgraphlink **listp, int which)
{
BUBBLE_SORT_LINKED_LIST(listp, tmgraphlink,
(TM_LINK_TO_EDGE(curr, which)->allocs.bytes.total
< TM_LINK_TO_EDGE(next, which)->allocs.bytes.total));
}
static void dump_graphlink_list(tmgraphlink *list, int which, const char *name,
FILE *fp)
{
tmcounts bytes;
tmgraphlink *link;
tmgraphedge *edge;
char buf[16];
bytes.direct = bytes.total = 0;
for (link = list; link; link = link->next) {
edge = TM_LINK_TO_EDGE(link, which);
bytes.direct += edge->allocs.bytes.direct;
bytes.total += edge->allocs.bytes.total;
}
if (js_mode) {
fprintf(fp,
" %s:{dbytes:%ld, tbytes:%ld, edges:[\n",
name, (long) bytes.direct, (long) bytes.total);
for (link = list; link; link = link->next) {
edge = TM_LINK_TO_EDGE(link, which);
fprintf(fp,
" {node:%d, dbytes:%ld, tbytes:%ld},\n",
link->node->sort,
(long) edge->allocs.bytes.direct,
(long) edge->allocs.bytes.total);
}
fputs(" ]},\n", fp);
} else {
fputs("<td valign=top>", fp);
for (link = list; link; link = link->next) {
edge = TM_LINK_TO_EDGE(link, which);
fprintf(fp,
"<a href='#%s'>%s&nbsp;(%1.2f%%)</a>\n",
tmgraphnode_name(link->node),
prettybig(edge->allocs.bytes.total, buf, sizeof buf),
percent(edge->allocs.bytes.total, bytes.total));
}
fputs("</td>", fp);
}
}
static void dump_graph(tmreader *tmr, PLHashTable *hashtbl, const char *varname,
const char *title, FILE *fp)
{
uint32_t i, count;
tmgraphnode **table, *node;
char *name;
size_t namelen;
char buf1[16], buf2[16], buf3[16], buf4[16];
count = hashtbl->nentries;
table = (tmgraphnode**) malloc(count * sizeof(tmgraphnode*));
if (!table) {
perror(program);
exit(1);
}
PL_HashTableEnumerateEntries(hashtbl, tabulate_node, table);
qsort(table, count, sizeof(tmgraphnode*), node_table_compare);
for (i = 0; i < count; i++)
table[i]->sort = i;
if (js_mode) {
fprintf(fp,
"var %s = {\n name:'%s', title:'%s', nodes:[\n",
varname, varname, title);
} else {
fprintf(fp,
"<table border=1>\n"
"<tr>"
"<th>%s</th>"
"<th>Down</th>"
"<th>Next</th>"
"<th>Total/Direct (percents)</th>"
"<th>Allocations</th>"
"<th>Fan-in</th>"
"<th>Fan-out</th>"
"</tr>\n",
title);
}
for (i = 0; i < count; i++) {
/* Don't bother with truly puny nodes. */
node = table[i];
if (node->allocs.bytes.total < min_subtotal)
break;
name = tmgraphnode_name(node);
if (js_mode) {
fprintf(fp,
" {name:'%s', dbytes:%ld, tbytes:%ld,"
" dallocs:%ld, tallocs:%ld,\n",
name,
(long) node->allocs.bytes.direct,
(long) node->allocs.bytes.total,
(long) node->allocs.calls.direct,
(long) node->allocs.calls.total);
} else {
namelen = strlen(name);
fprintf(fp,
"<tr>"
"<td valign=top><a name='%s'>%.*s%s</a></td>",
name,
(namelen > 40) ? 40 : (int)namelen, name,
(namelen > 40) ? "<i>...</i>" : "");
if (node->down) {
fprintf(fp,
"<td valign=top><a href='#%s'><i>down</i></a></td>",
tmgraphnode_name(node->down));
} else {
fputs("<td></td>", fp);
}
if (node->next) {
fprintf(fp,
"<td valign=top><a href='#%s'><i>next</i></a></td>",
tmgraphnode_name(node->next));
} else {
fputs("<td></td>", fp);
}
fprintf(fp,
"<td valign=top>%s/%s (%1.2f%%/%1.2f%%)</td>"
"<td valign=top>%s/%s (%1.2f%%/%1.2f%%)</td>",
prettybig(node->allocs.bytes.total, buf1, sizeof buf1),
prettybig(node->allocs.bytes.direct, buf2, sizeof buf2),
percent(node->allocs.bytes.total,
tmr->calltree_root.allocs.bytes.total),
percent(node->allocs.bytes.direct,
tmr->calltree_root.allocs.bytes.total),
prettybig(node->allocs.calls.total, buf3, sizeof buf3),
prettybig(node->allocs.calls.direct, buf4, sizeof buf4),
percent(node->allocs.calls.total,
tmr->calltree_root.allocs.calls.total),
percent(node->allocs.calls.direct,
tmr->calltree_root.allocs.calls.total));
}
/* NB: we must use 'fin' because 'in' is a JS keyword! */
sort_graphlink_list(&node->in, TM_EDGE_IN_LINK);
dump_graphlink_list(node->in, TM_EDGE_IN_LINK, "fin", fp);
sort_graphlink_list(&node->out, TM_EDGE_OUT_LINK);
dump_graphlink_list(node->out, TM_EDGE_OUT_LINK, "out", fp);
if (js_mode)
fputs(" },\n", fp);
else
fputs("</tr>\n", fp);
}
if (js_mode) {
fputs("]};\n", fp);
} else {
fputs("</table>\n<hr>\n", fp);
qsort(table, count, sizeof(tmgraphnode*), mean_size_compare);
fprintf(fp,
"<table border=1>\n"
"<tr><th colspan=4>Direct Allocators</th></tr>\n"
"<tr>"
"<th>%s</th>"
"<th>Mean&nbsp;Size</th>"
"<th>StdDev</th>"
"<th>Allocations<th>"
"</tr>\n",
title);
for (i = 0; i < count; i++) {
double allocs, bytes, mean, variance, sigma;
node = table[i];
allocs = (double)node->allocs.calls.direct;
if (!allocs)
continue;
/* Compute direct-size mean and standard deviation. */
bytes = (double)node->allocs.bytes.direct;
mean = bytes / allocs;
variance = allocs * node->sqsum - bytes * bytes;
if (variance < 0 || allocs == 1)
variance = 0;
else
variance /= allocs * (allocs - 1);
sigma = sqrt(variance);
name = tmgraphnode_name(node);
namelen = strlen(name);
fprintf(fp,
"<tr>"
"<td valign=top>%.*s%s</td>"
"<td valign=top>%s</td>"
"<td valign=top>%s</td>"
"<td valign=top>%s</td>"
"</tr>\n",
(namelen > 65) ? 45 : (int)namelen, name,
(namelen > 65) ? "<i>...</i>" : "",
prettybig((uint32_t)mean, buf1, sizeof buf1),
prettybig((uint32_t)sigma, buf2, sizeof buf2),
prettybig(node->allocs.calls.direct, buf3, sizeof buf3));
}
fputs("</table>\n", fp);
}
free((void*) table);
}
static void my_tmevent_handler(tmreader *tmr, tmevent *event)
{
switch (event->type) {
case TM_EVENT_STATS:
if (js_mode)
break;
fprintf(stdout,
"<p><table border=1>"
"<tr><th>Counter</th><th>Value</th></tr>\n"
"<tr><td>maximum actual stack depth</td><td align=right>%lu</td></tr>\n"
"<tr><td>maximum callsite tree depth</td><td align=right>%lu</td></tr>\n"
"<tr><td>number of parent callsites</td><td align=right>%lu</td></tr>\n"
"<tr><td>maximum kids per parent</td><td align=right>%lu</td></tr>\n"
"<tr><td>hits looking for a kid</td><td align=right>%lu</td></tr>\n"
"<tr><td>misses looking for a kid</td><td align=right>%lu</td></tr>\n"
"<tr><td>steps over other kids</td><td align=right>%lu</td></tr>\n"
"<tr><td>callsite recurrences</td><td align=right>%lu</td></tr>\n"
"<tr><td>number of stack backtraces</td><td align=right>%lu</td></tr>\n"
"<tr><td>backtrace failures</td><td align=right>%lu</td></tr>\n"
"<tr><td>backtrace malloc failures</td><td align=right>%lu</td></tr>\n"
"<tr><td>backtrace dladdr failures</td><td align=right>%lu</td></tr>\n"
"<tr><td>malloc calls</td><td align=right>%lu</td></tr>\n"
"<tr><td>malloc failures</td><td align=right>%lu</td></tr>\n"
"<tr><td>calloc calls</td><td align=right>%lu</td></tr>\n"
"<tr><td>calloc failures</td><td align=right>%lu</td></tr>\n"
"<tr><td>realloc calls</td><td align=right>%lu</td></tr>\n"
"<tr><td>realloc failures</td><td align=right>%lu</td></tr>\n"
"<tr><td>free calls</td><td align=right>%lu</td></tr>\n"
"<tr><td>free(null) calls</td><td align=right>%lu</td></tr>\n"
"</table>",
(unsigned long) event->u.stats.tmstats.calltree_maxstack,
(unsigned long) event->u.stats.tmstats.calltree_maxdepth,
(unsigned long) event->u.stats.tmstats.calltree_parents,
(unsigned long) event->u.stats.tmstats.calltree_maxkids,
(unsigned long) event->u.stats.tmstats.calltree_kidhits,
(unsigned long) event->u.stats.tmstats.calltree_kidmisses,
(unsigned long) event->u.stats.tmstats.calltree_kidsteps,
(unsigned long) event->u.stats.tmstats.callsite_recurrences,
(unsigned long) event->u.stats.tmstats.backtrace_calls,
(unsigned long) event->u.stats.tmstats.backtrace_failures,
(unsigned long) event->u.stats.tmstats.btmalloc_failures,
(unsigned long) event->u.stats.tmstats.dladdr_failures,
(unsigned long) event->u.stats.tmstats.malloc_calls,
(unsigned long) event->u.stats.tmstats.malloc_failures,
(unsigned long) event->u.stats.tmstats.calloc_calls,
(unsigned long) event->u.stats.tmstats.calloc_failures,
(unsigned long) event->u.stats.tmstats.realloc_calls,
(unsigned long) event->u.stats.tmstats.realloc_failures,
(unsigned long) event->u.stats.tmstats.free_calls,
(unsigned long) event->u.stats.tmstats.null_free_calls);
if (event->u.stats.calltree_maxkids_parent) {
tmcallsite *site =
tmreader_callsite(tmr, event->u.stats.calltree_maxkids_parent);
if (site && site->method) {
fprintf(stdout, "<p>callsite with the most kids: %s</p>",
tmmethodnode_name(site->method));
}
}
if (event->u.stats.calltree_maxstack_top) {
tmcallsite *site =
tmreader_callsite(tmr, event->u.stats.calltree_maxstack_top);
fputs("<p>deepest callsite tree path:\n"
"<table border=1>\n"
"<tr><th>Method</th><th>Offset</th></tr>\n",
stdout);
while (site) {
fprintf(stdout,
"<tr><td>%s</td><td>0x%08lX</td></tr>\n",
site->method ? tmmethodnode_name(site->method) : "???",
(unsigned long) site->offset);
site = site->parent;
}
fputs("</table>\n<hr>\n", stdout);
}
break;
}
}
int main(int argc, char **argv)
{
int c, i, j, rv;
tmreader *tmr;
FILE *fp;
program = *argv;
tmr = tmreader_new(program, NULL);
if (!tmr) {
perror(program);
exit(1);
}
while ((c = getopt(argc, argv, "djtuf:m:")) != EOF) {
switch (c) {
case 'd':
sort_by_direct = 1;
break;
case 'j':
js_mode = 1;
break;
case 't':
do_tree_dump = 1;
break;
case 'u':
unified_output = 1;
break;
case 'f':
function_dump = optarg;
break;
case 'm':
min_subtotal = atoi(optarg);
break;
default:
fprintf(stderr,
"usage: %s [-dtu] [-f function-dump-filename] [-m min] [output.html]\n",
program);
exit(2);
}
}
if (!js_mode) {
time_t start = time(NULL);
fprintf(stdout,
"<script language=\"JavaScript\">\n"
"function onload() {\n"
" document.links[0].__proto__.onmouseover = new Function("
"\"window.status ="
" this.href.substring(this.href.lastIndexOf('#') + 1)\");\n"
"}\n"
"</script>\n");
fprintf(stdout, "%s starting at %s", program, ctime(&start));
fflush(stdout);
}
argc -= optind;
argv += optind;
if (argc == 0) {
if (tmreader_eventloop(tmr, "-", my_tmevent_handler) <= 0)
exit(1);
} else {
for (i = j = 0; i < argc; i++) {
fp = fopen(argv[i], "r");
if (!fp) {
fprintf(stderr, "%s: can't open %s: %s\n",
program, argv[i], strerror(errno));
exit(1);
}
rv = tmreader_eventloop(tmr, argv[i], my_tmevent_handler);
if (rv < 0)
exit(1);
if (rv > 0)
j++;
fclose(fp);
}
if (j == 0)
exit(1);
}
compute_callsite_totals(&tmr->calltree_root);
walk_callsite_tree(&tmr->calltree_root, 0, 0, stdout);
if (js_mode) {
fprintf(stdout,
"<script language='javascript'>\n"
"// direct and total byte and allocator-call counts\n"
"var dbytes = %ld, tbytes = %ld,"
" dallocs = %ld, tallocs = %ld;\n",
(long) tmr->calltree_root.allocs.bytes.direct,
(long) tmr->calltree_root.allocs.bytes.total,
(long) tmr->calltree_root.allocs.calls.direct,
(long) tmr->calltree_root.allocs.calls.total);
}
dump_graph(tmr, tmr->libraries, "libraries", "Library", stdout);
if (!js_mode)
fputs("<hr>\n", stdout);
dump_graph(tmr, tmr->components, "classes", "Class or Component", stdout);
if (js_mode || unified_output || function_dump) {
if (js_mode || unified_output || strcmp(function_dump, "-") == 0) {
fp = stdout;
if (!js_mode)
fputs("<hr>\n", fp);
} else {
struct stat sb, fsb;
fstat(fileno(stdout), &sb);
if (stat(function_dump, &fsb) == 0 &&
fsb.st_dev == sb.st_dev && fsb.st_ino == sb.st_ino) {
fp = stdout;
fputs("<hr>\n", fp);
} else {
fp = fopen(function_dump, "w");
if (!fp) {
fprintf(stderr, "%s: can't open %s: %s\n",
program, function_dump, strerror(errno));
exit(1);
}
}
}
dump_graph(tmr, tmr->methods, "methods", "Function or Method", fp);
if (fp != stdout)
fclose(fp);
if (js_mode) {
fputs("function viewnode(graph, index) {\n"
" view.location = viewsrc();\n"
"}\n"
"function viewnodelink(graph, index) {\n"
" var node = graph.nodes[index];\n"
" return '<a href=\"javascript:viewnode('"
" + graph.name.quote() + ', ' + node.sort"
" + ')\" onmouseover=' + node.name.quote() + '>'"
" + node.name + '</a>';\n"
"}\n"
"function search(expr) {\n"
" var re = new RegExp(expr);\n"
" var src = '';\n"
" var graphs = [libraries, classes, methods]\n"
" var nodes;\n"
" for (var n = 0; n < (nodes = graphs[n].nodes).length; n++) {\n"
" for (var i = 0; i < nodes.length; i++) {\n"
" if (re.test(nodes[i].name))\n"
" src += viewnodelink(graph, i) + '\\n';\n"
" }\n"
" }\n"
" view.location = viewsrc();\n"
"}\n"
"function ctrlsrc() {\n"
" return \"<form>\\n"
"search: <input size=40 onchange='search(this.value)'>\\n"
"</form>\\n\";\n"
"}\n"
"function viewsrc() {\n"
" return 'hiiiii'\n"
"}\n"
"</script>\n"
"<frameset rows='10%,*'>\n"
" <frame name='ctrl' src='javascript:top.ctrlsrc()'>\n"
" <frame name='view' src='javascript:top.viewsrc()'>\n"
"</frameset>\n",
stdout);
}
}
exit(0);
}

View File

@ -1,207 +0,0 @@
#!/usr/bin/perl -w
# vim:cindent:ts=8:et:sw=4:
# 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 script produces a diff between two files that are the result of
# calling NS_TraceMallocDumpAllocations. Such files can be created
# through the command-line option --shutdown-leaks=<filename> or through
# the DOM function window.TraceMallocDumpAllocations(<filename>). Both
# methods will work only if --trace-malloc=<malloc-log> is also given on
# the command line.
use 5.004;
use strict;
use Getopt::Long;
$::opt_help = 0;
$::opt_depth = 6;
$::opt_include_zero = 0;
$::opt_allocation_count = 0;
$::opt_use_address = 0;
# XXX Change --use-address to be the default and remove the option
# once tinderbox is no longer using it without --use-address.
Getopt::Long::Configure("pass_through");
Getopt::Long::GetOptions("help", "allocation-count", "depth=i",
"include-zero", "use-address");
if ($::opt_help) {
die "usage: diffbloatdump.pl [options] <dump1> <dump2>
--help Display this message
--allocation-count Use allocation count rather than size (i.e., treat
all sizes as 1).
--depth=<num> Only display <num> frames at top of allocation stack.
--include-zero Display subtrees totalling zero.
--use-address Don't ignore the address part of the stack trace
(can make comparison more accurate when comparing
results from the same build)
The input files (<dump1> and <dump2> above) are either trace-malloc
memory dumps OR this script's output. (If this script's output,
--allocation-count and --use-address are ignored.) If the input files
have .gz or .bz2 extension, they are uncompressed.
";
}
my $calltree = { count => 0 }; # leave children undefined
sub get_child($$) {
my ($node, $frame) = @_;
if (!defined($node->{children})) {
$node->{children} = {};
}
if (!defined($node->{children}->{$frame})) {
$node->{children}->{$frame} = { count => 0 };
}
return $node->{children}->{$frame};
}
sub add_tree_file($$$) {
my ($infile, $firstline, $factor) = @_;
my @nodestack;
$nodestack[1] = $calltree;
$firstline =~ /^(-?\d+) malloc$/;
$calltree->{count} += $1 * $factor;
my $lineno = 1;
while (!eof($infile)) {
my $line = <$infile>;
++$lineno;
$line =~ /^( *)(-?\d+) (.*)$/ || die "malformed input, line $lineno";
my $depth = length($1);
my $count = $2;
my $frame = $3;
die "malformed input, line $lineno" if ($depth % 2 != 0);
$depth /= 2;
die "malformed input, line $lineno" if ($depth > $#nodestack);
$#nodestack = $depth;
my $node = get_child($nodestack[$depth], $frame);
push @nodestack, $node;
$node->{count} += $count * $factor;
}
}
sub add_file($$) {
# Takes (1) a reference to a file descriptor for input and (2) the
# factor to multiply the stacks by (generally +1 or -1).
# Returns a reference to an array representing the stack, allocation
# site in array[0].
sub read_stack($) {
my ($infile) = @_;
my $line;
my @stack;
# read the data at the memory location
while ( defined($infile) && ($line = <$infile>) && substr($line,0,1) eq "\t" ) {
# do nothing
}
# read the stack
do {
chomp($line);
if ( ! $::opt_use_address &&
$line =~ /(.*)\[(.*)\]/) {
$line = $1;
}
$stack[$#stack+1] = $line;
} while ( defined($infile) && ($line = <$infile>) && $line ne "\n" && $line ne "\r\n" );
return \@stack;
}
# adds the stack given as a parameter (reference to array, $stack[0] is
# allocator) to $calltree, with the call count multiplied by $factor
# (typically +1 or -1).
sub add_stack($$) {
my @stack = @{$_[0]};
my $factor = $_[1];
my $i = 0;
my $node = $calltree;
while ($i < $#stack && $i < $::opt_depth) {
$node->{count} += $factor;
$node = get_child($node, $stack[$i]);
++$i;
}
$node->{count} += $factor;
}
my ($infile, $factor) = @_;
if ($infile =~ /\.bz2$/) {
# XXX This doesn't propagate errors from bzip2.
open (INFILE, "bzip2 -cd '$infile' |") || die "Can't open input \"$infile\"";
} elsif ($infile =~ /\.gz$/) {
# XXX This doesn't propagate errors from gzip.
open (INFILE, "gzip -cd '$infile' |") || die "Can't open input \"$infile\"";
} else {
open (INFILE, "<$infile") || die "Can't open input \"$infile\"";
}
my $first = 1;
while ( ! eof(INFILE) ) {
# read the type and address
my $line = <INFILE>;
if ($first) {
$first = 0;
if ($line =~ /^-?\d+ malloc$/) {
# We're capable of reading in our own output as well.
add_tree_file(\*INFILE, $line, $factor);
close INFILE;
return;
}
}
unless ($line =~ /.*\((\d*)\)[\r|\n]/) {
die "badly formed allocation header in $infile";
}
my $size;
if ($::opt_allocation_count) {
$size = 1;
} else {
$size = $1;
}
add_stack(read_stack(\*INFILE), $size * $factor);
}
close INFILE;
}
sub print_node_indent($$$);
sub print_calltree() {
sub print_indent($) {
my ($i) = @_;
while (--$i >= 0) {
print " ";
}
}
sub print_node_indent($$$) {
my ($nodename, $node, $indent) = @_;
if (!$::opt_include_zero && $node->{count} == 0) {
return;
}
print_indent($indent);
print "$node->{count} $nodename\n";
if (defined($node->{children})) {
my %kids = %{$node->{children}};
++$indent;
foreach my $kid (sort { $kids{$b}->{count} <=> $kids{$a}->{count} }
keys (%kids)) {
print_node_indent($kid, $kids{$kid}, $indent);
}
}
}
print_node_indent("malloc", $calltree, 0);
}
add_file($ARGV[0], -1);
add_file($ARGV[1], 1);
print_calltree();

View File

@ -1,204 +0,0 @@
/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
*
* 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/. */
/*
** formdata.c
**
** Play utility to parse up form get data into name value pairs.
*/
#include "formdata.h"
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
static void unhexcape(char* inPlace)
/*
** Real low tech unhexcaper....
**
** inPlace string to decode, in place as it were.
*/
{
if(NULL != inPlace)
{
int index1 = 0;
int index2 = 0;
int theLen = strlen(inPlace);
for(; index1 <= theLen; index1++)
{
if('%' == inPlace[index1] && '\0' != inPlace[index1 + 1] && '\0' != inPlace[index1 + 2])
{
int unhex = 0;
if('9' >= inPlace[index1 + 1])
{
unhex |= ((inPlace[index1 + 1] - '0') << 4);
}
else
{
unhex |= ((toupper(inPlace[index1 + 1]) - 'A' + 10) << 4);
}
if('9' >= inPlace[index1 + 2])
{
unhex |= (inPlace[index1 + 2] - '0');
}
else
{
unhex |= (toupper(inPlace[index1 + 2]) - 'A' + 10);
}
index1 += 2;
inPlace[index1] = unhex;
}
inPlace[index2++] = inPlace[index1];
}
}
}
FormData* FormData_Create(const char* inFormData)
{
FormData* retval = NULL;
if(NULL != inFormData)
{
FormData* container = NULL;
/*
** Allocate form data container.
*/
container = (FormData*)calloc(1, sizeof(FormData));
if(NULL != container)
{
/*
** Dup the incoming form data.
*/
container->mStorage = strdup(inFormData);
if(NULL != container->mStorage)
{
char* traverse = NULL;
unsigned nvpairs = 1;
unsigned storeLen = 0;
/*
** Count the number of pairs we are going to have.
** We do this by counting '&' + 1.
*/
for(traverse = container->mStorage; '\0' != *traverse; traverse++)
{
if('&' == *traverse)
{
nvpairs++;
}
}
storeLen = (unsigned)(traverse - container->mStorage);
/*
** Allocate space for our names and values.
*/
container->mNArray = (char**)calloc(nvpairs * 2, sizeof(char*));
if(NULL != container->mNArray)
{
char* amp = NULL;
char* equ = NULL;
container->mVArray = &container->mNArray[nvpairs];
/*
** Go back over the storage.
** Fill in the names and values as we go.
** Terminate on dividing '=' and '&' characters.
** Increase the count of items as we go.
*/
for(traverse = container->mStorage; NULL != traverse; container->mNVCount++)
{
container->mNArray[container->mNVCount] = traverse;
amp = strchr(traverse, '&');
equ = strchr(traverse, '=');
traverse = NULL;
if(NULL != equ && (NULL == amp || equ < amp))
{
*equ++ = '\0';
container->mVArray[container->mNVCount] = equ;
}
else
{
container->mVArray[container->mNVCount] = (container->mStorage + storeLen);
}
if(NULL != amp)
{
*amp++ = '\0';
traverse = amp;
}
unhexcape(container->mNArray[container->mNVCount]);
unhexcape(container->mVArray[container->mNVCount]);
}
retval = container;
}
}
}
/*
** If we failed, cleanup.
*/
if(NULL == retval)
{
FormData_Destroy(container);
}
}
return retval;
}
void FormData_Destroy(FormData* inDestroy)
{
if(NULL != inDestroy)
{
unsigned traverse = 0;
for(traverse = 0; traverse < inDestroy->mNVCount; traverse++)
{
if(NULL != inDestroy->mNArray)
{
inDestroy->mNArray[traverse] = NULL;
}
if(NULL != inDestroy->mVArray)
{
inDestroy->mVArray[traverse] = NULL;
}
}
inDestroy->mNVCount = 0;
if(NULL != inDestroy->mStorage)
{
free(inDestroy->mStorage);
inDestroy->mStorage = NULL;
}
if(NULL != inDestroy->mNArray)
{
free(inDestroy->mNArray);
inDestroy->mNArray = NULL;
inDestroy->mVArray = NULL;
}
free(inDestroy);
inDestroy = NULL;
}
}

View File

@ -1,64 +0,0 @@
/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
*
* 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/. */
#if !defined(__formdata_H__)
#define __formdata_H__
/*
** formdata.h
**
** Play (quick and dirty) utility API to parse up form get data into
** name value pairs.
*/
typedef struct __struct_FormData
/*
** Structure to hold the breakdown of any form data.
**
** mNArray Each form datum is a name value pair.
** This array holds the names.
** You can find the corresponding value at the same index in
** mVArray.
** Never NULL, but perhpas an empty string.
** mVArray Each form datum is a name value pair.
** This array holds the values.
** You can find the corresponding name at the same index in
** mNArray.
** Never NULL, but perhpas an empty string.
** mNVCount Count of array items in both mNArray and mVArray.
** mStorage Should be ignored by users of this API.
** In reality holds the duped and decoded form data.
*/
{
char** mNArray;
char** mVArray;
unsigned mNVCount;
char* mStorage;
}
FormData;
FormData* FormData_Create(const char* inFormData)
/*
** Take a contiguous string of form data, possibly hex encoded, and return
** the name value pairs parsed up and decoded.
** A caller of this routine should call FormData_Destroy at some point.
**
** inFormData The form data to parse up and decode.
** returns FormData* The result of our effort.
** This should be passed to FormData_Destroy at some
** point of the memory will be leaked.
*/
;
void FormData_Destroy(FormData* inDestroy)
/*
** Release to the heap the structure previously created via FormData_Create.
**
** inDestroy The object to free off.
*/
;
#endif /* __formdata_H__ */

View File

@ -1,107 +0,0 @@
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/* 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/. */
/*
* Copyright (c) 1987 Regents of the University of California.
* All rights reserved.
*
* Redistribution and use in source and binary forms are permitted
* provided that: (1) source distributions retain this entire copyright
* notice and comment, and (2) distributions including binaries display
* the following acknowledgement: ``This product includes software
* developed by the University of California, Berkeley and its contributors''
* in the documentation or other materials provided with the distribution
* and in all advertising materials mentioning features or use of this
* software. Neither the name of the University nor the names of its
* contributors may be used to endorse or promote products derived
* from this software without specific prior written permission.
* THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR
* IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
*/
#if defined(LIBC_SCCS) && !defined(lint)
static char sccsid[] = "@(#)getopt.c 4.12 (Berkeley) 6/1/90";
#endif /* LIBC_SCCS and not lint */
#include <stdio.h>
#include <string.h>
#define index strchr
#define rindex strrchr
/*
* get option letter from argument vector
*/
int opterr = 1, /* if error message should be printed */
optind = 1, /* index into parent argv vector */
optopt; /* character checked for validity */
char *optarg; /* argument associated with option */
#define BADCH (int)'?'
#define EMSG ""
int getopt(int nargc, char **nargv, char *ostr)
{
static char *place = EMSG; /* option letter processing */
register char *oli; /* option letter list index */
char *p;
if (!*place) { /* update scanning pointer */
if (optind >= nargc || *(place = nargv[optind]) != '-') {
place = EMSG;
return(EOF);
}
if (place[1] && *++place == '-') { /* found "--" */
++optind;
place = EMSG;
return(EOF);
}
} /* option letter okay? */
if ((optopt = (int)*place++) == (int)':' ||
!(oli = index(ostr, optopt))) {
/*
* if the user didn't specify '-' as an option,
* assume it means EOF.
*/
if (optopt == (int)'-')
return(EOF);
if (!*place)
++optind;
if (opterr) {
if (!(p = rindex(*nargv, '/')))
p = *nargv;
else
++p;
(void)fprintf(stderr, "%s: illegal option -- %c\n",
p, optopt);
}
return(BADCH);
}
if (*++oli != ':') { /* don't need argument */
optarg = NULL;
if (!*place)
++optind;
}
else { /* need an argument */
if (*place) /* no white space */
optarg = place;
else if (nargc <= ++optind) { /* no arg */
place = EMSG;
if (!(p = rindex(*nargv, '/')))
p = *nargv;
else
++p;
if (opterr)
(void)fprintf(stderr,
"%s: option requires an argument -- %c\n",
p, optopt);
return(BADCH);
}
else /* white space */
optarg = nargv[optind];
place = EMSG;
++optind;
}
return(optopt); /* dump back option letter */
}

View File

@ -1,76 +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/.
# histogram-diff.sh [-c <count>] <base> <incr>
#
# Compute incremental memory growth from histogram in file <base> to
# histogram in file <incr>, displaying at most <count> rows.
# How many rows are we gonna show?
COUNT=20
# Read arguments
while [ $# -gt 0 ]; do
case "$1" in
-c) COUNT=$2
shift 2
;;
*) break
;;
esac
done
BASE=$1
INCR=$2
# Sort the base and incremental files so that we can `join' them on
# the type name
sort $BASE > /tmp/$$.left
sort $INCR > /tmp/$$.right
# Do the join. The `awk' script computes the difference between
# the base and the incremental files.
join /tmp/$$.left /tmp/$$.right \
| awk '{ print $1, $2, $3, $4, $5, $4 - $2, $5 - $3; }' \
> /tmp/$$.joined
rm -f /tmp/$$.left /tmp/$$.right
# Now compute a `TOTAL' row.
awk '{ tobj1 += $2; tbytes1 += $3; tobj2 += $4; tbytes2 += $5; tdobj += $6; tdbytes += $7; } END { print "TOTAL", tobj1, tbytes1, tobj2, tbytes2, tdobj, tdbytes; }' /tmp/$$.joined \
> /tmp/$$.sorted
# Then, we sort by the largest delta in bytes.
sort -nr +6 /tmp/$$.joined >> /tmp/$$.sorted
rm -f /tmp/$$.joined
# Pretty-print, including percentages
cat <<EOF > /tmp/$$.awk
BEGIN {
print " ---- Base ---- ---- Incr ---- ----- Difference ----";
print "Type Count Bytes Count Bytes Count Bytes %Total";
}
\$1 == "TOTAL" {
tbytes = \$7;
}
NR <= $COUNT {
printf "%-22s %6d %8d %6d %8d %6d %8d %6.2lf\n", \$1, \$2, \$3, \$4, \$5, \$6, \$7, 100.0 * \$7 / tbytes;
}
NR > $COUNT {
oobjs1 += \$2; obytes1 += \$3;
oobjs2 += \$4; obytes2 += \$5;
odobjs += \$6; odbytes += \$7;
}
END {
printf "%-22s %6d %8d %6d %8d %6d %8d %6.2lf\n", "OTHER", oobjs1, obytes1, oobjs2, obytes2, odobjs, odbytes, odbytes * 100.0 / tbytes;
}
EOF
# Now pretty print the file, and spit it out on stdout.
awk -f /tmp/$$.awk /tmp/$$.sorted
rm -f /tmp/$$.awk /tmp/$$.sorted

View File

@ -1,63 +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/.
# histogram-pretty.sh [-c <count>] [-w <width>] <file>
#
# Pretty-print the histogram in file <file>, displaying at most
# <count> rows.
# How many rows are we gonna show?
COUNT=20
WIDTH=22
# Read arguments
while [ $# -gt 0 ]; do
case "$1" in
-c) COUNT=$2
shift 2
;;
-w) WIDTH=$2
shift 2
;;
*) break
;;
esac
done
FILE=$1
# The first `awk' script computes a `TOTAL' row. Then, we sort by the
# larges delta in bytes.
awk '{ tobj += $2; tbytes += $3; } END { print "TOTAL", tobj, tbytes; }' ${FILE} > /tmp/$$.sorted
sort -nr +2 ${FILE} >> /tmp/$$.sorted
# Pretty-print, including percentages
cat <<EOF > /tmp/$$.awk
BEGIN {
printf "%-${WIDTH}s Count Bytes %Total %Cov\n", "Type";
}
\$1 == "TOTAL" {
tbytes = \$3;
}
NR <= $COUNT {
if (\$1 != "TOTAL") {
covered += \$3;
}
printf "%-${WIDTH}s %6d %8d %6.2lf %6.2lf\n", \$1, \$2, \$3, 100.0 * \$3 / tbytes, 100.0 * covered / tbytes;
}
NR > $COUNT {
oobjs += \$2; obytes += \$3; covered += \$3;
}
END {
printf "%-${WIDTH}s %6d %8d %6.2lf %6.2lf\n", "OTHER", oobjs, obytes, obytes * 100.0 / tbytes, covered * 100.0 / tbytes;
}
EOF
# Now pretty print the file, and spit it out on stdout.
awk -f /tmp/$$.awk /tmp/$$.sorted
rm -f /tmp/$$.awk /tmp/$$.sorted

View File

@ -1,64 +0,0 @@
#!/usr/bin/perl -w
#
# 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 produces a ``class histogram'' of the live objects, one
# line per class, with the total number of objects allocated, and
# total number of bytes attributed to those objects.
use 5.004;
use strict;
use Getopt::Long;
# So we can find TraceMalloc.pm
use FindBin;
use lib "$FindBin::Bin";
use TraceMalloc;
# Collect program options
$::opt_help = 0;
$::opt_types = "${FindBin::Bin}/types.dat";
GetOptions("help", "types=s");
if ($::opt_help) {
die "usage: histogram.pl [options] <dumpfile>
--help Display this message
--types=<file> Read type heuristics from <file>";
}
# Initialize type inference juju from the type file specified by
# ``--types''.
if ($::opt_types) {
TraceMalloc::init_type_inference($::opt_types);
}
# Read the dump file, collecting count and size information for each
# object that's detected.
# This'll hold a record for each class that we detect
$::Classes = { };
sub collect_objects($) {
my ($object) = @_;
my $type = $object->{'type'};
my $entry = $::Classes{$type};
if (! $entry) {
$entry = $::Classes{$type} = { '#count#' => 0, '#bytes#' => 0 };
}
$entry->{'#count#'} += 1;
$entry->{'#bytes#'} += $object->{'size'};
}
TraceMalloc::read(\&collect_objects);
# Print one line per class, sorted with the classes that accumulated
# the most bytes first.
foreach my $class (sort { $::Classes{$b}->{'#bytes#'} <=> $::Classes{$a}->{'#bytes#'} } keys %::Classes) {
print "$class $::Classes{$class}->{'#count#'} $::Classes{$class}->{'#bytes#'}\n";
}

File diff suppressed because it is too large Load Diff

View File

@ -1,416 +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 "adreader.h"
#include <stdio.h>
#include "plhash.h"
#include "nsTArray.h"
#include "nsQuickSort.h"
#include "nsXPCOM.h"
const uint32_t kPointersDefaultSize = 8;
/*
* Read in an allocation dump, presumably one taken at shutdown (using
* the --shutdown-leaks=file option, which must be used along with
* --trace-malloc=tmlog), and treat the memory in the dump as leaks.
* Find the leak roots, including cycles that are roots, by finding the
* strongly connected components in the graph. Print output to stdout
* as HTML.
*/
struct AllocationNode {
const ADLog::Entry *entry;
// Other |AllocationNode| objects whose memory has a pointer to
// this object.
nsAutoTArray<AllocationNode*, kPointersDefaultSize> pointers_to;
// The reverse.
nsAutoTArray<AllocationNode*, kPointersDefaultSize> pointers_from;
// Early on in the algorithm, the pre-order index from a DFS.
// Later on, set to the index of the strongly connected component to
// which this node belongs.
uint32_t index;
bool reached;
bool is_root;
};
static PLHashNumber hash_pointer(const void *key)
{
return (PLHashNumber) NS_PTR_TO_INT32(key);
}
static int sort_by_index(const void* e1, const void* e2, void*)
{
const AllocationNode *n1 = *static_cast<const AllocationNode*const*>(e1);
const AllocationNode *n2 = *static_cast<const AllocationNode*const*>(e2);
return n1->index - n2->index;
}
static int sort_by_reverse_index(const void* e1, const void* e2, void*)
{
const AllocationNode *n1 = *static_cast<const AllocationNode*const*>(e1);
const AllocationNode *n2 = *static_cast<const AllocationNode*const*>(e2);
return n2->index - n1->index;
}
static void print_escaped(FILE *aStream, const char* aData)
{
char c;
char buf[1000];
char *p = buf;
while ((c = *aData++)) {
switch (c) {
#define CH(char) *p++ = char
case '<':
CH('&'); CH('l'); CH('t'); CH(';');
break;
case '>':
CH('&'); CH('g'); CH('t'); CH(';');
break;
case '&':
CH('&'); CH('a'); CH('m'); CH('p'); CH(';');
break;
default:
CH(c);
break;
#undef CH
}
if (p + 10 > buf + sizeof(buf)) {
*p = '\0';
fputs(buf, aStream);
p = buf;
}
}
*p = '\0';
fputs(buf, aStream);
}
static const char *allocation_format =
(sizeof(ADLog::Pointer) == 4) ? "0x%08zX" :
(sizeof(ADLog::Pointer) == 8) ? "0x%016zX" :
"UNEXPECTED sizeof(void*)";
int main(int argc, char **argv)
{
if (argc != 2) {
fprintf(stderr,
"Expected usage: %s <sd-leak-file>\n"
" sd-leak-file: Output of --shutdown-leaks=<file> option.\n",
argv[0]);
return 1;
}
NS_InitXPCOM2(nullptr, nullptr, nullptr);
ADLog log;
if (!log.Read(argv[1])) {
fprintf(stderr,
"%s: Error reading input file %s.\n", argv[0], argv[1]);
}
const size_t count = log.count();
PLHashTable *memory_map =
PL_NewHashTable(count * 8, hash_pointer, PL_CompareValues,
PL_CompareValues, 0, 0);
if (!memory_map) {
fprintf(stderr, "%s: Out of memory.\n", argv[0]);
return 1;
}
// Create one |AllocationNode| object for each log entry, and create
// entries in the hashtable pointing to it for each byte it occupies.
AllocationNode *nodes = new AllocationNode[count];
if (!nodes) {
fprintf(stderr, "%s: Out of memory.\n", argv[0]);
return 1;
}
{
AllocationNode *cur_node = nodes;
for (ADLog::const_iterator entry = log.begin(), entry_end = log.end();
entry != entry_end; ++entry, ++cur_node) {
const ADLog::Entry *e = cur_node->entry = *entry;
cur_node->reached = false;
for (ADLog::Pointer p = e->address,
p_end = e->address + e->datasize;
p != p_end; ++p) {
PLHashEntry *he = PL_HashTableAdd(memory_map, p, cur_node);
if (!he) {
fprintf(stderr, "%s: Out of memory.\n", argv[0]);
return 1;
}
}
}
}
// Construct graph based on pointers.
for (AllocationNode *node = nodes, *node_end = nodes + count;
node != node_end; ++node) {
const ADLog::Entry *e = node->entry;
for (const char *d = e->data, *d_end = e->data + e->datasize -
e->datasize % sizeof(ADLog::Pointer);
d != d_end; d += sizeof(ADLog::Pointer)) {
AllocationNode *target = (AllocationNode*)
PL_HashTableLookup(memory_map, *(void**)d);
if (target) {
target->pointers_from.AppendElement(node);
node->pointers_to.AppendElement(target);
}
}
}
// Do a depth-first search on the graph (i.e., by following
// |pointers_to|) and assign the post-order index to |index|.
{
uint32_t dfs_index = 0;
nsTArray<AllocationNode*> stack;
for (AllocationNode *n = nodes, *n_end = nodes+count; n != n_end; ++n) {
if (n->reached) {
continue;
}
stack.AppendElement(n);
do {
uint32_t pos = stack.Length() - 1;
AllocationNode *n = stack[pos];
if (n->reached) {
n->index = dfs_index++;
stack.RemoveElementAt(pos);
} else {
n->reached = true;
// When doing post-order processing, we have to be
// careful not to put reached nodes into the stack.
for (int32_t i = n->pointers_to.Length() - 1; i >= 0; --i) {
AllocationNode* e = n->pointers_to[i];
if (!e->reached) {
stack.AppendElement(e);
}
}
}
} while (stack.Length() > 0);
}
}
// Sort the nodes by their DFS index, in reverse, so that the first
// node is guaranteed to be in a root SCC.
AllocationNode **sorted_nodes = new AllocationNode*[count];
if (!sorted_nodes) {
fprintf(stderr, "%s: Out of memory.\n", argv[0]);
return 1;
}
{
for (size_t i = 0; i < count; ++i) {
sorted_nodes[i] = nodes + i;
}
NS_QuickSort(sorted_nodes, count, sizeof(AllocationNode*),
sort_by_reverse_index, 0);
}
// Put the nodes into their strongly-connected components.
uint32_t num_sccs = 0;
{
for (size_t i = 0; i < count; ++i) {
nodes[i].reached = false;
}
nsTArray<AllocationNode*> stack;
for (AllocationNode **sn = sorted_nodes,
**sn_end = sorted_nodes + count; sn != sn_end; ++sn) {
if ((*sn)->reached) {
continue;
}
// We found a new strongly connected index.
stack.AppendElement(*sn);
do {
uint32_t pos = stack.Length() - 1;
AllocationNode *n = stack[pos];
stack.RemoveElementAt(pos);
if (!n->reached) {
n->reached = true;
n->index = num_sccs;
stack.AppendElements(n->pointers_from);
}
} while (stack.Length() > 0);
++num_sccs;
}
}
// Identify which nodes are leak roots by using DFS, and watching
// for component transitions.
uint32_t num_root_nodes = count;
{
for (size_t i = 0; i < count; ++i) {
nodes[i].is_root = true;
}
nsTArray<AllocationNode*> stack;
for (AllocationNode *n = nodes, *n_end = nodes+count; n != n_end; ++n) {
if (!n->is_root) {
continue;
}
// Loop through pointers_to, and add any that are in a
// different SCC to stack:
for (int i = n->pointers_to.Length() - 1; i >= 0; --i) {
AllocationNode *target = n->pointers_to[i];
if (n->index != target->index) {
stack.AppendElement(target);
}
}
while (stack.Length() > 0) {
uint32_t pos = stack.Length() - 1;
AllocationNode *n = stack[pos];
stack.RemoveElementAt(pos);
if (n->is_root) {
n->is_root = false;
--num_root_nodes;
stack.AppendElements(n->pointers_to);
}
}
}
}
// Sort the nodes by their SCC index.
NS_QuickSort(sorted_nodes, count, sizeof(AllocationNode*),
sort_by_index, 0);
// Print output.
{
printf("<!DOCTYPE html PUBLIC \"-//W3C//DTD HTML 4.01//EN\">\n"
"<html>\n"
"<head>\n"
"<title>Leak analysis</title>\n"
"<style type=\"text/css\">\n"
" .root { background: white; color: black; }\n"
" .nonroot { background: #ccc; color: black; }\n"
"</style>\n"
"</head>\n");
printf("<body>\n\n"
"<p>Generated %zd entries (%d in root SCCs) and %d SCCs.</p>\n\n",
count, num_root_nodes, num_sccs);
for (size_t i = 0; i < count; ++i) {
nodes[i].reached = false;
}
// Loop over the sorted nodes twice, first printing the roots
// and then the non-roots.
for (int32_t root_type = true;
root_type == true || root_type == false; --root_type) {
if (root_type) {
printf("\n\n"
"<div class=\"root\">\n"
"<h1 id=\"root\">Root components</h1>\n");
} else {
printf("\n\n"
"<div class=\"nonroot\">\n"
"<h1 id=\"nonroot\">Non-root components</h1>\n");
}
uint32_t component = (uint32_t)-1;
bool one_object_component;
for (const AllocationNode *const* sn = sorted_nodes,
*const* sn_end = sorted_nodes + count;
sn != sn_end; ++sn) {
const AllocationNode *n = *sn;
if (n->is_root != root_type)
continue;
const ADLog::Entry *e = n->entry;
if (n->index != component) {
component = n->index;
one_object_component =
sn + 1 == sn_end || (*(sn+1))->index != component;
if (!one_object_component)
printf("\n\n<h2 id=\"c%d\">Component %d</h2>\n",
component, component);
}
if (one_object_component) {
printf("\n\n<div id=\"c%d\">\n", component);
printf("<h2 id=\"o%td\">Object %td "
"(single-object component %d)</h2>\n",
n-nodes, n-nodes, component);
} else {
printf("\n\n<h3 id=\"o%td\">Object %td</h3>\n",
n-nodes, n-nodes);
}
printf("<pre>\n");
printf("%p &lt;%s&gt; (%zd)\n",
e->address, e->type, e->datasize);
for (size_t d = 0; d < e->datasize;
d += sizeof(ADLog::Pointer)) {
AllocationNode *target = (AllocationNode*)
PL_HashTableLookup(memory_map, *(void**)(e->data + d));
if (target) {
printf(" <a href=\"#o%td\">",
target - nodes);
printf(allocation_format,
*(size_t*)(e->data + d));
printf("</a> &lt;%s&gt;",
target->entry->type);
if (target->index != n->index) {
printf(", component %d", target->index);
}
printf("\n");
} else {
printf(" ");
printf(allocation_format,
*(size_t*)(e->data + d));
printf("\n");
}
}
if (n->pointers_from.Length()) {
printf("\nPointers from:\n");
for (uint32_t i = 0, i_end = n->pointers_from.Length();
i != i_end; ++i) {
AllocationNode *t = n->pointers_from[i];
const ADLog::Entry *te = t->entry;
printf(" <a href=\"#o%td\">%s</a> (Object %td, ",
t - nodes, te->type, t - nodes);
if (t->index != n->index) {
printf("component %d, ", t->index);
}
if (t == n) {
printf("self)\n");
} else {
printf("%p)\n", te->address);
}
}
}
print_escaped(stdout, e->allocation_stack);
printf("</pre>\n");
if (one_object_component) {
printf("</div>\n");
}
}
printf("</div>\n");
}
printf("</body>\n"
"</html>\n");
}
delete [] sorted_nodes;
delete [] nodes;
NS_ShutdownXPCOM(nullptr);
return 0;
}

View File

@ -1,161 +0,0 @@
/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
*
* 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 <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#ifdef HAVE_GETOPT_H
#include <getopt.h>
#else
extern int getopt(int argc, char *const *argv, const char *shortopts);
extern char *optarg;
extern int optind;
#ifdef XP_WIN32
int optind=1;
#endif
#endif
#include <time.h>
#include "nsTraceMalloc.h"
#include "tmreader.h"
#include "prlog.h"
static char *program;
typedef struct handler_data {
uint32_t current_heapsize;
uint32_t max_heapsize;
uint32_t bytes_allocated;
uint32_t current_allocations;
uint32_t total_allocations;
uint32_t unmatched_frees;
int finished;
} handler_data;
static void handler_data_init(handler_data *data)
{
data->current_heapsize = 0;
data->max_heapsize = 0;
data->bytes_allocated = 0;
data->current_allocations = 0;
data->total_allocations = 0;
data->unmatched_frees = 0;
data->finished = 0;
}
static void handler_data_finish(handler_data *data)
{
}
static void my_tmevent_handler(tmreader *tmr, tmevent *event)
{
handler_data *data = (handler_data*) tmr->data;
switch (event->type) {
case TM_EVENT_REALLOC:
/* On Windows, original allocation could be before we overrode malloc */
if (event->u.alloc.oldserial != 0) {
data->current_heapsize -= event->u.alloc.oldsize;
--data->current_allocations;
} else {
++data->unmatched_frees;
PR_ASSERT(event->u.alloc.oldsize == 0);
}
/* fall-through intentional */
case TM_EVENT_MALLOC:
case TM_EVENT_CALLOC:
++data->current_allocations;
++data->total_allocations;
data->bytes_allocated += event->u.alloc.size;
data->current_heapsize += event->u.alloc.size;
if (data->current_heapsize > data->max_heapsize)
data->max_heapsize = data->current_heapsize;
break;
case TM_EVENT_FREE:
/* On Windows, original allocation could be before we overrode malloc */
if (event->serial != 0) {
--data->current_allocations;
data->current_heapsize -= event->u.alloc.size;
} else {
++data->unmatched_frees;
PR_ASSERT(event->u.alloc.size == 0);
}
break;
case TM_EVENT_STATS:
data->finished = 1;
break;
}
}
int main(int argc, char **argv)
{
int i, j, rv;
tmreader *tmr;
FILE *fp;
time_t start;
handler_data data;
program = *argv;
handler_data_init(&data);
tmr = tmreader_new(program, &data);
if (!tmr) {
perror(program);
exit(1);
}
start = time(NULL);
fprintf(stdout, "%s starting at %s", program, ctime(&start));
fflush(stdout);
argc -= optind;
argv += optind;
if (argc == 0) {
if (tmreader_eventloop(tmr, "-", my_tmevent_handler) <= 0)
exit(1);
} else {
for (i = j = 0; i < argc; i++) {
fp = fopen(argv[i], "r");
if (!fp) {
fprintf(stderr,
"TEST-UNEXPECTED-FAIL | leakstats | can't open %s: %s\n",
argv[i], strerror(errno));
exit(1);
}
rv = tmreader_eventloop(tmr, argv[i], my_tmevent_handler);
if (rv < 0)
exit(1);
if (rv > 0)
j++;
fclose(fp);
}
if (j == 0)
exit(1);
}
if (!data.finished) {
fprintf(stderr, "TEST-UNEXPECTED-FAIL | leakstats | log file incomplete\n");
exit(1);
}
fprintf(stdout,
"Leaks: %u bytes, %u allocations\n"
"Maximum Heap Size: %u bytes\n"
"%u bytes were allocated in %u allocations.\n",
data.current_heapsize, data.current_allocations,
data.max_heapsize,
data.bytes_allocated, data.total_allocations);
if (data.unmatched_frees != 0)
fprintf(stdout,
"Logged %u free (or realloc) calls for which we missed the "
"original malloc.\n",
data.unmatched_frees);
handler_data_finish(&data);
tmreader_destroy(tmr);
exit(0);
}

View File

@ -1,34 +0,0 @@
# -*- Mode: python; c-basic-offset: 4; 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 += [
'nsTraceMalloc.h',
]
SOURCES += [
'nsTraceMalloc.c',
]
SOURCES += [
'nsTypeInfo.cpp',
]
if CONFIG['OS_ARCH'] == 'WINNT':
SOURCES += [
'nsDebugHelpWin32.cpp',
'nsWinTraceMalloc.cpp',
]
FINAL_LIBRARY = 'xul'
if CONFIG['WRAP_SYSTEM_INCLUDES']:
DEFINES['WRAP_SYSTEM_INCLUDES'] = True
DEFINES['MOZ_NO_MOZALLOC'] = True
DEFFILE = SRCDIR + '/tm.def'
DISABLE_STL_WRAPPING = True

View File

@ -1,372 +0,0 @@
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/* 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/. */
#if defined(_WIN32) && (defined(_M_IX86) || defined(_M_X64))
// This is the .cpp file where the globals live
#define DHW_IMPLEMENT_GLOBALS
#include <stdio.h>
#include "prprf.h"
#include "prlog.h"
#include "plstr.h"
#include "prlock.h"
#include "nscore.h"
#include "nsDebugHelpWin32.h"
#else
#error "nsDebugHelpWin32.cpp should only be built in Win32 x86/x64 builds"
#endif
/***************************************************************************/
PRLock* DHWImportHooker::gLock = nullptr;
DHWImportHooker* DHWImportHooker::gHooks = nullptr;
decltype(GetProcAddress)* DHWImportHooker::gRealGetProcAddress = nullptr;
static bool
dhwEnsureImageHlpInitialized()
{
static bool gInitialized = false;
static bool gTried = false;
if (!gInitialized && !gTried) {
gTried = true;
HMODULE module = ::LoadLibrary("DBGHELP.DLL");
if (!module) {
DWORD dw = GetLastError();
printf("DumpStack Error: DBGHELP.DLL wasn't found. GetLastError() returned 0x%8.8X\n"
" This DLL is needed for succeessfully implementing trace-malloc.\n"
" This dll ships by default on Win2k. Disabling trace-malloc functionality.\n"
, dw);
return false;
}
#define INIT_PROC(typename_, name_) \
dhw##name_ = (decltype(name_)*) ::GetProcAddress(module, #name_); \
if(!dhw##name_) return false;
#ifdef _WIN64
INIT_PROC(ENUMERATELOADEDMODULES64, EnumerateLoadedModules64);
#else
INIT_PROC(ENUMERATELOADEDMODULES, EnumerateLoadedModules);
#endif
INIT_PROC(IMAGEDIRECTORYENTRYTODATA, ImageDirectoryEntryToData);
#undef INIT_PROC
gInitialized = true;
}
return gInitialized;
}
DHWImportHooker&
DHWImportHooker::getGetProcAddressHooker()
{
static DHWImportHooker gGetProcAddress("Kernel32.dll", "GetProcAddress",
(PROC)DHWImportHooker::GetProcAddress);
return gGetProcAddress;
}
DHWImportHooker&
DHWImportHooker::getLoadLibraryWHooker()
{
static DHWImportHooker gLoadLibraryW("Kernel32.dll", "LoadLibraryW",
(PROC)DHWImportHooker::LoadLibraryW);
return gLoadLibraryW;
}
DHWImportHooker&
DHWImportHooker::getLoadLibraryExWHooker()
{
static DHWImportHooker gLoadLibraryExW("Kernel32.dll", "LoadLibraryExW",
(PROC)DHWImportHooker::LoadLibraryExW);
return gLoadLibraryExW;
}
DHWImportHooker&
DHWImportHooker::getLoadLibraryAHooker()
{
static DHWImportHooker gLoadLibraryA("Kernel32.dll", "LoadLibraryA",
(PROC)DHWImportHooker::LoadLibraryA);
return gLoadLibraryA;
}
DHWImportHooker&
DHWImportHooker::getLoadLibraryExAHooker()
{
static DHWImportHooker gLoadLibraryExA("Kernel32.dll", "LoadLibraryExA",
(PROC)DHWImportHooker::LoadLibraryExA);
return gLoadLibraryExA;
}
static HMODULE ThisModule()
{
MEMORY_BASIC_INFORMATION info;
return VirtualQuery(ThisModule, &info, sizeof(info)) ?
(HMODULE) info.AllocationBase : nullptr;
}
DHWImportHooker::DHWImportHooker(const char* aModuleName,
const char* aFunctionName,
PROC aHook,
bool aExcludeOurModule /* = false */)
: mNext(nullptr),
mModuleName(aModuleName),
mFunctionName(aFunctionName),
mOriginal(nullptr),
mHook(aHook),
mIgnoreModule(aExcludeOurModule ? ThisModule() : nullptr),
mHooking(true)
{
//printf("DHWImportHooker hooking %s, function %s\n",aModuleName, aFunctionName);
if(!gLock)
gLock = PR_NewLock();
PR_Lock(gLock);
dhwEnsureImageHlpInitialized(); // for the extra ones we care about.
if(!gRealGetProcAddress)
gRealGetProcAddress = ::GetProcAddress;
mOriginal = gRealGetProcAddress(::GetModuleHandleA(aModuleName),
aFunctionName),
mNext = gHooks;
gHooks = this;
PatchAllModules();
PR_Unlock(gLock);
}
DHWImportHooker::~DHWImportHooker()
{
PR_Lock(gLock);
mHooking = false;
PatchAllModules();
for (DHWImportHooker **cur = &gHooks;
(PR_ASSERT(*cur), *cur); /* assert that we find this */
cur = &(*cur)->mNext)
{
if (*cur == this)
{
*cur = mNext;
break;
}
}
if(!gHooks)
{
PRLock* theLock = gLock;
gLock = nullptr;
PR_Unlock(theLock);
PR_DestroyLock(theLock);
}
if (gLock)
PR_Unlock(gLock);
}
#ifdef _WIN64
static BOOL CALLBACK ModuleEnumCallback(PCSTR ModuleName,
DWORD64 ModuleBase,
ULONG ModuleSize,
PVOID UserContext)
#else
static BOOL CALLBACK ModuleEnumCallback(PCSTR ModuleName,
ULONG ModuleBase,
ULONG ModuleSize,
PVOID UserContext)
#endif
{
//printf("Module Name %s\n",ModuleName);
DHWImportHooker* self = (DHWImportHooker*) UserContext;
HMODULE aModule = (HMODULE) ModuleBase;
return self->PatchOneModule(aModule, ModuleName);
}
bool
DHWImportHooker::PatchAllModules()
{
// Need to cast to PENUMLOADED_MODULES_CALLBACK because the
// constness of the first parameter of PENUMLOADED_MODULES_CALLBACK
// varies over SDK versions (from non-const to const over time).
// See bug 391848 and bug 415426.
#ifdef _WIN64
return dhwEnumerateLoadedModules64(::GetCurrentProcess(),
(PENUMLOADED_MODULES_CALLBACK64)ModuleEnumCallback, this);
#else
return dhwEnumerateLoadedModules(::GetCurrentProcess(),
(PENUMLOADED_MODULES_CALLBACK)ModuleEnumCallback, this);
#endif
}
bool
DHWImportHooker::PatchOneModule(HMODULE aModule, const char* name)
{
if(aModule == mIgnoreModule)
{
return true;
}
// do the fun stuff...
PIMAGE_IMPORT_DESCRIPTOR desc;
ULONG size;
desc = (PIMAGE_IMPORT_DESCRIPTOR)
dhwImageDirectoryEntryToData(aModule, true,
IMAGE_DIRECTORY_ENTRY_IMPORT, &size);
if(!desc)
{
return true;
}
for(; desc->Name; desc++)
{
const char* entryModuleName = (const char*)
((char*)aModule + desc->Name);
if(!lstrcmpi(entryModuleName, mModuleName))
break;
}
if(!desc->Name)
{
return true;
}
PIMAGE_THUNK_DATA thunk = (PIMAGE_THUNK_DATA)
((char*) aModule + desc->FirstThunk);
for(; thunk->u1.Function; thunk++)
{
PROC original;
PROC replacement;
if(mHooking)
{
original = mOriginal;
replacement = mHook;
}
else
{
original = mHook;
replacement = mOriginal;
}
PROC* ppfn = (PROC*) &thunk->u1.Function;
if(*ppfn == original)
{
DWORD dwDummy;
VirtualProtect(ppfn, sizeof(ppfn), PAGE_EXECUTE_READWRITE, &dwDummy);
BOOL result = WriteProcessMemory(GetCurrentProcess(),
ppfn, &replacement, sizeof(replacement), nullptr);
if (!result) //failure
{
printf("failure name %s func %x\n",name,*ppfn);
DWORD error = GetLastError();
return true;
}
else
{
// printf("success name %s func %x\n",name,*ppfn);
DWORD filler = result+1;
return result;
}
}
}
return true;
}
bool
DHWImportHooker::ModuleLoaded(HMODULE aModule, DWORD flags)
{
//printf("ModuleLoaded\n");
if(aModule && !(flags & LOAD_LIBRARY_AS_DATAFILE))
{
PR_Lock(gLock);
// We don't know that the newly loaded module didn't drag in implicitly
// linked modules, so we patch everything in sight.
for(DHWImportHooker* cur = gHooks; cur; cur = cur->mNext)
cur->PatchAllModules();
PR_Unlock(gLock);
}
return true;
}
// static
HMODULE WINAPI
DHWImportHooker::LoadLibraryW(PCWSTR path)
{
//wprintf(L"LoadLibraryW %s\n",path);
HMODULE hmod = DHW_ORIGINAL(::LoadLibraryW, getLoadLibraryWHooker())(path);
ModuleLoaded(hmod, 0);
return hmod;
}
// static
HMODULE WINAPI
DHWImportHooker::LoadLibraryExW(PCWSTR path, HANDLE file, DWORD flags)
{
//wprintf(L"LoadLibraryExW %s\n",path);
HMODULE hmod = DHW_ORIGINAL(::LoadLibraryExW, getLoadLibraryExWHooker())(path, file, flags);
ModuleLoaded(hmod, flags);
return hmod;
}
// static
HMODULE WINAPI
DHWImportHooker::LoadLibraryA(PCSTR path)
{
//printf("LoadLibraryA %s\n",path);
HMODULE hmod = DHW_ORIGINAL(::LoadLibraryA, getLoadLibraryAHooker())(path);
ModuleLoaded(hmod, 0);
return hmod;
}
// static
HMODULE WINAPI
DHWImportHooker::LoadLibraryExA(PCSTR path, HANDLE file, DWORD flags)
{
//printf("LoadLibraryExA %s\n",path);
HMODULE hmod = DHW_ORIGINAL(::LoadLibraryExA, getLoadLibraryExAHooker())(path, file, flags);
ModuleLoaded(hmod, flags);
return hmod;
}
// static
FARPROC WINAPI
DHWImportHooker::GetProcAddress(HMODULE aModule, PCSTR aFunctionName)
{
FARPROC pfn = gRealGetProcAddress(aModule, aFunctionName);
if(pfn)
{
PR_Lock(gLock);
for(DHWImportHooker* cur = gHooks; cur; cur = cur->mNext)
{
if(pfn == cur->mOriginal)
{
pfn = cur->mHook;
break;
}
}
PR_Unlock(gLock);
}
return pfn;
}

View File

@ -1,133 +0,0 @@
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/* 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/. */
/* Win32 x86/x64 code for stack walking, symbol resolution, and function hooking */
#ifndef __nsDebugHelpWin32_h__
#define __nsDebugHelpWin32_h__
#if defined(_WIN32) && (defined(_M_IX86) || defined(_M_X64))
#ifndef WIN32_LEAN_AND_MEAN
#define WIN32_LEAN_AND_MEAN
#endif
#include <windows.h>
#include <imagehlp.h>
#include <crtdbg.h>
#else
#error "nsDebugHelpWin32.h should only be included in Win32 x86/x64 builds"
#endif
// XXX temporary hack...
//#include "hacky_defines.h"
/***************************************************************************/
// useful macros...
#ifdef DHW_IMPLEMENT_GLOBALS
#define DHW_DECLARE_FUN_GLOBAL(name_) decltype(name_)* dhw##name_
#else
#define DHW_DECLARE_FUN_GLOBAL(name_) extern decltype(name_)* dhw##name_
#endif
/**********************************************************/
// This is used to get 'original' function addresses from DHWImportHooker.
#define DHW_ORIGINAL(name_, hooker_) \
((decltype(name_)*) hooker_ . GetOriginalFunction())
/***************************************************************************/
// Global declarations of entry points into ImgHelp functions
#ifndef _WIN64
DHW_DECLARE_FUN_GLOBAL(EnumerateLoadedModules);
#else
DHW_DECLARE_FUN_GLOBAL(EnumerateLoadedModules64);
#endif
DHW_DECLARE_FUN_GLOBAL(ImageDirectoryEntryToData);
/***************************************************************************/
extern bool
dhwEnsureImageHlpInitialized();
/***************************************************************************/
class DHWImportHooker
{
public:
DHWImportHooker(const char* aModuleName,
const char* aFunctionName,
PROC aHook,
bool aExcludeOurModule = false);
~DHWImportHooker();
PROC GetOriginalFunction() {return mOriginal;}
bool PatchAllModules();
bool PatchOneModule(HMODULE aModule, const char* name);
static bool ModuleLoaded(HMODULE aModule, DWORD flags);
// I think that these should be made not static members, but allocated
// things created in an explicit static 'init' method and cleaned up in
// an explicit static 'finish' method. This would allow the application
// to have proper lifetime control over all the hooks.
static DHWImportHooker &getLoadLibraryWHooker();
static DHWImportHooker &getLoadLibraryExWHooker();
static DHWImportHooker &getLoadLibraryAHooker();
static DHWImportHooker &getLoadLibraryExAHooker();
static DHWImportHooker &getGetProcAddressHooker();
static HMODULE WINAPI LoadLibraryA(PCSTR path);
private:
DHWImportHooker* mNext;
const char* mModuleName;
const char* mFunctionName;
PROC mOriginal;
PROC mHook;
HMODULE mIgnoreModule;
bool mHooking;
private:
static PRLock* gLock;
static DHWImportHooker* gHooks;
static decltype(GetProcAddress)* gRealGetProcAddress;
static HMODULE WINAPI LoadLibraryW(PCWSTR path);
static HMODULE WINAPI LoadLibraryExW(PCWSTR path, HANDLE file, DWORD flags);
static HMODULE WINAPI LoadLibraryExA(PCSTR path, HANDLE file, DWORD flags);
static FARPROC WINAPI GetProcAddress(HMODULE aModule, PCSTR aFunctionName);
};
/***************************************************************************/
// This supports the _CrtSetAllocHook based hooking.
// This system sucks because you don't get to see the allocated pointer. I
// don't think it appropriate for nsTraceMalloc, but is useful as a means to make
// malloc fail for testing purposes.
#if 0 //comment out this stuff. not necessary
class DHWAllocationSizeDebugHook
{
public:
virtual bool AllocHook(size_t size) = 0;
virtual bool ReallocHook(size_t size, size_t sizeOld) = 0;
virtual bool FreeHook(size_t size) = 0;
};
extern bool dhwSetAllocationSizeDebugHook(DHWAllocationSizeDebugHook* hook);
extern bool dhwClearAllocationSizeDebugHook();
/***************************************************************************/
#endif //0
#endif /* __nsDebugHelpWin32_h__ */

File diff suppressed because it is too large Load Diff

View File

@ -1,226 +0,0 @@
/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
*
* 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 nsTraceMalloc_h___
#define nsTraceMalloc_h___
#include <stdint.h>
#include <stdio.h> /* for FILE */
#include "prtypes.h"
#ifdef XP_WIN
#define setlinebuf(stream) setvbuf((stream), (char *)NULL, _IOLBF, 0)
#endif
#ifdef __cplusplus
extern "C" {
#endif
/**
* Magic "number" at start of a trace-malloc log file. Inspired by the PNG
* magic string, which inspired XPCOM's typelib (.xpt) file magic. See the
* NS_TraceMallocStartup comment (below) for magic number differences in log
* file structure.
*/
#define NS_TRACE_MALLOC_MAGIC "XPCOM\nTMLog08\r\n\032"
#define NS_TRACE_MALLOC_MAGIC_SIZE 16
/**
* Trace-malloc stats, traced via the 'Z' event at the end of a log file.
*/
typedef struct nsTMStats {
uint32_t calltree_maxstack;
uint32_t calltree_maxdepth;
uint32_t calltree_parents;
uint32_t calltree_maxkids;
uint32_t calltree_kidhits;
uint32_t calltree_kidmisses;
uint32_t calltree_kidsteps;
uint32_t callsite_recurrences;
uint32_t backtrace_calls;
uint32_t backtrace_failures;
uint32_t btmalloc_failures;
uint32_t dladdr_failures;
uint32_t malloc_calls;
uint32_t malloc_failures;
uint32_t calloc_calls;
uint32_t calloc_failures;
uint32_t realloc_calls;
uint32_t realloc_failures;
uint32_t free_calls;
uint32_t null_free_calls;
} nsTMStats;
#define NS_TMSTATS_STATIC_INITIALIZER {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}
/**
* Call NS_TraceMallocStartup with a valid file descriptor to enable logging
* of compressed malloc traces, including callsite chains. Integers may be
* unsigned serial numbers, sizes, or offsets, and require at most 32 bits.
* They're encoded as follows:
* 0-127 0xxxxxxx (binary, one byte)
* 128-16383 10xxxxxx xxxxxxxx
* 16384-0x1fffff 110xxxxx xxxxxxxx xxxxxxxx
* 0x200000-0xfffffff 1110xxxx xxxxxxxx xxxxxxxx xxxxxxxx
* 0x10000000-0xffffffff 11110000 xxxxxxxx xxxxxxxx xxxxxxxx xxxxxxxx
* Strings are NUL-terminated ASCII.
*
* Event Operands (magic TMLog01)
* 'L' library serial, shared object filename string
* 'N' method serial, library serial, demangled name string
* 'S' site serial, parent serial, method serial, calling pc offset
* 'M' site serial, malloc size
* 'C' site serial, calloc size
* 'R' site serial, realloc oldsize, realloc size
* 'F' site serial, free size
*
* Event Operands (magic TMLog02)
* 'Z' serialized struct tmstats (20 unsigned integers),
* maxkids parent callsite serial,
* maxstack top callsite serial
*
* Event Operands (magic TMLog03)
* 'T' seconds, microseconds, caption
*
* Event Operands (magic TMLog04)
* 'R' site serial, realloc size, old site serial, realloc oldsize
*
* Event Operands (magic TMLog05)
* 'M' site serial, address, malloc size
* 'C' site serial, address, calloc size
* 'R' site serial, address, realloc size, old site serial,
* old address, old size
* 'F' site serial, address, free size
*
* Event Operands (magic TMLog06)
* 'M' site serial, interval time (end), address, malloc size
* 'C' site serial, interval time (end), address, calloc size
* 'R' site serial, interval time (end), address, realloc size,
* old site serial, old address, old size
* 'F' site serial, interval time (end), address, free size
*
* Event Operands (magic TMLog07)
* 'M' site serial, interval time (start), duration, address, malloc size
* 'C' site serial, interval time (start), duration, address, calloc size
* 'R' site serial, interval time (start), duration, address, realloc size,
* old site serial, old address, old size
* 'F' site serial, interval time (start), duration, address, free size
*
* Event Operands (magic TMLog08)
* 'G' filename serial, source filename string.
* 'N' method serial, library serial, source filename serial,
* source file linenumber, demangled name string
*
* See tools/trace-malloc/bloatblame.c for an example log-file reader.
*/
#define TM_EVENT_LIBRARY 'L'
#define TM_EVENT_FILENAME 'G'
#define TM_EVENT_METHOD 'N'
#define TM_EVENT_CALLSITE 'S'
#define TM_EVENT_MALLOC 'M'
#define TM_EVENT_CALLOC 'C'
#define TM_EVENT_REALLOC 'R'
#define TM_EVENT_FREE 'F'
#define TM_EVENT_STATS 'Z'
#define TM_EVENT_TIMESTAMP 'T'
PR_EXTERN(void) NS_TraceMallocStartup(int logfd);
/**
* Initialize malloc tracing, using the ``standard'' startup arguments.
*/
PR_EXTERN(int) NS_TraceMallocStartupArgs(int argc, char* argv[]);
/**
* Return PR_TRUE iff |NS_TraceMallocStartup[Args]| has been successfully called.
*/
PR_EXTERN(PRBool) NS_TraceMallocHasStarted(void);
/**
* Stop all malloc tracing, flushing any buffered events to the logfile.
*/
PR_EXTERN(void) NS_TraceMallocShutdown(void);
/**
* Disable malloc tracing.
*/
PR_EXTERN(void) NS_TraceMallocDisable(void);
/**
* Enable malloc tracing.
*/
PR_EXTERN(void) NS_TraceMallocEnable(void);
/**
* Change the log file descriptor, flushing any buffered output to the old
* fd, and writing NS_TRACE_MALLOC_MAGIC to the new file if it is zero length.
* Return the old fd, so the caller can swap open fds. Return -2 on failure,
* which means malloc failure.
*/
PR_EXTERN(int) NS_TraceMallocChangeLogFD(int fd);
/**
* Close the file descriptor fd and forget any bookkeeping associated with it.
* Do nothing if fd is -1.
*/
PR_EXTERN(void) NS_TraceMallocCloseLogFD(int fd);
/**
* Emit a timestamp event with the given caption to the current log file.
*/
PR_EXTERN(void) NS_TraceMallocLogTimestamp(const char *caption);
/**
* Walk the stack, dumping frames in standard form to ofp. If skip is 0,
* exclude the frames for NS_TraceStack and anything it calls to do the walk.
* If skip is less than 0, include -skip such frames. If skip is positive,
* exclude that many frames leading to the call to NS_TraceStack.
*/
PR_EXTERN(void)
NS_TraceStack(int skip, FILE *ofp);
/**
* Dump a human-readable listing of current allocations and their compressed
* stack backtraces to the file named by pathname. Beware this file may have
* very long lines.
*
* Return -1 on error with errno set by the system, 0 on success.
*/
PR_EXTERN(int)
NS_TraceMallocDumpAllocations(const char *pathname);
/**
* Flush all logfile buffers.
*/
PR_EXTERN(void)
NS_TraceMallocFlushLogfiles(void);
/**
* Track all realloc and free calls operating on a given allocation.
*/
PR_EXTERN(void)
NS_TrackAllocation(void* ptr, FILE *ofp);
/* opaque type for API */
typedef struct nsTMStackTraceIDStruct *nsTMStackTraceID;
/**
* Get an identifier for the stack trace of the current thread (to this
* function's callsite) that can be used to print that stack trace later.
*/
PR_EXTERN(nsTMStackTraceID)
NS_TraceMallocGetStackTrace(void);
/**
* Print the stack trace identified.
*/
PR_EXTERN(void)
NS_TraceMallocPrintStackTrace(FILE *ofp, nsTMStackTraceID id);
#ifdef __cplusplus
}
#endif
#endif /* nsTraceMalloc_h___ */

View File

@ -1,64 +0,0 @@
/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
*
* 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/. */
/* declarations needed by both nsTraceMalloc.c and nsWinTraceMalloc.cpp */
#ifndef NSTRACEMALLOCCALLBACKS_H
#define NSTRACEMALLOCCALLBACKS_H
#include <stdint.h>
#include <stdlib.h>
#ifdef __cplusplus
extern "C" {
#endif
/* Used by backtrace. */
typedef struct stack_buffer_info {
void **buffer;
size_t size;
size_t entries;
} stack_buffer_info;
typedef struct tm_thread tm_thread;
struct tm_thread {
/*
* This counter suppresses tracing, in case any tracing code needs
* to malloc.
*/
uint32_t suppress_tracing;
/* buffer for backtrace, below */
stack_buffer_info backtrace_buf;
};
/* implemented in nsTraceMalloc.c */
tm_thread * tm_get_thread(void);
/* implemented in nsTraceMalloc.c */
PR_EXTERN(void) MallocCallback(void *aPtr, size_t aSize, uint32_t start, uint32_t end, tm_thread *t);
PR_EXTERN(void) CallocCallback(void *aPtr, size_t aCount, size_t aSize, uint32_t start, uint32_t end, tm_thread *t);
PR_EXTERN(void) ReallocCallback(void *aPin, void* aPout, size_t aSize, uint32_t start, uint32_t end, tm_thread *t);
PR_EXTERN(void) FreeCallback(void *aPtr, uint32_t start, uint32_t end, tm_thread *t);
#ifdef XP_WIN32
/* implemented in nsTraceMalloc.c */
PR_EXTERN(void) StartupHooker();
PR_EXTERN(void) ShutdownHooker();
/* implemented in nsWinTraceMalloc.cpp */
void* dhw_orig_malloc(size_t);
void* dhw_orig_calloc(size_t, size_t);
void* dhw_orig_realloc(void*, size_t);
void dhw_orig_free(void*);
#endif /* defined(XP_WIN32) */
#ifdef __cplusplus
}
#endif
#endif /* !defined(NSTRACEMALLOCCALLBACKS_H) */

View File

@ -1,279 +0,0 @@
/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
*
* 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/. */
/*
typeinfo.cpp
Speculatively use RTTI on a random object. If it contains a pointer at offset 0
that is in the current process' address space, and that so on, then attempt to
use C++ RTTI's typeid operation to obtain the name of the type.
by Patrick C. Beard.
*/
#include <exception> /* Needed for MSVC2010 due to bug 1055675 */
#include <typeinfo>
#include <ctype.h>
#include "nsTypeInfo.h"
extern "C" void NS_TraceMallocShutdown();
struct TraceMallocShutdown {
TraceMallocShutdown() {}
~TraceMallocShutdown() {
NS_TraceMallocShutdown();
}
};
extern "C" void RegisterTraceMallocShutdown() {
// 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 TraceMallocShutdown t;
}
class IUnknown {
public:
virtual long QueryInterface() = 0;
virtual long AddRef() = 0;
virtual long Release() = 0;
};
#if defined(MACOS)
#include <Processes.h>
class AddressSpace {
public:
AddressSpace();
Boolean contains(void* ptr);
private:
ProcessInfoRec mInfo;
};
AddressSpace::AddressSpace()
{
ProcessSerialNumber psn = { 0, kCurrentProcess };
mInfo.processInfoLength = sizeof(mInfo);
::GetProcessInformation(&psn, &mInfo);
}
Boolean AddressSpace::contains(void* ptr)
{
UInt32 start = UInt32(mInfo.processLocation);
return (UInt32(ptr) >= start && UInt32(ptr) < (start + mInfo.processSize));
}
const char* nsGetTypeName(void* ptr)
{
// construct only one of these per process.
static AddressSpace space;
// sanity check the vtable pointer, before trying to use RTTI on the object.
void** vt = *(void***)ptr;
if (vt && !(unsigned(vt) & 0x3) && space.contains(vt) && space.contains(*vt)) {
IUnknown* u = static_cast<IUnknown*>(ptr);
const char* type = typeid(*u).name();
// make sure it looks like a C++ identifier.
if (type && (isalnum(type[0]) || type[0] == '_'))
return type;
}
return "void*";
}
#endif
// New, more "portable" Linux code is below, but this might be a useful
// model for other platforms, so keeping.
//#if defined(linux)
#if 0
#include <signal.h>
#include <setjmp.h>
static jmp_buf context;
static void handler(int signum)
{
longjmp(context, signum);
}
#define attempt() setjmp(context)
class Signaller {
public:
Signaller(int signum);
~Signaller();
private:
typedef void (*handler_t) (int signum);
int mSignal;
handler_t mOldHandler;
};
Signaller::Signaller(int signum)
: mSignal(signum), mOldHandler(signal(signum, &handler))
{
}
Signaller::~Signaller()
{
signal(mSignal, mOldHandler);
}
// The following are pointers that bamboozle our otherwise feeble
// attempts to "safely" collect type names.
//
// XXX this kind of sucks because it means that anyone trying to use
// this without NSPR will get unresolved symbols when this library
// loads. It's also not very extensible. Oh well: FIX ME!
extern "C" {
// from nsprpub/pr/src/io/priometh.c (libnspr4.so)
extern void* _pr_faulty_methods;
};
static inline int
sanity_check_vtable_i386(void** vt)
{
// Now that we're "safe" inside the signal handler, we can
// start poking around. If we're really an object with
// RTTI, then the second entry in the vtable should point
// to a function.
//
// Let's see if the second entry:
//
// 1) looks like a 4-byte aligned pointer
//
// 2) points to something that looks like the following
// i386 instructions:
//
// 55 push %ebp
// 89e5 mov %esp,%ebp
// 53 push %ebx
//
// or
//
// 55 push %ebp
// 89e5 mov %esp,%ebp
// 56 push %esi
//
// (which is the standard function prologue generated
// by egcs, plus a ``signature'' instruction that appears
// in the typeid() function's implementation).
unsigned char** fp1 = reinterpret_cast<unsigned char**>(vt) + 1;
// Does it look like an address?
unsigned char* ip = *fp1;
if ((unsigned(ip) & 3) != 0)
return 0;
// Does it look like it refers to the standard prologue?
static unsigned char prologue[] = { 0x55, 0x89, 0xE5 };
for (unsigned i = 0; i < sizeof(prologue); ++i)
if (*ip++ != prologue[i])
return 0;
// Is the next instruction a `push %ebx' or `push %esi'?
if (*ip == 0x53 || *ip == 0x56) {
return 1;
}
// Nope. There's another variant that has a `sub' instruction,
// followed by a `cmpl' and a `jne'. Check for that.
if (ip[0] == 0x83 && ip[1] == 0xec // sub
&& ip[3] == 0x83 && ip[4] == 0x3d // cmpl
&& ip[10] == 0x75 // jne
) {
return 1;
}
return 0;
}
static inline int
sanity_check_vtable_ppc(void** vt)
{
// XXX write me!
return 1;
}
#if defined(__i386)
# define SANITY_CHECK_VTABLE(vt) (sanity_check_vtable_i386(vt))
#elif defined(PPC)
# define SANITY_CHECK_VTABLE(vt) (sanity_check_vtable_ppc(vt))
#else
# define SANITY_CHECK_VTABLE(vt) (1)
#endif
const char* nsGetTypeName(void* ptr)
{
// sanity check the vtable pointer, before trying to use RTTI on the object.
void** vt = *(void***)ptr;
if (vt && !(unsigned(vt) & 3) && (vt != &_pr_faulty_methods)) {
Signaller s1(SIGSEGV);
if (attempt() == 0) {
if (SANITY_CHECK_VTABLE(vt)) {
// Looks like a function: what the hell, let's call it.
IUnknown* u = static_cast<IUnknown*>(ptr);
const char* type = typeid(*u).name();
// EGCS seems to prefix a length string.
while (isdigit(*type)) ++type;
return type;
}
}
}
return "void*";
}
#endif
#if defined(linux) || defined(XP_MACOSX)
#define __USE_GNU
#include <dlfcn.h>
#include <ctype.h>
#include <string.h>
const char* nsGetTypeName(void* ptr)
{
#if defined(__GXX_ABI_VERSION) && __GXX_ABI_VERSION >= 100 /* G++ V3 ABI */
const int expected_offset = 8;
const char vtable_sym_start[] = "_ZTV";
const int vtable_sym_start_length = sizeof(vtable_sym_start) - 1;
#else
const int expected_offset = 0;
const char vtable_sym_start[] = "__vt_";
const int vtable_sym_start_length = sizeof(vtable_sym_start) - 1;
#endif
void* vt = *(void**)ptr;
Dl_info info;
// If dladdr fails, if we're not at the expected offset in the vtable,
// or if the symbol name isn't a vtable symbol name, return "void*".
if ( !dladdr(vt, &info) ||
((char*)info.dli_saddr) + expected_offset != vt ||
!info.dli_sname ||
strncmp(info.dli_sname, vtable_sym_start, vtable_sym_start_length))
return "void*";
// skip the garbage at the beginning of things like
// __vt_14nsRootBoxFrame (gcc 2.96) or _ZTV14nsRootBoxFrame (gcc 3.0)
const char* rv = info.dli_sname + vtable_sym_start_length;
while (*rv && isdigit(*rv))
++rv;
return rv;
}
#endif
#ifdef XP_WIN32
const char* nsGetTypeName(void* ptr)
{
//TODO: COMPLETE THIS
return "void*";
}
#endif //XP_WIN32

View File

@ -1,22 +0,0 @@
/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
*
* 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 trace_malloc_nsTypeInfo_h_
#define trace_malloc_nsTypeInfo_h_
#ifdef __cplusplus
extern "C" {
#endif
extern const char* nsGetTypeName(void* ptr);
extern void RegisterTraceMallocShutdown();
#ifdef __cplusplus
}
#endif
#endif /* trace_malloc_nsTypeInfo_h_ */

View File

@ -1,242 +0,0 @@
/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
*
* 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 <stdio.h>
#include <stdlib.h>
#include "prinrval.h"
#include "prlock.h"
#include "nscore.h"
#include "nsDebugHelpWin32.h"
#include "nsTraceMallocCallbacks.h"
/***************************************************************************/
// shows how to use the dhw stuff to hook imported functions
#if _MSC_VER == 1600
#define NS_DEBUG_CRT "msvcr100d.dll"
#elif _MSC_VER == 1700
#define NS_DEBUG_CRT "msvcr110d.dll"
#elif _MSC_VER == 1800
#define NS_DEBUG_CRT "msvcr120d.dll"
#else
#error "Don't know filename of MSVC debug library."
#endif
decltype(malloc) dhw_malloc;
DHWImportHooker &getMallocHooker()
{
static DHWImportHooker gMallocHooker(NS_DEBUG_CRT, "malloc", (PROC) dhw_malloc);
return gMallocHooker;
}
void * __cdecl dhw_malloc( size_t size )
{
tm_thread *t = tm_get_thread();
++t->suppress_tracing;
uint32_t start = PR_IntervalNow();
void* result = DHW_ORIGINAL(malloc, getMallocHooker())(size);
uint32_t end = PR_IntervalNow();
--t->suppress_tracing;
MallocCallback(result, size, start, end, t);
return result;
}
decltype(calloc) dhw_calloc;
DHWImportHooker &getCallocHooker()
{
static DHWImportHooker gCallocHooker(NS_DEBUG_CRT, "calloc", (PROC) dhw_calloc);
return gCallocHooker;
}
void * __cdecl dhw_calloc( size_t count, size_t size )
{
tm_thread *t = tm_get_thread();
++t->suppress_tracing;
uint32_t start = PR_IntervalNow();
void* result = DHW_ORIGINAL(calloc, getCallocHooker())(count,size);
uint32_t end = PR_IntervalNow();
--t->suppress_tracing;
CallocCallback(result, count, size, start, end, t);
return result;
}
decltype(free) dhw_free;
DHWImportHooker &getFreeHooker()
{
static DHWImportHooker gFreeHooker(NS_DEBUG_CRT, "free", (PROC) dhw_free);
return gFreeHooker;
}
void __cdecl dhw_free( void* p )
{
tm_thread *t = tm_get_thread();
++t->suppress_tracing;
uint32_t start = PR_IntervalNow();
DHW_ORIGINAL(free, getFreeHooker())(p);
uint32_t end = PR_IntervalNow();
--t->suppress_tracing;
/* FIXME bug 392008: We could race with reallocation of p. */
FreeCallback(p, start, end, t);
}
decltype(realloc) dhw_realloc;
DHWImportHooker &getReallocHooker()
{
static DHWImportHooker gReallocHooker(NS_DEBUG_CRT, "realloc", (PROC) dhw_realloc);
return gReallocHooker;
}
void * __cdecl dhw_realloc(void * pin, size_t size)
{
tm_thread *t = tm_get_thread();
++t->suppress_tracing;
uint32_t start = PR_IntervalNow();
void* pout = DHW_ORIGINAL(realloc, getReallocHooker())(pin, size);
uint32_t end = PR_IntervalNow();
--t->suppress_tracing;
/* FIXME bug 392008: We could race with reallocation of pin. */
ReallocCallback(pin, pout, size, start, end, t);
return pout;
}
// Note the mangled name!
void * __cdecl dhw_new(size_t size);
DHWImportHooker &getNewHooker()
{
static DHWImportHooker gNewHooker(NS_DEBUG_CRT, "??2@YAPAXI@Z", (PROC) dhw_new);
return gNewHooker;
}
void * __cdecl dhw_new(size_t size)
{
tm_thread *t = tm_get_thread();
++t->suppress_tracing;
uint32_t start = PR_IntervalNow();
void* result = DHW_ORIGINAL(dhw_new, getNewHooker())(size);
uint32_t end = PR_IntervalNow();
--t->suppress_tracing;
MallocCallback(result, size, start, end, t);//do we need a different one for new?
return result;
}
// Note the mangled name!
void __cdecl dhw_delete(void* p);
DHWImportHooker &getDeleteHooker()
{
static DHWImportHooker gDeleteHooker(NS_DEBUG_CRT, "??3@YAXPAX@Z", (PROC) dhw_delete);
return gDeleteHooker;
}
void __cdecl dhw_delete(void* p)
{
tm_thread *t = tm_get_thread();
++t->suppress_tracing;
uint32_t start = PR_IntervalNow();
DHW_ORIGINAL(dhw_delete, getDeleteHooker())(p);
uint32_t end = PR_IntervalNow();
--t->suppress_tracing;
FreeCallback(p, start, end, t);
}
// Note the mangled name!
void * __cdecl dhw_vec_new(size_t size);
DHWImportHooker &getVecNewHooker()
{
static DHWImportHooker gVecNewHooker(NS_DEBUG_CRT, "??_U@YAPAXI@Z", (PROC) dhw_vec_new);
return gVecNewHooker;
}
void * __cdecl dhw_vec_new(size_t size)
{
tm_thread *t = tm_get_thread();
++t->suppress_tracing; // need to suppress since new[] calls new
uint32_t start = PR_IntervalNow();
void* result = DHW_ORIGINAL(dhw_vec_new, getVecNewHooker())(size);
uint32_t end = PR_IntervalNow();
--t->suppress_tracing;
MallocCallback(result, size, start, end, t);//do we need a different one for new[]?
return result;
}
// Note the mangled name!
void __cdecl dhw_vec_delete(void* p);
DHWImportHooker &getVecDeleteHooker()
{
static DHWImportHooker gVecDeleteHooker(NS_DEBUG_CRT, "??_V@YAXPAX@Z", (PROC) dhw_vec_delete);
return gVecDeleteHooker;
}
void __cdecl dhw_vec_delete(void* p)
{
tm_thread *t = tm_get_thread();
++t->suppress_tracing;
uint32_t start = PR_IntervalNow();
DHW_ORIGINAL(dhw_vec_delete, getVecDeleteHooker())(p);
uint32_t end = PR_IntervalNow();
--t->suppress_tracing;
FreeCallback(p, start, end, t);
}
/*C Callbacks*/
PR_IMPLEMENT(void)
StartupHooker()
{
//run through get all hookers
DHWImportHooker &loadlibraryW = DHWImportHooker::getLoadLibraryWHooker();
DHWImportHooker &loadlibraryExW = DHWImportHooker::getLoadLibraryExWHooker();
DHWImportHooker &loadlibraryA = DHWImportHooker::getLoadLibraryAHooker();
DHWImportHooker &loadlibraryExA = DHWImportHooker::getLoadLibraryExAHooker();
DHWImportHooker &mallochooker = getMallocHooker();
DHWImportHooker &reallochooker = getReallocHooker();
DHWImportHooker &callochooker = getCallocHooker();
DHWImportHooker &freehooker = getFreeHooker();
DHWImportHooker &newhooker = getNewHooker();
DHWImportHooker &deletehooker = getDeleteHooker();
DHWImportHooker &vecnewhooker = getVecNewHooker();
DHWImportHooker &vecdeletehooker = getVecDeleteHooker();
printf("Startup Hooker\n");
}
PR_IMPLEMENT(void)
ShutdownHooker()
{
}
extern "C" {
void* dhw_orig_malloc(size_t);
void* dhw_orig_calloc(size_t, size_t);
void* dhw_orig_realloc(void*, size_t);
void dhw_orig_free(void*);
}
void*
dhw_orig_malloc(size_t size)
{
return DHW_ORIGINAL(malloc, getMallocHooker())(size);
}
void*
dhw_orig_calloc(size_t count, size_t size)
{
return DHW_ORIGINAL(calloc, getCallocHooker())(count,size);
}
void*
dhw_orig_realloc(void* pin, size_t size)
{
return DHW_ORIGINAL(realloc, getReallocHooker())(pin, size);
}
void
dhw_orig_free(void* p)
{
DHW_ORIGINAL(free, getFreeHooker())(p);
}

View File

@ -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/.
LIBRARY tracemalloc.dll
EXPORTS
NS_TraceMallocStartup
NS_TraceMallocStartupArgs
NS_TraceMallocShutdown
NS_TraceMallocDisable
NS_TraceMallocEnable
NS_TraceMallocChangeLogFD
NS_TraceMallocCloseLogFD
NS_TraceMallocLogTimestamp
NS_TraceStack
NS_TraceMallocDumpAllocations
NS_TraceMallocFlushLogfiles

View File

@ -1,12 +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>Live Bloat Dump</title>
</head>
<body>
<button onclick="window.TraceMallocDumpAllocations('allocations.log');">Dump to allocations.log</button>
</body>
</html>

View File

@ -1,57 +0,0 @@
#!/usr/bin/perl
# 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/.
$argv = $ARGV[0];
open( bloatFile, $argv ) or die "Can't open $argv: $!\n";
while (<bloatFile>) {
if (/name=/) {
($td,$name,$func,$a,$ntd) = split(/>/, $_);
($fname, $memSize) = split( /&nbsp;/, $func );
($trash, $linkName) = split( /"/, $name );
$namesLinks{$fname} = $linkName;
if ($namesSizes{$fname}) {
$namesSizes{$fname} = $namesSizes{$fname} + $memSize;
}
else {
$namesSizes{$fname} = $memSize;
}
}
}
$argv = $ARGV[1];
if ($argv)
{
open( bloatFile, $argv ) or die "Can't open $argv: $!\n";
while (<bloatFile>) {
if (/name=/) {
($td,$name,$func,$a,$ntd) = split(/>/, $_);
($fname, $memSize) = split( /&nbsp;/, $func );
$namesSizes{$fname} = $namesSizes{$fname} - $memSize;
}
}
}
sub byvalue { $namesSizes{$b} <=> $namesSizes{$a} }
print '<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN">';
print "\n<html><head>\n";
print "<title>Bloat Blame Delta</title>\n";
print '<link rel="stylesheet" type="text/css" href="blame.css">';
print "\n</head>\n<body>\n<table>\n";
print "<thead><tr><td>Memory Allocated</td><td>Function Name</td><td>Link</td></tr></thead>\n";
foreach $key (sort byvalue keys %namesSizes) {
if ($namesSizes{$key})
{
print "<tr>\n";
print ' <td>', $namesSizes{$key},"</td>\n";
print " <td> <a href=\"$ARGV[0]#$namesLinks{$key}\">", $key, "</a></td>\n";
print "</tr>\n"
}
}
print "</table>\n</body></html>";

View File

@ -1,34 +0,0 @@
# -*- Mode: python; c-basic-offset: 4; 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/.
if not CONFIG['MOZ_PROFILE_GENERATE']:
Program('spacetrace')
SOURCES += [
'formdata.c',
'spacecategory.c',
'spacetrace.c',
]
bin_suffix = CONFIG['BIN_SUFFIX']
SOURCES += [
'tmreader.c',
]
SimplePrograms([
'leakstats',
'tmstats',
], ext='.c')
GeckoSimplePrograms([
'bloatblame',
'leaksoup',
])
RESOURCE_FILES += [
'spacetrace.css'
]

View File

@ -1,442 +0,0 @@
# Categorization rules for spacetrace
#
# This file defines the stack frame rules that will categorize
# allocations that spacetrace processes. The format of this file is
#
# <categoryname>
# initial string match for stack frame n
# initial string match for stack frame n+1
# initial string match for stack frame n+2
#
# The key in the matching rule is that for every rule, we provide a
# snippet of the stack frame - contiguous substring matches.
# categorynames and rules substring matches are case sensitive
#
# Predefined Categories
# "All" - All allocations [default]
# "uncategorized" - All allocations that don't match any category
#
#
# Suresh Duddi <dp@netscape.com>
###########################################################################
# NOTE: This is still under definition
###########################################################################
# General principle of categorization:
#
# - Each category, in general, denotes a module or feature.
#
# - We assign each allocation to the module/feature that directly
# caused the allocation irrespective of which higher level module
# caused the allocation. There are very few exceptions usually when
# an allocation belongs both to a feature-category and to a
# module-category.
#
# bookmarks
# Bookmarks. Mainly initialization. Does not include menu cost.
# css
# Cascading style sheets
# dom
# Memory held by DOM.
# font
# All font stuff
# global-history
# html
# html parsing and layout
# layout
# reflow, frames, line, view
# images
# All images.
# jar
# jar, zip
# js
# javascript
# necko
# All protocol and uri stuff. All urls created accounted here
# preferences
# Preferences stuff. All js cost for preferences is included here.
## rdf
# Most of the rdf allocations. rdf cost from xul, chromeregistry,
# is assigned to xul.
#
# wallet
# Form cache.
#
# xbl
# xbl stuff. Includes js in xbl.
# xpcom
# xpcom, xpt. Allocations for creations of objects are assigned
# onto the respective modules
# xul
# XUL parsing and layout. Includes rdf overhead from xul like
# nsChromeRegistry
#
#
# ===========================================================================
# Leaf rules. We categorize them out first.
# All allocations matching these rules DO belong to the category.
# ===========================================================================
<js>
nsXULPrototypeScript::Deserialize
<X>
XSupportsLocale
<X>
/usr/X11R6/lib/libX11.so
<js>
JS_Init
<atoms>
NS_NewPermanentAtom
<images>
gif_write
<images>
imgRequest::OnDataAvailable
<images>
imgLoader::
<images>
jinit_master_decompress
<images>
jinit_marker_reader
<images>
jinit_marker_decompress
<images>
nsJPEGDecoder::
<images>
nsGIFDecoder2::
<jar>
nsZipArchive::BuildFileList
<jar>
nsZipArchive::ReadInit
<jar>
nsJARChannel::Open
<xpcom>
NS_InitXPCOM2
# xpt file loads
<xpcom>
xptiInterfaceInfoManager::LoadFile
<xpcom>
nsGenericModule::Initialize
<intl>
nsStringBundle
<intl>
nsLocale::
<intl>
nsCharsetConverterManager::
<necko>
nsDiskCacheDevice::Init
<necko>
nsCacheEntryDescriptor::nsTransportWrapper::OpenOutputStream
<necko>
nsSocketTransport::
<necko>
nsCacheService::
<necko>
nsDiskCacheStreamIO::
<necko>
nsHttpResponseHead::
<wallet>
WLLT
<xul>
nsXULElement::Create
<xul>
nsXULAttribute::Create
<xul>
nsXULDocument::AddElementToMap
<xul>
XULContentSinkImpl::AddAttributes
<xul>
XULContentSinkImpl::CreateElement
<xul>
nsXULElement::SetAttr
<xul>
nsXULDocument::InsertElement
<xul>
NS_NewXULContentBuilder
<xul>
nsXULElement::AppendChildTo
<xbl>
nsXBLPrototypeHandler::AppendHandlerText(
<html>
FrameArena::AllocateFrame
<global-history>
nsGlobalHistory::OpenDB
<font>
nsFontCache::
<font>
nsFont::nsFont
<gtk>
gtk_init
<rdf>
RDFServiceImpl::GetResource
<rdf>
RDFContentSinkImpl::AddProperties
<rdf>
RDFContainerImpl::AppendElement
<rdf>
RDFXMLDataSourceImpl::Assert
<rdf>
NS_NewRDFInMemoryDataSource
<css>
CSSLoaderImpl::ParseSheet
<css>
CSSParserImpl::Parse(
<css>
NS_NewCSS
<css>
RuleHash::AppendRule
<css>
nsRuleNode::GetStyleData
<CSS>
SelectorList::
<css>
CSSRuleProcessor::RulesMatching
<css>
CSSStyleSheetImpl::Clone
<necko>
nsHttpHeaderArray::
<html>
nsScanner::Append
<html>
nsHTMLTokenizer::
<html>
NS_NewHTMLTokenizer
<html>
nsSlidingString::
<html>
nsParser::
<html>
nsIParserService::
<html>
nsCParserNode::
<html>
CNavDTD::CNavDTD
<html>
nsHTMLDocument::
<layout>
IncrementalReflow::AddCommand
<layout>
nsBlockFrame::
<layout>
nsBoxFrame::
<layout>
nsImageFrame::
<layout>
nsInlineFrame::
<layout>
nsLeafFrame::
<layout>
nsLineLayout::
<layout>
nsReflowPath::EnsureSubtreeFor
<layout>
nsSliderFrame::
<layout>
nsScrollBoxFrame::
<layout>
nsTextFrame::
<layout>
nsTableRowFrame::
<layout>
nsTableRowGroupFrame::
<layout>
nsViewManager::
<layout>
PresShell::ProcessReflowCommands
# ======================================================================
# Rules that match higher up on the stack
# These go later.
# ======================================================================
<preferences>
PREF_
<bookmarks>
nsBookmarksService::
<xbl>
nsXBLService::LoadBindings
<xbl>
nsXBLBinding::ExecuteAttachedHandler
<js>
nsXULDocument::LoadScript
<js>
nsXULDocument::ExecuteScript
<xul>
XULContentSinkImpl::Open
<rdf>
RDFContentSinkImpl::HandleEndElement
<html>
HTMLContentSink::AddAttributes
<html>
HTMLContentSink::OpenContainer
<html>
HTMLContentSink::CloseContainer
# XXX whom to account LoadImage to? I am going with images.
<images>
imgLoader::LoadImage
<html>
StackArena::
# ======================================================================
# Even more genralized rules. There could be lots of activity that
# happens below them in the stack. But we are sure we have categorized
# a lot of them by using the rules above and know the category of the
# most of the rest.
# ======================================================================
<rdf>
RDFContentSinkImpl::
<dom>
nsGenericDOMDataNode::
<dom>
nsDOMClassInfo::
<dom>
nsDOMSOFactory::
<xul>
nsXULTemplateBuilder::
<xul>
XULContentSinkImpl::HandleEndElement
<xul>
nsChromeRegistry::
<js>
nsJSContext::EvaluateString
# Almost all of what is left with XULContentSink belongs to xul
# Also, this was roughly 0.3% of startup memory
<xul>
XULContentSinkImpl::
# XXX this is a wild guess -> html What remains here is about
# XXX 1.5% of startup footprint
<html>
HTMLContentSink::
<xbl>
nsXBLContentSink::
<xbl>
nsXBLStreamListener::OnDataAvailable
<necko>
nsIOService::NewURI
<necko>
nsIOService::NewChannelFromURI
<necko>
nsSegmentedBuffer::AppendNewSegment
<necko>
nsHttpChannel::
# Catchalls to help categorize
# ----------------------------------------------------------------------
<js-catchall>
js_
# Everything else
# ----------------------------------------------------------------------
# <uncategorized>
# This is a predefined category. Don't create it yourself.

View File

@ -1,959 +0,0 @@
/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
*
* 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/. */
/*
** spacecategory.c
**
** Cagtegorizes each allocation using a predefined set of rules
** and presents a tree of categories for browsing.
*/
/*
** Required include files.
*/
#include "spacetrace.h"
#include <ctype.h>
#include <string.h>
#include "nsQuickSort.h"
#if defined(HAVE_BOUTELL_GD)
/*
** See http://www.boutell.com/gd for the GD graphics library.
** Ports for many platorms exist.
** Your box may already have the lib (mine did, redhat 7.1 workstation).
*/
#include <gd.h>
#include <gdfontt.h>
#include <gdfonts.h>
#include <gdfontmb.h>
#endif /* HAVE_BOUTELL_GD */
/*
** AddRule
**
** Add a rule into the list of rules maintainted in global
*/
int
AddRule(STGlobals * g, STCategoryRule * rule)
{
if (g->mNRules % ST_ALLOC_STEP == 0) {
/* Need more space */
STCategoryRule **newrules;
newrules = (STCategoryRule **) realloc(g->mCategoryRules,
(g->mNRules +
ST_ALLOC_STEP) *
sizeof(STCategoryRule *));
if (!newrules) {
REPORT_ERROR(__LINE__, AddRule_No_Memory);
return -1;
}
g->mCategoryRules = newrules;
}
g->mCategoryRules[g->mNRules++] = rule;
return 0;
}
/*
** AddChild
**
** Add the node as a child of the parent node
*/
int
AddChild(STCategoryNode * parent, STCategoryNode * child)
{
if (parent->nchildren % ST_ALLOC_STEP == 0) {
/* need more space */
STCategoryNode **newnodes;
newnodes = (STCategoryNode **) realloc(parent->children,
(parent->nchildren +
ST_ALLOC_STEP) *
sizeof(STCategoryNode *));
if (!newnodes) {
REPORT_ERROR(__LINE__, AddChild_No_Memory);
return -1;
}
parent->children = newnodes;
}
parent->children[parent->nchildren++] = child;
return 0;
}
int
Reparent(STCategoryNode * parent, STCategoryNode * child)
{
uint32_t i;
if (child->parent == parent)
return 0;
/* Remove child from old parent */
if (child->parent) {
for (i = 0; i < child->parent->nchildren; i++) {
if (child->parent->children[i] == child) {
/* Remove child from list */
if (i + 1 < child->parent->nchildren)
memmove(&child->parent->children[i],
&child->parent->children[i + 1],
(child->parent->nchildren - i -
1) * sizeof(STCategoryNode *));
child->parent->nchildren--;
break;
}
}
}
/* Add child into new parent */
AddChild(parent, child);
return 0;
}
/*
** findCategoryNode
**
** Given a category name, finds the Node corresponding to the category
*/
STCategoryNode *
findCategoryNode(const char *catName, STGlobals * g)
{
uint32_t i;
for (i = 0; i < g->mNCategoryMap; i++) {
if (!strcmp(g->mCategoryMap[i]->categoryName, catName))
return g->mCategoryMap[i]->node;
}
/* Check if we are looking for the root node */
if (!strcmp(catName, ST_ROOT_CATEGORY_NAME))
return &g->mCategoryRoot;
return NULL;
}
/*
** AddCategoryNode
**
** Adds a mapping between a category and its Node into the categoryMap
*/
int
AddCategoryNode(STCategoryNode * node, STGlobals * g)
{
if (g->mNCategoryMap % ST_ALLOC_STEP == 0) {
/* Need more space */
STCategoryMapEntry **newmap =
(STCategoryMapEntry **) realloc(g->mCategoryMap,
(g->mNCategoryMap +
ST_ALLOC_STEP) *
sizeof(STCategoryMapEntry *));
if (!newmap) {
REPORT_ERROR(__LINE__, AddCategoryNode_No_Memory);
return -1;
}
g->mCategoryMap = newmap;
}
g->mCategoryMap[g->mNCategoryMap] =
(STCategoryMapEntry *) calloc(1, sizeof(STCategoryMapEntry));
if (!g->mCategoryMap[g->mNCategoryMap]) {
REPORT_ERROR(__LINE__, AddCategoryNode_No_Memory);
return -1;
}
g->mCategoryMap[g->mNCategoryMap]->categoryName = node->categoryName;
g->mCategoryMap[g->mNCategoryMap]->node = node;
g->mNCategoryMap++;
return 0;
}
/*
** NewCategoryNode
**
** Creates a new category node for category name 'catname' and makes
** 'parent' its parent.
*/
STCategoryNode *
NewCategoryNode(const char *catName, STCategoryNode * parent, STGlobals * g)
{
STCategoryNode *node;
node = (STCategoryNode *) calloc(1, sizeof(STCategoryNode));
if (!node)
return NULL;
node->runs =
(STRun **) calloc(g->mCommandLineOptions.mContexts, sizeof(STRun *));
if (NULL == node->runs) {
free(node);
return NULL;
}
node->categoryName = catName;
/* Set parent of child */
node->parent = parent;
/* Set child in parent */
AddChild(parent, node);
/* Add node into mapping table */
AddCategoryNode(node, g);
return node;
}
/*
** ProcessCategoryLeafRule
**
** Add this into the tree as a leaf node. It doesn't know who its parent is. For now we make
** root as its parent
*/
int
ProcessCategoryLeafRule(STCategoryRule * leafRule, STCategoryNode * root,
STGlobals * g)
{
STCategoryRule *rule;
STCategoryNode *node;
rule = (STCategoryRule *) calloc(1, sizeof(STCategoryRule));
if (!rule)
return -1;
/* Take ownership of all elements of rule */
*rule = *leafRule;
/* Find/Make a STCategoryNode and add it into the tree */
node = findCategoryNode(rule->categoryName, g);
if (!node)
node = NewCategoryNode(rule->categoryName, root, g);
/* Make sure rule knows which node to access */
rule->node = node;
/* Add rule into rulelist */
AddRule(g, rule);
return 0;
}
/*
** ProcessCategoryParentRule
**
** Rule has all the children of category as patterns. Sets up the tree so that
** the parent child relationship is honored.
*/
int
ProcessCategoryParentRule(STCategoryRule * parentRule, STCategoryNode * root,
STGlobals * g)
{
STCategoryNode *node;
STCategoryNode *child;
uint32_t i;
/* Find the parent node in the tree. If not make one and add it into the tree */
node = findCategoryNode(parentRule->categoryName, g);
if (!node) {
node = NewCategoryNode(parentRule->categoryName, root, g);
if (!node)
return -1;
}
/* For every child node, Find/Create it and make it the child of this node */
for (i = 0; i < parentRule->npats; i++) {
child = findCategoryNode(parentRule->pats[i], g);
if (!child) {
child = NewCategoryNode(parentRule->pats[i], node, g);
if (!child)
return -1;
}
else {
/* Reparent child to node. This is because when we created the node
** we would have created it as the child of root. Now we need to
** remove it from root's child list and add it into this node
*/
Reparent(node, child);
}
}
return 0;
}
/*
** initCategories
**
** Initialize all categories. This reads in a file that says how to categorize
** each callsite, creates a tree of these categories and makes a list of these
** patterns in order for matching
*/
int
initCategories(STGlobals * g)
{
FILE *fp;
char buf[1024], *in;
int n;
PRBool inrule, leaf;
STCategoryRule rule;
fp = fopen(g->mCommandLineOptions.mCategoryFile, "r");
if (!fp) {
/* It isn't an error to not have a categories file */
REPORT_INFO("No categories file.");
return -1;
}
inrule = PR_FALSE;
leaf = PR_FALSE;
memset(&rule, 0, sizeof(rule));
while (fgets(buf, sizeof(buf), fp) != NULL) {
/* Lose the \n */
n = strlen(buf);
if (buf[n - 1] == '\n')
buf[--n] = '\0';
in = buf;
/* skip comments */
if (*in == '#')
continue;
/* skip empty lines. If we are in a rule, end the rule. */
while (*in && isspace(*in))
in++;
if (*in == '\0') {
if (inrule) {
/* End the rule : leaf or non-leaf */
if (leaf)
ProcessCategoryLeafRule(&rule, &g->mCategoryRoot, g);
else
/* non-leaf */
ProcessCategoryParentRule(&rule, &g->mCategoryRoot, g);
inrule = PR_FALSE;
memset(&rule, 0, sizeof(rule));
}
continue;
}
/* if we are in a rule acculumate */
if (inrule) {
rule.pats[rule.npats] = strdup(in);
rule.patlen[rule.npats++] = strlen(in);
}
else if (*in == '<') {
/* Start a category */
inrule = PR_TRUE;
leaf = PR_TRUE;
/* Get the category name */
in++;
n = strlen(in);
if (in[n - 1] == '>')
in[n - 1] = '\0';
rule.categoryName = strdup(in);
}
else {
/* this is a non-leaf category. Should be of the form CategoryName
** followed by list of child category names one per line
*/
inrule = PR_TRUE;
leaf = PR_FALSE;
rule.categoryName = strdup(in);
}
}
/* If we were in a rule when processing the last line, end the rule */
if (inrule) {
/* End the rule : leaf or non-leaf */
if (leaf)
ProcessCategoryLeafRule(&rule, &g->mCategoryRoot, g);
else
/* non-leaf */
ProcessCategoryParentRule(&rule, &g->mCategoryRoot, g);
}
/* Add the final "uncategorized" category. We make new memory locations
** for all these to conform to the general principle of all strings are allocated
** so it makes release logic very simple.
*/
memset(&rule, 0, sizeof(rule));
rule.categoryName = strdup("uncategorized");
rule.pats[0] = strdup("");
rule.patlen[0] = 0;
rule.npats = 1;
ProcessCategoryLeafRule(&rule, &g->mCategoryRoot, g);
return 0;
}
/*
** callsiteMatchesRule
**
** Returns the corresponding node if callsite matches the rule. Rule is a sequence
** of patterns that must match contiguously the callsite.
*/
int
callsiteMatchesRule(tmcallsite * aCallsite, STCategoryRule * aRule)
{
uint32_t patnum = 0;
const char *methodName = NULL;
while (patnum < aRule->npats && aCallsite && aCallsite->method) {
methodName = tmmethodnode_name(aCallsite->method);
if (!methodName)
return 0;
if (!*aRule->pats[patnum]
|| !strncmp(methodName, aRule->pats[patnum],
aRule->patlen[patnum])) {
/* We have matched so far. Proceed up the stack and to the next pattern */
patnum++;
aCallsite = aCallsite->parent;
}
else {
/* Deal with mismatch */
if (patnum > 0) {
/* contiguous mismatch. Stop */
return 0;
}
/* We still haven't matched the first pattern. Proceed up the stack without
** moving to the next pattern.
*/
aCallsite = aCallsite->parent;
}
}
if (patnum == aRule->npats) {
/* all patterns matched. We have a winner. */
#if defined(DEBUG_dp) && 0
fprintf(stderr, "[%s] match\n", aRule->categoryName);
#endif
return 1;
}
return 0;
}
#ifdef DEBUG_dp
PRIntervalTime _gMatchTime = 0;
uint32_t _gMatchCount = 0;
uint32_t _gMatchRules = 0;
#endif
/*
** matchAllocation
**
** Runs through all rules and returns the node corresponding to
** a match of the allocation.
*/
STCategoryNode *
matchAllocation(STGlobals * g, STAllocation * aAllocation)
{
#ifdef DEBUG_dp
PRIntervalTime start = PR_IntervalNow();
#endif
uint32_t rulenum;
STCategoryNode *node = NULL;
STCategoryRule *rule;
for (rulenum = 0; rulenum < g->mNRules; rulenum++) {
#ifdef DEBUG_dp
_gMatchRules++;
#endif
rule = g->mCategoryRules[rulenum];
if (callsiteMatchesRule(aAllocation->mEvents[0].mCallsite, rule)) {
node = rule->node;
break;
}
}
#ifdef DEBUG_dp
_gMatchCount++;
_gMatchTime += PR_IntervalNow() - start;
#endif
return node;
}
/*
** categorizeAllocation
**
** Given an allocation, it adds it into the category tree at the right spot
** by comparing the allocation to the rules and adding into the right node.
** Also, does propogation of cost upwards in the tree.
** The root of the tree is in the globls as the tree is dependent on the
** category file (options) rather than the run.
*/
int
categorizeAllocation(STOptions * inOptions, STContext * inContext,
STAllocation * aAllocation, STGlobals * g)
{
/* Run through the rules in order to see if this allcation matches
** any of them.
*/
STCategoryNode *node;
node = matchAllocation(g, aAllocation);
if (!node) {
/* ugh! it should atleast go into the "uncategorized" node. wierd!
*/
REPORT_ERROR(__LINE__, categorizeAllocation);
return -1;
}
/* Create run for node if not already */
if (!node->runs[inContext->mIndex]) {
/*
** Create run with positive timestamp as we can harvest it later
** for callsite details summarization
*/
node->runs[inContext->mIndex] =
createRun(inContext, PR_IntervalNow());
if (!node->runs[inContext->mIndex]) {
REPORT_ERROR(__LINE__, categorizeAllocation_No_Memory);
return -1;
}
}
/* Add allocation into node. We expand the table of allocations in steps */
if (node->runs[inContext->mIndex]->mAllocationCount % ST_ALLOC_STEP == 0) {
/* Need more space */
STAllocation **allocs;
allocs =
(STAllocation **) realloc(node->runs[inContext->mIndex]->
mAllocations,
(node->runs[inContext->mIndex]->
mAllocationCount +
ST_ALLOC_STEP) *
sizeof(STAllocation *));
if (!allocs) {
REPORT_ERROR(__LINE__, categorizeAllocation_No_Memory);
return -1;
}
node->runs[inContext->mIndex]->mAllocations = allocs;
}
node->runs[inContext->mIndex]->mAllocations[node->
runs[inContext->mIndex]->
mAllocationCount++] =
aAllocation;
/*
** Make sure run's stats are calculated. We don't go update the parents of allocation
** at this time. That will happen when we focus on this category. This updating of
** stats will provide us fast categoryreports.
*/
recalculateAllocationCost(inOptions, inContext,
node->runs[inContext->mIndex], aAllocation,
PR_FALSE);
/* Propagate upwards the statistics */
/* XXX */
#if defined(DEBUG_dp) && 0
fprintf(stderr, "DEBUG: [%s] match\n", node->categoryName);
#endif
return 0;
}
typedef PRBool STCategoryNodeProcessor(STRequest * inRequest,
STOptions * inOptions,
STContext * inContext,
void *clientData,
STCategoryNode * node);
PRBool
freeNodeRunProcessor(STRequest * inRequest, STOptions * inOptions,
STContext * inContext, void *clientData,
STCategoryNode * node)
{
if (node->runs && node->runs[inContext->mIndex]) {
freeRun(node->runs[inContext->mIndex]);
node->runs[inContext->mIndex] = NULL;
}
return PR_TRUE;
}
PRBool
freeNodeRunsProcessor(STRequest * inRequest, STOptions * inOptions,
STContext * inContext, void *clientData,
STCategoryNode * node)
{
if (node->runs) {
uint32_t loop = 0;
for (loop = 0; loop < globals.mCommandLineOptions.mContexts; loop++) {
if (node->runs[loop]) {
freeRun(node->runs[loop]);
node->runs[loop] = NULL;
}
}
free(node->runs);
node->runs = NULL;
}
return PR_TRUE;
}
#if defined(DEBUG_dp)
PRBool
printNodeProcessor(STRequest * inRequest, STOptions * inOptions,
STContext * inContext, void *clientData,
STCategoryNode * node)
{
STCategoryNode *root = (STCategoryNode *) clientData;
fprintf(stderr, "%-25s [ %9s size", node->categoryName,
FormatNumber(node->run ? node->run->mStats[inContext->mIndex].
mSize : 0));
fprintf(stderr, ", %5.1f%%",
node->run ? ((double) node->run->mStats[inContext->mIndex].mSize /
root->run->mStats[inContext->mIndex].mSize *
100) : 0);
fprintf(stderr, ", %7s allocations ]\n",
FormatNumber(node->run ? node->run->mStats[inContext->mIndex].
mCompositeCount : 0));
return PR_TRUE;
}
#endif
typedef struct __struct_optcon
{
STOptions *mOptions;
STContext *mContext;
}
optcon;
/*
** compareNode
**
** qsort callback.
** Compare the nodes as specified by the options.
*/
int
compareNode(const void *aNode1, const void *aNode2, void *aContext)
{
int retval = 0;
STCategoryNode *node1, *node2;
uint32_t a, b;
optcon *oc = (optcon *) aContext;
if (!aNode1 || !aNode2 || !oc->mOptions || !oc->mContext)
return 0;
node1 = *((STCategoryNode **) aNode1);
node2 = *((STCategoryNode **) aNode2);
if (node1 && node2) {
if (oc->mOptions->mOrderBy == ST_COUNT) {
a = (node1->runs[oc->mContext->mIndex]) ? node1->runs[oc->
mContext->
mIndex]->
mStats[oc->mContext->mIndex].mCompositeCount : 0;
b = (node2->runs[oc->mContext->mIndex]) ? node2->runs[oc->
mContext->
mIndex]->
mStats[oc->mContext->mIndex].mCompositeCount : 0;
}
else {
/* Default is by size */
a = (node1->runs[oc->mContext->mIndex]) ? node1->runs[oc->
mContext->
mIndex]->
mStats[oc->mContext->mIndex].mSize : 0;
b = (node2->runs[oc->mContext->mIndex]) ? node2->runs[oc->
mContext->
mIndex]->
mStats[oc->mContext->mIndex].mSize : 0;
}
if (a < b)
retval = __LINE__;
else
retval = -__LINE__;
}
return retval;
}
PRBool
sortNodeProcessor(STRequest * inRequest, STOptions * inOptions,
STContext * inContext, void *clientData,
STCategoryNode * node)
{
if (node->nchildren) {
optcon context;
context.mOptions = inOptions;
context.mContext = inContext;
NS_QuickSort(node->children, node->nchildren,
sizeof(STCategoryNode *), compareNode, &context);
}
return PR_TRUE;
}
/*
** walkTree
**
** General purpose tree walker. Issues callback for each node.
** If 'maxdepth' > 0, then stops after processing that depth. Root is
** depth 0, the nodes below it are depth 1 etc...
*/
#define MODINC(n, mod) ((n+1) % mod)
void
walkTree(STCategoryNode * root, STCategoryNodeProcessor func,
STRequest * inRequest, STOptions * inOptions, STContext * inContext,
void *clientData, int maxdepth)
{
STCategoryNode *nodes[1024], *node;
uint32_t begin, end, i;
int ret = 0;
int curdepth = 0, ncurdepth = 0;
nodes[0] = root;
begin = 0;
end = 1;
ncurdepth = 1;
while (begin != end) {
node = nodes[begin];
ret = (*func) (inRequest, inOptions, inContext, clientData, node);
if (!ret) {
/* Abort */
break;
}
begin = MODINC(begin, 1024);
for (i = 0; i < node->nchildren; i++) {
nodes[end] = node->children[i];
end = MODINC(end, 1024);
}
/* Depth tracking. Do it only if walkTree is contrained by a maxdepth */
if (maxdepth > 0 && --ncurdepth == 0) {
/*
** No more children in current depth. The rest of the nodes
** we have in our list should be nodes in the depth below us.
*/
ncurdepth = (begin < end) ? (end - begin) : (1024 - begin + end);
if (++curdepth > maxdepth) {
/*
** Gone too deep. Stop.
*/
break;
}
}
}
return;
}
int
freeRule(STCategoryRule * rule)
{
uint32_t i;
char *p = (char *) rule->categoryName;
PR_FREEIF(p);
for (i = 0; i < rule->npats; i++)
free(rule->pats[i]);
free(rule);
return 0;
}
void
freeNodeRuns(STCategoryNode * root)
{
walkTree(root, freeNodeRunsProcessor, NULL, NULL, NULL, NULL, 0);
}
void
freeNodeMap(STGlobals * g)
{
uint32_t i;
/* all nodes are in the map table. Just delete all of those. */
for (i = 0; i < g->mNCategoryMap; i++) {
free(g->mCategoryMap[i]->node);
free(g->mCategoryMap[i]);
}
free(g->mCategoryMap);
}
int
freeCategories(STGlobals * g)
{
uint32_t i;
/*
** walk the tree and free runs held in nodes
*/
freeNodeRuns(&g->mCategoryRoot);
/*
** delete nodemap. This is the where nodes get deleted.
*/
freeNodeMap(g);
/*
** delete rule stuff
*/
for (i = 0; i < g->mNRules; i++) {
freeRule(g->mCategoryRules[i]);
}
free(g->mCategoryRules);
return 0;
}
/*
** categorizeRun
**
** categorize all the allocations of the run using the rules into
** a tree rooted at globls.mCategoryRoot
*/
int
categorizeRun(STOptions * inOptions, STContext * inContext,
const STRun * aRun, STGlobals * g)
{
uint32_t i;
#if defined(DEBUG_dp)
PRIntervalTime start = PR_IntervalNow();
fprintf(stderr, "DEBUG: categorizing run...\n");
#endif
/*
** First, cleanup our tree
*/
walkTree(&g->mCategoryRoot, freeNodeRunProcessor, NULL, inOptions,
inContext, NULL, 0);
if (g->mNCategoryMap > 0) {
for (i = 0; i < aRun->mAllocationCount; i++) {
categorizeAllocation(inOptions, inContext, aRun->mAllocations[i],
g);
}
}
/*
** the run is always going to be the one corresponding to the root node
*/
g->mCategoryRoot.runs[inContext->mIndex] = (STRun *) aRun;
g->mCategoryRoot.categoryName = ST_ROOT_CATEGORY_NAME;
#if defined(DEBUG_dp)
fprintf(stderr,
"DEBUG: categorizing ends: %dms [%d rules, %d allocations]\n",
PR_IntervalToMilliseconds(PR_IntervalNow() - start), g->mNRules,
aRun->mAllocationCount);
fprintf(stderr, "DEBUG: match : %dms [%d calls, %d rule-compares]\n",
PR_IntervalToMilliseconds(_gMatchTime), _gMatchCount,
_gMatchRules);
#endif
/*
** sort the tree based on our sort criterion
*/
walkTree(&g->mCategoryRoot, sortNodeProcessor, NULL, inOptions, inContext,
NULL, 0);
#if defined(DEBUG_dp)
walkTree(&g->mCategoryRoot, printNodeProcessor, NULL, inOptions,
inContext, &g->mCategoryRoot, 0);
#endif
return 0;
}
/*
** displayCategoryReport
**
** Generate the category report - a list of all categories and details about each
** depth parameter controls how deep we traverse the category tree.
*/
PRBool
displayCategoryNodeProcessor(STRequest * inRequest, STOptions * inOptions,
STContext * inContext, void *clientData,
STCategoryNode * node)
{
STCategoryNode *root = (STCategoryNode *) clientData;
uint32_t byteSize = 0, heapCost = 0, count = 0;
double percent = 0;
STOptions customOps;
if (node->runs[inContext->mIndex]) {
/*
** Byte size
*/
byteSize =
node->runs[inContext->mIndex]->mStats[inContext->mIndex].mSize;
/*
** Composite count
*/
count =
node->runs[inContext->mIndex]->mStats[inContext->mIndex].
mCompositeCount;
/*
** Heap operation cost
**/
heapCost =
node->runs[inContext->mIndex]->mStats[inContext->mIndex].
mHeapRuntimeCost;
/*
** % of total size
*/
if (root->runs[inContext->mIndex]) {
percent =
((double) byteSize) /
root->runs[inContext->mIndex]->mStats[inContext->mIndex].
mSize * 100;
}
}
PR_fprintf(inRequest->mFD, " <tr>\n" " <td>");
/* a link to topcallsites report with focus on category */
memcpy(&customOps, inOptions, sizeof(customOps));
PR_snprintf(customOps.mCategoryName, sizeof(customOps.mCategoryName),
"%s", node->categoryName);
htmlAnchor(inRequest, "top_callsites.html", node->categoryName, NULL,
"category-callsites", &customOps);
PR_fprintf(inRequest->mFD,
"</td>\n" " <td align=right>%u</td>\n"
" <td align=right>%4.1f%%</td>\n"
" <td align=right>%u</td>\n" " <td align=right>"
ST_MICROVAL_FORMAT "</td>\n" " </tr>\n", byteSize, percent,
count, ST_MICROVAL_PRINTABLE(heapCost));
return PR_TRUE;
}
int
displayCategoryReport(STRequest * inRequest, STCategoryNode * root, int depth)
{
PR_fprintf(inRequest->mFD,
"<table class=\"category-list data\">\n"
" <tr class=\"row-header\">\n"
" <th>Category</th>\n"
" <th>Composite Byte Size</th>\n"
" <th>%% of Total Size</th>\n"
" <th>Heap Object Count</th>\n"
" <th>Composite Heap Operations Seconds</th>\n" " </tr>\n");
walkTree(root, displayCategoryNodeProcessor, inRequest,
&inRequest->mOptions, inRequest->mContext, root, depth);
PR_fprintf(inRequest->mFD, "</table>\n");
return 0;
}

File diff suppressed because it is too large Load Diff

View File

@ -1,170 +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/. */
body {
margin: 0px;
padding: 0px;
font: Arial, sans-serif;
}
/* header stuff */
.spacetrace-header {
color: white;
background: green;
/* min-height: 2em; */
}
.spacetrace-title {
font-size: x-large;
font-weight: bold;
padding: 0.5em;
}
.navigate {
/* background: lightgrey; */
color: black;
position: absolute;
right: 0px;
margin: 0px;
padding: 2px;
}
.header-item {
border: 1px outset;
color: ButtonText;
background: ButtonFace;
margin: 0px;
padding: 2px;
}
.header-item > a {
font-weight: bold;
text-decoration: none;
}
.header-item {
font-weight: bold;
}
.category-title {
font-weight: bold;
}
/* footer stuff */
.footer-separator {
border: 1px inset;
display: none;
}
.footer {
text-align: right;
display: none;
}
.footer-text {
font-style: italic;
}
.option-box {
border: solid black;
background: grey;
padding-left: .5em;
padding-right: .5em;
margin: .5em;
}
.option-name {
font-weight: bold;
margin: 1em;
}
.option-box input[type=text]
{
border: inset thin;
padding: 2px;
}
.option-help {
white-space: pre;
right: 0px;
background: white;
padding: 0.5em;
border: thin inset;
}
.callsite-header {
padding: 10px;
}
/* data tables */
#callsite-details {
position: absolute;
right: 1px;
top: 40px;
width: 20%;
}
#callsites {
width: 75%;
height: 40%;
overflow: scroll;
}
#caller-stack {
height: 45%;
width: 75%;
overflow: scroll;
padding-top: 5px;
}
#allocations {
position: absolute;
right: 1px;
bottom: 1px;
height: 40%;
width: 20%;
overflow: scroll;
}
/* headers at the top of specific call site pages */
table.summary {
border: 1px solid black;
}
/* lists of callsites/etc */
table.data td {
border-top: 1px solid;
padding: 5px;
}
table.data {
clear: right;
border-collapse: collapse;
}
tr.row-header {
background: #009090;
width: 100%;
}
table.data th {
padding: 5px;
margin: 0px;
text-align: right;
}
th.callsite {
text-align: left !important;
}
/* links to source */
.source-extra {
display: none;
}
a.source, a.callsite {
text-decoration: none;
}

View File

@ -1,694 +0,0 @@
/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
*
* 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 spacetrace_h__
#define spacetrace_h__
/*
** spacetrace.h
**
** SpaceTrace is meant to take the output of trace-malloc and present
** a picture of allocations over the run of the application.
*/
/*
** Required includes.
*/
#include <stdint.h>
#include "nspr.h"
#include "prlock.h"
#include "prrwlock.h"
#include "nsTraceMalloc.h"
#include "tmreader.h"
#include "formdata.h"
/*
** Turn on to attempt adding support for graphs on your platform.
*/
#if defined(HAVE_BOUTELL_GD)
#define ST_WANT_GRAPHS 1
#endif /* HAVE_BOUTELL_GD */
#if !defined(ST_WANT_GRAPHS)
#define ST_WANT_GRAPHS 0
#endif
/*
** REPORT_ERROR
** REPORT_INFO
**
** Just report errors and stuff in a consistent manner.
*/
#define REPORT_ERROR(code, function) \
PR_fprintf(PR_STDERR, "error(%d):\t%s\n", code, #function)
#define REPORT_ERROR_MSG(code, msg) \
PR_fprintf(PR_STDERR, "error(%d):\t%s\n", code, msg)
#define REPORT_INFO(msg) \
PR_fprintf(PR_STDOUT, "%s: %s\n", globals.mProgramName, (msg))
#if defined(DEBUG_blythe) && 1
#define REPORT_blythe(code, msg) \
PR_fprintf(PR_STDOUT, "gab(%d):\t%s\n", code, msg)
#else
#define REPORT_blythe(code, msg)
#endif /* DEBUG_blythe */
/*
** CALLSITE_RUN
**
** How to get a callsite run.
** Allows for further indirection if needed later.
*/
#define CALLSITE_RUN(callsite) \
((STRun*)((callsite)->data))
/*
** ST_PERMS
** ST_FLAGS
**
** File permissions we desire.
** 0644
*/
#define ST_PERMS (PR_IRUSR | PR_IWUSR | PR_IRGRP | PR_IROTH)
#define ST_FLAGS (PR_WRONLY | PR_CREATE_FILE | PR_TRUNCATE)
/*
** Sorting order
*/
#define ST_WEIGHT 0 /* size * timeval */
#define ST_SIZE 1
#define ST_TIMEVAL 2
#define ST_COUNT 3
#define ST_HEAPCOST 4
/*
** Callsite loop direction flags.
*/
#define ST_FOLLOW_SIBLINGS 0
#define ST_FOLLOW_PARENTS 1
/*
** Graph data.
*/
#define STGD_WIDTH 640
#define STGD_HEIGHT 480
#define STGD_MARGIN 75
#define STGD_SPACE_X (STGD_WIDTH - (2 * STGD_MARGIN))
#define STGD_SPACE_Y (STGD_HEIGHT - (2 * STGD_MARGIN))
/*
** Minimum lifetime default, in seconds.
*/
#define ST_DEFAULT_LIFETIME_MIN 10
/*
** Allocations fall to this boundry size by default.
** Overhead is taken after alignment.
**
** The msvcrt malloc has an alignment of 16 with an overhead of 8.
** The win32 HeapAlloc has an alignment of 8 with an overhead of 8.
*/
#define ST_DEFAULT_ALIGNMENT_SIZE 16
#define ST_DEFAULT_OVERHEAD_SIZE 8
/*
** Numer of substring match specifications to allow.
*/
#define ST_SUBSTRING_MATCH_MAX 5
/*
** Max Number of patterns per rule
*/
#define ST_MAX_PATTERNS_PER_RULE 16
/*
** Rule pointers and child pointers are allocated in steps of ST_ALLOC_STEP
*/
#define ST_ALLOC_STEP 16
/*
** Name of the root category. Appears in UI.
*/
#define ST_ROOT_CATEGORY_NAME "All"
/*
** Size of our option string buffers.
*/
#define ST_OPTION_STRING_MAX 256
/*
** Set the desired resolution of the timevals.
** The resolution is just mimicking what is recorded in the trace-malloc
** output, and that is currently milliseconds.
*/
#define ST_TIMEVAL_RESOLUTION 1000
#define ST_TIMEVAL_FORMAT "%.3f"
#define ST_TIMEVAL_PRINTABLE(timeval) ((double)(timeval) / (double)ST_TIMEVAL_RESOLUTION)
#define ST_TIMEVAL_PRINTABLE64(timeval) ((double)((int64_t)(timeval)) / (double)ST_TIMEVAL_RESOLUTION)
#define ST_TIMEVAL_MAX ((uint32_t)-1 - ((uint32_t)-1 % ST_TIMEVAL_RESOLUTION))
#define ST_MICROVAL_RESOLUTION 1000000
#define ST_MICROVAL_FORMAT "%.6f"
#define ST_MICROVAL_PRINTABLE(timeval) ((double)(timeval) / (double)ST_MICROVAL_RESOLUTION)
#define ST_MICROVAL_PRINTABLE64(timeval) ((double)((int64_t)(timeval)) / (double)ST_MICROVAL_RESOLUTION)
#define ST_MICROVAL_MAX ((uint32_t)-1 - ((uint32_t)-1 % ST_MICROVAL_RESOLUTION))
/*
** Forward Declaration
*/
typedef struct __struct_STCategoryNode STCategoryNode;
typedef struct __struct_STCategoryRule STCategoryRule;
/*
** STAllocEvent
**
** An event that happens to an allocation (malloc, free, et. al.)
*/
typedef struct __struct_STAllocEvent
{
/*
** The type of allocation event.
** This maps directly to the trace malloc events (i.e. TM_EVENT_MALLOC)
*/
char mEventType;
/*
** Each event, foremost, has a chronologically increasing ID in
** relation to other allocation events. This is a time stamp
** of sorts.
*/
uint32_t mTimeval;
/*
** Every event has a heap ID (pointer).
** In the event of a realloc, this is the new heap ID.
** In the event of a free, this is the previous heap ID value.
*/
uint32_t mHeapID;
/*
** Every event, along with the heap ID, tells of the size.
** In the event of a realloc, this is the new size.
** In th event of a free, this is the previous size.
*/
uint32_t mHeapSize;
/*
** Every event has a callsite/stack backtrace.
** In the event of a realloc, this is the new callsite.
** In the event of a free, this is the previous call site.
*/
tmcallsite* mCallsite;
} STAllocEvent;
/*
** STAllocation
**
** An allocation is a temporal entity in the heap.
** It possibly lives under different heap IDs (pointers) and different
** sizes during its given time.
** An allocation is defined by the events during its lifetime.
** An allocation's lifetime is defined by the range of event IDs it holds.
*/
typedef struct __struct_STAllocation
{
/*
** The array of events.
*/
uint32_t mEventCount;
STAllocEvent* mEvents;
/*
** The lifetime/lifespan of the allocation.
*/
uint32_t mMinTimeval;
uint32_t mMaxTimeval;
/*
** Index of this allocation in the global run.
*/
uint32_t mRunIndex;
/*
** The runtime cost of heap events in this allocation.
** The cost is defined as the number of time units recorded as being
** spent in heap code (time of malloc, free, et al.).
** We do not track individual event cost in order to save space.
*/
uint32_t mHeapRuntimeCost;
} STAllocation;
/*
** STCallsiteStats
**
** Stats regarding a run, kept mainly for callsite runs.
*/
typedef struct __struct_STCallsiteStats
{
/*
** Sum timeval of the allocations.
** Callsite runs total all allocations below the callsite.
*/
uint64_t mTimeval64;
/*
** Sum weight of the allocations.
** Callsite runs total all allocations below the callsite.
*/
uint64_t mWeight64;
/*
** Sum size of the allocations.
** Callsite runs total all allocations below the callsite.
*/
uint32_t mSize;
/*
** A stamp, indicated the relevance of the run.
** If the stamp does not match the origin value, the
** data contained here-in is considered invalid.
*/
uint32_t mStamp;
/*
** A sum total of allocations (note, not sizes) below the callsite.
** This is NOT the same as STRun::mAllocationCount which
** tracks the STRun::mAllocations array size.
*/
uint32_t mCompositeCount;
/*
** A sum total runtime cost of heap operations below the calliste.
** The cost is defined as the number of time units recorded as being
** spent in heap code (time of malloc, free, et al.).
*/
uint32_t mHeapRuntimeCost;
} STCallsiteStats;
/*
** STRun
**
** A run is a closed set of allocations.
** Given a run, we can deduce information about the contained allocations.
** We can also determine if an allocation lives beyond a run (leak).
**
** A run might be used to represent allocations for an entire application.
** A run might also be used to represent allocations from a single callstack.
*/
typedef struct __struct_STRun
{
/*
** The array of allocations.
*/
uint32_t mAllocationCount;
STAllocation** mAllocations;
/*
** Callsites like to keep some information.
** As callsites are possibly shared between all contexts, each
** different context needs to keep different stats.
*/
STCallsiteStats *mStats;
} STRun;
/*
** Categorize allocations
**
** The objective is to have a tree of categories with each leaf node of the tree
** matching a set of callsites that belong to the category. Each category can
** signify a functional area like say css and hence the user can browse this
** tree looking for how much of each of these are live at an instant.
*/
/*
** STCategoryNode
*/
struct __struct_STCategoryNode
{
/*
** Category name
*/
const char *categoryName;
/*
** Pointer to parent node. NULL for Root.
*/
STCategoryNode *parent;
/*
** For non-leaf nodes, an array of children node pointers.
** NULL if leaf node.
*/
STCategoryNode** children;
uint32_t nchildren;
/*
** The Run(s). Valid for both leaf and parent nodes.
** One run per --Context to handle multiple data sets.
** The relevant index for the particular request will be
** mIndex stored by the mContext of the request.
*/
STRun **runs;
};
struct __struct_STCategoryRule
{
/*
** The pattern for the rule. Patterns are an array of strings.
** A callsite needs to pass substring match for all the strings.
*/
char* pats[ST_MAX_PATTERNS_PER_RULE];
uint32_t patlen[ST_MAX_PATTERNS_PER_RULE];
uint32_t npats;
/*
** Category name that this rule belongs to
*/
const char* categoryName;
/*
** The node this should be categorized into
*/
STCategoryNode* node;
};
/*
** CategoryName to Node mapping table
*/
typedef struct __struct_STCategoryMapEntry {
STCategoryNode* node;
const char * categoryName;
} STCategoryMapEntry;
/*
** Option genres.
**
** This helps to determine what functionality each option effects.
** In specific, this will help use determine when and when not to
** totally recaclulate the sorted run and categories.
** Be very aware that adding things to a particular genre, or adding a genre,
** may completely screw up the caching algorithms of SpaceTrace.
** See contextLookup() or ask someone that knows if you are in doubt.
*/
typedef enum __enum_STOptionGenre
{
CategoryGenre = 0,
DataSortGenre,
DataSetGenre,
DataSizeGenre,
UIGenre,
ServerGenre,
BatchModeGenre,
/*
** Last one please.
*/
MaxGenres
}
STOptionGenre;
/*
** STOptions
**
** Structure containing the varios options for the code.
** The definition of these options exists in a different file.
** We access that definition via macros to inline our structure definition.
*/
#define ST_CMD_OPTION_BOOL(option_name, option_genre, option_help) PRBool m##option_name;
#define ST_CMD_OPTION_STRING(option_name, option_genre, default_value, option_help) char m##option_name[ST_OPTION_STRING_MAX];
#define ST_CMD_OPTION_STRING_ARRAY(option_name, option_genre, array_size, option_help) char m##option_name[array_size][ST_OPTION_STRING_MAX];
#define ST_CMD_OPTION_STRING_PTR_ARRAY(option_name, option_genre, option_help) const char** m##option_name; uint32_t m##option_name##Count;
#define ST_CMD_OPTION_UINT32(option_name, option_genre, default_value, multiplier, option_help) uint32_t m##option_name;
#define ST_CMD_OPTION_UINT64(option_name, option_genre, default_value, multiplier, option_help) uint64_t m##option_name##64;
typedef struct __struct_STOptions
{
#include "stoptions.h"
}
STOptions;
typedef struct __struct_STContext
/*
** A per request, thread safe, manner of accessing the contained members.
** A reader/writer lock ensures that the data is properly initialized before
** readers of the data begin their work.
**
** mRWLock reader/writer lock.
** writer lock is held to ensure initialization, though
** others can be attempting to acquire read locks
** at that time.
** writer lock is also used in destruction to make sure
** there are no more readers of data contained herein.
** reader lock is to allow multiple clients to read the
** data at the same time; implies is they must not
** write anything.
** mIndex Consider this much like thread private data or thread
** local storage in a few places.
** The index is specifically reserved for this context's
** usage in other data structure array's provided
** for the particular thread/client/context.
** This should not be modified after initialization.
** mSortedRun A pre sorted run taken from the global run, with our
** options applied.
** mImageLock An overly simplistic locking mechanism to protect the
** shared image cache.
** The proper implementation would have a reader/writer
** lock per cached image data.
** However, this will prove to be simpler for the time
** being.
** mFootprintCached Whether or not YData contains something useful.
** mTimevalCached Whether or not YData contains something useful.
** mLifespanCached Whether or not YData contains something useful.
** mWeightCached Whether or not YData contains something useful.
** mFootprintYData Precomputed cached graph data.
** mTimevalYData Precomputed cached graph data.
** mLifespanYData Precomputed cached graph data.
** mWeightYData Precomputed cached graph data.
*/
{
PRRWLock* mRWLock;
uint32_t mIndex;
STRun* mSortedRun;
#if ST_WANT_GRAPHS
PRLock* mImageLock;
PRBool mFootprintCached;
PRBool mTimevalCached;
PRBool mLifespanCached;
PRBool mWeightCached;
uint32_t mFootprintYData[STGD_SPACE_X];
uint32_t mTimevalYData[STGD_SPACE_X];
uint32_t mLifespanYData[STGD_SPACE_X];
uint64_t mWeightYData64[STGD_SPACE_X];
#endif
}
STContext;
typedef struct __struct_STContextCacheItem
/*
** This basically pools the common items that the context cache will
** want to track on a per context basis.
**
** mOptions What options this item represents.
** mContext State/data this cache item is wrapping.
** mReferenceCount A count of clients currently using this item.
** Should this item be 0, then the cache might
** decide to evict this context.
** Should this item not be 0, once it reaches
** zero a condition variable in the context cache
** will be signaled to notify the availability.
** mLastAccessed A timestamp of when this item was last accessed/released.
** Ignore this unless the reference count is 0,
** This is used to evict the oldest unused item from
** the context cache.
** mInUse Mainly PR_FALSE only at the beginning of the process,
** but this indicates that the item has not yet been
** used at all, and thus shouldn't be evaluated for
** a cache hit.
*/
{
STOptions mOptions;
STContext mContext;
int32_t mReferenceCount;
PRIntervalTime mLastAccessed;
PRBool mInUse;
}
STContextCacheItem;
typedef struct __struct_STContextCache
/*
** A thread safe, possibly blocking, cache of context items.
**
** mLock Must hold the lock to read/access/write to this struct, as
** well as any items it holds.
** mCacheMiss All items are busy and there were no cache matches.
** This condition variable is used to wait until an item becomes
** "available" to be evicted from the cache.
** mItems Array of items.
** mItemCount Number of items in array.
** This is generally the same as the global option's command line
** mContexts....
*/
{
PRLock* mLock;
PRCondVar* mCacheMiss;
STContextCacheItem* mItems;
uint32_t mItemCount;
}
STContextCache;
/*
** STRequest
**
** Things specific to a request.
*/
typedef struct __struct_STRequest
{
/*
** Sink/where to output.
*/
PRFileDesc* mFD;
/*
** The filename requested.
*/
const char* mGetFileName;
/*
** The GET form data, if any.
*/
const FormData* mGetData;
/*
** Options specific to this request.
*/
STOptions mOptions;
/*
** The context/data/state of the request.
*/
STContext* mContext;
} STRequest;
/*
** STGlobals
**
** Various globals we keep around.
*/
typedef struct __struct_STGlobals
{
/*
** The string which identifies this program.
*/
const char* mProgramName;
/*
** Options derived from the command line.
** These are used as defaults, and should remain static during
** the run of the application.
*/
STOptions mCommandLineOptions;
/*
** Context cache.
** As clients come in, based on their options, a different context
** will be used to service them.
*/
STContextCache mContextCache;
/*
** Various counters for different types of events.
*/
uint32_t mMallocCount;
uint32_t mCallocCount;
uint32_t mReallocCount;
uint32_t mFreeCount;
/*
** Total events, operation counter.
*/
uint32_t mOperationCount;
/*
** The "run" of the input.
*/
STRun mRun;
/*
** Operation minimum/maximum timevals.
** So that we can determine the overall timeval of the run.
** NOTE: These are NOT the options to control the data set.
*/
uint32_t mMinTimeval;
uint32_t mMaxTimeval;
/*
** Calculates peak allocation overall for all allocations.
*/
uint32_t mPeakMemoryUsed;
uint32_t mMemoryUsed;
/*
** A list of rules for categorization read in from the mCategoryFile
*/
STCategoryRule** mCategoryRules;
uint32_t mNRules;
/*
** CategoryName to Node mapping table
*/
STCategoryMapEntry** mCategoryMap;
uint32_t mNCategoryMap;
/*
** Categorized allocations. For now we support only one tree.
*/
STCategoryNode mCategoryRoot;
/*
** tmreader hash tables.
** Moved into globals since we need to destroy these only after all
** client threads are finishes (after PR_Cleanup).
*/
tmreader* mTMR;
} STGlobals;
/*
** Function prototypes
*/
extern STRun* createRun(STContext* inContext, uint32_t aStamp);
extern void freeRun(STRun* aRun);
extern int initCategories(STGlobals* g);
extern int categorizeRun(STOptions* inOptions, STContext* inContext, const STRun* aRun, STGlobals* g);
extern STCategoryNode* findCategoryNode(const char *catName, STGlobals *g);
extern int freeCategories(STGlobals* g);
extern int displayCategoryReport(STRequest* inRequest, STCategoryNode *root, int depth);
extern int recalculateAllocationCost(STOptions* inOptions, STContext* inContext, STRun* aRun, STAllocation* aAllocation, PRBool updateParent);
extern void htmlHeader(STRequest* inRequest, const char* aTitle);
extern void htmlFooter(STRequest* inRequest);
extern void htmlAnchor(STRequest* inRequest,
const char* aHref,
const char* aText,
const char* aTarget,
const char* aClass,
STOptions* inOptions);
extern char *FormatNumber(int32_t num);
/*
** shared globals
*/
extern STGlobals globals;
#endif /* spacetrace_h__ */

View File

@ -1,302 +0,0 @@
/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
*
* 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/. */
/*
** stoptions.h
**
** Abstract the spacetrace options into a reusable format, such that
** many different pieces of the code can utilize the common list.
*/
/*
** There are three types of options.
** The destinction is quite important.
**
** CMD options are accessible from only the comamnd line.
** Such options should be considered global/static for the entire
** run of the application.
** Once set, no one can change these options during the run.
**
** WEB options are accessible from the web server options page.
** Such options can and will be changed on a per user basis during
** the run of the application.
** You should NEVER make an option a WEB only option, as this will
** break batch mode processing, and will likely not correctly
** define the options structure itself.
** These options will control the data caching used in the application
** to match a client to a cache of data.
**
** ALL options are both CMD and WEB options, with the properties of WEB
** options (the user will change these on a per client basis).
** Most likely this is the type of option you will desire to create.
*/
/*
** All types of options have some combination of the following elements:
**
** option_name The name of the option.
** option_genre Area the option effects; STOptionGenre.
** default_value The default value for the option.
** array_size Used to size a string array.
** multiplier Some numbers prefer conversion.
** option_help Help text to explain the option.
**
** NOTE! that the multiplier should be applied to the default value if you
** are going to assign the default_value into anything.
**
** Be very aware that adding things to a particular genre, or adding a genre,
** may completely screw up the caching algorithms of SpaceTrace.
** See contextLookup() or ask someone that knows if you are in doubt.
**
** The actual definition of the WEB and CMD macros however is left to the
** end user.
** We cover those that you do not define herein.
*/
#if !defined(ST_CMD_OPTION_BOOL)
#define ST_CMD_OPTION_BOOL(option_name, option_genre, option_help)
#endif
#if !defined(ST_WEB_OPTION_BOOL)
#define ST_WEB_OPTION_BOOL(option_name, option_genre, option_help)
#endif
#if !defined(ST_CMD_OPTION_STRING)
#define ST_CMD_OPTION_STRING(option_name, option_genre, default_value, option_help)
#endif
#if !defined(ST_WEB_OPTION_STRING)
#define ST_WEB_OPTION_STRING(option_name, option_genre, default_value, option_help)
#endif
#if !defined(ST_CMD_OPTION_STRING_ARRAY)
#define ST_CMD_OPTION_STRING_ARRAY(option_name, option_genre, array_size, option_help)
#endif
#if !defined(ST_WEB_OPTION_STRING_ARRAY)
#define ST_WEB_OPTION_STRING_ARRAY(option_name, option_genre, array_size, option_help)
#endif
#if !defined(ST_CMD_OPTION_STRING_PTR_ARRAY)
#define ST_CMD_OPTION_STRING_PTR_ARRAY(option_name, option_genre, option_help)
#endif
#if !defined(ST_WEB_OPTION_STRING_PTR_ARRAY)
#define ST_WEB_OPTION_STRING_PTR_ARRAY(option_name, option_genre, option_help)
#endif
#if !defined(ST_CMD_OPTION_UINT32)
#define ST_CMD_OPTION_UINT32(option_name, option_genre, default_value, multiplier, option_help)
#endif
#if !defined(ST_WEB_OPTION_UINT32)
#define ST_WEB_OPTION_UINT32(option_name, option_genre, default_value, multiplier, option_help)
#endif
#if !defined(ST_CMD_OPTION_UINT64)
#define ST_CMD_OPTION_UINT64(option_name, option_genre, default_value, multiplier, option_help)
#endif
#if !defined(ST_WEB_OPTION_UINT64)
#define ST_WEB_OPTION_UINT64(option_name, option_genre, default_value, multiplier, option_help)
#endif
/*
** ALL macros expand to both CMD and WEB macros.
** This basically means such options are accessible from both the command
** line and from the web options.
*/
#define ST_ALL_OPTION_BOOL(option_name, option_genre, option_help) \
ST_CMD_OPTION_BOOL(option_name, option_genre, option_help) \
ST_WEB_OPTION_BOOL(option_name, option_genre, option_help)
#define ST_ALL_OPTION_STRING(option_name, option_genre, default_value, option_help) \
ST_CMD_OPTION_STRING(option_name, option_genre, default_value, option_help) \
ST_WEB_OPTION_STRING(option_name, option_genre, default_value, option_help)
#define ST_ALL_OPTION_STRING_ARRAY(option_name, option_genre, array_size, option_help) \
ST_CMD_OPTION_STRING_ARRAY(option_name, option_genre, array_size, option_help) \
ST_WEB_OPTION_STRING_ARRAY(option_name, option_genre, array_size, option_help)
#define ST_ALL_OPTION_STRING_PTR_ARRAY(option_name, option_genre, option_help) \
ST_CMD_OPTION_STRING_PTR_ARRAY(option_name, option_genre, option_help) \
ST_WEB_OPTION_STRING_PTR_ARRAY(option_name, option_genre, option_help)
#define ST_ALL_OPTION_UINT32(option_name, option_genre, default_value, multiplier, option_help) \
ST_CMD_OPTION_UINT32(option_name, option_genre, default_value, multiplier, option_help) \
ST_WEB_OPTION_UINT32(option_name, option_genre, default_value, multiplier, option_help)
#define ST_ALL_OPTION_UINT64(option_name, option_genre, default_value, multiplier, option_help) \
ST_CMD_OPTION_UINT64(option_name, option_genre, default_value, multiplier, option_help) \
ST_WEB_OPTION_UINT64(option_name, option_genre, default_value, multiplier, option_help)
/****************************************************************************
** BEGIN, THE OPTIONS
**
** Order is somewhat relevant in that it will control 3 different things:
** 1) The order the members will be in the options structure.
** 2) The order the options are presented on the command line.
** 3) The order the options are presented on the web options page.
*/
ST_ALL_OPTION_STRING(CategoryName,
CategoryGenre,
ST_ROOT_CATEGORY_NAME,
"Specify a category for reports to focus upon.\n"
"See http://lxr.mozilla.org/mozilla/source/tools/trace-malloc/rules.txt\n")
ST_ALL_OPTION_UINT32(OrderBy,
DataSortGenre,
ST_SIZE, /* for dp :-D */
1,
"Determine the sort order.\n"
"0 by weight (size * lifespan).\n"
"1 by size.\n"
"2 by lifespan.\n"
"3 by allocation count.\n"
"4 by performance cost.\n")
ST_ALL_OPTION_STRING_ARRAY(RestrictText,
DataSetGenre,
ST_SUBSTRING_MATCH_MAX,
"Exclude allocations which do not have this text in their backtrace.\n"
"Multiple restrictions are treated as a logical AND operation.\n")
ST_ALL_OPTION_UINT32(SizeMin,
DataSetGenre,
0,
1,
"Exclude allocations that are below this byte size.\n")
ST_ALL_OPTION_UINT32(SizeMax,
DataSetGenre,
0xFFFFFFFF,
1,
"Exclude allocations that are above this byte size.\n")
ST_ALL_OPTION_UINT32(LifetimeMin,
DataSetGenre,
ST_DEFAULT_LIFETIME_MIN,
ST_TIMEVAL_RESOLUTION,
"Allocations must live this number of seconds or be ignored.\n")
ST_ALL_OPTION_UINT32(LifetimeMax,
DataSetGenre,
ST_TIMEVAL_MAX / ST_TIMEVAL_RESOLUTION,
ST_TIMEVAL_RESOLUTION,
"Allocations living longer than this number of seconds will be ignored.\n")
ST_ALL_OPTION_UINT32(TimevalMin,
DataSetGenre,
0,
ST_TIMEVAL_RESOLUTION,
"Allocations existing solely before this second will be ignored.\n"
"Live allocations at this second and after can be considered.\n")
ST_ALL_OPTION_UINT32(TimevalMax,
DataSetGenre,
ST_TIMEVAL_MAX / ST_TIMEVAL_RESOLUTION,
ST_TIMEVAL_RESOLUTION,
"Allocations existing solely after this second will be ignored.\n"
"Live allocations at this second and before can be considered.\n")
ST_ALL_OPTION_UINT32(AllocationTimevalMin,
DataSetGenre,
0,
ST_TIMEVAL_RESOLUTION,
"Live and dead allocations created before this second will be ignored.\n")
ST_ALL_OPTION_UINT32(AllocationTimevalMax,
DataSetGenre,
ST_TIMEVAL_MAX / ST_TIMEVAL_RESOLUTION,
ST_TIMEVAL_RESOLUTION,
"Live and dead allocations created after this second will be ignored.\n")
ST_ALL_OPTION_UINT32(AlignBy,
DataSizeGenre,
ST_DEFAULT_ALIGNMENT_SIZE,
1,
"All allocation sizes are made to be a multiple of this number.\n"
"Closer to actual heap conditions; set to 1 for true sizes.\n")
ST_ALL_OPTION_UINT32(Overhead,
DataSizeGenre,
ST_DEFAULT_OVERHEAD_SIZE,
1,
"After alignment, all allocations are made to increase by this number.\n"
"Closer to actual heap conditions; set to 0 for true sizes.\n")
ST_ALL_OPTION_UINT32(ListItemMax,
UIGenre,
500,
1,
"Specifies the maximum number of list items to present in each list.\n")
ST_ALL_OPTION_UINT64(WeightMin,
DataSetGenre,
0,
1,
"Exclude allocations that are below this weight (lifespan * size).\n")
ST_ALL_OPTION_UINT64(WeightMax,
DataSetGenre,
(0xFFFFFFFFLL << 32) + 0xFFFFFFFFLL,
1,
"Exclude allocations that are above this weight (lifespan * size).\n")
ST_CMD_OPTION_STRING(FileName,
DataSetGenre,
"-",
"Specifies trace-malloc input file.\n"
"\"-\" indicates stdin will be used as input.\n")
ST_CMD_OPTION_STRING(CategoryFile,
CategoryGenre,
"rules.txt",
"Specifies the category rules file.\n"
"This file contains rules about how to categorize allocations.\n")
ST_CMD_OPTION_UINT32(HttpdPort,
ServerGenre,
1969,
1,
"Specifies the default port the web server will listen on.\n")
ST_CMD_OPTION_STRING(OutputDir,
BatchModeGenre,
".",
"Specifies a directory to output batch mode requests.\n"
"The directory must exist and must not use a trailing slash.\n")
ST_CMD_OPTION_STRING_PTR_ARRAY(BatchRequest,
BatchModeGenre,
"This implicitly turns on batch mode.\n"
"Save each requested file into the output dir, then exit.\n")
ST_CMD_OPTION_UINT32(Contexts,
ServerGenre,
1,
1,
"How many configurations to cache at the cost of a lot of memory.\n"
"Dedicated servers can cache more client configurations for performance.\n")
ST_CMD_OPTION_BOOL(Help,
UIGenre,
"Show command line help.\n"
"See http://www.mozilla.org/projects/footprint/spaceTrace.html\n")
/*
** END, THE OPTIONS
****************************************************************************/
/*
** Everything is undefined after the header is included.
** This sets it up for multiple inclusion if so desired.
*/
#undef ST_ALL_OPTION_BOOL
#undef ST_CMD_OPTION_BOOL
#undef ST_WEB_OPTION_BOOL
#undef ST_ALL_OPTION_STRING
#undef ST_CMD_OPTION_STRING
#undef ST_WEB_OPTION_STRING
#undef ST_ALL_OPTION_STRING_ARRAY
#undef ST_CMD_OPTION_STRING_ARRAY
#undef ST_WEB_OPTION_STRING_ARRAY
#undef ST_ALL_OPTION_STRING_PTR_ARRAY
#undef ST_CMD_OPTION_STRING_PTR_ARRAY
#undef ST_WEB_OPTION_STRING_PTR_ARRAY
#undef ST_ALL_OPTION_UINT32
#undef ST_CMD_OPTION_UINT32
#undef ST_WEB_OPTION_UINT32
#undef ST_ALL_OPTION_UINT64
#undef ST_CMD_OPTION_UINT64
#undef ST_WEB_OPTION_UINT64

View File

@ -1,950 +0,0 @@
/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
*
* 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 <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <ctype.h>
#include <errno.h>
#include <math.h>
#include "nspr.h"
#include "tmreader.h"
#define ERROR_REPORT(num, val, msg) fprintf(stderr, "error(%d):\t\"%s\"\t%s\n", (num), (val), (msg));
#define CLEANUP(ptr) do { if(NULL != ptr) { free(ptr); ptr = NULL; } } while(0)
#define ticks2msec(reader, ticks) ticks2xsec((reader), (ticks), 1000)
#define ticks2usec(reader, ticks) ticks2xsec((reader), (ticks), 1000000)
#define TICK_RESOLUTION 1000
#define TICK_PRINTABLE(timeval) ((double)(timeval) / (double)ST_TIMEVAL_RESOLUTION)
typedef struct __struct_Options
/*
** Options to control how we perform.
**
** mProgramName Used in help text.
** mInputName Name of the file.
** mOutput Output file, append.
** Default is stdout.
** mOutputName Name of the file.
** mHelp Whether or not help should be shown.
** mOverhead How much overhead an allocation will have.
** mAlignment What boundry will the end of an allocation line up on.
** mPageSize Controls the page size. A page containing only fragments
** is not fragmented. A page containing any life memory
** costs mPageSize in bytes.
*/
{
const char* mProgramName;
char* mInputName;
FILE* mOutput;
char* mOutputName;
int mHelp;
unsigned mOverhead;
unsigned mAlignment;
unsigned mPageSize;
}
Options;
typedef struct __struct_Switch
/*
** Command line options.
*/
{
const char* mLongName;
const char* mShortName;
int mHasValue;
const char* mValue;
const char* mDescription;
}
Switch;
#define DESC_NEWLINE "\n\t\t"
static Switch gInputSwitch = {"--input", "-i", 1, NULL, "Specify input file." DESC_NEWLINE "stdin is default."};
static Switch gOutputSwitch = {"--output", "-o", 1, NULL, "Specify output file." DESC_NEWLINE "Appends if file exists." DESC_NEWLINE "stdout is default."};
static Switch gHelpSwitch = {"--help", "-h", 0, NULL, "Information on usage."};
static Switch gAlignmentSwitch = {"--alignment", "-al", 1, NULL, "All allocation sizes are made to be a multiple of this number." DESC_NEWLINE "Closer to actual heap conditions; set to 1 for true sizes." DESC_NEWLINE "Default value is 16."};
static Switch gOverheadSwitch = {"--overhead", "-ov", 1, NULL, "After alignment, all allocations are made to increase by this number." DESC_NEWLINE "Closer to actual heap conditions; set to 0 for true sizes." DESC_NEWLINE "Default value is 8."};
static Switch gPageSizeSwitch = {"--page-size", "-ps", 1, NULL, "Sets the page size which aids the identification of fragmentation." DESC_NEWLINE "Closer to actual heap conditions; set to 4294967295 for true sizes." DESC_NEWLINE "Default value is 4096."};
static Switch* gSwitches[] = {
&gInputSwitch,
&gOutputSwitch,
&gAlignmentSwitch,
&gOverheadSwitch,
&gPageSizeSwitch,
&gHelpSwitch
};
typedef struct __struct_AnyArray
/*
** Variable sized item array.
**
** mItems The void pointer items.
** mItemSize Size of each different item.
** mCount The number of items in the array.
** mCapacity How many more items we can hold before reallocing.
** mGrowBy How many items we allocate when we grow.
*/
{
void* mItems;
unsigned mItemSize;
unsigned mCount;
unsigned mCapacity;
unsigned mGrowBy;
}
AnyArray;
typedef int (*arrayMatchFunc)(void* inContext, AnyArray* inArray, void* inItem, unsigned inItemIndex)
/*
** Callback function for the arrayIndexFn function.
** Used to determine an item match by customizable criteria.
**
** inContext The criteria and state of the search.
** User specified/created.
** inArray The array the item is in.
** inItem The item to evaluate for match.
** inItemIndex The index of this particular item in the array.
**
** return int 0 to specify a match.
** !0 to continue the search performed by arrayIndexFn.
*/
;
typedef enum __enum_HeapEventType
/*
** Simple heap events are really one of two things.
*/
{
FREE,
ALLOC
}
HeapEventType;
typedef enum __enum_HeapObjectType
/*
** The various types of heap objects we track.
*/
{
ALLOCATION,
FRAGMENT
}
HeapObjectType;
typedef struct __struct_HeapObject HeapObject;
typedef struct __struct_HeapHistory
/*
** A marker as to what has happened.
**
** mTimestamp When history occurred.
** mTMRSerial The historical state as known to the tmreader.
** mObjectIndex Index to the object that was before or after this event.
** The index as in the index according to all heap objects
** kept in the TMState structure.
** We use an index instead of a pointer as the array of
** objects can change location in the heap.
*/
{
unsigned mTimestamp;
unsigned mTMRSerial;
unsigned mObjectIndex;
}
HeapHistory;
struct __struct_HeapObject
/*
** An object in the heap.
**
** A special case should be noted here. If either the birth or death
** history leads to an object of the same type, then this object
** is the same as that object, but was modified somehow.
** Also note that multiple objects may have the same birth object,
** as well as the same death object.
**
** mUniqueID Each object is unique.
** mType Either allocation or fragment.
** mHeapOffset Where in the heap the object is.
** mSize How much of the heap the object takes.
** mBirth History about the birth event.
** mDeath History about the death event.
*/
{
unsigned mUniqueID;
HeapObjectType mType;
unsigned mHeapOffset;
unsigned mSize;
HeapHistory mBirth;
HeapHistory mDeath;
};
typedef struct __struct_TMState
/*
** State of our current operation.
** Stats we are trying to calculate.
**
** mOptions Obilgatory options pointer.
** mTMR The tmreader, used in tmreader API calls.
** mLoopExitTMR Set to non zero in order to quickly exit from tmreader
** input loop. This will also result in an error.
** uMinTicks Start of run, milliseconds.
** uMaxTicks End of run, milliseconds.
*/
{
Options* mOptions;
tmreader* mTMR;
int mLoopExitTMR;
unsigned uMinTicks;
unsigned uMaxTicks;
}
TMState;
int initOptions(Options* outOptions, int inArgc, char** inArgv)
/*
** returns int 0 if successful.
*/
{
int retval = 0;
int loop = 0;
int switchLoop = 0;
int match = 0;
const int switchCount = sizeof(gSwitches) / sizeof(gSwitches[0]);
Switch* current = NULL;
/*
** Set any defaults.
*/
memset(outOptions, 0, sizeof(Options));
outOptions->mProgramName = inArgv[0];
outOptions->mInputName = strdup("-");
outOptions->mOutput = stdout;
outOptions->mOutputName = strdup("stdout");
outOptions->mAlignment = 16;
outOptions->mOverhead = 8;
if(NULL == outOptions->mOutputName || NULL == outOptions->mInputName)
{
retval = __LINE__;
ERROR_REPORT(retval, "stdin/stdout", "Unable to strdup.");
}
/*
** Go through and attempt to do the right thing.
*/
for(loop = 1; loop < inArgc && 0 == retval; loop++)
{
match = 0;
current = NULL;
for(switchLoop = 0; switchLoop < switchCount && 0 == retval; switchLoop++)
{
if(0 == strcmp(gSwitches[switchLoop]->mLongName, inArgv[loop]))
{
match = __LINE__;
}
else if(0 == strcmp(gSwitches[switchLoop]->mShortName, inArgv[loop]))
{
match = __LINE__;
}
if(match)
{
if(gSwitches[switchLoop]->mHasValue)
{
/*
** Attempt to absorb next option to fullfill value.
*/
if(loop + 1 < inArgc)
{
loop++;
current = gSwitches[switchLoop];
current->mValue = inArgv[loop];
}
}
else
{
current = gSwitches[switchLoop];
}
break;
}
}
if(0 == match)
{
outOptions->mHelp = __LINE__;
retval = __LINE__;
ERROR_REPORT(retval, inArgv[loop], "Unknown command line switch.");
}
else if(NULL == current)
{
outOptions->mHelp = __LINE__;
retval = __LINE__;
ERROR_REPORT(retval, inArgv[loop], "Command line switch requires a value.");
}
else
{
/*
** Do something based on address/swtich.
*/
if(current == &gInputSwitch)
{
CLEANUP(outOptions->mInputName);
outOptions->mInputName = strdup(current->mValue);
if(NULL == outOptions->mInputName)
{
retval = __LINE__;
ERROR_REPORT(retval, current->mValue, "Unable to strdup.");
}
}
else if(current == &gOutputSwitch)
{
CLEANUP(outOptions->mOutputName);
if(NULL != outOptions->mOutput && stdout != outOptions->mOutput)
{
fclose(outOptions->mOutput);
outOptions->mOutput = NULL;
}
outOptions->mOutput = fopen(current->mValue, "a");
if(NULL == outOptions->mOutput)
{
retval = __LINE__;
ERROR_REPORT(retval, current->mValue, "Unable to open output file.");
}
else
{
outOptions->mOutputName = strdup(current->mValue);
if(NULL == outOptions->mOutputName)
{
retval = __LINE__;
ERROR_REPORT(retval, current->mValue, "Unable to strdup.");
}
}
}
else if(current == &gHelpSwitch)
{
outOptions->mHelp = __LINE__;
}
else if(current == &gAlignmentSwitch)
{
unsigned arg = 0;
char* endScan = NULL;
errno = 0;
arg = strtoul(current->mValue, &endScan, 0);
if(0 == errno && endScan != current->mValue)
{
outOptions->mAlignment = arg;
}
else
{
retval = __LINE__;
ERROR_REPORT(retval, current->mValue, "Unable to convert to a number.");
}
}
else if(current == &gOverheadSwitch)
{
unsigned arg = 0;
char* endScan = NULL;
errno = 0;
arg = strtoul(current->mValue, &endScan, 0);
if(0 == errno && endScan != current->mValue)
{
outOptions->mOverhead = arg;
}
else
{
retval = __LINE__;
ERROR_REPORT(retval, current->mValue, "Unable to convert to a number.");
}
}
else if(current == &gPageSizeSwitch)
{
unsigned arg = 0;
char* endScan = NULL;
errno = 0;
arg = strtoul(current->mValue, &endScan, 0);
if(0 == errno && endScan != current->mValue)
{
outOptions->mPageSize = arg;
}
else
{
retval = __LINE__;
ERROR_REPORT(retval, current->mValue, "Unable to convert to a number.");
}
}
else
{
retval = __LINE__;
ERROR_REPORT(retval, current->mLongName, "No handler for command line switch.");
}
}
}
return retval;
}
uint32_t ticks2xsec(tmreader* aReader, uint32_t aTicks, uint32_t aResolution)
/*
** Convert platform specific ticks to second units
*/
{
return (uint32)((aResolution * aTicks) / aReader->ticksPerSec);
}
void cleanOptions(Options* inOptions)
/*
** Clean up any open handles.
*/
{
unsigned loop = 0;
CLEANUP(inOptions->mInputName);
CLEANUP(inOptions->mOutputName);
if(NULL != inOptions->mOutput && stdout != inOptions->mOutput)
{
fclose(inOptions->mOutput);
}
memset(inOptions, 0, sizeof(Options));
}
void showHelp(Options* inOptions)
/*
** Show some simple help text on usage.
*/
{
int loop = 0;
const int switchCount = sizeof(gSwitches) / sizeof(gSwitches[0]);
const char* valueText = NULL;
printf("usage:\t%s [arguments]\n", inOptions->mProgramName);
printf("\n");
printf("arguments:\n");
for(loop = 0; loop < switchCount; loop++)
{
if(gSwitches[loop]->mHasValue)
{
valueText = " <value>";
}
else
{
valueText = "";
}
printf("\t%s%s\n", gSwitches[loop]->mLongName, valueText);
printf("\t %s%s", gSwitches[loop]->mShortName, valueText);
printf(DESC_NEWLINE "%s\n\n", gSwitches[loop]->mDescription);
}
printf("This tool reports heap fragmentation stats from a trace-malloc log.\n");
}
AnyArray* arrayCreate(unsigned inItemSize, unsigned inGrowBy)
/*
** Create an array container object.
*/
{
AnyArray* retval = NULL;
if(0 != inGrowBy && 0 != inItemSize)
{
retval = (AnyArray*)calloc(1, sizeof(AnyArray));
retval->mItemSize = inItemSize;
retval->mGrowBy = inGrowBy;
}
return retval;
}
void arrayDestroy(AnyArray* inArray)
/*
** Release the memory the array contains.
** This will release the items as well.
*/
{
if(NULL != inArray)
{
if(NULL != inArray->mItems)
{
free(inArray->mItems);
}
free(inArray);
}
}
unsigned arrayAlloc(AnyArray* inArray, unsigned inItems)
/*
** Resize the item array capcity to a specific number of items.
** This could possibly truncate the array, so handle that as well.
**
** returns unsigned <= inArray->mCapacity on success.
*/
{
unsigned retval = (unsigned)-1;
if(NULL != inArray)
{
void* moved = NULL;
moved = realloc(inArray->mItems, inItems * inArray->mItemSize);
if(NULL != moved)
{
inArray->mItems = moved;
inArray->mCapacity = inItems;
if(inArray->mCount > inItems)
{
inArray->mCount = inItems;
}
retval = inItems;
}
}
return retval;
}
void* arrayItem(AnyArray* inArray, unsigned inIndex)
/*
** Return the array item at said index.
** Zero based index.
**
** returns void* NULL on failure.
*/
{
void* retval = NULL;
if(NULL != inArray && inIndex < inArray->mCount)
{
retval = (void*)((char*)inArray->mItems + (inArray->mItemSize * inIndex));
}
return retval;
}
unsigned arrayIndex(AnyArray* inArray, void* inItem, unsigned inStartIndex)
/*
** Go through the array from the index specified looking for an item
** match based on byte for byte comparison.
** We allow specifying the start index in order to handle arrays with
** duplicate items.
**
** returns unsigned >= inArray->mCount on failure.
*/
{
unsigned retval = (unsigned)-1;
if(NULL != inArray && NULL != inItem && inStartIndex < inArray->mCount)
{
void* curItem = NULL;
for(retval = inStartIndex; retval < inArray->mCount; retval++)
{
curItem = arrayItem(inArray, retval);
if(0 == memcmp(inItem, curItem, inArray->mItemSize))
{
break;
}
}
}
return retval;
}
unsigned arrayIndexFn(AnyArray* inArray, arrayMatchFunc inFunc, void* inFuncContext, unsigned inStartIndex)
/*
** Go through the array from the index specified looking for an item
** match based upon the return value of inFunc (0, Zero, is a match).
** We allow specifying the start index in order to facilitate looping over
** the array which could have multiple matches.
**
** returns unsigned >= inArray->mCount on failure.
*/
{
unsigned retval = (unsigned)-1;
if(NULL != inArray && NULL != inFunc && inStartIndex < inArray->mCount)
{
void* curItem = NULL;
for(retval = inStartIndex; retval < inArray->mCount; retval++)
{
curItem = arrayItem(inArray, retval);
if(0 == inFunc(inFuncContext, inArray, curItem, retval))
{
break;
}
}
}
return retval;
}
unsigned arrayAddItem(AnyArray* inArray, void* inItem)
/*
** Add a new item to the array.
** This is done by copying the item.
**
** returns unsigned < inArray->mCount on success.
*/
{
unsigned retval = (unsigned)-1;
if(NULL != inArray && NULL != inItem)
{
int noCopy = 0;
/*
** See if the array should grow.
*/
if(inArray->mCount == inArray->mCapacity)
{
unsigned allocRes = 0;
allocRes = arrayAlloc(inArray, inArray->mCapacity + inArray->mGrowBy);
if(allocRes > inArray->mCapacity)
{
noCopy = __LINE__;
}
}
if(0 == noCopy)
{
retval = inArray->mCount;
inArray->mCount++;
memcpy(arrayItem(inArray, retval), inItem, inArray->mItemSize);
}
}
return retval;
}
HeapObject* initHeapObject(HeapObject* inObject)
/*
** Function to init the heap object just right.
** Sets the unique ID to something unique.
*/
{
HeapObject* retval = inObject;
if(NULL != inObject)
{
static unsigned uniqueGenerator = 0;
memset(inObject, -1, sizeof(HeapObject));
inObject->mUniqueID = uniqueGenerator;
uniqueGenerator++;
}
return retval;
}
int simpleHeapEvent(TMState* inStats, HeapEventType inType, unsigned mTimestamp, unsigned inSerial, unsigned inHeapID, unsigned inSize)
/*
** A new heap event will cause the creation of a new heap object.
** The new heap object will displace, or replace, a heap object of a different type.
*/
{
int retval = 0;
HeapObject newObject;
/*
** Set the most basic object details.
*/
initHeapObject(&newObject);
newObject.mHeapOffset = inHeapID;
newObject.mSize = inSize;
if(FREE == inType)
{
newObject.mType = FRAGMENT;
}
else if(ALLOC == inType)
{
newObject.mType = ALLOCATION;
}
/*
** Add it to the heap object array.
*/
/*
** TODO GAB
**
** First thing to do is to add the new object to the heap in order to
** obtain a valid index.
**
** Next, find all matches to this range of heap memory that this event
** refers to, that are alive during this timestamp (no death yet).
** Fill in the death event of those objects.
** If the objects contain some portions outside of the range, then
** new objects for those ranges need to be created that carry on
** the same object type, have the index of the old object for birth,
** and the serial of the old object, new timestamp of course.
** The old object's death points to the new object, which tells why the
** fragmentation took place.
** The new object birth points to the old object only if a fragment.
** An allocation only has a birth object when it is a realloc (complex)
** heap event.
**
** I believe this give us enough information to look up particular
** details of the heap at any given time.
*/
return retval;
}
int complexHeapEvent(TMState* inStats, unsigned mTimestamp, unsigned inOldSerial, unsigned inOldHeapID, unsigned inOSize, unsigned inNewSerial, unsigned inNewHeapID, unsigned inNewSize)
/*
** Generally, this event intends to chain one old heap object to a newer heap object.
** Otherwise, the functionality should recognizable ala simpleHeapEvent.
*/
{
int retval = 0;
/*
** TODO GAB
*/
return retval;
}
unsigned actualByteSize(Options* inOptions, unsigned retval)
/*
** Apply alignment and overhead to size to figure out actual byte size.
** This by default mimics spacetrace with default options (msvc crt heap).
*/
{
if(0 != retval)
{
unsigned eval = 0;
unsigned over = 0;
eval = retval - 1;
if(0 != inOptions->mAlignment)
{
over = eval % inOptions->mAlignment;
}
retval = eval + inOptions->mOverhead + inOptions->mAlignment - over;
}
return retval;
}
void tmEventHandler(tmreader* inReader, tmevent* inEvent)
/*
** Callback from the tmreader_eventloop.
** Build up our fragmentation information herein.
*/
{
char type = inEvent->type;
TMState* stats = (TMState*)inReader->data;
/*
** Only intersted in handling events of a particular type.
*/
switch(type)
{
default:
return;
case TM_EVENT_MALLOC:
case TM_EVENT_CALLOC:
case TM_EVENT_REALLOC:
case TM_EVENT_FREE:
break;
}
/*
** Should we even try to look?
** Set mLoopExitTMR to non-zero to abort the read loop faster.
*/
if(0 == stats->mLoopExitTMR)
{
Options* options = (Options*)stats->mOptions;
unsigned timestamp = ticks2msec(stats->mTMR, inEvent->u.alloc.interval);
unsigned actualSize = actualByteSize(options, inEvent->u.alloc.size);
unsigned heapID = inEvent->u.alloc.ptr;
unsigned serial = inEvent->serial;
/*
** Check the timestamp range of our overall state.
*/
if(stats->uMinTicks > timestamp)
{
stats->uMinTicks = timestamp;
}
if(stats->uMaxTicks < timestamp)
{
stats->uMaxTicks = timestamp;
}
/*
** Realloc in general deserves some special attention if dealing
** with an old allocation (not new memory).
*/
if(TM_EVENT_REALLOC == type && 0 != inEvent->u.alloc.oldserial)
{
unsigned oldActualSize = actualByteSize(options, inEvent->u.alloc.oldsize);
unsigned oldHeapID = inEvent->u.alloc.oldptr;
unsigned oldSerial = inEvent->u.alloc.oldserial;
if(0 == actualSize)
{
/*
** Reallocs of size zero are to become free events.
*/
stats->mLoopExitTMR = simpleHeapEvent(stats, FREE, timestamp, serial, oldHeapID, oldActualSize);
}
else if(heapID != oldHeapID || actualSize != oldActualSize)
{
/*
** Reallocs which moved generate two events.
** Reallocs which changed size generate two events.
**
** One event to free the old memory area.
** Another event to allocate the new memory area.
** They are to be linked to one another, so the history
** and true origin can be tracked.
*/
stats->mLoopExitTMR = complexHeapEvent(stats, timestamp, oldSerial, oldHeapID, oldActualSize, serial, heapID, actualSize);
}
else
{
/*
** The realloc is not considered an operation and is skipped.
** It is not an operation, because it did not move or change
** size; this can happen if a realloc falls within the
** alignment of an allocation.
** Say if you realloc a 1 byte allocation to 2 bytes, it will
** not really change heap impact unless you have 1 set as
** the alignment of your allocations.
*/
}
}
else if(TM_EVENT_FREE == type)
{
/*
** Generate a free event to create a fragment.
*/
stats->mLoopExitTMR = simpleHeapEvent(stats, FREE, timestamp, serial, heapID, actualSize);
}
else
{
/*
** Generate an allocation event to clear fragments.
*/
stats->mLoopExitTMR = simpleHeapEvent(stats, ALLOC, timestamp, serial, heapID, actualSize);
}
}
}
int tmfrags(Options* inOptions)
/*
** Load the input file and report stats.
*/
{
int retval = 0;
TMState stats;
memset(&stats, 0, sizeof(stats));
stats.mOptions = inOptions;
stats.uMinTicks = 0xFFFFFFFFU;
/*
** Need a tmreader.
*/
stats.mTMR = tmreader_new(inOptions->mProgramName, &stats);
if(NULL != stats.mTMR)
{
int tmResult = 0;
tmResult = tmreader_eventloop(stats.mTMR, inOptions->mInputName, tmEventHandler);
if(0 == tmResult)
{
retval = __LINE__;
ERROR_REPORT(retval, inOptions->mInputName, "Problem reading trace-malloc data.");
}
if(0 != stats.mLoopExitTMR)
{
retval = stats.mLoopExitTMR;
ERROR_REPORT(retval, inOptions->mInputName, "Aborted trace-malloc input loop.");
}
tmreader_destroy(stats.mTMR);
stats.mTMR = NULL;
}
else
{
retval = __LINE__;
ERROR_REPORT(retval, inOptions->mProgramName, "Unable to obtain tmreader.");
}
return retval;
}
int main(int inArgc, char** inArgv)
{
int retval = 0;
Options options;
retval = initOptions(&options, inArgc, inArgv);
if(options.mHelp)
{
showHelp(&options);
}
else if(0 == retval)
{
retval = tmfrags(&options);
}
cleanOptions(&options);
return retval;
}

View File

@ -1,892 +0,0 @@
/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
*
* 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 <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <errno.h> /* XXX push error reporting out to clients? */
#ifndef XP_WIN
#include <unistd.h>
#else
#include <stddef.h>
#endif
#include "prlog.h"
#include "plhash.h"
/* make sure this happens before tmreader.h */
#define PL_ARENA_CONST_ALIGN_MASK 2
#include "plarena.h"
#include "prnetdb.h"
#include "nsTraceMalloc.h"
#include "tmreader.h"
#undef DEBUG_tmreader
static int accum_byte(FILE *fp, uint32_t *uip)
{
int c = getc(fp);
if (c == EOF)
return 0;
*uip = (*uip << 8) | c;
return 1;
}
static int get_uint32(FILE *fp, uint32_t *uip)
{
int c;
uint32_t ui;
c = getc(fp);
if (c == EOF)
return 0;
ui = 0;
if (c & 0x80) {
c &= 0x7f;
if (c & 0x40) {
c &= 0x3f;
if (c & 0x20) {
c &= 0x1f;
if (c & 0x10) {
if (!accum_byte(fp, &ui))
return 0;
} else {
ui = (uint32_t) c;
}
if (!accum_byte(fp, &ui))
return 0;
} else {
ui = (uint32_t) c;
}
if (!accum_byte(fp, &ui))
return 0;
} else {
ui = (uint32_t) c;
}
if (!accum_byte(fp, &ui))
return 0;
} else {
ui = (uint32_t) c;
}
*uip = ui;
return 1;
}
static char *get_string(FILE *fp)
{
char *cp;
int c;
static char buf[256];
static char *bp = buf, *ep = buf + sizeof buf;
static size_t bsize = sizeof buf;
cp = bp;
do {
c = getc(fp);
if (c == EOF)
return 0;
if (cp == ep) {
if (bp == buf) {
bp = malloc(2 * bsize);
if (bp)
memcpy(bp, buf, bsize);
} else {
bp = realloc(bp, 2 * bsize);
}
if (!bp)
return 0;
cp = bp + bsize;
bsize *= 2;
ep = bp + bsize;
}
*cp++ = c;
} while (c != '\0');
return strdup(bp);
}
static int get_tmevent(FILE *fp, tmevent *event)
{
int c;
char *s;
c = getc(fp);
if (c == EOF)
return 0;
event->type = (char) c;
if (!get_uint32(fp, &event->serial))
return 0;
switch (c) {
case TM_EVENT_LIBRARY:
s = get_string(fp);
if (!s)
return 0;
event->u.libname = s;
#ifdef DEBUG_tmreader
fprintf(stderr, "tmevent %c %u libname=\"%s\"\n", event->type, event->serial,
event->u.libname);
#endif
break;
case TM_EVENT_FILENAME:
s = get_string(fp);
if (!s)
return 0;
event->u.srcname = s;
#ifdef DEBUG_tmreader
fprintf(stderr, "tmevent %c %u srcname=\"%s\"\n",
event->type, event->serial, event->u.srcname);
#endif
break;
case TM_EVENT_METHOD:
if (!get_uint32(fp, &event->u.method.library))
return 0;
if (!get_uint32(fp, &event->u.method.filename))
return 0;
if (!get_uint32(fp, &event->u.method.linenumber))
return 0;
s = get_string(fp);
if (!s)
return 0;
event->u.method.name = s;
#ifdef DEBUG_tmreader
fprintf(stderr, "tmevent %c %u library=%u filename=%u linenumber=%u "
"name=\"%s\"\n",
event->type, event->serial,
event->u.method.library, event->u.method.filename,
event->u.method.linenumber, event->u.method.name);
#endif
break;
case TM_EVENT_CALLSITE:
if (!get_uint32(fp, &event->u.site.parent))
return 0;
if (!get_uint32(fp, &event->u.site.method))
return 0;
if (!get_uint32(fp, &event->u.site.offset))
return 0;
#ifdef DEBUG_tmreader
fprintf(stderr, "tmevent %c %u parent=%u method=%u offset=%u\n",
event->type, event->serial,
event->u.site.parent, event->u.site.method,
event->u.site.offset);
#endif
break;
case TM_EVENT_MALLOC:
case TM_EVENT_CALLOC:
case TM_EVENT_FREE:
if (!get_uint32(fp, &event->u.alloc.interval))
return 0;
if (!get_uint32(fp, &event->u.alloc.cost))
return 0;
if (!get_uint32(fp, &event->u.alloc.ptr))
return 0;
if (!get_uint32(fp, &event->u.alloc.size))
return 0;
event->u.alloc.oldserial = 0;
event->u.alloc.oldptr = 0;
event->u.alloc.oldsize = 0;
#ifdef DEBUG_tmreader
fprintf(stderr, "tmevent %c %u interval=%u cost=%u ptr=0x%x size=%u\n",
event->type, event->serial,
event->u.alloc.interval, event->u.alloc.cost,
event->u.alloc.ptr, event->u.alloc.size);
#endif
#if defined(DEBUG_dp)
if (c == TM_EVENT_MALLOC)
printf("%d malloc %d 0x%p\n", event->u.alloc.cost,
event->u.alloc.size, event->u.alloc.ptr);
else if (c == TM_EVENT_CALLOC)
printf("%d calloc %d 0x%p\n", event->u.alloc.cost,
event->u.alloc.size, event->u.alloc.ptr);
else
printf("%d free %d 0x%p\n", event->u.alloc.cost,
event->u.alloc.size, event->u.alloc.ptr);
#endif
break;
case TM_EVENT_REALLOC:
if (!get_uint32(fp, &event->u.alloc.interval))
return 0;
if (!get_uint32(fp, &event->u.alloc.cost))
return 0;
if (!get_uint32(fp, &event->u.alloc.ptr))
return 0;
if (!get_uint32(fp, &event->u.alloc.size))
return 0;
if (!get_uint32(fp, &event->u.alloc.oldserial))
return 0;
if (!get_uint32(fp, &event->u.alloc.oldptr))
return 0;
if (!get_uint32(fp, &event->u.alloc.oldsize))
return 0;
#ifdef DEBUG_tmreader
fprintf(stderr, "tmevent %c %u interval=%u cost=%u ptr=0x%x size=%u "
"oldserial=%u oldptr=0x%x oldsize=%u\n",
event->type, event->serial,
event->u.alloc.interval, event->u.alloc.cost,
event->u.alloc.ptr, event->u.alloc.size,
event->u.alloc.oldserial, event->u.alloc.oldptr,
event->u.alloc.oldsize);
#endif
#if defined(DEBUG_dp)
printf("%d realloc %d 0x%p %d\n", event->u.alloc.cost,
event->u.alloc.size, event->u.alloc.ptr, event->u.alloc.oldsize);
#endif
break;
case TM_EVENT_STATS:
if (!get_uint32(fp, &event->u.stats.tmstats.calltree_maxstack))
return 0;
if (!get_uint32(fp, &event->u.stats.tmstats.calltree_maxdepth))
return 0;
if (!get_uint32(fp, &event->u.stats.tmstats.calltree_parents))
return 0;
if (!get_uint32(fp, &event->u.stats.tmstats.calltree_maxkids))
return 0;
if (!get_uint32(fp, &event->u.stats.tmstats.calltree_kidhits))
return 0;
if (!get_uint32(fp, &event->u.stats.tmstats.calltree_kidmisses))
return 0;
if (!get_uint32(fp, &event->u.stats.tmstats.calltree_kidsteps))
return 0;
if (!get_uint32(fp, &event->u.stats.tmstats.callsite_recurrences))
return 0;
if (!get_uint32(fp, &event->u.stats.tmstats.backtrace_calls))
return 0;
if (!get_uint32(fp, &event->u.stats.tmstats.backtrace_failures))
return 0;
if (!get_uint32(fp, &event->u.stats.tmstats.btmalloc_failures))
return 0;
if (!get_uint32(fp, &event->u.stats.tmstats.dladdr_failures))
return 0;
if (!get_uint32(fp, &event->u.stats.tmstats.malloc_calls))
return 0;
if (!get_uint32(fp, &event->u.stats.tmstats.malloc_failures))
return 0;
if (!get_uint32(fp, &event->u.stats.tmstats.calloc_calls))
return 0;
if (!get_uint32(fp, &event->u.stats.tmstats.calloc_failures))
return 0;
if (!get_uint32(fp, &event->u.stats.tmstats.realloc_calls))
return 0;
if (!get_uint32(fp, &event->u.stats.tmstats.realloc_failures))
return 0;
if (!get_uint32(fp, &event->u.stats.tmstats.free_calls))
return 0;
if (!get_uint32(fp, &event->u.stats.tmstats.null_free_calls))
return 0;
if (!get_uint32(fp, &event->u.stats.calltree_maxkids_parent))
return 0;
if (!get_uint32(fp, &event->u.stats.calltree_maxstack_top))
return 0;
#ifdef DEBUG_tmreader
fprintf(stderr, "tmevent %c %u\n", event->type, event->serial);
#endif
break;
default:
fprintf(stderr, "Unknown event type 0x%x\n", (unsigned int)event->type);
return 0;
}
return 1;
}
static void *arena_alloc(void* pool, size_t size)
{
PLArenaPool* arena = (PLArenaPool*)pool;
void* result;
PL_ARENA_ALLOCATE(result, arena, size);
memset(result, 0, size);
return result;
}
static void *generic_alloctable(void *pool, size_t size)
{
return arena_alloc(pool, size);
}
static void generic_freetable(void *pool, void *item)
{
/* do nothing - arena-allocated */
}
static PLHashEntry *filename_allocentry(void *pool, const void *key)
{
return (PLHashEntry*)arena_alloc(pool, sizeof(PLHashEntry));
}
static PLHashEntry *callsite_allocentry(void *pool, const void *key)
{
return (PLHashEntry*)arena_alloc(pool, sizeof(tmcallsite));
}
static void init_graphnode(tmgraphnode* node)
{
node->in = node->out = NULL;
node->up = node->down = node->next = NULL;
node->low = 0;
node->allocs.bytes.direct = node->allocs.bytes.total = 0;
node->allocs.calls.direct = node->allocs.calls.total = 0;
node->frees.bytes.direct = node->frees.bytes.total = 0;
node->frees.calls.direct = node->frees.calls.total = 0;
node->sqsum = 0;
node->sort = -1;
}
static PLHashEntry *graphnode_allocentry(void *pool, const void *key)
{
tmgraphnode* node = (tmgraphnode*)arena_alloc(pool, sizeof(tmgraphnode));
if (!node)
return NULL;
init_graphnode(node);
return &node->entry;
}
static void init_method(tmmethodnode *node)
{
node->graphnode.in = node->graphnode.out = NULL;
node->graphnode.up = node->graphnode.down = node->graphnode.next = NULL;
node->graphnode.low = 0;
node->graphnode.allocs.bytes.direct = node->graphnode.allocs.bytes.total = 0;
node->graphnode.allocs.calls.direct = node->graphnode.allocs.calls.total = 0;
node->graphnode.frees.bytes.direct = node->graphnode.frees.bytes.total = 0;
node->graphnode.frees.calls.direct = node->graphnode.frees.calls.total = 0;
node->graphnode.sqsum = 0;
node->graphnode.sort = -1;
node->sourcefile = NULL;
node->linenumber = 0;
}
static PLHashEntry *method_allocentry(void *pool, const void *key)
{
tmmethodnode *node =
(tmmethodnode*) arena_alloc(pool, sizeof(tmmethodnode));
if (!node)
return NULL;
init_method(node);
return &node->graphnode.entry;
}
static void graphnode_freeentry(void *pool, PLHashEntry *he, unsigned flag)
{
/* Always free the value, which points to a strdup'd string. */
free(he->value);
#if 0 /* using arenas now, no freeing! */
/* Free the whole thing if we're told to. */
if (flag == HT_FREE_ENTRY)
free((void*) he);
#endif
}
static void component_freeentry(void *pool, PLHashEntry *he, unsigned flag)
{
if (flag == HT_FREE_ENTRY) {
tmgraphnode *comp = (tmgraphnode*) he;
/* Free the key, which was strdup'd (N.B. value also points to it). */
free((void*) tmcomponent_name(comp));
#if 0 /* using arenas now, no freeing! */
free((void*) comp);
#endif
}
}
static PLHashAllocOps filename_hashallocops = {
generic_alloctable, generic_freetable,
filename_allocentry, graphnode_freeentry
};
static PLHashAllocOps callsite_hashallocops = {
generic_alloctable, generic_freetable,
callsite_allocentry, graphnode_freeentry
};
static PLHashAllocOps graphnode_hashallocops = {
generic_alloctable, generic_freetable,
graphnode_allocentry, graphnode_freeentry
};
static PLHashAllocOps method_hashallocops = {
generic_alloctable, generic_freetable,
method_allocentry, graphnode_freeentry
};
static PLHashAllocOps component_hashallocops = {
generic_alloctable, generic_freetable,
graphnode_allocentry, component_freeentry
};
static PLHashNumber hash_serial(const void *key)
{
return (PLHashNumber) key;
}
tmreader *tmreader_new(const char *program, void *data)
{
tmreader *tmr;
tmr = calloc(1, sizeof *tmr);
if (!tmr)
return NULL;
tmr->program = program;
tmr->data = data;
PL_INIT_ARENA_POOL(&tmr->arena, "TMReader", 256*1024);
tmr->libraries = PL_NewHashTable(100, hash_serial, PL_CompareValues,
PL_CompareStrings, &graphnode_hashallocops,
&tmr->arena);
tmr->filenames = PL_NewHashTable(100, hash_serial, PL_CompareValues,
PL_CompareStrings, &filename_hashallocops,
&tmr->arena);
tmr->components = PL_NewHashTable(10000, PL_HashString, PL_CompareStrings,
PL_CompareValues, &component_hashallocops,
&tmr->arena);
tmr->methods = PL_NewHashTable(10000, hash_serial, PL_CompareValues,
PL_CompareStrings, &method_hashallocops,
&tmr->arena);
tmr->callsites = PL_NewHashTable(200000, hash_serial, PL_CompareValues,
PL_CompareValues, &callsite_hashallocops,
&tmr->arena);
tmr->calltree_root.entry.value = (void*) strdup("root");
if (!tmr->libraries || !tmr->components || !tmr->methods ||
!tmr->callsites || !tmr->calltree_root.entry.value ||
!tmr->filenames) {
tmreader_destroy(tmr);
return NULL;
}
return tmr;
}
void tmreader_destroy(tmreader *tmr)
{
if (tmr->libraries)
PL_HashTableDestroy(tmr->libraries);
if (tmr->filenames)
PL_HashTableDestroy(tmr->filenames);
if (tmr->components)
PL_HashTableDestroy(tmr->components);
if (tmr->methods)
PL_HashTableDestroy(tmr->methods);
if (tmr->callsites)
PL_HashTableDestroy(tmr->callsites);
PL_FinishArenaPool(&tmr->arena);
free(tmr);
}
int tmreader_eventloop(tmreader *tmr, const char *filename,
tmeventhandler eventhandler)
{
FILE *fp;
char buf[NS_TRACE_MALLOC_MAGIC_SIZE];
tmevent event;
static const char magic[] = NS_TRACE_MALLOC_MAGIC;
if (strcmp(filename, "-") == 0) {
fp = stdin;
} else {
#if defined(XP_WIN32)
fp = fopen(filename, "rb");
#else
fp = fopen(filename, "r");
#endif
if (!fp) {
fprintf(stderr, "%s: can't open %s: %s.\n",
tmr->program, filename, strerror(errno));
return 0;
}
}
if (read(fileno(fp), buf, sizeof buf) != sizeof buf ||
strncmp(buf, magic, sizeof buf) != 0) {
fprintf(stderr, "%s: bad magic string %s at start of %s.\n",
tmr->program, buf, filename);
fprintf(stderr, "either the data file is out of date,\nor your tools are out of date.\n");
return 0;
}
/* Read in ticks per second. Used to convert platform specific intervals to time values */
if (read(fileno(fp), &tmr->ticksPerSec, sizeof tmr->ticksPerSec) != sizeof tmr->ticksPerSec) {
fprintf(stderr, "%s: Cannot read ticksPerSec. Log file read error.\n",
tmr->program);
return 0;
}
tmr->ticksPerSec = PR_ntohl(tmr->ticksPerSec);
#ifdef DEBUG_dp
printf("DEBUG: ticks per sec = %d\n", tmr->ticksPerSec);
#endif
while (get_tmevent(fp, &event)) {
switch (event.type) {
case TM_EVENT_LIBRARY: {
const void *key;
PLHashNumber hash;
PLHashEntry **hep, *he;
key = (const void*) (uintptr_t) event.serial;
hash = hash_serial(key);
hep = PL_HashTableRawLookup(tmr->libraries, hash, key);
he = *hep;
PR_ASSERT(!he);
if (he) exit(2);
he = PL_HashTableRawAdd(tmr->libraries, hep, hash, key,
event.u.libname);
if (!he) {
perror(tmr->program);
return -1;
}
break;
}
case TM_EVENT_FILENAME: {
const void *key;
PLHashNumber hash;
PLHashEntry **hep, *he;
key = (const void*) (uintptr_t) event.serial;
hash = hash_serial(key);
hep = PL_HashTableRawLookup(tmr->filenames, hash, key);
he = *hep;
PR_ASSERT(!he);
if (he) exit(2);
he = PL_HashTableRawAdd(tmr->filenames, hep, hash, key,
event.u.srcname);
if (!he) {
perror(tmr->program);
return -1;
}
break;
}
case TM_EVENT_METHOD: {
const void *key, *sourcekey;
PLHashNumber hash, sourcehash;
PLHashEntry **hep, *he, **sourcehep, *sourcehe;
char *name, *head, *mark, save;
tmgraphnode *comp, *lib;
tmmethodnode *meth;
key = (const void*) (uintptr_t) event.serial;
hash = hash_serial(key);
hep = PL_HashTableRawLookup(tmr->methods, hash, key);
he = *hep;
PR_ASSERT(!he);
if (he) exit(2);
name = event.u.method.name;
he = PL_HashTableRawAdd(tmr->methods, hep, hash, key, name);
if (!he) {
perror(tmr->program);
return -1;
}
meth = (tmmethodnode*) he;
meth->linenumber = event.u.method.linenumber;
sourcekey = (const void*) (uintptr_t) event.u.method.filename;
sourcehash = hash_serial(sourcekey);
sourcehep = PL_HashTableRawLookup(tmr->filenames, sourcehash, sourcekey);
sourcehe = *sourcehep;
meth->sourcefile = filename_name(sourcehe);
head = name;
mark = strchr(name, ':');
if (!mark) {
mark = name;
while (*mark != '\0' && *mark == '_')
mark++;
head = mark;
mark = strchr(head, '_');
if (!mark) {
mark = strchr(head, '+');
if (!mark)
mark = head + strlen(head);
}
}
save = *mark;
*mark = '\0';
hash = PL_HashString(head);
hep = PL_HashTableRawLookup(tmr->components, hash, head);
he = *hep;
if (he) {
comp = (tmgraphnode*) he;
} else {
head = strdup(head);
if (head) {
he = PL_HashTableRawAdd(tmr->components, hep, hash, head,
head);
}
if (!he) {
perror(tmr->program);
return -1;
}
comp = (tmgraphnode*) he;
key = (const void*) (uintptr_t) event.u.method.library;
hash = hash_serial(key);
lib = (tmgraphnode*)
*PL_HashTableRawLookup(tmr->libraries, hash, key);
if (lib) {
comp->up = lib;
comp->next = lib->down;
lib->down = comp;
}
}
*mark = save;
meth->graphnode.up = comp;
meth->graphnode.next = comp->down;
comp->down = &(meth->graphnode);
break;
}
case TM_EVENT_CALLSITE: {
const void *key, *mkey;
PLHashNumber hash, mhash;
PLHashEntry **hep, *he;
tmcallsite *site, *parent;
tmmethodnode *meth;
key = (const void*) (uintptr_t) event.serial;
hash = hash_serial(key);
hep = PL_HashTableRawLookup(tmr->callsites, hash, key);
he = *hep;
/* there should not be an entry here! */
PR_ASSERT(!he);
if (he) exit(2);
if (event.u.site.parent == 0) {
parent = &tmr->calltree_root;
} else {
parent = tmreader_callsite(tmr, event.u.site.parent);
if (!parent) {
fprintf(stderr, "%s: no parent for %lu (%lu)!\n",
tmr->program, (unsigned long) event.serial,
(unsigned long) event.u.site.parent);
continue;
}
}
he = PL_HashTableRawAdd(tmr->callsites, hep, hash, key, NULL);
if (!he) {
perror(tmr->program);
return -1;
}
site = (tmcallsite*) he;
site->parent = parent;
site->siblings = parent->kids;
parent->kids = site;
site->kids = NULL;
mkey = (const void*) (uintptr_t) event.u.site.method;
mhash = hash_serial(mkey);
meth = (tmmethodnode*)
*PL_HashTableRawLookup(tmr->methods, mhash, mkey);
site->method = meth;
site->offset = event.u.site.offset;
site->allocs.bytes.direct = site->allocs.bytes.total = 0;
site->allocs.calls.direct = site->allocs.calls.total = 0;
site->frees.bytes.direct = site->frees.bytes.total = 0;
site->frees.calls.direct = site->frees.calls.total = 0;
break;
}
case TM_EVENT_MALLOC:
case TM_EVENT_CALLOC:
case TM_EVENT_REALLOC: {
tmcallsite *site;
uint32_t size, oldsize;
double delta, sqdelta, sqszdelta = 0;
tmgraphnode *comp, *lib;
tmmethodnode *meth;
site = tmreader_callsite(tmr, event.serial);
if (!site) {
fprintf(stderr, "%s: no callsite for '%c' (%lu)!\n",
tmr->program, event.type, (unsigned long) event.serial);
continue;
}
size = event.u.alloc.size;
oldsize = event.u.alloc.oldsize;
delta = (double)size - (double)oldsize;
site->allocs.bytes.direct += (unsigned long)delta;
if (event.type != TM_EVENT_REALLOC)
site->allocs.calls.direct++;
meth = site->method;
if (meth) {
meth->graphnode.allocs.bytes.direct += (unsigned long)delta;
sqdelta = delta * delta;
if (event.type == TM_EVENT_REALLOC) {
sqszdelta = ((double)size * size)
- ((double)oldsize * oldsize);
meth->graphnode.sqsum += sqszdelta;
} else {
meth->graphnode.sqsum += sqdelta;
meth->graphnode.allocs.calls.direct++;
}
comp = meth->graphnode.up;
if (comp) {
comp->allocs.bytes.direct += (unsigned long)delta;
if (event.type == TM_EVENT_REALLOC) {
comp->sqsum += sqszdelta;
} else {
comp->sqsum += sqdelta;
comp->allocs.calls.direct++;
}
lib = comp->up;
if (lib) {
lib->allocs.bytes.direct += (unsigned long)delta;
if (event.type == TM_EVENT_REALLOC) {
lib->sqsum += sqszdelta;
} else {
lib->sqsum += sqdelta;
lib->allocs.calls.direct++;
}
}
}
}
break;
}
case TM_EVENT_FREE: {
tmcallsite *site;
uint32_t size;
tmgraphnode *comp, *lib;
tmmethodnode *meth;
site = tmreader_callsite(tmr, event.serial);
if (!site) {
fprintf(stderr, "%s: no callsite for '%c' (%lu)!\n",
tmr->program, event.type, (unsigned long) event.serial);
continue;
}
size = event.u.alloc.size;
site->frees.bytes.direct += size;
site->frees.calls.direct++;
meth = site->method;
if (meth) {
meth->graphnode.frees.bytes.direct += size;
meth->graphnode.frees.calls.direct++;
comp = meth->graphnode.up;
if (comp) {
comp->frees.bytes.direct += size;
comp->frees.calls.direct++;
lib = comp->up;
if (lib) {
lib->frees.bytes.direct += size;
lib->frees.calls.direct++;
}
}
}
break;
}
case TM_EVENT_STATS:
break;
}
eventhandler(tmr, &event);
}
return 1;
}
tmgraphnode *tmreader_library(tmreader *tmr, uint32_t serial)
{
const void *key;
PLHashNumber hash;
key = (const void*) (uintptr_t) serial;
hash = hash_serial(key);
return (tmgraphnode*) *PL_HashTableRawLookup(tmr->libraries, hash, key);
}
tmgraphnode *tmreader_filename(tmreader *tmr, uint32_t serial)
{
const void *key;
PLHashNumber hash;
key = (const void*) (uintptr_t) serial;
hash = hash_serial(key);
return (tmgraphnode*) *PL_HashTableRawLookup(tmr->filenames, hash, key);
}
tmgraphnode *tmreader_component(tmreader *tmr, const char *name)
{
PLHashNumber hash;
hash = PL_HashString(name);
return (tmgraphnode*) *PL_HashTableRawLookup(tmr->components, hash, name);
}
tmmethodnode *tmreader_method(tmreader *tmr, uint32_t serial)
{
const void *key;
PLHashNumber hash;
key = (const void*) (uintptr_t) serial;
hash = hash_serial(key);
return (tmmethodnode*) *PL_HashTableRawLookup(tmr->methods, hash, key);
}
tmcallsite *tmreader_callsite(tmreader *tmr, uint32_t serial)
{
const void *key;
PLHashNumber hash;
key = (const void*) (uintptr_t) serial;
hash = hash_serial(key);
return (tmcallsite*) *PL_HashTableRawLookup(tmr->callsites, hash, key);
}
int tmgraphnode_connect(tmgraphnode *from, tmgraphnode *to, tmcallsite *site)
{
tmgraphlink *outlink;
tmgraphedge *edge;
for (outlink = from->out; outlink; outlink = outlink->next) {
if (outlink->node == to) {
/*
* Say the stack looks like this: ... => JS => js => JS => js.
* We must avoid overcounting JS=>js because the first edge total
* includes the second JS=>js edge's total (which is because the
* lower site's total includes all its kids' totals).
*/
edge = TM_LINK_TO_EDGE(outlink, TM_EDGE_OUT_LINK);
if (!to->low || to->low < from->low) {
/* Add the direct and total counts to edge->allocs. */
edge->allocs.bytes.direct += site->allocs.bytes.direct;
edge->allocs.bytes.total += site->allocs.bytes.total;
edge->allocs.calls.direct += site->allocs.calls.direct;
edge->allocs.calls.total += site->allocs.calls.total;
/* Now update the free counts. */
edge->frees.bytes.direct += site->frees.bytes.direct;
edge->frees.bytes.total += site->frees.bytes.total;
edge->frees.calls.direct += site->frees.calls.direct;
edge->frees.calls.total += site->frees.calls.total;
}
return 1;
}
}
edge = (tmgraphedge*) malloc(sizeof(tmgraphedge));
if (!edge)
return 0;
edge->links[TM_EDGE_OUT_LINK].node = to;
edge->links[TM_EDGE_OUT_LINK].next = from->out;
from->out = &edge->links[TM_EDGE_OUT_LINK];
edge->links[TM_EDGE_IN_LINK].node = from;
edge->links[TM_EDGE_IN_LINK].next = to->in;
to->in = &edge->links[TM_EDGE_IN_LINK];
edge->allocs = site->allocs;
edge->frees = site->frees;
return 1;
}

View File

@ -1,191 +0,0 @@
/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
*
* 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 tmreader_h___
#define tmreader_h___
#include "plhash.h"
#include "nsTraceMalloc.h"
#include "plarena.h"
#ifdef __cplusplus
extern "C" {
#endif
typedef struct tmreader tmreader;
typedef struct tmevent tmevent;
typedef struct tmcounts tmcounts;
typedef struct tmallcounts tmallcounts;
typedef struct tmgraphlink tmgraphlink;
typedef struct tmgraphedge tmgraphedge;
typedef struct tmgraphnode tmgraphnode;
typedef struct tmcallsite tmcallsite;
typedef struct tmmethodnode tmmethodnode;
struct tmevent {
char type;
uint32_t serial;
union {
char *libname;
char *srcname;
struct {
uint32_t library;
uint32_t filename;
uint32_t linenumber;
char *name;
} method;
struct {
uint32_t parent;
uint32_t method;
uint32_t offset;
} site;
struct {
uint32_t interval; /* in ticks */
uint32_t ptr;
uint32_t size;
uint32_t oldserial;
uint32_t oldptr;
uint32_t oldsize;
uint32_t cost; /* in ticks */
} alloc;
struct {
nsTMStats tmstats;
uint32_t calltree_maxkids_parent;
uint32_t calltree_maxstack_top;
} stats;
} u;
};
struct tmcounts {
uint32_t direct; /* things allocated by this node's code */
uint32_t total; /* direct + things from all descendents */
};
struct tmallcounts {
tmcounts bytes;
tmcounts calls;
};
struct tmgraphnode {
PLHashEntry entry; /* key is serial or name, value must be name */
tmgraphlink *in;
tmgraphlink *out;
tmgraphnode *up; /* parent in supergraph, e.g., JS for JS_*() */
tmgraphnode *down; /* subgraph kids, declining bytes.total order */
tmgraphnode *next; /* next kid in supergraph node's down list */
int low; /* 0 or lowest current tree walk level */
tmallcounts allocs;
tmallcounts frees;
double sqsum; /* sum of squared bytes.direct */
int sort; /* sorted index in node table, -1 if no table */
};
struct tmmethodnode {
tmgraphnode graphnode;
char *sourcefile;
uint32_t linenumber;
};
#define tmgraphnode_name(node) ((char*) (node)->entry.value)
#define tmmethodnode_name(node) ((char*) (node)->graphnode.entry.value)
#define tmlibrary_serial(lib) ((uint32_t) (lib)->entry.key)
#define tmcomponent_name(comp) ((const char*) (comp)->entry.key)
#define filename_name(hashentry) ((char*)hashentry->value)
/* Half a graphedge, not including per-edge allocation stats. */
struct tmgraphlink {
tmgraphlink *next; /* next fanning out from or into a node */
tmgraphnode *node; /* the other node (to if OUT, from if IN) */
};
/*
* It's safe to downcast a "from" tmgraphlink (one linked from a node's out
* pointer) to tmgraphedge. To go from an "out" (linked via tmgraphedge.from)
* or "in" (linked via tmgraphedge.to) list link to its containing edge, use
* TM_LINK_TO_EDGE(link, which).
*/
struct tmgraphedge {
tmgraphlink links[2];
tmallcounts allocs;
tmallcounts frees;
};
/* Indices into tmgraphedge.links -- out must come first. */
#define TM_EDGE_OUT_LINK 0
#define TM_EDGE_IN_LINK 1
#define TM_LINK_TO_EDGE(link,which) ((tmgraphedge*) &(link)[-(which)])
struct tmcallsite {
PLHashEntry entry; /* key is site serial number */
tmcallsite *parent; /* calling site */
tmcallsite *siblings; /* other sites reached from parent */
tmcallsite *kids; /* sites reached from here */
tmmethodnode *method; /* method node in tmr->methods graph */
uint32_t offset; /* pc offset from start of method */
tmallcounts allocs;
tmallcounts frees;
void *data; /* tmreader clients can stick arbitrary
* data onto a callsite.
*/
};
struct tmreader {
const char *program;
void *data;
PLHashTable *libraries;
PLHashTable *filenames;
PLHashTable *components;
PLHashTable *methods;
PLHashTable *callsites;
PLArenaPool arena;
tmcallsite calltree_root;
uint32_t ticksPerSec;
};
typedef void (*tmeventhandler)(tmreader *tmr, tmevent *event);
/* The tmreader constructor and destructor. */
extern tmreader *tmreader_new(const char *program, void *data);
extern void tmreader_destroy(tmreader *tmr);
/*
* Return -1 on permanent fatal error, 0 if filename can't be opened or is not
* a trace-malloc logfile, and 1 on success.
*/
extern int tmreader_eventloop(tmreader *tmr, const char *filename,
tmeventhandler eventhandler);
/* Map serial number or name to graphnode or callsite. */
extern tmgraphnode *tmreader_library(tmreader *tmr, uint32_t serial);
extern tmgraphnode *tmreader_filename(tmreader *tmr, uint32_t serial);
extern tmgraphnode *tmreader_component(tmreader *tmr, const char *name);
extern tmmethodnode *tmreader_method(tmreader *tmr, uint32_t serial);
extern tmcallsite *tmreader_callsite(tmreader *tmr, uint32_t serial);
/*
* Connect node 'from' to node 'to' with an edge, if there isn't one already
* connecting the nodes. Add site's allocation stats to the edge only if we
* create the edge, or if we find that it exists, but that to->low is zero or
* less than from->low.
*
* If the callsite tree already totals allocation costs (tmcounts.total for
* each site includes tmcounts.direct for that site, plus tmcounts.total for
* all kid sites), then the node->low watermarks should be set from the tree
* level when walking the callsite tree, and should be set to non-zero values
* only if zero (the root is at level 0). A low watermark should be cleared
* when the tree walk unwinds past the level at which it was set non-zero.
*
* Return 0 on error (malloc failure) and 1 on success.
*/
extern int tmgraphnode_connect(tmgraphnode *from, tmgraphnode *to,
tmcallsite *site);
#ifdef __cplusplus
}
#endif
#endif /* tmreader_h___ */

View File

@ -1,830 +0,0 @@
/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
*
* 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 <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <ctype.h>
#include <errno.h>
#include <math.h>
#include "nspr.h"
#include "tmreader.h"
#define ERROR_REPORT(num, val, msg) fprintf(stderr, "error(%d):\t\"%s\"\t%s\n", (num), (val), (msg));
#define CLEANUP(ptr) do { if(NULL != ptr) { free(ptr); ptr = NULL; } } while(0)
#define COST_RESOLUTION 1000
#define COST_PRINTABLE(cost) ((double)(cost) / (double)COST_RESOLUTION)
typedef struct __struct_Options
/*
** Options to control how we perform.
**
** mProgramName Used in help text.
** mInputName Name of the file.
** mOutput Output file, append.
** Default is stdout.
** mOutputName Name of the file.
** mHelp Whether or not help should be shown.
** mOverhead How much overhead an allocation will have.
** mAlignment What boundry will the end of an allocation line up on.
** mAverages Whether or not to display averages.
** mDeviances Whether or not to display standard deviations.
** mRunLength Whether or not to display run length.
*/
{
const char* mProgramName;
char* mInputName;
FILE* mOutput;
char* mOutputName;
int mHelp;
unsigned mOverhead;
unsigned mAlignment;
int mAverages;
int mDeviances;
int mRunLength;
}
Options;
typedef struct __struct_Switch
/*
** Command line options.
*/
{
const char* mLongName;
const char* mShortName;
int mHasValue;
const char* mValue;
const char* mDescription;
}
Switch;
#define DESC_NEWLINE "\n\t\t"
static Switch gInputSwitch = {"--input", "-i", 1, NULL, "Specify input file." DESC_NEWLINE "stdin is default."};
static Switch gOutputSwitch = {"--output", "-o", 1, NULL, "Specify output file." DESC_NEWLINE "Appends if file exists." DESC_NEWLINE "stdout is default."};
static Switch gHelpSwitch = {"--help", "-h", 0, NULL, "Information on usage."};
static Switch gAlignmentSwitch = {"--alignment", "-al", 1, NULL, "All allocation sizes are made to be a multiple of this number." DESC_NEWLINE "Closer to actual heap conditions; set to 1 for true sizes." DESC_NEWLINE "Default value is 16."};
static Switch gOverheadSwitch = {"--overhead", "-ov", 1, NULL, "After alignment, all allocations are made to increase by this number." DESC_NEWLINE "Closer to actual heap conditions; set to 0 for true sizes." DESC_NEWLINE "Default value is 8."};
static Switch gAveragesSwitch = {"--averages", "-avg", 0, NULL, "Display averages."};
static Switch gDeviationsSwitch = {"--deviations", "-dev", 0, NULL, "Display standard deviations from the average." DESC_NEWLINE "Implies --averages."};
static Switch gRunLengthSwitch = {"--run-length", "-rl", 0, NULL, "Display the run length in seconds."};
static Switch* gSwitches[] = {
&gInputSwitch,
&gOutputSwitch,
&gAlignmentSwitch,
&gOverheadSwitch,
&gAveragesSwitch,
&gDeviationsSwitch,
&gRunLengthSwitch,
&gHelpSwitch
};
typedef struct _struct_VarianceState
/*
** State for a single pass variance calculation.
*/
{
unsigned mCount;
uint64_t mSum;
uint64_t mSquaredSum;
}
VarianceState;
typedef struct __struct_TMStats
/*
** Stats we are trying to calculate.
**
** mOptions Obilgatory options pointer.
** uMemoryInUse Current tally of memory in use.
** uPeakMemory Heap topped out at this byte level.
** uObjectsInUse Different allocations outstanding.
** uPeakObjects Highest object count.
** uMallocs Number of malloc calls.
** uCallocs Number of calloc calls.
** uReallocs Number of realloc calls.
** uFrees Number of free calls.
** uMallocSize Bytes from malloc.
** uCallocSize Bytes from calloc.
** uReallocSize Bytes from realloc.
** uFreeSize Bytes from free.
** mMallocSizeVar Variance of bytes.
** mCallocSizeVar Variance of bytes.
** mReallocSizeVar Variance of bytes.
** mFreeSizeVar Variance of bytes.
** uMallocCost Time of mallocs.
** uCallocCost Time of callocs.
** uReallocCost Time of reallocs.
** uFreeCost Time of frees.
** mMallocCostVar Variance of cost.
** mCallocCostVar Variance of cost.
** mReallocCostVar Variance of cost.
** mFreeCostVar Variance of cost.
** uMinTicks Start of run.
** uMaxTicks End of run.
*/
{
Options* mOptions;
unsigned uMemoryInUse;
unsigned uPeakMemory;
unsigned uObjectsInUse;
unsigned uPeakObjects;
unsigned uMallocs;
unsigned uCallocs;
unsigned uReallocs;
unsigned uFrees;
unsigned uMallocSize;
unsigned uCallocSize;
unsigned uReallocSize;
unsigned uFreeSize;
VarianceState mMallocSizeVar;
VarianceState mCallocSizeVar;
VarianceState mReallocSizeVar;
VarianceState mFreeSizeVar;
unsigned uMallocCost;
unsigned uCallocCost;
unsigned uReallocCost;
unsigned uFreeCost;
VarianceState mMallocCostVar;
VarianceState mCallocCostVar;
VarianceState mReallocCostVar;
VarianceState mFreeCostVar;
unsigned uMinTicks;
unsigned uMaxTicks;
}
TMStats;
int initOptions(Options* outOptions, int inArgc, char** inArgv)
/*
** returns int 0 if successful.
*/
{
int retval = 0;
int loop = 0;
int switchLoop = 0;
int match = 0;
const int switchCount = sizeof(gSwitches) / sizeof(gSwitches[0]);
Switch* current = NULL;
/*
** Set any defaults.
*/
memset(outOptions, 0, sizeof(Options));
outOptions->mProgramName = inArgv[0];
outOptions->mInputName = strdup("-");
outOptions->mOutput = stdout;
outOptions->mOutputName = strdup("stdout");
outOptions->mAlignment = 16;
outOptions->mOverhead = 8;
if(NULL == outOptions->mOutputName || NULL == outOptions->mInputName)
{
retval = __LINE__;
ERROR_REPORT(retval, "stdin/stdout", "Unable to strdup.");
}
/*
** Go through and attempt to do the right thing.
*/
for(loop = 1; loop < inArgc && 0 == retval; loop++)
{
match = 0;
current = NULL;
for(switchLoop = 0; switchLoop < switchCount && 0 == retval; switchLoop++)
{
if(0 == strcmp(gSwitches[switchLoop]->mLongName, inArgv[loop]))
{
match = __LINE__;
}
else if(0 == strcmp(gSwitches[switchLoop]->mShortName, inArgv[loop]))
{
match = __LINE__;
}
if(match)
{
if(gSwitches[switchLoop]->mHasValue)
{
/*
** Attempt to absorb next option to fullfill value.
*/
if(loop + 1 < inArgc)
{
loop++;
current = gSwitches[switchLoop];
current->mValue = inArgv[loop];
}
}
else
{
current = gSwitches[switchLoop];
}
break;
}
}
if(0 == match)
{
outOptions->mHelp = __LINE__;
retval = __LINE__;
ERROR_REPORT(retval, inArgv[loop], "Unknown command line switch.");
}
else if(NULL == current)
{
outOptions->mHelp = __LINE__;
retval = __LINE__;
ERROR_REPORT(retval, inArgv[loop], "Command line switch requires a value.");
}
else
{
/*
** Do something based on address/swtich.
*/
if(current == &gInputSwitch)
{
CLEANUP(outOptions->mInputName);
outOptions->mInputName = strdup(current->mValue);
if(NULL == outOptions->mInputName)
{
retval = __LINE__;
ERROR_REPORT(retval, current->mValue, "Unable to strdup.");
}
}
else if(current == &gOutputSwitch)
{
CLEANUP(outOptions->mOutputName);
if(NULL != outOptions->mOutput && stdout != outOptions->mOutput)
{
fclose(outOptions->mOutput);
outOptions->mOutput = NULL;
}
outOptions->mOutput = fopen(current->mValue, "a");
if(NULL == outOptions->mOutput)
{
retval = __LINE__;
ERROR_REPORT(retval, current->mValue, "Unable to open output file.");
}
else
{
outOptions->mOutputName = strdup(current->mValue);
if(NULL == outOptions->mOutputName)
{
retval = __LINE__;
ERROR_REPORT(retval, current->mValue, "Unable to strdup.");
}
}
}
else if(current == &gHelpSwitch)
{
outOptions->mHelp = __LINE__;
}
else if(current == &gAlignmentSwitch)
{
unsigned arg = 0;
char* endScan = NULL;
errno = 0;
arg = strtoul(current->mValue, &endScan, 0);
if(0 == errno && endScan != current->mValue)
{
outOptions->mAlignment = arg;
}
else
{
retval = __LINE__;
ERROR_REPORT(retval, current->mValue, "Unable to convert to a number.");
}
}
else if(current == &gOverheadSwitch)
{
unsigned arg = 0;
char* endScan = NULL;
errno = 0;
arg = strtoul(current->mValue, &endScan, 0);
if(0 == errno && endScan != current->mValue)
{
outOptions->mOverhead = arg;
}
else
{
retval = __LINE__;
ERROR_REPORT(retval, current->mValue, "Unable to convert to a number.");
}
}
else if(current == &gAveragesSwitch)
{
outOptions->mAverages = __LINE__;
}
else if(current == &gDeviationsSwitch)
{
outOptions->mAverages = __LINE__;
outOptions->mDeviances = __LINE__;
}
else if(current == &gRunLengthSwitch)
{
outOptions->mRunLength = __LINE__;
}
else
{
retval = __LINE__;
ERROR_REPORT(retval, current->mLongName, "No handler for command line switch.");
}
}
}
return retval;
}
void cleanOptions(Options* inOptions)
/*
** Clean up any open handles.
*/
{
unsigned loop = 0;
CLEANUP(inOptions->mInputName);
CLEANUP(inOptions->mOutputName);
if(NULL != inOptions->mOutput && stdout != inOptions->mOutput)
{
fclose(inOptions->mOutput);
}
memset(inOptions, 0, sizeof(Options));
}
void showHelp(Options* inOptions)
/*
** Show some simple help text on usage.
*/
{
int loop = 0;
const int switchCount = sizeof(gSwitches) / sizeof(gSwitches[0]);
const char* valueText = NULL;
printf("usage:\t%s [arguments]\n", inOptions->mProgramName);
printf("\n");
printf("arguments:\n");
for(loop = 0; loop < switchCount; loop++)
{
if(gSwitches[loop]->mHasValue)
{
valueText = " <value>";
}
else
{
valueText = "";
}
printf("\t%s%s\n", gSwitches[loop]->mLongName, valueText);
printf("\t %s%s", gSwitches[loop]->mShortName, valueText);
printf(DESC_NEWLINE "%s\n\n", gSwitches[loop]->mDescription);
}
printf("This tool reports simple heap usage and allocation call counts.\n");
printf("Useful for eyeballing trace-malloc numbers quickly.\n");
}
void addVariance(VarianceState* inVariance, unsigned inValue)
/*
** Add a value to a variance state.
*/
{
uint64_t squared;
uint64_t bigValue;
bigValue = inValue;
inVariance->mSum += bigValue;
squared = bigValue * bigValue;
inVariance->mSquaredSum += squared;
inVariance->mCount++;
}
double getAverage(VarianceState* inVariance)
/*
** Determine the mean/average based on the given state.
*/
{
double retval = 0.0;
if(NULL != inVariance && 0 < inVariance->mCount)
{
double count;
int64_t isum;
/*
** Avoids a compiler error (not impl) under MSVC.
*/
isum = inVariance->mSum;
count = (double)inVariance->mCount;
retval = (double)isum / count;
}
return retval;
}
double getVariance(VarianceState* inVariance)
/*
** Determine the variance based on the given state.
*/
{
double retval = 0.0;
if(NULL != inVariance && 1 < inVariance->mCount)
{
double count;
double avg;
double squaredAvg;
int64_t isquaredSum;
/*
** Avoids a compiler error (not impl) under MSVC.
*/
isquaredSum = inVariance->mSquaredSum;
count = (double)inVariance->mCount;
avg = getAverage(inVariance);
squaredAvg = avg * avg;
retval = ((double)isquaredSum - (count * squaredAvg)) / (count - 1.0);
}
return retval;
}
double getStdDev(VarianceState* inVariance)
/*
** Determine the standard deviation based on the given state.
*/
{
double retval = 0.0;
double variance;
variance = getVariance(inVariance);
retval = sqrt(variance);
return retval;
}
unsigned actualByteSize(Options* inOptions, unsigned retval)
/*
** Apply alignment and overhead to size to figure out actual byte size.
** This by default mimics spacetrace with default options (msvc crt heap).
*/
{
if(0 != retval)
{
unsigned eval = 0;
unsigned over = 0;
eval = retval - 1;
if(0 != inOptions->mAlignment)
{
over = eval % inOptions->mAlignment;
}
retval = eval + inOptions->mOverhead + inOptions->mAlignment - over;
}
return retval;
}
uint32_t ticks2xsec(tmreader* aReader, uint32_t aTicks, uint32_t aResolution)
/*
** Convert platform specific ticks to second units
** Returns 0 on success.
*/
{
return (uint32_t)((aResolution * aTicks) / aReader->ticksPerSec);
}
#define ticks2msec(reader, ticks) ticks2xsec((reader), (ticks), 1000)
void tmEventHandler(tmreader* inReader, tmevent* inEvent)
/*
** Callback from the tmreader_eventloop.
** Keep it simple in here, this is where we'll spend the most time.
** The goal is to be fast.
*/
{
TMStats* stats = (TMStats*)inReader->data;
Options* options = (Options*)stats->mOptions;
char type = inEvent->type;
unsigned size = inEvent->u.alloc.size;
unsigned actualSize = 0;
unsigned actualOldSize = 0;
uint32_t interval = 0;
/*
** To match spacetrace stats, reallocs of size zero are frees.
** Adjust the size to match what free expects.
*/
if(TM_EVENT_REALLOC == type && 0 == size)
{
type = TM_EVENT_FREE;
if(0 != inEvent->u.alloc.oldserial)
{
size = inEvent->u.alloc.oldsize;
}
}
/*
** Adjust the size due to the options.
*/
actualSize = actualByteSize(options, size);
if(TM_EVENT_REALLOC == type && 0 != inEvent->u.alloc.oldserial)
{
actualOldSize = actualByteSize(options, inEvent->u.alloc.oldsize);
}
/*
** Modify event specific data.
*/
switch(type)
{
case TM_EVENT_MALLOC:
stats->uMallocs++;
stats->uMallocSize += actualSize;
stats->uMallocCost += ticks2msec(inReader, inEvent->u.alloc.cost);
stats->uMemoryInUse += actualSize;
stats->uObjectsInUse++;
addVariance(&stats->mMallocSizeVar, actualSize);
addVariance(&stats->mMallocCostVar, inEvent->u.alloc.cost);
break;
case TM_EVENT_CALLOC:
stats->uCallocs++;
stats->uCallocSize += actualSize;
stats->uCallocCost += ticks2msec(inReader, inEvent->u.alloc.cost);
stats->uMemoryInUse += actualSize;
stats->uObjectsInUse++;
addVariance(&stats->mCallocSizeVar, actualSize);
addVariance(&stats->mCallocCostVar, inEvent->u.alloc.cost);
break;
case TM_EVENT_REALLOC:
stats->uReallocs++;
stats->uReallocSize -= actualOldSize;
stats->uReallocSize += actualSize;
stats->uReallocCost += ticks2msec(inReader, inEvent->u.alloc.cost);
stats->uMemoryInUse -= actualOldSize;
stats->uMemoryInUse += actualSize;
if(0 == inEvent->u.alloc.oldserial)
{
stats->uObjectsInUse++;
}
if(actualSize > actualOldSize)
{
addVariance(&stats->mReallocSizeVar, actualSize - actualOldSize);
}
else
{
addVariance(&stats->mReallocSizeVar, actualOldSize - actualSize);
}
addVariance(&stats->mReallocCostVar, inEvent->u.alloc.cost);
break;
case TM_EVENT_FREE:
stats->uFrees++;
stats->uFreeSize += actualSize;
stats->uFreeCost += ticks2msec(inReader, inEvent->u.alloc.cost);
stats->uMemoryInUse -= actualSize;
stats->uObjectsInUse--;
addVariance(&stats->mFreeSizeVar, actualSize);
addVariance(&stats->mFreeCostVar, inEvent->u.alloc.cost);
break;
default:
/*
** Don't care.
*/
break;
}
switch(type)
{
case TM_EVENT_MALLOC:
case TM_EVENT_CALLOC:
case TM_EVENT_REALLOC:
/*
** Check the peaks.
*/
if(stats->uMemoryInUse > stats->uPeakMemory)
{
stats->uPeakMemory = stats->uMemoryInUse;
}
if(stats->uObjectsInUse > stats->uPeakObjects)
{
stats->uPeakObjects = stats->uObjectsInUse;
}
/*
** Falling through.
*/
case TM_EVENT_FREE:
/*
** Check the overall time.
*/
interval = ticks2msec(inReader, inEvent->u.alloc.interval);
if(stats->uMinTicks > interval)
{
stats->uMinTicks = interval;
}
if(stats->uMaxTicks < interval)
{
stats->uMaxTicks = interval;
}
break;
default:
/*
** Don't care.
*/
break;
}
}
int report_stats(Options* inOptions, TMStats* inStats)
{
int retval = 0;
fprintf(inOptions->mOutput, "Peak Memory Usage: %11d\n", inStats->uPeakMemory);
fprintf(inOptions->mOutput, "Memory Leaked: %11d\n", inStats->uMemoryInUse);
fprintf(inOptions->mOutput, "\n");
fprintf(inOptions->mOutput, "Peak Object Count: %11d\n", inStats->uPeakObjects);
fprintf(inOptions->mOutput, "Objects Leaked: %11d\n", inStats->uObjectsInUse);
if(0 != inOptions->mAverages && 0 != inStats->uObjectsInUse)
{
fprintf(inOptions->mOutput, "Average Leaked Object Size: %11.4f\n", (double)inStats->uMemoryInUse / (double)inStats->uObjectsInUse);
}
fprintf(inOptions->mOutput, "\n");
fprintf(inOptions->mOutput, "Call Total: %11d\n", inStats->uMallocs + inStats->uCallocs + inStats->uReallocs + inStats->uFrees);
fprintf(inOptions->mOutput, " malloc: %11d\n", inStats->uMallocs);
fprintf(inOptions->mOutput, " calloc: %11d\n", inStats->uCallocs);
fprintf(inOptions->mOutput, " realloc: %11d\n", inStats->uReallocs);
fprintf(inOptions->mOutput, " free: %11d\n", inStats->uFrees);
fprintf(inOptions->mOutput, "\n");
fprintf(inOptions->mOutput, "Byte Total (sans free): %11d\n", inStats->uMallocSize + inStats->uCallocSize + inStats->uReallocSize);
fprintf(inOptions->mOutput, " malloc: %11d\n", inStats->uMallocSize);
fprintf(inOptions->mOutput, " calloc: %11d\n", inStats->uCallocSize);
fprintf(inOptions->mOutput, " realloc: %11d\n", inStats->uReallocSize);
fprintf(inOptions->mOutput, " free: %11d\n", inStats->uFreeSize);
if(0 != inOptions->mAverages)
{
fprintf(inOptions->mOutput, "Byte Averages:\n");
fprintf(inOptions->mOutput, " malloc: %11.4f\n", getAverage(&inStats->mMallocSizeVar));
fprintf(inOptions->mOutput, " calloc: %11.4f\n", getAverage(&inStats->mCallocSizeVar));
fprintf(inOptions->mOutput, " realloc: %11.4f\n", getAverage(&inStats->mReallocSizeVar));
fprintf(inOptions->mOutput, " free: %11.4f\n", getAverage(&inStats->mFreeSizeVar));
}
if(0 != inOptions->mDeviances)
{
fprintf(inOptions->mOutput, "Byte Standard Deviations:\n");
fprintf(inOptions->mOutput, " malloc: %11.4f\n", getStdDev(&inStats->mMallocSizeVar));
fprintf(inOptions->mOutput, " calloc: %11.4f\n", getStdDev(&inStats->mCallocSizeVar));
fprintf(inOptions->mOutput, " realloc: %11.4f\n", getStdDev(&inStats->mReallocSizeVar));
fprintf(inOptions->mOutput, " free: %11.4f\n", getStdDev(&inStats->mFreeSizeVar));
}
fprintf(inOptions->mOutput, "\n");
fprintf(inOptions->mOutput, "Overhead Total: %11.4f\n", COST_PRINTABLE(inStats->uMallocCost) + COST_PRINTABLE(inStats->uCallocCost) + COST_PRINTABLE(inStats->uReallocCost) + COST_PRINTABLE(inStats->uFreeCost));
fprintf(inOptions->mOutput, " malloc: %11.4f\n", COST_PRINTABLE(inStats->uMallocCost));
fprintf(inOptions->mOutput, " calloc: %11.4f\n", COST_PRINTABLE(inStats->uCallocCost));
fprintf(inOptions->mOutput, " realloc: %11.4f\n", COST_PRINTABLE(inStats->uReallocCost));
fprintf(inOptions->mOutput, " free: %11.4f\n", COST_PRINTABLE(inStats->uFreeCost));
if(0 != inOptions->mAverages)
{
fprintf(inOptions->mOutput, "Overhead Averages:\n");
fprintf(inOptions->mOutput, " malloc: %11.4f\n", COST_PRINTABLE(getAverage(&inStats->mMallocCostVar)));
fprintf(inOptions->mOutput, " calloc: %11.4f\n", COST_PRINTABLE(getAverage(&inStats->mCallocCostVar)));
fprintf(inOptions->mOutput, " realloc: %11.4f\n", COST_PRINTABLE(getAverage(&inStats->mReallocCostVar)));
fprintf(inOptions->mOutput, " free: %11.4f\n", COST_PRINTABLE(getAverage(&inStats->mFreeCostVar)));
}
if(0 != inOptions->mDeviances)
{
fprintf(inOptions->mOutput, "Overhead Standard Deviations:\n");
fprintf(inOptions->mOutput, " malloc: %11.4f\n", COST_PRINTABLE(getStdDev(&inStats->mMallocCostVar)));
fprintf(inOptions->mOutput, " calloc: %11.4f\n", COST_PRINTABLE(getStdDev(&inStats->mCallocCostVar)));
fprintf(inOptions->mOutput, " realloc: %11.4f\n", COST_PRINTABLE(getStdDev(&inStats->mReallocCostVar)));
fprintf(inOptions->mOutput, " free: %11.4f\n", COST_PRINTABLE(getStdDev(&inStats->mFreeCostVar)));
}
fprintf(inOptions->mOutput, "\n");
if(0 != inOptions->mRunLength)
{
unsigned length = inStats->uMaxTicks - inStats->uMinTicks;
fprintf(inOptions->mOutput, "Run Length: %11.4f\n", COST_PRINTABLE(length));
fprintf(inOptions->mOutput, "\n");
}
return retval;
}
int tmstats(Options* inOptions)
/*
** As quick as possible, load the input file and report stats.
*/
{
int retval = 0;
tmreader* tmr = NULL;
TMStats stats;
memset(&stats, 0, sizeof(stats));
stats.mOptions = inOptions;
stats.uMinTicks = 0xFFFFFFFFU;
/*
** Need a tmreader.
*/
tmr = tmreader_new(inOptions->mProgramName, &stats);
if(NULL != tmr)
{
int tmResult = 0;
tmResult = tmreader_eventloop(tmr, inOptions->mInputName, tmEventHandler);
if(0 == tmResult)
{
retval = __LINE__;
ERROR_REPORT(retval, inOptions->mInputName, "Problem reading trace-malloc data.");
}
tmreader_destroy(tmr);
tmr = NULL;
if(0 == retval)
{
retval = report_stats(inOptions, &stats);
}
}
else
{
retval = __LINE__;
ERROR_REPORT(retval, inOptions->mProgramName, "Unable to obtain tmreader.");
}
return retval;
}
int main(int inArgc, char** inArgv)
{
int retval = 0;
Options options;
retval = initOptions(&options, inArgc, inArgv);
if(options.mHelp)
{
showHelp(&options);
}
else if(0 == retval)
{
retval = tmstats(&options);
}
cleanOptions(&options);
return retval;
}

File diff suppressed because it is too large Load Diff

View File

@ -1,117 +0,0 @@
#!/usr/bin/perl -w
#
# 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 tool is used to construct the ``type inference'' file. It
# prints the total number of bytes that are attributed to a type that
# cannot be inferred, grouped by stack trace; e.g.,
#
# (100) PR_Malloc
# (50) foo
# (50) foo2::foo2
# (25) bar
# (25) baz
# (50) __builtin_new
# (50) foo2::foo2
#
#
# Which indicates that 100 bytes were allocated by uninferrable
# classes via PR_Malloc(). Of that 100 bytes, 50 were allocated from
# calls by foo(), 25 from calls by bar(), and 25 from calls by baz().
# 50 bytes were allocated by __builtin_new from foo2's ctor.
#
#
# From this, we might be able to infer the type of the object that was
# created by examining the PR_Malloc() usage in foo() and the
# ::operator new() usage in foo2(), and could add new type inference
# rules; e.g.,
#
# <unclassified-string>
# foo
# foo2
#
# # Attribute ::operator new() usage in foo2's ctor to foo2
# <foo2>
# __builtin_new
# foo2::foo2
use 5.004;
use strict;
use Getopt::Long;
# So we can find TraceMalloc.pm
use FindBin;
use lib "$FindBin::Bin";
use TraceMalloc;
# Collect program options
$::opt_help = 0;
$::opt_depth = 10;
$::opt_types = "${FindBin::Bin}/types.dat";
GetOptions("help", "depth=n", "types=s");
if ($::opt_help) {
die "usage: uncategorized.pl [options] <dumpfile>
--help Display this message
--depth=<n> Display at most <n> stack frames
--types=<file> Read type heuristics from <file>";
}
# Initialize type inference juju from the type file specified by
# ``--types''.
TraceMalloc::init_type_inference($::opt_types);
# Read the objects from the dump file. For each object, remember up to
# ``--depth'' stack frames (from the top). Thread together common
# stack prefixes, accumulating the number of bytes attributed to the
# prefix.
# This'll hold the inverted stacks
$::Stacks = { '#bytes#' => 0 };
sub collect_stacks($) {
my ($object) = @_;
my $stack = $object->{'stack'};
return unless ($object->{'type'} eq 'void*') && (TraceMalloc::infer_type($stack) eq 'void*');
my $count = 0;
my $link = \%::Stacks;
FRAME: foreach my $frame (@$stack) {
last FRAME unless $count++ < $::opt_depth;
$link->{'#bytes#'} += $object->{'size'};
$link->{$frame} = { '#bytes#' => 0 } unless $link->{$frame};
$link = $link->{$frame};
}
}
TraceMalloc::read(\&collect_stacks);
# Do a depth-first walk of the inverted stack tree.
sub walk($$) {
my ($links, $indent) = @_;
my @keys;
KEY: foreach my $key (keys %$links) {
next KEY if $key eq '#bytes#';
$keys[$#keys + 1] = $key;
}
foreach my $key (sort { $links->{$b}->{'#bytes#'} <=> $links->{$a}->{'#bytes#'} } @keys) {
for (my $i = 0; $i < $indent; ++$i) {
print " ";
}
print "($links->{$key}->{'#bytes#'}) $key\n";
walk($links->{$key}, $indent + 1);
}
}
walk(\%::Stacks, 0);

View File

@ -32,10 +32,6 @@
#include <unistd.h>
#endif
#ifdef NS_TRACE_MALLOC
#include "nsTraceMalloc.h"
#endif
#include "mozilla/BlockingResourceBase.h"
#include "mozilla/PoisonIOInterposer.h"
@ -1024,14 +1020,6 @@ NS_LogInit()
nsTraceRefcnt::SetActivityIsLegal(true);
}
#endif
#ifdef NS_TRACE_MALLOC
// XXX we don't have to worry about shutting down trace-malloc; it
// handles this itself, through an atexit() callback.
if (!NS_TraceMallocHasStarted()) {
NS_TraceMallocStartup(-1); // -1 == no logging
}
#endif
}
EXPORT_XPCOM_API(void)

View File

@ -242,7 +242,7 @@
* sense to touch memory pages and free that memory at shutdown,
* unless we are running leak stats.
*/
#if defined(NS_TRACE_MALLOC) || defined(NS_BUILD_REFCNT_LOGGING) || defined(MOZ_VALGRIND)
#if defined(NS_BUILD_REFCNT_LOGGING) || defined(MOZ_VALGRIND)
#define NS_FREE_PERMANENT_DATA
#endif

View File

@ -155,70 +155,6 @@ NS_HIDDEN __typeof(dlclose) __wrap_dlclose;
#define dlclose __wrap_dlclose
#endif
#ifdef NS_TRACE_MALLOC
extern "C" {
NS_EXPORT_(__ptr_t) __libc_malloc(size_t);
NS_EXPORT_(__ptr_t) __libc_calloc(size_t, size_t);
NS_EXPORT_(__ptr_t) __libc_realloc(__ptr_t, size_t);
NS_EXPORT_(void) __libc_free(__ptr_t);
NS_EXPORT_(__ptr_t) __libc_memalign(size_t, size_t);
NS_EXPORT_(__ptr_t) __libc_valloc(size_t);
}
static __ptr_t (*_malloc)(size_t) = __libc_malloc;
static __ptr_t (*_calloc)(size_t, size_t) = __libc_calloc;
static __ptr_t (*_realloc)(__ptr_t, size_t) = __libc_realloc;
static void (*_free)(__ptr_t) = __libc_free;
static __ptr_t (*_memalign)(size_t, size_t) = __libc_memalign;
static __ptr_t (*_valloc)(size_t) = __libc_valloc;
NS_EXPORT_(__ptr_t) malloc(size_t size)
{
return _malloc(size);
}
NS_EXPORT_(__ptr_t) calloc(size_t nmemb, size_t size)
{
return _calloc(nmemb, size);
}
NS_EXPORT_(__ptr_t) realloc(__ptr_t ptr, size_t size)
{
return _realloc(ptr, size);
}
NS_EXPORT_(void) free(__ptr_t ptr)
{
_free(ptr);
}
NS_EXPORT_(void) cfree(__ptr_t ptr)
{
_free(ptr);
}
NS_EXPORT_(__ptr_t) memalign(size_t boundary, size_t size)
{
return _memalign(boundary, size);
}
NS_EXPORT_(int)
posix_memalign(void** memptr, size_t alignment, size_t size)
{
__ptr_t ptr = _memalign(alignment, size);
if (!ptr) {
return ENOMEM;
}
*memptr = ptr;
return 0;
}
NS_EXPORT_(__ptr_t) valloc(size_t size)
{
return _valloc(size);
}
#endif /* NS_TRACE_MALLOC */
typedef void* LibHandleType;
static LibHandleType
@ -322,17 +258,6 @@ typedef Scoped<ScopedCloseFileTraits> ScopedCloseFile;
static void
XPCOMGlueUnload()
{
#if !defined(XP_WIN) && !defined(XP_MACOSX) && defined(NS_TRACE_MALLOC)
if (sTop) {
_malloc = __libc_malloc;
_calloc = __libc_calloc;
_realloc = __libc_realloc;
_free = __libc_free;
_memalign = __libc_memalign;
_valloc = __libc_valloc;
}
#endif
while (sTop) {
CloseLibHandle(sTop->libHandle);
@ -468,15 +393,6 @@ XPCOMGlueLoad(const char* aXPCOMFile)
return nullptr;
}
#if !defined(XP_WIN) && !defined(XP_MACOSX) && defined(NS_TRACE_MALLOC)
_malloc = (__ptr_t(*)(size_t)) GetSymbol(sTop->libHandle, "malloc");
_calloc = (__ptr_t(*)(size_t, size_t)) GetSymbol(sTop->libHandle, "calloc");
_realloc = (__ptr_t(*)(__ptr_t, size_t)) GetSymbol(sTop->libHandle, "realloc");
_free = (void(*)(__ptr_t)) GetSymbol(sTop->libHandle, "free");
_memalign = (__ptr_t(*)(size_t, size_t)) GetSymbol(sTop->libHandle, "memalign");
_valloc = (__ptr_t(*)(size_t)) GetSymbol(sTop->libHandle, "valloc");
#endif
return sym;
}