зеркало из https://github.com/mozilla/gecko-dev.git
Move trace-malloc readers from mozilla/xpcom/base/ to mozilla/tools/trace-malloc/. Add a --shutdown-leaks option to nsTraceMalloc to dump, to the file given as an argument to the option, information about allocations still live at shutdown. Add a new trace-malloc reader (leakstats.c) to print leak statistics. b=84831 r=jag sr=brendan
This commit is contained in:
Родитель
7404594d2f
Коммит
ce012c5510
|
@ -154,6 +154,10 @@ ifdef ENABLE_TESTS
|
||||||
DIRS += xpcom/tests
|
DIRS += xpcom/tests
|
||||||
endif
|
endif
|
||||||
|
|
||||||
|
ifdef NS_TRACE_MALLOC
|
||||||
|
DIRS += tools/trace-malloc
|
||||||
|
endif
|
||||||
|
|
||||||
DIRS += l10n
|
DIRS += l10n
|
||||||
|
|
||||||
ifneq (,$(MOZ_STATIC_COMPONENTS)$(MOZ_META_COMPONENTS))
|
ifneq (,$(MOZ_STATIC_COMPONENTS)$(MOZ_META_COMPONENTS))
|
||||||
|
|
|
@ -913,6 +913,11 @@ if [ "$MOZ_LEAKY" ]; then
|
||||||
MAKEFILES_leaky="tools/leaky/Makefile"
|
MAKEFILES_leaky="tools/leaky/Makefile"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# tools/trace-malloc
|
||||||
|
if [ "$NS_TRACE_MALLOC" ]; then
|
||||||
|
MAKEFILES_tracemalloc="tools/trace-malloc/Makefile"
|
||||||
|
fi
|
||||||
|
|
||||||
# layout/mathml
|
# layout/mathml
|
||||||
if [ "$MOZ_MATHML" ]; then
|
if [ "$MOZ_MATHML" ]; then
|
||||||
MAKEFILES_layout="$MAKEFILES_layout
|
MAKEFILES_layout="$MAKEFILES_layout
|
||||||
|
@ -1123,6 +1128,7 @@ $MAKEFILES_rdf
|
||||||
$MAKEFILES_static_components
|
$MAKEFILES_static_components
|
||||||
$MAKEFILES_sun_java
|
$MAKEFILES_sun_java
|
||||||
$MAKEFILES_themes
|
$MAKEFILES_themes
|
||||||
|
$MAKEFILES_tracemalloc
|
||||||
$MAKEFILES_uriloader
|
$MAKEFILES_uriloader
|
||||||
$MAKEFILES_view
|
$MAKEFILES_view
|
||||||
$MAKEFILES_webshell
|
$MAKEFILES_webshell
|
||||||
|
|
|
@ -0,0 +1,47 @@
|
||||||
|
#
|
||||||
|
# The contents of this file are subject to the Netscape Public
|
||||||
|
# License Version 1.1 (the "License"); you may not use this file
|
||||||
|
# except in compliance with the License. You may obtain a copy of
|
||||||
|
# the License at http://www.mozilla.org/NPL/
|
||||||
|
#
|
||||||
|
# Software distributed under the License is distributed on an "AS
|
||||||
|
# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
|
||||||
|
# implied. See the License for the specific language governing
|
||||||
|
# rights and limitations under the License.
|
||||||
|
#
|
||||||
|
# The Original Code is mozilla.org code.
|
||||||
|
#
|
||||||
|
# The Initial Developer of the Original Code is Netscape
|
||||||
|
# Communications Corporation. Portions created by Netscape are
|
||||||
|
# Copyright (C) 1998 Netscape Communications Corporation. All
|
||||||
|
# Rights Reserved.
|
||||||
|
#
|
||||||
|
# Contributor(s):
|
||||||
|
#
|
||||||
|
|
||||||
|
DEPTH = ../..
|
||||||
|
topsrcdir = @top_srcdir@
|
||||||
|
srcdir = @srcdir@
|
||||||
|
VPATH = @srcdir@
|
||||||
|
|
||||||
|
include $(DEPTH)/config/autoconf.mk
|
||||||
|
|
||||||
|
REQUIRES = string xpcom
|
||||||
|
|
||||||
|
CSRCS += \
|
||||||
|
bloatblame.c \
|
||||||
|
leakstats.c \
|
||||||
|
$(NULL)
|
||||||
|
|
||||||
|
SIMPLE_PROGRAMS = $(CSRCS:.c=$(BIN_SUFFIX))
|
||||||
|
|
||||||
|
include $(topsrcdir)/config/config.mk
|
||||||
|
|
||||||
|
LIBS += \
|
||||||
|
$(NSPR_LIBS) \
|
||||||
|
tmreader.o \
|
||||||
|
$(NULL)
|
||||||
|
|
||||||
|
EXTRA_DEPS = tmreader.o
|
||||||
|
|
||||||
|
include $(topsrcdir)/config/rules.mk
|
|
@ -0,0 +1,167 @@
|
||||||
|
/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
|
||||||
|
*
|
||||||
|
* The contents of this file are subject to the Mozilla Public
|
||||||
|
* License Version 1.1 (the "License"); you may not use this file
|
||||||
|
* except in compliance with the License. You may obtain a copy of
|
||||||
|
* the License at http://www.mozilla.org/MPL/
|
||||||
|
*
|
||||||
|
* Software distributed under the License is distributed on an "AS
|
||||||
|
* IS" basis, WITHOUT WARRANTY OF ANY KIND, either express oqr
|
||||||
|
* implied. See the License for the specific language governing
|
||||||
|
* rights and limitations under the License.
|
||||||
|
*
|
||||||
|
* The Original Code is nsTraceMalloc.c/bloatblame.c code, released
|
||||||
|
* April 19, 2000.
|
||||||
|
*
|
||||||
|
* The Initial Developer of the Original Code is Netscape
|
||||||
|
* Communications Corporation. Portions created by Netscape are
|
||||||
|
* Copyright (C) 2000 Netscape Communications Corporation. All
|
||||||
|
* Rights Reserved.
|
||||||
|
*
|
||||||
|
* Contributor(s):
|
||||||
|
* Brendan Eich, 14-April-2000
|
||||||
|
* L. David Baron, 2001-06-07, created leakstats.c based on bloatblame.c
|
||||||
|
*
|
||||||
|
* Alternatively, the contents of this file may be used under the
|
||||||
|
* terms of the GNU Public License (the "GPL"), in which case the
|
||||||
|
* provisions of the GPL are applicable instead of those above.
|
||||||
|
* If you wish to allow use of your version of this file only
|
||||||
|
* under the terms of the GPL and not to allow others to use your
|
||||||
|
* version of this file under the MPL, indicate your decision by
|
||||||
|
* deleting the provisions above and replace them with the notice
|
||||||
|
* and other provisions required by the GPL. If you do not delete
|
||||||
|
* the provisions above, a recipient may use your version of this
|
||||||
|
* file under either the MPL or the GPL.
|
||||||
|
*/
|
||||||
|
#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;
|
||||||
|
#endif
|
||||||
|
#include <time.h>
|
||||||
|
#include "nsTraceMalloc.h"
|
||||||
|
#include "tmreader.h"
|
||||||
|
|
||||||
|
static char *program;
|
||||||
|
|
||||||
|
typedef struct handler_data {
|
||||||
|
uint32 current_heapsize;
|
||||||
|
uint32 max_heapsize;
|
||||||
|
uint32 bytes_allocated;
|
||||||
|
uint32 current_allocations;
|
||||||
|
uint32 total_allocations;
|
||||||
|
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->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:
|
||||||
|
data->current_heapsize -= event->u.alloc.oldsize;
|
||||||
|
--data->current_allocations;
|
||||||
|
/* 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:
|
||||||
|
--data->current_allocations;
|
||||||
|
data->current_heapsize -= event->u.alloc.size;
|
||||||
|
break;
|
||||||
|
case TM_EVENT_STATS:
|
||||||
|
data->finished = 1;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
int main(int argc, char **argv)
|
||||||
|
{
|
||||||
|
int c, 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, "%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);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!data.finished) {
|
||||||
|
fprintf(stderr, "%s: log file incomplete\n", program);
|
||||||
|
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);
|
||||||
|
|
||||||
|
handler_data_finish(&data);
|
||||||
|
tmreader_destroy(tmr);
|
||||||
|
|
||||||
|
exit(0);
|
||||||
|
}
|
|
@ -56,6 +56,7 @@
|
||||||
#include "prlog.h"
|
#include "prlog.h"
|
||||||
#include "prmon.h"
|
#include "prmon.h"
|
||||||
#include "prprf.h"
|
#include "prprf.h"
|
||||||
|
#include "prenv.h"
|
||||||
#include "nsTraceMalloc.h"
|
#include "nsTraceMalloc.h"
|
||||||
|
|
||||||
#ifdef XP_WIN32
|
#ifdef XP_WIN32
|
||||||
|
@ -68,7 +69,7 @@
|
||||||
|
|
||||||
#define WRITE_FLAGS "w"
|
#define WRITE_FLAGS "w"
|
||||||
|
|
||||||
#endif //WIN32
|
#endif /* WIN32 */
|
||||||
|
|
||||||
|
|
||||||
#ifdef XP_UNIX
|
#ifdef XP_UNIX
|
||||||
|
@ -268,6 +269,7 @@ static logfile *logfile_list = NULL;
|
||||||
static logfile **logfile_tail = &logfile_list;
|
static logfile **logfile_tail = &logfile_list;
|
||||||
static logfile *logfp = &default_logfile;
|
static logfile *logfp = &default_logfile;
|
||||||
static PRMonitor *tmmon = NULL;
|
static PRMonitor *tmmon = NULL;
|
||||||
|
static char *sdlogname = NULL; /* filename for shutdown leak log */
|
||||||
|
|
||||||
/* We don't want more than 32 logfiles open at once, ok? */
|
/* We don't want more than 32 logfiles open at once, ok? */
|
||||||
typedef uint32 lfd_set;
|
typedef uint32 lfd_set;
|
||||||
|
@ -318,7 +320,7 @@ retry:
|
||||||
static void flush_logfile(logfile *fp)
|
static void flush_logfile(logfile *fp)
|
||||||
{
|
{
|
||||||
int len, cnt;
|
int len, cnt;
|
||||||
int fd;
|
int fd;
|
||||||
char *bp;
|
char *bp;
|
||||||
|
|
||||||
len = fp->pos;
|
len = fp->pos;
|
||||||
|
@ -570,15 +572,17 @@ static callsite *calltree(int skip)
|
||||||
if (! ok)
|
if (! ok)
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
// Get the context information for this thread. That way we will
|
/*
|
||||||
// know where our sp, fp, pc, etc. are and can fill in the
|
* Get the context information for this thread. That way we will
|
||||||
// STACKFRAME with the initial values.
|
* know where our sp, fp, pc, etc. are and can fill in the
|
||||||
|
* STACKFRAME with the initial values.
|
||||||
|
*/
|
||||||
context.ContextFlags = CONTEXT_FULL;
|
context.ContextFlags = CONTEXT_FULL;
|
||||||
ok = GetThreadContext(myThread, &context);
|
ok = GetThreadContext(myThread, &context);
|
||||||
if (! ok)
|
if (! ok)
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
// Setup initial stack frame to walk from
|
/* Setup initial stack frame to walk from */
|
||||||
memset(&(frame[0]), 0, sizeof(frame[0]));
|
memset(&(frame[0]), 0, sizeof(frame[0]));
|
||||||
frame[0].AddrPC.Offset = context.Eip;
|
frame[0].AddrPC.Offset = context.Eip;
|
||||||
frame[0].AddrPC.Mode = AddrModeFlat;
|
frame[0].AddrPC.Mode = AddrModeFlat;
|
||||||
|
@ -598,10 +602,11 @@ static callsite *calltree(int skip)
|
||||||
myThread,
|
myThread,
|
||||||
&(frame[framenum]),
|
&(frame[framenum]),
|
||||||
&context,
|
&context,
|
||||||
0, // read process memory routine
|
0, /* read process memory routine */
|
||||||
_SymFunctionTableAccess, // function table access routine
|
_SymFunctionTableAccess, /* function table access
|
||||||
_SymGetModuleBase, // module base routine
|
routine */
|
||||||
0); // translate address routine
|
_SymGetModuleBase, /* module base routine */
|
||||||
|
0); /* translate address routine */
|
||||||
|
|
||||||
if (!ok) {
|
if (!ok) {
|
||||||
break;
|
break;
|
||||||
|
@ -676,7 +681,7 @@ static callsite *calltree(int skip)
|
||||||
{
|
{
|
||||||
DWORD error = GetLastError();
|
DWORD error = GetLastError();
|
||||||
PR_ASSERT(error);
|
PR_ASSERT(error);
|
||||||
library = "unknown";//ew
|
library = "unknown";/* ew */
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
@ -707,7 +712,7 @@ static callsite *calltree(int skip)
|
||||||
hash = PL_HashString(library);
|
hash = PL_HashString(library);
|
||||||
hep = PL_HashTableRawLookup(libraries, hash, library);
|
hep = PL_HashTableRawLookup(libraries, hash, library);
|
||||||
he = *hep;
|
he = *hep;
|
||||||
library = strdup(library); //strdup it always?
|
library = strdup(library); /* strdup it always? */
|
||||||
if (he) {
|
if (he) {
|
||||||
library_serial = (uint32) he->value;
|
library_serial = (uint32) he->value;
|
||||||
le = (lfdset_entry *) he;
|
le = (lfdset_entry *) he;
|
||||||
|
@ -716,7 +721,7 @@ static callsite *calltree(int skip)
|
||||||
le = NULL;
|
le = NULL;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// library = strdup(library);
|
/* library = strdup(library); */
|
||||||
if (library) {
|
if (library) {
|
||||||
library_serial = ++library_serial_generator;
|
library_serial = ++library_serial_generator;
|
||||||
he = PL_HashTableRawAdd(libraries, hep, hash, library,
|
he = PL_HashTableRawAdd(libraries, hep, hash, library,
|
||||||
|
@ -1430,97 +1435,131 @@ PR_IMPLEMENT(void) NS_TraceMallocStartup(int logfd)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Options for log files, with the log file name either as the next option
|
||||||
|
* or separated by '=' (e.g. "./mozilla --trace-malloc * malloc.log" or
|
||||||
|
* "./mozilla --trace-malloc=malloc.log").
|
||||||
|
*/
|
||||||
|
static const char TMLOG_OPTION[] = "--trace-malloc";
|
||||||
|
static const char SDLOG_OPTION[] = "--shutdown-leaks";
|
||||||
|
|
||||||
|
#define SHOULD_PARSE_ARG(name_, log_, arg_) \
|
||||||
|
(0 == strncmp(arg_, name_, sizeof(name_) - 1))
|
||||||
|
|
||||||
|
#define PARSE_ARG(name_, log_, argv_, i_, consumed_) \
|
||||||
|
PR_BEGIN_MACRO \
|
||||||
|
char _nextchar = argv_[i_][sizeof(name_) - 1]; \
|
||||||
|
if (_nextchar == '=') { \
|
||||||
|
log_ = argv_[i_] + sizeof(name_); \
|
||||||
|
consumed_ = 1; \
|
||||||
|
} else if (_nextchar == '\0') { \
|
||||||
|
log_ = argv_[i_+1]; \
|
||||||
|
consumed_ = 2; \
|
||||||
|
} \
|
||||||
|
PR_END_MACRO
|
||||||
|
|
||||||
PR_IMPLEMENT(int) NS_TraceMallocStartupArgs(int argc, char* argv[])
|
PR_IMPLEMENT(int) NS_TraceMallocStartupArgs(int argc, char* argv[])
|
||||||
{
|
{
|
||||||
int i, logfd = -1;
|
int i, logfd = -1, consumed;
|
||||||
|
char *tmlogname = NULL; /* note global |sdlogname| */
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Look for the --trace-malloc <logfile> option early, to avoid missing
|
* Look for the --trace-malloc <logfile> option early, to avoid missing
|
||||||
* early mallocs (we miss static constructors whose output overflows the
|
* early mallocs (we miss static constructors whose output overflows the
|
||||||
* log file's static 16K output buffer).
|
* log file's static 16K output buffer).
|
||||||
*/
|
*/
|
||||||
for (i = 1; i < argc; i++) {
|
for (i = 1; i < argc; i += consumed) {
|
||||||
if (strcmp(argv[i], "--trace-malloc") == 0 && i < argc-1) {
|
consumed = 0;
|
||||||
char *logfilename;
|
if (SHOULD_PARSE_ARG(TMLOG_OPTION, tmlogname, argv[i]))
|
||||||
int pipefds[2];
|
PARSE_ARG(TMLOG_OPTION, tmlogname, argv, i, consumed);
|
||||||
|
else if (SHOULD_PARSE_ARG(SDLOG_OPTION, sdlogname, argv[i]))
|
||||||
|
PARSE_ARG(SDLOG_OPTION, sdlogname, argv, i, consumed);
|
||||||
|
|
||||||
logfilename = argv[i+1];
|
if (consumed) {
|
||||||
switch (*logfilename) {
|
#ifndef XP_WIN32 /* If we don't comment this out, it will crash Windows. */
|
||||||
#if XP_UNIX
|
int j;
|
||||||
case '|':
|
|
||||||
if (pipe(pipefds) == 0) {
|
|
||||||
pid_t pid = fork();
|
|
||||||
if (pid == 0) {
|
|
||||||
/* In child: set up stdin, parse args, and exec. */
|
|
||||||
int maxargc, nargc;
|
|
||||||
char **nargv, *token;
|
|
||||||
|
|
||||||
if (pipefds[0] != 0) {
|
|
||||||
dup2(pipefds[0], 0);
|
|
||||||
close(pipefds[0]);
|
|
||||||
}
|
|
||||||
close(pipefds[1]);
|
|
||||||
|
|
||||||
logfilename = strtok(logfilename + 1, " \t");
|
|
||||||
maxargc = 3;
|
|
||||||
nargv = (char **) malloc((maxargc+1) * sizeof(char *));
|
|
||||||
if (!nargv) exit(1);
|
|
||||||
nargc = 0;
|
|
||||||
nargv[nargc++] = logfilename;
|
|
||||||
while ((token = strtok(NULL, " \t")) != NULL) {
|
|
||||||
if (nargc == maxargc) {
|
|
||||||
maxargc *= 2;
|
|
||||||
nargv = (char**)
|
|
||||||
realloc(nargv, (maxargc+1) * sizeof(char*));
|
|
||||||
if (!nargv) exit(1);
|
|
||||||
}
|
|
||||||
nargv[nargc++] = token;
|
|
||||||
}
|
|
||||||
nargv[nargc] = NULL;
|
|
||||||
|
|
||||||
(void) setsid();
|
|
||||||
execvp(logfilename, nargv);
|
|
||||||
exit(127);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (pid > 0) {
|
|
||||||
/* In parent: set logfd to the pipe's write side. */
|
|
||||||
close(pipefds[0]);
|
|
||||||
logfd = pipefds[1];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (logfd < 0) {
|
|
||||||
fprintf(stderr,
|
|
||||||
"%s: can't pipe to trace-malloc child process %s: %s\n",
|
|
||||||
argv[0], logfilename, strerror(errno));
|
|
||||||
exit(1);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
#endif /*XP_UNIX*/
|
|
||||||
case '-':
|
|
||||||
/* Don't log from startup, but do prepare to log later. */
|
|
||||||
/* XXX traditional meaning of '-' as option argument is "stdin" or "stdout" */
|
|
||||||
if (logfilename[1] == '\0')
|
|
||||||
break;
|
|
||||||
/* FALL THROUGH */
|
|
||||||
|
|
||||||
default:
|
|
||||||
logfd = open(logfilename, O_CREAT | O_WRONLY | O_TRUNC, 0644);
|
|
||||||
if (logfd < 0) {
|
|
||||||
fprintf(stderr,
|
|
||||||
"%s: can't create trace-malloc logfilename %s: %s\n",
|
|
||||||
argv[0], logfilename, strerror(errno));
|
|
||||||
exit(1);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
#ifndef XP_WIN32
|
|
||||||
/* Now remove --trace-malloc and its argument from argv. */
|
/* Now remove --trace-malloc and its argument from argv. */
|
||||||
for (argc -= 2; i < argc; i++)
|
argc -= consumed;
|
||||||
argv[i] = argv[i+2];
|
for (j = i; j < argc; ++j)
|
||||||
|
argv[j] = argv[j+consumed];
|
||||||
argv[argc] = NULL;
|
argv[argc] = NULL;
|
||||||
#endif//if you dont comment this out it will crash windows
|
consumed = 0; /* don't advance next iteration */
|
||||||
|
#endif
|
||||||
|
} else {
|
||||||
|
consumed = 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (tmlogname) {
|
||||||
|
int pipefds[2];
|
||||||
|
|
||||||
|
switch (*tmlogname) {
|
||||||
|
#if XP_UNIX
|
||||||
|
case '|':
|
||||||
|
if (pipe(pipefds) == 0) {
|
||||||
|
pid_t pid = fork();
|
||||||
|
if (pid == 0) {
|
||||||
|
/* In child: set up stdin, parse args, and exec. */
|
||||||
|
int maxargc, nargc;
|
||||||
|
char **nargv, *token;
|
||||||
|
|
||||||
|
if (pipefds[0] != 0) {
|
||||||
|
dup2(pipefds[0], 0);
|
||||||
|
close(pipefds[0]);
|
||||||
|
}
|
||||||
|
close(pipefds[1]);
|
||||||
|
|
||||||
|
tmlogname = strtok(tmlogname + 1, " \t");
|
||||||
|
maxargc = 3;
|
||||||
|
nargv = (char **) malloc((maxargc+1) * sizeof(char *));
|
||||||
|
if (!nargv) exit(1);
|
||||||
|
nargc = 0;
|
||||||
|
nargv[nargc++] = tmlogname;
|
||||||
|
while ((token = strtok(NULL, " \t")) != NULL) {
|
||||||
|
if (nargc == maxargc) {
|
||||||
|
maxargc *= 2;
|
||||||
|
nargv = (char**)
|
||||||
|
realloc(nargv, (maxargc+1) * sizeof(char*));
|
||||||
|
if (!nargv) exit(1);
|
||||||
|
}
|
||||||
|
nargv[nargc++] = token;
|
||||||
|
}
|
||||||
|
nargv[nargc] = NULL;
|
||||||
|
|
||||||
|
(void) setsid();
|
||||||
|
execvp(tmlogname, nargv);
|
||||||
|
exit(127);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pid > 0) {
|
||||||
|
/* In parent: set logfd to the pipe's write side. */
|
||||||
|
close(pipefds[0]);
|
||||||
|
logfd = pipefds[1];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (logfd < 0) {
|
||||||
|
fprintf(stderr,
|
||||||
|
"%s: can't pipe to trace-malloc child process %s: %s\n",
|
||||||
|
argv[0], tmlogname, strerror(errno));
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
#endif /*XP_UNIX*/
|
||||||
|
case '-':
|
||||||
|
/* Don't log from startup, but do prepare to log later. */
|
||||||
|
/* XXX traditional meaning of '-' as option argument is "stdin" or "stdout" */
|
||||||
|
if (tmlogname[1] == '\0')
|
||||||
|
break;
|
||||||
|
/* FALL THROUGH */
|
||||||
|
|
||||||
|
default:
|
||||||
|
logfd = open(tmlogname, O_CREAT | O_WRONLY | O_TRUNC, 0644);
|
||||||
|
if (logfd < 0) {
|
||||||
|
fprintf(stderr,
|
||||||
|
"%s: can't create trace-malloc log named %s: %s\n",
|
||||||
|
argv[0], tmlogname, strerror(errno));
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1533,6 +1572,9 @@ PR_IMPLEMENT(void) NS_TraceMallocShutdown()
|
||||||
{
|
{
|
||||||
logfile *fp;
|
logfile *fp;
|
||||||
|
|
||||||
|
if (sdlogname)
|
||||||
|
NS_TraceMallocDumpAllocations(sdlogname);
|
||||||
|
|
||||||
if (tmstats.backtrace_failures) {
|
if (tmstats.backtrace_failures) {
|
||||||
fprintf(stderr,
|
fprintf(stderr,
|
||||||
"TraceMalloc backtrace failures: %lu (malloc %lu dladdr %lu)\n",
|
"TraceMalloc backtrace failures: %lu (malloc %lu dladdr %lu)\n",
|
||||||
|
|
|
@ -81,10 +81,6 @@ CSRCS += nsTraceMalloc.c
|
||||||
CPPSRCS += nsTypeInfo.cpp
|
CPPSRCS += nsTypeInfo.cpp
|
||||||
EXPORTS += nsTraceMalloc.h
|
EXPORTS += nsTraceMalloc.h
|
||||||
DEFINES += -DNS_TRACE_MALLOC
|
DEFINES += -DNS_TRACE_MALLOC
|
||||||
SIMPLE_PROGRAMS = bloatblame
|
|
||||||
|
|
||||||
LIBS += tmreader.o $(NSPR_LIBS)
|
|
||||||
EXTRA_DEPS = tmreader.o
|
|
||||||
endif
|
endif
|
||||||
|
|
||||||
XPIDLSRCS = \
|
XPIDLSRCS = \
|
||||||
|
|
|
@ -1,746 +0,0 @@
|
||||||
/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
|
|
||||||
*
|
|
||||||
* The contents of this file are subject to the Mozilla Public
|
|
||||||
* License Version 1.1 (the "License"); you may not use this file
|
|
||||||
* except in compliance with the License. You may obtain a copy of
|
|
||||||
* the License at http://www.mozilla.org/MPL/
|
|
||||||
*
|
|
||||||
* Software distributed under the License is distributed on an "AS
|
|
||||||
* IS" basis, WITHOUT WARRANTY OF ANY KIND, either express oqr
|
|
||||||
* implied. See the License for the specific language governing
|
|
||||||
* rights and limitations under the License.
|
|
||||||
*
|
|
||||||
* The Original Code is nsTraceMalloc.c/bloatblame.c code, released
|
|
||||||
* April 19, 2000.
|
|
||||||
*
|
|
||||||
* The Initial Developer of the Original Code is Netscape
|
|
||||||
* Communications Corporation. Portions created by Netscape are
|
|
||||||
* Copyright (C) 2000 Netscape Communications Corporation. All
|
|
||||||
* Rights Reserved.
|
|
||||||
*
|
|
||||||
* Contributor(s):
|
|
||||||
* Brendan Eich, 14-April-2000
|
|
||||||
*
|
|
||||||
* Alternatively, the contents of this file may be used under the
|
|
||||||
* terms of the GNU Public License (the "GPL"), in which case the
|
|
||||||
* provisions of the GPL are applicable instead of those above.
|
|
||||||
* If you wish to allow use of your version of this file only
|
|
||||||
* under the terms of the GPL and not to allow others to use your
|
|
||||||
* version of this file under the MPL, indicate your decision by
|
|
||||||
* deleting the provisions above and replace them with the notice
|
|
||||||
* and other provisions required by the GPL. If you do not delete
|
|
||||||
* the provisions above, a recipient may use your version of this
|
|
||||||
* file under either the MPL or the GPL.
|
|
||||||
*/
|
|
||||||
#include <stdio.h>
|
|
||||||
#include <stdlib.h>
|
|
||||||
#include <string.h>
|
|
||||||
#include <ctype.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;
|
|
||||||
#endif
|
|
||||||
#include <math.h>
|
|
||||||
#include <time.h>
|
|
||||||
#include <sys/stat.h>
|
|
||||||
#include "prtypes.h"
|
|
||||||
#include "prlog.h"
|
|
||||||
#include "prprf.h"
|
|
||||||
#include "plhash.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 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 *meth, *pmeth, *comp, *pcomp, *lib, *plib;
|
|
||||||
int old_meth_low, old_comp_low, old_lib_low, nkids;
|
|
||||||
tmcallsite *kid;
|
|
||||||
|
|
||||||
parent = site->parent;
|
|
||||||
meth = comp = lib = NULL;
|
|
||||||
if (parent) {
|
|
||||||
meth = site->method;
|
|
||||||
if (meth) {
|
|
||||||
pmeth = parent->method;
|
|
||||||
if (pmeth && pmeth != meth) {
|
|
||||||
if (!meth->low) {
|
|
||||||
meth->allocs.bytes.total += site->allocs.bytes.total;
|
|
||||||
meth->allocs.calls.total += site->allocs.calls.total;
|
|
||||||
}
|
|
||||||
if (!tmgraphnode_connect(pmeth, meth, site))
|
|
||||||
goto bad;
|
|
||||||
|
|
||||||
comp = meth->up;
|
|
||||||
if (comp) {
|
|
||||||
pcomp = pmeth->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->low;
|
|
||||||
if (!old_meth_low)
|
|
||||||
meth->low = level;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (do_tree_dump) {
|
|
||||||
fprintf(fp, "%c%*s%3d %3d %s %lu %ld\n",
|
|
||||||
site->kids ? '+' : '-', level, "", level, kidnum,
|
|
||||||
meth ? tmgraphnode_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->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 PRIntn tabulate_node(PLHashEntry *he, PRIntn 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 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 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 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 num, uint32 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 i, count;
|
|
||||||
tmgraphnode **table, *node;
|
|
||||||
char *name;
|
|
||||||
size_t namelen;
|
|
||||||
char buf1[16], buf2[16], buf3[16], buf4[16];
|
|
||||||
static char NA[] = "N/A";
|
|
||||||
|
|
||||||
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)mean, buf1, sizeof buf1),
|
|
||||||
prettybig((uint32)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>",
|
|
||||||
tmgraphnode_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 ? tmgraphnode_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);
|
|
||||||
}
|
|
|
@ -56,6 +56,7 @@
|
||||||
#include "prlog.h"
|
#include "prlog.h"
|
||||||
#include "prmon.h"
|
#include "prmon.h"
|
||||||
#include "prprf.h"
|
#include "prprf.h"
|
||||||
|
#include "prenv.h"
|
||||||
#include "nsTraceMalloc.h"
|
#include "nsTraceMalloc.h"
|
||||||
|
|
||||||
#ifdef XP_WIN32
|
#ifdef XP_WIN32
|
||||||
|
@ -68,7 +69,7 @@
|
||||||
|
|
||||||
#define WRITE_FLAGS "w"
|
#define WRITE_FLAGS "w"
|
||||||
|
|
||||||
#endif //WIN32
|
#endif /* WIN32 */
|
||||||
|
|
||||||
|
|
||||||
#ifdef XP_UNIX
|
#ifdef XP_UNIX
|
||||||
|
@ -268,6 +269,7 @@ static logfile *logfile_list = NULL;
|
||||||
static logfile **logfile_tail = &logfile_list;
|
static logfile **logfile_tail = &logfile_list;
|
||||||
static logfile *logfp = &default_logfile;
|
static logfile *logfp = &default_logfile;
|
||||||
static PRMonitor *tmmon = NULL;
|
static PRMonitor *tmmon = NULL;
|
||||||
|
static char *sdlogname = NULL; /* filename for shutdown leak log */
|
||||||
|
|
||||||
/* We don't want more than 32 logfiles open at once, ok? */
|
/* We don't want more than 32 logfiles open at once, ok? */
|
||||||
typedef uint32 lfd_set;
|
typedef uint32 lfd_set;
|
||||||
|
@ -318,7 +320,7 @@ retry:
|
||||||
static void flush_logfile(logfile *fp)
|
static void flush_logfile(logfile *fp)
|
||||||
{
|
{
|
||||||
int len, cnt;
|
int len, cnt;
|
||||||
int fd;
|
int fd;
|
||||||
char *bp;
|
char *bp;
|
||||||
|
|
||||||
len = fp->pos;
|
len = fp->pos;
|
||||||
|
@ -570,15 +572,17 @@ static callsite *calltree(int skip)
|
||||||
if (! ok)
|
if (! ok)
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
// Get the context information for this thread. That way we will
|
/*
|
||||||
// know where our sp, fp, pc, etc. are and can fill in the
|
* Get the context information for this thread. That way we will
|
||||||
// STACKFRAME with the initial values.
|
* know where our sp, fp, pc, etc. are and can fill in the
|
||||||
|
* STACKFRAME with the initial values.
|
||||||
|
*/
|
||||||
context.ContextFlags = CONTEXT_FULL;
|
context.ContextFlags = CONTEXT_FULL;
|
||||||
ok = GetThreadContext(myThread, &context);
|
ok = GetThreadContext(myThread, &context);
|
||||||
if (! ok)
|
if (! ok)
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
// Setup initial stack frame to walk from
|
/* Setup initial stack frame to walk from */
|
||||||
memset(&(frame[0]), 0, sizeof(frame[0]));
|
memset(&(frame[0]), 0, sizeof(frame[0]));
|
||||||
frame[0].AddrPC.Offset = context.Eip;
|
frame[0].AddrPC.Offset = context.Eip;
|
||||||
frame[0].AddrPC.Mode = AddrModeFlat;
|
frame[0].AddrPC.Mode = AddrModeFlat;
|
||||||
|
@ -598,10 +602,11 @@ static callsite *calltree(int skip)
|
||||||
myThread,
|
myThread,
|
||||||
&(frame[framenum]),
|
&(frame[framenum]),
|
||||||
&context,
|
&context,
|
||||||
0, // read process memory routine
|
0, /* read process memory routine */
|
||||||
_SymFunctionTableAccess, // function table access routine
|
_SymFunctionTableAccess, /* function table access
|
||||||
_SymGetModuleBase, // module base routine
|
routine */
|
||||||
0); // translate address routine
|
_SymGetModuleBase, /* module base routine */
|
||||||
|
0); /* translate address routine */
|
||||||
|
|
||||||
if (!ok) {
|
if (!ok) {
|
||||||
break;
|
break;
|
||||||
|
@ -676,7 +681,7 @@ static callsite *calltree(int skip)
|
||||||
{
|
{
|
||||||
DWORD error = GetLastError();
|
DWORD error = GetLastError();
|
||||||
PR_ASSERT(error);
|
PR_ASSERT(error);
|
||||||
library = "unknown";//ew
|
library = "unknown";/* ew */
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
@ -707,7 +712,7 @@ static callsite *calltree(int skip)
|
||||||
hash = PL_HashString(library);
|
hash = PL_HashString(library);
|
||||||
hep = PL_HashTableRawLookup(libraries, hash, library);
|
hep = PL_HashTableRawLookup(libraries, hash, library);
|
||||||
he = *hep;
|
he = *hep;
|
||||||
library = strdup(library); //strdup it always?
|
library = strdup(library); /* strdup it always? */
|
||||||
if (he) {
|
if (he) {
|
||||||
library_serial = (uint32) he->value;
|
library_serial = (uint32) he->value;
|
||||||
le = (lfdset_entry *) he;
|
le = (lfdset_entry *) he;
|
||||||
|
@ -716,7 +721,7 @@ static callsite *calltree(int skip)
|
||||||
le = NULL;
|
le = NULL;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// library = strdup(library);
|
/* library = strdup(library); */
|
||||||
if (library) {
|
if (library) {
|
||||||
library_serial = ++library_serial_generator;
|
library_serial = ++library_serial_generator;
|
||||||
he = PL_HashTableRawAdd(libraries, hep, hash, library,
|
he = PL_HashTableRawAdd(libraries, hep, hash, library,
|
||||||
|
@ -1430,97 +1435,131 @@ PR_IMPLEMENT(void) NS_TraceMallocStartup(int logfd)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Options for log files, with the log file name either as the next option
|
||||||
|
* or separated by '=' (e.g. "./mozilla --trace-malloc * malloc.log" or
|
||||||
|
* "./mozilla --trace-malloc=malloc.log").
|
||||||
|
*/
|
||||||
|
static const char TMLOG_OPTION[] = "--trace-malloc";
|
||||||
|
static const char SDLOG_OPTION[] = "--shutdown-leaks";
|
||||||
|
|
||||||
|
#define SHOULD_PARSE_ARG(name_, log_, arg_) \
|
||||||
|
(0 == strncmp(arg_, name_, sizeof(name_) - 1))
|
||||||
|
|
||||||
|
#define PARSE_ARG(name_, log_, argv_, i_, consumed_) \
|
||||||
|
PR_BEGIN_MACRO \
|
||||||
|
char _nextchar = argv_[i_][sizeof(name_) - 1]; \
|
||||||
|
if (_nextchar == '=') { \
|
||||||
|
log_ = argv_[i_] + sizeof(name_); \
|
||||||
|
consumed_ = 1; \
|
||||||
|
} else if (_nextchar == '\0') { \
|
||||||
|
log_ = argv_[i_+1]; \
|
||||||
|
consumed_ = 2; \
|
||||||
|
} \
|
||||||
|
PR_END_MACRO
|
||||||
|
|
||||||
PR_IMPLEMENT(int) NS_TraceMallocStartupArgs(int argc, char* argv[])
|
PR_IMPLEMENT(int) NS_TraceMallocStartupArgs(int argc, char* argv[])
|
||||||
{
|
{
|
||||||
int i, logfd = -1;
|
int i, logfd = -1, consumed;
|
||||||
|
char *tmlogname = NULL; /* note global |sdlogname| */
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Look for the --trace-malloc <logfile> option early, to avoid missing
|
* Look for the --trace-malloc <logfile> option early, to avoid missing
|
||||||
* early mallocs (we miss static constructors whose output overflows the
|
* early mallocs (we miss static constructors whose output overflows the
|
||||||
* log file's static 16K output buffer).
|
* log file's static 16K output buffer).
|
||||||
*/
|
*/
|
||||||
for (i = 1; i < argc; i++) {
|
for (i = 1; i < argc; i += consumed) {
|
||||||
if (strcmp(argv[i], "--trace-malloc") == 0 && i < argc-1) {
|
consumed = 0;
|
||||||
char *logfilename;
|
if (SHOULD_PARSE_ARG(TMLOG_OPTION, tmlogname, argv[i]))
|
||||||
int pipefds[2];
|
PARSE_ARG(TMLOG_OPTION, tmlogname, argv, i, consumed);
|
||||||
|
else if (SHOULD_PARSE_ARG(SDLOG_OPTION, sdlogname, argv[i]))
|
||||||
|
PARSE_ARG(SDLOG_OPTION, sdlogname, argv, i, consumed);
|
||||||
|
|
||||||
logfilename = argv[i+1];
|
if (consumed) {
|
||||||
switch (*logfilename) {
|
#ifndef XP_WIN32 /* If we don't comment this out, it will crash Windows. */
|
||||||
#if XP_UNIX
|
int j;
|
||||||
case '|':
|
|
||||||
if (pipe(pipefds) == 0) {
|
|
||||||
pid_t pid = fork();
|
|
||||||
if (pid == 0) {
|
|
||||||
/* In child: set up stdin, parse args, and exec. */
|
|
||||||
int maxargc, nargc;
|
|
||||||
char **nargv, *token;
|
|
||||||
|
|
||||||
if (pipefds[0] != 0) {
|
|
||||||
dup2(pipefds[0], 0);
|
|
||||||
close(pipefds[0]);
|
|
||||||
}
|
|
||||||
close(pipefds[1]);
|
|
||||||
|
|
||||||
logfilename = strtok(logfilename + 1, " \t");
|
|
||||||
maxargc = 3;
|
|
||||||
nargv = (char **) malloc((maxargc+1) * sizeof(char *));
|
|
||||||
if (!nargv) exit(1);
|
|
||||||
nargc = 0;
|
|
||||||
nargv[nargc++] = logfilename;
|
|
||||||
while ((token = strtok(NULL, " \t")) != NULL) {
|
|
||||||
if (nargc == maxargc) {
|
|
||||||
maxargc *= 2;
|
|
||||||
nargv = (char**)
|
|
||||||
realloc(nargv, (maxargc+1) * sizeof(char*));
|
|
||||||
if (!nargv) exit(1);
|
|
||||||
}
|
|
||||||
nargv[nargc++] = token;
|
|
||||||
}
|
|
||||||
nargv[nargc] = NULL;
|
|
||||||
|
|
||||||
(void) setsid();
|
|
||||||
execvp(logfilename, nargv);
|
|
||||||
exit(127);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (pid > 0) {
|
|
||||||
/* In parent: set logfd to the pipe's write side. */
|
|
||||||
close(pipefds[0]);
|
|
||||||
logfd = pipefds[1];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (logfd < 0) {
|
|
||||||
fprintf(stderr,
|
|
||||||
"%s: can't pipe to trace-malloc child process %s: %s\n",
|
|
||||||
argv[0], logfilename, strerror(errno));
|
|
||||||
exit(1);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
#endif /*XP_UNIX*/
|
|
||||||
case '-':
|
|
||||||
/* Don't log from startup, but do prepare to log later. */
|
|
||||||
/* XXX traditional meaning of '-' as option argument is "stdin" or "stdout" */
|
|
||||||
if (logfilename[1] == '\0')
|
|
||||||
break;
|
|
||||||
/* FALL THROUGH */
|
|
||||||
|
|
||||||
default:
|
|
||||||
logfd = open(logfilename, O_CREAT | O_WRONLY | O_TRUNC, 0644);
|
|
||||||
if (logfd < 0) {
|
|
||||||
fprintf(stderr,
|
|
||||||
"%s: can't create trace-malloc logfilename %s: %s\n",
|
|
||||||
argv[0], logfilename, strerror(errno));
|
|
||||||
exit(1);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
#ifndef XP_WIN32
|
|
||||||
/* Now remove --trace-malloc and its argument from argv. */
|
/* Now remove --trace-malloc and its argument from argv. */
|
||||||
for (argc -= 2; i < argc; i++)
|
argc -= consumed;
|
||||||
argv[i] = argv[i+2];
|
for (j = i; j < argc; ++j)
|
||||||
|
argv[j] = argv[j+consumed];
|
||||||
argv[argc] = NULL;
|
argv[argc] = NULL;
|
||||||
#endif//if you dont comment this out it will crash windows
|
consumed = 0; /* don't advance next iteration */
|
||||||
|
#endif
|
||||||
|
} else {
|
||||||
|
consumed = 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (tmlogname) {
|
||||||
|
int pipefds[2];
|
||||||
|
|
||||||
|
switch (*tmlogname) {
|
||||||
|
#if XP_UNIX
|
||||||
|
case '|':
|
||||||
|
if (pipe(pipefds) == 0) {
|
||||||
|
pid_t pid = fork();
|
||||||
|
if (pid == 0) {
|
||||||
|
/* In child: set up stdin, parse args, and exec. */
|
||||||
|
int maxargc, nargc;
|
||||||
|
char **nargv, *token;
|
||||||
|
|
||||||
|
if (pipefds[0] != 0) {
|
||||||
|
dup2(pipefds[0], 0);
|
||||||
|
close(pipefds[0]);
|
||||||
|
}
|
||||||
|
close(pipefds[1]);
|
||||||
|
|
||||||
|
tmlogname = strtok(tmlogname + 1, " \t");
|
||||||
|
maxargc = 3;
|
||||||
|
nargv = (char **) malloc((maxargc+1) * sizeof(char *));
|
||||||
|
if (!nargv) exit(1);
|
||||||
|
nargc = 0;
|
||||||
|
nargv[nargc++] = tmlogname;
|
||||||
|
while ((token = strtok(NULL, " \t")) != NULL) {
|
||||||
|
if (nargc == maxargc) {
|
||||||
|
maxargc *= 2;
|
||||||
|
nargv = (char**)
|
||||||
|
realloc(nargv, (maxargc+1) * sizeof(char*));
|
||||||
|
if (!nargv) exit(1);
|
||||||
|
}
|
||||||
|
nargv[nargc++] = token;
|
||||||
|
}
|
||||||
|
nargv[nargc] = NULL;
|
||||||
|
|
||||||
|
(void) setsid();
|
||||||
|
execvp(tmlogname, nargv);
|
||||||
|
exit(127);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pid > 0) {
|
||||||
|
/* In parent: set logfd to the pipe's write side. */
|
||||||
|
close(pipefds[0]);
|
||||||
|
logfd = pipefds[1];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (logfd < 0) {
|
||||||
|
fprintf(stderr,
|
||||||
|
"%s: can't pipe to trace-malloc child process %s: %s\n",
|
||||||
|
argv[0], tmlogname, strerror(errno));
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
#endif /*XP_UNIX*/
|
||||||
|
case '-':
|
||||||
|
/* Don't log from startup, but do prepare to log later. */
|
||||||
|
/* XXX traditional meaning of '-' as option argument is "stdin" or "stdout" */
|
||||||
|
if (tmlogname[1] == '\0')
|
||||||
|
break;
|
||||||
|
/* FALL THROUGH */
|
||||||
|
|
||||||
|
default:
|
||||||
|
logfd = open(tmlogname, O_CREAT | O_WRONLY | O_TRUNC, 0644);
|
||||||
|
if (logfd < 0) {
|
||||||
|
fprintf(stderr,
|
||||||
|
"%s: can't create trace-malloc log named %s: %s\n",
|
||||||
|
argv[0], tmlogname, strerror(errno));
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1533,6 +1572,9 @@ PR_IMPLEMENT(void) NS_TraceMallocShutdown()
|
||||||
{
|
{
|
||||||
logfile *fp;
|
logfile *fp;
|
||||||
|
|
||||||
|
if (sdlogname)
|
||||||
|
NS_TraceMallocDumpAllocations(sdlogname);
|
||||||
|
|
||||||
if (tmstats.backtrace_failures) {
|
if (tmstats.backtrace_failures) {
|
||||||
fprintf(stderr,
|
fprintf(stderr,
|
||||||
"TraceMalloc backtrace failures: %lu (malloc %lu dladdr %lu)\n",
|
"TraceMalloc backtrace failures: %lu (malloc %lu dladdr %lu)\n",
|
||||||
|
|
|
@ -1,702 +0,0 @@
|
||||||
/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
|
|
||||||
*
|
|
||||||
* The contents of this file are subject to the Mozilla Public
|
|
||||||
* License Version 1.1 (the "License"); you may not use this file
|
|
||||||
* except in compliance with the License. You may obtain a copy of
|
|
||||||
* the License at http://www.mozilla.org/MPL/
|
|
||||||
*
|
|
||||||
* Software distributed under the License is distributed on an "AS
|
|
||||||
* IS" basis, WITHOUT WARRANTY OF ANY KIND, either express oqr
|
|
||||||
* implied. See the License for the specific language governing
|
|
||||||
* rights and limitations under the License.
|
|
||||||
*
|
|
||||||
* The Original Code is tmreader.h/tmreader.c code, released
|
|
||||||
* July 7, 2000.
|
|
||||||
*
|
|
||||||
* The Initial Developer of the Original Code is Netscape
|
|
||||||
* Communications Corporation. Portions created by Netscape are
|
|
||||||
* Copyright (C) 2000 Netscape Communications Corporation. All
|
|
||||||
* Rights Reserved.
|
|
||||||
*
|
|
||||||
* Contributor(s):
|
|
||||||
* Brendan Eich, 7-July-2000
|
|
||||||
*
|
|
||||||
* Alternatively, the contents of this file may be used under the
|
|
||||||
* terms of the GNU Public License (the "GPL"), in which case the
|
|
||||||
* provisions of the GPL are applicable instead of those above.
|
|
||||||
* If you wish to allow use of your version of this file only
|
|
||||||
* under the terms of the GPL and not to allow others to use your
|
|
||||||
* version of this file under the MPL, indicate your decision by
|
|
||||||
* deleting the provisions above and replace them with the notice
|
|
||||||
* and other provisions required by the GPL. If you do not delete
|
|
||||||
* the provisions above, a recipient may use your version of this
|
|
||||||
* file under either the MPL or the GPL.
|
|
||||||
*/
|
|
||||||
#include <stdio.h>
|
|
||||||
#include <stdlib.h>
|
|
||||||
#include <string.h>
|
|
||||||
#include <errno.h> /* XXX push error reporting out to clients? */
|
|
||||||
#include <unistd.h>
|
|
||||||
#include "prlog.h"
|
|
||||||
#include "plhash.h"
|
|
||||||
#include "nsTraceMalloc.h"
|
|
||||||
#include "tmreader.h"
|
|
||||||
|
|
||||||
static int accum_byte(FILE *fp, uint32 *uip)
|
|
||||||
{
|
|
||||||
int c = getc(fp);
|
|
||||||
if (c == EOF)
|
|
||||||
return 0;
|
|
||||||
*uip = (*uip << 8) | c;
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int get_uint32(FILE *fp, uint32 *uip)
|
|
||||||
{
|
|
||||||
int c;
|
|
||||||
uint32 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) c;
|
|
||||||
}
|
|
||||||
if (!accum_byte(fp, &ui))
|
|
||||||
return 0;
|
|
||||||
} else {
|
|
||||||
ui = (uint32) c;
|
|
||||||
}
|
|
||||||
if (!accum_byte(fp, &ui))
|
|
||||||
return 0;
|
|
||||||
} else {
|
|
||||||
ui = (uint32) c;
|
|
||||||
}
|
|
||||||
if (!accum_byte(fp, &ui))
|
|
||||||
return 0;
|
|
||||||
} else {
|
|
||||||
ui = (uint32) 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;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case TM_EVENT_METHOD:
|
|
||||||
if (!get_uint32(fp, &event->u.method.library))
|
|
||||||
return 0;
|
|
||||||
s = get_string(fp);
|
|
||||||
if (!s)
|
|
||||||
return 0;
|
|
||||||
event->u.method.name = s;
|
|
||||||
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;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case TM_EVENT_MALLOC:
|
|
||||||
case TM_EVENT_CALLOC:
|
|
||||||
case TM_EVENT_FREE:
|
|
||||||
event->u.alloc.oldsize = 0;
|
|
||||||
if (!get_uint32(fp, &event->u.alloc.size))
|
|
||||||
return 0;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case TM_EVENT_REALLOC:
|
|
||||||
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.oldsize))
|
|
||||||
return 0;
|
|
||||||
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;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void *generic_alloctable(void *pool, PRSize size)
|
|
||||||
{
|
|
||||||
return malloc(size);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void generic_freetable(void *pool, void *item)
|
|
||||||
{
|
|
||||||
free(item);
|
|
||||||
}
|
|
||||||
|
|
||||||
static PLHashEntry *callsite_allocentry(void *pool, const void *key)
|
|
||||||
{
|
|
||||||
return malloc(sizeof(tmcallsite));
|
|
||||||
}
|
|
||||||
|
|
||||||
static PLHashEntry *graphnode_allocentry(void *pool, const void *key)
|
|
||||||
{
|
|
||||||
tmgraphnode *node = (tmgraphnode*) malloc(sizeof(tmgraphnode));
|
|
||||||
if (!node)
|
|
||||||
return NULL;
|
|
||||||
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;
|
|
||||||
return &node->entry;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void graphnode_freeentry(void *pool, PLHashEntry *he, PRUintn flag)
|
|
||||||
{
|
|
||||||
/* Always free the value, which points to a strdup'd string. */
|
|
||||||
free(he->value);
|
|
||||||
|
|
||||||
/* Free the whole thing if we're told to. */
|
|
||||||
if (flag == HT_FREE_ENTRY)
|
|
||||||
free((void*) he);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void component_freeentry(void *pool, PLHashEntry *he, PRUintn 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));
|
|
||||||
free((void*) comp);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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 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;
|
|
||||||
|
|
||||||
tmr->libraries = PL_NewHashTable(100, hash_serial, PL_CompareValues,
|
|
||||||
PL_CompareStrings, &graphnode_hashallocops,
|
|
||||||
NULL);
|
|
||||||
tmr->components = PL_NewHashTable(10000, PL_HashString, PL_CompareStrings,
|
|
||||||
PL_CompareValues, &component_hashallocops,
|
|
||||||
NULL);
|
|
||||||
tmr->methods = PL_NewHashTable(10000, hash_serial, PL_CompareValues,
|
|
||||||
PL_CompareStrings, &graphnode_hashallocops,
|
|
||||||
NULL);
|
|
||||||
tmr->callsites = PL_NewHashTable(200000, hash_serial, PL_CompareValues,
|
|
||||||
PL_CompareValues, &callsite_hashallocops,
|
|
||||||
NULL);
|
|
||||||
tmr->calltree_root.entry.value = (void*) strdup("root");
|
|
||||||
|
|
||||||
if (!tmr->libraries || !tmr->components || !tmr->methods ||
|
|
||||||
!tmr->callsites || !tmr->calltree_root.entry.value) {
|
|
||||||
tmreader_destroy(tmr);
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
return tmr;
|
|
||||||
}
|
|
||||||
|
|
||||||
void tmreader_destroy(tmreader *tmr)
|
|
||||||
{
|
|
||||||
if (tmr->libraries)
|
|
||||||
PL_HashTableDestroy(tmr->libraries);
|
|
||||||
if (tmr->components)
|
|
||||||
PL_HashTableDestroy(tmr->components);
|
|
||||||
if (tmr->methods)
|
|
||||||
PL_HashTableDestroy(tmr->methods);
|
|
||||||
if (tmr->callsites)
|
|
||||||
PL_HashTableDestroy(tmr->callsites);
|
|
||||||
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 {
|
|
||||||
fp = fopen(filename, "r");
|
|
||||||
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);
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
while (get_tmevent(fp, &event)) {
|
|
||||||
switch (event.type) {
|
|
||||||
case TM_EVENT_LIBRARY: {
|
|
||||||
const void *key;
|
|
||||||
PLHashNumber hash;
|
|
||||||
PLHashEntry **hep, *he;
|
|
||||||
|
|
||||||
key = (const void*) 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_METHOD: {
|
|
||||||
const void *key;
|
|
||||||
PLHashNumber hash;
|
|
||||||
PLHashEntry **hep, *he;
|
|
||||||
char *name, *head, *mark, save;
|
|
||||||
tmgraphnode *meth, *comp, *lib;
|
|
||||||
|
|
||||||
key = (const void*) 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 = (tmgraphnode*) he;
|
|
||||||
|
|
||||||
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*) 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->up = comp;
|
|
||||||
meth->next = comp->down;
|
|
||||||
comp->down = meth;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
case TM_EVENT_CALLSITE: {
|
|
||||||
const void *key, *mkey;
|
|
||||||
PLHashNumber hash, mhash;
|
|
||||||
PLHashEntry **hep, *he;
|
|
||||||
tmcallsite *site, *parent;
|
|
||||||
tmgraphnode *meth;
|
|
||||||
|
|
||||||
key = (const void*) event.serial;
|
|
||||||
hash = hash_serial(key);
|
|
||||||
hep = PL_HashTableRawLookup(tmr->callsites, hash, key);
|
|
||||||
he = *hep;
|
|
||||||
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*) event.u.site.method;
|
|
||||||
mhash = hash_serial(mkey);
|
|
||||||
meth = (tmgraphnode*)
|
|
||||||
*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 size, oldsize;
|
|
||||||
double delta, sqdelta, sqszdelta;
|
|
||||||
tmgraphnode *meth, *comp, *lib;
|
|
||||||
|
|
||||||
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 += delta;
|
|
||||||
if (event.type != TM_EVENT_REALLOC)
|
|
||||||
site->allocs.calls.direct++;
|
|
||||||
meth = site->method;
|
|
||||||
if (meth) {
|
|
||||||
meth->allocs.bytes.direct += delta;
|
|
||||||
sqdelta = delta * delta;
|
|
||||||
if (event.type == TM_EVENT_REALLOC) {
|
|
||||||
sqszdelta = ((double)size * size)
|
|
||||||
- ((double)oldsize * oldsize);
|
|
||||||
meth->sqsum += sqszdelta;
|
|
||||||
} else {
|
|
||||||
meth->sqsum += sqdelta;
|
|
||||||
meth->allocs.calls.direct++;
|
|
||||||
}
|
|
||||||
comp = meth->up;
|
|
||||||
if (comp) {
|
|
||||||
comp->allocs.bytes.direct += 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 += 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 size;
|
|
||||||
tmgraphnode *meth, *comp, *lib;
|
|
||||||
|
|
||||||
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->frees.bytes.direct += size;
|
|
||||||
meth->frees.calls.direct++;
|
|
||||||
comp = meth->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 serial)
|
|
||||||
{
|
|
||||||
const void *key;
|
|
||||||
PLHashNumber hash;
|
|
||||||
|
|
||||||
key = (const void*) serial;
|
|
||||||
hash = hash_serial(key);
|
|
||||||
return (tmgraphnode*) *PL_HashTableRawLookup(tmr->libraries, hash, key);
|
|
||||||
}
|
|
||||||
|
|
||||||
tmgraphnode *tmreader_component(tmreader *tmr, const char *name)
|
|
||||||
{
|
|
||||||
PLHashNumber hash;
|
|
||||||
|
|
||||||
hash = PL_HashString(name);
|
|
||||||
return (tmgraphnode*) *PL_HashTableRawLookup(tmr->components, hash, name);
|
|
||||||
}
|
|
||||||
|
|
||||||
tmgraphnode *tmreader_method(tmreader *tmr, uint32 serial)
|
|
||||||
{
|
|
||||||
const void *key;
|
|
||||||
PLHashNumber hash;
|
|
||||||
|
|
||||||
key = (const void*) serial;
|
|
||||||
hash = hash_serial(key);
|
|
||||||
return (tmgraphnode*) *PL_HashTableRawLookup(tmr->methods, hash, key);
|
|
||||||
}
|
|
||||||
|
|
||||||
tmcallsite *tmreader_callsite(tmreader *tmr, uint32 serial)
|
|
||||||
{
|
|
||||||
const void *key;
|
|
||||||
PLHashNumber hash;
|
|
||||||
|
|
||||||
key = (const void*) 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,193 +0,0 @@
|
||||||
/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
|
|
||||||
*
|
|
||||||
* The contents of this file are subject to the Mozilla Public
|
|
||||||
* License Version 1.1 (the "License"); you may not use this file
|
|
||||||
* except in compliance with the License. You may obtain a copy of
|
|
||||||
* the License at http://www.mozilla.org/MPL/
|
|
||||||
*
|
|
||||||
* Software distributed under the License is distributed on an "AS
|
|
||||||
* IS" basis, WITHOUT WARRANTY OF ANY KIND, either express oqr
|
|
||||||
* implied. See the License for the specific language governing
|
|
||||||
* rights and limitations under the License.
|
|
||||||
*
|
|
||||||
* The Original Code is tmreader.h/tmreader.c code, released
|
|
||||||
* July 7, 2000.
|
|
||||||
*
|
|
||||||
* The Initial Developer of the Original Code is Netscape
|
|
||||||
* Communications Corporation. Portions created by Netscape are
|
|
||||||
* Copyright (C) 2000 Netscape Communications Corporation. All
|
|
||||||
* Rights Reserved.
|
|
||||||
*
|
|
||||||
* Contributor(s):
|
|
||||||
* Brendan Eich, 7-July-2000
|
|
||||||
*
|
|
||||||
* Alternatively, the contents of this file may be used under the
|
|
||||||
* terms of the GNU Public License (the "GPL"), in which case the
|
|
||||||
* provisions of the GPL are applicable instead of those above.
|
|
||||||
* If you wish to allow use of your version of this file only
|
|
||||||
* under the terms of the GPL and not to allow others to use your
|
|
||||||
* version of this file under the MPL, indicate your decision by
|
|
||||||
* deleting the provisions above and replace them with the notice
|
|
||||||
* and other provisions required by the GPL. If you do not delete
|
|
||||||
* the provisions above, a recipient may use your version of this
|
|
||||||
* file under either the MPL or the GPL.
|
|
||||||
*/
|
|
||||||
#ifndef tmreader_h___
|
|
||||||
#define tmreader_h___
|
|
||||||
|
|
||||||
#include "prtypes.h"
|
|
||||||
#include "plhash.h"
|
|
||||||
#include "nsTraceMalloc.h"
|
|
||||||
|
|
||||||
PR_BEGIN_EXTERN_C
|
|
||||||
|
|
||||||
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;
|
|
||||||
|
|
||||||
struct tmevent {
|
|
||||||
char type;
|
|
||||||
uint32 serial;
|
|
||||||
union {
|
|
||||||
char *libname;
|
|
||||||
struct {
|
|
||||||
uint32 library;
|
|
||||||
char *name;
|
|
||||||
} method;
|
|
||||||
struct {
|
|
||||||
uint32 parent;
|
|
||||||
uint32 method;
|
|
||||||
uint32 offset;
|
|
||||||
} site;
|
|
||||||
struct {
|
|
||||||
uint32 size;
|
|
||||||
uint32 oldserial;
|
|
||||||
uint32 oldsize;
|
|
||||||
} alloc;
|
|
||||||
struct {
|
|
||||||
nsTMStats tmstats;
|
|
||||||
uint32 calltree_maxkids_parent;
|
|
||||||
uint32 calltree_maxstack_top;
|
|
||||||
} stats;
|
|
||||||
} u;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct tmcounts {
|
|
||||||
uint32 direct; /* things allocated by this node's code */
|
|
||||||
uint32 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 */
|
|
||||||
};
|
|
||||||
|
|
||||||
#define tmgraphnode_name(node) ((char*) (node)->entry.value)
|
|
||||||
|
|
||||||
#define tmlibrary_serial(lib) ((uint32) (lib)->entry.key)
|
|
||||||
#define tmcomponent_name(comp) ((const char*) (comp)->entry.key)
|
|
||||||
|
|
||||||
/* 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 */
|
|
||||||
tmgraphnode *method; /* method node in tmr->methods graph */
|
|
||||||
uint32 offset; /* pc offset from start of method */
|
|
||||||
tmallcounts allocs;
|
|
||||||
tmallcounts frees;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct tmreader {
|
|
||||||
const char *program;
|
|
||||||
void *data;
|
|
||||||
PLHashTable *libraries;
|
|
||||||
PLHashTable *components;
|
|
||||||
PLHashTable *methods;
|
|
||||||
PLHashTable *callsites;
|
|
||||||
tmcallsite calltree_root;
|
|
||||||
};
|
|
||||||
|
|
||||||
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 serial);
|
|
||||||
extern tmgraphnode *tmreader_component(tmreader *tmr, const char *name);
|
|
||||||
extern tmgraphnode *tmreader_method(tmreader *tmr, uint32 serial);
|
|
||||||
extern tmcallsite *tmreader_callsite(tmreader *tmr, uint32 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);
|
|
||||||
|
|
||||||
PR_END_EXTERN_C
|
|
||||||
|
|
||||||
#endif /* tmreader_h___ */
|
|
Загрузка…
Ссылка в новой задаче