зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1083686 - Tool to capture and reproduce Firefox's memory allocations. r=njn,r=mshal
--HG-- rename : mozglue/build/Makefile.in => mozglue/build/replace_malloc.mk
This commit is contained in:
Родитель
746f48e4a4
Коммит
e25908f6c5
|
@ -0,0 +1,125 @@
|
|||
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#include <cstdarg>
|
||||
|
||||
#ifdef _WIN32
|
||||
#include <io.h>
|
||||
#else
|
||||
#include <unistd.h>
|
||||
#endif
|
||||
#include <cstring>
|
||||
#include "mozilla/Assertions.h"
|
||||
|
||||
/* Template class allowing a limited number of increments on a value */
|
||||
template <typename T>
|
||||
class CheckedIncrement
|
||||
{
|
||||
public:
|
||||
CheckedIncrement(T aValue, size_t aMaxIncrement)
|
||||
: mValue(aValue), mMaxIncrement(aMaxIncrement)
|
||||
{}
|
||||
|
||||
T operator ++(int)
|
||||
{
|
||||
if (!mMaxIncrement) {
|
||||
MOZ_CRASH("overflow detected");
|
||||
}
|
||||
mMaxIncrement--;
|
||||
return mValue++;
|
||||
}
|
||||
|
||||
T& operator ++()
|
||||
{
|
||||
(*this)++;
|
||||
return mValue;
|
||||
}
|
||||
|
||||
operator T() { return mValue; }
|
||||
|
||||
private:
|
||||
T mValue;
|
||||
size_t mMaxIncrement;
|
||||
};
|
||||
|
||||
void
|
||||
FdPrintf(int aFd, const char* aFormat, ...)
|
||||
{
|
||||
if (aFd == -1) {
|
||||
return;
|
||||
}
|
||||
char buf[256];
|
||||
CheckedIncrement<char*> b(buf, sizeof(buf));
|
||||
CheckedIncrement<const char*> f(aFormat, strlen(aFormat) + 1);
|
||||
va_list ap;
|
||||
va_start(ap, aFormat);
|
||||
while (true) {
|
||||
switch (*f) {
|
||||
case '\0':
|
||||
goto out;
|
||||
|
||||
case '%':
|
||||
switch (*++f) {
|
||||
case 'z': {
|
||||
if (*(++f) == 'u') {
|
||||
size_t i = va_arg(ap, size_t);
|
||||
size_t x = 1;
|
||||
// Compute the number of digits.
|
||||
while (x <= i / 10) {
|
||||
x *= 10;
|
||||
}
|
||||
// Write the digits into the buffer.
|
||||
do {
|
||||
*(b++) = "0123456789"[(i / x) % 10];
|
||||
x /= 10;
|
||||
} while (x > 0);
|
||||
} else {
|
||||
// Write out the format specifier if it's unknown.
|
||||
*(b++) = '%';
|
||||
*(b++) = 'z';
|
||||
*(b++) = *f;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case 'p': {
|
||||
intptr_t ptr = va_arg(ap, intptr_t);
|
||||
*(b++) = '0';
|
||||
*(b++) = 'x';
|
||||
int x = sizeof(intptr_t) * 8;
|
||||
bool wrote_msb = false;
|
||||
do {
|
||||
x -= 4;
|
||||
size_t hex_digit = ptr >> x & 0xf;
|
||||
if (hex_digit || wrote_msb) {
|
||||
*(b++) = "0123456789abcdef"[hex_digit];
|
||||
wrote_msb = true;
|
||||
}
|
||||
} while (x > 0);
|
||||
if (!wrote_msb) {
|
||||
*(b++) = '0';
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
// Write out the format specifier if it's unknown.
|
||||
*(b++) = '%';
|
||||
*(b++) = *f;
|
||||
break;
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
*(b++) = *f;
|
||||
break;
|
||||
}
|
||||
f++;
|
||||
}
|
||||
out:
|
||||
write(aFd, buf, b - buf);
|
||||
va_end(ap);
|
||||
}
|
|
@ -0,0 +1,22 @@
|
|||
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#ifndef __FdPrintf_h__
|
||||
#define __FdPrintf_h__
|
||||
|
||||
/* We can't use libc's (f)printf because it would reenter in replace_malloc,
|
||||
* So use a custom and simplified version.
|
||||
* Only %p and %z are supported.
|
||||
* /!\ This function used a fixed-size internal buffer. The caller is
|
||||
* expected to not use a format string that may overflow.
|
||||
*/
|
||||
extern void FdPrintf(int aFd, const char* aFormat, ...)
|
||||
#ifdef __GNUC__
|
||||
__attribute__((format(printf, 2, 3)))
|
||||
#endif
|
||||
;
|
||||
|
||||
#endif /* __FdPrintf_h__ */
|
|
@ -0,0 +1,201 @@
|
|||
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#include <cstdlib>
|
||||
#include <cstdio>
|
||||
#include <fcntl.h>
|
||||
|
||||
#ifdef _WIN32
|
||||
#include <windows.h>
|
||||
#include <io.h>
|
||||
#include <process.h>
|
||||
#else
|
||||
#include <unistd.h>
|
||||
#include <pthread.h>
|
||||
#endif
|
||||
|
||||
#include "replace_malloc.h"
|
||||
#include "FdPrintf.h"
|
||||
#include "mozilla/NullPtr.h"
|
||||
|
||||
#include "base/lock.h"
|
||||
|
||||
static const malloc_table_t* sFuncs = nullptr;
|
||||
static int sFd = -1;
|
||||
|
||||
static Lock sLock;
|
||||
|
||||
static void
|
||||
prefork() {
|
||||
sLock.Acquire();
|
||||
}
|
||||
|
||||
static void
|
||||
postfork() {
|
||||
sLock.Release();
|
||||
}
|
||||
|
||||
#ifdef ANDROID
|
||||
/* See mozglue/android/APKOpen.cpp */
|
||||
extern "C" MOZ_EXPORT __attribute__((weak))
|
||||
void* __dso_handle;
|
||||
|
||||
/* Android doesn't have pthread_atfork defined in pthread.h */
|
||||
extern "C" MOZ_EXPORT
|
||||
int pthread_atfork(void (*)(void), void (*)(void), void (*)(void));
|
||||
#endif
|
||||
|
||||
void
|
||||
replace_init(const malloc_table_t* aTable)
|
||||
{
|
||||
sFuncs = aTable;
|
||||
|
||||
#ifndef _WIN32
|
||||
/* When another thread has acquired a lock before forking, the child
|
||||
* process will inherit the lock state but the thread, being nonexistent
|
||||
* in the child process, will never release it, leading to a dead-lock
|
||||
* whenever the child process gets the lock. We thus need to ensure no
|
||||
* other thread is holding the lock before forking, by acquiring it
|
||||
* ourselves, and releasing it after forking, both in the parent and child
|
||||
* processes.
|
||||
* Windows doesn't have this problem since there is no fork(). */
|
||||
pthread_atfork(prefork, postfork, postfork);
|
||||
#endif
|
||||
|
||||
/* Initialize output file descriptor from the MALLOC_LOG environment
|
||||
* variable. Numbers up to 9999 are considered as a preopened file
|
||||
* descriptor number. Other values are considered as a file name. */
|
||||
char* log = getenv("MALLOC_LOG");
|
||||
if (log && *log) {
|
||||
sFd = 0;
|
||||
const char *fd_num = log;
|
||||
while (*fd_num) {
|
||||
/* Reject non digits. */
|
||||
if (*fd_num < '0' || *fd_num > '9') {
|
||||
sFd = -1;
|
||||
break;
|
||||
}
|
||||
sFd = sFd * 10 + (*fd_num - '0');
|
||||
/* Reject values >= 10000. */
|
||||
if (sFd >= 10000) {
|
||||
sFd = -1;
|
||||
break;
|
||||
}
|
||||
fd_num++;
|
||||
}
|
||||
if (sFd == -1) {
|
||||
sFd = open(log, O_WRONLY | O_CREAT | O_APPEND, 0644);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Do a simple, text-form, log of all calls to replace-malloc functions.
|
||||
* Use locking to guarantee that an allocation that did happen is logged
|
||||
* before any other allocation/free happens.
|
||||
* TODO: Add a thread id to the log: different allocators, or even different
|
||||
* configurations of jemalloc behave differently when allocations are coming
|
||||
* from different threads. Reproducing those multi-threaded workloads would be
|
||||
* useful to test those differences.
|
||||
*/
|
||||
|
||||
void*
|
||||
replace_malloc(size_t aSize)
|
||||
{
|
||||
AutoLock lock(sLock);
|
||||
void* ptr = sFuncs->malloc(aSize);
|
||||
if (ptr) {
|
||||
FdPrintf(sFd, "%zu malloc(%zu)=%p\n", size_t(getpid()), aSize, ptr);
|
||||
}
|
||||
return ptr;
|
||||
}
|
||||
|
||||
int
|
||||
replace_posix_memalign(void** aPtr, size_t aAlignment, size_t aSize)
|
||||
{
|
||||
AutoLock lock(sLock);
|
||||
int ret = sFuncs->posix_memalign(aPtr, aAlignment, aSize);
|
||||
if (ret == 0) {
|
||||
FdPrintf(sFd, "%zu posix_memalign(%zu,%zu)=%p\n", size_t(getpid()),
|
||||
aAlignment, aSize, *aPtr);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
void*
|
||||
replace_aligned_alloc(size_t aAlignment, size_t aSize)
|
||||
{
|
||||
AutoLock lock(sLock);
|
||||
void* ptr = sFuncs->aligned_alloc(aAlignment, aSize);
|
||||
if (ptr) {
|
||||
FdPrintf(sFd, "%zu aligned_alloc(%zu,%zu)=%p\n", size_t(getpid()),
|
||||
aAlignment, aSize, ptr);
|
||||
}
|
||||
return ptr;
|
||||
}
|
||||
|
||||
void*
|
||||
replace_calloc(size_t aNum, size_t aSize)
|
||||
{
|
||||
AutoLock lock(sLock);
|
||||
void* ptr = sFuncs->calloc(aNum, aSize);
|
||||
if (ptr) {
|
||||
FdPrintf(sFd, "%zu calloc(%zu,%zu)=%p\n", size_t(getpid()), aNum, aSize, ptr);
|
||||
}
|
||||
return ptr;
|
||||
}
|
||||
|
||||
void*
|
||||
replace_realloc(void* aPtr, size_t aSize)
|
||||
{
|
||||
AutoLock lock(sLock);
|
||||
void* new_ptr = sFuncs->realloc(aPtr, aSize);
|
||||
if (new_ptr || !aSize) {
|
||||
FdPrintf(sFd, "%zu realloc(%p,%zu)=%p\n", size_t(getpid()), aPtr, aSize,
|
||||
new_ptr);
|
||||
}
|
||||
return new_ptr;
|
||||
}
|
||||
|
||||
void
|
||||
replace_free(void* aPtr)
|
||||
{
|
||||
AutoLock lock(sLock);
|
||||
if (aPtr) {
|
||||
FdPrintf(sFd, "%zu free(%p)\n", size_t(getpid()), aPtr);
|
||||
}
|
||||
sFuncs->free(aPtr);
|
||||
}
|
||||
|
||||
void*
|
||||
replace_memalign(size_t aAlignment, size_t aSize)
|
||||
{
|
||||
AutoLock lock(sLock);
|
||||
void* ptr = sFuncs->memalign(aAlignment, aSize);
|
||||
if (ptr) {
|
||||
FdPrintf(sFd, "%zu memalign(%zu,%zu)=%p\n", size_t(getpid()), aAlignment,
|
||||
aSize, ptr);
|
||||
}
|
||||
return ptr;
|
||||
}
|
||||
|
||||
void*
|
||||
replace_valloc(size_t aSize)
|
||||
{
|
||||
AutoLock lock(sLock);
|
||||
void* ptr = sFuncs->valloc(aSize);
|
||||
if (ptr) {
|
||||
FdPrintf(sFd, "%zu valloc(%zu)=%p\n", size_t(getpid()), aSize, ptr);
|
||||
}
|
||||
return ptr;
|
||||
}
|
||||
|
||||
void
|
||||
replace_jemalloc_stats(jemalloc_stats_t* aStats)
|
||||
{
|
||||
AutoLock lock(sLock);
|
||||
sFuncs->jemalloc_stats(aStats);
|
||||
FdPrintf(sFd, "%zu jemalloc_stats()\n", size_t(getpid()));
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
# This Source Code Form is subject to the terms of the Mozilla Public
|
||||
# License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
# Disable mozglue.
|
||||
WRAP_LDFLAGS =
|
||||
MOZ_GLUE_LDFLAGS=
|
||||
|
||||
# Avoid Lock_impl code depending on mozilla::Logger
|
||||
MOZ_DEBUG_ENABLE_DEFS=
|
|
@ -0,0 +1,107 @@
|
|||
Logalloc is a replace-malloc library for Firefox (see
|
||||
memory/build/replace_malloc.h) that dumps a log of memory allocations to a
|
||||
given file descriptor or file name. That log can then be replayed against
|
||||
Firefox's default memory allocator independently or through another
|
||||
replace-malloc library, allowing the testing of other allocators under the
|
||||
exact same workload.
|
||||
|
||||
To get an allocation log the following environment variables need to be set
|
||||
when starting Firefox:
|
||||
- on Linux:
|
||||
LD_PRELOAD=/path/to/liblogalloc.so
|
||||
- on Mac OSX:
|
||||
DYLD_INSERT_LIBRARIES=/path/to/liblogalloc.dylib
|
||||
- on Windows:
|
||||
MOZ_REPLACE_MALLOC_LIB=/path/to/logalloc.dll
|
||||
- on Android:
|
||||
MOZ_REPLACE_MALLOC_LIB=/path/to/liblogalloc.so
|
||||
(see https://wiki.mozilla.org/Mobile/Fennec/Android#Arguments_and_Environment_Variables
|
||||
for how to pass environment variables to Firefox for Android)
|
||||
|
||||
- on all platforms:
|
||||
MALLOC_LOG=/path/to/log-file
|
||||
or
|
||||
MALLOC_LOG=number
|
||||
|
||||
When MALLOC_LOG is a number below 10000, it is considered as a file
|
||||
descriptor number that is fed to Firefox when it is started. Otherwise,
|
||||
it is considered as a file name.
|
||||
|
||||
As those allocation logs can grow large quite quickly, it can be useful
|
||||
to pipe the output to a compression tool.
|
||||
|
||||
MALLOC_LOG=1 would send to Firefox's stdout, MALLOC_LOG=2 would send to
|
||||
its stderr. Since in both cases that could be mixed with other output
|
||||
from Firefox, it is usually better to use another file descriptor
|
||||
by shell redirections, such as:
|
||||
|
||||
MALLOC_LOG=3 firefox 3>&1 1>&2 | gzip -c > log.gz
|
||||
|
||||
(3>&1 copies the `| gzip` pipe file descriptor to file descriptor #3, 1>&2
|
||||
then copies stderr to stdout. This leads to: fd1 and fd2 sending to stderr
|
||||
of the parent process (the shell), and fd3 sending to gzip.)
|
||||
|
||||
Each line of the allocations log is formatted as follows:
|
||||
<pid> <function>([<args>])[=<result>]
|
||||
where <args> is a comma separated list of values. The number of <args> and
|
||||
the presence of <result> depend on the <function>.
|
||||
|
||||
Example log:
|
||||
18545 malloc(32)=0x7f90495120e0
|
||||
18545 calloc(1,148)=0x7f9049537480
|
||||
18545 realloc(0x7f90495120e0,64)=0x7f9049536680
|
||||
18545 posix_memalign(256,240)=0x7f9049583300
|
||||
18545 jemalloc_stats()
|
||||
18545 free(0x7f9049536680)
|
||||
|
||||
This log can be replayed with the logalloc-replay tool in
|
||||
memory/replace/logalloc/replay. However, as the goal of that tool is to
|
||||
reproduce the recorded memory allocations, it needs to avoid as much as
|
||||
possible doing its own allocations for bookkeeping. Reading the logs as
|
||||
they are would require data structures and memory allocations. As a
|
||||
consequence, the logs need to be preprocessed beforehand.
|
||||
|
||||
The logalloc_munge.py script is responsible for that preprocessing. It simply
|
||||
takes a raw log on its stdin, and outputs the preprocessed log on its stdout.
|
||||
It replaces pointer addresses with indexes the logalloc-replay tool can use
|
||||
in a large (almost) linear array of allocation tracking slots (prefixed with
|
||||
'#'). It also replaces the pids with numbers starting from 1 (such as the
|
||||
first seen pid number is 1, the second is 2, etc.).
|
||||
|
||||
The above example log would become the following, once preprocessed:
|
||||
1 malloc(32)=#1
|
||||
1 calloc(1,148)=#2
|
||||
1 realloc(#1,64)=#1
|
||||
1 posix_memalign(256,240)=#3
|
||||
1 jemalloc_stats()
|
||||
1 free(#1)
|
||||
|
||||
The logalloc-replay tool then takes the preprocessed log on its stdin and
|
||||
replays the allocations printed there, but will only replay those with the
|
||||
same process id as the first line (which normally is 1).
|
||||
|
||||
As the log files are simple text files, though, it is easy to separate out
|
||||
the different processes log with e.g. grep, and feed the separate processes
|
||||
logs to logalloc-replay.
|
||||
|
||||
The logalloc-replay program won't output anything unless jemalloc_stats
|
||||
records appears in the log. You can expect those to be recorded when going
|
||||
to about:memory in Firefox, but they can also be added after preprocessing.
|
||||
|
||||
Here is an example of what one can do:
|
||||
|
||||
gunzip -c log.gz | python logalloc_munge.py | \
|
||||
awk '$1 == "2" { print $0 } !(NR % 10000) { print "2 jemalloc_stats()" }' | \
|
||||
./logalloc-replay
|
||||
|
||||
The above command replays the allocations of process #2, with some stats
|
||||
output every 10000 records.
|
||||
|
||||
The logalloc-replay tool itself being hooked with replace-malloc, it is possible
|
||||
to set LD_PRELOAD/DYLD_INSERT_LIBRARIES/MOZ_REPLACE_MALLOC_LIB and replay a log
|
||||
through a different allocator. For example:
|
||||
|
||||
LD_PRELOAD=libreplace_jemalloc.so logalloc-replay < log
|
||||
|
||||
Will replay the log against jemalloc3 (which is, as of writing, what
|
||||
libreplace_jemalloc.so contains).
|
|
@ -0,0 +1,40 @@
|
|||
# -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*-
|
||||
# vim: set filetype=python:
|
||||
# This Source Code Form is subject to the terms of the Mozilla Public
|
||||
# License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
SharedLibrary('logalloc')
|
||||
|
||||
SOURCES += [
|
||||
'FdPrintf.cpp',
|
||||
'LogAlloc.cpp',
|
||||
]
|
||||
|
||||
DISABLE_STL_WRAPPING = True
|
||||
USE_STATIC_LIBS = True
|
||||
DEFINES['MOZ_NO_MOZALLOC'] = True
|
||||
# Avoid Lock_impl code depending on mozilla::Logger.
|
||||
DEFINES['NDEBUG'] = True
|
||||
|
||||
# Use locking code from the chromium stack.
|
||||
if CONFIG['OS_TARGET'] == 'WINNT':
|
||||
SOURCES += [
|
||||
'../../../ipc/chromium/src/base/lock_impl_win.cc',
|
||||
]
|
||||
else:
|
||||
SOURCES += [
|
||||
'../../../ipc/chromium/src/base/lock_impl_posix.cc',
|
||||
]
|
||||
|
||||
include('/ipc/chromium/chromium-config.mozbuild')
|
||||
|
||||
# Android doesn't have pthread_atfork, but we have our own in mozglue.
|
||||
if CONFIG['OS_TARGET'] == 'Android':
|
||||
USE_LIBS += [
|
||||
'mozglue',
|
||||
]
|
||||
|
||||
DIRS += [
|
||||
'replay',
|
||||
]
|
|
@ -0,0 +1,37 @@
|
|||
# This Source Code Form is subject to the terms of the Mozilla Public
|
||||
# License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
# Disable mozglue.
|
||||
WRAP_LDFLAGS =
|
||||
MOZ_GLUE_LDFLAGS=
|
||||
MOZ_GLUE_PROGRAM_LDFLAGS=
|
||||
|
||||
include $(topsrcdir)/mozglue/build/replace_malloc.mk
|
||||
|
||||
ifndef CROSS_COMPILE
|
||||
|
||||
ifeq ($(OS_TARGET),WINNT)
|
||||
LOGALLOC = MOZ_REPLACE_MALLOC_LIB=$(CURDIR)/../logalloc.dll
|
||||
else
|
||||
ifeq ($(OS_TARGET),Darwin)
|
||||
LOGALLOC = DYLD_INSERT_LIBRARIES=$(CURDIR)/../liblogalloc.dylib
|
||||
else
|
||||
LOGALLOC = LD_PRELOAD=$(CURDIR)/../$(DLL_PREFIX)logalloc$(DLL_SUFFIX)
|
||||
endif
|
||||
endif
|
||||
|
||||
expected_output.log: $(srcdir)/replay.log
|
||||
# The logalloc-replay program will only replay entries from the first pid,
|
||||
# so the expected output only contains entries beginning with "1 "
|
||||
grep "^1 " $< > $@
|
||||
|
||||
check:: $(srcdir)/replay.log expected_output.log
|
||||
# Test with MALLOC_LOG as a file descriptor number
|
||||
MALLOC_LOG=1 $(LOGALLOC) ./$(PROGRAM) < $< | $(PYTHON) $(srcdir)/logalloc_munge.py | diff -w - expected_output.log
|
||||
# Test with MALLOC_LOG as a file name
|
||||
$(RM) test_output.log
|
||||
MALLOC_LOG=test_output.log $(LOGALLOC) ./$(PROGRAM) < $<
|
||||
$(PYTHON) $(srcdir)/logalloc_munge.py < test_output.log | diff -w - expected_output.log
|
||||
|
||||
endif
|
|
@ -0,0 +1,561 @@
|
|||
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#define MOZ_MEMORY_IMPL
|
||||
#include "mozmemory_wrap.h"
|
||||
|
||||
#ifdef _WIN32
|
||||
/* windef.h, which windows.h includes, #defines min and max, which
|
||||
* breaks std::min. Defining NOMINMAX prevents those #defines. */
|
||||
#define NOMINMAX
|
||||
#include <windows.h>
|
||||
#include <io.h>
|
||||
typedef int ssize_t;
|
||||
#else
|
||||
#include <sys/mman.h>
|
||||
#include <unistd.h>
|
||||
#endif
|
||||
#include <algorithm>
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
|
||||
#include "mozilla/Assertions.h"
|
||||
#include "mozilla/NullPtr.h"
|
||||
#include "FdPrintf.h"
|
||||
|
||||
static void
|
||||
die(const char* message)
|
||||
{
|
||||
/* Here, it doesn't matter that fprintf may allocate memory. */
|
||||
fprintf(stderr, "%s\n", message);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
/* We don't want to be using malloc() to allocate our internal tracking
|
||||
* data, because that would change the parameters of what is being measured,
|
||||
* so we want to use data types that directly use mmap/VirtualAlloc. */
|
||||
template <typename T, size_t Len>
|
||||
class MappedArray
|
||||
{
|
||||
public:
|
||||
MappedArray(): mPtr(nullptr) {}
|
||||
|
||||
~MappedArray()
|
||||
{
|
||||
if (mPtr) {
|
||||
#ifdef _WIN32
|
||||
VirtualFree(mPtr, sizeof(T) * Len, MEM_RELEASE);
|
||||
#else
|
||||
munmap(mPtr, sizeof(T) * Len);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
T& operator[] (size_t aIndex) const
|
||||
{
|
||||
if (mPtr) {
|
||||
return mPtr[aIndex];
|
||||
}
|
||||
|
||||
#ifdef _WIN32
|
||||
mPtr = reinterpret_cast<T*>(VirtualAlloc(nullptr, sizeof(T) * Len,
|
||||
MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE));
|
||||
if (mPtr == nullptr) {
|
||||
die("VirtualAlloc error");
|
||||
}
|
||||
#else
|
||||
mPtr = reinterpret_cast<T*>(mmap(nullptr, sizeof(T) * Len,
|
||||
PROT_READ | PROT_WRITE, MAP_ANON | MAP_PRIVATE, -1, 0));
|
||||
if (mPtr == MAP_FAILED) {
|
||||
die("Mmap error");
|
||||
}
|
||||
#endif
|
||||
return mPtr[aIndex];
|
||||
}
|
||||
|
||||
private:
|
||||
mutable T* mPtr;
|
||||
};
|
||||
|
||||
/* Type for records of allocations. */
|
||||
struct MemSlot
|
||||
{
|
||||
void* mPtr;
|
||||
size_t mSize;
|
||||
};
|
||||
|
||||
/* An almost infinite list of slots.
|
||||
* In essence, this is a linked list of arrays of groups of slots.
|
||||
* Each group is 1MB. On 64-bits, one group allows to store 64k allocations.
|
||||
* Each MemSlotList instance can store 1023 such groups, which means more
|
||||
* than 65M allocations. In case more would be needed, we chain to another
|
||||
* MemSlotList, and so on.
|
||||
* Using 1023 groups makes the MemSlotList itself page sized on 32-bits
|
||||
* and 2 pages-sized on 64-bits.
|
||||
*/
|
||||
class MemSlotList
|
||||
{
|
||||
static const size_t kGroups = 1024 - 1;
|
||||
static const size_t kGroupSize = (1024 * 1024) / sizeof(MemSlot);
|
||||
|
||||
MappedArray<MemSlot, kGroupSize> mSlots[kGroups];
|
||||
MappedArray<MemSlotList, 1> mNext;
|
||||
|
||||
public:
|
||||
MemSlot& operator[] (size_t aIndex) const
|
||||
{
|
||||
if (aIndex < kGroupSize * kGroups) {
|
||||
return mSlots[aIndex / kGroupSize][aIndex % kGroupSize];
|
||||
}
|
||||
aIndex -= kGroupSize * kGroups;
|
||||
return mNext[0][aIndex];
|
||||
}
|
||||
};
|
||||
|
||||
/* Helper class for memory buffers */
|
||||
class Buffer
|
||||
{
|
||||
public:
|
||||
Buffer() : mBuf(nullptr), mLength(0) {}
|
||||
|
||||
Buffer(const void* aBuf, size_t aLength)
|
||||
: mBuf(reinterpret_cast<const char*>(aBuf)), mLength(aLength)
|
||||
{}
|
||||
|
||||
/* Constructor for string literals. */
|
||||
template <size_t Size>
|
||||
Buffer(const char (&aStr)[Size])
|
||||
: mBuf(aStr), mLength(Size - 1)
|
||||
{}
|
||||
|
||||
/* Returns a sub-buffer up-to but not including the given aNeedle character.
|
||||
* The "parent" buffer itself is altered to begin after the aNeedle
|
||||
* character.
|
||||
* If the aNeedle character is not found, return the entire buffer, and empty
|
||||
* the "parent" buffer. */
|
||||
Buffer SplitChar(char aNeedle)
|
||||
{
|
||||
char* buf = const_cast<char*>(mBuf);
|
||||
char* c = reinterpret_cast<char*>(memchr(buf, aNeedle, mLength));
|
||||
if (!c) {
|
||||
return Split(mLength);
|
||||
}
|
||||
|
||||
Buffer result = Split(c - buf);
|
||||
// Remove the aNeedle character itself.
|
||||
Split(1);
|
||||
return result;
|
||||
}
|
||||
|
||||
/* Returns a sub-buffer of at most aLength characters. The "parent" buffer is
|
||||
* amputated of those aLength characters. If the "parent" buffer is smaller
|
||||
* than aLength, then its length is used instead. */
|
||||
Buffer Split(size_t aLength)
|
||||
{
|
||||
Buffer result(mBuf, std::min(aLength, mLength));
|
||||
mLength -= result.mLength;
|
||||
mBuf += result.mLength;
|
||||
return result;
|
||||
}
|
||||
|
||||
/* Move the buffer (including its content) to the memory address of the aOther
|
||||
* buffer. */
|
||||
void Slide(Buffer aOther)
|
||||
{
|
||||
memmove(const_cast<char*>(aOther.mBuf), mBuf, mLength);
|
||||
mBuf = aOther.mBuf;
|
||||
}
|
||||
|
||||
/* Returns whether the two involved buffers have the same content. */
|
||||
bool operator ==(Buffer aOther)
|
||||
{
|
||||
return mLength == aOther.mLength && (mBuf == aOther.mBuf ||
|
||||
!strncmp(mBuf, aOther.mBuf, mLength));
|
||||
}
|
||||
|
||||
/* Returns whether the buffer is empty. */
|
||||
operator bool() { return mLength; }
|
||||
|
||||
/* Returns the memory location of the buffer. */
|
||||
const char* get() { return mBuf; }
|
||||
|
||||
/* Returns the memory location of the end of the buffer (technically, the
|
||||
* first byte after the buffer). */
|
||||
const char* GetEnd() { return mBuf + mLength; }
|
||||
|
||||
/* Extend the buffer over the content of the other buffer, assuming it is
|
||||
* adjacent. */
|
||||
void Extend(Buffer aOther)
|
||||
{
|
||||
MOZ_ASSERT(aOther.mBuf == GetEnd());
|
||||
mLength += aOther.mLength;
|
||||
}
|
||||
|
||||
private:
|
||||
const char* mBuf;
|
||||
size_t mLength;
|
||||
};
|
||||
|
||||
/* Helper class to read from a file descriptor line by line. */
|
||||
class FdReader {
|
||||
public:
|
||||
FdReader(int aFd)
|
||||
: mFd(aFd)
|
||||
, mData(&mRawBuf, 0)
|
||||
, mBuf(&mRawBuf, sizeof(mRawBuf))
|
||||
{}
|
||||
|
||||
/* Read a line from the file descriptor and returns it as a Buffer instance */
|
||||
Buffer ReadLine()
|
||||
{
|
||||
while (true) {
|
||||
Buffer result = mData.SplitChar('\n');
|
||||
|
||||
/* There are essentially three different cases here:
|
||||
* - '\n' was found "early". In this case, the end of the result buffer
|
||||
* is before the beginning of the mData buffer (since SplitChar
|
||||
* amputated it).
|
||||
* - '\n' was found as the last character of mData. In this case, mData
|
||||
* is empty, but still points at the end of mBuf. result points to what
|
||||
* used to be in mData, without the last character.
|
||||
* - '\n' was not found. In this case too, mData is empty and points at
|
||||
* the end of mBuf. But result points to the entire buffer that used to
|
||||
* be pointed by mData.
|
||||
* Only in the latter case do both result and mData's end match, and it's
|
||||
* the only case where we need to refill the buffer.
|
||||
*/
|
||||
if (result.GetEnd() != mData.GetEnd()) {
|
||||
return result;
|
||||
}
|
||||
|
||||
/* Since SplitChar emptied mData, make it point to what it had before. */
|
||||
mData = result;
|
||||
|
||||
/* And move it to the beginning of the read buffer. */
|
||||
mData.Slide(mBuf);
|
||||
|
||||
FillBuffer();
|
||||
|
||||
if (!mData) {
|
||||
return Buffer();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
/* Fill the read buffer. */
|
||||
void FillBuffer()
|
||||
{
|
||||
size_t size = mBuf.GetEnd() - mData.GetEnd();
|
||||
Buffer remainder(mData.GetEnd(), size);
|
||||
|
||||
ssize_t len = 1;
|
||||
while (remainder && len > 0) {
|
||||
len = ::read(mFd, const_cast<char*>(remainder.get()), size);
|
||||
if (len < 0) {
|
||||
die("Read error");
|
||||
}
|
||||
size -= len;
|
||||
mData.Extend(remainder.Split(len));
|
||||
}
|
||||
}
|
||||
|
||||
/* File descriptor to read from. */
|
||||
int mFd;
|
||||
/* Part of data that was read from the file descriptor but not returned with
|
||||
* ReadLine yet. */
|
||||
Buffer mData;
|
||||
/* Buffer representation of mRawBuf */
|
||||
Buffer mBuf;
|
||||
/* read() buffer */
|
||||
char mRawBuf[4096];
|
||||
};
|
||||
|
||||
MOZ_BEGIN_EXTERN_C
|
||||
|
||||
/* Function declarations for all the replace_malloc _impl functions.
|
||||
* See memory/build/replace_malloc.c */
|
||||
#define MALLOC_DECL(name, return_type, ...) \
|
||||
return_type name ## _impl(__VA_ARGS__);
|
||||
#define MALLOC_FUNCS MALLOC_FUNCS_MALLOC
|
||||
#include "malloc_decls.h"
|
||||
|
||||
#define MALLOC_DECL(name, return_type, ...) \
|
||||
return_type name ## _impl(__VA_ARGS__);
|
||||
#define MALLOC_FUNCS MALLOC_FUNCS_JEMALLOC
|
||||
#include "malloc_decls.h"
|
||||
|
||||
/* mozjemalloc relies on DllMain to initialize, but DllMain is not invoked
|
||||
* for executables, so manually invoke mozjemalloc initialization. */
|
||||
#if defined(_WIN32) && !defined(MOZ_JEMALLOC3)
|
||||
void malloc_init_hard(void);
|
||||
#endif
|
||||
|
||||
#ifdef ANDROID
|
||||
/* mozjemalloc uses MozTagAnonymousMemory, which doesn't have an inline
|
||||
* implementation on Android */
|
||||
void
|
||||
MozTagAnonymousMemory(const void* aPtr, size_t aLength, const char* aTag) {}
|
||||
|
||||
/* mozjemalloc and jemalloc use pthread_atfork, which Android doesn't have.
|
||||
* While gecko has one in libmozglue, the replay program can't use that.
|
||||
* Since we're not going to fork anyways, make it a dummy function. */
|
||||
int
|
||||
pthread_atfork(void (*aPrepare)(void), void (*aParent)(void),
|
||||
void (*aChild)(void))
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef MOZ_NUWA_PROCESS
|
||||
#include <pthread.h>
|
||||
|
||||
/* NUWA builds have jemalloc built with
|
||||
* -Dpthread_mutex_lock=__real_pthread_mutex_lock */
|
||||
int
|
||||
__real_pthread_mutex_lock(pthread_mutex_t* aMutex)
|
||||
{
|
||||
return pthread_mutex_lock(aMutex);
|
||||
}
|
||||
#endif
|
||||
|
||||
MOZ_END_EXTERN_C
|
||||
|
||||
size_t parseNumber(Buffer aBuf)
|
||||
{
|
||||
if (!aBuf) {
|
||||
die("Malformed input");
|
||||
}
|
||||
|
||||
size_t result = 0;
|
||||
for (const char* c = aBuf.get(), *end = aBuf.GetEnd(); c < end; c++) {
|
||||
if (*c < '0' || *c > '9') {
|
||||
die("Malformed input");
|
||||
}
|
||||
result *= 10;
|
||||
result += *c - '0';
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/* Class to handle dispatching the replay function calls to replace-malloc. */
|
||||
class Replay
|
||||
{
|
||||
public:
|
||||
Replay(): mOps(0) {}
|
||||
|
||||
MemSlot& operator[] (size_t index) const
|
||||
{
|
||||
return mSlots[index];
|
||||
}
|
||||
|
||||
void malloc(MemSlot& aSlot, Buffer& aArgs)
|
||||
{
|
||||
mOps++;
|
||||
size_t size = parseNumber(aArgs);
|
||||
aSlot.mPtr = ::malloc_impl(size);
|
||||
aSlot.mSize = size;
|
||||
Commit(aSlot);
|
||||
}
|
||||
|
||||
void posix_memalign(MemSlot& aSlot, Buffer& aArgs)
|
||||
{
|
||||
mOps++;
|
||||
size_t alignment = parseNumber(aArgs.SplitChar(','));
|
||||
size_t size = parseNumber(aArgs);
|
||||
void* ptr;
|
||||
if (::posix_memalign_impl(&ptr, alignment, size) == 0) {
|
||||
aSlot.mPtr = ptr;
|
||||
aSlot.mSize = size;
|
||||
} else {
|
||||
aSlot.mPtr = nullptr;
|
||||
aSlot.mSize = 0;
|
||||
}
|
||||
Commit(aSlot);
|
||||
}
|
||||
|
||||
void aligned_alloc(MemSlot& aSlot, Buffer& aArgs)
|
||||
{
|
||||
mOps++;
|
||||
size_t alignment = parseNumber(aArgs.SplitChar(','));
|
||||
size_t size = parseNumber(aArgs);
|
||||
aSlot.mPtr = ::aligned_alloc_impl(alignment, size);
|
||||
aSlot.mSize = size;
|
||||
Commit(aSlot);
|
||||
}
|
||||
|
||||
void calloc(MemSlot& aSlot, Buffer& aArgs)
|
||||
{
|
||||
mOps++;
|
||||
size_t num = parseNumber(aArgs.SplitChar(','));
|
||||
size_t size = parseNumber(aArgs);
|
||||
aSlot.mPtr = ::calloc_impl(num, size);
|
||||
aSlot.mSize = size;
|
||||
Commit(aSlot);
|
||||
}
|
||||
|
||||
void realloc(MemSlot& aSlot, Buffer& aArgs)
|
||||
{
|
||||
mOps++;
|
||||
Buffer dummy = aArgs.SplitChar('#');
|
||||
if (dummy) {
|
||||
die("Malformed input");
|
||||
}
|
||||
size_t slot_id = parseNumber(aArgs.SplitChar(','));
|
||||
size_t size = parseNumber(aArgs);
|
||||
MemSlot& old_slot = (*this)[slot_id];
|
||||
void* old_ptr = old_slot.mPtr;
|
||||
old_slot.mPtr = nullptr;
|
||||
old_slot.mSize = 0;
|
||||
aSlot.mPtr = ::realloc_impl(old_ptr, size);
|
||||
aSlot.mSize = size;
|
||||
Commit(aSlot);
|
||||
}
|
||||
|
||||
void free(Buffer& aArgs)
|
||||
{
|
||||
mOps++;
|
||||
Buffer dummy = aArgs.SplitChar('#');
|
||||
if (dummy) {
|
||||
die("Malformed input");
|
||||
}
|
||||
size_t slot_id = parseNumber(aArgs);
|
||||
MemSlot& slot = (*this)[slot_id];
|
||||
::free_impl(slot.mPtr);
|
||||
slot.mPtr = nullptr;
|
||||
slot.mSize = 0;
|
||||
}
|
||||
|
||||
void memalign(MemSlot& aSlot, Buffer& aArgs)
|
||||
{
|
||||
mOps++;
|
||||
size_t alignment = parseNumber(aArgs.SplitChar(','));
|
||||
size_t size = parseNumber(aArgs);
|
||||
aSlot.mPtr = ::memalign_impl(alignment, size);
|
||||
aSlot.mSize = size;
|
||||
Commit(aSlot);
|
||||
}
|
||||
|
||||
void valloc(MemSlot& aSlot, Buffer& aArgs)
|
||||
{
|
||||
mOps++;
|
||||
size_t size = parseNumber(aArgs);
|
||||
aSlot.mPtr = ::valloc_impl(size);
|
||||
aSlot.mSize = size;
|
||||
Commit(aSlot);
|
||||
}
|
||||
|
||||
void jemalloc_stats(Buffer& aArgs)
|
||||
{
|
||||
if (aArgs) {
|
||||
die("Malformed input");
|
||||
}
|
||||
jemalloc_stats_t stats;
|
||||
::jemalloc_stats_impl(&stats);
|
||||
FdPrintf(2, "#%zu mapped: %zu; allocated: %zu; waste: %zu; dirty: %zu; "
|
||||
"bookkeep: %zu; binunused: %zu\n", mOps, stats.mapped,
|
||||
stats.allocated, stats.waste, stats.page_cache,
|
||||
stats.bookkeeping, stats.bin_unused);
|
||||
/* TODO: Add more data, like actual RSS as measured by OS, but compensated
|
||||
* for the replay internal data. */
|
||||
}
|
||||
|
||||
private:
|
||||
void Commit(MemSlot& aSlot)
|
||||
{
|
||||
memset(aSlot.mPtr, 0x5a, aSlot.mSize);
|
||||
}
|
||||
|
||||
size_t mOps;
|
||||
MemSlotList mSlots;
|
||||
};
|
||||
|
||||
|
||||
int
|
||||
main()
|
||||
{
|
||||
size_t first_pid = 0;
|
||||
FdReader reader(0);
|
||||
Replay replay;
|
||||
|
||||
#if defined(_WIN32) && !defined(MOZ_JEMALLOC3)
|
||||
malloc_init_hard();
|
||||
#endif
|
||||
|
||||
/* Read log from stdin and dispatch function calls to the Replay instance.
|
||||
* The log format is essentially:
|
||||
* <pid> <function>([<args>])[=<result>]
|
||||
* <args> is a comma separated list of arguments.
|
||||
*
|
||||
* The logs are expected to be preprocessed so that allocations are
|
||||
* attributed a tracking slot. The input is trusted not to have crazy
|
||||
* values for these slot numbers.
|
||||
*
|
||||
* <result>, as well as some of the args to some of the function calls are
|
||||
* such slot numbers.
|
||||
*/
|
||||
while (true) {
|
||||
Buffer line = reader.ReadLine();
|
||||
|
||||
if (!line) {
|
||||
break;
|
||||
}
|
||||
|
||||
size_t pid = parseNumber(line.SplitChar(' '));
|
||||
if (!first_pid) {
|
||||
first_pid = pid;
|
||||
}
|
||||
|
||||
/* The log may contain data for several processes, only entries for the
|
||||
* very first that appears are treated. */
|
||||
if (first_pid != pid) {
|
||||
continue;
|
||||
}
|
||||
|
||||
Buffer func = line.SplitChar('(');
|
||||
Buffer args = line.SplitChar(')');
|
||||
|
||||
/* jemalloc_stats and free are functions with no result. */
|
||||
if (func == Buffer("jemalloc_stats")) {
|
||||
replay.jemalloc_stats(args);
|
||||
continue;
|
||||
} else if (func == Buffer("free")) {
|
||||
replay.free(args);
|
||||
continue;
|
||||
}
|
||||
|
||||
/* Parse result value and get the corresponding slot. */
|
||||
Buffer dummy = line.SplitChar('=');
|
||||
Buffer dummy2 = line.SplitChar('#');
|
||||
if (dummy || dummy2) {
|
||||
die("Malformed input");
|
||||
}
|
||||
|
||||
size_t slot_id = parseNumber(line);
|
||||
MemSlot& slot = replay[slot_id];
|
||||
|
||||
if (func == Buffer("malloc")) {
|
||||
replay.malloc(slot, args);
|
||||
} else if (func == Buffer("posix_memalign")) {
|
||||
replay.posix_memalign(slot, args);
|
||||
} else if (func == Buffer("aligned_alloc")) {
|
||||
replay.aligned_alloc(slot, args);
|
||||
} else if (func == Buffer("calloc")) {
|
||||
replay.calloc(slot, args);
|
||||
} else if (func == Buffer("realloc")) {
|
||||
replay.realloc(slot, args);
|
||||
} else if (func == Buffer("memalign")) {
|
||||
replay.memalign(slot, args);
|
||||
} else if (func == Buffer("valloc")) {
|
||||
replay.valloc(slot, args);
|
||||
} else {
|
||||
die("Malformed input");
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
|
@ -0,0 +1,135 @@
|
|||
# This Source Code Form is subject to the terms of the Mozilla Public
|
||||
# License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
"""
|
||||
This script takes a log from the replace-malloc logalloc library on stdin
|
||||
and munges it so that it can be used with the logalloc-replay tool.
|
||||
|
||||
Given the following output:
|
||||
13663 malloc(42)=0x7f0c33502040
|
||||
13663 malloc(24)=0x7f0c33503040
|
||||
13663 free(0x7f0c33502040)
|
||||
The resulting output is:
|
||||
1 malloc(42)=#1
|
||||
1 malloc(24)=#2
|
||||
1 free(#1)
|
||||
|
||||
See README for more details.
|
||||
"""
|
||||
|
||||
from __future__ import print_function
|
||||
import sys
|
||||
from collections import (
|
||||
defaultdict,
|
||||
deque,
|
||||
)
|
||||
|
||||
class IdMapping(object):
|
||||
"""Class to map values to ids.
|
||||
|
||||
Each value is associated to an increasing id, starting from 1.
|
||||
When a value is removed, its id is recycled and will be reused for
|
||||
subsequent values.
|
||||
"""
|
||||
def __init__(self):
|
||||
self.id = 1
|
||||
self._values = {}
|
||||
self._recycle = deque()
|
||||
|
||||
def __getitem__(self, value):
|
||||
if value not in self._values:
|
||||
if self._recycle:
|
||||
self._values[value] = self._recycle.popleft()
|
||||
else:
|
||||
self._values[value] = self.id
|
||||
self.id += 1
|
||||
return self._values[value]
|
||||
|
||||
def __delitem__(self, value):
|
||||
if value == 0:
|
||||
return
|
||||
self._recycle.append(self._values[value])
|
||||
del self._values[value]
|
||||
|
||||
def __contains__(self, value):
|
||||
return value == 0 or value in self._values
|
||||
|
||||
|
||||
class Ignored(Exception): pass
|
||||
|
||||
|
||||
def split_log_line(line):
|
||||
try:
|
||||
pid, func_call = line.split(' ', 1)
|
||||
# func_call format is <function>([<args>])[=<result>]
|
||||
call, result = func_call.split(')')
|
||||
func, args = call.split('(')
|
||||
args = args.split(',') if args else []
|
||||
if result:
|
||||
if result[0] != '=':
|
||||
raise Ignored('Malformed input')
|
||||
result = result[1:]
|
||||
return pid, func, args, result
|
||||
except:
|
||||
raise Ignored('Malformed input')
|
||||
|
||||
|
||||
NUM_ARGUMENTS = {
|
||||
'jemalloc_stats': 0,
|
||||
'free': 1,
|
||||
'malloc': 1,
|
||||
'posix_memalign': 2,
|
||||
'aligned_alloc': 2,
|
||||
'calloc': 2,
|
||||
'realloc': 2,
|
||||
'memalign': 2,
|
||||
'valloc': 1,
|
||||
}
|
||||
|
||||
|
||||
def main():
|
||||
process_pointers = defaultdict(IdMapping)
|
||||
pids = IdMapping()
|
||||
for line in sys.stdin:
|
||||
line = line.strip()
|
||||
|
||||
try:
|
||||
pid, func, args, result = split_log_line(line)
|
||||
|
||||
# Replace pid with an id.
|
||||
pid = pids[int(pid)]
|
||||
|
||||
pointers = process_pointers[pid]
|
||||
|
||||
if func not in NUM_ARGUMENTS:
|
||||
raise Ignored('Unknown function')
|
||||
|
||||
if len(args) != NUM_ARGUMENTS[func]:
|
||||
raise Ignored('Malformed input')
|
||||
|
||||
if func in ('jemalloc_stats', 'free') and result:
|
||||
raise Ignored('Malformed input')
|
||||
|
||||
if func in ('free', 'realloc'):
|
||||
ptr = int(args[0], 16)
|
||||
if ptr and ptr not in pointers:
|
||||
raise Ignored('Did not see an alloc for pointer')
|
||||
args[0] = "#%d" % pointers[ptr]
|
||||
del pointers[ptr]
|
||||
|
||||
if result:
|
||||
result = int(result, 16)
|
||||
if not result:
|
||||
raise Ignored('Result is NULL')
|
||||
result = "#%d" % pointers[result]
|
||||
|
||||
print('%d %s(%s)%s' % (pid, func, ','.join(args),
|
||||
'=%s' % result if result else ''))
|
||||
|
||||
except Exception as e:
|
||||
print('Ignored "%s": %s' % (line, e.message), file=sys.stderr)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
|
@ -0,0 +1,25 @@
|
|||
# -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*-
|
||||
# vim: set filetype=python:
|
||||
# This Source Code Form is subject to the terms of the Mozilla Public
|
||||
# License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
Program('logalloc-replay')
|
||||
|
||||
SOURCES += [
|
||||
'../FdPrintf.cpp',
|
||||
'Replay.cpp',
|
||||
]
|
||||
|
||||
LOCAL_INCLUDES += [
|
||||
'..',
|
||||
]
|
||||
|
||||
# Link replace-malloc and the default allocator.
|
||||
USE_LIBS += [
|
||||
'memory',
|
||||
]
|
||||
|
||||
DISABLE_STL_WRAPPING = True
|
||||
|
||||
DEFINES['MOZ_REPLACE_MALLOC'] = True
|
|
@ -0,0 +1,17 @@
|
|||
1 malloc(42)=#1
|
||||
1 malloc(24)=#2
|
||||
2 malloc(42)=#1
|
||||
1 free(#1)
|
||||
1 posix_memalign(4096,1024)=#1
|
||||
1 calloc(4,42)=#3
|
||||
1 free(#2)
|
||||
1 realloc(#3,84)=#2
|
||||
1 aligned_alloc(512,1024)=#3
|
||||
1 memalign(512,1024)=#4
|
||||
1 valloc(1024)=#5
|
||||
1 jemalloc_stats()
|
||||
1 free(#5)
|
||||
1 free(#4)
|
||||
1 free(#3)
|
||||
1 free(#2)
|
||||
1 free(#1)
|
|
@ -4,6 +4,8 @@
|
|||
# License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
DIRS += ['logalloc']
|
||||
|
||||
# Build jemalloc3 as a replace-malloc lib when building with mozjemalloc
|
||||
if not CONFIG['MOZ_JEMALLOC3']:
|
||||
DIRS += ['jemalloc']
|
||||
|
|
|
@ -23,31 +23,7 @@ endif
|
|||
|
||||
endif
|
||||
|
||||
ifeq (Darwin_1,$(OS_TARGET)_$(MOZ_REPLACE_MALLOC))
|
||||
OS_LDFLAGS += \
|
||||
-Wl,-U,_replace_init \
|
||||
-Wl,-U,_replace_malloc \
|
||||
-Wl,-U,_replace_posix_memalign \
|
||||
-Wl,-U,_replace_aligned_alloc \
|
||||
-Wl,-U,_replace_calloc \
|
||||
-Wl,-U,_replace_realloc \
|
||||
-Wl,-U,_replace_free \
|
||||
-Wl,-U,_replace_memalign \
|
||||
-Wl,-U,_replace_valloc \
|
||||
-Wl,-U,_replace_malloc_usable_size \
|
||||
-Wl,-U,_replace_malloc_good_size \
|
||||
-Wl,-U,_replace_jemalloc_stats \
|
||||
-Wl,-U,_replace_jemalloc_purge_freed_pages \
|
||||
-Wl,-U,_replace_jemalloc_free_dirty_pages \
|
||||
$(NULL)
|
||||
|
||||
ifneq ($(MOZ_REPLACE_MALLOC_LINKAGE),compiler support)
|
||||
OS_LDFLAGS += -flat_namespace
|
||||
endif
|
||||
ifeq ($(MOZ_REPLACE_MALLOC_LINKAGE),dummy library)
|
||||
OS_LDFLAGS += -Wl,-weak_library,$(DEPTH)/memory/replace/dummy/$(DLL_PREFIX)replace_malloc$(DLL_SUFFIX)
|
||||
endif
|
||||
endif
|
||||
include $(topsrcdir)/mozglue/build/replace_malloc.mk
|
||||
|
||||
ifdef MOZ_LINKER
|
||||
ifeq (arm, $(TARGET_CPU))
|
||||
|
|
|
@ -0,0 +1,29 @@
|
|||
# This Source Code Form is subject to the terms of the Mozilla Public
|
||||
# License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
ifeq (Darwin_1,$(OS_TARGET)_$(MOZ_REPLACE_MALLOC))
|
||||
OS_LDFLAGS += \
|
||||
-Wl,-U,_replace_init \
|
||||
-Wl,-U,_replace_malloc \
|
||||
-Wl,-U,_replace_posix_memalign \
|
||||
-Wl,-U,_replace_aligned_alloc \
|
||||
-Wl,-U,_replace_calloc \
|
||||
-Wl,-U,_replace_realloc \
|
||||
-Wl,-U,_replace_free \
|
||||
-Wl,-U,_replace_memalign \
|
||||
-Wl,-U,_replace_valloc \
|
||||
-Wl,-U,_replace_malloc_usable_size \
|
||||
-Wl,-U,_replace_malloc_good_size \
|
||||
-Wl,-U,_replace_jemalloc_stats \
|
||||
-Wl,-U,_replace_jemalloc_purge_freed_pages \
|
||||
-Wl,-U,_replace_jemalloc_free_dirty_pages \
|
||||
$(NULL)
|
||||
|
||||
ifneq ($(MOZ_REPLACE_MALLOC_LINKAGE),compiler support)
|
||||
OS_LDFLAGS += -flat_namespace
|
||||
endif
|
||||
ifeq ($(MOZ_REPLACE_MALLOC_LINKAGE),dummy library)
|
||||
OS_LDFLAGS += -Wl,-weak_library,$(DEPTH)/memory/replace/dummy/$(DLL_PREFIX)replace_malloc$(DLL_SUFFIX)
|
||||
endif
|
||||
endif
|
Загрузка…
Ссылка в новой задаче