Bug 1587503 - Provide a mechanism to inspect the current profiler configuration; r=gerald
authorGreg Tatum <gtatum@mozilla.com>
Fri, 11 Oct 2019 18:22:42 +0000
changeset 497306 d5a6d3421d546b79418ddd279e8dc24b739e2cff
parent 497305 71e49603ed69ca06aa1ff437b0e534153480f5cf
child 497307 8fac21359ba7cd7f6014bfe149bb5b0d43c7cc2f
push id36682
push userncsoregi@mozilla.com
push dateSat, 12 Oct 2019 09:52:03 +0000
treeherdermozilla-central@06ea2371f897 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersgerald
bugs1587503
milestone71.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1587503 - Provide a mechanism to inspect the current profiler configuration; r=gerald This patch creates a new API to the nsIProfiler interface, through the activeConfiguration property. It exposes the internal configuration of the profiler. In addition, this information is serialized into the profile meta object. Differential Revision: https://phabricator.services.mozilla.com/D48733
tools/profiler/core/platform.cpp
tools/profiler/core/platform.h
tools/profiler/gecko/nsIProfiler.idl
tools/profiler/gecko/nsProfiler.cpp
tools/profiler/tests/xpcshell/test_active_configuration.js
tools/profiler/tests/xpcshell/xpcshell.ini
--- a/tools/profiler/core/platform.cpp
+++ b/tools/profiler/core/platform.cpp
@@ -740,16 +740,65 @@ class ActivePS {
   }
 
   static bool ShouldProfileThread(PSLockRef aLock, ThreadInfo* aInfo) {
     MOZ_ASSERT(sInstance);
     return ((aInfo->IsMainThread() || FeatureThreads(aLock)) &&
             sInstance->ThreadSelected(aInfo->Name()));
   }
 
+  // Writes out the current active configuration of the profile.
+  static void WriteActiveConfiguration(PSLockRef aLock, JSONWriter& aWriter,
+                                       const char* aPropertyName = nullptr) {
+    if (!sInstance) {
+      if (aPropertyName) {
+        aWriter.NullProperty(aPropertyName);
+      } else {
+        aWriter.NullElement();
+      }
+      return;
+    };
+
+    if (aPropertyName) {
+      aWriter.StartObjectProperty(aPropertyName);
+    } else {
+      aWriter.StartObjectElement();
+    }
+
+    {
+      aWriter.StartArrayProperty("features", aWriter.SingleLineStyle);
+#define WRITE_ACTIVE_FEATURES(n_, str_, Name_, desc_)    \
+  if (profiler_feature_active(ProfilerFeature::Name_)) { \
+    aWriter.StringElement(str_);                         \
+  }
+
+      PROFILER_FOR_EACH_FEATURE(WRITE_ACTIVE_FEATURES)
+#undef WRITE_ACTIVE_FEATURES
+      aWriter.EndArray();
+    }
+    {
+      aWriter.StartArrayProperty("threads", aWriter.SingleLineStyle);
+      for (const auto& filter : sInstance->mFilters) {
+        aWriter.StringElement(filter.c_str());
+      }
+      aWriter.EndArray();
+    }
+    {
+      // Now write all the simple values.
+
+      // The interval is also available on profile.meta.interval
+      aWriter.DoubleProperty("interval", sInstance->mInterval);
+      aWriter.IntProperty("capacity", sInstance->mCapacity.Value());
+      if (sInstance->mDuration) {
+        aWriter.DoubleProperty("duration", sInstance->mDuration.value());
+      }
+    }
+    aWriter.EndObject();
+  }
+
   PS_GET(uint32_t, Generation)
 
   PS_GET(PowerOfTwo32, Capacity)
 
   PS_GET(Maybe<double>, Duration)
 
   PS_GET(double, Interval)
 
@@ -2031,16 +2080,18 @@ static void StreamMetaJSCustomObject(PSL
   } else {
     aWriter.NullProperty("shutdownTime");
   }
 
   aWriter.StartArrayProperty("categories");
   StreamCategories(aWriter);
   aWriter.EndArray();
 
