diff options
Diffstat (limited to 'tools/memory-profiler')
-rw-r--r-- | tools/memory-profiler/CompactTraceTable.h | 116 | ||||
-rw-r--r-- | tools/memory-profiler/GCHeapProfilerImpl.cpp | 168 | ||||
-rw-r--r-- | tools/memory-profiler/GCHeapProfilerImpl.h | 53 | ||||
-rw-r--r-- | tools/memory-profiler/MemoryProfiler.cpp | 324 | ||||
-rw-r--r-- | tools/memory-profiler/MemoryProfiler.h | 159 | ||||
-rw-r--r-- | tools/memory-profiler/NativeProfilerImpl.cpp | 82 | ||||
-rw-r--r-- | tools/memory-profiler/NativeProfilerImpl.h | 43 | ||||
-rw-r--r-- | tools/memory-profiler/UncensoredAllocator.cpp | 121 | ||||
-rw-r--r-- | tools/memory-profiler/UncensoredAllocator.h | 48 | ||||
-rw-r--r-- | tools/memory-profiler/moz.build | 29 | ||||
-rw-r--r-- | tools/memory-profiler/nsIMemoryProfiler.idl | 72 | ||||
-rw-r--r-- | tools/memory-profiler/nsMemoryProfilerFactory.cpp | 32 |
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; |