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:
Mike Hommey 2014-10-24 13:08:01 +09:00
Родитель 746f48e4a4
Коммит e25908f6c5
14 изменённых файлов: 1312 добавлений и 25 удалений

Просмотреть файл

@ -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