+  ActivePS::WriteActiveConfiguration(aLock, aWriter, "configuration");
+
   if (!NS_IsMainThread()) {
     // Leave the rest of the properties out if we're not on the main thread.
     // At the moment, the only case in which this function is called on a
     // background thread is if we're in a content process and are going to
     // send this profile to the parent process. In that case, the parent
     // process profile's "meta" object already has the rest of the properties,
     // and the parent process profile is dumped on that process's main thread.
     return;
@@ -3965,16 +4016,22 @@ bool profiler_feature_active(uint32_t aF
   // This function runs both on and off the main thread.
 
   MOZ_RELEASE_ASSERT(CorePS::Exists());
 
   // This function is hot enough that we use RacyFeatures, not ActivePS.
   return RacyFeatures::IsActiveWithFeature(aFeature);
 }
 
+void profiler_write_active_configuration(JSONWriter& aWriter) {
+  MOZ_RELEASE_ASSERT(CorePS::Exists());
+  PSAutoLock lock(gPSMutex);
+  ActivePS::WriteActiveConfiguration(lock, aWriter);
+}
+
 void profiler_add_sampled_counter(BaseProfilerCount* aCounter) {
   DEBUG_LOG("profiler_add_sampled_counter(%s)", aCounter->mLabel);
   PSAutoLock lock(gPSMutex);
   CorePS::AppendCounter(lock, aCounter);
 }
 
 void profiler_remove_sampled_counter(BaseProfilerCount* aCounter) {
   DEBUG_LOG("profiler_remove_sampled_counter(%s)", aCounter->mLabel);
--- a/tools/profiler/core/platform.h
+++ b/tools/profiler/core/platform.h
@@ -98,16 +98,19 @@ enum class JSInstrumentationFlags {
   TrackOptimizations = 0x2,
   TraceLogging = 0x4,
   Allocations = 0x8
 };
 
 // Record an exit profile from a child process.
 void profiler_received_exit_profile(const nsCString& aExitProfile);
 
+// Write out the information of the active profiling configuration.
+void profiler_write_active_configuration(mozilla::JSONWriter& aWriter);
+
 // Extract all received exit profiles that have not yet expired (i.e., they
 // still intersect with this process' buffer range).
 mozilla::Vector<nsCString> profiler_move_exit_profiles();
 
 // If the "MOZ_PROFILER_SYMBOLICATE" env-var is set, we return a new
 // ProfilerCodeAddressService object to use for local symbolication of profiles.
 // This is off by default, and mainly intended for local development.
 mozilla::UniquePtr<ProfilerCodeAddressService>
--- a/tools/profiler/gecko/nsIProfiler.idl
+++ b/tools/profiler/gecko/nsIProfiler.idl
@@ -81,16 +81,25 @@ interface nsIProfiler : nsISupports
 
   /**
    * Returns an array of the features that are supported in this build.
    * Features may vary depending on platform and build flags.
    */
   Array<AUTF8String> GetFeatures();
 
   /**
+   * Returns a JavaScript object that contains a description of the currently configured
+   * state of the profiler when the profiler is active. This can be useful to assert
+   * the UI of the profiler's recording panel in tests. It returns null when the profiler
+   * is not active.
+   */
+  [implicit_jscontext]
+  readonly attribute jsval activeConfiguration;
+
+  /**
    * Returns an array of all features that are supported by the profiler.
    * The array may contain features that are not supported in this build.
    */
   Array<AUTF8String> GetAllFeatures();
 
   void GetBufferInfo(out uint32_t aCurrentPosition, out uint32_t aTotalSize,
                      out uint32_t aGeneration);
 
--- a/tools/profiler/gecko/nsProfiler.cpp
+++ b/tools/profiler/gecko/nsProfiler.cpp
@@ -219,16 +219,40 @@ nsProfiler::GetSharedLibraries(JSContext
   if (!obj) {
     return NS_ERROR_FAILURE;
   }
   aResult.setObject(*obj);
   return NS_OK;
 }
 
 NS_IMETHODIMP
+nsProfiler::GetActiveConfiguration(JSContext* aCx,
+                                   JS::MutableHandle<JS::Value> aResult) {
+  JS::RootedValue jsValue(aCx);
+  {
+    nsString buffer;
+    JSONWriter writer(MakeUnique<StringWriteFunc>(buffer));
+    profiler_write_active_configuration(writer);
+    MOZ_ALWAYS_TRUE(JS_ParseJSON(aCx,
+                                 static_cast<const char16_t*>(buffer.get()),
+                                 buffer.Length(), &jsValue));
+  }
+  if (jsValue.isNull()) {
+    aResult.setNull();
+  } else {
+    JS::RootedObject obj(aCx, &jsValue.toObject());
+    if (!obj) {
+      return NS_ERROR_FAILURE;
+    }
+    aResult.setObject(*obj);
+  }
+  return NS_OK;
+}
+
+NS_IMETHODIMP
 nsProfiler::DumpProfileToFile(const char* aFilename) {
   profiler_save_profile_to_file(aFilename);
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsProfiler::GetProfileData(double aSinceTime, JSContext* aCx,
                            JS::MutableHandle<JS::Value> aResult) {
new file mode 100644
--- /dev/null
+++ b/tools/profiler/tests/xpcshell/test_active_configuration.js
@@ -0,0 +1,107 @@
+/* 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/. */
+
+function run_test() {
+  if (!AppConstants.MOZ_GECKO_PROFILER) {
+    return;
+  }
+  info(
+    "Checking that the profiler can fetch the information about the active " +
+      "configuration that is being used to power the profiler."
+  );
+
+  equal(
+    Services.profiler.activeConfiguration,
+    null,
+    "When the profile is off, there is no active configuration."
+  );
+
+  {
+    info("Start the profiler.");
+    const entries = 10000;
+    const interval = 1;
+    const threads = ["GeckoMain"];
+    const features = ["js", "leaf", "threads"];
+    Services.profiler.StartProfiler(entries, interval, features, threads);
+
+    info("Generate the activeConfiguration.");
+    const { activeConfiguration } = Services.profiler;
+    const expectedConfiguration = {
+      interval,
+      threads,
+      features,
+      // The buffer is created as a power of two that can fit all of the entires
+      // into it. If the ratio of entries to buffer size ever changes, this setting
+      // will need to be updated.
+      capacity: Math.pow(2, 14),
+    };
+
+    deepEqual(
+      activeConfiguration,
+      expectedConfiguration,
+      "The active configuration matches configuration given."
+    );
+
+    info("Get the profile.");
+    const profile = Services.profiler.getProfileData();
+    deepEqual(
+      profile.meta.configuration,
+      expectedConfiguration,
+      "The configuration also matches on the profile meta object."
+    );
+  }
+
+  {
+    const entries = 20000;
+    const interval = 0.5;
+    const threads = ["GeckoMain", "DOM Worker"];
+    const features = ["threads"];
+    const duration = 20;
+
+    info("Restart the profiler with a new configuration.");
+    Services.profiler.StartProfiler(
+      entries,
+      interval,
+      features,
+      threads,
+      // Also start it with duration, this property is optional.
+      duration
+    );
+
+    info("Generate the activeConfiguration.");
+    const { activeConfiguration } = Services.profiler;
+    const expectedConfiguration = {
+      interval,
+      threads,
+      features,
+      duration,
+      // The buffer is created as a power of two that can fit all of the entires
+      // into it. If the ratio of entries to buffer size ever changes, this setting
+      // will need to be updated.
+      capacity: Math.pow(2, 15),
+    };
+
+    deepEqual(
+      activeConfiguration,
+      expectedConfiguration,
+      "The active configuration matches the new configuration."
+    );
+
+    info("Get the profile.");
+    const profile = Services.profiler.getProfileData();
+    deepEqual(
+      profile.meta.configuration,
+      expectedConfiguration,
+      "The configuration also matches on the profile meta object."
+    );
+  }
+
+  Services.profiler.StopProfiler();
+
+  equal(
+    Services.profiler.activeConfiguration,
+    null,
+    "When the profile is off, there is no active configuration."
+  );
+}
--- a/tools/profiler/tests/xpcshell/xpcshell.ini
+++ b/tools/profiler/tests/xpcshell/xpcshell.ini
@@ -1,12 +1,13 @@
 [DEFAULT]
 head = head_profiler.js
 skip-if = toolkit == 'android'
 
+[test_active_configuration.js]
 [test_start.js]
 skip-if = true
 [test_get_features.js]
 [test_shared_library.js]
 [test_run.js]
 skip-if = true
 [test_pause.js]
 [test_enterjit_osr.js]