Bug 1355634 - Use MozPromise to make ProfileGatherer more generic.
MozReview-Commit-ID: GKqxJW8zjca
--- a/tools/profiler/gecko/ProfileGatherer.cpp
+++ b/tools/profiler/gecko/ProfileGatherer.cpp
@@ -3,21 +3,16 @@
/* 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 "ProfileGatherer.h"
#include "mozilla/Services.h"
#include "nsIObserverService.h"
-#include "nsLocalFile.h"
-#include "nsIFileStreams.h"
-
-using mozilla::dom::AutoJSAPI;
-using mozilla::dom::Promise;
namespace mozilla {
/**
* When a subprocess exits before we've gathered profiles, we'll
* store profiles for those processes until gathering starts. We'll
* only store up to MAX_SUBPROCESS_EXIT_PROFILES. The buffer is
* circular, so as soon as we receive another exit profile, we'll
@@ -45,22 +40,16 @@ ProfileGatherer::GatheredOOPProfile(cons
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;
}
- if (NS_WARN_IF(!mPromise && !mFile)) {
- // If we're not holding on to a Promise, then someone is
- // calling us erroneously.
- return;
- }
-
MOZ_RELEASE_ASSERT(mWriter.isSome(), "Should always have a writer if mGathering is true");
mWriter->Splice(PromiseFlatCString(aProfile).get());
mPendingProfiles--;
if (mPendingProfiles == 0) {
// We've got all of the async profiles now. Let's
@@ -70,184 +59,116 @@ ProfileGatherer::GatheredOOPProfile(cons
}
void
ProfileGatherer::WillGatherOOPProfile()
{
mPendingProfiles++;
}
-void
-ProfileGatherer::Start(double aSinceTime, Promise* aPromise)
+RefPtr<ProfileGatherer::ProfileGatherPromise>
+ProfileGatherer::Start(double aSinceTime)
{
MOZ_RELEASE_ASSERT(NS_IsMainThread());
if (mGathering) {
- // If we're already gathering, reject the promise - this isn't going
- // to end well.
- if (aPromise) {
- aPromise->MaybeReject(NS_ERROR_NOT_AVAILABLE);
- }
- return;
+ // If we're already gathering, return a rejected promise - this isn't
+ // going to end well.
+ return ProfileGatherPromise::CreateAndReject(NS_ERROR_NOT_AVAILABLE, __func__);
}
- mPromise = aPromise;
-
- Start2(aSinceTime);
-}
-
-void
-ProfileGatherer::Start(double aSinceTime, const nsACString& aFileName)
-{
- MOZ_RELEASE_ASSERT(NS_IsMainThread());
-
- nsCOMPtr<nsIFile> file = do_CreateInstance(NS_LOCAL_FILE_CONTRACTID);
- nsresult rv = file->InitWithNativePath(aFileName);
- if (NS_FAILED(rv)) {
- MOZ_CRASH();
- }
-
- if (mGathering) {
- return;
- }
-
- mFile = file;
-
- Start2(aSinceTime);
-}
-
-// This is the common tail shared by both Start() methods.
-void
-ProfileGatherer::Start2(double aSinceTime)
-{
- MOZ_RELEASE_ASSERT(NS_IsMainThread());
-
mGathering = true;
mPendingProfiles = 0;
- mWriter.emplace();
// 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.
- Cancel();
- return;
+ 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(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();
-
- if (mFile) {
- nsCOMPtr<nsIFileOutputStream> of =
- do_CreateInstance("@mozilla.org/network/file-output-stream;1");
- of->Init(mFile, -1, -1, 0);
- uint32_t sz;
- of->Write(buf.get(), strlen(buf.get()), &sz);
- of->Close();
- Reset();
- return;
- }
-
- AutoJSAPI jsapi;
- if (NS_WARN_IF(!jsapi.Init(mPromise->GlobalJSObject()))) {
- // We're really hosed if we can't get a JS context for some reason.
- Reset();
- return;
- }
-
- JSContext* cx = jsapi.cx();
-
- // Now parse the JSON so that we resolve with a JS Object.
- JS::RootedValue val(cx);
- {
- NS_ConvertUTF8toUTF16 js_string(nsDependentCString(buf.get()));
- if (!JS_ParseJSON(cx, static_cast<const char16_t*>(js_string.get()),
- js_string.Length(), &val)) {
- if (!jsapi.HasException()) {
- mPromise->MaybeReject(NS_ERROR_DOM_UNKNOWN_ERR);
- } else {
- JS::RootedValue exn(cx);
- DebugOnly<bool> gotException = jsapi.StealException(&exn);
- MOZ_ASSERT(gotException);
-
- jsapi.ClearException();
- mPromise->MaybeReject(cx, exn);
- }
- } else {
- mPromise->MaybeResolve(val);
- }
- }
+ nsCString result(buf.get());
+ mPromiseHolder->Resolve(result, __func__);
Reset();
}
void
ProfileGatherer::Reset()
{
- mPromise = nullptr;
- mFile = nullptr;
+ mPromiseHolder.reset();
mPendingProfiles = 0;
mGathering = false;
mWriter.reset();
}
void
ProfileGatherer::Cancel()
{
// If we have a Promise in flight, we should reject it.
- if (mPromise) {
- mPromise->MaybeReject(NS_ERROR_DOM_ABORT_ERR);
+ if (mPromiseHolder.isSome()) {
+ mPromiseHolder->RejectIfExists(NS_ERROR_DOM_ABORT_ERR, __func__);
}
Reset();
}
void
ProfileGatherer::OOPExitProfile(const nsACString& aProfile)
{
// Append the exit profile to mExitProfiles so that it can be picked up the
--- a/tools/profiler/gecko/ProfileGatherer.h
+++ b/tools/profiler/gecko/ProfileGatherer.h
@@ -1,44 +1,43 @@
/* 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 MOZ_PROFILE_GATHERER_H
#define MOZ_PROFILE_GATHERER_H
-#include "mozilla/dom/Promise.h"
#include "nsIFile.h"
#include "ProfileJSONWriter.h"
+#include "mozilla/MozPromise.h"
namespace mozilla {
// This class holds the state for an async profile-gathering request.
class ProfileGatherer final : public nsISupports
{
public:
NS_DECL_ISUPPORTS
+ typedef MozPromise<nsCString, nsresult, false> ProfileGatherPromise;
+
explicit ProfileGatherer();
void WillGatherOOPProfile();
void GatheredOOPProfile(const nsACString& aProfile);
- void Start(double aSinceTime, mozilla::dom::Promise* aPromise);
- void Start(double aSinceTime, const nsACString& aFileName);
- void Cancel();
+ RefPtr<ProfileGatherPromise> Start(double aSinceTime);
void OOPExitProfile(const nsACString& aProfile);
private:
~ProfileGatherer();
+ void Cancel();
void Finish();
void Reset();
- void Start2(double aSinceTime);
nsTArray<nsCString> mExitProfiles;
- RefPtr<mozilla::dom::Promise> mPromise;
- nsCOMPtr<nsIFile> mFile;
+ Maybe<MozPromiseHolder<ProfileGatherPromise>> mPromiseHolder;
Maybe<SpliceableChunkedJSONWriter> mWriter;
uint32_t mPendingProfiles;
bool mGathering;
};
} // namespace mozilla
#endif
--- a/tools/profiler/gecko/nsProfiler.cpp
+++ b/tools/profiler/gecko/nsProfiler.cpp
@@ -2,34 +2,39 @@
/* 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 <string>
#include <sstream>
#include "GeckoProfiler.h"
+#include "nsIFileStreams.h"
#include "nsProfiler.h"
#include "nsProfilerStartParams.h"
#include "nsMemory.h"
#include "nsString.h"
#include "mozilla/Services.h"
#include "nsIObserverService.h"
#include "nsIInterfaceRequestor.h"
#include "nsILoadContext.h"
#include "nsIWebNavigation.h"
#include "nsIInterfaceRequestorUtils.h"
#include "shared-libraries.h"
#include "js/Value.h"
#include "mozilla/ErrorResult.h"
#include "mozilla/dom/Promise.h"
#include "ProfileGatherer.h"
+#include "nsLocalFile.h"
+#include "platform.h"
-using mozilla::ErrorResult;
-using mozilla::dom::Promise;
+using namespace mozilla;
+
+using dom::AutoJSAPI;
+using dom::Promise;
using std::string;
NS_IMPL_ISUPPORTS(nsProfiler, nsIProfiler)
nsProfiler::nsProfiler()
: mLockedForPrivateBrowsing(false)
{
}
@@ -236,33 +241,85 @@ nsProfiler::GetProfileDataAsync(double a
}
ErrorResult result;
RefPtr<Promise> promise = Promise::Create(go, result);
if (NS_WARN_IF(result.Failed())) {
return result.StealNSResult();
}
- mGatherer->Start(aSinceTime, promise);
+ mGatherer->Start(aSinceTime)->Then(
+ AbstractThread::MainThread(), __func__,
+ [promise](nsCString aResult) {
+ AutoJSAPI jsapi;
+ if (NS_WARN_IF(!jsapi.Init(promise->GlobalJSObject()))) {
+ // We're really hosed if we can't get a JS context for some reason.
+ promise->MaybeReject(NS_ERROR_DOM_UNKNOWN_ERR);
+ return;
+ }
+
+ JSContext* cx = jsapi.cx();
+
+ // Now parse the JSON so that we resolve with a JS Object.
+ JS::RootedValue val(cx);
+ {
+ NS_ConvertUTF8toUTF16 js_string(aResult);
+ if (!JS_ParseJSON(cx, static_cast<const char16_t*>(js_string.get()),
+ js_string.Length(), &val)) {
+ if (!jsapi.HasException()) {
+ promise->MaybeReject(NS_ERROR_DOM_UNKNOWN_ERR);
+ } else {
+ JS::RootedValue exn(cx);
+ DebugOnly<bool> gotException = jsapi.StealException(&exn);
+ MOZ_ASSERT(gotException);
+
+ jsapi.ClearException();
+ promise->MaybeReject(cx, exn);
+ }
+ } else {
+ promise->MaybeResolve(val);
+ }
+ }
+ },
+ [promise](nsresult aRv) {
+ promise->MaybeReject(aRv);
+ });
promise.forget(aPromise);
return NS_OK;
}
NS_IMETHODIMP
nsProfiler::DumpProfileToFileAsync(const nsACString& aFilename,
double aSinceTime)
{
MOZ_ASSERT(NS_IsMainThread());
if (!mGatherer) {
return NS_ERROR_FAILURE;
}
- mGatherer->Start(aSinceTime, aFilename);
+ nsCString filename(aFilename);
+
+ mGatherer->Start(aSinceTime)->Then(
+ AbstractThread::MainThread(), __func__,
+ [filename](const nsCString& aResult) {
+ nsCOMPtr<nsIFile> file = do_CreateInstance(NS_LOCAL_FILE_CONTRACTID);
+ nsresult rv = file->InitWithNativePath(filename);
+ if (NS_FAILED(rv)) {
+ MOZ_CRASH();
+ }
+ nsCOMPtr<nsIFileOutputStream> of =
+ do_CreateInstance("@mozilla.org/network/file-output-stream;1");
+ of->Init(file, -1, -1, 0);
+ uint32_t sz;
+ of->Write(aResult.get(), aResult.Length(), &sz);
+ of->Close();
+ },
+ [](nsresult aRv) { });
return NS_OK;
}
NS_IMETHODIMP
nsProfiler::GetElapsedTime(double* aElapsedTime)
{
--- a/tools/profiler/gecko/nsProfiler.h
+++ b/tools/profiler/gecko/nsProfiler.h
@@ -4,16 +4,17 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef _NSPROFILER_H_
#define _NSPROFILER_H_
#include "nsIProfiler.h"
#include "nsIObserver.h"
#include "mozilla/Attributes.h"
+#include "nsServiceManagerUtils.h"
namespace mozilla {
class ProfileGatherer;
}
class nsProfiler final : public nsIProfiler, public nsIObserver
{
public:
@@ -34,14 +35,14 @@ public:
void WillGatherOOPProfile();
void GatheredOOPProfile(const nsACString& aProfile);
void OOPExitProfile(const nsACString& aProfile);
private:
~nsProfiler();
- RefPtr<ProfileGatherer> mGatherer;
+ RefPtr<mozilla::ProfileGatherer> mGatherer;
bool mLockedForPrivateBrowsing;
};
#endif /* _NSPROFILER_H_ */
--- a/tools/profiler/moz.build
+++ b/tools/profiler/moz.build
@@ -23,29 +23,33 @@ if CONFIG['MOZ_GECKO_PROFILER']:
'core/ProfileBuffer.cpp',
'core/ProfileBufferEntry.cpp',
'core/ProfileJSONWriter.cpp',
'core/ProfilerBacktrace.cpp',
'core/ProfilerMarkers.cpp',
'core/StackTop.cpp',
'core/ThreadInfo.cpp',
'gecko/CrossProcessProfilerController.cpp',
- 'gecko/nsProfiler.cpp',
'gecko/nsProfilerFactory.cpp',
'gecko/nsProfilerStartParams.cpp',
+ 'gecko/ProfileGatherer.cpp',
'gecko/ProfilerIOInterposeObserver.cpp',
'gecko/ThreadResponsiveness.cpp',
]
if CONFIG['OS_TARGET'] == 'Darwin':
+ # This file cannot be built in unified mode because it includes
+ # "nsLocalFile.h", which pulls in a system header which uses a type
+ # called TextRange, which conflicts with mozilla::TextRange due to
+ # a "using namespace mozilla;" declaration from a different file.
SOURCES += [
- 'gecko/ProfileGatherer.cpp',
+ 'gecko/nsProfiler.cpp',
]
else:
UNIFIED_SOURCES += [
- 'gecko/ProfileGatherer.cpp',
+ 'gecko/nsProfiler.cpp',
]
if CONFIG['OS_TARGET'] in ('Android', 'Linux'):
UNIFIED_SOURCES += [
'lul/AutoObjectMapper.cpp',
'lul/LulCommon.cpp',
'lul/LulDwarf.cpp',
'lul/LulDwarfSummariser.cpp',