summaryrefslogtreecommitdiffstats
path: root/tools/memory-profiler
diff options
context:
space:
mode:
Diffstat (limited to 'tools/memory-profiler')
-rw-r--r--tools/memory-profiler/CompactTraceTable.h116
-rw-r--r--tools/memory-profiler/GCHeapProfilerImpl.cpp168
-rw-r--r--tools/memory-profiler/GCHeapProfilerImpl.h53
-rw-r--r--tools/memory-profiler/MemoryProfiler.cpp324
-rw-r--r--tools/memory-profiler/MemoryProfiler.h159
-rw-r--r--tools/memory-profiler/NativeProfilerImpl.cpp82
-rw-r--r--tools/memory-profiler/NativeProfilerImpl.h43
-rw-r--r--tools/memory-profiler/UncensoredAllocator.cpp121
-rw-r--r--tools/memory-profiler/UncensoredAllocator.h48
-rw-r--r--tools/memory-profiler/moz.build29
-rw-r--r--tools/memory-profiler/nsIMemoryProfiler.idl72
-rw-r--r--tools/memory-profiler/nsMemoryProfilerFactory.cpp32
12 files changed, 1247 insertions, 0 deletions
diff --git a/tools/memory-profiler/CompactTraceTable.h b/tools/memory-profiler/CompactTraceTable.h
new file mode 100644
index 000000000..5a6bd7e5c
--- /dev/null
+++ b/tools/memory-profiler/CompactTraceTable.h
@@ -0,0 +1,116 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=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 memory_profiler_CompactTraceTable_h
+#define memory_profiler_CompactTraceTable_h
+
+#include "mozilla/HashFunctions.h"
+
+#include "nsDataHashtable.h"
+#include "nsTArray.h"
+
+namespace mozilla {
+
+struct TrieNode final
+{
+ uint32_t parentIdx;
+ uint32_t nameIdx;
+ bool operator==(const TrieNode t) const
+ {
+ return parentIdx == t.parentIdx && nameIdx == t.nameIdx;
+ }
+ uint32_t Hash() const
+ {
+ return HashGeneric(parentIdx, nameIdx);
+ }
+};
+
+// This class maps a Node of type T to its parent's index in the
+// map. When serializing, the map is traversed and put into an ordered
+// array of Nodes.
+template<typename KeyClass, typename T>
+class NodeIndexMap final
+{
+public:
+ uint32_t Insert(const T& e)
+ {
+ uint32_t index = mMap.Count();
+ if (!mMap.Get(e, &index)) {
+ mMap.Put(e, index);
+ }
+ return index;
+ }
+
+ nsTArray<T> Serialize() const
+ {
+ nsTArray<T> v;
+ v.SetLength(mMap.Count());
+ for (auto iter = mMap.ConstIter(); !iter.Done(); iter.Next()) {
+ v[iter.Data()] = iter.Key();
+ }
+ return v;
+ }
+
+ uint32_t Size() const
+ {
+ return mMap.Count();
+ }
+
+ void Clear()
+ {
+ mMap.Clear();
+ }
+private:
+ nsDataHashtable<KeyClass, uint32_t> mMap;
+};
+
+// Backtraces are stored in a trie to save spaces.
+// Function names are stored in an unique table and TrieNodes contain indexes
+// into that table.
+// The trie is implemented with a hash table; children are stored in
+// traces[TrieNode{parent node index, branch/function name index}].
+class CompactTraceTable final
+{
+public:
+ CompactTraceTable()
+ {
+ mNames.Insert(nsAutoCString("(unknown)"));
+ mTraces.Insert(TrieNode{0, 0});
+ }
+
+ nsTArray<nsCString> GetNames() const
+ {
+ return mNames.Serialize();
+ }
+
+ nsTArray<TrieNode> GetTraces() const
+ {
+ return mTraces.Serialize();
+ }
+
+ // Returns an ID to a stacktrace.
+ uint32_t Insert(const nsTArray<nsCString>& aRawStacktrace)
+ {
+ uint32_t parent = 0;
+ for (auto& frame: aRawStacktrace) {
+ parent = mTraces.Insert(TrieNode{parent, mNames.Insert(frame)});
+ }
+ return parent;
+ }
+
+ void Reset()
+ {
+ mNames.Clear();
+ mTraces.Clear();
+ }
+private:
+ NodeIndexMap<nsCStringHashKey, nsCString> mNames;
+ NodeIndexMap<nsGenericHashKey<TrieNode>, TrieNode> mTraces;
+};
+
+} // namespace mozilla
+
+#endif // memory_profiler_CompactTraceTable_h
diff --git a/tools/memory-profiler/GCHeapProfilerImpl.cpp b/tools/memory-profiler/GCHeapProfilerImpl.cpp
new file mode 100644
index 000000000..528a05501
--- /dev/null
+++ b/tools/memory-profiler/GCHeapProfilerImpl.cpp
@@ -0,0 +1,168 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=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 "GCHeapProfilerImpl.h"
+
+#include "UncensoredAllocator.h"
+
+namespace mozilla {
+
+GCHeapProfilerImpl::GCHeapProfilerImpl()
+{
+ mLock = PR_NewLock();
+ mMarking = false;
+}
+
+GCHeapProfilerImpl::~GCHeapProfilerImpl()
+{
+ if (mLock) {
+ PR_DestroyLock(mLock);
+ }
+}
+
+nsTArray<nsCString>
+GCHeapProfilerImpl::GetNames() const
+{
+ return mTraceTable.GetNames();
+}
+
+nsTArray<TrieNode>
+GCHeapProfilerImpl::GetTraces() const
+{
+ return mTraceTable.GetTraces();
+}
+
+const nsTArray<AllocEvent>&
+GCHeapProfilerImpl::GetEvents() const
+{
+ return mAllocEvents;
+}
+
+void
+GCHeapProfilerImpl::reset()
+{
+ mTraceTable.Reset();
+ mAllocEvents.Clear();
+ mNurseryEntries.Clear();
+ mTenuredEntriesFG.Clear();
+ mTenuredEntriesBG.Clear();
+}
+
+void
+GCHeapProfilerImpl::sampleTenured(void* addr, uint32_t size)
+{
+ SampleInternal(addr, size, mTenuredEntriesFG);
+}
+
+void
+GCHeapProfilerImpl::sampleNursery(void* addr, uint32_t size)
+{
+ SampleInternal(addr, size, mNurseryEntries);
+}
+
+void
+GCHeapProfilerImpl::markTenuredStart()
+{
+ AutoUseUncensoredAllocator ua;
+ AutoMPLock lock(mLock);
+ if (!mMarking) {
+ mMarking = true;
+ mTenuredEntriesFG.SwapElements(mTenuredEntriesBG);
+ MOZ_ASSERT(mTenuredEntriesFG.Count() == 0);
+ }
+}
+
+void
+GCHeapProfilerImpl::markTenured(void* addr)
+{
+ AutoUseUncensoredAllocator ua;
+ AutoMPLock lock(mLock);
+ if (mMarking) {
+ AllocEntry entry;
+ if (mTenuredEntriesBG.Get(addr, &entry)) {
+ entry.mMarked = true;
+ mTenuredEntriesBG.Put(addr, entry);
+ }
+ }
+}
+
+void
+GCHeapProfilerImpl::sweepTenured()
+{
+ AutoUseUncensoredAllocator ua;
+ AutoMPLock lock(mLock);
+ if (mMarking) {
+ mMarking = false;
+ for (auto iter = mTenuredEntriesBG.Iter(); !iter.Done(); iter.Next()) {
+ if (iter.Data().mMarked) {
+ iter.Data().mMarked = false;
+ mTenuredEntriesFG.Put(iter.Key(), iter.Data());
+ } else {
+ AllocEvent& oldEvent = mAllocEvents[iter.Data().mEventIdx];
+ AllocEvent newEvent(oldEvent.mTraceIdx, -oldEvent.mSize, TimeStamp::Now());
+ mAllocEvents.AppendElement(newEvent);
+ }
+ }
+ mTenuredEntriesBG.Clear();
+ }
+}
+
+void
+GCHeapProfilerImpl::sweepNursery()
+{
+ AutoUseUncensoredAllocator ua;
+ AutoMPLock lock(mLock);
+ for (auto iter = mNurseryEntries.Iter(); !iter.Done(); iter.Next()) {
+ AllocEvent& oldEvent = mAllocEvents[iter.Data().mEventIdx];
+ AllocEvent newEvent(oldEvent.mTraceIdx, -oldEvent.mSize, TimeStamp::Now());
+ mAllocEvents.AppendElement(newEvent);
+ }
+ mNurseryEntries.Clear();
+}
+
+void
+GCHeapProfilerImpl::moveNurseryToTenured(void* addrOld, void* addrNew)
+{
+ AutoUseUncensoredAllocator ua;
+ AutoMPLock lock(mLock);
+ AllocEntry entryOld;
+ if (!mNurseryEntries.Get(addrOld, &entryOld)) {
+ return;
+ }
+
+ // Because the tenured heap is sampled, the address might already be there.
+ // If not, the address is inserted with the old event.
+ AllocEntry tenuredEntryOld;
+ if (!mTenuredEntriesFG.Get(addrNew, &tenuredEntryOld)) {
+ mTenuredEntriesFG.Put(addrNew, AllocEntry(entryOld.mEventIdx));
+ } else {
+ // If it is already inserted, the insertion above will fail and the
+ // iterator of the already-inserted element is returned.
+ // We choose to ignore the the new event by setting its size zero and point
+ // the newly allocated address to the old event.
+ // An event of size zero will be skipped when reporting.
+ mAllocEvents[entryOld.mEventIdx].mSize = 0;
+ tenuredEntryOld.mEventIdx = entryOld.mEventIdx;
+ mTenuredEntriesFG.Put(addrNew, tenuredEntryOld);
+ }
+ mNurseryEntries.Remove(addrOld);
+}
+
+void
+GCHeapProfilerImpl::SampleInternal(void* aAddr, uint32_t aSize, AllocMap& aTable)
+{
+ AutoUseUncensoredAllocator ua;
+ AutoMPLock lock(mLock);
+ size_t nSamples = AddBytesSampled(aSize);
+ if (nSamples > 0) {
+ nsTArray<nsCString> trace = GetStacktrace();
+ AllocEvent ai(mTraceTable.Insert(trace), nSamples * mSampleSize, TimeStamp::Now());
+ aTable.Put(aAddr, AllocEntry(mAllocEvents.Length()));
+ mAllocEvents.AppendElement(ai);
+ }
+}
+
+} // namespace mozilla
diff --git a/tools/memory-profiler/GCHeapProfilerImpl.h b/tools/memory-profiler/GCHeapProfilerImpl.h
new file mode 100644
index 000000000..e84e97017
--- /dev/null
+++ b/tools/memory-profiler/GCHeapProfilerImpl.h
@@ -0,0 +1,53 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=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 memory_profiler_GCHeapProfilerImpl_h
+#define memory_profiler_GCHeapProfilerImpl_h
+
+#include "CompactTraceTable.h"
+#include "MemoryProfiler.h"
+
+#include "jsfriendapi.h"
+
+namespace mozilla {
+
+class GCHeapProfilerImpl final : public GCHeapProfiler
+ , public ProfilerImpl
+{
+public:
+ GCHeapProfilerImpl();
+ ~GCHeapProfilerImpl() override;
+
+ nsTArray<nsCString> GetNames() const override;
+ nsTArray<TrieNode> GetTraces() const override;
+ const nsTArray<AllocEvent>& GetEvents() const override;
+
+ void reset() override;
+ void sampleTenured(void* addr, uint32_t size) override;
+ void sampleNursery(void* addr, uint32_t size) override;
+ void markTenuredStart() override;
+ void markTenured(void* addr) override;
+ void sweepTenured() override;
+ void sweepNursery() override;
+ void moveNurseryToTenured(void* addrOld, void* addrNew) override;
+
+private:
+ void SampleInternal(void* addr, uint32_t size, AllocMap& table);
+
+ PRLock* mLock;
+ bool mMarking;
+
+ AllocMap mNurseryEntries;
+ AllocMap mTenuredEntriesFG;
+ AllocMap mTenuredEntriesBG;
+
+ nsTArray<AllocEvent> mAllocEvents;
+ CompactTraceTable mTraceTable;
+};
+
+} // namespace mozilla
+
+#endif // memory_profiler_GCHeapProfilerImpl_h
diff --git a/tools/memory-profiler/MemoryProfiler.cpp b/tools/memory-profiler/MemoryProfiler.cpp
new file mode 100644
index 000000000..c2b8cbd1d
--- /dev/null
+++ b/tools/memory-profiler/MemoryProfiler.cpp
@@ -0,0 +1,324 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=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 "MemoryProfiler.h"
+
+#include <cmath>
+#include <cstdlib>
+
+#include "mozilla/ClearOnShutdown.h"
+#include "mozilla/Move.h"
+#include "mozilla/TimeStamp.h"
+#include "mozilla/UniquePtr.h"
+
+#include "GCHeapProfilerImpl.h"
+#include "GeckoProfiler.h"
+#include "NativeProfilerImpl.h"
+#include "UncensoredAllocator.h"
+#include "js/TypeDecls.h"
+#include "jsfriendapi.h"
+#include "nsIDOMClassInfo.h"
+#include "nsIGlobalObject.h"
+#include "prtime.h"
+#include "xpcprivate.h"
+
+namespace mozilla {
+
+#define MEMORY_PROFILER_SAMPLE_SIZE 65536
+#define BACKTRACE_BUFFER_SIZE 16384
+
+ProfilerImpl::ProfilerImpl()
+ : mSampleSize(MEMORY_PROFILER_SAMPLE_SIZE)
+{
+ mLog1minusP = std::log(1.0 - 1.0 / mSampleSize);
+ mRemainingBytes = std::floor(std::log(1.0 - DRandom()) / mLog1minusP);
+}
+
+nsTArray<nsCString>
+ProfilerImpl::GetStacktrace()
+{
+ nsTArray<nsCString> trace;
+ auto output = MakeUnique<char[]>(BACKTRACE_BUFFER_SIZE);
+
+ profiler_get_backtrace_noalloc(output.get(), BACKTRACE_BUFFER_SIZE);
+ for (const char* p = output.get(); *p; p += strlen(p) + 1) {
+ trace.AppendElement()->Assign(p);
+ }
+
+ return trace;
+}
+
+// Generate a random number in [0, 1).
+double
+ProfilerImpl::DRandom()
+{
+ return double(((uint64_t(std::rand()) & ((1 << 26) - 1)) << 27) +
+ (uint64_t(std::rand()) & ((1 << 27) - 1)))
+ / (uint64_t(1) << 53);
+}
+
+size_t
+ProfilerImpl::AddBytesSampled(uint32_t aBytes)
+{
+ size_t nSamples = 0;
+ while (mRemainingBytes <= aBytes) {
+ mRemainingBytes += std::floor(std::log(1.0 - DRandom()) / mLog1minusP);
+ nSamples++;
+ }
+ mRemainingBytes -= aBytes;
+ return nSamples;
+}
+
+NS_IMPL_ISUPPORTS(MemoryProfiler, nsIMemoryProfiler)
+
+PRLock* MemoryProfiler::sLock;
+uint32_t MemoryProfiler::sProfileContextCount;
+StaticAutoPtr<NativeProfilerImpl> MemoryProfiler::sNativeProfiler;
+StaticAutoPtr<JSContextProfilerMap> MemoryProfiler::sJSContextProfilerMap;
+TimeStamp MemoryProfiler::sStartTime;
+
+void
+MemoryProfiler::InitOnce()
+{
+ MOZ_ASSERT(NS_IsMainThread());
+
+ static bool initialized = false;
+
+ if (!initialized) {
+ MallocHook::Initialize();
+ sLock = PR_NewLock();
+ sProfileContextCount = 0;
+ sJSContextProfilerMap = new JSContextProfilerMap();
+ ClearOnShutdown(&sJSContextProfilerMap);
+ ClearOnShutdown(&sNativeProfiler);
+ std::srand(PR_Now());
+ bool ignored;
+ sStartTime = TimeStamp::ProcessCreation(ignored);
+ initialized = true;
+ }
+}
+
+NS_IMETHODIMP
+MemoryProfiler::StartProfiler()
+{
+ InitOnce();
+ AutoUseUncensoredAllocator ua;
+ AutoMPLock lock(sLock);
+ JSContext* context = XPCJSContext::Get()->Context();
+ ProfilerForJSContext profiler;
+ if (!sJSContextProfilerMap->Get(context, &profiler) ||
+ !profiler.mEnabled) {
+ if (sProfileContextCount == 0) {
+ js::EnableContextProfilingStack(context, true);
+ if (!sNativeProfiler) {
+ sNativeProfiler = new NativeProfilerImpl();
+ }
+ MemProfiler::SetNativeProfiler(sNativeProfiler);
+ }
+ GCHeapProfilerImpl* gp = new GCHeapProfilerImpl();
+ profiler.mEnabled = true;
+ profiler.mProfiler = gp;
+ sJSContextProfilerMap->Put(context, profiler);
+ MemProfiler::GetMemProfiler(context)->start(gp);
+ if (sProfileContextCount == 0) {
+ MallocHook::Enable(sNativeProfiler);
+ }
+ sProfileContextCount++;
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+MemoryProfiler::StopProfiler()
+{
+ InitOnce();
+ AutoUseUncensoredAllocator ua;
+ AutoMPLock lock(sLock);
+ JSContext* context = XPCJSContext::Get()->Context();
+ ProfilerForJSContext profiler;
+ if (sJSContextProfilerMap->Get(context, &profiler) &&
+ profiler.mEnabled) {
+ MemProfiler::GetMemProfiler(context)->stop();
+ if (--sProfileContextCount == 0) {
+ MallocHook::Disable();
+ MemProfiler::SetNativeProfiler(nullptr);
+ js::EnableContextProfilingStack(context, false);
+ }
+ profiler.mEnabled = false;
+ sJSContextProfilerMap->Put(context, profiler);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+MemoryProfiler::ResetProfiler()
+{
+ InitOnce();
+ AutoUseUncensoredAllocator ua;
+ AutoMPLock lock(sLock);
+ JSContext* context = XPCJSContext::Get()->Context();
+ ProfilerForJSContext profiler;
+ if (!sJSContextProfilerMap->Get(context, &profiler) ||
+ !profiler.mEnabled) {
+ delete profiler.mProfiler;
+ profiler.mProfiler = nullptr;
+ sJSContextProfilerMap->Put(context, profiler);
+ }
+ if (sProfileContextCount == 0) {
+ sNativeProfiler = nullptr;
+ }
+ return NS_OK;
+}
+
+struct MergedTraces
+{
+ nsTArray<nsCString> mNames;
+ nsTArray<TrieNode> mTraces;
+ nsTArray<AllocEvent> mEvents;
+};
+
+// Merge events and corresponding traces and names.
+static MergedTraces
+MergeResults(const nsTArray<nsCString>& names0,
+ const nsTArray<TrieNode>& traces0,
+ const nsTArray<AllocEvent>& events0,
+ const nsTArray<nsCString>& names1,
+ const nsTArray<TrieNode>& traces1,
+ const nsTArray<AllocEvent>& events1)
+{
+ NodeIndexMap<nsCStringHashKey, nsCString> names;
+ NodeIndexMap<nsGenericHashKey<TrieNode>, TrieNode> traces;
+ nsTArray<AllocEvent> events;
+
+ nsTArray<size_t> names1Tonames0(names1.Length());
+ nsTArray<size_t> traces1Totraces0(traces1.Length());
+
+ // Merge names.
+ for (auto& i: names0) {
+ names.Insert(i);
+ }
+ for (auto& i: names1) {
+ names1Tonames0.AppendElement(names.Insert(i));
+ }
+
+ // Merge traces. Note that traces1[i].parentIdx < i for all i > 0.
+ for (auto& i: traces0) {
+ traces.Insert(i);
+ }
+ traces1Totraces0.AppendElement(0);
+ for (size_t i = 1; i < traces1.Length(); i++) {
+ TrieNode node = traces1[i];
+ node.parentIdx = traces1Totraces0[node.parentIdx];
+ node.nameIdx = names1Tonames0[node.nameIdx];
+ traces1Totraces0.AppendElement(traces.Insert(node));
+ }
+
+ // Merge the events according to timestamps.
+ auto p0 = events0.begin();
+ auto p1 = events1.begin();
+
+ while (p0 != events0.end() && p1 != events1.end()) {
+ if (p0->mTimestamp < p1->mTimestamp) {
+ events.AppendElement(*p0++);
+ } else {
+ events.AppendElement(*p1++);
+ events.LastElement().mTraceIdx =
+ traces1Totraces0[events.LastElement().mTraceIdx];
+ }
+ }
+
+ while (p0 != events0.end()) {
+ events.AppendElement(*p0++);
+ }
+
+ while (p1 != events1.end()) {
+ events.AppendElement(*p1++);
+ events.LastElement().mTraceIdx =
+ traces1Totraces0[events.LastElement().mTraceIdx];
+ }
+
+ return MergedTraces{names.Serialize(), traces.Serialize(), Move(events)};
+}
+
+NS_IMETHODIMP
+MemoryProfiler::GetResults(JSContext* cx, JS::MutableHandle<JS::Value> aResult)
+{
+ InitOnce();
+ AutoUseUncensoredAllocator ua;
+ AutoMPLock lock(sLock);
+ JSContext* context = XPCJSContext::Get()->Context();
+ // Getting results when the profiler is running is not allowed.
+ if (sProfileContextCount > 0) {
+ return NS_OK;
+ }
+ // Return immediately when native profiler does not exist.
+ if (!sNativeProfiler) {
+ return NS_OK;
+ }
+ // Return immediately when there's no result in current context.
+ ProfilerForJSContext profiler;
+ if (!sJSContextProfilerMap->Get(context, &profiler) ||
+ !profiler.mProfiler) {
+ return NS_OK;
+ }
+ GCHeapProfilerImpl* gp = profiler.mProfiler;
+
+ auto results = MergeResults(gp->GetNames(), gp->GetTraces(), gp->GetEvents(),
+ sNativeProfiler->GetNames(),
+ sNativeProfiler->GetTraces(),
+ sNativeProfiler->GetEvents());
+ const nsTArray<nsCString>& names = results.mNames;
+ const nsTArray<TrieNode>& traces = results.mTraces;
+ const nsTArray<AllocEvent>& events = results.mEvents;
+
+ JS::RootedObject jsnames(cx, JS_NewArrayObject(cx, names.Length()));
+ JS::RootedObject jstraces(cx, JS_NewArrayObject(cx, traces.Length()));
+ JS::RootedObject jsevents(cx, JS_NewArrayObject(cx, events.Length()));
+
+ for (size_t i = 0; i < names.Length(); i++) {
+ JS::RootedString name(cx, JS_NewStringCopyZ(cx, names[i].get()));
+ JS_SetElement(cx, jsnames, i, name);
+ }
+
+ for (size_t i = 0; i < traces.Length(); i++) {
+ JS::RootedObject tn(cx, JS_NewPlainObject(cx));
+ JS::RootedValue nameIdx(cx, JS_NumberValue(traces[i].nameIdx));
+ JS::RootedValue parentIdx(cx, JS_NumberValue(traces[i].parentIdx));
+ JS_SetProperty(cx, tn, "nameIdx", nameIdx);
+ JS_SetProperty(cx, tn, "parentIdx", parentIdx);
+ JS_SetElement(cx, jstraces, i, tn);
+ }
+
+ int i = 0;
+ for (auto ent: events) {
+ if (ent.mSize == 0) {
+ continue;
+ }
+ MOZ_ASSERT(!sStartTime.IsNull());
+ double time = (ent.mTimestamp - sStartTime).ToMilliseconds();
+ JS::RootedObject tn(cx, JS_NewPlainObject(cx));
+ JS::RootedValue size(cx, JS_NumberValue(ent.mSize));
+ JS::RootedValue traceIdx(cx, JS_NumberValue(ent.mTraceIdx));
+ JS::RootedValue timestamp(cx, JS_NumberValue(time));
+ JS_SetProperty(cx, tn, "size", size);
+ JS_SetProperty(cx, tn, "traceIdx", traceIdx);
+ JS_SetProperty(cx, tn, "timestamp", timestamp);
+ JS_SetElement(cx, jsevents, i++, tn);
+ }
+ JS_SetArrayLength(cx, jsevents, i);
+
+ JS::RootedObject result(cx, JS_NewPlainObject(cx));
+ JS::RootedValue objnames(cx, ObjectOrNullValue(jsnames));
+ JS_SetProperty(cx, result, "names", objnames);
+ JS::RootedValue objtraces(cx, ObjectOrNullValue(jstraces));
+ JS_SetProperty(cx, result, "traces", objtraces);
+ JS::RootedValue objevents(cx, ObjectOrNullValue(jsevents));
+ JS_SetProperty(cx, result, "events", objevents);
+ aResult.setObject(*result);
+ return NS_OK;
+}
+
+} // namespace mozilla
diff --git a/tools/memory-profiler/MemoryProfiler.h b/tools/memory-profiler/MemoryProfiler.h
new file mode 100644
index 000000000..85a378fb2
--- /dev/null
+++ b/tools/memory-profiler/MemoryProfiler.h
@@ -0,0 +1,159 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=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 tools_profiler_MemoryProfiler_h
+#define tools_profiler_MemoryProfiler_h
+
+#include "nsIMemoryProfiler.h"
+
+#include "mozilla/StaticPtr.h"
+#include "mozilla/TimeStamp.h"
+
+#include "CompactTraceTable.h"
+#include "nsTArray.h"
+#include "prlock.h"
+
+#define MEMORY_PROFILER_CID \
+ { 0xf976eaa2, 0xcc1f, 0x47ee, \
+ { 0x81, 0x29, 0xb8, 0x26, 0x2a, 0x3d, 0xb6, 0xb2 } }
+
+#define MEMORY_PROFILER_CONTRACT_ID "@mozilla.org/tools/memory-profiler;1"
+
+struct PRLock;
+
+namespace mozilla {
+
+class NativeProfilerImpl;
+class GCHeapProfilerImpl;
+
+struct ProfilerForJSContext
+{
+ ProfilerForJSContext()
+ : mProfiler(nullptr)
+ , mEnabled(false)
+ {}
+ GCHeapProfilerImpl* mProfiler;
+ bool mEnabled;
+};
+using JSContextProfilerMap =
+ nsDataHashtable<nsClearingPtrHashKey<JSContext>, ProfilerForJSContext>;
+
+class MemoryProfiler final : public nsIMemoryProfiler
+{
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIMEMORYPROFILER
+
+private:
+ static void InitOnce();
+ ~MemoryProfiler() {}
+
+ // The accesses to other static member are guarded by sLock and
+ // sProfileContextCount.
+ static PRLock* sLock;
+ static uint32_t sProfileContextCount;
+
+ static StaticAutoPtr<NativeProfilerImpl> sNativeProfiler;
+ static StaticAutoPtr<JSContextProfilerMap> sJSContextProfilerMap;
+ static TimeStamp sStartTime;
+};
+
+// Allocation events to be reported.
+struct AllocEvent {
+ TimeStamp mTimestamp;
+ // index to a stacktrace singleton.
+ uint32_t mTraceIdx;
+ // Allocation size
+ int32_t mSize;
+
+ AllocEvent(uint32_t aTraceIdx, int32_t aSize, TimeStamp aTimestamp)
+ : mTimestamp(aTimestamp)
+ , mTraceIdx(aTraceIdx)
+ , mSize(aSize)
+ {}
+};
+
+// Index to allocation events but also a mark bit to be GC-able.
+struct AllocEntry {
+ uint32_t mEventIdx : 31;
+ bool mMarked : 1;
+
+ // Default constructor for uninitialized stack value required by
+ // getter methods.
+ AllocEntry()
+ : mEventIdx(0)
+ , mMarked(false)
+ {}
+ explicit AllocEntry(int aEventIdx)
+ : mEventIdx(aEventIdx)
+ , mMarked(false)
+ {}
+};
+
+using AllocMap = nsDataHashtable<nsClearingVoidPtrHashKey, AllocEntry>;
+
+class ProfilerImpl
+{
+public:
+ static nsTArray<nsCString> GetStacktrace();
+ static double DRandom();
+
+ ProfilerImpl();
+ virtual nsTArray<nsCString> GetNames() const = 0;
+ virtual nsTArray<TrieNode> GetTraces() const = 0;
+ virtual const nsTArray<AllocEvent>& GetEvents() const = 0;
+
+protected:
+ /**
+ * The sampler generates a random variable which conforms to a geometric
+ * distribution of probability p = 1 / mSampleSize to calculate the
+ * next-to-be-sampled byte directly; It avoids rolling a dice on each byte.
+ *
+ * Let Bn denote a Bernoulli process with first success on n-th trial, the
+ * cumulative distribution function of Bn is Cn = 1 - (1 - p) ^ n.
+ * Let U denote a uniformly distributed random variable in [0, 1).
+ * A geometric random variable can be generated by Cn's reverse function:
+ * G = floor(log(1 - U) / log(1 - p)).
+ *
+ * @param aSize the number of bytes seen
+ * @return the number of events sampled
+ */
+ size_t AddBytesSampled(uint32_t aBytes);
+
+ uint32_t mSampleSize;
+
+private:
+ uint32_t mRemainingBytes;
+ double mLog1minusP;
+};
+
+/*
+ * This class is used to make sure the profile data is only accessed
+ * on one thread at a time. Don't use mozilla::Mutex because we don't
+ * want to allocate memory.
+ */
+class AutoMPLock
+{
+public:
+ explicit AutoMPLock(PRLock* aLock)
+ {
+ MOZ_ASSERT(aLock);
+ mLock = aLock;
+ PR_Lock(mLock);
+ }
+
+ ~AutoMPLock()
+ {
+ PR_Unlock(mLock);
+ }
+
+private:
+ PRLock* mLock;
+};
+
+} // namespace mozilla
+
+#endif
diff --git a/tools/memory-profiler/NativeProfilerImpl.cpp b/tools/memory-profiler/NativeProfilerImpl.cpp
new file mode 100644
index 000000000..48c684c72
--- /dev/null
+++ b/tools/memory-profiler/NativeProfilerImpl.cpp
@@ -0,0 +1,82 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=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 "NativeProfilerImpl.h"
+
+#include "UncensoredAllocator.h"
+
+namespace mozilla {
+
+NativeProfilerImpl::NativeProfilerImpl()
+{
+ mLock = PR_NewLock();
+}
+
+NativeProfilerImpl::~NativeProfilerImpl()
+{
+ if (mLock) {
+ PR_DestroyLock(mLock);
+ }
+}
+
+nsTArray<nsCString>
+NativeProfilerImpl::GetNames() const
+{
+ return mTraceTable.GetNames();
+}
+
+nsTArray<TrieNode>
+NativeProfilerImpl::GetTraces() const
+{
+ return mTraceTable.GetTraces();
+}
+
+const nsTArray<AllocEvent>&
+NativeProfilerImpl::GetEvents() const
+{
+ return mAllocEvents;
+}
+
+void
+NativeProfilerImpl::reset()
+{
+ mTraceTable.Reset();
+ mAllocEvents.Clear();
+ mNativeEntries.Clear();
+}
+
+void
+NativeProfilerImpl::sampleNative(void* addr, uint32_t size)
+{
+ AutoUseUncensoredAllocator ua;
+ AutoMPLock lock(mLock);
+ size_t nSamples = AddBytesSampled(size);
+ if (nSamples > 0) {
+ nsTArray<nsCString> trace = GetStacktrace();
+ AllocEvent ai(mTraceTable.Insert(trace), nSamples * mSampleSize, TimeStamp::Now());
+ mNativeEntries.Put(addr, AllocEntry(mAllocEvents.Length()));
+ mAllocEvents.AppendElement(ai);
+ }
+}
+
+void
+NativeProfilerImpl::removeNative(void* addr)
+{
+ AutoUseUncensoredAllocator ua;
+ AutoMPLock lock(mLock);
+
+ AllocEntry entry;
+ if (!mNativeEntries.Get(addr, &entry)) {
+ return;
+ }
+
+ AllocEvent& oldEvent = mAllocEvents[entry.mEventIdx];
+ AllocEvent newEvent(oldEvent.mTraceIdx, -oldEvent.mSize, TimeStamp::Now());
+ mAllocEvents.AppendElement(newEvent);
+ mNativeEntries.Remove(addr);
+}
+
+} // namespace mozilla
diff --git a/tools/memory-profiler/NativeProfilerImpl.h b/tools/memory-profiler/NativeProfilerImpl.h
new file mode 100644
index 000000000..95cc2ec13
--- /dev/null
+++ b/tools/memory-profiler/NativeProfilerImpl.h
@@ -0,0 +1,43 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=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 memory_profiler_NativeProfilerImpl_h
+#define memory_profiler_NativeProfilerImpl_h
+
+#include "CompactTraceTable.h"
+#include "MemoryProfiler.h"
+
+#include "jsfriendapi.h"
+
+struct PRLock;
+
+namespace mozilla {
+
+class NativeProfilerImpl final : public NativeProfiler
+ , public ProfilerImpl
+{
+public:
+ NativeProfilerImpl();
+ ~NativeProfilerImpl() override;
+
+ nsTArray<nsCString> GetNames() const override;
+ nsTArray<TrieNode> GetTraces() const override;
+ const nsTArray<AllocEvent>& GetEvents() const override;
+
+ void reset() override;
+ void sampleNative(void* addr, uint32_t size) override;
+ void removeNative(void* addr) override;
+
+private:
+ PRLock* mLock;
+ AllocMap mNativeEntries;
+ nsTArray<AllocEvent> mAllocEvents;
+ CompactTraceTable mTraceTable;
+};
+
+} // namespace mozilla
+
+#endif // memory_profiler_NativeProfilerImpl_h
diff --git a/tools/memory-profiler/UncensoredAllocator.cpp b/tools/memory-profiler/UncensoredAllocator.cpp
new file mode 100644
index 000000000..92caeb633
--- /dev/null
+++ b/tools/memory-profiler/UncensoredAllocator.cpp
@@ -0,0 +1,121 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=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 "UncensoredAllocator.h"
+
+#include "mozilla/Assertions.h"
+#include "mozilla/Unused.h"
+
+#include "MainThreadUtils.h"
+#include "jsfriendapi.h"
+#include "nsDebug.h"
+#include "prlock.h"
+#ifdef MOZ_REPLACE_MALLOC
+#include "replace_malloc_bridge.h"
+#endif
+
+namespace mozilla {
+
+#ifdef MOZ_REPLACE_MALLOC
+MOZ_THREAD_LOCAL(bool) MallocHook::mEnabledTLS;
+NativeProfiler* MallocHook::mNativeProfiler;
+malloc_hook_table_t MallocHook::mMallocHook;
+#endif
+
+AutoUseUncensoredAllocator::AutoUseUncensoredAllocator()
+{
+#ifdef MOZ_REPLACE_MALLOC
+ MallocHook::mEnabledTLS.set(false);
+#endif
+}
+
+AutoUseUncensoredAllocator::~AutoUseUncensoredAllocator()
+{
+#ifdef MOZ_REPLACE_MALLOC
+ MallocHook::mEnabledTLS.set(true);
+#endif
+}
+
+bool
+MallocHook::Enabled()
+{
+#ifdef MOZ_REPLACE_MALLOC
+ return mEnabledTLS.get() && mNativeProfiler;
+#else
+ return false;
+#endif
+}
+
+void*
+MallocHook::SampleNative(void* aAddr, size_t aSize)
+{
+#ifdef MOZ_REPLACE_MALLOC
+ if (MallocHook::Enabled()) {
+ mNativeProfiler->sampleNative(aAddr, aSize);
+ }
+#endif
+ return aAddr;
+}
+
+void
+MallocHook::RemoveNative(void* aAddr)
+{
+#ifdef MOZ_REPLACE_MALLOC
+ if (MallocHook::Enabled()) {
+ mNativeProfiler->removeNative(aAddr);
+ }
+#endif
+}
+
+void
+MallocHook::Initialize()
+{
+#ifdef MOZ_REPLACE_MALLOC
+ MOZ_ASSERT(NS_IsMainThread());
+ mMallocHook.free_hook = RemoveNative;
+ mMallocHook.malloc_hook = SampleNative;
+ ReplaceMallocBridge* bridge = ReplaceMallocBridge::Get(3);
+ if (bridge) {
+ mozilla::Unused << bridge->RegisterHook("memory-profiler", nullptr, nullptr);
+ }
+
+ bool success = mEnabledTLS.init();
+ if (NS_WARN_IF(!success)) {
+ return;
+ }
+#endif
+}
+
+void
+MallocHook::Enable(NativeProfiler* aNativeProfiler)
+{
+#ifdef MOZ_REPLACE_MALLOC
+ MOZ_ASSERT(NS_IsMainThread());
+ ReplaceMallocBridge* bridge = ReplaceMallocBridge::Get(3);
+ if (bridge) {
+ const malloc_table_t* alloc_funcs =
+ bridge->RegisterHook("memory-profiler", nullptr, &mMallocHook);
+ if (alloc_funcs) {
+ mNativeProfiler = aNativeProfiler;
+ }
+ }
+#endif
+}
+
+void
+MallocHook::Disable()
+{
+#ifdef MOZ_REPLACE_MALLOC
+ MOZ_ASSERT(NS_IsMainThread());
+ ReplaceMallocBridge* bridge = ReplaceMallocBridge::Get(3);
+ if (bridge) {
+ bridge->RegisterHook("memory-profiler", nullptr, nullptr);
+ mNativeProfiler = nullptr;
+ }
+#endif
+}
+
+} // namespace mozilla
diff --git a/tools/memory-profiler/UncensoredAllocator.h b/tools/memory-profiler/UncensoredAllocator.h
new file mode 100644
index 000000000..b9074c738
--- /dev/null
+++ b/tools/memory-profiler/UncensoredAllocator.h
@@ -0,0 +1,48 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=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 memory_profiler_UncensoredAllocator_h
+#define memory_profiler_UncensoredAllocator_h
+
+#include "mozilla/Attributes.h"
+#include "mozilla/ThreadLocal.h"
+
+#ifdef MOZ_REPLACE_MALLOC
+#include "replace_malloc_bridge.h"
+#endif
+
+class NativeProfiler;
+
+namespace mozilla {
+
+class MallocHook final
+{
+public:
+ static void Initialize();
+ static void Enable(NativeProfiler* aNativeProfiler);
+ static void Disable();
+ static bool Enabled();
+private:
+ static void* SampleNative(void* aAddr, size_t aSize);
+ static void RemoveNative(void* aAddr);
+#ifdef MOZ_REPLACE_MALLOC
+ static MOZ_THREAD_LOCAL(bool) mEnabledTLS;
+ static NativeProfiler* mNativeProfiler;
+ static malloc_hook_table_t mMallocHook;
+#endif
+ friend class AutoUseUncensoredAllocator;
+};
+
+class MOZ_RAII AutoUseUncensoredAllocator final
+{
+public:
+ AutoUseUncensoredAllocator();
+ ~AutoUseUncensoredAllocator();
+};
+
+} // namespace mozilla
+
+#endif // memory_profiler_UncensoredAllocator_h
diff --git a/tools/memory-profiler/moz.build b/tools/memory-profiler/moz.build
new file mode 100644
index 000000000..be2fe7432
--- /dev/null
+++ b/tools/memory-profiler/moz.build
@@ -0,0 +1,29 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+if CONFIG['MOZ_ENABLE_PROFILER_SPS']:
+ XPIDL_MODULE = 'memory_profiler'
+ XPIDL_SOURCES += [
+ 'nsIMemoryProfiler.idl',
+ ]
+
+ UNIFIED_SOURCES += [
+ 'GCHeapProfilerImpl.cpp',
+ 'MemoryProfiler.cpp',
+ 'NativeProfilerImpl.cpp',
+ 'nsMemoryProfilerFactory.cpp',
+ 'UncensoredAllocator.cpp',
+ ]
+
+ LOCAL_INCLUDES += [
+ '/js/xpconnect/src',
+ '/xpcom/base',
+ ]
+
+ FINAL_LIBRARY = 'xul'
+
+if CONFIG['GNU_CXX']:
+ CXXFLAGS += ['-Wno-error=shadow']
diff --git a/tools/memory-profiler/nsIMemoryProfiler.idl b/tools/memory-profiler/nsIMemoryProfiler.idl
new file mode 100644
index 000000000..4ca386f9d
--- /dev/null
+++ b/tools/memory-profiler/nsIMemoryProfiler.idl
@@ -0,0 +1,72 @@
+/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+/**
+ * The memory profiler samples allocation events. An allocation event
+ * includes a type (what and at where is going to be allocated), a
+ * size, a timestamp and the corresponding stack trace. Free events
+ * are also tracked. For managed languages, namely languages relying
+ * on garbage collection, a free event is generated when an object is
+ * reclaimed by the garbage collector. These sampled events can be
+ * used to approximate the full history of allocations afterwards.
+ * That means we can get various memory profiles of a program in
+ * different perspectives by post-processing the history in different
+ * ways. The profiler is designed at the very beginning to support not
+ * only JavaScript but also native codes. Naturally, not only
+ * JavaScript objects but also native allocations are tracked.
+ *
+ * The result returned is the sampled allocation event traces in a
+ * compact format. The events is sorted according to the timestamp
+ * when the event happened. Each event has a trace index pointing to
+ * the traces table. Each trace entry has a name index pointing to the
+ * names table and a parent index pointing to the parent trace in the
+ * traces table. By following the trace index one could rebuild the
+ * complete backtrace of an allocation event.
+ *
+ * [ Events ]
+ * +-------+-------+ +-------+
+ * | Size | Size | | Size |
+ * |-------|-------| |-------|
+ * | Time | Time |......| Time |
+ * |-------|-------| |-------|
+ * +-- Trace | Trace | | Trace |
+ * | +-------+-------+ +-------+
+ * |
+ * | [ Traces ]
+ * +->--------+--------+ +--------+ +--------+
+ * -| Name | Name | | Name | | Name |
+ * / |--------|--------|...|--------|...|--------|
+ * | | Parent | Parent | | Parent | | Parent |
+ * | +---|----+----^--++ +--^--|--+ +---^----+
+ * | | | | | | |
+ * | +---------+ +-------+ +----------+
+ * | [ Names ]
+ * | +-----------------+-----------------+
+ * +-> Function name | Function name |
+ * | & line numbers | & line numbers |......
+ * +-----------------+-----------------+
+ *
+ */
+[scriptable, uuid(1e10e7a9-bc05-4878-a687-36c9ea4428b1)]
+interface nsIMemoryProfiler : nsISupports
+{
+ void startProfiler();
+ void stopProfiler();
+ void resetProfiler();
+
+ /**
+ * Get results in an object which contains three tables:
+ * {
+ * names, // an array of function names and positions
+ * traces, // an array of {nameIdx, parentIdx}
+ * events, // an array of {size, timestamp, traceIdx}
+ * }
+ * Should only be called after stopProfiler.
+ */
+ [implicit_jscontext]
+ jsval getResults();
+};
diff --git a/tools/memory-profiler/nsMemoryProfilerFactory.cpp b/tools/memory-profiler/nsMemoryProfilerFactory.cpp
new file mode 100644
index 000000000..b962a6604
--- /dev/null
+++ b/tools/memory-profiler/nsMemoryProfilerFactory.cpp
@@ -0,0 +1,32 @@
+/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/ModuleUtils.h"
+#include "nsCOMPtr.h"
+#include "MemoryProfiler.h"
+
+using mozilla::MemoryProfiler;
+
+NS_GENERIC_FACTORY_CONSTRUCTOR(MemoryProfiler)
+
+NS_DEFINE_NAMED_CID(MEMORY_PROFILER_CID);
+
+static const mozilla::Module::CIDEntry kMemoryProfilerCIDs[] = {
+ { &kMEMORY_PROFILER_CID, false, nullptr, MemoryProfilerConstructor },
+ { nullptr }
+};
+
+static const mozilla::Module::ContractIDEntry kMemoryProfilerContracts[] = {
+ { MEMORY_PROFILER_CONTRACT_ID, &kMEMORY_PROFILER_CID },
+ { nullptr }
+};
+
+static const mozilla::Module kMemoryProfilerModule = {
+ mozilla::Module::kVersion,
+ kMemoryProfilerCIDs,
+ kMemoryProfilerContracts
+};
+
+NSMODULE_DEFN(nsMemoryProfilerModule) = &kMemoryProfilerModule;