mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-11-24 21:31:04 +00:00
Backed out 2 changesets (bug 1437428) for frequent xpcfailures on marAppApplyUpdateStageOldVersionFailure.js a=backout
Backed out changeset b915e160a690 (bug 1437428) Backed out changeset 0fcad4eaabb6 (bug 1437428) --HG-- rename : tools/profiler/core/RegisteredThread.cpp => tools/profiler/core/ThreadInfo.cpp
This commit is contained in:
parent
0548f006a3
commit
768831260e
@ -294,7 +294,7 @@ RegisterContextProfilingEventMarker(JSContext* cx, void (*fn)(const char*));
|
||||
// - When popping an old entry, the only operation is the decrementing of the
|
||||
// stack pointer, which is obviously atomic.
|
||||
//
|
||||
class PseudoStack final
|
||||
class PseudoStack
|
||||
{
|
||||
public:
|
||||
PseudoStack()
|
||||
|
@ -1,144 +0,0 @@
|
||||
/* -*- Mode: C++; tab-width: 2; 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/. */
|
||||
|
||||
#ifndef ProfiledThreadData_h
|
||||
#define ProfiledThreadData_h
|
||||
|
||||
#include "mozilla/NotNull.h"
|
||||
#include "mozilla/TimeStamp.h"
|
||||
#include "mozilla/UniquePtrExtensions.h"
|
||||
|
||||
#include "js/ProfilingStack.h"
|
||||
#include "platform.h"
|
||||
#include "ProfileBuffer.h"
|
||||
#include "ThreadInfo.h"
|
||||
|
||||
// Contains data for partial profiles that get saved when
|
||||
// ThreadInfo::FlushSamplesAndMarkers gets called.
|
||||
struct PartialThreadProfile final
|
||||
{
|
||||
PartialThreadProfile(mozilla::UniquePtr<char[]>&& aSamplesJSON,
|
||||
mozilla::UniquePtr<char[]>&& aMarkersJSON,
|
||||
mozilla::UniquePtr<UniqueStacks>&& aUniqueStacks)
|
||||
: mSamplesJSON(mozilla::Move(aSamplesJSON))
|
||||
, mMarkersJSON(mozilla::Move(aMarkersJSON))
|
||||
, mUniqueStacks(mozilla::Move(aUniqueStacks))
|
||||
{}
|
||||
|
||||
mozilla::UniquePtr<char[]> mSamplesJSON;
|
||||
mozilla::UniquePtr<char[]> mMarkersJSON;
|
||||
mozilla::UniquePtr<UniqueStacks> mUniqueStacks;
|
||||
};
|
||||
|
||||
// This class contains information about a thread that is only relevant while
|
||||
// the profiler is running, for any threads (both alive and dead) whose thread
|
||||
// name matches the "thread filter" in the current profiler run.
|
||||
// ProfiledThreadData objects may be kept alive even after the thread is
|
||||
// unregistered, as long as there is still data for that thread in the profiler
|
||||
// buffer.
|
||||
//
|
||||
// Accesses to this class are protected by the profiler state lock.
|
||||
//
|
||||
// Created as soon as the following are true for the thread:
|
||||
// - The profiler is running, and
|
||||
// - the thread matches the profiler's thread filter, and
|
||||
// - the thread is registered with the profiler.
|
||||
// So it gets created in response to either (1) the profiler being started (for
|
||||
// an existing registered thread) or (2) the thread being registered (if the
|
||||
// profiler is already running).
|
||||
//
|
||||
// The thread may be unregistered during the lifetime of ProfiledThreadData.
|
||||
// If that happens, NotifyUnregistered() is called.
|
||||
//
|
||||
// This class is the right place to store buffer positions. Profiler buffer
|
||||
// positions become invalid if the profiler buffer is destroyed, which happens
|
||||
// when the profiler is stopped.
|
||||
class ProfiledThreadData final
|
||||
{
|
||||
public:
|
||||
ProfiledThreadData(ThreadInfo* aThreadInfo, nsIEventTarget* aEventTarget);
|
||||
~ProfiledThreadData();
|
||||
|
||||
void NotifyUnregistered(uint64_t aBufferPosition)
|
||||
{
|
||||
mResponsiveness.reset();
|
||||
mLastSample = mozilla::Nothing();
|
||||
mUnregisterTime = TimeStamp::Now();
|
||||
mBufferPositionWhenUnregistered = mozilla::Some(aBufferPosition);
|
||||
}
|
||||
mozilla::Maybe<uint64_t> BufferPositionWhenUnregistered() { return mBufferPositionWhenUnregistered; }
|
||||
|
||||
mozilla::Maybe<uint64_t>& LastSample() { return mLastSample; }
|
||||
|
||||
void StreamJSON(const ProfileBuffer& aBuffer, JSContext* aCx,
|
||||
SpliceableJSONWriter& aWriter,
|
||||
const mozilla::TimeStamp& aProcessStartTime,
|
||||
double aSinceTime);
|
||||
|
||||
// Call this method when the JS entries inside the buffer are about to
|
||||
// become invalid, i.e., just before JS shutdown.
|
||||
void FlushSamplesAndMarkers(JSContext* aCx,
|
||||
const mozilla::TimeStamp& aProcessStartTime,
|
||||
ProfileBuffer& aBuffer);
|
||||
|
||||
// Returns nullptr if this is not the main thread or if this thread is not
|
||||
// being profiled.
|
||||
ThreadResponsiveness* GetThreadResponsiveness()
|
||||
{
|
||||
ThreadResponsiveness* responsiveness = mResponsiveness.ptrOr(nullptr);
|
||||
return responsiveness;
|
||||
}
|
||||
|
||||
const RefPtr<ThreadInfo> Info() const { return mThreadInfo; }
|
||||
|
||||
private:
|
||||
// Group A:
|
||||
// The following fields are interesting for the entire lifetime of a
|
||||
// ProfiledThreadData object.
|
||||
|
||||
// This thread's thread info.
|
||||
const RefPtr<ThreadInfo> mThreadInfo;
|
||||
|
||||
// JS frames in the buffer may require a live JSRuntime to stream (e.g.,
|
||||
// stringifying JIT frames). In the case of JSRuntime destruction,
|
||||
// FlushSamplesAndMarkers should be called to save them. These are spliced
|
||||
// into the final stream.
|
||||
UniquePtr<PartialThreadProfile> mPartialProfile;
|
||||
|
||||
// Group B:
|
||||
// The following fields are only used while this thread is alive and
|
||||
// registered. They become Nothing() once the thread is unregistered.
|
||||
|
||||
// A helper object that instruments nsIThreads to obtain responsiveness
|
||||
// information about their event loop.
|
||||
mozilla::Maybe<ThreadResponsiveness> mResponsiveness;
|
||||
|
||||
// When sampling, this holds the position in ActivePS::mBuffer of the most
|
||||
// recent sample for this thread, or Nothing() if there is no sample for this
|
||||
// thread in the buffer.
|
||||
mozilla::Maybe<uint64_t> mLastSample;
|
||||
|
||||
// Group C:
|
||||
// The following fields are only used once this thread has been unregistered.
|
||||
|
||||
mozilla::Maybe<uint64_t> mBufferPositionWhenUnregistered;
|
||||
mozilla::TimeStamp mUnregisterTime;
|
||||
};
|
||||
|
||||
void
|
||||
StreamSamplesAndMarkers(const char* aName, int aThreadId,
|
||||
const ProfileBuffer& aBuffer,
|
||||
SpliceableJSONWriter& aWriter,
|
||||
const mozilla::TimeStamp& aProcessStartTime,
|
||||
const TimeStamp& aRegisterTime,
|
||||
const TimeStamp& aUnregisterTime,
|
||||
double aSinceTime,
|
||||
JSContext* aContext,
|
||||
UniquePtr<char[]>&& aPartialSamplesJSON,
|
||||
UniquePtr<char[]>&& aPartialMarkersJSON,
|
||||
UniqueStacks& aUniqueStacks);
|
||||
|
||||
#endif // ProfiledThreadData_h
|
@ -1,47 +0,0 @@
|
||||
/* -*- Mode: C++; tab-width: 2; 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 "RegisteredThread.h"
|
||||
|
||||
RegisteredThread::RegisteredThread(ThreadInfo* aInfo, nsIEventTarget* aThread,
|
||||
void* aStackTop)
|
||||
: mRacyRegisteredThread(aInfo->ThreadId())
|
||||
, mPlatformData(AllocPlatformData(aInfo->ThreadId()))
|
||||
, mStackTop(aStackTop)
|
||||
, mThreadInfo(aInfo)
|
||||
, mThread(aThread)
|
||||
, mContext(nullptr)
|
||||
, mJSSampling(INACTIVE)
|
||||
{
|
||||
MOZ_COUNT_CTOR(RegisteredThread);
|
||||
|
||||
// We don't have to guess on mac
|
||||
#if defined(GP_OS_darwin)
|
||||
pthread_t self = pthread_self();
|
||||
mStackTop = pthread_get_stackaddr_np(self);
|
||||
#endif
|
||||
}
|
||||
|
||||
RegisteredThread::~RegisteredThread()
|
||||
{
|
||||
MOZ_COUNT_DTOR(RegisteredThread);
|
||||
}
|
||||
|
||||
size_t
|
||||
RegisteredThread::SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const
|
||||
{
|
||||
size_t n = aMallocSizeOf(this);
|
||||
|
||||
// Measurement of the following members may be added later if DMD finds it
|
||||
// is worthwhile:
|
||||
// - mPlatformData
|
||||
// - mRacyRegisteredThread.mPendingMarkers
|
||||
//
|
||||
// The following members are not measured:
|
||||
// - mThreadInfo: because it is non-owning
|
||||
|
||||
return n;
|
||||
}
|
@ -1,309 +0,0 @@
|
||||
/* -*- Mode: C++; tab-width: 2; 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/. */
|
||||
|
||||
#ifndef RegisteredThread_h
|
||||
#define RegisteredThread_h
|
||||
|
||||
#include "mozilla/UniquePtrExtensions.h"
|
||||
|
||||
#include "platform.h"
|
||||
#include "ThreadInfo.h"
|
||||
|
||||
// This class contains the state for a single thread that is accessible without
|
||||
// protection from gPSMutex in platform.cpp. Because there is no external
|
||||
// protection against data races, it must provide internal protection. Hence
|
||||
// the "Racy" prefix.
|
||||
//
|
||||
class RacyRegisteredThread final
|
||||
{
|
||||
public:
|
||||
explicit RacyRegisteredThread(int aThreadId)
|
||||
: mThreadId(aThreadId)
|
||||
, mSleep(AWAKE)
|
||||
{
|
||||
MOZ_COUNT_CTOR(RacyRegisteredThread);
|
||||
}
|
||||
|
||||
~RacyRegisteredThread()
|
||||
{
|
||||
MOZ_COUNT_DTOR(RacyRegisteredThread);
|
||||
}
|
||||
|
||||
void AddPendingMarker(const char* aMarkerName,
|
||||
mozilla::UniquePtr<ProfilerMarkerPayload> aPayload,
|
||||
double aTime)
|
||||
{
|
||||
ProfilerMarker* marker =
|
||||
new ProfilerMarker(aMarkerName, mThreadId, Move(aPayload), aTime);
|
||||
mPendingMarkers.insert(marker);
|
||||
}
|
||||
|
||||
// Called within signal. Function must be reentrant.
|
||||
ProfilerMarkerLinkedList* GetPendingMarkers()
|
||||
{
|
||||
// The profiled thread is interrupted, so we can access the list safely.
|
||||
// Unless the profiled thread was in the middle of changing the list when
|
||||
// we interrupted it - in that case, accessList() will return null.
|
||||
return mPendingMarkers.accessList();
|
||||
}
|
||||
|
||||
// This is called on every profiler restart. Put things that should happen at
|
||||
// that time here.
|
||||
void ReinitializeOnResume()
|
||||
{
|
||||
// This is needed to cause an initial sample to be taken from sleeping
|
||||
// threads that had been observed prior to the profiler stopping and
|
||||
// restarting. Otherwise sleeping threads would not have any samples to
|
||||
// copy forward while sleeping.
|
||||
(void)mSleep.compareExchange(SLEEPING_OBSERVED, SLEEPING_NOT_OBSERVED);
|
||||
}
|
||||
|
||||
// This returns true for the second and subsequent calls in each sleep cycle.
|
||||
bool CanDuplicateLastSampleDueToSleep()
|
||||
{
|
||||
if (mSleep == AWAKE) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (mSleep.compareExchange(SLEEPING_NOT_OBSERVED, SLEEPING_OBSERVED)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// Call this whenever the current thread sleeps. Calling it twice in a row
|
||||
// without an intervening setAwake() call is an error.
|
||||
void SetSleeping()
|
||||
{
|
||||
MOZ_ASSERT(mSleep == AWAKE);
|
||||
mSleep = SLEEPING_NOT_OBSERVED;
|
||||
}
|
||||
|
||||
// Call this whenever the current thread wakes. Calling it twice in a row
|
||||
// without an intervening setSleeping() call is an error.
|
||||
void SetAwake()
|
||||
{
|
||||
MOZ_ASSERT(mSleep != AWAKE);
|
||||
mSleep = AWAKE;
|
||||
}
|
||||
|
||||
bool IsSleeping() { return mSleep != AWAKE; }
|
||||
|
||||
int ThreadId() const { return mThreadId; }
|
||||
|
||||
class PseudoStack& PseudoStack() { return mPseudoStack; }
|
||||
const class PseudoStack& PseudoStack() const { return mPseudoStack; }
|
||||
|
||||
private:
|
||||
class PseudoStack mPseudoStack;
|
||||
|
||||
// A list of pending markers that must be moved to the circular buffer.
|
||||
ProfilerSignalSafeLinkedList<ProfilerMarker> mPendingMarkers;
|
||||
|
||||
// mThreadId contains the thread ID of the current thread. It is safe to read
|
||||
// this from multiple threads concurrently, as it will never be mutated.
|
||||
const int mThreadId;
|
||||
|
||||
// mSleep tracks whether the thread is sleeping, and if so, whether it has
|
||||
// been previously observed. This is used for an optimization: in some cases,
|
||||
// when a thread is asleep, we duplicate the previous sample, which is
|
||||
// cheaper than taking a new sample.
|
||||
//
|
||||
// mSleep is atomic because it is accessed from multiple threads.
|
||||
//
|
||||
// - It is written only by this thread, via setSleeping() and setAwake().
|
||||
//
|
||||
// - It is read by SamplerThread::Run().
|
||||
//
|
||||
// There are two cases where racing between threads can cause an issue.
|
||||
//
|
||||
// - If CanDuplicateLastSampleDueToSleep() returns false but that result is
|
||||
// invalidated before being acted upon, we will take a full sample
|
||||
// unnecessarily. This is additional work but won't cause any correctness
|
||||
// issues. (In actual fact, this case is impossible. In order to go from
|
||||
// CanDuplicateLastSampleDueToSleep() returning false to it returning true
|
||||
// requires an intermediate call to it in order for mSleep to go from
|
||||
// SLEEPING_NOT_OBSERVED to SLEEPING_OBSERVED.)
|
||||
//
|
||||
// - If CanDuplicateLastSampleDueToSleep() returns true but that result is
|
||||
// invalidated before being acted upon -- i.e. the thread wakes up before
|
||||
// DuplicateLastSample() is called -- we will duplicate the previous
|
||||
// sample. This is inaccurate, but only slightly... we will effectively
|
||||
// treat the thread as having slept a tiny bit longer than it really did.
|
||||
//
|
||||
// This latter inaccuracy could be avoided by moving the
|
||||
// CanDuplicateLastSampleDueToSleep() check within the thread-freezing code,
|
||||
// e.g. the section where Tick() is called. But that would reduce the
|
||||
// effectiveness of the optimization because more code would have to be run
|
||||
// before we can tell that duplication is allowed.
|
||||
//
|
||||
static const int AWAKE = 0;
|
||||
static const int SLEEPING_NOT_OBSERVED = 1;
|
||||
static const int SLEEPING_OBSERVED = 2;
|
||||
mozilla::Atomic<int> mSleep;
|
||||
};
|
||||
|
||||
// This class contains information that's relevant to a single thread only
|
||||
// while that thread is running and registered with the profiler, but
|
||||
// regardless of whether the profiler is running. All accesses to it are
|
||||
// protected by the profiler state lock.
|
||||
class RegisteredThread final
|
||||
{
|
||||
public:
|
||||
RegisteredThread(ThreadInfo* aInfo, nsIEventTarget* aThread,
|
||||
void* aStackTop);
|
||||
~RegisteredThread();
|
||||
|
||||
class RacyRegisteredThread& RacyRegisteredThread() { return mRacyRegisteredThread; }
|
||||
const class RacyRegisteredThread& RacyRegisteredThread() const { return mRacyRegisteredThread; }
|
||||
|
||||
PlatformData* GetPlatformData() const { return mPlatformData.get(); }
|
||||
const void* StackTop() const { return mStackTop; }
|
||||
|
||||
size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const;
|
||||
|
||||
// Set the JSContext of the thread to be sampled. Sampling cannot begin until
|
||||
// this has been set.
|
||||
void SetJSContext(JSContext* aContext)
|
||||
{
|
||||
// This function runs on-thread.
|
||||
|
||||
MOZ_ASSERT(aContext && !mContext);
|
||||
|
||||
mContext = aContext;
|
||||
|
||||
// We give the JS engine a non-owning reference to the PseudoStack. It's
|
||||
// important that the JS engine doesn't touch this once the thread dies.
|
||||
js::SetContextProfilingStack(aContext, &RacyRegisteredThread().PseudoStack());
|
||||
|
||||
PollJSSampling();
|
||||
}
|
||||
|
||||
void ClearJSContext()
|
||||
{
|
||||
// This function runs on-thread.
|
||||
mContext = nullptr;
|
||||
}
|
||||
|
||||
JSContext* GetJSContext() const { return mContext; }
|
||||
|
||||
const RefPtr<ThreadInfo> Info() const { return mThreadInfo; }
|
||||
const nsCOMPtr<nsIEventTarget> GetEventTarget() const { return mThread; }
|
||||
|
||||
// Request that this thread start JS sampling. JS sampling won't actually
|
||||
// start until a subsequent PollJSSampling() call occurs *and* mContext has
|
||||
// been set.
|
||||
void StartJSSampling()
|
||||
{
|
||||
// This function runs on-thread or off-thread.
|
||||
|
||||
MOZ_RELEASE_ASSERT(mJSSampling == INACTIVE ||
|
||||
mJSSampling == INACTIVE_REQUESTED);
|
||||
mJSSampling = ACTIVE_REQUESTED;
|
||||
}
|
||||
|
||||
// Request that this thread stop JS sampling. JS sampling won't actually stop
|
||||
// until a subsequent PollJSSampling() call occurs.
|
||||
void StopJSSampling()
|
||||
{
|
||||
// This function runs on-thread or off-thread.
|
||||
|
||||
MOZ_RELEASE_ASSERT(mJSSampling == ACTIVE ||
|
||||
mJSSampling == ACTIVE_REQUESTED);
|
||||
mJSSampling = INACTIVE_REQUESTED;
|
||||
}
|
||||
|
||||
// Poll to see if JS sampling should be started/stopped.
|
||||
void PollJSSampling()
|
||||
{
|
||||
// This function runs on-thread.
|
||||
|
||||
// We can't start/stop profiling until we have the thread's JSContext.
|
||||
if (mContext) {
|
||||
// It is possible for mJSSampling to go through the following sequences.
|
||||
//
|
||||
// - INACTIVE, ACTIVE_REQUESTED, INACTIVE_REQUESTED, INACTIVE
|
||||
//
|
||||
// - ACTIVE, INACTIVE_REQUESTED, ACTIVE_REQUESTED, ACTIVE
|
||||
//
|
||||
// Therefore, the if and else branches here aren't always interleaved.
|
||||
// This is ok because the JS engine can handle that.
|
||||
//
|
||||
if (mJSSampling == ACTIVE_REQUESTED) {
|
||||
mJSSampling = ACTIVE;
|
||||
js::EnableContextProfilingStack(mContext, true);
|
||||
js::RegisterContextProfilingEventMarker(mContext, profiler_add_marker);
|
||||
|
||||
} else if (mJSSampling == INACTIVE_REQUESTED) {
|
||||
mJSSampling = INACTIVE;
|
||||
js::EnableContextProfilingStack(mContext, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
class RacyRegisteredThread mRacyRegisteredThread;
|
||||
|
||||
const UniquePlatformData mPlatformData;
|
||||
const void* mStackTop;
|
||||
|
||||
const RefPtr<ThreadInfo> mThreadInfo;
|
||||
const nsCOMPtr<nsIEventTarget> mThread;
|
||||
|
||||
// If this is a JS thread, this is its JSContext, which is required for any
|
||||
// JS sampling.
|
||||
JSContext* mContext;
|
||||
|
||||
// The profiler needs to start and stop JS sampling of JS threads at various
|
||||
// times. However, the JS engine can only do the required actions on the
|
||||
// JS thread itself ("on-thread"), not from another thread ("off-thread").
|
||||
// Therefore, we have the following two-step process.
|
||||
//
|
||||
// - The profiler requests (on-thread or off-thread) that the JS sampling be
|
||||
// started/stopped, by changing mJSSampling to the appropriate REQUESTED
|
||||
// state.
|
||||
//
|
||||
// - The relevant JS thread polls (on-thread) for changes to mJSSampling.
|
||||
// When it sees a REQUESTED state, it performs the appropriate actions to
|
||||
// actually start/stop JS sampling, and changes mJSSampling out of the
|
||||
// REQUESTED state.
|
||||
//
|
||||
// The state machine is as follows.
|
||||
//
|
||||
// INACTIVE --> ACTIVE_REQUESTED
|
||||
// ^ ^ |
|
||||
// | _/ |
|
||||
// | _/ |
|
||||
// | / |
|
||||
// | v v
|
||||
// INACTIVE_REQUESTED <-- ACTIVE
|
||||
//
|
||||
// The polling is done in the following two ways.
|
||||
//
|
||||
// - Via the interrupt callback mechanism; the JS thread must call
|
||||
// profiler_js_interrupt_callback() from its own interrupt callback.
|
||||
// This is how sampling must be started/stopped for threads where the
|
||||
// request was made off-thread.
|
||||
//
|
||||
// - When {Start,Stop}JSSampling() is called on-thread, we can immediately
|
||||
// follow it with a PollJSSampling() call to avoid the delay between the
|
||||
// two steps. Likewise, setJSContext() calls PollJSSampling().
|
||||
//
|
||||
// One non-obvious thing about all this: these JS sampling requests are made
|
||||
// on all threads, even non-JS threads. mContext needs to also be set (via
|
||||
// setJSContext(), which can only happen for JS threads) for any JS sampling
|
||||
// to actually happen.
|
||||
//
|
||||
enum {
|
||||
INACTIVE = 0,
|
||||
ACTIVE_REQUESTED = 1,
|
||||
ACTIVE = 2,
|
||||
INACTIVE_REQUESTED = 3,
|
||||
} mJSSampling;
|
||||
};
|
||||
|
||||
#endif // RegisteredThread_h
|
@ -4,7 +4,9 @@
|
||||
* 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 "ProfiledThreadData.h"
|
||||
#include "ThreadInfo.h"
|
||||
|
||||
#include "mozilla/DebugOnly.h"
|
||||
|
||||
#if defined(GP_OS_darwin)
|
||||
#include <pthread.h>
|
||||
@ -17,23 +19,63 @@
|
||||
#include <unistd.h> // for getpid()
|
||||
#endif
|
||||
|
||||
ProfiledThreadData::ProfiledThreadData(ThreadInfo* aThreadInfo,
|
||||
nsIEventTarget* aEventTarget)
|
||||
: mThreadInfo(aThreadInfo)
|
||||
ThreadInfo::ThreadInfo(const char* aName,
|
||||
int aThreadId,
|
||||
bool aIsMainThread,
|
||||
nsIEventTarget* aThread,
|
||||
void* aStackTop)
|
||||
: mName(strdup(aName))
|
||||
, mRegisterTime(TimeStamp::Now())
|
||||
, mIsMainThread(aIsMainThread)
|
||||
, mThread(aThread)
|
||||
, mRacyInfo(mozilla::MakeNotNull<RacyThreadInfo*>(aThreadId))
|
||||
, mPlatformData(AllocPlatformData(aThreadId))
|
||||
, mStackTop(aStackTop)
|
||||
, mIsBeingProfiled(false)
|
||||
, mContext(nullptr)
|
||||
, mJSSampling(INACTIVE)
|
||||
, mLastSample()
|
||||
{
|
||||
MOZ_COUNT_CTOR(ProfiledThreadData);
|
||||
mResponsiveness.emplace(aEventTarget, aThreadInfo->IsMainThread());
|
||||
MOZ_COUNT_CTOR(ThreadInfo);
|
||||
|
||||
// We don't have to guess on mac
|
||||
#if defined(GP_OS_darwin)
|
||||
pthread_t self = pthread_self();
|
||||
mStackTop = pthread_get_stackaddr_np(self);
|
||||
#endif
|
||||
|
||||
// I don't know if we can assert this. But we should warn.
|
||||
MOZ_ASSERT(aThreadId >= 0, "native thread ID is < 0");
|
||||
MOZ_ASSERT(aThreadId <= INT32_MAX, "native thread ID is > INT32_MAX");
|
||||
}
|
||||
|
||||
ProfiledThreadData::~ProfiledThreadData()
|
||||
ThreadInfo::~ThreadInfo()
|
||||
{
|
||||
MOZ_COUNT_DTOR(ProfiledThreadData);
|
||||
MOZ_COUNT_DTOR(ThreadInfo);
|
||||
|
||||
delete mRacyInfo;
|
||||
}
|
||||
|
||||
void
|
||||
ProfiledThreadData::StreamJSON(const ProfileBuffer& aBuffer, JSContext* aCx,
|
||||
SpliceableJSONWriter& aWriter,
|
||||
const TimeStamp& aProcessStartTime, double aSinceTime)
|
||||
ThreadInfo::StartProfiling()
|
||||
{
|
||||
mIsBeingProfiled = true;
|
||||
mRacyInfo->ReinitializeOnResume();
|
||||
mResponsiveness.emplace(mThread, mIsMainThread);
|
||||
}
|
||||
|
||||
void
|
||||
ThreadInfo::StopProfiling()
|
||||
{
|
||||
mResponsiveness.reset();
|
||||
mPartialProfile = nullptr;
|
||||
mIsBeingProfiled = false;
|
||||
}
|
||||
|
||||
void
|
||||
ThreadInfo::StreamJSON(const ProfileBuffer& aBuffer,
|
||||
SpliceableJSONWriter& aWriter,
|
||||
const TimeStamp& aProcessStartTime, double aSinceTime)
|
||||
{
|
||||
UniquePtr<PartialThreadProfile> partialProfile = Move(mPartialProfile);
|
||||
|
||||
@ -52,11 +94,10 @@ ProfiledThreadData::StreamJSON(const ProfileBuffer& aBuffer, JSContext* aCx,
|
||||
|
||||
aWriter.Start();
|
||||
{
|
||||
StreamSamplesAndMarkers(mThreadInfo->Name(), mThreadInfo->ThreadId(),
|
||||
aBuffer, aWriter,
|
||||
StreamSamplesAndMarkers(Name(), ThreadId(), aBuffer, aWriter,
|
||||
aProcessStartTime,
|
||||
mThreadInfo->RegisterTime(), mUnregisterTime,
|
||||
aSinceTime, aCx,
|
||||
mRegisterTime, mUnregisterTime,
|
||||
aSinceTime, mContext,
|
||||
Move(partialSamplesJSON),
|
||||
Move(partialMarkersJSON),
|
||||
*uniqueStacks);
|
||||
@ -192,13 +233,12 @@ StreamSamplesAndMarkers(const char* aName,
|
||||
}
|
||||
|
||||
void
|
||||
ProfiledThreadData::FlushSamplesAndMarkers(JSContext* aCx,
|
||||
const TimeStamp& aProcessStartTime,
|
||||
ProfileBuffer& aBuffer)
|
||||
ThreadInfo::FlushSamplesAndMarkers(const TimeStamp& aProcessStartTime,
|
||||
ProfileBuffer& aBuffer)
|
||||
{
|
||||
// This function is used to serialize the current buffer just before
|
||||
// JSContext destruction.
|
||||
MOZ_ASSERT(aCx);
|
||||
MOZ_ASSERT(mContext);
|
||||
|
||||
// Unlike StreamJSObject, do not surround the samples in brackets by calling
|
||||
// aWriter.{Start,End}BareList. The result string will be a comma-separated
|
||||
@ -230,9 +270,8 @@ ProfiledThreadData::FlushSamplesAndMarkers(JSContext* aCx,
|
||||
// `haveSamples || aBuffer.StreamSamplesToJSON(...)` because we don't want
|
||||
// to short-circuit the call.
|
||||
bool streamedNewSamples =
|
||||
aBuffer.StreamSamplesToJSON(b, mThreadInfo->ThreadId(),
|
||||
/* aSinceTime = */ 0,
|
||||
aCx, *uniqueStacks);
|
||||
aBuffer.StreamSamplesToJSON(b, ThreadId(), /* aSinceTime = */ 0,
|
||||
mContext, *uniqueStacks);
|
||||
haveSamples = haveSamples || streamedNewSamples;
|
||||
}
|
||||
b.EndBareList();
|
||||
@ -260,8 +299,7 @@ ProfiledThreadData::FlushSamplesAndMarkers(JSContext* aCx,
|
||||
// `haveMarkers || aBuffer.StreamMarkersToJSON(...)` because we don't want
|
||||
// to short-circuit the call.
|
||||
bool streamedNewMarkers =
|
||||
aBuffer.StreamMarkersToJSON(b, mThreadInfo->ThreadId(),
|
||||
aProcessStartTime,
|
||||
aBuffer.StreamMarkersToJSON(b, ThreadId(), aProcessStartTime,
|
||||
/* aSinceTime = */ 0, *uniqueStacks);
|
||||
haveMarkers = haveMarkers || streamedNewMarkers;
|
||||
}
|
||||
@ -283,3 +321,21 @@ ProfiledThreadData::FlushSamplesAndMarkers(JSContext* aCx,
|
||||
// gone away will crash.
|
||||
aBuffer.Reset();
|
||||
}
|
||||
|
||||
size_t
|
||||
ThreadInfo::SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const
|
||||
{
|
||||
size_t n = aMallocSizeOf(this);
|
||||
n += aMallocSizeOf(mName.get());
|
||||
n += mRacyInfo->SizeOfIncludingThis(aMallocSizeOf);
|
||||
|
||||
// Measurement of the following members may be added later if DMD finds it
|
||||
// is worthwhile:
|
||||
// - mPlatformData
|
||||
// - mPartialProfile
|
||||
//
|
||||
// The following members are not measured:
|
||||
// - mThread: because it is non-owning
|
||||
|
||||
return n;
|
||||
}
|
@ -7,42 +7,415 @@
|
||||
#ifndef ThreadInfo_h
|
||||
#define ThreadInfo_h
|
||||
|
||||
#include "mozilla/NotNull.h"
|
||||
#include "mozilla/TimeStamp.h"
|
||||
#include "mozilla/UniquePtrExtensions.h"
|
||||
|
||||
#include "nsString.h"
|
||||
#include "platform.h"
|
||||
#include "ProfileBuffer.h"
|
||||
#include "js/ProfilingStack.h"
|
||||
|
||||
// This class contains information about a thread which needs to be stored
|
||||
// across restarts of the profiler and which can be useful even after the
|
||||
// thread has stopped running.
|
||||
// It uses threadsafe refcounting and only contains immutable data.
|
||||
// This class contains the info for a single thread that is accessible without
|
||||
// protection from gPSMutex in platform.cpp. Because there is no external
|
||||
// protection against data races, it must provide internal protection. Hence
|
||||
// the "Racy" prefix.
|
||||
//
|
||||
class RacyThreadInfo final : public PseudoStack
|
||||
{
|
||||
public:
|
||||
explicit RacyThreadInfo(int aThreadId)
|
||||
: PseudoStack()
|
||||
, mThreadId(aThreadId)
|
||||
, mSleep(AWAKE)
|
||||
{
|
||||
MOZ_COUNT_CTOR(RacyThreadInfo);
|
||||
}
|
||||
|
||||
~RacyThreadInfo()
|
||||
{
|
||||
MOZ_COUNT_DTOR(RacyThreadInfo);
|
||||
}
|
||||
|
||||
size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const
|
||||
{
|
||||
size_t n = aMallocSizeOf(this);
|
||||
|
||||
// Measurement of the following members may be added later if DMD finds it
|
||||
// is worthwhile:
|
||||
// - things in the PseudoStack
|
||||
// - mPendingMarkers
|
||||
//
|
||||
// If these measurements are added, the code must be careful to avoid data
|
||||
// races. (The current code doesn't have any race issues because the
|
||||
// contents of the PseudoStack object aren't accessed; |this| is used only
|
||||
// as an address for lookup by aMallocSizeof).
|
||||
|
||||
return n;
|
||||
}
|
||||
|
||||
void AddPendingMarker(const char* aMarkerName,
|
||||
mozilla::UniquePtr<ProfilerMarkerPayload> aPayload,
|
||||
double aTime)
|
||||
{
|
||||
ProfilerMarker* marker =
|
||||
new ProfilerMarker(aMarkerName, mThreadId, Move(aPayload), aTime);
|
||||
mPendingMarkers.insert(marker);
|
||||
}
|
||||
|
||||
// Called within signal. Function must be reentrant.
|
||||
ProfilerMarkerLinkedList* GetPendingMarkers()
|
||||
{
|
||||
// The profiled thread is interrupted, so we can access the list safely.
|
||||
// Unless the profiled thread was in the middle of changing the list when
|
||||
// we interrupted it - in that case, accessList() will return null.
|
||||
return mPendingMarkers.accessList();
|
||||
}
|
||||
|
||||
// This is called on every profiler restart. Put things that should happen at
|
||||
// that time here.
|
||||
void ReinitializeOnResume()
|
||||
{
|
||||
// This is needed to cause an initial sample to be taken from sleeping
|
||||
// threads that had been observed prior to the profiler stopping and
|
||||
// restarting. Otherwise sleeping threads would not have any samples to
|
||||
// copy forward while sleeping.
|
||||
(void)mSleep.compareExchange(SLEEPING_OBSERVED, SLEEPING_NOT_OBSERVED);
|
||||
}
|
||||
|
||||
// This returns true for the second and subsequent calls in each sleep cycle.
|
||||
bool CanDuplicateLastSampleDueToSleep()
|
||||
{
|
||||
if (mSleep == AWAKE) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (mSleep.compareExchange(SLEEPING_NOT_OBSERVED, SLEEPING_OBSERVED)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// Call this whenever the current thread sleeps. Calling it twice in a row
|
||||
// without an intervening setAwake() call is an error.
|
||||
void SetSleeping()
|
||||
{
|
||||
MOZ_ASSERT(mSleep == AWAKE);
|
||||
mSleep = SLEEPING_NOT_OBSERVED;
|
||||
}
|
||||
|
||||
// Call this whenever the current thread wakes. Calling it twice in a row
|
||||
// without an intervening setSleeping() call is an error.
|
||||
void SetAwake()
|
||||
{
|
||||
MOZ_ASSERT(mSleep != AWAKE);
|
||||
mSleep = AWAKE;
|
||||
}
|
||||
|
||||
bool IsSleeping() { return mSleep != AWAKE; }
|
||||
|
||||
int ThreadId() const { return mThreadId; }
|
||||
|
||||
private:
|
||||
// A list of pending markers that must be moved to the circular buffer.
|
||||
ProfilerSignalSafeLinkedList<ProfilerMarker> mPendingMarkers;
|
||||
|
||||
// mThreadId contains the thread ID of the current thread. It is safe to read
|
||||
// this from multiple threads concurrently, as it will never be mutated.
|
||||
const int mThreadId;
|
||||
|
||||
// mSleep tracks whether the thread is sleeping, and if so, whether it has
|
||||
// been previously observed. This is used for an optimization: in some cases,
|
||||
// when a thread is asleep, we duplicate the previous sample, which is
|
||||
// cheaper than taking a new sample.
|
||||
//
|
||||
// mSleep is atomic because it is accessed from multiple threads.
|
||||
//
|
||||
// - It is written only by this thread, via setSleeping() and setAwake().
|
||||
//
|
||||
// - It is read by SamplerThread::Run().
|
||||
//
|
||||
// There are two cases where racing between threads can cause an issue.
|
||||
//
|
||||
// - If CanDuplicateLastSampleDueToSleep() returns false but that result is
|
||||
// invalidated before being acted upon, we will take a full sample
|
||||
// unnecessarily. This is additional work but won't cause any correctness
|
||||
// issues. (In actual fact, this case is impossible. In order to go from
|
||||
// CanDuplicateLastSampleDueToSleep() returning false to it returning true
|
||||
// requires an intermediate call to it in order for mSleep to go from
|
||||
// SLEEPING_NOT_OBSERVED to SLEEPING_OBSERVED.)
|
||||
//
|
||||
// - If CanDuplicateLastSampleDueToSleep() returns true but that result is
|
||||
// invalidated before being acted upon -- i.e. the thread wakes up before
|
||||
// DuplicateLastSample() is called -- we will duplicate the previous
|
||||
// sample. This is inaccurate, but only slightly... we will effectively
|
||||
// treat the thread as having slept a tiny bit longer than it really did.
|
||||
//
|
||||
// This latter inaccuracy could be avoided by moving the
|
||||
// CanDuplicateLastSampleDueToSleep() check within the thread-freezing code,
|
||||
// e.g. the section where Tick() is called. But that would reduce the
|
||||
// effectiveness of the optimization because more code would have to be run
|
||||
// before we can tell that duplication is allowed.
|
||||
//
|
||||
static const int AWAKE = 0;
|
||||
static const int SLEEPING_NOT_OBSERVED = 1;
|
||||
static const int SLEEPING_OBSERVED = 2;
|
||||
mozilla::Atomic<int> mSleep;
|
||||
};
|
||||
|
||||
// Contains data for partial profiles that get saved when
|
||||
// ThreadInfo::FlushSamplesAndMarkers gets called.
|
||||
struct PartialThreadProfile final
|
||||
{
|
||||
PartialThreadProfile(mozilla::UniquePtr<char[]>&& aSamplesJSON,
|
||||
mozilla::UniquePtr<char[]>&& aMarkersJSON,
|
||||
mozilla::UniquePtr<UniqueStacks>&& aUniqueStacks)
|
||||
: mSamplesJSON(mozilla::Move(aSamplesJSON))
|
||||
, mMarkersJSON(mozilla::Move(aMarkersJSON))
|
||||
, mUniqueStacks(mozilla::Move(aUniqueStacks))
|
||||
{}
|
||||
|
||||
mozilla::UniquePtr<char[]> mSamplesJSON;
|
||||
mozilla::UniquePtr<char[]> mMarkersJSON;
|
||||
mozilla::UniquePtr<UniqueStacks> mUniqueStacks;
|
||||
};
|
||||
|
||||
// This class contains the info for a single thread.
|
||||
//
|
||||
// Note: A thread's ThreadInfo can be held onto after the thread itself exits,
|
||||
// because we may need to output profiling information about that thread. But
|
||||
// some of the fields in this class are only relevant while the thread is
|
||||
// alive. It's possible that this class could be refactored so there is a
|
||||
// clearer split between those fields and the fields that are still relevant
|
||||
// after the thread exists.
|
||||
class ThreadInfo final
|
||||
{
|
||||
public:
|
||||
NS_INLINE_DECL_THREADSAFE_REFCOUNTING(ThreadInfo)
|
||||
ThreadInfo(const char* aName, int aThreadId, bool aIsMainThread,
|
||||
nsIEventTarget* aThread, void* aStackTop);
|
||||
|
||||
ThreadInfo(const char* aName, int aThreadId, bool aIsMainThread)
|
||||
: mName(aName)
|
||||
, mRegisterTime(TimeStamp::Now())
|
||||
, mThreadId(aThreadId)
|
||||
, mIsMainThread(aIsMainThread)
|
||||
{
|
||||
// I don't know if we can assert this. But we should warn.
|
||||
MOZ_ASSERT(aThreadId >= 0, "native thread ID is < 0");
|
||||
MOZ_ASSERT(aThreadId <= INT32_MAX, "native thread ID is > INT32_MAX");
|
||||
}
|
||||
~ThreadInfo();
|
||||
|
||||
const char* Name() const { return mName.get(); }
|
||||
mozilla::TimeStamp RegisterTime() const { return mRegisterTime; }
|
||||
int ThreadId() const { return mThreadId; }
|
||||
|
||||
// This is a safe read even when the target thread is not blocked, as this
|
||||
// thread id is never mutated.
|
||||
int ThreadId() const { return RacyInfo()->ThreadId(); }
|
||||
|
||||
bool IsMainThread() const { return mIsMainThread; }
|
||||
|
||||
private:
|
||||
~ThreadInfo() {}
|
||||
mozilla::NotNull<RacyThreadInfo*> RacyInfo() const { return mRacyInfo; }
|
||||
|
||||
const nsCString mName;
|
||||
const mozilla::TimeStamp mRegisterTime;
|
||||
const int mThreadId;
|
||||
void StartProfiling();
|
||||
void StopProfiling();
|
||||
bool IsBeingProfiled() { return mIsBeingProfiled; }
|
||||
|
||||
void NotifyUnregistered(uint64_t aBufferPosition)
|
||||
{
|
||||
mUnregisterTime = TimeStamp::Now();
|
||||
mBufferPositionWhenUnregistered = mozilla::Some(aBufferPosition);
|
||||
}
|
||||
mozilla::Maybe<uint64_t> BufferPositionWhenUnregistered() { return mBufferPositionWhenUnregistered; }
|
||||
|
||||
PlatformData* GetPlatformData() const { return mPlatformData.get(); }
|
||||
void* StackTop() const { return mStackTop; }
|
||||
|
||||
size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const;
|
||||
|
||||
mozilla::Maybe<uint64_t>& LastSample() { return mLastSample; }
|
||||
|
||||
private:
|
||||
mozilla::UniqueFreePtr<char> mName;
|
||||
mozilla::TimeStamp mRegisterTime;
|
||||
mozilla::TimeStamp mUnregisterTime;
|
||||
mozilla::Maybe<uint64_t> mBufferPositionWhenUnregistered;
|
||||
const bool mIsMainThread;
|
||||
nsCOMPtr<nsIEventTarget> mThread;
|
||||
|
||||
// The thread's RacyThreadInfo. This is an owning pointer. It could be an
|
||||
// inline member, but we don't do that because RacyThreadInfo is quite large
|
||||
// (due to the PseudoStack within it), and we have ThreadInfo vectors and so
|
||||
// we'd end up wasting a lot of space in those vectors for excess elements.
|
||||
mozilla::NotNull<RacyThreadInfo*> mRacyInfo;
|
||||
|
||||
UniquePlatformData mPlatformData;
|
||||
void* mStackTop;
|
||||
|
||||
//
|
||||
// The following code is only used for threads that are being profiled, i.e.
|
||||
// for which IsBeingProfiled() returns true.
|
||||
//
|
||||
|
||||
public:
|
||||
void StreamJSON(const ProfileBuffer& aBuffer, SpliceableJSONWriter& aWriter,
|
||||
const mozilla::TimeStamp& aProcessStartTime,
|
||||
double aSinceTime);
|
||||
|
||||
// Call this method when the JS entries inside the buffer are about to
|
||||
// become invalid, i.e., just before JS shutdown.
|
||||
void FlushSamplesAndMarkers(const mozilla::TimeStamp& aProcessStartTime,
|
||||
ProfileBuffer& aBuffer);
|
||||
|
||||
// Returns nullptr if this is not the main thread or if this thread is not
|
||||
// being profiled.
|
||||
ThreadResponsiveness* GetThreadResponsiveness()
|
||||
{
|
||||
ThreadResponsiveness* responsiveness = mResponsiveness.ptrOr(nullptr);
|
||||
MOZ_ASSERT(!responsiveness || mIsBeingProfiled);
|
||||
return responsiveness;
|
||||
}
|
||||
|
||||
// Set the JSContext of the thread to be sampled. Sampling cannot begin until
|
||||
// this has been set.
|
||||
void SetJSContext(JSContext* aContext)
|
||||
{
|
||||
// This function runs on-thread.
|
||||
|
||||
MOZ_ASSERT(aContext && !mContext);
|
||||
|
||||
mContext = aContext;
|
||||
|
||||
// We give the JS engine a non-owning reference to the RacyInfo (just the
|
||||
// PseudoStack, really). It's important that the JS engine doesn't touch
|
||||
// this once the thread dies.
|
||||
js::SetContextProfilingStack(aContext, RacyInfo());
|
||||
|
||||
PollJSSampling();
|
||||
}
|
||||
|
||||
// Request that this thread start JS sampling. JS sampling won't actually
|
||||
// start until a subsequent PollJSSampling() call occurs *and* mContext has
|
||||
// been set.
|
||||
void StartJSSampling()
|
||||
{
|
||||
// This function runs on-thread or off-thread.
|
||||
|
||||
MOZ_RELEASE_ASSERT(mJSSampling == INACTIVE ||
|
||||
mJSSampling == INACTIVE_REQUESTED);
|
||||
mJSSampling = ACTIVE_REQUESTED;
|
||||
}
|
||||
|
||||
// Request that this thread stop JS sampling. JS sampling won't actually stop
|
||||
// until a subsequent PollJSSampling() call occurs.
|
||||
void StopJSSampling()
|
||||
{
|
||||
// This function runs on-thread or off-thread.
|
||||
|
||||
MOZ_RELEASE_ASSERT(mJSSampling == ACTIVE ||
|
||||
mJSSampling == ACTIVE_REQUESTED);
|
||||
mJSSampling = INACTIVE_REQUESTED;
|
||||
}
|
||||
|
||||
// Poll to see if JS sampling should be started/stopped.
|
||||
void PollJSSampling()
|
||||
{
|
||||
// This function runs on-thread.
|
||||
|
||||
// We can't start/stop profiling until we have the thread's JSContext.
|
||||
if (mContext) {
|
||||
// It is possible for mJSSampling to go through the following sequences.
|
||||
//
|
||||
// - INACTIVE, ACTIVE_REQUESTED, INACTIVE_REQUESTED, INACTIVE
|
||||
//
|
||||
// - ACTIVE, INACTIVE_REQUESTED, ACTIVE_REQUESTED, ACTIVE
|
||||
//
|
||||
// Therefore, the if and else branches here aren't always interleaved.
|
||||
// This is ok because the JS engine can handle that.
|
||||
//
|
||||
if (mJSSampling == ACTIVE_REQUESTED) {
|
||||
mJSSampling = ACTIVE;
|
||||
js::EnableContextProfilingStack(mContext, true);
|
||||
js::RegisterContextProfilingEventMarker(mContext, profiler_add_marker);
|
||||
|
||||
} else if (mJSSampling == INACTIVE_REQUESTED) {
|
||||
mJSSampling = INACTIVE;
|
||||
js::EnableContextProfilingStack(mContext, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
bool mIsBeingProfiled;
|
||||
|
||||
// JS frames in the buffer may require a live JSRuntime to stream (e.g.,
|
||||
// stringifying JIT frames). In the case of JSRuntime destruction,
|
||||
// FlushSamplesAndMarkers should be called to save them. These are spliced
|
||||
// into the final stream.
|
||||
UniquePtr<PartialThreadProfile> mPartialProfile;
|
||||
|
||||
// This is used only for nsIThreads.
|
||||
mozilla::Maybe<ThreadResponsiveness> mResponsiveness;
|
||||
|
||||
public:
|
||||
// If this is a JS thread, this is its JSContext, which is required for any
|
||||
// JS sampling.
|
||||
JSContext* mContext;
|
||||
|
||||
private:
|
||||
// The profiler needs to start and stop JS sampling of JS threads at various
|
||||
// times. However, the JS engine can only do the required actions on the
|
||||
// JS thread itself ("on-thread"), not from another thread ("off-thread").
|
||||
// Therefore, we have the following two-step process.
|
||||
//
|
||||
// - The profiler requests (on-thread or off-thread) that the JS sampling be
|
||||
// started/stopped, by changing mJSSampling to the appropriate REQUESTED
|
||||
// state.
|
||||
//
|
||||
// - The relevant JS thread polls (on-thread) for changes to mJSSampling.
|
||||
// When it sees a REQUESTED state, it performs the appropriate actions to
|
||||
// actually start/stop JS sampling, and changes mJSSampling out of the
|
||||
// REQUESTED state.
|
||||
//
|
||||
// The state machine is as follows.
|
||||
//
|
||||
// INACTIVE --> ACTIVE_REQUESTED
|
||||
// ^ ^ |
|
||||
// | _/ |
|
||||
// | _/ |
|
||||
// | / |
|
||||
// | v v
|
||||
// INACTIVE_REQUESTED <-- ACTIVE
|
||||
//
|
||||
// The polling is done in the following two ways.
|
||||
//
|
||||
// - Via the interrupt callback mechanism; the JS thread must call
|
||||
// profiler_js_interrupt_callback() from its own interrupt callback.
|
||||
// This is how sampling must be started/stopped for threads where the
|
||||
// request was made off-thread.
|
||||
//
|
||||
// - When {Start,Stop}JSSampling() is called on-thread, we can immediately
|
||||
// follow it with a PollJSSampling() call to avoid the delay between the
|
||||
// two steps. Likewise, setJSContext() calls PollJSSampling().
|
||||
//
|
||||
// One non-obvious thing about all this: these JS sampling requests are made
|
||||
// on all threads, even non-JS threads. mContext needs to also be set (via
|
||||
// setJSContext(), which can only happen for JS threads) for any JS sampling
|
||||
// to actually happen.
|
||||
//
|
||||
enum {
|
||||
INACTIVE = 0,
|
||||
ACTIVE_REQUESTED = 1,
|
||||
ACTIVE = 2,
|
||||
INACTIVE_REQUESTED = 3,
|
||||
} mJSSampling;
|
||||
|
||||
// When sampling, this holds the position in ActivePS::mBuffer of the most
|
||||
// recent sample for this thread, or Nothing() if there is no sample for this
|
||||
// thread in the buffer.
|
||||
mozilla::Maybe<uint64_t> mLastSample;
|
||||
};
|
||||
|
||||
void
|
||||
StreamSamplesAndMarkers(const char* aName, int aThreadId,
|
||||
const ProfileBuffer& aBuffer,
|
||||
SpliceableJSONWriter& aWriter,
|
||||
const mozilla::TimeStamp& aProcessStartTime,
|
||||
const TimeStamp& aRegisterTime,
|
||||
const TimeStamp& aUnregisterTime,
|
||||
double aSinceTime,
|
||||
JSContext* aContext,
|
||||
UniquePtr<char[]>&& aPartialSamplesJSON,
|
||||
UniquePtr<char[]>&& aPartialMarkersJSON,
|
||||
UniqueStacks& aUniqueStacks);
|
||||
|
||||
#endif // ThreadInfo_h
|
||||
|
@ -296,7 +296,7 @@ Sampler::Disable(PSLockRef aLock)
|
||||
template<typename Func>
|
||||
void
|
||||
Sampler::SuspendAndSampleAndResumeThread(PSLockRef aLock,
|
||||
const RegisteredThread& aRegisteredThread,
|
||||
const ThreadInfo& aThreadInfo,
|
||||
const Func& aProcessRegs)
|
||||
{
|
||||
// Only one sampler thread can be sampling at once. So we expect to have
|
||||
@ -306,7 +306,7 @@ Sampler::SuspendAndSampleAndResumeThread(PSLockRef aLock,
|
||||
if (mSamplerTid == -1) {
|
||||
mSamplerTid = gettid();
|
||||
}
|
||||
int sampleeTid = aRegisteredThread.Info()->ThreadId();
|
||||
int sampleeTid = aThreadInfo.ThreadId();
|
||||
MOZ_RELEASE_ASSERT(sampleeTid != mSamplerTid);
|
||||
|
||||
//----------------------------------------------------------------//
|
||||
|
@ -84,11 +84,11 @@ Sampler::Disable(PSLockRef aLock)
|
||||
template<typename Func>
|
||||
void
|
||||
Sampler::SuspendAndSampleAndResumeThread(PSLockRef aLock,
|
||||
const RegisteredThread& aRegisteredThread,
|
||||
const ThreadInfo& aThreadInfo,
|
||||
const Func& aProcessRegs)
|
||||
{
|
||||
thread_act_t samplee_thread =
|
||||
aRegisteredThread.GetPlatformData()->ProfiledThread();
|
||||
aThreadInfo.GetPlatformData()->ProfiledThread();
|
||||
|
||||
//----------------------------------------------------------------//
|
||||
// Suspend the samplee thread and get its context.
|
||||
|
@ -127,10 +127,10 @@ Sampler::Disable(PSLockRef aLock)
|
||||
template<typename Func>
|
||||
void
|
||||
Sampler::SuspendAndSampleAndResumeThread(PSLockRef aLock,
|
||||
const RegisteredThread& aRegisteredThread,
|
||||
const ThreadInfo& aThreadInfo,
|
||||
const Func& aProcessRegs)
|
||||
{
|
||||
HANDLE profiled_thread = aRegisteredThread.GetPlatformData()->ProfiledThread();
|
||||
HANDLE profiled_thread = aThreadInfo.GetPlatformData()->ProfiledThread();
|
||||
if (profiled_thread == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -21,11 +21,10 @@ if CONFIG['MOZ_GECKO_PROFILER']:
|
||||
'core/platform.cpp',
|
||||
'core/ProfileBuffer.cpp',
|
||||
'core/ProfileBufferEntry.cpp',
|
||||
'core/ProfiledThreadData.cpp',
|
||||
'core/ProfileJSONWriter.cpp',
|
||||
'core/ProfilerBacktrace.cpp',
|
||||
'core/ProfilerMarkerPayload.cpp',
|
||||
'core/RegisteredThread.cpp',
|
||||
'core/ThreadInfo.cpp',
|
||||
'gecko/ChildProfilerController.cpp',
|
||||
'gecko/nsProfilerFactory.cpp',
|
||||
'gecko/nsProfilerStartParams.cpp',
|
||||
|
@ -9,8 +9,21 @@
|
||||
#include "ProfileBufferEntry.h"
|
||||
#include "ThreadInfo.h"
|
||||
|
||||
// Make sure we can initialize our thread profile
|
||||
TEST(ThreadProfile, Initialization) {
|
||||
int tid = 1000;
|
||||
nsCOMPtr<nsIThread> mainThread;
|
||||
NS_GetMainThread(getter_AddRefs(mainThread));
|
||||
ThreadInfo info("testThread", tid, true, mainThread, nullptr);
|
||||
info.StartProfiling();
|
||||
}
|
||||
|
||||
// Make sure we can record one entry and read it
|
||||
TEST(ThreadProfile, InsertOneEntry) {
|
||||
int tid = 1000;
|
||||
nsCOMPtr<nsIThread> mainThread;
|
||||
NS_GetMainThread(getter_AddRefs(mainThread));
|
||||
ThreadInfo info("testThread", tid, true, mainThread, nullptr);
|
||||
auto pb = MakeUnique<ProfileBuffer>(10);
|
||||
pb->AddEntry(ProfileBufferEntry::Time(123.1));
|
||||
ASSERT_TRUE(pb->GetEntry(pb->mRangeStart).IsTime());
|
||||
@ -19,6 +32,10 @@ TEST(ThreadProfile, InsertOneEntry) {
|
||||
|
||||
// See if we can insert some entries
|
||||
TEST(ThreadProfile, InsertEntriesNoWrap) {
|
||||
int tid = 1000;
|
||||
nsCOMPtr<nsIThread> mainThread;
|
||||
NS_GetMainThread(getter_AddRefs(mainThread));
|
||||
ThreadInfo info("testThread", tid, true, mainThread, nullptr);
|
||||
auto pb = MakeUnique<ProfileBuffer>(100);
|
||||
int test_size = 50;
|
||||
for (int i = 0; i < test_size; i++) {
|
||||
@ -34,7 +51,11 @@ TEST(ThreadProfile, InsertEntriesNoWrap) {
|
||||
|
||||
// See if evicting works as it should in the basic case
|
||||
TEST(ThreadProfile, InsertEntriesWrap) {
|
||||
int tid = 1000;
|
||||
int entries = 32;
|
||||
nsCOMPtr<nsIThread> mainThread;
|
||||
NS_GetMainThread(getter_AddRefs(mainThread));
|
||||
ThreadInfo info("testThread", tid, true, mainThread, nullptr);
|
||||
auto pb = MakeUnique<ProfileBuffer>(entries);
|
||||
ASSERT_TRUE(pb->mRangeStart == 0);
|
||||
ASSERT_TRUE(pb->mRangeEnd == 0);
|
||||
|
Loading…
Reference in New Issue
Block a user