summaryrefslogtreecommitdiffstats
path: root/tools/memory-profiler/MemoryProfiler.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'tools/memory-profiler/MemoryProfiler.cpp')
-rw-r--r--tools/memory-profiler/MemoryProfiler.cpp324
1 files changed, 324 insertions, 0 deletions
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