/* 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 os = mozilla::services::GetObserverService(); if (os) { nsresult rv = os->AddObserver(this, "profiler-subprocess", false); NS_WARN_IF(NS_FAILED(rv)); rv = os->NotifyObservers(this, "profiler-subprocess-gather", nullptr); NS_WARN_IF(NS_FAILED(rv)); } 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 buf = mTicker->ToJSON(mSinceTime); nsCOMPtr os = mozilla::services::GetObserverService(); if (os) { nsresult rv = os->RemoveObserver(this, "profiler-subprocess"); NS_WARN_IF(NS_FAILED(rv)); } 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(js_string.get()), js_string.Length(), &val)) { if (!jsapi.HasException()) { mPromise->MaybeReject(NS_ERROR_DOM_UNKNOWN_ERR); } else { JS::RootedValue exn(cx); DebugOnly 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 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