diff options
Diffstat (limited to 'tools/profiler/gecko/ProfileGatherer.cpp')
-rw-r--r-- | tools/profiler/gecko/ProfileGatherer.cpp | 207 |
1 files changed, 207 insertions, 0 deletions
diff --git a/tools/profiler/gecko/ProfileGatherer.cpp b/tools/profiler/gecko/ProfileGatherer.cpp new file mode 100644 index 000000000..5cd45bee3 --- /dev/null +++ b/tools/profiler/gecko/ProfileGatherer.cpp @@ -0,0 +1,207 @@ +/* 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 "mozilla/ProfileGatherer.h" +#include "mozilla/Services.h" +#include "nsIObserverService.h" +#include "GeckoSampler.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 + * bump the oldest one out of the buffer. + */ +static const uint32_t MAX_SUBPROCESS_EXIT_PROFILES = 5; + +NS_IMPL_ISUPPORTS(ProfileGatherer, nsIObserver) + +ProfileGatherer::ProfileGatherer(GeckoSampler* aTicker) + : mTicker(aTicker) + , mSinceTime(0) + , mPendingProfiles(0) + , mGathering(false) +{ +} + +void +ProfileGatherer::GatheredOOPProfile() +{ + MOZ_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; + } + + if (NS_WARN_IF(!mPromise)) { + // If we're not holding on to a Promise, then someone is + // calling us erroneously. + return; + } + + mPendingProfiles--; + + if (mPendingProfiles == 0) { + // We've got all of the async profiles now. Let's + // finish off the profile and resolve the Promise. + Finish(); + } +} + +void +ProfileGatherer::WillGatherOOPProfile() +{ + mPendingProfiles++; +} + +void +ProfileGatherer::Start(double aSinceTime, + Promise* aPromise) +{ + MOZ_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; + } + + mSinceTime = aSinceTime; + mPromise = aPromise; + mGathering = true; + mPendingProfiles = 0; + + nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService(); + if (os) { + DebugOnly<nsresult> rv = + os->AddObserver(this, "profiler-subprocess", false); + NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "AddObserver failed"); + rv = os->NotifyObservers(this, "profiler-subprocess-gather", nullptr); + NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "NotifyObservers failed"); + } + + if (!mPendingProfiles) { + Finish(); + } +} + +void +ProfileGatherer::Finish() +{ + MOZ_ASSERT(NS_IsMainThread()); + + if (!mTicker) { + // We somehow got called after we were cancelled! This shouldn't + // be possible, but doing a belt-and-suspenders check to be sure. + return; + } + + UniquePtr<char[]> buf = mTicker->ToJSON(mSinceTime); + + nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService(); + if (os) { + DebugOnly<nsresult> rv = os->RemoveObserver(this, "profiler-subprocess"); + NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "RemoveObserver failed"); + } + + 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); + } + } + + Reset(); +} + +void +ProfileGatherer::Reset() +{ + mSinceTime = 0; + mPromise = nullptr; + mPendingProfiles = 0; + mGathering = false; +} + +void +ProfileGatherer::Cancel() +{ + // The GeckoSampler is going away. If we have a Promise in flight, we + // should reject it. + if (mPromise) { + mPromise->MaybeReject(NS_ERROR_DOM_ABORT_ERR); + } + + // Clear out the GeckoSampler reference, since it's being destroyed. + mTicker = nullptr; +} + +void +ProfileGatherer::OOPExitProfile(const nsCString& aProfile) +{ + if (mExitProfiles.Length() >= MAX_SUBPROCESS_EXIT_PROFILES) { + mExitProfiles.RemoveElementAt(0); + } + mExitProfiles.AppendElement(aProfile); + + // If a process exited while gathering, we need to make + // sure we decrement the counter. + if (mGathering) { + GatheredOOPProfile(); + } +} + +NS_IMETHODIMP +ProfileGatherer::Observe(nsISupports* aSubject, + const char* aTopic, + const char16_t *someData) +{ + if (!strcmp(aTopic, "profiler-subprocess")) { + nsCOMPtr<nsIProfileSaveEvent> pse = do_QueryInterface(aSubject); + if (pse) { + for (size_t i = 0; i < mExitProfiles.Length(); ++i) { + if (!mExitProfiles[i].IsEmpty()) { + pse->AddSubProfile(mExitProfiles[i].get()); + } + } + mExitProfiles.Clear(); + } + } + return NS_OK; +} + +} // namespace mozilla |