/* -*- 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