summaryrefslogtreecommitdiffstats
path: root/memory/replace/logalloc/replay/Replay.cpp
diff options
context:
space:
mode:
authorMatt A. Tobin <mattatobin@localhost.localdomain>2018-02-02 04:16:08 -0500
committerMatt A. Tobin <mattatobin@localhost.localdomain>2018-02-02 04:16:08 -0500
commit5f8de423f190bbb79a62f804151bc24824fa32d8 (patch)
tree10027f336435511475e392454359edea8e25895d /memory/replace/logalloc/replay/Replay.cpp
parent49ee0794b5d912db1f95dce6eb52d781dc210db5 (diff)
downloadUXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar
UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.gz
UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.lz
UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.xz
UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.zip
Add m-esr52 at 52.6.0
Diffstat (limited to 'memory/replace/logalloc/replay/Replay.cpp')
-rw-r--r--memory/replace/logalloc/replay/Replay.cpp558
1 files changed, 558 insertions, 0 deletions
diff --git a/memory/replace/logalloc/replay/Replay.cpp b/memory/replace/logalloc/replay/Replay.cpp
new file mode 100644
index 000000000..30fcd21e5
--- /dev/null
+++ b/memory/replace/logalloc/replay/Replay.cpp
@@ -0,0 +1,558 @@
+/* -*- 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
+#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 "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>
+ explicit 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. */
+ explicit 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:
+ explicit 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_JEMALLOC4)
+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
+
+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) {
+#ifdef _WIN32
+ // See comment in FdPrintf.h as to why native win32 handles are used.
+ mStdErr = reinterpret_cast<intptr_t>(GetStdHandle(STD_ERROR_HANDLE));
+#else
+ mStdErr = fileno(stderr);
+#endif
+ }
+
+ 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 * num;
+ 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(mStdErr,
+ "#%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);
+ }
+
+ intptr_t mStdErr;
+ size_t mOps;
+ MemSlotList mSlots;
+};
+
+
+int
+main()
+{
+ size_t first_pid = 0;
+ FdReader reader(0);
+ Replay replay;
+
+#if defined(_WIN32) && !defined(MOZ_JEMALLOC4)
+ 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;
+ }
+
+ /* The log contains thread ids for manual analysis, but we just ignore them
+ * for now. */
+ parseNumber(line.SplitChar(' '));
+
+ 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;
+}