mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-10-08 02:14:43 +00:00
Bug 1014341 (part 1) - Remove trace-malloc. r=dbaron,glandium.
--HG-- extra : rebase_source : 771710c5427141d738eef112fab00951eb8e20e3
This commit is contained in:
parent
e9f49ca06a
commit
97b5d348cc
@ -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")
|
||||
|
@ -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')
|
||||
|
@ -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'
|
||||
|
@ -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
|
||||
|
||||
|
35
configure.in
35
configure.in
@ -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
|
||||
|
||||
|
@ -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);
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
||||
|
@ -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.
|
||||
|
@ -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");
|
||||
|
@ -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;
|
||||
|
@ -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']
|
||||
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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
|
@ -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.
|
@ -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
|
@ -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;
|
||||
}
|
@ -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; }
|
||||
};
|
@ -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);
|
||||
}
|
@ -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;
|
||||
}
|
@ -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/&/&/g;
|
||||
s/</</g;
|
||||
s/>/>/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 $memory ($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> $memory ($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> $memory ($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";
|
@ -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 (%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 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);
|
||||
}
|
@ -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();
|
@ -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;
|
||||
}
|
||||
}
|
@ -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__ */
|
@ -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 */
|
||||
}
|
@ -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
|
@ -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
|
@ -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
@ -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 <%s> (%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> <%s>",
|
||||
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;
|
||||
}
|
@ -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);
|
||||
}
|
@ -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
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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
@ -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___ */
|
@ -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) */
|
@ -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
|
@ -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_ */
|
@ -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);
|
||||
}
|
@ -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
|
@ -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>
|
@ -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( / /, $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( / /, $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>";
|
@ -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'
|
||||
]
|
@ -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.
|
@ -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
@ -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;
|
||||
}
|
||||
|
@ -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__ */
|
@ -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
|
@ -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;
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
@ -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___ */
|
@ -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
@ -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);
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user