Bug 1431755 - Part 2: Teach GeckoProfiler to profile responsiveness on nsIThreads.
MozReview-Commit-ID: AqpNf9pDzrg
--- a/tools/profiler/core/ThreadInfo.cpp
+++ b/tools/profiler/core/ThreadInfo.cpp
@@ -17,20 +17,22 @@
#define getpid _getpid
#else
#include <unistd.h> // for getpid()
#endif
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)
, mFirstSavedStreamedSampleTime{0.0}
, mContext(nullptr)
, mJSSampling(INACTIVE)
, mLastSample()
@@ -55,19 +57,17 @@ ThreadInfo::~ThreadInfo()
delete mRacyInfo;
}
void
ThreadInfo::StartProfiling()
{
mIsBeingProfiled = true;
mRacyInfo->ReinitializeOnResume();
- if (mIsMainThread) {
- mResponsiveness.emplace();
- }
+ mResponsiveness.emplace(mThread, mIsMainThread);
}
void
ThreadInfo::StopProfiling()
{
mResponsiveness.reset();
mIsBeingProfiled = false;
}
--- a/tools/profiler/core/ThreadInfo.h
+++ b/tools/profiler/core/ThreadInfo.h
@@ -170,17 +170,17 @@ private:
// 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);
+ nsIEventTarget* aThread, void* aStackTop);
~ThreadInfo();
const char* Name() const { return mName.get(); }
// 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(); }
@@ -202,16 +202,17 @@ public:
ProfileBuffer::LastSample& LastSample() { return mLastSample; }
private:
mozilla::UniqueFreePtr<char> mName;
mozilla::TimeStamp mRegisterTime;
mozilla::TimeStamp mUnregisterTime;
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;
@@ -233,17 +234,17 @@ public:
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));
+ 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.
@@ -318,17 +319,17 @@ private:
// 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.
+ // 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:
--- a/tools/profiler/core/platform.cpp
+++ b/tools/profiler/core/platform.cpp
@@ -1998,20 +1998,17 @@ SamplerThread::Run()
ActivePS::Buffer(lock).DuplicateLastSample(
info->ThreadId(), CorePS::ProcessStartTime(),
info->LastSample());
if (dup_ok) {
continue;
}
}
- // We only track responsiveness for the main thread.
- if (info->IsMainThread()) {
- info->GetThreadResponsiveness()->Update();
- }
+ info->GetThreadResponsiveness()->Update();
// We only get the memory measurements once for all live threads.
int64_t rssMemory = 0;
int64_t ussMemory = 0;
if (i == 0 && ActivePS::FeatureMemory(lock)) {
rssMemory = nsMemoryReporterManager::ResidentFast();
#if defined(GP_OS_linux) || defined(GP_OS_android)
ussMemory = nsMemoryReporterManager::ResidentUnique();
@@ -2184,17 +2181,19 @@ locked_register_thread(PSLockRef aLock,
VTUNE_REGISTER_THREAD(aName);
if (!TLSInfo::Init(aLock)) {
return;
}
ThreadInfo* info = new ThreadInfo(aName, Thread::GetCurrentId(),
- NS_IsMainThread(), aStackTop);
+ NS_IsMainThread(),
+ NS_GetCurrentThreadNoCreate(),
+ aStackTop);
TLSInfo::SetInfo(aLock, info);
if (ActivePS::Exists(aLock) && ActivePS::ShouldProfileThread(aLock, info)) {
info->StartProfiling();
if (ActivePS::FeatureJS(aLock)) {
// This StartJSSampling() call is on-thread, so we can poll manually to
// start JS sampling immediately.
info->StartJSSampling();
--- a/tools/profiler/gecko/ThreadResponsiveness.cpp
+++ b/tools/profiler/gecko/ThreadResponsiveness.cpp
@@ -8,129 +8,163 @@
#include "mozilla/Atomics.h"
#include "mozilla/SystemGroup.h"
#include "nsITimer.h"
#include "platform.h"
using namespace mozilla;
-class CheckResponsivenessTask : public Runnable,
+class CheckResponsivenessTask : public CancelableRunnable,
public nsITimerCallback {
public:
- CheckResponsivenessTask()
- : Runnable("CheckResponsivenessTask")
+ explicit CheckResponsivenessTask(nsIEventTarget* aThread, bool aIsMainThread)
+ : CancelableRunnable("CheckResponsivenessTask")
, mStartToPrevTracer_us(uint64_t(profiler_time() * 1000.0))
, mStop(false)
, mHasEverBeenSuccessfullyDispatched(false)
+ , mThread(aThread)
+ , mIsMainThread(aIsMainThread)
{
}
protected:
~CheckResponsivenessTask()
{
}
public:
// Must be called from the same thread every time. Call that the update
// thread, because it's the thread that ThreadResponsiveness::Update() is
// called on. In reality it's the profiler's sampler thread.
- void DoFirstDispatchIfNeeded()
+ bool DoFirstDispatchIfNeeded()
{
if (mHasEverBeenSuccessfullyDispatched) {
- return;
+ return true;
}
- // We can hit this code very early during startup, at a time where the
- // thread manager hasn't been initialized with the main thread yet.
- // In that case, calling SystemGroup::Dispatch would assert, so we make
- // sure that NS_GetMainThread succeeds before attempting to dispatch this
- // runnable.
- nsCOMPtr<nsIThread> mainThread;
- nsresult rv = NS_GetMainThread(getter_AddRefs(mainThread));
- if (NS_SUCCEEDED(rv) && mainThread) {
- rv = SystemGroup::Dispatch(TaskCategory::Other, do_AddRef(this));
+ // The profiler for the main thread is set up before the thread manager is,
+ // meaning we can't get the nsIThread when the CheckResponsivenessTask is
+ // constructed. We _do_ know whether it is the main thread at that time,
+ // however, so here's the workaround. We can still hit this code before the
+ // thread manager is initted, in which case we won't try to record
+ // responsiveness, which is fine because there's no event queue to check
+ // responsiveness on anyway.
+ if (mIsMainThread) {
+ if (!mThread) {
+ nsCOMPtr<nsIThread> temp;
+ NS_GetMainThread(getter_AddRefs(temp));
+ mThread = temp.forget();
+ }
+
+ if (mThread) {
+ nsresult rv = SystemGroup::Dispatch(TaskCategory::Other, do_AddRef(this));
+ if (NS_SUCCEEDED(rv)) {
+ mHasEverBeenSuccessfullyDispatched = true;
+ }
+ }
+ } else if (mThread) {
+ nsresult rv = mThread->Dispatch(this, nsIThread::NS_DISPATCH_NORMAL);
if (NS_SUCCEEDED(rv)) {
mHasEverBeenSuccessfullyDispatched = true;
}
}
+
+ return mHasEverBeenSuccessfullyDispatched;
}
- // Can only run on the main thread.
+ nsresult Cancel() override
+ {
+ // No special work needed.
+ return NS_OK;
+ }
+
+ // Only runs on the thread being profiled
NS_IMETHOD Run() override
{
mStartToPrevTracer_us = uint64_t(profiler_time() * 1000.0);
if (!mStop) {
if (!mTimer) {
- mTimer = NS_NewTimer(
- SystemGroup::EventTargetFor(TaskCategory::Other));
+ if (mIsMainThread) {
+ mTimer = NS_NewTimer(
+ SystemGroup::EventTargetFor(TaskCategory::Other));
+ } else {
+ mTimer = NS_NewTimer();
+ }
}
mTimer->InitWithCallback(this, 16, nsITimer::TYPE_ONE_SHOT);
}
return NS_OK;
}
- // Main thread only
+ // Should always fire on the thread being profiled
NS_IMETHOD Notify(nsITimer* aTimer) final override
{
- SystemGroup::Dispatch(TaskCategory::Other,
- do_AddRef(this));
+ mThread->Dispatch(this, nsIThread::NS_DISPATCH_NORMAL);
return NS_OK;
}
// Can be called on any thread.
void Terminate() {
mStop = true;
}
// Can be called on any thread.
double GetStartToPrevTracer_ms() const {
return mStartToPrevTracer_us / 1000.0;
}
NS_DECL_ISUPPORTS_INHERITED
private:
- // The timer that's responsible for redispatching this event to the main
- // thread. This field is only accessed on the main thread.
+ // The timer that's responsible for redispatching this event to the thread we
+ // are profiling (ie; mThread). Only touched on mThread.
nsCOMPtr<nsITimer> mTimer;
// The time (in integer microseconds since process startup) at which this
// event was last processed (Run() was last called).
- // This field is written on the main thread and read on the update thread.
+ // This field is written on mThread and read on the update thread.
// This is stored as integer microseconds instead of double milliseconds
// because Atomic<double> is not available.
Atomic<uint64_t> mStartToPrevTracer_us;
// Whether we should stop redispatching this event once the timer fires the
// next time. Set to true by any thread when the profiler is stopped; read on
- // the main thread.
+ // mThread.
Atomic<bool> mStop;
// Only accessed on the update thread.
bool mHasEverBeenSuccessfullyDispatched;
+
+ // The thread that we're profiling. Use nsIEventTarget to allow for checking
+ // responsiveness on non-nsIThreads someday.
+ nsCOMPtr<nsIEventTarget> mThread;
+ bool mIsMainThread;
};
-NS_IMPL_ISUPPORTS_INHERITED(CheckResponsivenessTask, mozilla::Runnable,
+NS_IMPL_ISUPPORTS_INHERITED(CheckResponsivenessTask, CancelableRunnable,
nsITimerCallback)
-ThreadResponsiveness::ThreadResponsiveness()
- : mActiveTracerEvent(new CheckResponsivenessTask())
+ThreadResponsiveness::ThreadResponsiveness(nsIEventTarget* aThread,
+ bool aIsMainThread)
+ : mActiveTracerEvent(new CheckResponsivenessTask(aThread, aIsMainThread))
{
MOZ_COUNT_CTOR(ThreadResponsiveness);
}
ThreadResponsiveness::~ThreadResponsiveness()
{
MOZ_COUNT_DTOR(ThreadResponsiveness);
mActiveTracerEvent->Terminate();
}
void
ThreadResponsiveness::Update()
{
- mActiveTracerEvent->DoFirstDispatchIfNeeded();
+ if (!mActiveTracerEvent->DoFirstDispatchIfNeeded()) {
+ return;
+ }
mStartToPrevTracer_ms = Some(mActiveTracerEvent->GetStartToPrevTracer_ms());
}
--- a/tools/profiler/gecko/ThreadResponsiveness.h
+++ b/tools/profiler/gecko/ThreadResponsiveness.h
@@ -7,21 +7,22 @@
#define ThreadResponsiveness_h
#include "nsISupports.h"
#include "mozilla/Maybe.h"
#include "mozilla/RefPtr.h"
#include "mozilla/TimeStamp.h"
class CheckResponsivenessTask;
+class nsIEventTarget;
// This class should only be used for the main thread.
class ThreadResponsiveness {
public:
- explicit ThreadResponsiveness();
+ explicit ThreadResponsiveness(nsIEventTarget* aThread, bool aIsMainThread);
~ThreadResponsiveness();
void Update();
// The number of milliseconds that elapsed since the last
// CheckResponsivenessTask was processed.
double GetUnresponsiveDuration(double aStartToNow_ms) const {
--- a/tools/profiler/tests/gtest/ThreadProfileTest.cpp
+++ b/tools/profiler/tests/gtest/ThreadProfileTest.cpp
@@ -7,35 +7,41 @@
#include "gtest/gtest.h"
#include "ProfileBufferEntry.h"
#include "ThreadInfo.h"
// Make sure we can initialize our thread profile
TEST(ThreadProfile, Initialization) {
int tid = 1000;
- ThreadInfo info("testThread", tid, true, nullptr);
+ 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;
- ThreadInfo info("testThread", tid, true, nullptr);
+ 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->mEntries != nullptr);
ASSERT_TRUE(pb->mEntries[pb->mReadPos].IsTime());
ASSERT_TRUE(pb->mEntries[pb->mReadPos].u.mDouble == 123.1);
}
// See if we can insert some entries
TEST(ThreadProfile, InsertEntriesNoWrap) {
int tid = 1000;
- ThreadInfo info("testThread", tid, true, nullptr);
+ 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++) {
pb->AddEntry(ProfileBufferEntry::Time(i));
}
ASSERT_TRUE(pb->mEntries != nullptr);
int readPos = pb->mReadPos;
while (readPos != pb->mWritePos) {
@@ -46,17 +52,19 @@ TEST(ThreadProfile, InsertEntriesNoWrap)
}
// See if wrapping works as it should in the basic case
TEST(ThreadProfile, InsertEntriesWrap) {
int tid = 1000;
// we can fit only 24 entries in this buffer because of the empty slot
int entries = 24;
int buffer_size = entries + 1;
- ThreadInfo info("testThread", tid, true, nullptr);
+ nsCOMPtr<nsIThread> mainThread;
+ NS_GetMainThread(getter_AddRefs(mainThread));
+ ThreadInfo info("testThread", tid, true, mainThread, nullptr);
auto pb = MakeUnique<ProfileBuffer>(buffer_size);
int test_size = 43;
for (int i = 0; i < test_size; i++) {
pb->AddEntry(ProfileBufferEntry::Time(i));
}
ASSERT_TRUE(pb->mEntries != nullptr);
int readPos = pb->mReadPos;
int ctr = 0;