зеркало из https://github.com/mozilla/gecko-dev.git
2667 строки
74 KiB
C++
2667 строки
74 KiB
C++
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
|
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
|
|
/* This Source Code Form is subject to the terms of the Mozilla Public
|
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
|
|
|
#include <ostream>
|
|
#include <fstream>
|
|
#include <sstream>
|
|
#include <errno.h>
|
|
|
|
#include "platform.h"
|
|
#include "PlatformMacros.h"
|
|
#include "mozilla/ArrayUtils.h"
|
|
#include "mozilla/Atomics.h"
|
|
#include "mozilla/UniquePtr.h"
|
|
#include "mozilla/Vector.h"
|
|
#include "GeckoProfiler.h"
|
|
#include "ProfilerIOInterposeObserver.h"
|
|
#include "mozilla/StackWalk.h"
|
|
#include "mozilla/StaticPtr.h"
|
|
#include "mozilla/ThreadLocal.h"
|
|
#include "mozilla/TimeStamp.h"
|
|
#include "mozilla/Sprintf.h"
|
|
#include "mozilla/StaticPtr.h"
|
|
#include "PseudoStack.h"
|
|
#include "ThreadInfo.h"
|
|
#include "nsIHttpProtocolHandler.h"
|
|
#include "nsIObserverService.h"
|
|
#include "nsIProfileSaveEvent.h"
|
|
#include "nsIXULAppInfo.h"
|
|
#include "nsIXULRuntime.h"
|
|
#include "nsDirectoryServiceUtils.h"
|
|
#include "nsDirectoryServiceDefs.h"
|
|
#include "nsXULAppAPI.h"
|
|
#include "nsProfilerStartParams.h"
|
|
#include "mozilla/Services.h"
|
|
#include "nsThreadUtils.h"
|
|
#include "mozilla/ProfileGatherer.h"
|
|
#include "ProfilerMarkers.h"
|
|
#include "shared-libraries.h"
|
|
|
|
#ifdef MOZ_TASK_TRACER
|
|
#include "GeckoTaskTracer.h"
|
|
#endif
|
|
|
|
#if defined(PROFILE_JAVA)
|
|
# include "FennecJNINatives.h"
|
|
# include "FennecJNIWrappers.h"
|
|
#endif
|
|
|
|
#if defined(MOZ_PROFILING) && \
|
|
(defined(GP_OS_windows) || defined(GP_OS_darwin))
|
|
# define USE_NS_STACKWALK
|
|
#endif
|
|
|
|
// This should also work on ARM Linux, but not tested there yet.
|
|
#if defined(GP_arm_android)
|
|
# define USE_EHABI_STACKWALK
|
|
# include "EHABIStackWalk.h"
|
|
#endif
|
|
|
|
#if defined(GP_PLAT_amd64_linux) || defined(GP_PLAT_x86_linux)
|
|
# define USE_LUL_STACKWALK
|
|
# include "lul/LulMain.h"
|
|
# include "lul/platform-linux-lul.h"
|
|
#endif
|
|
|
|
#ifdef MOZ_VALGRIND
|
|
# include <valgrind/memcheck.h>
|
|
#else
|
|
# define VALGRIND_MAKE_MEM_DEFINED(_addr,_len) ((void)0)
|
|
#endif
|
|
|
|
#if defined(GP_OS_windows)
|
|
typedef CONTEXT tickcontext_t;
|
|
#elif defined(GP_OS_linux) || defined(GP_OS_android)
|
|
#include <ucontext.h>
|
|
typedef ucontext_t tickcontext_t;
|
|
#endif
|
|
|
|
using namespace mozilla;
|
|
|
|
#if defined(PROFILE_JAVA)
|
|
class GeckoJavaSampler : public mozilla::java::GeckoJavaSampler::Natives<GeckoJavaSampler>
|
|
{
|
|
private:
|
|
GeckoJavaSampler();
|
|
|
|
public:
|
|
static double GetProfilerTime() {
|
|
if (!profiler_is_active()) {
|
|
return 0.0;
|
|
}
|
|
return profiler_time();
|
|
};
|
|
};
|
|
#endif
|
|
|
|
MOZ_THREAD_LOCAL(PseudoStack *) tlsPseudoStack;
|
|
|
|
static Sampler* gSampler;
|
|
|
|
static std::vector<ThreadInfo*>* gRegisteredThreads = nullptr;
|
|
static mozilla::StaticMutex gRegisteredThreadsMutex;
|
|
|
|
// All accesses to gGatherer are on the main thread, so no locking is needed.
|
|
static StaticRefPtr<mozilla::ProfileGatherer> gGatherer;
|
|
|
|
// XXX: gBuffer is used on multiple threads -- including via copies in
|
|
// ThreadInfo::mBuffer -- without any apparent synchronization(!)
|
|
static StaticRefPtr<ProfileBuffer> gBuffer;
|
|
|
|
// gThreadNameFilters is accessed from multiple threads. All accesses to it
|
|
// must be guarded by gThreadNameFiltersMutex.
|
|
static Vector<std::string> gThreadNameFilters;
|
|
static mozilla::StaticMutex gThreadNameFiltersMutex;
|
|
|
|
// All accesses to gFeatures are on the main thread, so no locking is needed.
|
|
static Vector<std::string> gFeatures;
|
|
|
|
// All accesses to gEntrySize are on the main thread, so no locking is needed.
|
|
static int gEntrySize = 0;
|
|
|
|
// This variable is set on the main thread in profiler_{start,stop}(), and
|
|
// mostly read on the main thread. There is one read off the main thread in
|
|
// SigprofSender() in platform-linux.cc which is safe because there is implicit
|
|
// synchronization between that function and the set points in
|
|
// profiler_{start,stop}().
|
|
static double gInterval = 0;
|
|
|
|
// XXX: These two variables are used extensively both on and off the main
|
|
// thread. It's possible that code that checks them then unsafely assumes their
|
|
// values don't subsequently change.
|
|
static Atomic<bool> gIsActive(false);
|
|
static Atomic<bool> gIsPaused(false);
|
|
|
|
// We need to track whether we've been initialized otherwise
|
|
// we end up using tlsStack without initializing it.
|
|
// Because tlsStack is totally opaque to us we can't reuse
|
|
// it as the flag itself.
|
|
bool stack_key_initialized;
|
|
|
|
// XXX: This is set by profiler_init() and profiler_start() on the main thread.
|
|
// It is read off the main thread, e.g. by Tick(). It might require more
|
|
// inter-thread synchronization than it currently has.
|
|
mozilla::TimeStamp gStartTime;
|
|
|
|
// XXX: These are accessed by multiple threads and might require more
|
|
// inter-thread synchronization than they currently have.
|
|
static int gFrameNumber = 0;
|
|
static int gLastFrameNumber = 0;
|
|
|
|
static int gInitCount = 0; // Each init must have a matched shutdown.
|
|
|
|
static bool gIsProfiling = false; // is raced on
|
|
|
|
// All accesses to these are on the main thread, so no locking is needed.
|
|
static bool gProfileJava = false;
|
|
static bool gProfileJS = false;
|
|
static bool gTaskTracer = false;
|
|
|
|
// XXX: These are all accessed by multiple threads and might require more
|
|
// inter-thread synchronization than they currently have.
|
|
static Atomic<bool> gAddLeafAddresses(false);
|
|
static Atomic<bool> gDisplayListDump(false);
|
|
static Atomic<bool> gLayersDump(false);
|
|
static Atomic<bool> gProfileGPU(false);
|
|
static Atomic<bool> gProfileMemory(false);
|
|
static Atomic<bool> gProfileRestyle(false);
|
|
static Atomic<bool> gProfileThreads(false);
|
|
static Atomic<bool> gUseStackWalk(false);
|
|
|
|
// Environment variables to control the profiler
|
|
const char* PROFILER_HELP = "MOZ_PROFILER_HELP";
|
|
const char* PROFILER_INTERVAL = "MOZ_PROFILER_INTERVAL";
|
|
const char* PROFILER_ENTRIES = "MOZ_PROFILER_ENTRIES";
|
|
const char* PROFILER_STACK = "MOZ_PROFILER_STACK_SCAN";
|
|
const char* PROFILER_FEATURES = "MOZ_PROFILING_FEATURES";
|
|
|
|
/* we don't need to worry about overflow because we only treat the
|
|
* case of them being the same as special. i.e. we only run into
|
|
* a problem if 2^32 events happen between samples that we need
|
|
* to know are associated with different events */
|
|
|
|
// Values harvested from env vars, that control the profiler.
|
|
static int gUnwindInterval; /* in milliseconds */
|
|
static int gUnwindStackScan; /* max # of dubious frames allowed */
|
|
static int gProfileEntries; /* how many entries do we store? */
|
|
|
|
static mozilla::StaticAutoPtr<mozilla::ProfilerIOInterposeObserver>
|
|
gInterposeObserver;
|
|
|
|
// The name that identifies the gecko thread for calls to
|
|
// profiler_register_thread.
|
|
static const char* gGeckoThreadName = "GeckoMain";
|
|
|
|
static bool
|
|
CanNotifyObservers()
|
|
{
|
|
MOZ_RELEASE_ASSERT(NS_IsMainThread());
|
|
|
|
#if defined(GP_OS_android)
|
|
// Android ANR reporter uses the profiler off the main thread.
|
|
return NS_IsMainThread();
|
|
#else
|
|
return true;
|
|
#endif
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////
|
|
// BEGIN tick/unwinding code
|
|
|
|
// TickSample captures the information collected for each sample.
|
|
class TickSample {
|
|
public:
|
|
TickSample()
|
|
: pc(NULL)
|
|
, sp(NULL)
|
|
, fp(NULL)
|
|
, lr(NULL)
|
|
, context(NULL)
|
|
, isSamplingCurrentThread(false)
|
|
, threadInfo(nullptr)
|
|
, rssMemory(0)
|
|
, ussMemory(0)
|
|
{}
|
|
|
|
void PopulateContext(void* aContext);
|
|
|
|
Address pc; // Instruction pointer.
|
|
Address sp; // Stack pointer.
|
|
Address fp; // Frame pointer.
|
|
Address lr; // ARM link register
|
|
void* context; // The context from the signal handler, if available. On
|
|
// Win32 this may contain the windows thread context.
|
|
bool isSamplingCurrentThread;
|
|
ThreadInfo* threadInfo;
|
|
mozilla::TimeStamp timestamp;
|
|
int64_t rssMemory;
|
|
int64_t ussMemory;
|
|
};
|
|
|
|
static void
|
|
AddDynamicCodeLocationTag(ThreadInfo& aInfo, const char* aStr)
|
|
{
|
|
aInfo.addTag(ProfileEntry::CodeLocation(""));
|
|
|
|
size_t strLen = strlen(aStr) + 1; // +1 for the null terminator
|
|
for (size_t j = 0; j < strLen; ) {
|
|
// Store as many characters in the void* as the platform allows.
|
|
char text[sizeof(void*)];
|
|
size_t len = sizeof(void*) / sizeof(char);
|
|
if (j+len >= strLen) {
|
|
len = strLen - j;
|
|
}
|
|
memcpy(text, &aStr[j], len);
|
|
j += sizeof(void*) / sizeof(char);
|
|
|
|
// Cast to *((void**) to pass the text data to a void*.
|
|
aInfo.addTag(ProfileEntry::EmbeddedString(*((void**)(&text[0]))));
|
|
}
|
|
}
|
|
|
|
static void
|
|
AddPseudoEntry(volatile StackEntry& entry, ThreadInfo& aInfo,
|
|
PseudoStack* stack, void* lastpc)
|
|
{
|
|
// Pseudo-frames with the BEGIN_PSEUDO_JS flag are just annotations and
|
|
// should not be recorded in the profile.
|
|
if (entry.hasFlag(StackEntry::BEGIN_PSEUDO_JS)) {
|
|
return;
|
|
}
|
|
|
|
int lineno = -1;
|
|
|
|
// First entry has kind CodeLocation. Check for magic pointer bit 1 to
|
|
// indicate copy.
|
|
const char* sampleLabel = entry.label();
|
|
|
|
if (entry.isCopyLabel()) {
|
|
// Store the string using 1 or more EmbeddedString tags.
|
|
// That will happen to the preceding tag.
|
|
AddDynamicCodeLocationTag(aInfo, sampleLabel);
|
|
if (entry.isJs()) {
|
|
JSScript* script = entry.script();
|
|
if (script) {
|
|
if (!entry.pc()) {
|
|
// The JIT only allows the top-most entry to have a nullptr pc.
|
|
MOZ_ASSERT(&entry == &stack->mStack[stack->stackSize() - 1]);
|
|
|
|
// If stack-walking was disabled, then that's just unfortunate.
|
|
if (lastpc) {
|
|
jsbytecode* jspc = js::ProfilingGetPC(stack->mContext, script,
|
|
lastpc);
|
|
if (jspc) {
|
|
lineno = JS_PCToLineNumber(script, jspc);
|
|
}
|
|
}
|
|
} else {
|
|
lineno = JS_PCToLineNumber(script, entry.pc());
|
|
}
|
|
}
|
|
} else {
|
|
lineno = entry.line();
|
|
}
|
|
} else {
|
|
aInfo.addTag(ProfileEntry::CodeLocation(sampleLabel));
|
|
|
|
// XXX: Bug 1010578. Don't assume a CPP entry and try to get the line for
|
|
// js entries as well.
|
|
if (entry.isCpp()) {
|
|
lineno = entry.line();
|
|
}
|
|
}
|
|
|
|
if (lineno != -1) {
|
|
aInfo.addTag(ProfileEntry::LineNumber(lineno));
|
|
}
|
|
|
|
uint32_t category = entry.category();
|
|
MOZ_ASSERT(!(category & StackEntry::IS_CPP_ENTRY));
|
|
MOZ_ASSERT(!(category & StackEntry::FRAME_LABEL_COPY));
|
|
|
|
if (category) {
|
|
aInfo.addTag(ProfileEntry::Category((int)category));
|
|
}
|
|
}
|
|
|
|
struct NativeStack
|
|
{
|
|
void** pc_array;
|
|
void** sp_array;
|
|
size_t size;
|
|
size_t count;
|
|
};
|
|
|
|
mozilla::Atomic<bool> WALKING_JS_STACK(false);
|
|
|
|
struct AutoWalkJSStack
|
|
{
|
|
bool walkAllowed;
|
|
|
|
AutoWalkJSStack() : walkAllowed(false) {
|
|
walkAllowed = WALKING_JS_STACK.compareExchange(false, true);
|
|
}
|
|
|
|
~AutoWalkJSStack() {
|
|
if (walkAllowed) {
|
|
WALKING_JS_STACK = false;
|
|
}
|
|
}
|
|
};
|
|
|
|
static void
|
|
MergeStacksIntoProfile(ThreadInfo& aInfo, TickSample* aSample,
|
|
NativeStack& aNativeStack)
|
|
{
|
|
PseudoStack* pseudoStack = aInfo.Stack();
|
|
volatile StackEntry* pseudoFrames = pseudoStack->mStack;
|
|
uint32_t pseudoCount = pseudoStack->stackSize();
|
|
|
|
// Make a copy of the JS stack into a JSFrame array. This is necessary since,
|
|
// like the native stack, the JS stack is iterated youngest-to-oldest and we
|
|
// need to iterate oldest-to-youngest when adding entries to aInfo.
|
|
|
|
// Synchronous sampling reports an invalid buffer generation to
|
|
// ProfilingFrameIterator to avoid incorrectly resetting the generation of
|
|
// sampled JIT entries inside the JS engine. See note below concerning 'J'
|
|
// entries.
|
|
uint32_t startBufferGen;
|
|
startBufferGen = aSample->isSamplingCurrentThread
|
|
? UINT32_MAX
|
|
: aInfo.bufferGeneration();
|
|
uint32_t jsCount = 0;
|
|
JS::ProfilingFrameIterator::Frame jsFrames[1000];
|
|
|
|
// Only walk jit stack if profiling frame iterator is turned on.
|
|
if (pseudoStack->mContext &&
|
|
JS::IsProfilingEnabledForContext(pseudoStack->mContext)) {
|
|
AutoWalkJSStack autoWalkJSStack;
|
|
const uint32_t maxFrames = mozilla::ArrayLength(jsFrames);
|
|
|
|
if (aSample && autoWalkJSStack.walkAllowed) {
|
|
JS::ProfilingFrameIterator::RegisterState registerState;
|
|
registerState.pc = aSample->pc;
|
|
registerState.sp = aSample->sp;
|
|
registerState.lr = aSample->lr;
|
|
|
|
JS::ProfilingFrameIterator jsIter(pseudoStack->mContext,
|
|
registerState,
|
|
startBufferGen);
|
|
for (; jsCount < maxFrames && !jsIter.done(); ++jsIter) {
|
|
// See note below regarding 'J' entries.
|
|
if (aSample->isSamplingCurrentThread || jsIter.isWasm()) {
|
|
uint32_t extracted =
|
|
jsIter.extractStack(jsFrames, jsCount, maxFrames);
|
|
jsCount += extracted;
|
|
if (jsCount == maxFrames) {
|
|
break;
|
|
}
|
|
} else {
|
|
mozilla::Maybe<JS::ProfilingFrameIterator::Frame> frame =
|
|
jsIter.getPhysicalFrameWithoutLabel();
|
|
if (frame.isSome()) {
|
|
jsFrames[jsCount++] = frame.value();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Start the sample with a root entry.
|
|
aInfo.addTag(ProfileEntry::Sample("(root)"));
|
|
|
|
// While the pseudo-stack array is ordered oldest-to-youngest, the JS and
|
|
// native arrays are ordered youngest-to-oldest. We must add frames to aInfo
|
|
// oldest-to-youngest. Thus, iterate over the pseudo-stack forwards and JS
|
|
// and native arrays backwards. Note: this means the terminating condition
|
|
// jsIndex and nativeIndex is being < 0.
|
|
uint32_t pseudoIndex = 0;
|
|
int32_t jsIndex = jsCount - 1;
|
|
int32_t nativeIndex = aNativeStack.count - 1;
|
|
|
|
uint8_t* lastPseudoCppStackAddr = nullptr;
|
|
|
|
// Iterate as long as there is at least one frame remaining.
|
|
while (pseudoIndex != pseudoCount || jsIndex >= 0 || nativeIndex >= 0) {
|
|
// There are 1 to 3 frames available. Find and add the oldest.
|
|
uint8_t* pseudoStackAddr = nullptr;
|
|
uint8_t* jsStackAddr = nullptr;
|
|
uint8_t* nativeStackAddr = nullptr;
|
|
|
|
if (pseudoIndex != pseudoCount) {
|
|
volatile StackEntry& pseudoFrame = pseudoFrames[pseudoIndex];
|
|
|
|
if (pseudoFrame.isCpp()) {
|
|
lastPseudoCppStackAddr = (uint8_t*) pseudoFrame.stackAddress();
|
|
}
|
|
|
|
// Skip any pseudo-stack JS frames which are marked isOSR. Pseudostack
|
|
// frames are marked isOSR when the JS interpreter enters a jit frame on
|
|
// a loop edge (via on-stack-replacement, or OSR). To avoid both the
|
|
// pseudoframe and jit frame being recorded (and showing up twice), the
|
|
// interpreter marks the interpreter pseudostack entry with the OSR flag
|
|
// to ensure that it doesn't get counted.
|
|
if (pseudoFrame.isJs() && pseudoFrame.isOSR()) {
|
|
pseudoIndex++;
|
|
continue;
|
|
}
|
|
|
|
MOZ_ASSERT(lastPseudoCppStackAddr);
|
|
pseudoStackAddr = lastPseudoCppStackAddr;
|
|
}
|
|
|
|
if (jsIndex >= 0) {
|
|
jsStackAddr = (uint8_t*) jsFrames[jsIndex].stackAddress;
|
|
}
|
|
|
|
if (nativeIndex >= 0) {
|
|
nativeStackAddr = (uint8_t*) aNativeStack.sp_array[nativeIndex];
|
|
}
|
|
|
|
// If there's a native stack entry which has the same SP as a pseudo stack
|
|
// entry, pretend we didn't see the native stack entry. Ditto for a native
|
|
// stack entry which has the same SP as a JS stack entry. In effect this
|
|
// means pseudo or JS entries trump conflicting native entries.
|
|
if (nativeStackAddr && (pseudoStackAddr == nativeStackAddr ||
|
|
jsStackAddr == nativeStackAddr)) {
|
|
nativeStackAddr = nullptr;
|
|
nativeIndex--;
|
|
MOZ_ASSERT(pseudoStackAddr || jsStackAddr);
|
|
}
|
|
|
|
// Sanity checks.
|
|
MOZ_ASSERT_IF(pseudoStackAddr, pseudoStackAddr != jsStackAddr &&
|
|
pseudoStackAddr != nativeStackAddr);
|
|
MOZ_ASSERT_IF(jsStackAddr, jsStackAddr != pseudoStackAddr &&
|
|
jsStackAddr != nativeStackAddr);
|
|
MOZ_ASSERT_IF(nativeStackAddr, nativeStackAddr != pseudoStackAddr &&
|
|
nativeStackAddr != jsStackAddr);
|
|
|
|
// Check to see if pseudoStack frame is top-most.
|
|
if (pseudoStackAddr > jsStackAddr && pseudoStackAddr > nativeStackAddr) {
|
|
MOZ_ASSERT(pseudoIndex < pseudoCount);
|
|
volatile StackEntry& pseudoFrame = pseudoFrames[pseudoIndex];
|
|
AddPseudoEntry(pseudoFrame, aInfo, pseudoStack, nullptr);
|
|
pseudoIndex++;
|
|
continue;
|
|
}
|
|
|
|
// Check to see if JS jit stack frame is top-most
|
|
if (jsStackAddr > nativeStackAddr) {
|
|
MOZ_ASSERT(jsIndex >= 0);
|
|
const JS::ProfilingFrameIterator::Frame& jsFrame = jsFrames[jsIndex];
|
|
|
|
// Stringifying non-wasm JIT frames is delayed until streaming time. To
|
|
// re-lookup the entry in the JitcodeGlobalTable, we need to store the
|
|
// JIT code address (OptInfoAddr) in the circular buffer.
|
|
//
|
|
// Note that we cannot do this when we are sychronously sampling the
|
|
// current thread; that is, when called from profiler_get_backtrace. The
|
|
// captured backtrace is usually externally stored for an indeterminate
|
|
// amount of time, such as in nsRefreshDriver. Problematically, the
|
|
// stored backtrace may be alive across a GC during which the profiler
|
|
// itself is disabled. In that case, the JS engine is free to discard its
|
|
// JIT code. This means that if we inserted such OptInfoAddr entries into
|
|
// the buffer, nsRefreshDriver would now be holding on to a backtrace
|
|
// with stale JIT code return addresses.
|
|
if (aSample->isSamplingCurrentThread ||
|
|
jsFrame.kind == JS::ProfilingFrameIterator::Frame_Wasm) {
|
|
AddDynamicCodeLocationTag(aInfo, jsFrame.label);
|
|
} else {
|
|
MOZ_ASSERT(jsFrame.kind == JS::ProfilingFrameIterator::Frame_Ion ||
|
|
jsFrame.kind == JS::ProfilingFrameIterator::Frame_Baseline);
|
|
aInfo.addTag(
|
|
ProfileEntry::JitReturnAddr(jsFrames[jsIndex].returnAddress));
|
|
}
|
|
|
|
jsIndex--;
|
|
continue;
|
|
}
|
|
|
|
// If we reach here, there must be a native stack entry and it must be the
|
|
// greatest entry.
|
|
if (nativeStackAddr) {
|
|
MOZ_ASSERT(nativeIndex >= 0);
|
|
void* addr = (void*)aNativeStack.pc_array[nativeIndex];
|
|
aInfo.addTag(ProfileEntry::NativeLeafAddr(addr));
|
|
}
|
|
if (nativeIndex >= 0) {
|
|
nativeIndex--;
|
|
}
|
|
}
|
|
|
|
// Update the JS context with the current profile sample buffer generation.
|
|
//
|
|
// Do not do this for synchronous sampling, which create their own
|
|
// ProfileBuffers.
|
|
if (!aSample->isSamplingCurrentThread && pseudoStack->mContext) {
|
|
MOZ_ASSERT(aInfo.bufferGeneration() >= startBufferGen);
|
|
uint32_t lapCount = aInfo.bufferGeneration() - startBufferGen;
|
|
JS::UpdateJSContextProfilerSampleBufferGen(pseudoStack->mContext,
|
|
aInfo.bufferGeneration(),
|
|
lapCount);
|
|
}
|
|
}
|
|
|
|
#if defined(GP_OS_windows)
|
|
static uintptr_t GetThreadHandle(PlatformData* aData);
|
|
#endif
|
|
|
|
#ifdef USE_NS_STACKWALK
|
|
static void
|
|
StackWalkCallback(uint32_t aFrameNumber, void* aPC, void* aSP, void* aClosure)
|
|
{
|
|
NativeStack* nativeStack = static_cast<NativeStack*>(aClosure);
|
|
MOZ_ASSERT(nativeStack->count < nativeStack->size);
|
|
nativeStack->sp_array[nativeStack->count] = aSP;
|
|
nativeStack->pc_array[nativeStack->count] = aPC;
|
|
nativeStack->count++;
|
|
}
|
|
|
|
static void
|
|
DoNativeBacktrace(ThreadInfo& aInfo, TickSample* aSample)
|
|
{
|
|
void* pc_array[1000];
|
|
void* sp_array[1000];
|
|
NativeStack nativeStack = {
|
|
pc_array,
|
|
sp_array,
|
|
mozilla::ArrayLength(pc_array),
|
|
0
|
|
};
|
|
|
|
// Start with the current function. We use 0 as the frame number here because
|
|
// the FramePointerStackWalk() and MozStackWalk() calls below will use 1..N.
|
|
// This is a bit weird but it doesn't matter because StackWalkCallback()
|
|
// doesn't use the frame number argument.
|
|
StackWalkCallback(/* frameNum */ 0, aSample->pc, aSample->sp, &nativeStack);
|
|
|
|
uint32_t maxFrames = uint32_t(nativeStack.size - nativeStack.count);
|
|
|
|
#if defined(GP_OS_darwin) || (defined(GP_PLAT_x86_windows))
|
|
void* stackEnd = aSample->threadInfo->StackTop();
|
|
if (aSample->fp >= aSample->sp && aSample->fp <= stackEnd) {
|
|
FramePointerStackWalk(StackWalkCallback, /* skipFrames */ 0, maxFrames,
|
|
&nativeStack, reinterpret_cast<void**>(aSample->fp),
|
|
stackEnd);
|
|
}
|
|
#else
|
|
// Win64 always omits frame pointers so for it we use the slower
|
|
// MozStackWalk().
|
|
uintptr_t thread = GetThreadHandle(aSample->threadInfo->GetPlatformData());
|
|
MOZ_ASSERT(thread);
|
|
MozStackWalk(StackWalkCallback, /* skipFrames */ 0, maxFrames, &nativeStack,
|
|
thread, /* platformData */ nullptr);
|
|
#endif
|
|
|
|
MergeStacksIntoProfile(aInfo, aSample, nativeStack);
|
|
}
|
|
#endif
|
|
|
|
#ifdef USE_EHABI_STACKWALK
|
|
static void
|
|
DoNativeBacktrace(ThreadInfo& aInfo, TickSample* aSample)
|
|
{
|
|
void* pc_array[1000];
|
|
void* sp_array[1000];
|
|
NativeStack nativeStack = {
|
|
pc_array,
|
|
sp_array,
|
|
mozilla::ArrayLength(pc_array),
|
|
0
|
|
};
|
|
|
|
const mcontext_t* mcontext =
|
|
&reinterpret_cast<ucontext_t*>(aSample->context)->uc_mcontext;
|
|
mcontext_t savedContext;
|
|
PseudoStack* pseudoStack = aInfo.Stack();
|
|
|
|
nativeStack.count = 0;
|
|
|
|
// The pseudostack contains an "EnterJIT" frame whenever we enter
|
|
// JIT code with profiling enabled; the stack pointer value points
|
|
// the saved registers. We use this to unwind resume unwinding
|
|
// after encounting JIT code.
|
|
for (uint32_t i = pseudoStack->stackSize(); i > 0; --i) {
|
|
// The pseudostack grows towards higher indices, so we iterate
|
|
// backwards (from callee to caller).
|
|
volatile StackEntry& entry = pseudoStack->mStack[i - 1];
|
|
if (!entry.isJs() && strcmp(entry.label(), "EnterJIT") == 0) {
|
|
// Found JIT entry frame. Unwind up to that point (i.e., force
|
|
// the stack walk to stop before the block of saved registers;
|
|
// note that it yields nondecreasing stack pointers), then restore
|
|
// the saved state.
|
|
uint32_t* vSP = reinterpret_cast<uint32_t*>(entry.stackAddress());
|
|
|
|
nativeStack.count += EHABIStackWalk(*mcontext,
|
|
/* stackBase = */ vSP,
|
|
sp_array + nativeStack.count,
|
|
pc_array + nativeStack.count,
|
|
nativeStack.size - nativeStack.count);
|
|
|
|
memset(&savedContext, 0, sizeof(savedContext));
|
|
|
|
// See also: struct EnterJITStack in js/src/jit/arm/Trampoline-arm.cpp
|
|
savedContext.arm_r4 = *vSP++;
|
|
savedContext.arm_r5 = *vSP++;
|
|
savedContext.arm_r6 = *vSP++;
|
|
savedContext.arm_r7 = *vSP++;
|
|
savedContext.arm_r8 = *vSP++;
|
|
savedContext.arm_r9 = *vSP++;
|
|
savedContext.arm_r10 = *vSP++;
|
|
savedContext.arm_fp = *vSP++;
|
|
savedContext.arm_lr = *vSP++;
|
|
savedContext.arm_sp = reinterpret_cast<uint32_t>(vSP);
|
|
savedContext.arm_pc = savedContext.arm_lr;
|
|
mcontext = &savedContext;
|
|
}
|
|
}
|
|
|
|
// Now unwind whatever's left (starting from either the last EnterJIT frame
|
|
// or, if no EnterJIT was found, the original registers).
|
|
nativeStack.count += EHABIStackWalk(*mcontext,
|
|
aInfo.StackTop(),
|
|
sp_array + nativeStack.count,
|
|
pc_array + nativeStack.count,
|
|
nativeStack.size - nativeStack.count);
|
|
|
|
MergeStacksIntoProfile(aInfo, aSample, nativeStack);
|
|
}
|
|
#endif
|
|
|
|
#ifdef USE_LUL_STACKWALK
|
|
static void
|
|
DoNativeBacktrace(ThreadInfo& aInfo, TickSample* aSample)
|
|
{
|
|
const mcontext_t* mc =
|
|
&reinterpret_cast<ucontext_t*>(aSample->context)->uc_mcontext;
|
|
|
|
lul::UnwindRegs startRegs;
|
|
memset(&startRegs, 0, sizeof(startRegs));
|
|
|
|
#if defined(GP_PLAT_amd64_linux)
|
|
startRegs.xip = lul::TaggedUWord(mc->gregs[REG_RIP]);
|
|
startRegs.xsp = lul::TaggedUWord(mc->gregs[REG_RSP]);
|
|
startRegs.xbp = lul::TaggedUWord(mc->gregs[REG_RBP]);
|
|
#elif defined(GP_PLAT_arm_android)
|
|
startRegs.r15 = lul::TaggedUWord(mc->arm_pc);
|
|
startRegs.r14 = lul::TaggedUWord(mc->arm_lr);
|
|
startRegs.r13 = lul::TaggedUWord(mc->arm_sp);
|
|
startRegs.r12 = lul::TaggedUWord(mc->arm_ip);
|
|
startRegs.r11 = lul::TaggedUWord(mc->arm_fp);
|
|
startRegs.r7 = lul::TaggedUWord(mc->arm_r7);
|
|
#elif defined(GP_PLAT_x86_linux) || defined(GP_PLAT_x86_android)
|
|
startRegs.xip = lul::TaggedUWord(mc->gregs[REG_EIP]);
|
|
startRegs.xsp = lul::TaggedUWord(mc->gregs[REG_ESP]);
|
|
startRegs.xbp = lul::TaggedUWord(mc->gregs[REG_EBP]);
|
|
#else
|
|
# error "Unknown plat"
|
|
#endif
|
|
|
|
// Copy up to N_STACK_BYTES from rsp-REDZONE upwards, but not going past the
|
|
// stack's registered top point. Do some basic sanity checks too. This
|
|
// assumes that the TaggedUWord holding the stack pointer value is valid, but
|
|
// it should be, since it was constructed that way in the code just above.
|
|
|
|
lul::StackImage stackImg;
|
|
|
|
{
|
|
#if defined(GP_PLAT_amd64_linux)
|
|
uintptr_t rEDZONE_SIZE = 128;
|
|
uintptr_t start = startRegs.xsp.Value() - rEDZONE_SIZE;
|
|
#elif defined(GP_PLAT_arm_android)
|
|
uintptr_t rEDZONE_SIZE = 0;
|
|
uintptr_t start = startRegs.r13.Value() - rEDZONE_SIZE;
|
|
#elif defined(GP_PLAT_x86_linux) || defined(GP_PLAT_x86_android)
|
|
uintptr_t rEDZONE_SIZE = 0;
|
|
uintptr_t start = startRegs.xsp.Value() - rEDZONE_SIZE;
|
|
#else
|
|
# error "Unknown plat"
|
|
#endif
|
|
uintptr_t end = reinterpret_cast<uintptr_t>(aInfo.StackTop());
|
|
uintptr_t ws = sizeof(void*);
|
|
start &= ~(ws-1);
|
|
end &= ~(ws-1);
|
|
uintptr_t nToCopy = 0;
|
|
if (start < end) {
|
|
nToCopy = end - start;
|
|
if (nToCopy > lul::N_STACK_BYTES)
|
|
nToCopy = lul::N_STACK_BYTES;
|
|
}
|
|
MOZ_ASSERT(nToCopy <= lul::N_STACK_BYTES);
|
|
stackImg.mLen = nToCopy;
|
|
stackImg.mStartAvma = start;
|
|
if (nToCopy > 0) {
|
|
memcpy(&stackImg.mContents[0], (void*)start, nToCopy);
|
|
(void)VALGRIND_MAKE_MEM_DEFINED(&stackImg.mContents[0], nToCopy);
|
|
}
|
|
}
|
|
|
|
// The maximum number of frames that LUL will produce. Setting it
|
|
// too high gives a risk of it wasting a lot of time looping on
|
|
// corrupted stacks.
|
|
const int MAX_NATIVE_FRAMES = 256;
|
|
|
|
size_t scannedFramesAllowed = 0;
|
|
|
|
uintptr_t framePCs[MAX_NATIVE_FRAMES];
|
|
uintptr_t frameSPs[MAX_NATIVE_FRAMES];
|
|
size_t framesAvail = mozilla::ArrayLength(framePCs);
|
|
size_t framesUsed = 0;
|
|
size_t scannedFramesAcquired = 0;
|
|
gLUL->Unwind(&framePCs[0], &frameSPs[0],
|
|
&framesUsed, &scannedFramesAcquired,
|
|
framesAvail, scannedFramesAllowed,
|
|
&startRegs, &stackImg );
|
|
|
|
NativeStack nativeStack = {
|
|
reinterpret_cast<void**>(framePCs),
|
|
reinterpret_cast<void**>(frameSPs),
|
|
mozilla::ArrayLength(framePCs),
|
|
0
|
|
};
|
|
|
|
nativeStack.count = framesUsed;
|
|
|
|
MergeStacksIntoProfile(aInfo, aSample, nativeStack);
|
|
|
|
// Update stats in the LUL stats object. Unfortunately this requires
|
|
// three global memory operations.
|
|
gLUL->mStats.mContext += 1;
|
|
gLUL->mStats.mCFI += framesUsed - 1 - scannedFramesAcquired;
|
|
gLUL->mStats.mScanned += scannedFramesAcquired;
|
|
}
|
|
#endif
|
|
|
|
static void
|
|
DoSampleStackTrace(ThreadInfo& aInfo, TickSample* aSample,
|
|
bool aAddLeafAddresses)
|
|
{
|
|
NativeStack nativeStack = { nullptr, nullptr, 0, 0 };
|
|
MergeStacksIntoProfile(aInfo, aSample, nativeStack);
|
|
|
|
if (aSample && aAddLeafAddresses) {
|
|
aInfo.addTag(ProfileEntry::NativeLeafAddr((void*)aSample->pc));
|
|
}
|
|
}
|
|
|
|
// This function is called for each sampling period with the current program
|
|
// counter. It is called within a signal and so must be re-entrant.
|
|
static void
|
|
Tick(TickSample* aSample)
|
|
{
|
|
ThreadInfo& currThreadInfo = *aSample->threadInfo;
|
|
|
|
currThreadInfo.addTag(ProfileEntry::ThreadId(currThreadInfo.ThreadId()));
|
|
|
|
mozilla::TimeDuration delta = aSample->timestamp - gStartTime;
|
|
currThreadInfo.addTag(ProfileEntry::Time(delta.ToMilliseconds()));
|
|
|
|
PseudoStack* stack = currThreadInfo.Stack();
|
|
|
|
#if defined(USE_NS_STACKWALK) || defined(USE_EHABI_STACKWALK) || \
|
|
defined(USE_LUL_STACKWALK)
|
|
if (gUseStackWalk) {
|
|
DoNativeBacktrace(currThreadInfo, aSample);
|
|
} else {
|
|
DoSampleStackTrace(currThreadInfo, aSample, gAddLeafAddresses);
|
|
}
|
|
#else
|
|
DoSampleStackTrace(currThreadInfo, aSample, gAddLeafAddresses);
|
|
#endif
|
|
|
|
// Don't process the PeudoStack's markers if we're synchronously sampling the
|
|
// current thread.
|
|
if (!aSample->isSamplingCurrentThread) {
|
|
ProfilerMarkerLinkedList* pendingMarkersList = stack->getPendingMarkers();
|
|
while (pendingMarkersList && pendingMarkersList->peek()) {
|
|
ProfilerMarker* marker = pendingMarkersList->popHead();
|
|
currThreadInfo.addStoredMarker(marker);
|
|
currThreadInfo.addTag(ProfileEntry::Marker(marker));
|
|
}
|
|
}
|
|
|
|
if (currThreadInfo.GetThreadResponsiveness()->HasData()) {
|
|
mozilla::TimeDuration delta =
|
|
currThreadInfo.GetThreadResponsiveness()->GetUnresponsiveDuration(
|
|
aSample->timestamp);
|
|
currThreadInfo.addTag(ProfileEntry::Responsiveness(delta.ToMilliseconds()));
|
|
}
|
|
|
|
// rssMemory is equal to 0 when we are not recording.
|
|
if (aSample->rssMemory != 0) {
|
|
currThreadInfo.addTag(ProfileEntry::ResidentMemory(
|
|
static_cast<double>(aSample->rssMemory)));
|
|
}
|
|
|
|
// ussMemory is equal to 0 when we are not recording.
|
|
if (aSample->ussMemory != 0) {
|
|
currThreadInfo.addTag(ProfileEntry::UnsharedMemory(
|
|
static_cast<double>(aSample->ussMemory)));
|
|
}
|
|
|
|
if (gLastFrameNumber != gFrameNumber) {
|
|
currThreadInfo.addTag(ProfileEntry::FrameNumber(gFrameNumber));
|
|
gLastFrameNumber = gFrameNumber;
|
|
}
|
|
}
|
|
|
|
// END tick/unwinding code
|
|
////////////////////////////////////////////////////////////////////////
|
|
|
|
////////////////////////////////////////////////////////////////////////
|
|
// BEGIN saving/streaming code
|
|
|
|
class ProfileSaveEvent final : public nsIProfileSaveEvent
|
|
{
|
|
public:
|
|
typedef void (*AddSubProfileFunc)(const char* aProfile, void* aClosure);
|
|
NS_DECL_ISUPPORTS
|
|
|
|
ProfileSaveEvent(AddSubProfileFunc aFunc, void* aClosure)
|
|
: mFunc(aFunc)
|
|
, mClosure(aClosure)
|
|
{}
|
|
|
|
NS_IMETHOD AddSubProfile(const char* aProfile) override {
|
|
mFunc(aProfile, mClosure);
|
|
return NS_OK;
|
|
}
|
|
|
|
private:
|
|
~ProfileSaveEvent() {}
|
|
|
|
AddSubProfileFunc mFunc;
|
|
void* mClosure;
|
|
};
|
|
|
|
NS_IMPL_ISUPPORTS(ProfileSaveEvent, nsIProfileSaveEvent)
|
|
|
|
static void
|
|
AddSharedLibraryInfoToStream(std::ostream& aStream, const SharedLibrary& aLib)
|
|
{
|
|
aStream << "{";
|
|
aStream << "\"start\":" << aLib.GetStart();
|
|
aStream << ",\"end\":" << aLib.GetEnd();
|
|
aStream << ",\"offset\":" << aLib.GetOffset();
|
|
aStream << ",\"name\":\"" << aLib.GetNativeDebugName() << "\"";
|
|
const std::string& breakpadId = aLib.GetBreakpadId();
|
|
aStream << ",\"breakpadId\":\"" << breakpadId << "\"";
|
|
aStream << "}";
|
|
}
|
|
|
|
static std::string
|
|
GetSharedLibraryInfoStringInternal()
|
|
{
|
|
SharedLibraryInfo info = SharedLibraryInfo::GetInfoForSelf();
|
|
if (info.GetSize() == 0) {
|
|
return "[]";
|
|
}
|
|
|
|
std::ostringstream os;
|
|
os << "[";
|
|
AddSharedLibraryInfoToStream(os, info.GetEntry(0));
|
|
|
|
for (size_t i = 1; i < info.GetSize(); i++) {
|
|
os << ",";
|
|
AddSharedLibraryInfoToStream(os, info.GetEntry(i));
|
|
}
|
|
|
|
os << "]";
|
|
return os.str();
|
|
}
|
|
|
|
static void
|
|
StreamTaskTracer(SpliceableJSONWriter& aWriter)
|
|
{
|
|
#ifdef MOZ_TASK_TRACER
|
|
aWriter.StartArrayProperty("data");
|
|
{
|
|
UniquePtr<nsTArray<nsCString>> data =
|
|
mozilla::tasktracer::GetLoggedData(gStartTime);
|
|
for (uint32_t i = 0; i < data->Length(); ++i) {
|
|
aWriter.StringElement((data->ElementAt(i)).get());
|
|
}
|
|
}
|
|
aWriter.EndArray();
|
|
|
|
aWriter.StartArrayProperty("threads");
|
|
{
|
|
StaticMutexAutoLock lock(gRegisteredThreadsMutex);
|
|
|
|
for (size_t i = 0; i < gRegisteredThreads->size(); i++) {
|
|
// Thread meta data
|
|
ThreadInfo* info = gRegisteredThreads->at(i);
|
|
aWriter.StartObjectElement();
|
|
{
|
|
if (XRE_GetProcessType() == GeckoProcessType_Plugin) {
|
|
// TODO Add the proper plugin name
|
|
aWriter.StringProperty("name", "Plugin");
|
|
} else {
|
|
aWriter.StringProperty("name", info->Name());
|
|
}
|
|
aWriter.IntProperty("tid", static_cast<int>(info->ThreadId()));
|
|
}
|
|
aWriter.EndObject();
|
|
}
|
|
}
|
|
aWriter.EndArray();
|
|
|
|
aWriter.DoubleProperty(
|
|
"start", static_cast<double>(mozilla::tasktracer::GetStartTime()));
|
|
#endif
|
|
}
|
|
|
|
static void
|
|
StreamMetaJSCustomObject(SpliceableJSONWriter& aWriter)
|
|
{
|
|
MOZ_RELEASE_ASSERT(NS_IsMainThread());
|
|
|
|
aWriter.IntProperty("version", 3);
|
|
aWriter.DoubleProperty("interval", gInterval);
|
|
aWriter.IntProperty("stackwalk", gUseStackWalk);
|
|
|
|
#ifdef DEBUG
|
|
aWriter.IntProperty("debug", 1);
|
|
#else
|
|
aWriter.IntProperty("debug", 0);
|
|
#endif
|
|
|
|
aWriter.IntProperty("gcpoison", JS::IsGCPoisoning() ? 1 : 0);
|
|
|
|
bool asyncStacks = Preferences::GetBool("javascript.options.asyncstack");
|
|
aWriter.IntProperty("asyncstack", asyncStacks);
|
|
|
|
mozilla::TimeDuration delta = mozilla::TimeStamp::Now() - gStartTime;
|
|
aWriter.DoubleProperty(
|
|
"startTime", static_cast<double>(PR_Now()/1000.0 - delta.ToMilliseconds()));
|
|
|
|
aWriter.IntProperty("processType", XRE_GetProcessType());
|
|
|
|
nsresult res;
|
|
nsCOMPtr<nsIHttpProtocolHandler> http =
|
|
do_GetService(NS_NETWORK_PROTOCOL_CONTRACTID_PREFIX "http", &res);
|
|
|
|
if (!NS_FAILED(res)) {
|
|
nsAutoCString string;
|
|
|
|
res = http->GetPlatform(string);
|
|
if (!NS_FAILED(res)) {
|
|
aWriter.StringProperty("platform", string.Data());
|
|
}
|
|
|
|
res = http->GetOscpu(string);
|
|
if (!NS_FAILED(res)) {
|
|
aWriter.StringProperty("oscpu", string.Data());
|
|
}
|
|
|
|
res = http->GetMisc(string);
|
|
if (!NS_FAILED(res)) {
|
|
aWriter.StringProperty("misc", string.Data());
|
|
}
|
|
}
|
|
|
|
nsCOMPtr<nsIXULRuntime> runtime = do_GetService("@mozilla.org/xre/runtime;1");
|
|
if (runtime) {
|
|
nsAutoCString string;
|
|
|
|
res = runtime->GetXPCOMABI(string);
|
|
if (!NS_FAILED(res))
|
|
aWriter.StringProperty("abi", string.Data());
|
|
|
|
res = runtime->GetWidgetToolkit(string);
|
|
if (!NS_FAILED(res))
|
|
aWriter.StringProperty("toolkit", string.Data());
|
|
}
|
|
|
|
nsCOMPtr<nsIXULAppInfo> appInfo =
|
|
do_GetService("@mozilla.org/xre/app-info;1");
|
|
|
|
if (appInfo) {
|
|
nsAutoCString string;
|
|
res = appInfo->GetName(string);
|
|
if (!NS_FAILED(res))
|
|
aWriter.StringProperty("product", string.Data());
|
|
}
|
|
}
|
|
|
|
struct SubprocessClosure
|
|
{
|
|
explicit SubprocessClosure(SpliceableJSONWriter* aWriter)
|
|
: mWriter(aWriter)
|
|
{}
|
|
|
|
SpliceableJSONWriter* mWriter;
|
|
};
|
|
|
|
static void
|
|
SubProcessCallback(const char* aProfile, void* aClosure)
|
|
{
|
|
// Called by the observer to get their profile data included as a sub profile.
|
|
SubprocessClosure* closure = (SubprocessClosure*)aClosure;
|
|
|
|
// Add the string profile into the profile.
|
|
closure->mWriter->StringElement(aProfile);
|
|
}
|
|
|
|
#if defined(PROFILE_JAVA)
|
|
static void
|
|
BuildJavaThreadJSObject(SpliceableJSONWriter& aWriter)
|
|
{
|
|
aWriter.StringProperty("name", "Java Main Thread");
|
|
|
|
aWriter.StartArrayProperty("samples");
|
|
{
|
|
for (int sampleId = 0; true; sampleId++) {
|
|
bool firstRun = true;
|
|
for (int frameId = 0; true; frameId++) {
|
|
jni::String::LocalRef frameName =
|
|
java::GeckoJavaSampler::GetFrameName(0, sampleId, frameId);
|
|
|
|
// When we run out of frames, we stop looping.
|
|
if (!frameName) {
|
|
// If we found at least one frame, we have objects to close.
|
|
if (!firstRun) {
|
|
aWriter.EndArray();
|
|
aWriter.EndObject();
|
|
}
|
|
break;
|
|
}
|
|
// The first time around, open the sample object and frames array.
|
|
if (firstRun) {
|
|
firstRun = false;
|
|
|
|
double sampleTime =
|
|
java::GeckoJavaSampler::GetSampleTime(0, sampleId);
|
|
|
|
aWriter.StartObjectElement();
|
|
aWriter.DoubleProperty("time", sampleTime);
|
|
|
|
aWriter.StartArrayProperty("frames");
|
|
}
|
|
|
|
// Add a frame to the sample.
|
|
aWriter.StartObjectElement();
|
|
{
|
|
aWriter.StringProperty("location",
|
|
frameName->ToCString().BeginReading());
|
|
}
|
|
aWriter.EndObject();
|
|
}
|
|
|
|
// If we found no frames for this sample, we are done.
|
|
if (firstRun) {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
aWriter.EndArray();
|
|
}
|
|
#endif
|
|
|
|
static void
|
|
StreamJSON(SpliceableJSONWriter& aWriter, double aSinceTime)
|
|
{
|
|
MOZ_RELEASE_ASSERT(NS_IsMainThread());
|
|
|
|
aWriter.Start(SpliceableJSONWriter::SingleLineStyle);
|
|
{
|
|
// Put shared library info
|
|
aWriter.StringProperty("libs",
|
|
GetSharedLibraryInfoStringInternal().c_str());
|
|
|
|
// Put meta data
|
|
aWriter.StartObjectProperty("meta");
|
|
{
|
|
StreamMetaJSCustomObject(aWriter);
|
|
}
|
|
aWriter.EndObject();
|
|
|
|
// Data of TaskTracer doesn't belong in the circular buffer.
|
|
if (gTaskTracer) {
|
|
aWriter.StartObjectProperty("tasktracer");
|
|
StreamTaskTracer(aWriter);
|
|
aWriter.EndObject();
|
|
}
|
|
|
|
// Lists the samples for each thread profile
|
|
aWriter.StartArrayProperty("threads");
|
|
{
|
|
gIsPaused = true;
|
|
|
|
{
|
|
StaticMutexAutoLock lock(gRegisteredThreadsMutex);
|
|
|
|
for (size_t i = 0; i < gRegisteredThreads->size(); i++) {
|
|
// Thread not being profiled, skip it
|
|
ThreadInfo* info = gRegisteredThreads->at(i);
|
|
if (!info->hasProfile()) {
|
|
continue;
|
|
}
|
|
|
|
// Note that we intentionally include thread profiles which
|
|
// have been marked for pending delete.
|
|
|
|
MutexAutoLock lock(info->GetMutex());
|
|
|
|
info->StreamJSON(aWriter, aSinceTime);
|
|
}
|
|
}
|
|
|
|
if (CanNotifyObservers()) {
|
|
// Send a event asking any subprocesses (plugins) to
|
|
// give us their information
|
|
SubprocessClosure closure(&aWriter);
|
|
nsCOMPtr<nsIObserverService> os =
|
|
mozilla::services::GetObserverService();
|
|
if (os) {
|
|
RefPtr<ProfileSaveEvent> pse =
|
|
new ProfileSaveEvent(SubProcessCallback, &closure);
|
|
os->NotifyObservers(pse, "profiler-subprocess", nullptr);
|
|
}
|
|
}
|
|
|
|
#if defined(PROFILE_JAVA)
|
|
if (gProfileJava) {
|
|
java::GeckoJavaSampler::Pause();
|
|
|
|
aWriter.Start();
|
|
{
|
|
BuildJavaThreadJSObject(aWriter);
|
|
}
|
|
aWriter.End();
|
|
|
|
java::GeckoJavaSampler::Unpause();
|
|
}
|
|
#endif
|
|
|
|
gIsPaused = false;
|
|
}
|
|
aWriter.EndArray();
|
|
}
|
|
aWriter.End();
|
|
}
|
|
|
|
UniquePtr<char[]>
|
|
ToJSON(double aSinceTime)
|
|
{
|
|
SpliceableChunkedJSONWriter b;
|
|
StreamJSON(b, aSinceTime);
|
|
return b.WriteFunc()->CopyData();
|
|
}
|
|
|
|
static JSObject*
|
|
ToJSObject(JSContext* aCx, double aSinceTime)
|
|
{
|
|
JS::RootedValue val(aCx);
|
|
{
|
|
UniquePtr<char[]> buf = ToJSON(aSinceTime);
|
|
NS_ConvertUTF8toUTF16 js_string(nsDependentCString(buf.get()));
|
|
MOZ_ALWAYS_TRUE(JS_ParseJSON(aCx, static_cast<const char16_t*>(js_string.get()),
|
|
js_string.Length(), &val));
|
|
}
|
|
return &val.toObject();
|
|
}
|
|
|
|
// END saving/streaming code
|
|
////////////////////////////////////////////////////////////////////////
|
|
|
|
ProfilerMarker::ProfilerMarker(const char* aMarkerName,
|
|
ProfilerMarkerPayload* aPayload,
|
|
double aTime)
|
|
: mMarkerName(strdup(aMarkerName))
|
|
, mPayload(aPayload)
|
|
, mTime(aTime)
|
|
{
|
|
}
|
|
|
|
ProfilerMarker::~ProfilerMarker() {
|
|
free(mMarkerName);
|
|
delete mPayload;
|
|
}
|
|
|
|
void
|
|
ProfilerMarker::SetGeneration(uint32_t aGenID) {
|
|
mGenID = aGenID;
|
|
}
|
|
|
|
double
|
|
ProfilerMarker::GetTime() const {
|
|
return mTime;
|
|
}
|
|
|
|
void ProfilerMarker::StreamJSON(SpliceableJSONWriter& aWriter,
|
|
UniqueStacks& aUniqueStacks) const
|
|
{
|
|
// Schema:
|
|
// [name, time, data]
|
|
|
|
aWriter.StartArrayElement();
|
|
{
|
|
aUniqueStacks.mUniqueStrings.WriteElement(aWriter, GetMarkerName());
|
|
aWriter.DoubleElement(mTime);
|
|
// TODO: Store the callsite for this marker if available:
|
|
// if have location data
|
|
// b.NameValue(marker, "location", ...);
|
|
if (mPayload) {
|
|
aWriter.StartObjectElement();
|
|
{
|
|
mPayload->StreamPayload(aWriter, aUniqueStacks);
|
|
}
|
|
aWriter.EndObject();
|
|
}
|
|
}
|
|
aWriter.EndArray();
|
|
}
|
|
|
|
/* Has MOZ_PROFILER_VERBOSE been set? */
|
|
|
|
// Verbosity control for the profiler. The aim is to check env var
|
|
// MOZ_PROFILER_VERBOSE only once. However, we may need to temporarily
|
|
// override that so as to print the profiler's help message. That's
|
|
// what profiler_set_verbosity is for.
|
|
|
|
enum class ProfilerVerbosity : int8_t { UNCHECKED, NOTVERBOSE, VERBOSE };
|
|
|
|
// Raced on, potentially
|
|
static ProfilerVerbosity profiler_verbosity = ProfilerVerbosity::UNCHECKED;
|
|
|
|
bool profiler_verbose()
|
|
{
|
|
if (profiler_verbosity == ProfilerVerbosity::UNCHECKED) {
|
|
if (getenv("MOZ_PROFILER_VERBOSE") != nullptr)
|
|
profiler_verbosity = ProfilerVerbosity::VERBOSE;
|
|
else
|
|
profiler_verbosity = ProfilerVerbosity::NOTVERBOSE;
|
|
}
|
|
|
|
return profiler_verbosity == ProfilerVerbosity::VERBOSE;
|
|
}
|
|
|
|
void profiler_set_verbosity(ProfilerVerbosity pv)
|
|
{
|
|
MOZ_ASSERT(pv == ProfilerVerbosity::UNCHECKED ||
|
|
pv == ProfilerVerbosity::VERBOSE);
|
|
profiler_verbosity = pv;
|
|
}
|
|
|
|
|
|
bool set_profiler_interval(const char* interval) {
|
|
if (interval) {
|
|
errno = 0;
|
|
long int n = strtol(interval, (char**)nullptr, 10);
|
|
if (errno == 0 && n >= 1 && n <= 1000) {
|
|
gUnwindInterval = n;
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool set_profiler_entries(const char* entries) {
|
|
if (entries) {
|
|
errno = 0;
|
|
long int n = strtol(entries, (char**)nullptr, 10);
|
|
if (errno == 0 && n > 0) {
|
|
gProfileEntries = n;
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool set_profiler_scan(const char* scanCount) {
|
|
if (scanCount) {
|
|
errno = 0;
|
|
long int n = strtol(scanCount, (char**)nullptr, 10);
|
|
if (errno == 0 && n >= 0 && n <= 100) {
|
|
gUnwindStackScan = n;
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool is_native_unwinding_avail() {
|
|
# if defined(HAVE_NATIVE_UNWIND)
|
|
return true;
|
|
#else
|
|
return false;
|
|
#endif
|
|
}
|
|
|
|
// Read env vars at startup, so as to set:
|
|
// gUnwindInterval, gProfileEntries, gUnwindStackScan.
|
|
void read_profiler_env_vars()
|
|
{
|
|
/* Set defaults */
|
|
gUnwindInterval = 0; /* We'll have to look elsewhere */
|
|
gProfileEntries = 0;
|
|
|
|
const char* interval = getenv(PROFILER_INTERVAL);
|
|
const char* entries = getenv(PROFILER_ENTRIES);
|
|
const char* scanCount = getenv(PROFILER_STACK);
|
|
|
|
if (getenv(PROFILER_HELP)) {
|
|
// Enable verbose output
|
|
profiler_set_verbosity(ProfilerVerbosity::VERBOSE);
|
|
profiler_usage();
|
|
// Now force the next enquiry of profiler_verbose to re-query
|
|
// env var MOZ_PROFILER_VERBOSE.
|
|
profiler_set_verbosity(ProfilerVerbosity::UNCHECKED);
|
|
}
|
|
|
|
if (!set_profiler_interval(interval) ||
|
|
!set_profiler_entries(entries) ||
|
|
!set_profiler_scan(scanCount)) {
|
|
profiler_usage();
|
|
} else {
|
|
LOG( "Profiler:");
|
|
LOGF("Profiler: Sampling interval = %d ms (zero means \"platform default\")",
|
|
(int)gUnwindInterval);
|
|
LOGF("Profiler: Entry store size = %d (zero means \"platform default\")",
|
|
(int)gProfileEntries);
|
|
LOGF("Profiler: UnwindStackScan = %d (max dubious frames per unwind).",
|
|
(int)gUnwindStackScan);
|
|
LOG( "Profiler:");
|
|
}
|
|
}
|
|
|
|
void profiler_usage() {
|
|
LOG( "Profiler: ");
|
|
LOG( "Profiler: Environment variable usage:");
|
|
LOG( "Profiler: ");
|
|
LOG( "Profiler: MOZ_PROFILER_HELP");
|
|
LOG( "Profiler: If set to any value, prints this message.");
|
|
LOG( "Profiler: ");
|
|
LOG( "Profiler: MOZ_PROFILER_INTERVAL=<number> (milliseconds, 1 to 1000)");
|
|
LOG( "Profiler: If unset, platform default is used.");
|
|
LOG( "Profiler: ");
|
|
LOG( "Profiler: MOZ_PROFILER_ENTRIES=<number> (count, minimum of 1)");
|
|
LOG( "Profiler: If unset, platform default is used.");
|
|
LOG( "Profiler: ");
|
|
LOG( "Profiler: MOZ_PROFILER_VERBOSE");
|
|
LOG( "Profiler: If set to any value, increases verbosity (recommended).");
|
|
LOG( "Profiler: ");
|
|
LOG( "Profiler: MOZ_PROFILER_STACK_SCAN=<number> (default is zero)");
|
|
LOG( "Profiler: The number of dubious (stack-scanned) frames allowed");
|
|
LOG( "Profiler: ");
|
|
LOG( "Profiler: MOZ_PROFILER_LUL_TEST");
|
|
LOG( "Profiler: If set to any value, runs LUL unit tests at startup of");
|
|
LOG( "Profiler: the unwinder thread, and prints a short summary of results.");
|
|
LOG( "Profiler: ");
|
|
LOGF("Profiler: This platform %s native unwinding.",
|
|
is_native_unwinding_avail() ? "supports" : "does not support");
|
|
LOG( "Profiler: ");
|
|
|
|
/* Re-set defaults */
|
|
gUnwindInterval = 0; /* We'll have to look elsewhere */
|
|
gProfileEntries = 0;
|
|
gUnwindStackScan = 0;
|
|
|
|
LOG( "Profiler:");
|
|
LOGF("Profiler: Sampling interval = %d ms (zero means \"platform default\")",
|
|
(int)gUnwindInterval);
|
|
LOGF("Profiler: Entry store size = %d (zero means \"platform default\")",
|
|
(int)gProfileEntries);
|
|
LOGF("Profiler: UnwindStackScan = %d (max dubious frames per unwind).",
|
|
(int)gUnwindStackScan);
|
|
LOG( "Profiler:");
|
|
|
|
return;
|
|
}
|
|
|
|
bool is_main_thread_name(const char* aName) {
|
|
if (!aName) {
|
|
return false;
|
|
}
|
|
return strcmp(aName, gGeckoThreadName) == 0;
|
|
}
|
|
|
|
#ifdef HAVE_VA_COPY
|
|
#define VARARGS_ASSIGN(foo, bar) VA_COPY(foo,bar)
|
|
#elif defined(HAVE_VA_LIST_AS_ARRAY)
|
|
#define VARARGS_ASSIGN(foo, bar) foo[0] = bar[0]
|
|
#else
|
|
#define VARARGS_ASSIGN(foo, bar) (foo) = (bar)
|
|
#endif
|
|
|
|
void
|
|
profiler_log(const char* str)
|
|
{
|
|
// This function runs both on and off the main thread.
|
|
|
|
profiler_tracing("log", str, TRACING_EVENT);
|
|
}
|
|
|
|
void
|
|
profiler_log(const char* fmt, va_list args)
|
|
{
|
|
// This function runs both on and off the main thread.
|
|
|
|
if (profiler_is_active()) {
|
|
// nsAutoCString AppendPrintf would be nicer but
|
|
// this is mozilla external code
|
|
char buf[2048];
|
|
va_list argsCpy;
|
|
VARARGS_ASSIGN(argsCpy, args);
|
|
int required = VsprintfLiteral(buf, fmt, argsCpy);
|
|
va_end(argsCpy);
|
|
|
|
if (required < 0) {
|
|
return; // silently drop for now
|
|
} else if (required < 2048) {
|
|
profiler_tracing("log", buf, TRACING_EVENT);
|
|
} else {
|
|
char* heapBuf = new char[required+1];
|
|
va_list argsCpy;
|
|
VARARGS_ASSIGN(argsCpy, args);
|
|
vsnprintf(heapBuf, required+1, fmt, argsCpy);
|
|
va_end(argsCpy);
|
|
// EVENT_BACKTRACE could be used to get a source
|
|
// for all log events. This could be a runtime
|
|
// flag later.
|
|
profiler_tracing("log", heapBuf, TRACING_EVENT);
|
|
delete[] heapBuf;
|
|
}
|
|
}
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////
|
|
// BEGIN externally visible functions
|
|
|
|
MOZ_DEFINE_MALLOC_SIZE_OF(GeckoProfilerMallocSizeOf);
|
|
|
|
NS_IMETHODIMP
|
|
GeckoProfilerReporter::CollectReports(nsIHandleReportCallback* aHandleReport,
|
|
nsISupports* aData, bool aAnonymize)
|
|
{
|
|
MOZ_RELEASE_ASSERT(NS_IsMainThread());
|
|
|
|
size_t n = 0;
|
|
{
|
|
StaticMutexAutoLock lock(gRegisteredThreadsMutex);
|
|
|
|
for (uint32_t i = 0; i < gRegisteredThreads->size(); i++) {
|
|
ThreadInfo* info = gRegisteredThreads->at(i);
|
|
|
|
n += info->SizeOfIncludingThis(GeckoProfilerMallocSizeOf);
|
|
}
|
|
}
|
|
MOZ_COLLECT_REPORT(
|
|
"explicit/profiler/thread-info", KIND_HEAP, UNITS_BYTES, n,
|
|
"Memory used by the Gecko Profiler's ThreadInfo objects.");
|
|
|
|
if (gBuffer) {
|
|
n = gBuffer->SizeOfIncludingThis(GeckoProfilerMallocSizeOf);
|
|
MOZ_COLLECT_REPORT(
|
|
"explicit/profiler/profile-buffer", KIND_HEAP, UNITS_BYTES, n,
|
|
"Memory used by the Gecko Profiler's ProfileBuffer object.");
|
|
}
|
|
|
|
#if defined(USE_LUL_STACKWALK)
|
|
n = gLUL ? gLUL->SizeOfIncludingThis(GeckoProfilerMallocSizeOf) : 0;
|
|
MOZ_COLLECT_REPORT(
|
|
"explicit/profiler/lul", KIND_HEAP, UNITS_BYTES, n,
|
|
"Memory used by LUL, a stack unwinder used by the Gecko Profiler.");
|
|
#endif
|
|
|
|
// Measurement of the following things may be added later if DMD finds it
|
|
// is worthwhile:
|
|
// - gThreadNameFilters
|
|
// - gFeatures
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMPL_ISUPPORTS(GeckoProfilerReporter, nsIMemoryReporter)
|
|
|
|
static bool
|
|
ThreadSelected(const char* aThreadName)
|
|
{
|
|
StaticMutexAutoLock lock(gThreadNameFiltersMutex);
|
|
|
|
if (gThreadNameFilters.empty()) {
|
|
return true;
|
|
}
|
|
|
|
std::string name = aThreadName;
|
|
std::transform(name.begin(), name.end(), name.begin(), ::tolower);
|
|
|
|
for (uint32_t i = 0; i < gThreadNameFilters.length(); ++i) {
|
|
std::string filter = gThreadNameFilters[i];
|
|
std::transform(filter.begin(), filter.end(), filter.begin(), ::tolower);
|
|
|
|
// Crude, non UTF-8 compatible, case insensitive substring search
|
|
if (name.find(filter) != std::string::npos) {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
static void
|
|
MaybeSetProfile(ThreadInfo* aInfo)
|
|
{
|
|
if ((aInfo->IsMainThread() || gProfileThreads) &&
|
|
ThreadSelected(aInfo->Name())) {
|
|
aInfo->SetProfile(gBuffer);
|
|
}
|
|
}
|
|
|
|
static void
|
|
RegisterCurrentThread(const char* aName, PseudoStack* aPseudoStack,
|
|
bool aIsMainThread, void* stackTop)
|
|
{
|
|
StaticMutexAutoLock lock(gRegisteredThreadsMutex);
|
|
|
|
if (!gRegisteredThreads) {
|
|
return;
|
|
}
|
|
|
|
Thread::tid_t id = Thread::GetCurrentId();
|
|
|
|
for (uint32_t i = 0; i < gRegisteredThreads->size(); i++) {
|
|
ThreadInfo* info = gRegisteredThreads->at(i);
|
|
if (info->ThreadId() == id && !info->IsPendingDelete()) {
|
|
// Thread already registered. This means the first unregister will be
|
|
// too early.
|
|
MOZ_ASSERT(false);
|
|
return;
|
|
}
|
|
}
|
|
|
|
ThreadInfo* info =
|
|
new ThreadInfo(aName, id, aIsMainThread, aPseudoStack, stackTop);
|
|
|
|
MaybeSetProfile(info);
|
|
|
|
gRegisteredThreads->push_back(info);
|
|
}
|
|
|
|
// Platform-specific init/start/stop actions.
|
|
static void PlatformInit();
|
|
static void PlatformStart();
|
|
static void PlatformStop();
|
|
|
|
void
|
|
profiler_init(void* stackTop)
|
|
{
|
|
MOZ_RELEASE_ASSERT(NS_IsMainThread());
|
|
|
|
gInitCount++;
|
|
|
|
if (stack_key_initialized)
|
|
return;
|
|
|
|
#ifdef MOZ_TASK_TRACER
|
|
mozilla::tasktracer::InitTaskTracer();
|
|
#endif
|
|
|
|
LOG("BEGIN profiler_init");
|
|
if (!tlsPseudoStack.init()) {
|
|
LOG("Failed to init.");
|
|
return;
|
|
}
|
|
bool ignore;
|
|
gStartTime = mozilla::TimeStamp::ProcessCreation(ignore);
|
|
|
|
stack_key_initialized = true;
|
|
|
|
{
|
|
StaticMutexAutoLock lock(gRegisteredThreadsMutex);
|
|
|
|
gRegisteredThreads = new std::vector<ThreadInfo*>();
|
|
}
|
|
|
|
// (Linux-only) We could create the gLUL object and read unwind info into it
|
|
// at this point. That would match the lifetime implied by destruction of it
|
|
// in profiler_shutdown() just below. However, that gives a big delay on
|
|
// startup, even if no profiling is actually to be done. So, instead, gLUL is
|
|
// created on demand at the first call to PlatformStart().
|
|
|
|
PseudoStack* stack = new PseudoStack();
|
|
tlsPseudoStack.set(stack);
|
|
|
|
bool isMainThread = true;
|
|
RegisterCurrentThread(gGeckoThreadName, stack, isMainThread, stackTop);
|
|
|
|
// Read interval settings from MOZ_PROFILER_INTERVAL and stack-scan
|
|
// threshhold from MOZ_PROFILER_STACK_SCAN.
|
|
read_profiler_env_vars();
|
|
|
|
// Platform-specific initialization.
|
|
PlatformInit();
|
|
|
|
set_stderr_callback(profiler_log);
|
|
|
|
#if defined(PROFILE_JAVA)
|
|
if (mozilla::jni::IsFennec()) {
|
|
GeckoJavaSampler::Init();
|
|
}
|
|
#endif
|
|
|
|
// We can't open pref so we use an environment variable
|
|
// to know if we should trigger the profiler on startup
|
|
// NOTE: Default
|
|
const char *val = getenv("MOZ_PROFILER_STARTUP");
|
|
if (!val || !*val) {
|
|
return;
|
|
}
|
|
|
|
const char* features[] = { "js", "leaf", "threads"
|
|
#if defined(HAVE_NATIVE_UNWIND)
|
|
, "stackwalk"
|
|
#endif
|
|
#if defined(PROFILE_JAVA)
|
|
, "java"
|
|
#endif
|
|
};
|
|
|
|
const char* threadFilters[] = { "GeckoMain", "Compositor" };
|
|
|
|
profiler_start(PROFILE_DEFAULT_ENTRY, PROFILE_DEFAULT_INTERVAL,
|
|
features, MOZ_ARRAY_LENGTH(features),
|
|
threadFilters, MOZ_ARRAY_LENGTH(threadFilters));
|
|
LOG("END profiler_init");
|
|
}
|
|
|
|
void
|
|
profiler_shutdown()
|
|
{
|
|
MOZ_RELEASE_ASSERT(NS_IsMainThread());
|
|
|
|
gInitCount--;
|
|
|
|
if (gInitCount > 0)
|
|
return;
|
|
|
|
// Save the profile on shutdown if requested.
|
|
if (gSampler) {
|
|
const char* filename = getenv("MOZ_PROFILER_SHUTDOWN");
|
|
if (filename) {
|
|
profiler_save_profile_to_file(filename);
|
|
}
|
|
}
|
|
|
|
profiler_stop();
|
|
|
|
set_stderr_callback(nullptr);
|
|
|
|
{
|
|
StaticMutexAutoLock lock(gRegisteredThreadsMutex);
|
|
|
|
while (gRegisteredThreads->size() > 0) {
|
|
delete gRegisteredThreads->back();
|
|
gRegisteredThreads->pop_back();
|
|
}
|
|
|
|
// UnregisterThread can be called after shutdown in XPCShell. Thus we need
|
|
// to point to null to ignore such a call after shutdown.
|
|
delete gRegisteredThreads;
|
|
gRegisteredThreads = nullptr;
|
|
}
|
|
|
|
#if defined(USE_LUL_STACKWALK)
|
|
// Delete the gLUL object, if it actually got created.
|
|
if (gLUL) {
|
|
delete gLUL;
|
|
gLUL = nullptr;
|
|
}
|
|
#endif
|
|
|
|
// We just destroyed all the ThreadInfos in gRegisteredThreads, so it is safe
|
|
// the delete the PseudoStack.
|
|
delete tlsPseudoStack.get();
|
|
tlsPseudoStack.set(nullptr);
|
|
|
|
#ifdef MOZ_TASK_TRACER
|
|
mozilla::tasktracer::ShutdownTaskTracer();
|
|
#endif
|
|
}
|
|
|
|
mozilla::UniquePtr<char[]>
|
|
profiler_get_profile(double aSinceTime)
|
|
{
|
|
MOZ_RELEASE_ASSERT(NS_IsMainThread());
|
|
|
|
if (!gSampler) {
|
|
return nullptr;
|
|
}
|
|
|
|
return ToJSON(aSinceTime);
|
|
}
|
|
|
|
JSObject*
|
|
profiler_get_profile_jsobject(JSContext *aCx, double aSinceTime)
|
|
{
|
|
MOZ_RELEASE_ASSERT(NS_IsMainThread());
|
|
|
|
if (!gSampler) {
|
|
return nullptr;
|
|
}
|
|
|
|
return ToJSObject(aCx, aSinceTime);
|
|
}
|
|
|
|
void
|
|
profiler_get_profile_jsobject_async(double aSinceTime,
|
|
mozilla::dom::Promise* aPromise)
|
|
{
|
|
MOZ_RELEASE_ASSERT(NS_IsMainThread());
|
|
|
|
if (!gGatherer) {
|
|
return;
|
|
}
|
|
|
|
gGatherer->Start(aSinceTime, aPromise);
|
|
}
|
|
|
|
void
|
|
profiler_save_profile_to_file_async(double aSinceTime, const char* aFileName)
|
|
{
|
|
MOZ_RELEASE_ASSERT(NS_IsMainThread());
|
|
|
|
nsCString filename(aFileName);
|
|
NS_DispatchToMainThread(NS_NewRunnableFunction([=] () {
|
|
if (!gGatherer) {
|
|
return;
|
|
}
|
|
|
|
gGatherer->Start(aSinceTime, filename);
|
|
}));
|
|
}
|
|
|
|
void
|
|
profiler_get_start_params(int* aEntrySize,
|
|
double* aInterval,
|
|
mozilla::Vector<const char*>* aFilters,
|
|
mozilla::Vector<const char*>* aFeatures)
|
|
{
|
|
MOZ_RELEASE_ASSERT(NS_IsMainThread());
|
|
|
|
if (NS_WARN_IF(!aEntrySize) || NS_WARN_IF(!aInterval) ||
|
|
NS_WARN_IF(!aFilters) || NS_WARN_IF(!aFeatures)) {
|
|
return;
|
|
}
|
|
|
|
*aEntrySize = gEntrySize;
|
|
*aInterval = gInterval;
|
|
|
|
{
|
|
StaticMutexAutoLock lock(gThreadNameFiltersMutex);
|
|
|
|
MOZ_ALWAYS_TRUE(aFilters->resize(gThreadNameFilters.length()));
|
|
for (uint32_t i = 0; i < gThreadNameFilters.length(); ++i) {
|
|
(*aFilters)[i] = gThreadNameFilters[i].c_str();
|
|
}
|
|
}
|
|
|
|
MOZ_ALWAYS_TRUE(aFeatures->resize(gFeatures.length()));
|
|
for (size_t i = 0; i < gFeatures.length(); ++i) {
|
|
(*aFeatures)[i] = gFeatures[i].c_str();
|
|
}
|
|
}
|
|
|
|
void
|
|
profiler_get_gatherer(nsISupports** aRetVal)
|
|
{
|
|
MOZ_RELEASE_ASSERT(NS_IsMainThread());
|
|
|
|
if (!aRetVal) {
|
|
return;
|
|
}
|
|
|
|
if (NS_WARN_IF(!profiler_is_active())) {
|
|
*aRetVal = nullptr;
|
|
return;
|
|
}
|
|
|
|
if (NS_WARN_IF(!gGatherer)) {
|
|
*aRetVal = nullptr;
|
|
return;
|
|
}
|
|
|
|
NS_ADDREF(*aRetVal = gGatherer);
|
|
}
|
|
|
|
void
|
|
profiler_save_profile_to_file(const char* aFilename)
|
|
{
|
|
MOZ_RELEASE_ASSERT(NS_IsMainThread());
|
|
|
|
if (!gSampler) {
|
|
return;
|
|
}
|
|
|
|
std::ofstream stream;
|
|
stream.open(aFilename);
|
|
if (stream.is_open()) {
|
|
SpliceableJSONWriter w(mozilla::MakeUnique<OStreamJSONWriteFunc>(stream));
|
|
StreamJSON(w, /* sinceTime */ 0);
|
|
stream.close();
|
|
LOGF("Saved to %s", aFilename);
|
|
} else {
|
|
LOG("Fail to open profile log file.");
|
|
}
|
|
}
|
|
|
|
const char**
|
|
profiler_get_features()
|
|
{
|
|
// This function currently only used on the main thread, but that restriction
|
|
// (and this assertion) could be removed trivially because it doesn't touch
|
|
// data that requires locking.
|
|
MOZ_RELEASE_ASSERT(NS_IsMainThread());
|
|
|
|
static const char* features[] = {
|
|
#if defined(MOZ_PROFILING) && defined(HAVE_NATIVE_UNWIND)
|
|
// Walk the C++ stack.
|
|
"stackwalk",
|
|
#endif
|
|
// Include the C++ leaf node if not stackwalking. DevTools
|
|
// profiler doesn't want the native addresses.
|
|
"leaf",
|
|
// Profile Java code (Android only).
|
|
"java",
|
|
// Tell the JS engine to emit pseudostack entries in the prologue/epilogue.
|
|
"js",
|
|
// GPU Profiling (may not be supported by the GL)
|
|
"gpu",
|
|
// Profile the registered secondary threads.
|
|
"threads",
|
|
// Do not include user-identifiable information
|
|
"privacy",
|
|
// Dump the layer tree with the textures.
|
|
"layersdump",
|
|
// Dump the display list with the textures.
|
|
"displaylistdump",
|
|
// Add main thread I/O to the profile
|
|
"mainthreadio",
|
|
// Add RSS collection
|
|
"memory",
|
|
// Restyle profiling.
|
|
"restyle",
|
|
#ifdef MOZ_TASK_TRACER
|
|
// Start profiling with feature TaskTracer.
|
|
"tasktracer",
|
|
#endif
|
|
nullptr
|
|
};
|
|
|
|
return features;
|
|
}
|
|
|
|
void
|
|
profiler_get_buffer_info_helper(uint32_t *aCurrentPosition,
|
|
uint32_t *aTotalSize,
|
|
uint32_t *aGeneration)
|
|
{
|
|
// This function is called by profiler_get_buffer_info(), which has already
|
|
// zeroed the outparams.
|
|
|
|
MOZ_RELEASE_ASSERT(NS_IsMainThread());
|
|
|
|
if (!stack_key_initialized)
|
|
return;
|
|
|
|
if (!gBuffer) {
|
|
return;
|
|
}
|
|
|
|
*aCurrentPosition = gBuffer->mWritePos;
|
|
*aTotalSize = gEntrySize;
|
|
*aGeneration = gBuffer->mGeneration;
|
|
}
|
|
|
|
static bool
|
|
hasFeature(const char** aFeatures, uint32_t aFeatureCount, const char* aFeature)
|
|
{
|
|
for (size_t i = 0; i < aFeatureCount; i++) {
|
|
if (strcmp(aFeatures[i], aFeature) == 0) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
// XXX: an empty class left behind after refactoring. Will be removed soon.
|
|
class Sampler {};
|
|
|
|
// Values are only honored on the first start
|
|
void
|
|
profiler_start(int aProfileEntries, double aInterval,
|
|
const char** aFeatures, uint32_t aFeatureCount,
|
|
const char** aThreadNameFilters, uint32_t aFilterCount)
|
|
|
|
{
|
|
MOZ_RELEASE_ASSERT(NS_IsMainThread());
|
|
|
|
LOG("BEGIN profiler_start");
|
|
|
|
if (!stack_key_initialized)
|
|
profiler_init(nullptr);
|
|
|
|
/* If the sampling interval was set using env vars, use that
|
|
in preference to anything else. */
|
|
if (gUnwindInterval > 0)
|
|
aInterval = gUnwindInterval;
|
|
|
|
/* If the entry count was set using env vars, use that, too: */
|
|
if (gProfileEntries > 0)
|
|
aProfileEntries = gProfileEntries;
|
|
|
|
// Reset the current state if the profiler is running
|
|
profiler_stop();
|
|
|
|
// Deep copy aThreadNameFilters. Must happen before the MaybeSetProfile()
|
|
// call below.
|
|
{
|
|
StaticMutexAutoLock lock(gThreadNameFiltersMutex);
|
|
|
|
MOZ_ALWAYS_TRUE(gThreadNameFilters.resize(aFilterCount));
|
|
for (uint32_t i = 0; i < aFilterCount; ++i) {
|
|
gThreadNameFilters[i] = aThreadNameFilters[i];
|
|
}
|
|
}
|
|
|
|
// Deep copy aFeatures.
|
|
MOZ_ALWAYS_TRUE(gFeatures.resize(aFeatureCount));
|
|
for (uint32_t i = 0; i < aFeatureCount; ++i) {
|
|
gFeatures[i] = aFeatures[i];
|
|
}
|
|
|
|
bool mainThreadIO = hasFeature(aFeatures, aFeatureCount, "mainthreadio");
|
|
bool privacyMode = hasFeature(aFeatures, aFeatureCount, "privacy");
|
|
|
|
#if defined(PROFILE_JAVA)
|
|
gProfileJava = mozilla::jni::IsFennec() &&
|
|
hasFeature(aFeatures, aFeatureCount, "java");
|
|
#endif
|
|
gProfileJS = hasFeature(aFeatures, aFeatureCount, "js");
|
|
gTaskTracer = hasFeature(aFeatures, aFeatureCount, "tasktracer");
|
|
|
|
gAddLeafAddresses = hasFeature(aFeatures, aFeatureCount, "leaf");
|
|
gDisplayListDump = hasFeature(aFeatures, aFeatureCount, "displaylistdump");
|
|
gLayersDump = hasFeature(aFeatures, aFeatureCount, "layersdump");
|
|
gProfileGPU = hasFeature(aFeatures, aFeatureCount, "gpu");
|
|
gProfileMemory = hasFeature(aFeatures, aFeatureCount, "memory");
|
|
gProfileRestyle = hasFeature(aFeatures, aFeatureCount, "restyle");
|
|
// Profile non-main threads if we have a filter, because users sometimes ask
|
|
// to filter by a list of threads but forget to explicitly request.
|
|
// gProfileThreads must be set before the MaybeSetProfile() call below.
|
|
gProfileThreads = hasFeature(aFeatures, aFeatureCount, "threads") ||
|
|
aFilterCount > 0;
|
|
gUseStackWalk = hasFeature(aFeatures, aFeatureCount, "stackwalk");
|
|
|
|
gEntrySize = aProfileEntries ? aProfileEntries : PROFILE_DEFAULT_ENTRY;
|
|
gInterval = aInterval ? aInterval : PROFILE_DEFAULT_INTERVAL;
|
|
gBuffer = new ProfileBuffer(gEntrySize);
|
|
gSampler = new Sampler();
|
|
|
|
bool ignore;
|
|
gStartTime = mozilla::TimeStamp::ProcessCreation(ignore);
|
|
|
|
{
|
|
StaticMutexAutoLock lock(gRegisteredThreadsMutex);
|
|
|
|
// Set up profiling for each registered thread, if appropriate
|
|
for (uint32_t i = 0; i < gRegisteredThreads->size(); i++) {
|
|
ThreadInfo* info = gRegisteredThreads->at(i);
|
|
|
|
MaybeSetProfile(info);
|
|
}
|
|
}
|
|
|
|
#ifdef MOZ_TASK_TRACER
|
|
if (gTaskTracer) {
|
|
mozilla::tasktracer::StartLogging();
|
|
}
|
|
#endif
|
|
|
|
gGatherer = new mozilla::ProfileGatherer(gSampler);
|
|
|
|
MOZ_ASSERT(!gIsActive && !gIsPaused);
|
|
PlatformStart();
|
|
MOZ_ASSERT(gIsActive && !gIsPaused); // PlatformStart() sets gIsActive.
|
|
|
|
if (gProfileJS || privacyMode) {
|
|
mozilla::StaticMutexAutoLock lock(gRegisteredThreadsMutex);
|
|
|
|
for (uint32_t i = 0; i < gRegisteredThreads->size(); i++) {
|
|
ThreadInfo* info = (*gRegisteredThreads)[i];
|
|
if (info->IsPendingDelete() || !info->hasProfile()) {
|
|
continue;
|
|
}
|
|
info->Stack()->reinitializeOnResume();
|
|
if (gProfileJS) {
|
|
info->Stack()->enableJSSampling();
|
|
}
|
|
if (privacyMode) {
|
|
info->Stack()->mPrivacyMode = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
#if defined(PROFILE_JAVA)
|
|
if (gProfileJava) {
|
|
int javaInterval = aInterval;
|
|
// Java sampling doesn't accuratly keep up with 1ms sampling
|
|
if (javaInterval < 10) {
|
|
aInterval = 10;
|
|
}
|
|
mozilla::java::GeckoJavaSampler::Start(javaInterval, 1000);
|
|
}
|
|
#endif
|
|
|
|
if (mainThreadIO) {
|
|
if (!gInterposeObserver) {
|
|
// Lazily create IO interposer observer
|
|
gInterposeObserver = new mozilla::ProfilerIOInterposeObserver();
|
|
}
|
|
mozilla::IOInterposer::Register(mozilla::IOInterposeObserver::OpAll,
|
|
gInterposeObserver);
|
|
}
|
|
|
|
gIsProfiling = true;
|
|
|
|
if (CanNotifyObservers()) {
|
|
nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
|
|
if (os) {
|
|
nsTArray<nsCString> featuresArray;
|
|
nsTArray<nsCString> threadNameFiltersArray;
|
|
|
|
for (size_t i = 0; i < aFeatureCount; ++i) {
|
|
featuresArray.AppendElement(aFeatures[i]);
|
|
}
|
|
|
|
for (size_t i = 0; i < aFilterCount; ++i) {
|
|
threadNameFiltersArray.AppendElement(aThreadNameFilters[i]);
|
|
}
|
|
|
|
nsCOMPtr<nsIProfilerStartParams> params =
|
|
new nsProfilerStartParams(aProfileEntries, aInterval, featuresArray,
|
|
threadNameFiltersArray);
|
|
|
|
os->NotifyObservers(params, "profiler-started", nullptr);
|
|
}
|
|
}
|
|
|
|
LOG("END profiler_start");
|
|
}
|
|
|
|
void
|
|
profiler_stop()
|
|
{
|
|
MOZ_RELEASE_ASSERT(NS_IsMainThread());
|
|
|
|
LOG("BEGIN profiler_stop");
|
|
|
|
if (!stack_key_initialized)
|
|
return;
|
|
|
|
if (!gSampler) {
|
|
LOG("END profiler_stop-early");
|
|
return;
|
|
}
|
|
|
|
bool disableJS = gProfileJS;
|
|
|
|
{
|
|
StaticMutexAutoLock lock(gThreadNameFiltersMutex);
|
|
gThreadNameFilters.clear();
|
|
}
|
|
gFeatures.clear();
|
|
|
|
PlatformStop();
|
|
MOZ_ASSERT(!gIsActive && !gIsPaused); // PlatformStop() clears these.
|
|
|
|
gProfileJava = false;
|
|
gProfileJS = false;
|
|
gTaskTracer = false;
|
|
|
|
gAddLeafAddresses = false;
|
|
gDisplayListDump = false;
|
|
gLayersDump = false;
|
|
gProfileGPU = false;
|
|
gProfileMemory = false;
|
|
gProfileRestyle = false;
|
|
gProfileThreads = false;
|
|
gUseStackWalk = false;
|
|
|
|
if (gIsActive)
|
|
PlatformStop();
|
|
|
|
// Destroy ThreadInfo for all threads
|
|
{
|
|
StaticMutexAutoLock lock(gRegisteredThreadsMutex);
|
|
|
|
for (uint32_t i = 0; i < gRegisteredThreads->size(); i++) {
|
|
ThreadInfo* info = gRegisteredThreads->at(i);
|
|
// We've stopped profiling. We no longer need to retain
|
|
// information for an old thread.
|
|
if (info->IsPendingDelete()) {
|
|
// The stack was nulled when SetPendingDelete() was called.
|
|
MOZ_ASSERT(!info->Stack());
|
|
delete info;
|
|
gRegisteredThreads->erase(gRegisteredThreads->begin() + i);
|
|
i--;
|
|
}
|
|
}
|
|
}
|
|
|
|
#ifdef MOZ_TASK_TRACER
|
|
if (gTaskTracer) {
|
|
mozilla::tasktracer::StopLogging();
|
|
}
|
|
#endif
|
|
|
|
delete gSampler;
|
|
gSampler = nullptr;
|
|
gBuffer = nullptr;
|
|
gEntrySize = 0;
|
|
gInterval = 0;
|
|
|
|
// Cancel any in-flight async profile gatherering requests.
|
|
gGatherer->Cancel();
|
|
gGatherer = nullptr;
|
|
|
|
if (disableJS) {
|
|
PseudoStack *stack = tlsPseudoStack.get();
|
|
MOZ_ASSERT(stack != nullptr);
|
|
stack->disableJSSampling();
|
|
}
|
|
|
|
mozilla::IOInterposer::Unregister(mozilla::IOInterposeObserver::OpAll,
|
|
gInterposeObserver);
|
|
gInterposeObserver = nullptr;
|
|
|
|
gIsProfiling = false;
|
|
|
|
if (CanNotifyObservers()) {
|
|
nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
|
|
if (os)
|
|
os->NotifyObservers(nullptr, "profiler-stopped", nullptr);
|
|
}
|
|
|
|
LOG("END profiler_stop");
|
|
}
|
|
|
|
bool
|
|
profiler_is_paused()
|
|
{
|
|
MOZ_RELEASE_ASSERT(NS_IsMainThread());
|
|
|
|
if (!gSampler) {
|
|
return false;
|
|
}
|
|
|
|
return gIsPaused;
|
|
}
|
|
|
|
void
|
|
profiler_pause()
|
|
{
|
|
MOZ_RELEASE_ASSERT(NS_IsMainThread());
|
|
|
|
if (!gSampler) {
|
|
return;
|
|
}
|
|
|
|
gIsPaused = true;
|
|
if (CanNotifyObservers()) {
|
|
nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
|
|
if (os) {
|
|
os->NotifyObservers(nullptr, "profiler-paused", nullptr);
|
|
}
|
|
}
|
|
}
|
|
|
|
void
|
|
profiler_resume()
|
|
{
|
|
MOZ_RELEASE_ASSERT(NS_IsMainThread());
|
|
|
|
if (!gSampler) {
|
|
return;
|
|
}
|
|
|
|
gIsPaused = false;
|
|
if (CanNotifyObservers()) {
|
|
nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
|
|
if (os) {
|
|
os->NotifyObservers(nullptr, "profiler-resumed", nullptr);
|
|
}
|
|
}
|
|
}
|
|
|
|
bool
|
|
profiler_feature_active(const char* aName)
|
|
{
|
|
// This function runs both on and off the main thread.
|
|
|
|
if (!gIsProfiling) {
|
|
return false;
|
|
}
|
|
|
|
if (strcmp(aName, "gpu") == 0) {
|
|
return gProfileGPU;
|
|
}
|
|
|
|
if (strcmp(aName, "layersdump") == 0) {
|
|
return gLayersDump;
|
|
}
|
|
|
|
if (strcmp(aName, "displaylistdump") == 0) {
|
|
return gDisplayListDump;
|
|
}
|
|
|
|
if (strcmp(aName, "restyle") == 0) {
|
|
return gProfileRestyle;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool
|
|
profiler_is_active()
|
|
{
|
|
// This function runs both on and off the main thread.
|
|
|
|
return gIsProfiling;
|
|
}
|
|
|
|
void
|
|
profiler_set_frame_number(int frameNumber)
|
|
{
|
|
MOZ_RELEASE_ASSERT(NS_IsMainThread());
|
|
|
|
gFrameNumber = frameNumber;
|
|
}
|
|
|
|
void
|
|
profiler_lock()
|
|
{
|
|
MOZ_RELEASE_ASSERT(NS_IsMainThread());
|
|
|
|
profiler_stop();
|
|
nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
|
|
if (os)
|
|
os->NotifyObservers(nullptr, "profiler-locked", nullptr);
|
|
}
|
|
|
|
void
|
|
profiler_unlock()
|
|
{
|
|
MOZ_RELEASE_ASSERT(NS_IsMainThread());
|
|
|
|
nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
|
|
if (os)
|
|
os->NotifyObservers(nullptr, "profiler-unlocked", nullptr);
|
|
}
|
|
|
|
void
|
|
profiler_register_thread(const char* aName, void* aGuessStackTop)
|
|
{
|
|
// This function runs both on and off the main thread.
|
|
|
|
if (gInitCount == 0) {
|
|
return;
|
|
}
|
|
|
|
MOZ_ASSERT(tlsPseudoStack.get() == nullptr);
|
|
PseudoStack* stack = new PseudoStack();
|
|
tlsPseudoStack.set(stack);
|
|
bool isMainThread = is_main_thread_name(aName);
|
|
void* stackTop = GetStackTop(aGuessStackTop);
|
|
RegisterCurrentThread(aName, stack, isMainThread, stackTop);
|
|
}
|
|
|
|
void
|
|
profiler_unregister_thread()
|
|
{
|
|
// This function runs both on and off the main thread.
|
|
|
|
// Don't check gInitCount count here -- we may be unregistering the
|
|
// thread after the sampler was shut down.
|
|
if (!stack_key_initialized) {
|
|
return;
|
|
}
|
|
|
|
{
|
|
StaticMutexAutoLock lock(gRegisteredThreadsMutex);
|
|
|
|
if (gRegisteredThreads) {
|
|
Thread::tid_t id = Thread::GetCurrentId();
|
|
|
|
for (uint32_t i = 0; i < gRegisteredThreads->size(); i++) {
|
|
ThreadInfo* info = gRegisteredThreads->at(i);
|
|
if (info->ThreadId() == id && !info->IsPendingDelete()) {
|
|
if (profiler_is_active()) {
|
|
// We still want to show the results of this thread if you
|
|
// save the profile shortly after a thread is terminated.
|
|
// For now we will defer the delete to profile stop.
|
|
info->SetPendingDelete();
|
|
} else {
|
|
delete info;
|
|
gRegisteredThreads->erase(gRegisteredThreads->begin() + i);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// We just cut the ThreadInfo's PseudoStack pointer (either nulling it via
|
|
// SetPendingDelete() or by deleting the ThreadInfo altogether), so it is
|
|
// safe to delete the PseudoStack.
|
|
delete tlsPseudoStack.get();
|
|
tlsPseudoStack.set(nullptr);
|
|
}
|
|
|
|
void
|
|
profiler_thread_sleep()
|
|
{
|
|
// This function runs both on and off the main thread.
|
|
|
|
if (gInitCount == 0) {
|
|
return;
|
|
}
|
|
|
|
PseudoStack *stack = tlsPseudoStack.get();
|
|
if (stack == nullptr) {
|
|
return;
|
|
}
|
|
stack->setSleeping();
|
|
}
|
|
|
|
void
|
|
profiler_thread_wake()
|
|
{
|
|
// This function runs both on and off the main thread.
|
|
|
|
if (gInitCount == 0) {
|
|
return;
|
|
}
|
|
|
|
PseudoStack *stack = tlsPseudoStack.get();
|
|
if (stack == nullptr) {
|
|
return;
|
|
}
|
|
stack->setAwake();
|
|
}
|
|
|
|
bool
|
|
profiler_thread_is_sleeping()
|
|
{
|
|
// This function currently only used on the main thread, but that restriction
|
|
// (and this assertion) could be removed without too much difficulty.
|
|
MOZ_RELEASE_ASSERT(NS_IsMainThread());
|
|
|
|
if (gInitCount == 0) {
|
|
return false;
|
|
}
|
|
PseudoStack *stack = tlsPseudoStack.get();
|
|
if (stack == nullptr) {
|
|
return false;
|
|
}
|
|
return stack->isSleeping();
|
|
}
|
|
|
|
void
|
|
profiler_js_operation_callback()
|
|
{
|
|
// This function runs both on and off the main thread.
|
|
|
|
PseudoStack *stack = tlsPseudoStack.get();
|
|
if (!stack) {
|
|
return;
|
|
}
|
|
|
|
stack->jsOperationCallback();
|
|
}
|
|
|
|
double
|
|
profiler_time(const mozilla::TimeStamp& aTime)
|
|
{
|
|
// This function runs both on and off the main thread.
|
|
|
|
mozilla::TimeDuration delta = aTime - gStartTime;
|
|
return delta.ToMilliseconds();
|
|
}
|
|
|
|
double
|
|
profiler_time()
|
|
{
|
|
// This function runs both on and off the main thread.
|
|
|
|
return profiler_time(mozilla::TimeStamp::Now());
|
|
}
|
|
|
|
bool
|
|
profiler_in_privacy_mode()
|
|
{
|
|
// This function runs both on and off the main thread.
|
|
|
|
PseudoStack *stack = tlsPseudoStack.get();
|
|
if (!stack) {
|
|
return false;
|
|
}
|
|
return stack->mPrivacyMode;
|
|
}
|
|
|
|
UniqueProfilerBacktrace
|
|
profiler_get_backtrace()
|
|
{
|
|
MOZ_RELEASE_ASSERT(NS_IsMainThread());
|
|
|
|
if (!stack_key_initialized)
|
|
return nullptr;
|
|
|
|
// Don't capture a stack if we're not profiling
|
|
if (!profiler_is_active()) {
|
|
return nullptr;
|
|
}
|
|
|
|
// Don't capture a stack if we don't want to include personal information
|
|
if (profiler_in_privacy_mode()) {
|
|
return nullptr;
|
|
}
|
|
|
|
if (!gSampler) {
|
|
return nullptr;
|
|
}
|
|
|
|
PseudoStack* stack = tlsPseudoStack.get();
|
|
if (!stack) {
|
|
MOZ_ASSERT(stack);
|
|
return nullptr;
|
|
}
|
|
Thread::tid_t tid = Thread::GetCurrentId();
|
|
|
|
SyncProfile* profile = new SyncProfile(tid, stack);
|
|
|
|
TickSample sample;
|
|
sample.threadInfo = profile;
|
|
|
|
#if defined(HAVE_NATIVE_UNWIND)
|
|
#if defined(GP_OS_windows) || defined(GP_OS_linux) || defined(GP_OS_android)
|
|
tickcontext_t context;
|
|
sample.PopulateContext(&context);
|
|
#elif defined(GP_OS_darwin)
|
|
sample.PopulateContext(nullptr);
|
|
#else
|
|
# error "unknown platform"
|
|
#endif
|
|
#endif
|
|
|
|
sample.isSamplingCurrentThread = true;
|
|
sample.timestamp = mozilla::TimeStamp::Now();
|
|
|
|
profile->BeginUnwind();
|
|
Tick(&sample);
|
|
profile->EndUnwind();
|
|
|
|
return UniqueProfilerBacktrace(new ProfilerBacktrace(profile));
|
|
}
|
|
|
|
void
|
|
ProfilerBacktraceDestructor::operator()(ProfilerBacktrace* aBacktrace)
|
|
{
|
|
delete aBacktrace;
|
|
}
|
|
|
|
// Fill the output buffer with the following pattern:
|
|
// "Lable 1" "\0" "Label 2" "\0" ... "Label N" "\0" "\0"
|
|
// TODO: use the unwinder instead of pseudo stack.
|
|
void
|
|
profiler_get_backtrace_noalloc(char *output, size_t outputSize)
|
|
{
|
|
MOZ_RELEASE_ASSERT(NS_IsMainThread());
|
|
|
|
MOZ_ASSERT(outputSize >= 2);
|
|
char *bound = output + outputSize - 2;
|
|
output[0] = output[1] = '\0';
|
|
PseudoStack *pseudoStack = tlsPseudoStack.get();
|
|
if (!pseudoStack) {
|
|
return;
|
|
}
|
|
|
|
volatile StackEntry *pseudoFrames = pseudoStack->mStack;
|
|
uint32_t pseudoCount = pseudoStack->stackSize();
|
|
|
|
for (uint32_t i = 0; i < pseudoCount; i++) {
|
|
size_t len = strlen(pseudoFrames[i].label());
|
|
if (output + len >= bound)
|
|
break;
|
|
strcpy(output, pseudoFrames[i].label());
|
|
output += len;
|
|
*output++ = '\0';
|
|
*output = '\0';
|
|
}
|
|
}
|
|
|
|
void
|
|
profiler_tracing(const char* aCategory, const char* aInfo,
|
|
TracingMetadata aMetaData)
|
|
{
|
|
// This function runs both on and off the main thread.
|
|
|
|
// Don't insert a marker if we're not profiling, to avoid the heap copy
|
|
// (malloc).
|
|
if (!stack_key_initialized || !profiler_is_active()) {
|
|
return;
|
|
}
|
|
|
|
profiler_add_marker(aInfo, new ProfilerMarkerTracing(aCategory, aMetaData));
|
|
}
|
|
|
|
void
|
|
profiler_tracing(const char* aCategory, const char* aInfo,
|
|
UniqueProfilerBacktrace aCause, TracingMetadata aMetaData)
|
|
{
|
|
// This function runs both on and off the main thread.
|
|
|
|
// Don't insert a marker if we're not profiling, to avoid the heap copy
|
|
// (malloc).
|
|
if (!stack_key_initialized || !profiler_is_active()) {
|
|
return;
|
|
}
|
|
|
|
profiler_add_marker(aInfo, new ProfilerMarkerTracing(aCategory, aMetaData,
|
|
mozilla::Move(aCause)));
|
|
}
|
|
|
|
void
|
|
profiler_add_marker(const char *aMarker, ProfilerMarkerPayload *aPayload)
|
|
{
|
|
// This function runs both on and off the main thread.
|
|
|
|
// Note that aPayload may be allocated by the caller, so we need to make sure
|
|
// that we free it at some point.
|
|
mozilla::UniquePtr<ProfilerMarkerPayload> payload(aPayload);
|
|
|
|
if (!stack_key_initialized)
|
|
return;
|
|
|
|
// Don't insert a marker if we're not profiling to avoid
|
|
// the heap copy (malloc).
|
|
if (!profiler_is_active()) {
|
|
return;
|
|
}
|
|
|
|
// Don't add a marker if we don't want to include personal information
|
|
if (profiler_in_privacy_mode()) {
|
|
return;
|
|
}
|
|
|
|
PseudoStack *stack = tlsPseudoStack.get();
|
|
if (!stack) {
|
|
return;
|
|
}
|
|
|
|
mozilla::TimeStamp origin = (aPayload && !aPayload->GetStartTime().IsNull()) ?
|
|
aPayload->GetStartTime() : mozilla::TimeStamp::Now();
|
|
mozilla::TimeDuration delta = origin - gStartTime;
|
|
stack->addMarker(aMarker, payload.release(), delta.ToMilliseconds());
|
|
}
|
|
|
|
// END externally visible functions
|
|
////////////////////////////////////////////////////////////////////////
|
|
|
|
void PseudoStack::flushSamplerOnJSShutdown()
|
|
{
|
|
MOZ_ASSERT(mContext);
|
|
|
|
if (!gIsActive) {
|
|
return;
|
|
}
|
|
|
|
gIsPaused = true;
|
|
|
|
{
|
|
StaticMutexAutoLock lock(gRegisteredThreadsMutex);
|
|
|
|
for (size_t i = 0; i < gRegisteredThreads->size(); i++) {
|
|
// Thread not being profiled, skip it.
|
|
ThreadInfo* info = gRegisteredThreads->at(i);
|
|
if (!info->hasProfile() || info->IsPendingDelete()) {
|
|
continue;
|
|
}
|
|
|
|
// Thread not profiling the context that's going away, skip it.
|
|
if (info->Stack()->mContext != mContext) {
|
|
continue;
|
|
}
|
|
|
|
MutexAutoLock lock(info->GetMutex());
|
|
info->FlushSamplesAndMarkers();
|
|
}
|
|
}
|
|
|
|
gIsPaused = false;
|
|
}
|
|
|
|
// We #include these files directly because it means those files can use
|
|
// declarations from this file trivially.
|
|
#if defined(GP_OS_windows)
|
|
# include "platform-win32.cpp"
|
|
#elif defined(GP_OS_darwin)
|
|
# include "platform-macos.cpp"
|
|
#elif defined(GP_OS_linux) || defined(GP_OS_android)
|
|
# include "platform-linux-android.cpp"
|
|
#else
|
|
# error "bad platform"
|
|
#endif
|
|
|