зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1357829 - Part 1: Expose profiler_suspend_and_sample_thread, r=njn
This patch performs a refactoring to the internals of the profiler in order to expose a function, profiler_suspend_and_sample_thread, which can be called from a background thread to suspend, sample the native stack, and then resume the target passed-in thread. The interface was designed to expose as few internals of the profiler as possible, exposing only a single callback which accepts the list of program counters and stack pointers collected during the backtrace. A method `profiler_current_thread_id` was also added to get the thread_id of the current thread, which can then be passed by another thread into profiler_suspend_sample_thread to sample the stack of that thread. This is implemented in two parts: 1) Splitting SamplerThread into two classes: Sampler, and SamplerThread. Sampler was created to extract the core logic from SamplerThread which manages unix signals on android and linux, as well as suspends the target thread on all platforms. SamplerThread was then modified to subclass this type, adding the extra methods and fields required for the creation and management of the actual Sampler Thread. Some work was done to ensure that the methods on Sampler would not require ActivePS to be present, as we intend to sample threads when the profiler is not active for the Background Hang Reporter. 2) Moving the Tick() logic into the TickController interface. A TickController interface was added to platform which has 2 methods: Tick and Backtrace. The Tick method replaces the previous Tick() static method, allowing it to be overridden by a different consumer of SuspendAndSampleAndResumeThread, while the Backtrace() method replaces the previous MergeStacksIntoProfile method, allowing it to be overridden by different consumers of DoNativeBacktrace. This interface object is then used to wrap implementation specific data, such as the ProfilerBuffer, and is threaded through the SuspendAndSampleAndResumeThread and DoNativeBacktrace methods. This change added 2 virtual calls to the SamplerThread's critical section, which I believe should be a small enough overhead that it will not affect profiling performance. These virtual calls could be avoided using templating, but I decided that doing so would be unnecessary. MozReview-Commit-ID: AT48xb2asgV
This commit is contained in:
Родитель
9e4c9d88a1
Коммит
029576f19d
|
@ -130,7 +130,7 @@ public:
|
|||
};
|
||||
|
||||
////////////////////////////////////////////////////////////////////////
|
||||
// BEGIN SamplerThread target specifics
|
||||
// BEGIN Sampler target specifics
|
||||
|
||||
// The only way to reliably interrupt a Linux thread and inspect its register
|
||||
// and stack state is by sending a signal to it, and doing the work inside the
|
||||
|
@ -199,7 +199,7 @@ struct SigHandlerCoordinator
|
|||
ucontext_t mUContext; // Context at signal
|
||||
};
|
||||
|
||||
struct SigHandlerCoordinator* SamplerThread::sSigHandlerCoordinator = nullptr;
|
||||
struct SigHandlerCoordinator* Sampler::sSigHandlerCoordinator = nullptr;
|
||||
|
||||
static void
|
||||
SigprofHandler(int aSignal, siginfo_t* aInfo, void* aContext)
|
||||
|
@ -208,18 +208,18 @@ SigprofHandler(int aSignal, siginfo_t* aInfo, void* aContext)
|
|||
int savedErrno = errno;
|
||||
|
||||
MOZ_ASSERT(aSignal == SIGPROF);
|
||||
MOZ_ASSERT(SamplerThread::sSigHandlerCoordinator);
|
||||
MOZ_ASSERT(Sampler::sSigHandlerCoordinator);
|
||||
|
||||
// By sending us this signal, the sampler thread has sent us message 1 in
|
||||
// the comment above, with the meaning "|sSigHandlerCoordinator| is ready
|
||||
// for use, please copy your register context into it."
|
||||
SamplerThread::sSigHandlerCoordinator->mUContext =
|
||||
Sampler::sSigHandlerCoordinator->mUContext =
|
||||
*static_cast<ucontext_t*>(aContext);
|
||||
|
||||
// Send message 2: tell the sampler thread that the context has been copied
|
||||
// into |sSigHandlerCoordinator->mUContext|. sem_post can never fail by
|
||||
// being interrupted by a signal, so there's no loop around this call.
|
||||
int r = sem_post(&SamplerThread::sSigHandlerCoordinator->mMessage2);
|
||||
int r = sem_post(&Sampler::sSigHandlerCoordinator->mMessage2);
|
||||
MOZ_ASSERT(r == 0);
|
||||
|
||||
// At this point, the sampler thread assumes we are suspended, so we must
|
||||
|
@ -227,7 +227,7 @@ SigprofHandler(int aSignal, siginfo_t* aInfo, void* aContext)
|
|||
|
||||
// Wait for message 3: the sampler thread tells us to resume.
|
||||
while (true) {
|
||||
r = sem_wait(&SamplerThread::sSigHandlerCoordinator->mMessage3);
|
||||
r = sem_wait(&Sampler::sSigHandlerCoordinator->mMessage3);
|
||||
if (r == -1 && errno == EINTR) {
|
||||
// Interrupted by a signal. Try again.
|
||||
continue;
|
||||
|
@ -240,29 +240,17 @@ SigprofHandler(int aSignal, siginfo_t* aInfo, void* aContext)
|
|||
// Send message 4: tell the sampler thread that we are finished accessing
|
||||
// |sSigHandlerCoordinator|. After this point it is not safe to touch
|
||||
// |sSigHandlerCoordinator|.
|
||||
r = sem_post(&SamplerThread::sSigHandlerCoordinator->mMessage4);
|
||||
r = sem_post(&Sampler::sSigHandlerCoordinator->mMessage4);
|
||||
MOZ_ASSERT(r == 0);
|
||||
|
||||
errno = savedErrno;
|
||||
}
|
||||
|
||||
static void*
|
||||
ThreadEntry(void* aArg)
|
||||
{
|
||||
auto thread = static_cast<SamplerThread*>(aArg);
|
||||
thread->mSamplerTid = gettid();
|
||||
thread->Run();
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
SamplerThread::SamplerThread(PSLockRef aLock, uint32_t aActivityGeneration,
|
||||
double aIntervalMilliseconds)
|
||||
: mActivityGeneration(aActivityGeneration)
|
||||
, mIntervalMicroseconds(
|
||||
std::max(1, int(floor(aIntervalMilliseconds * 1000 + 0.5))))
|
||||
, mMyPid(getpid())
|
||||
Sampler::Sampler(PSLockRef aLock)
|
||||
: mMyPid(getpid())
|
||||
// We don't know what the sampler thread's ID will be until it runs, so set
|
||||
// mSamplerTid to a dummy value and fill it in for real in ThreadEntry().
|
||||
// mSamplerTid to a dummy value and fill it in for real in
|
||||
// SuspendAndSampleAndResumeThread().
|
||||
, mSamplerTid(-1)
|
||||
{
|
||||
#if defined(USE_EHABI_STACKWALK)
|
||||
|
@ -302,64 +290,28 @@ SamplerThread::SamplerThread(PSLockRef aLock, uint32_t aActivityGeneration,
|
|||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
// Start the sampling thread. It repeatedly sends a SIGPROF signal. Sending
|
||||
// the signal ourselves instead of relying on itimer provides much better
|
||||
// accuracy.
|
||||
if (pthread_create(&mThread, nullptr, ThreadEntry, this) != 0) {
|
||||
MOZ_CRASH("pthread_create failed");
|
||||
}
|
||||
}
|
||||
|
||||
SamplerThread::~SamplerThread()
|
||||
{
|
||||
pthread_join(mThread, nullptr);
|
||||
}
|
||||
|
||||
void
|
||||
SamplerThread::Stop(PSLockRef aLock)
|
||||
Sampler::Disable(PSLockRef aLock)
|
||||
{
|
||||
// Restore old signal handler. This is global state so it's important that
|
||||
// we do it now, while gPSMutex is locked. It's safe to do this now even
|
||||
// though this SamplerThread is still alive, because the next time the main
|
||||
// loop of Run() iterates it won't get past the mActivityGeneration check,
|
||||
// and so won't send any signals.
|
||||
// we do it now, while gPSMutex is locked.
|
||||
sigaction(SIGPROF, &mOldSigprofHandler, 0);
|
||||
}
|
||||
|
||||
void
|
||||
SamplerThread::SleepMicro(uint32_t aMicroseconds)
|
||||
{
|
||||
if (aMicroseconds >= 1000000) {
|
||||
// Use usleep for larger intervals, because the nanosleep
|
||||
// code below only supports intervals < 1 second.
|
||||
MOZ_ALWAYS_TRUE(!::usleep(aMicroseconds));
|
||||
return;
|
||||
}
|
||||
|
||||
struct timespec ts;
|
||||
ts.tv_sec = 0;
|
||||
ts.tv_nsec = aMicroseconds * 1000UL;
|
||||
|
||||
int rv = ::nanosleep(&ts, &ts);
|
||||
|
||||
while (rv != 0 && errno == EINTR) {
|
||||
// Keep waiting in case of interrupt.
|
||||
// nanosleep puts the remaining time back into ts.
|
||||
rv = ::nanosleep(&ts, &ts);
|
||||
}
|
||||
|
||||
MOZ_ASSERT(!rv, "nanosleep call failed");
|
||||
}
|
||||
|
||||
void
|
||||
SamplerThread::SuspendAndSampleAndResumeThread(PSLockRef aLock,
|
||||
TickSample& aSample)
|
||||
Sampler::SuspendAndSampleAndResumeThread(PSLockRef aLock,
|
||||
TickController& aController,
|
||||
TickSample& aSample)
|
||||
{
|
||||
// Only one sampler thread can be sampling at once. So we expect to have
|
||||
// complete control over |sSigHandlerCoordinator|.
|
||||
MOZ_ASSERT(!sSigHandlerCoordinator);
|
||||
|
||||
if (mSamplerTid == -1) {
|
||||
mSamplerTid = gettid();
|
||||
}
|
||||
int sampleeTid = aSample.mThreadId;
|
||||
MOZ_RELEASE_ASSERT(sampleeTid != mSamplerTid);
|
||||
|
||||
|
@ -406,7 +358,7 @@ SamplerThread::SuspendAndSampleAndResumeThread(PSLockRef aLock,
|
|||
// Extract the current PC and sp.
|
||||
FillInSample(aSample, &sSigHandlerCoordinator->mUContext);
|
||||
|
||||
Tick(aLock, ActivePS::Buffer(aLock), aSample);
|
||||
aController.Tick(aLock, aSample);
|
||||
|
||||
//----------------------------------------------------------------//
|
||||
// Resume the target thread.
|
||||
|
@ -436,6 +388,76 @@ SamplerThread::SuspendAndSampleAndResumeThread(PSLockRef aLock,
|
|||
sSigHandlerCoordinator = nullptr;
|
||||
}
|
||||
|
||||
// END Sampler target specifics
|
||||
////////////////////////////////////////////////////////////////////////
|
||||
|
||||
////////////////////////////////////////////////////////////////////////
|
||||
// BEGIN SamplerThread target specifics
|
||||
|
||||
static void*
|
||||
ThreadEntry(void* aArg)
|
||||
{
|
||||
auto thread = static_cast<SamplerThread*>(aArg);
|
||||
thread->Run();
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
SamplerThread::SamplerThread(PSLockRef aLock, uint32_t aActivityGeneration,
|
||||
double aIntervalMilliseconds)
|
||||
: Sampler(aLock)
|
||||
, mActivityGeneration(aActivityGeneration)
|
||||
, mIntervalMicroseconds(
|
||||
std::max(1, int(floor(aIntervalMilliseconds * 1000 + 0.5))))
|
||||
{
|
||||
// Start the sampling thread. It repeatedly sends a SIGPROF signal. Sending
|
||||
// the signal ourselves instead of relying on itimer provides much better
|
||||
// accuracy.
|
||||
if (pthread_create(&mThread, nullptr, ThreadEntry, this) != 0) {
|
||||
MOZ_CRASH("pthread_create failed");
|
||||
}
|
||||
}
|
||||
|
||||
SamplerThread::~SamplerThread()
|
||||
{
|
||||
pthread_join(mThread, nullptr);
|
||||
}
|
||||
|
||||
void
|
||||
SamplerThread::SleepMicro(uint32_t aMicroseconds)
|
||||
{
|
||||
if (aMicroseconds >= 1000000) {
|
||||
// Use usleep for larger intervals, because the nanosleep
|
||||
// code below only supports intervals < 1 second.
|
||||
MOZ_ALWAYS_TRUE(!::usleep(aMicroseconds));
|
||||
return;
|
||||
}
|
||||
|
||||
struct timespec ts;
|
||||
ts.tv_sec = 0;
|
||||
ts.tv_nsec = aMicroseconds * 1000UL;
|
||||
|
||||
int rv = ::nanosleep(&ts, &ts);
|
||||
|
||||
while (rv != 0 && errno == EINTR) {
|
||||
// Keep waiting in case of interrupt.
|
||||
// nanosleep puts the remaining time back into ts.
|
||||
rv = ::nanosleep(&ts, &ts);
|
||||
}
|
||||
|
||||
MOZ_ASSERT(!rv, "nanosleep call failed");
|
||||
}
|
||||
|
||||
void
|
||||
SamplerThread::Stop(PSLockRef aLock)
|
||||
{
|
||||
// Restore old signal handler. This is global state so it's important that
|
||||
// we do it now, while gPSMutex is locked. It's safe to do this now even
|
||||
// though this SamplerThread is still alive, because the next time the main
|
||||
// loop of Run() iterates it won't get past the mActivityGeneration check,
|
||||
// and so won't send any signals.
|
||||
Sampler::Disable(aLock);
|
||||
}
|
||||
|
||||
// END SamplerThread target specifics
|
||||
////////////////////////////////////////////////////////////////////////
|
||||
|
||||
|
|
|
@ -62,51 +62,21 @@ private:
|
|||
};
|
||||
|
||||
////////////////////////////////////////////////////////////////////////
|
||||
// BEGIN SamplerThread target specifics
|
||||
// BEGIN Sampler target specifics
|
||||
|
||||
static void*
|
||||
ThreadEntry(void* aArg)
|
||||
{
|
||||
auto thread = static_cast<SamplerThread*>(aArg);
|
||||
thread->Run();
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
SamplerThread::SamplerThread(PSLockRef aLock, uint32_t aActivityGeneration,
|
||||
double aIntervalMilliseconds)
|
||||
: mActivityGeneration(aActivityGeneration)
|
||||
, mIntervalMicroseconds(
|
||||
std::max(1, int(floor(aIntervalMilliseconds * 1000 + 0.5))))
|
||||
{
|
||||
pthread_attr_t* attr_ptr = nullptr;
|
||||
if (pthread_create(&mThread, attr_ptr, ThreadEntry, this) != 0) {
|
||||
MOZ_CRASH("pthread_create failed");
|
||||
}
|
||||
}
|
||||
|
||||
SamplerThread::~SamplerThread()
|
||||
{
|
||||
pthread_join(mThread, nullptr);
|
||||
}
|
||||
|
||||
void
|
||||
SamplerThread::Stop(PSLockRef aLock)
|
||||
Sampler::Sampler(PSLockRef aLock)
|
||||
{
|
||||
}
|
||||
|
||||
void
|
||||
SamplerThread::SleepMicro(uint32_t aMicroseconds)
|
||||
Sampler::Disable(PSLockRef aLock)
|
||||
{
|
||||
usleep(aMicroseconds);
|
||||
// FIXME: the OSX 10.12 page for usleep says "The usleep() function is
|
||||
// obsolescent. Use nanosleep(2) instead." This implementation could be
|
||||
// merged with the linux-android version. Also, this doesn't handle the
|
||||
// case where the usleep call is interrupted by a signal.
|
||||
}
|
||||
|
||||
void
|
||||
SamplerThread::SuspendAndSampleAndResumeThread(PSLockRef aLock,
|
||||
TickSample& aSample)
|
||||
Sampler::SuspendAndSampleAndResumeThread(PSLockRef aLock,
|
||||
TickController& aController,
|
||||
TickSample& aSample)
|
||||
{
|
||||
thread_act_t samplee_thread = aSample.mPlatformData->ProfiledThread();
|
||||
|
||||
|
@ -148,7 +118,7 @@ SamplerThread::SuspendAndSampleAndResumeThread(PSLockRef aLock,
|
|||
aSample.mSP = reinterpret_cast<Address>(state.REGISTER_FIELD(sp));
|
||||
aSample.mFP = reinterpret_cast<Address>(state.REGISTER_FIELD(bp));
|
||||
|
||||
Tick(aLock, ActivePS::Buffer(aLock), aSample);
|
||||
aController.Tick(aLock, aSample);
|
||||
}
|
||||
|
||||
#undef REGISTER_FIELD
|
||||
|
@ -163,6 +133,54 @@ SamplerThread::SuspendAndSampleAndResumeThread(PSLockRef aLock,
|
|||
// WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING
|
||||
}
|
||||
|
||||
// END Sampler target specifics
|
||||
////////////////////////////////////////////////////////////////////////
|
||||
|
||||
////////////////////////////////////////////////////////////////////////
|
||||
// BEGIN SamplerThread target specifics
|
||||
|
||||
static void*
|
||||
ThreadEntry(void* aArg)
|
||||
{
|
||||
auto thread = static_cast<SamplerThread*>(aArg);
|
||||
thread->Run();
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
SamplerThread::SamplerThread(PSLockRef aLock, uint32_t aActivityGeneration,
|
||||
double aIntervalMilliseconds)
|
||||
: Sampler(aLock)
|
||||
, mActivityGeneration(aActivityGeneration)
|
||||
, mIntervalMicroseconds(
|
||||
std::max(1, int(floor(aIntervalMilliseconds * 1000 + 0.5))))
|
||||
{
|
||||
pthread_attr_t* attr_ptr = nullptr;
|
||||
if (pthread_create(&mThread, attr_ptr, ThreadEntry, this) != 0) {
|
||||
MOZ_CRASH("pthread_create failed");
|
||||
}
|
||||
}
|
||||
|
||||
SamplerThread::~SamplerThread()
|
||||
{
|
||||
pthread_join(mThread, nullptr);
|
||||
}
|
||||
|
||||
void
|
||||
SamplerThread::SleepMicro(uint32_t aMicroseconds)
|
||||
{
|
||||
usleep(aMicroseconds);
|
||||
// FIXME: the OSX 10.12 page for usleep says "The usleep() function is
|
||||
// obsolescent. Use nanosleep(2) instead." This implementation could be
|
||||
// merged with the linux-android version. Also, this doesn't handle the
|
||||
// case where the usleep call is interrupted by a signal.
|
||||
}
|
||||
|
||||
void
|
||||
SamplerThread::Stop(PSLockRef aLock)
|
||||
{
|
||||
Sampler::Disable(aLock);
|
||||
}
|
||||
|
||||
// END SamplerThread target specifics
|
||||
////////////////////////////////////////////////////////////////////////
|
||||
|
||||
|
|
|
@ -79,101 +79,26 @@ GetThreadHandle(PlatformData* aData)
|
|||
static const HANDLE kNoThread = INVALID_HANDLE_VALUE;
|
||||
|
||||
////////////////////////////////////////////////////////////////////////
|
||||
// BEGIN SamplerThread target specifics
|
||||
// BEGIN Sampler target specifics
|
||||
|
||||
static unsigned int __stdcall
|
||||
ThreadEntry(void* aArg)
|
||||
Sampler::Sampler(PSLockRef aLock)
|
||||
{
|
||||
auto thread = static_cast<SamplerThread*>(aArg);
|
||||
thread->Run();
|
||||
return 0;
|
||||
}
|
||||
|
||||
SamplerThread::SamplerThread(PSLockRef aLock, uint32_t aActivityGeneration,
|
||||
double aIntervalMilliseconds)
|
||||
: mActivityGeneration(aActivityGeneration)
|
||||
, mIntervalMicroseconds(
|
||||
std::max(1, int(floor(aIntervalMilliseconds * 1000 + 0.5))))
|
||||
{
|
||||
// By default we'll not adjust the timer resolution which tends to be
|
||||
// around 16ms. However, if the requested interval is sufficiently low
|
||||
// we'll try to adjust the resolution to match.
|
||||
if (mIntervalMicroseconds < 10*1000) {
|
||||
::timeBeginPeriod(mIntervalMicroseconds / 1000);
|
||||
}
|
||||
|
||||
// Create a new thread. It is important to use _beginthreadex() instead of
|
||||
// the Win32 function CreateThread(), because the CreateThread() does not
|
||||
// initialize thread-specific structures in the C runtime library.
|
||||
mThread = reinterpret_cast<HANDLE>(
|
||||
_beginthreadex(nullptr,
|
||||
/* stack_size */ 0,
|
||||
ThreadEntry,
|
||||
this,
|
||||
/* initflag */ 0,
|
||||
nullptr));
|
||||
if (mThread == 0) {
|
||||
MOZ_CRASH("_beginthreadex failed");
|
||||
}
|
||||
}
|
||||
|
||||
SamplerThread::~SamplerThread()
|
||||
{
|
||||
WaitForSingleObject(mThread, INFINITE);
|
||||
|
||||
// Close our own handle for the thread.
|
||||
if (mThread != kNoThread) {
|
||||
CloseHandle(mThread);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
SamplerThread::Stop(PSLockRef aLock)
|
||||
Sampler::Disable(PSLockRef aLock)
|
||||
{
|
||||
// Disable any timer resolution changes we've made. Do it now while
|
||||
// gPSMutex is locked, i.e. before any other SamplerThread can be created
|
||||
// and call ::timeBeginPeriod().
|
||||
//
|
||||
// It's safe to do this now even though this SamplerThread is still alive,
|
||||
// because the next time the main loop of Run() iterates it won't get past
|
||||
// the mActivityGeneration check, and so it won't make any more ::Sleep()
|
||||
// calls.
|
||||
if (mIntervalMicroseconds < 10 * 1000) {
|
||||
::timeEndPeriod(mIntervalMicroseconds / 1000);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
SamplerThread::SleepMicro(uint32_t aMicroseconds)
|
||||
{
|
||||
// For now, keep the old behaviour of minimum Sleep(1), even for
|
||||
// smaller-than-usual sleeps after an overshoot, unless the user has
|
||||
// explicitly opted into a sub-millisecond profiler interval.
|
||||
if (mIntervalMicroseconds >= 1000) {
|
||||
::Sleep(std::max(1u, aMicroseconds / 1000));
|
||||
} else {
|
||||
TimeStamp start = TimeStamp::Now();
|
||||
TimeStamp end = start + TimeDuration::FromMicroseconds(aMicroseconds);
|
||||
|
||||
// First, sleep for as many whole milliseconds as possible.
|
||||
if (aMicroseconds >= 1000) {
|
||||
::Sleep(aMicroseconds / 1000);
|
||||
}
|
||||
|
||||
// Then, spin until enough time has passed.
|
||||
while (TimeStamp::Now() < end) {
|
||||
_mm_pause();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
SamplerThread::SuspendAndSampleAndResumeThread(PSLockRef aLock,
|
||||
TickSample& aSample)
|
||||
Sampler::SuspendAndSampleAndResumeThread(PSLockRef aLock,
|
||||
TickController& aController,
|
||||
TickSample& aSample)
|
||||
{
|
||||
HANDLE profiled_thread = aSample.mPlatformData->ProfiledThread();
|
||||
if (profiled_thread == nullptr)
|
||||
if (profiled_thread == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Context used for sampling the register state of the profiled thread.
|
||||
CONTEXT context;
|
||||
|
@ -222,7 +147,7 @@ SamplerThread::SuspendAndSampleAndResumeThread(PSLockRef aLock,
|
|||
aSample.mFP = reinterpret_cast<Address>(context.Ebp);
|
||||
#endif
|
||||
|
||||
Tick(aLock, ActivePS::Buffer(aLock), aSample);
|
||||
aController.Tick(aLock, aSample);
|
||||
|
||||
//----------------------------------------------------------------//
|
||||
// Resume the target thread.
|
||||
|
@ -234,6 +159,101 @@ SamplerThread::SuspendAndSampleAndResumeThread(PSLockRef aLock,
|
|||
// WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING
|
||||
}
|
||||
|
||||
// END Sampler target specifics
|
||||
////////////////////////////////////////////////////////////////////////
|
||||
|
||||
////////////////////////////////////////////////////////////////////////
|
||||
// BEGIN SamplerThread target specifics
|
||||
|
||||
static unsigned int __stdcall
|
||||
ThreadEntry(void* aArg)
|
||||
{
|
||||
auto thread = static_cast<SamplerThread*>(aArg);
|
||||
thread->Run();
|
||||
return 0;
|
||||
}
|
||||
|
||||
SamplerThread::SamplerThread(PSLockRef aLock, uint32_t aActivityGeneration,
|
||||
double aIntervalMilliseconds)
|
||||
: Sampler(aLock)
|
||||
, mActivityGeneration(aActivityGeneration)
|
||||
, mIntervalMicroseconds(
|
||||
std::max(1, int(floor(aIntervalMilliseconds * 1000 + 0.5))))
|
||||
{
|
||||
// By default we'll not adjust the timer resolution which tends to be
|
||||
// around 16ms. However, if the requested interval is sufficiently low
|
||||
// we'll try to adjust the resolution to match.
|
||||
if (mIntervalMicroseconds < 10*1000) {
|
||||
::timeBeginPeriod(mIntervalMicroseconds / 1000);
|
||||
}
|
||||
|
||||
// Create a new thread. It is important to use _beginthreadex() instead of
|
||||
// the Win32 function CreateThread(), because the CreateThread() does not
|
||||
// initialize thread-specific structures in the C runtime library.
|
||||
mThread = reinterpret_cast<HANDLE>(
|
||||
_beginthreadex(nullptr,
|
||||
/* stack_size */ 0,
|
||||
ThreadEntry,
|
||||
this,
|
||||
/* initflag */ 0,
|
||||
nullptr));
|
||||
if (mThread == 0) {
|
||||
MOZ_CRASH("_beginthreadex failed");
|
||||
}
|
||||
}
|
||||
|
||||
SamplerThread::~SamplerThread()
|
||||
{
|
||||
WaitForSingleObject(mThread, INFINITE);
|
||||
|
||||
// Close our own handle for the thread.
|
||||
if (mThread != kNoThread) {
|
||||
CloseHandle(mThread);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
SamplerThread::SleepMicro(uint32_t aMicroseconds)
|
||||
{
|
||||
// For now, keep the old behaviour of minimum Sleep(1), even for
|
||||
// smaller-than-usual sleeps after an overshoot, unless the user has
|
||||
// explicitly opted into a sub-millisecond profiler interval.
|
||||
if (mIntervalMicroseconds >= 1000) {
|
||||
::Sleep(std::max(1u, aMicroseconds / 1000));
|
||||
} else {
|
||||
TimeStamp start = TimeStamp::Now();
|
||||
TimeStamp end = start + TimeDuration::FromMicroseconds(aMicroseconds);
|
||||
|
||||
// First, sleep for as many whole milliseconds as possible.
|
||||
if (aMicroseconds >= 1000) {
|
||||
::Sleep(aMicroseconds / 1000);
|
||||
}
|
||||
|
||||
// Then, spin until enough time has passed.
|
||||
while (TimeStamp::Now() < end) {
|
||||
_mm_pause();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
SamplerThread::Stop(PSLockRef aLock)
|
||||
{
|
||||
// Disable any timer resolution changes we've made. Do it now while
|
||||
// gPSMutex is locked, i.e. before any other SamplerThread can be created
|
||||
// and call ::timeBeginPeriod().
|
||||
//
|
||||
// It's safe to do this now even though this SamplerThread is still alive,
|
||||
// because the next time the main loop of Run() iterates it won't get past
|
||||
// the mActivityGeneration check, and so it won't make any more ::Sleep()
|
||||
// calls.
|
||||
if (mIntervalMicroseconds < 10 * 1000) {
|
||||
::timeEndPeriod(mIntervalMicroseconds / 1000);
|
||||
}
|
||||
|
||||
Sampler::Disable(aLock);
|
||||
}
|
||||
|
||||
// END SamplerThread target specifics
|
||||
////////////////////////////////////////////////////////////////////////
|
||||
|
||||
|
|
|
@ -815,6 +815,15 @@ struct NativeStack
|
|||
{}
|
||||
};
|
||||
|
||||
class TickController
|
||||
{
|
||||
public:
|
||||
// NOTE: This method is called when the target thread of the sample is paused.
|
||||
// Do not allocate or attempt to grab any locks during this function call.
|
||||
virtual void Tick(PSLockRef aLock,
|
||||
const TickSample& aSample) = 0;
|
||||
};
|
||||
|
||||
Atomic<bool> WALKING_JS_STACK(false);
|
||||
|
||||
struct AutoWalkJSStack
|
||||
|
@ -1045,24 +1054,22 @@ StackWalkCallback(uint32_t aFrameNumber, void* aPC, void* aSP, void* aClosure)
|
|||
}
|
||||
|
||||
static void
|
||||
DoNativeBacktrace(PSLockRef aLock, ProfileBuffer* aBuffer,
|
||||
DoNativeBacktrace(PSLockRef aLock, NativeStack& aNativeStack,
|
||||
const TickSample& aSample)
|
||||
{
|
||||
NativeStack nativeStack;
|
||||
|
||||
// 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.mPC, aSample.mSP, &nativeStack);
|
||||
StackWalkCallback(/* frameNum */ 0, aSample.mPC, aSample.mSP, &aNativeStack);
|
||||
|
||||
uint32_t maxFrames = uint32_t(MAX_NATIVE_FRAMES - nativeStack.mCount);
|
||||
uint32_t maxFrames = uint32_t(MAX_NATIVE_FRAMES - aNativeStack.mCount);
|
||||
|
||||
#if defined(GP_OS_darwin) || (defined(GP_PLAT_x86_windows))
|
||||
void* stackEnd = aSample.mStackTop;
|
||||
if (aSample.mFP >= aSample.mSP && aSample.mFP <= stackEnd) {
|
||||
FramePointerStackWalk(StackWalkCallback, /* skipFrames */ 0, maxFrames,
|
||||
&nativeStack, reinterpret_cast<void**>(aSample.mFP),
|
||||
&aNativeStack, reinterpret_cast<void**>(aSample.mFP),
|
||||
stackEnd);
|
||||
}
|
||||
#else
|
||||
|
@ -1070,21 +1077,17 @@ DoNativeBacktrace(PSLockRef aLock, ProfileBuffer* aBuffer,
|
|||
// MozStackWalk().
|
||||
uintptr_t thread = GetThreadHandle(aSample.mPlatformData);
|
||||
MOZ_ASSERT(thread);
|
||||
MozStackWalk(StackWalkCallback, /* skipFrames */ 0, maxFrames, &nativeStack,
|
||||
MozStackWalk(StackWalkCallback, /* skipFrames */ 0, maxFrames, &aNativeStack,
|
||||
thread, /* platformData */ nullptr);
|
||||
#endif
|
||||
|
||||
MergeStacksIntoProfile(aLock, aBuffer, aSample, nativeStack);
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef USE_EHABI_STACKWALK
|
||||
static void
|
||||
DoNativeBacktrace(PSLockRef aLock, ProfileBuffer* aBuffer,
|
||||
DoNativeBacktrace(PSLockRef aLock, NativeStack& aNativeStack,
|
||||
const TickSample& aSample)
|
||||
{
|
||||
NativeStack nativeStack;
|
||||
|
||||
const mcontext_t* mcontext = &aSample.mContext->uc_mcontext;
|
||||
mcontext_t savedContext;
|
||||
NotNull<RacyThreadInfo*> racyInfo = aSample.mRacyInfo;
|
||||
|
@ -1104,11 +1107,11 @@ DoNativeBacktrace(PSLockRef aLock, ProfileBuffer* aBuffer,
|
|||
// the saved state.
|
||||
uint32_t* vSP = reinterpret_cast<uint32_t*>(entry.stackAddress());
|
||||
|
||||
nativeStack.mCount +=
|
||||
aNativeStack.mCount +=
|
||||
EHABIStackWalk(*mcontext, /* stackBase = */ vSP,
|
||||
nativeStack.mSPs + nativeStack.mCount,
|
||||
nativeStack.mPCs + nativeStack.mCount,
|
||||
MAX_NATIVE_FRAMES - nativeStack.mCount);
|
||||
aNativeStack.mSPs + aNativeStack.mCount,
|
||||
aNativeStack.mPCs + aNativeStack.mCount,
|
||||
MAX_NATIVE_FRAMES - aNativeStack.mCount);
|
||||
|
||||
memset(&savedContext, 0, sizeof(savedContext));
|
||||
|
||||
|
@ -1130,13 +1133,11 @@ DoNativeBacktrace(PSLockRef aLock, ProfileBuffer* aBuffer,
|
|||
|
||||
// Now unwind whatever's left (starting from either the last EnterJIT frame
|
||||
// or, if no EnterJIT was found, the original registers).
|
||||
nativeStack.mCount +=
|
||||
aNativeStack.mCount +=
|
||||
EHABIStackWalk(*mcontext, aSample.mStackTop,
|
||||
nativeStack.mSPs + nativeStack.mCount,
|
||||
nativeStack.mPCs + nativeStack.mCount,
|
||||
MAX_NATIVE_FRAMES - nativeStack.mCount);
|
||||
|
||||
MergeStacksIntoProfile(aLock, aBuffer, aSample, nativeStack);
|
||||
aNativeStack.mSPs + aNativeStack.mCount,
|
||||
aNativeStack.mPCs + aNativeStack.mCount,
|
||||
MAX_NATIVE_FRAMES - aNativeStack.mCount);
|
||||
}
|
||||
#endif
|
||||
|
||||
|
@ -1161,7 +1162,7 @@ ASAN_memcpy(void* aDst, const void* aSrc, size_t aLen)
|
|||
#endif
|
||||
|
||||
static void
|
||||
DoNativeBacktrace(PSLockRef aLock, ProfileBuffer* aBuffer,
|
||||
DoNativeBacktrace(PSLockRef aLock, NativeStack& aNativeStack,
|
||||
const TickSample& aSample)
|
||||
{
|
||||
const mcontext_t* mc = &aSample.mContext->uc_mcontext;
|
||||
|
@ -1266,25 +1267,21 @@ DoNativeBacktrace(PSLockRef aLock, ProfileBuffer* aBuffer,
|
|||
}
|
||||
}
|
||||
|
||||
NativeStack nativeStack;
|
||||
|
||||
size_t scannedFramesAllowed = 0;
|
||||
|
||||
size_t scannedFramesAcquired = 0, framePointerFramesAcquired = 0;
|
||||
lul::LUL* lul = CorePS::Lul(aLock);
|
||||
lul->Unwind(reinterpret_cast<uintptr_t*>(nativeStack.mPCs),
|
||||
reinterpret_cast<uintptr_t*>(nativeStack.mSPs),
|
||||
&nativeStack.mCount, &framePointerFramesAcquired,
|
||||
lul->Unwind(reinterpret_cast<uintptr_t*>(aNativeStack.mPCs),
|
||||
reinterpret_cast<uintptr_t*>(aNativeStack.mSPs),
|
||||
&aNativeStack.mCount, &framePointerFramesAcquired,
|
||||
&scannedFramesAcquired,
|
||||
MAX_NATIVE_FRAMES, scannedFramesAllowed,
|
||||
&startRegs, &stackImg);
|
||||
|
||||
MergeStacksIntoProfile(aLock, aBuffer, aSample, nativeStack);
|
||||
|
||||
// Update stats in the LUL stats object. Unfortunately this requires
|
||||
// three global memory operations.
|
||||
lul->mStats.mContext += 1;
|
||||
lul->mStats.mCFI += nativeStack.mCount - 1 - framePointerFramesAcquired -
|
||||
lul->mStats.mCFI += aNativeStack.mCount - 1 - framePointerFramesAcquired -
|
||||
scannedFramesAcquired;
|
||||
lul->mStats.mFP += framePointerFramesAcquired;
|
||||
lul->mStats.mScanned += scannedFramesAcquired;
|
||||
|
@ -1306,23 +1303,50 @@ DoSampleStackTrace(PSLockRef aLock, ProfileBuffer* aBuffer,
|
|||
}
|
||||
}
|
||||
|
||||
// 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(PSLockRef aLock, ProfileBuffer* aBuffer, const TickSample& aSample)
|
||||
class ProfilerTickController : public TickController
|
||||
{
|
||||
aBuffer->addTagThreadId(aSample.mThreadId, aSample.mLastSample);
|
||||
public:
|
||||
explicit ProfilerTickController(PSLockRef aLock, ProfileBuffer* aBuffer = nullptr)
|
||||
: mBuffer(aBuffer ? aBuffer : ActivePS::Buffer(aLock))
|
||||
{
|
||||
}
|
||||
|
||||
// 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.
|
||||
void Tick(PSLockRef aLock, const TickSample& aSample) override;
|
||||
|
||||
private:
|
||||
ProfileBuffer* mBuffer;
|
||||
};
|
||||
|
||||
void
|
||||
ProfilerTickController::Tick(PSLockRef aLock, const TickSample& aSample)
|
||||
{
|
||||
MOZ_RELEASE_ASSERT(ActivePS::Exists(aLock));
|
||||
|
||||
mBuffer->addTagThreadId(aSample.mThreadId, aSample.mLastSample);
|
||||
|
||||
TimeDuration delta = aSample.mTimeStamp - CorePS::ProcessStartTime();
|
||||
aBuffer->addTag(ProfileBufferEntry::Time(delta.ToMilliseconds()));
|
||||
|
||||
#if defined(HAVE_NATIVE_UNWIND)
|
||||
if (ActivePS::FeatureStackWalk(aLock)) {
|
||||
DoNativeBacktrace(aLock, aBuffer, aSample);
|
||||
void* pc_array[1000];
|
||||
void* sp_array[1000];
|
||||
NativeStack nativeStack = {
|
||||
pc_array,
|
||||
sp_array,
|
||||
mozilla::ArrayLength(pc_array),
|
||||
0
|
||||
};
|
||||
|
||||
DoNativeBacktrace(aLock, nativeStack, aSample);
|
||||
|
||||
MergeStacksIntoProfile(aLock, mBuffer, aSample, nativeStack);
|
||||
} else
|
||||
#endif
|
||||
{
|
||||
DoSampleStackTrace(aLock, aBuffer, aSample);
|
||||
DoSampleStackTrace(aLock, mBuffer, aSample);
|
||||
}
|
||||
|
||||
// Don't process the PseudoStack's markers if we're synchronously sampling
|
||||
|
@ -1332,27 +1356,27 @@ Tick(PSLockRef aLock, ProfileBuffer* aBuffer, const TickSample& aSample)
|
|||
aSample.mRacyInfo->GetPendingMarkers();
|
||||
while (pendingMarkersList && pendingMarkersList->peek()) {
|
||||
ProfilerMarker* marker = pendingMarkersList->popHead();
|
||||
aBuffer->addStoredMarker(marker);
|
||||
aBuffer->addTag(ProfileBufferEntry::Marker(marker));
|
||||
mBuffer->addStoredMarker(marker);
|
||||
mBuffer->addTag(ProfileBufferEntry::Marker(marker));
|
||||
}
|
||||
}
|
||||
|
||||
if (aSample.mResponsiveness && aSample.mResponsiveness->HasData()) {
|
||||
TimeDuration delta =
|
||||
aSample.mResponsiveness->GetUnresponsiveDuration(aSample.mTimeStamp);
|
||||
aBuffer->addTag(ProfileBufferEntry::Responsiveness(delta.ToMilliseconds()));
|
||||
mBuffer->addTag(ProfileBufferEntry::Responsiveness(delta.ToMilliseconds()));
|
||||
}
|
||||
|
||||
// rssMemory is equal to 0 when we are not recording.
|
||||
if (aSample.mRSSMemory != 0) {
|
||||
double rssMemory = static_cast<double>(aSample.mRSSMemory);
|
||||
aBuffer->addTag(ProfileBufferEntry::ResidentMemory(rssMemory));
|
||||
mBuffer->addTag(ProfileBufferEntry::ResidentMemory(rssMemory));
|
||||
}
|
||||
|
||||
// ussMemory is equal to 0 when we are not recording.
|
||||
if (aSample.mUSSMemory != 0) {
|
||||
double ussMemory = static_cast<double>(aSample.mUSSMemory);
|
||||
aBuffer->addTag(ProfileBufferEntry::UnsharedMemory(ussMemory));
|
||||
mBuffer->addTag(ProfileBufferEntry::UnsharedMemory(ussMemory));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1727,17 +1751,72 @@ PrintUsageThenExit(int aExitCode)
|
|||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////
|
||||
// BEGIN SamplerThread
|
||||
// BEGIN Sampler
|
||||
|
||||
#if defined(GP_OS_linux) || defined(GP_OS_android)
|
||||
struct SigHandlerCoordinator;
|
||||
#endif
|
||||
|
||||
// Sampler performs setup and teardown of the state required to sample with the
|
||||
// profiler. Sampler may exist when ActivePS is not present.
|
||||
//
|
||||
// SuspendAndSampleAndResumeThread must only be called from a single thread,
|
||||
// and must not sample the thread it is being called from. A separate Sampler
|
||||
// instance must be used for each thread which wants to capture samples.
|
||||
|
||||
// WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING
|
||||
//
|
||||
// With the exception of SamplerThread, all Sampler objects must be Disable-d
|
||||
// before releasing the lock which was used to create them. This avoids races
|
||||
// on linux with the SIGPROF signal handler.
|
||||
|
||||
class Sampler
|
||||
{
|
||||
public:
|
||||
// Sets up the profiler such that it can begin sampling.
|
||||
explicit Sampler(PSLockRef aLock);
|
||||
|
||||
// Disable the sampler, restoring it to its previous state. This must be
|
||||
// called once, and only once, before the Sampler is destroyed.
|
||||
void Disable(PSLockRef aLock);
|
||||
|
||||
// This method suspends and resumes the samplee thread.
|
||||
void SuspendAndSampleAndResumeThread(PSLockRef aLock,
|
||||
TickController& aController,
|
||||
TickSample& aSample);
|
||||
|
||||
private:
|
||||
#if defined(GP_OS_linux) || defined(GP_OS_android)
|
||||
// Used to restore the SIGPROF handler when ours is removed.
|
||||
struct sigaction mOldSigprofHandler;
|
||||
|
||||
// This process' ID. Needed as an argument for tgkill in
|
||||
// SuspendAndSampleAndResumeThread.
|
||||
int mMyPid;
|
||||
|
||||
// The sampler thread's ID. Used to assert that it is not sampling itself,
|
||||
// which would lead to deadlock.
|
||||
int mSamplerTid;
|
||||
|
||||
public:
|
||||
// This is the one-and-only variable used to communicate between the sampler
|
||||
// thread and the samplee thread's signal handler. It's static because the
|
||||
// samplee thread's signal handler is static.
|
||||
static struct SigHandlerCoordinator* sSigHandlerCoordinator;
|
||||
#endif
|
||||
};
|
||||
|
||||
// END Sampler
|
||||
////////////////////////////////////////////////////////////////////////
|
||||
|
||||
////////////////////////////////////////////////////////////////////////
|
||||
// BEGIN SamplerThread
|
||||
|
||||
// The sampler thread controls sampling and runs whenever the profiler is
|
||||
// active. It periodically runs through all registered threads, finds those
|
||||
// that should be sampled, then pauses and samples them.
|
||||
|
||||
class SamplerThread
|
||||
class SamplerThread : public Sampler
|
||||
{
|
||||
public:
|
||||
// Creates a sampler thread, but doesn't start it.
|
||||
|
@ -1745,10 +1824,6 @@ public:
|
|||
double aIntervalMilliseconds);
|
||||
~SamplerThread();
|
||||
|
||||
// This runs on the sampler thread. It suspends and resumes the samplee
|
||||
// threads.
|
||||
void SuspendAndSampleAndResumeThread(PSLockRef aLock, TickSample& aSample);
|
||||
|
||||
// This runs on (is!) the sampler thread.
|
||||
void Run();
|
||||
|
||||
|
@ -1773,26 +1848,6 @@ private:
|
|||
pthread_t mThread;
|
||||
#endif
|
||||
|
||||
#if defined(GP_OS_linux) || defined(GP_OS_android)
|
||||
// Used to restore the SIGPROF handler when ours is removed.
|
||||
struct sigaction mOldSigprofHandler;
|
||||
|
||||
// This process' ID. Needed as an argument for tgkill in
|
||||
// SuspendAndSampleAndResumeThread.
|
||||
int mMyPid;
|
||||
|
||||
public:
|
||||
// The sampler thread's ID. Used to assert that it is not sampling itself,
|
||||
// which would lead to deadlock.
|
||||
int mSamplerTid;
|
||||
|
||||
// This is the one-and-only variable used to communicate between the sampler
|
||||
// thread and the samplee thread's signal handler. It's static because the
|
||||
// samplee thread's signal handler is static.
|
||||
static struct SigHandlerCoordinator* sSigHandlerCoordinator;
|
||||
#endif
|
||||
|
||||
private:
|
||||
SamplerThread(const SamplerThread&) = delete;
|
||||
void operator=(const SamplerThread&) = delete;
|
||||
};
|
||||
|
@ -1875,8 +1930,9 @@ SamplerThread::Run()
|
|||
}
|
||||
|
||||
TickSample sample(info, rssMemory, ussMemory);
|
||||
ProfilerTickController controller(lock);
|
||||
|
||||
SuspendAndSampleAndResumeThread(lock, sample);
|
||||
SuspendAndSampleAndResumeThread(lock, controller, sample);
|
||||
}
|
||||
|
||||
#if defined(USE_LUL_STACKWALK)
|
||||
|
@ -2829,7 +2885,8 @@ profiler_get_backtrace()
|
|||
#endif
|
||||
#endif
|
||||
|
||||
Tick(lock, buffer, sample);
|
||||
ProfilerTickController controller(lock, buffer);
|
||||
controller.Tick(lock, sample);
|
||||
|
||||
return UniqueProfilerBacktrace(
|
||||
new ProfilerBacktrace("SyncProfile", tid, buffer));
|
||||
|
@ -3043,5 +3100,74 @@ profiler_get_stack_top()
|
|||
return nullptr;
|
||||
}
|
||||
|
||||
int
|
||||
profiler_current_thread_id()
|
||||
{
|
||||
return Thread::GetCurrentId();
|
||||
}
|
||||
|
||||
class SimpleTickController : public TickController
|
||||
{
|
||||
public:
|
||||
explicit SimpleTickController(const std::function<void(void**, size_t)>& aCallback,
|
||||
bool aSampleNative)
|
||||
: mCallback(aCallback)
|
||||
, mSampleNative(aSampleNative)
|
||||
{
|
||||
}
|
||||
|
||||
void Tick(PSLockRef aLock, const TickSample& aSample) override {
|
||||
void* pc_array[1000];
|
||||
void* sp_array[1000];
|
||||
NativeStack nativeStack = {
|
||||
pc_array,
|
||||
sp_array,
|
||||
mozilla::ArrayLength(pc_array),
|
||||
0
|
||||
};
|
||||
|
||||
#if defined(HAVE_NATIVE_UNWIND)
|
||||
if (mSampleNative) {
|
||||
DoNativeBacktrace(aLock, nativeStack, aSample);
|
||||
}
|
||||
#endif
|
||||
|
||||
mCallback(nativeStack.pc_array, nativeStack.count);
|
||||
}
|
||||
|
||||
private:
|
||||
const std::function<void(void**, size_t)>& mCallback;
|
||||
bool mSampleNative;
|
||||
};
|
||||
|
||||
// NOTE: The callback function passed in will be called while the target thread
|
||||
// is paused. Doing stuff in this function like allocating which may try to
|
||||
// claim locks is a surefire way to deadlock.
|
||||
void
|
||||
profiler_suspend_and_sample_thread(int aThreadId,
|
||||
const std::function<void(void**, size_t)>& aCallback,
|
||||
bool aSampleNative /* = true */)
|
||||
{
|
||||
PSAutoLock lock(gPSMutex);
|
||||
|
||||
const CorePS::ThreadVector& liveThreads = CorePS::LiveThreads(lock);
|
||||
for (uint32_t i = 0; i < liveThreads.size(); i++) {
|
||||
ThreadInfo* info = liveThreads.at(i);
|
||||
|
||||
if (info->ThreadId() == aThreadId) {
|
||||
// Suspend, sample, and then resume the target thread.
|
||||
Sampler sampler(lock);
|
||||
TickSample sample(info, 0, 0);
|
||||
SimpleTickController controller(aCallback, aSampleNative);
|
||||
sampler.SuspendAndSampleAndResumeThread(lock, controller, sample);
|
||||
|
||||
// NOTE: Make sure to disable the sampler before it is destroyed, in case
|
||||
// the profiler is running at the same time.
|
||||
sampler.Disable(lock);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// END externally visible functions
|
||||
////////////////////////////////////////////////////////////////////////
|
||||
|
|
|
@ -22,6 +22,7 @@
|
|||
|
||||
#include <stdint.h>
|
||||
#include <stdarg.h>
|
||||
#include <functional>
|
||||
|
||||
#include "mozilla/Assertions.h"
|
||||
#include "mozilla/Attributes.h"
|
||||
|
@ -387,6 +388,20 @@ PROFILER_FUNC_VOID(profiler_log(const char* aStr))
|
|||
// this method will return nullptr.
|
||||
PROFILER_FUNC(void* profiler_get_stack_top(), nullptr)
|
||||
|
||||
PROFILER_FUNC(int profiler_current_thread_id(), 0)
|
||||
|
||||
// This method suspends the thread identified by aThreadId, optionally samples
|
||||
// it for its native stack, and then calls the callback. The callback is passed
|
||||
// the native stack's program counters and length as two arguments if
|
||||
// aSampleNative is true.
|
||||
//
|
||||
// WARNING: The target thread is suspended during the callback. Do not try to
|
||||
// allocate or acquire any locks, or you could deadlock. The target thread will
|
||||
// have resumed by the time that this function returns.
|
||||
PROFILER_FUNC_VOID(profiler_suspend_and_sample_thread(int aThreadId,
|
||||
const std::function<void(void**, size_t)>& aCallback,
|
||||
bool aSampleNative = true))
|
||||
|
||||
// End of the functions defined whether the profiler is enabled or not.
|
||||
|
||||
#if defined(MOZ_GECKO_PROFILER)
|
||||
|
|
Загрузка…
Ссылка в новой задаче