--- a/tools/profiler/gecko/ProfileGatherer.cpp
+++ b/tools/profiler/gecko/ProfileGatherer.cpp
@@ -18,168 +18,240 @@ namespace mozilla {
* circular, so as soon as we receive another exit profile, we'll
* bump the oldest one out of the buffer.
*/
static const uint32_t MAX_SUBPROCESS_EXIT_PROFILES = 5;
NS_IMPL_ISUPPORTS0(ProfileGatherer)
ProfileGatherer::ProfileGatherer()
- : mPendingProfiles(0)
+ : mRemainingProfilesCount(0)
+ , mRegisteredProfilesCount(0)
, mGathering(false)
{
}
ProfileGatherer::~ProfileGatherer()
{
- Cancel();
+ MOZ_RELEASE_ASSERT(NS_IsMainThread());
+ // If we have a Promise in flight, we should reject it.
+ if (mPromiseHolder.isSome()) {
+ mPromiseHolder->RejectIfExists(NS_ERROR_DOM_ABORT_ERR, __func__);
+ }
+ if (mThread) {
+ mThread->Shutdown();
+ }
+}
+
+nsresult
+ProfileGatherer::EnsureThread()
+{
+ MOZ_RELEASE_ASSERT(NS_IsMainThread());
+ if (!mThread) {
+ return NS_NewNamedThread("ProfileGather", getter_AddRefs(mThread));
+ }
+ return NS_OK;
+}
+
+RefPtr<ProfileGatherer::ProfileGatherPromise>
+ProfileGatherer::Start(double aSinceTime)
+{
+ MOZ_RELEASE_ASSERT(NS_IsMainThread());
+
+ if (mGathering) {
+ // If we're already gathering, return a rejected the promise - this isn't
+ // going to end well.
+ return ProfileGatherPromise::CreateAndReject(NS_ERROR_NOT_AVAILABLE, __func__);
+ }
+
+ nsresult rv = EnsureThread();
+ if (NS_FAILED(rv)) {
+ return ProfileGatherPromise::CreateAndReject(rv, __func__);
+ }
+
+ mGathering = true;
+
+ mPromiseHolder.emplace();
+ RefPtr<ProfileGatherPromise> promise = mPromiseHolder->Ensure(__func__);
+
+ mRegisteredProfilesCount = 0;
+
+ // Send a notification to request profiles from other processes. The
+ // observers of this notification will call WillGatherOOPProfile() which
+ // increments mRegisteredProfilesCount.
+ nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
+ if (os) {
+ DebugOnly<nsresult> rv =
+ os->NotifyObservers(this, "profiler-subprocess-gather", nullptr);
+ NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "NotifyObservers failed");
+ }
+
+ mThread->Dispatch(NewRunnableMethod<double, uint32_t>
+ (this, &ProfileGatherer::StartCalledOnProfileGatherThread, aSinceTime,
+ mRegisteredProfilesCount),
+ NS_DISPATCH_NORMAL);
+
+ return promise;
+}
+
+void
+ProfileGatherer::RejectPromise(nsresult aRv)
+{
+ MOZ_RELEASE_ASSERT(NS_IsMainThread());
+ if (mPromiseHolder.isSome()) {
+ mPromiseHolder->RejectIfExists(aRv, __func__);
+ }
+}
+
+void
+ProfileGatherer::StartCalledOnProfileGatherThread(double aSinceTime,
+ uint32_t aProfileCount)
+{
+ MOZ_RELEASE_ASSERT(mThread == NS_GetCurrentThread());
+ mWriter.emplace();
+
+ // Start building up the JSON result and grab the profile from this process.
+ mWriter->Start(SpliceableJSONWriter::SingleLineStyle);
+ if (!profiler_stream_json_for_this_process(*mWriter, aSinceTime)) {
+ // The profiler is inactive. This either means that it was inactive even
+ // at the time that ProfileGatherer::Start() was called, or that it was
+ // stopped on a different thread since that call. Either way, we need to
+ // reject the promise and stop gathering.
+ NS_DispatchToMainThread(NewRunnableMethod<nsresult>
+ (this, &ProfileGatherer::RejectPromise, NS_ERROR_NOT_AVAILABLE));
+ return;
+ }
+
+ mWriter->StartArrayProperty("processes");
+
+ // If we have any process exit profiles, add them immediately, and clear
+ // mExitProfiles.
+ for (size_t i = 0; i < mExitProfiles.Length(); ++i) {
+ if (!mExitProfiles[i].IsEmpty()) {
+ mWriter->Splice(mExitProfiles[i].get());
+ }
+ }
+ mExitProfiles.Clear();
+
+ mRemainingProfilesCount = aProfileCount;
+
+ // Keep the array property "processes" and the root object in mWriter open
+ // until Finish() is called. As profiles from the other processes come in,
+ // they will be inserted and end up in the right spot. Finish() will close
+ // the array and the root object.
+
+ if (aProfileCount == 0) {
+ FinishCalledOnProfileGatherThread();
+ }
+}
+
+void
+ProfileGatherer::WillGatherOOPProfile()
+{
+ MOZ_RELEASE_ASSERT(NS_IsMainThread());
+
+ mRegisteredProfilesCount++;
+}
+
+void
+ProfileGatherer::OOPExitProfile(const nsACString& aProfile)
+{
+ MOZ_RELEASE_ASSERT(NS_IsMainThread());
+
+ nsresult rv = EnsureThread();
+ if (NS_FAILED(rv)) {
+ return;
+ }
+ nsCString profile(aProfile);
+ mThread->Dispatch(NewRunnableMethod<nsCString>
+ (this, &ProfileGatherer::OOPExitProfileCalledOnProfileGatherThread, profile),
+ NS_DISPATCH_NORMAL);
+}
+
+void
+ProfileGatherer::OOPExitProfileCalledOnProfileGatherThread(const nsACString& aProfile)
+{
+ MOZ_RELEASE_ASSERT(mThread == NS_GetCurrentThread());
+
+ // Append the exit profile to mExitProfiles so that it can be picked up the
+ // next time a profile is requested. If we're currently gathering a profile,
+ // do not add this exit profile to it; chances are that we already have a
+ // profile from the exiting process and we don't want another one.
+ // We only keep around at most MAX_SUBPROCESS_EXIT_PROFILES exit profiles.
+ if (mExitProfiles.Length() >= MAX_SUBPROCESS_EXIT_PROFILES) {
+ mExitProfiles.RemoveElementAt(0);
+ }
+ mExitProfiles.AppendElement(aProfile);
}
void
ProfileGatherer::GatheredOOPProfile(const nsACString& aProfile)
{
MOZ_RELEASE_ASSERT(NS_IsMainThread());
if (!mGathering) {
// If we're not actively gathering, then we don't actually
// care that we gathered a profile here. This can happen for
// processes that exit while profiling.
return;
}
+ nsresult rv = EnsureThread();
+ if (NS_FAILED(rv)) {
+ return;
+ }
+ nsCString profile(aProfile);
+ mThread->Dispatch(NewRunnableMethod<nsCString>
+ (this, &ProfileGatherer::GatheredOOPProfileCalledOnProfileGatherThread, profile),
+ NS_DISPATCH_NORMAL);
+}
+
+void
+ProfileGatherer::GatheredOOPProfileCalledOnProfileGatherThread(const nsACString& aProfile)
+{
+ MOZ_RELEASE_ASSERT(mThread == NS_GetCurrentThread());
MOZ_RELEASE_ASSERT(mWriter.isSome(), "Should always have a writer if mGathering is true");
mWriter->Splice(PromiseFlatCString(aProfile).get());
- mPendingProfiles--;
+ mRemainingProfilesCount--;
- if (mPendingProfiles == 0) {
+ if (mRemainingProfilesCount == 0) {
// We've got all of the async profiles now. Let's
// finish off the profile and resolve the Promise.
- Finish();
+ FinishCalledOnProfileGatherThread();
}
}
void
-ProfileGatherer::WillGatherOOPProfile()
-{
- mPendingProfiles++;
-}
-
-RefPtr<ProfileGatherer::ProfileGatherPromise>
-ProfileGatherer::Start(double aSinceTime)
+ProfileGatherer::FinishCalledOnProfileGatherThread()
{
- MOZ_RELEASE_ASSERT(NS_IsMainThread());
-
- if (mGathering) {
- // If we're already gathering, return a rejected the promise - this isn't
- // going to end well.
- return ProfileGatherPromise::CreateAndReject(NS_ERROR_NOT_AVAILABLE, __func__);
- }
-
- mGathering = true;
- mPendingProfiles = 0;
-
- // Send a notification to request profiles from other processes. The
- // observers of this notification will call WillGatherOOPProfile() which
- // increments mPendingProfiles.
- // Do this before the call to profiler_stream_json_for_this_process because
- // that call is slow and we want to let the other processes grab their
- // profiles as soon as possible.
- nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
- if (os) {
- DebugOnly<nsresult> rv =
- os->NotifyObservers(this, "profiler-subprocess-gather", nullptr);
- NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "NotifyObservers failed");
- }
-
- mWriter.emplace();
-
- // Start building up the JSON result and grab the profile from this process.
- mWriter->Start(SpliceableJSONWriter::SingleLineStyle);
- if (!profiler_stream_json_for_this_process(*mWriter, aSinceTime)) {
- // The profiler is inactive. This either means that it was inactive even
- // at the time that ProfileGatherer::Start() was called, or that it was
- // stopped on a different thread since that call. Either way, we need to
- // reject the promise and stop gathering.
- return ProfileGatherPromise::CreateAndReject(NS_ERROR_NOT_AVAILABLE, __func__);
- }
-
- mWriter->StartArrayProperty("processes");
-
- // If we have any process exit profiles, add them immediately, and clear
- // mExitProfiles.
- for (size_t i = 0; i < mExitProfiles.Length(); ++i) {
- if (!mExitProfiles[i].IsEmpty()) {
- mWriter->Splice(mExitProfiles[i].get());
- }
- }
- mExitProfiles.Clear();
-
- mPromiseHolder.emplace();
- RefPtr<ProfileGatherPromise> promise = mPromiseHolder->Ensure(__func__);
-
- // Keep the array property "processes" and the root object in mWriter open
- // until Finish() is called. As profiles from the other processes come in,
- // they will be inserted and end up in the right spot. Finish() will close
- // the array and the root object.
-
- if (!mPendingProfiles) {
- Finish();
- }
-
- return promise;
-}
-
-void
-ProfileGatherer::Finish()
-{
- MOZ_RELEASE_ASSERT(NS_IsMainThread());
+ MOZ_RELEASE_ASSERT(mThread == NS_GetCurrentThread());
MOZ_RELEASE_ASSERT(mWriter.isSome());
- MOZ_RELEASE_ASSERT(mPromiseHolder.isSome());
// Close the "processes" array property.
mWriter->EndArray();
// Close the root object of the generated JSON.
mWriter->End();
UniquePtr<char[]> buf = mWriter->WriteFunc()->CopyData();
nsCString result(buf.get());
- mPromiseHolder->Resolve(result, __func__);
- Reset();
-}
+ mWriter.reset();
-void
-ProfileGatherer::Reset()
-{
- mPromiseHolder.reset();
- mPendingProfiles = 0;
- mGathering = false;
- mWriter.reset();
+ NS_DispatchToMainThread(NewRunnableMethod<nsCString>
+ (this, &ProfileGatherer::Finish, result));
}
void
-ProfileGatherer::Cancel()
+ProfileGatherer::Finish(const nsCString& aResult)
{
- // If we have a Promise in flight, we should reject it.
- if (mPromiseHolder.isSome()) {
- mPromiseHolder->RejectIfExists(NS_ERROR_DOM_ABORT_ERR, __func__);
- }
- Reset();
-}
+ MOZ_RELEASE_ASSERT(NS_IsMainThread());
+ MOZ_RELEASE_ASSERT(mPromiseHolder.isSome());
-void
-ProfileGatherer::OOPExitProfile(const nsACString& aProfile)
-{
- // Append the exit profile to mExitProfiles so that it can be picked up the
- // next time a profile is requested. If we're currently gathering a profile,
- // do not add this exit profile to it; chances are that we already have a
- // profile from the exiting process and we don't want another one.
- // We only keep around at most MAX_SUBPROCESS_EXIT_PROFILES exit profiles.
- if (mExitProfiles.Length() >= MAX_SUBPROCESS_EXIT_PROFILES) {
- mExitProfiles.RemoveElementAt(0);
- }
- mExitProfiles.AppendElement(aProfile);
+ mPromiseHolder->Resolve(aResult, __func__);
+
+ mPromiseHolder.reset();
+ mGathering = false;
}
} // namespace mozilla
--- a/tools/profiler/gecko/ProfileGatherer.h
+++ b/tools/profiler/gecko/ProfileGatherer.h
@@ -4,16 +4,18 @@
#ifndef MOZ_PROFILE_GATHERER_H
#define MOZ_PROFILE_GATHERER_H
#include "nsIFile.h"
#include "ProfileJSONWriter.h"
#include "mozilla/MozPromise.h"
+class nsIThread;
+
namespace mozilla {
// This class holds the state for an async profile-gathering request.
class ProfileGatherer final : public nsISupports
{
public:
NS_DECL_ISUPPORTS
@@ -22,22 +24,35 @@ public:
explicit ProfileGatherer();
void WillGatherOOPProfile();
void GatheredOOPProfile(const nsACString& aProfile);
RefPtr<ProfileGatherPromise> Start(double aSinceTime);
void OOPExitProfile(const nsACString& aProfile);
private:
~ProfileGatherer();
- void Cancel();
- void Finish();
- void Reset();
+ void Finish(const nsCString& aResult);
+ void RejectPromise(nsresult aRv);
+
+ nsresult EnsureThread();
+
+ void StartCalledOnProfileGatherThread(double aSinceTime, uint32_t aProfileCount);
+ void GatheredOOPProfileCalledOnProfileGatherThread(const nsACString& aProfile);
+ void OOPExitProfileCalledOnProfileGatherThread(const nsACString& aProfile);
+ void FinishCalledOnProfileGatherThread();
+ // The profile gather thread
+ RefPtr<nsIThread> mThread;
+
+ // Only accessed on mThread:
nsTArray<nsCString> mExitProfiles;
+ Maybe<SpliceableChunkedJSONWriter> mWriter;
+ uint32_t mRemainingProfilesCount;
+
+ // Only accessed on the main thread:
Maybe<MozPromiseHolder<ProfileGatherPromise>> mPromiseHolder;
- Maybe<SpliceableChunkedJSONWriter> mWriter;
- uint32_t mPendingProfiles;
+ uint32_t mRegisteredProfilesCount;
bool mGathering;
};
} // namespace mozilla
#endif