зеркало из https://github.com/mozilla/gecko-dev.git
394 строки
14 KiB
C++
394 строки
14 KiB
C++
/* -*- 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 ThreadInfo_h
|
|
#define ThreadInfo_h
|
|
|
|
#include "mozilla/NotNull.h"
|
|
#include "mozilla/TimeStamp.h"
|
|
#include "mozilla/UniquePtrExtensions.h"
|
|
|
|
#include "platform.h"
|
|
#include "ProfileBuffer.h"
|
|
#include "js/ProfilingStack.h"
|
|
|
|
// 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:
|
|
RacyThreadInfo()
|
|
: PseudoStack()
|
|
, 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, 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; }
|
|
|
|
private:
|
|
// A list of pending markers that must be moved to the circular buffer.
|
|
ProfilerSignalSafeLinkedList<ProfilerMarker> mPendingMarkers;
|
|
|
|
// 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 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:
|
|
ThreadInfo(const char* aName, int aThreadId, bool aIsMainThread,
|
|
void* aStackTop);
|
|
|
|
~ThreadInfo();
|
|
|
|
const char* Name() const { return mName.get(); }
|
|
int ThreadId() const { return mThreadId; }
|
|
|
|
bool IsMainThread() const { return mIsMainThread; }
|
|
|
|
mozilla::NotNull<RacyThreadInfo*> RacyInfo() const { return mRacyInfo; }
|
|
|
|
void StartProfiling();
|
|
void StopProfiling();
|
|
bool IsBeingProfiled() { return mIsBeingProfiled; }
|
|
|
|
void NotifyUnregistered() { mUnregisterTime = TimeStamp::Now(); }
|
|
|
|
PlatformData* GetPlatformData() const { return mPlatformData.get(); }
|
|
void* StackTop() const { return mStackTop; }
|
|
|
|
size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const;
|
|
|
|
ProfileBuffer::LastSample& LastSample() { return mLastSample; }
|
|
|
|
private:
|
|
mozilla::UniqueFreePtr<char> mName;
|
|
mozilla::TimeStamp mRegisterTime;
|
|
mozilla::TimeStamp mUnregisterTime;
|
|
int mThreadId;
|
|
const bool mIsMainThread;
|
|
|
|
// 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:
|
|
// Returns the time of the first sample.
|
|
double 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 == (mIsMainThread && 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.
|
|
mozilla::UniquePtr<char[]> mSavedStreamedSamples;
|
|
double mFirstSavedStreamedSampleTime;
|
|
mozilla::UniquePtr<char[]> mSavedStreamedMarkers;
|
|
mozilla::Maybe<UniqueStacks> mUniqueStacks;
|
|
|
|
// This is only used for the main thread.
|
|
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 generation number and offset in
|
|
// ActivePS::mBuffer of the most recent sample for this thread.
|
|
ProfileBuffer::LastSample 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,
|
|
double* aOutFirstSampleTime,
|
|
JSContext* aContext,
|
|
char* aSavedStreamedSamples,
|
|
double aFirstSavedStreamedSampleTime,
|
|
char* aSavedStreamedMarkers,
|
|
UniqueStacks& aUniqueStacks);
|
|
|
|
#endif // ThreadInfo_h
|