зеркало из https://github.com/mozilla/gecko-dev.git
189 строки
7.0 KiB
C++
189 строки
7.0 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 RegisteredThread_h
|
|
#define RegisteredThread_h
|
|
|
|
#include "platform.h"
|
|
#include "ProfilerMarker.h"
|
|
#include "BaseProfilerMarkerPayload.h"
|
|
#include "ThreadInfo.h"
|
|
|
|
#include "mozilla/UniquePtr.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), mIsBeingProfiled(false) {}
|
|
|
|
~RacyRegisteredThread() {}
|
|
|
|
void SetIsBeingProfiled(bool aIsBeingProfiled) {
|
|
mIsBeingProfiled = aIsBeingProfiled;
|
|
}
|
|
|
|
bool IsBeingProfiled() const { return mIsBeingProfiled; }
|
|
|
|
void AddPendingMarker(const char* aMarkerName,
|
|
JS::ProfilingCategoryPair aCategoryPair,
|
|
mozilla::UniquePtr<ProfilerMarkerPayload> aPayload,
|
|
double aTime) {
|
|
// Note: We don't assert on mIsBeingProfiled, because it could have changed
|
|
// between the check in the caller and now.
|
|
ProfilerMarker* marker = new ProfilerMarker(
|
|
aMarkerName, aCategoryPair, mThreadId, std::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 ProfilingStack& ProfilingStack() {
|
|
return mProfilingStack;
|
|
}
|
|
const class ProfilingStack& ProfilingStack() const { return mProfilingStack; }
|
|
|
|
private:
|
|
class ProfilingStack mProfilingStack;
|
|
|
|
// 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;
|
|
|
|
// Is this thread being profiled? (e.g., should markers be recorded?)
|
|
// Accesses to this atomic are not recorded by web replay as they may occur
|
|
// at non-deterministic points.
|
|
mozilla::Atomic<bool, mozilla::MemoryOrdering::Relaxed,
|
|
mozilla::recordreplay::Behavior::DontPreserve>
|
|
mIsBeingProfiled;
|
|
};
|
|
|
|
// 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, 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;
|
|
|
|
const RefPtr<ThreadInfo> Info() const { return mThreadInfo; }
|
|
|
|
private:
|
|
class RacyRegisteredThread mRacyRegisteredThread;
|
|
|
|
const UniquePlatformData mPlatformData;
|
|
const void* mStackTop;
|
|
|
|
const RefPtr<ThreadInfo> mThreadInfo;
|
|
};
|
|
|
|
#endif // RegisteredThread_h
|