summaryrefslogtreecommitdiffstats
path: root/tools/profiler/gecko/ProfileGatherer.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'tools/profiler/gecko/ProfileGatherer.cpp')
-rw-r--r--tools/profiler/gecko/ProfileGatherer.cpp207
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