Bug 1527737 - Add tests for main thread io in the profiler; r=julienw
authorGreg Tatum <gtatum@mozilla.com>
Thu, 07 Mar 2019 23:04:49 +0000
changeset 521036 cdd6695cfad89791a7f12e3e571d22c1e61389d4
parent 521035 affd43bd16376575d3f06af71b5c876b46279131
child 521037 551248df6193f5630f93f56a9e2d147c9cfc5744
push id10862
push userffxbld-merge
push dateMon, 11 Mar 2019 13:01:11 +0000
treeherdermozilla-beta@a2e7f5c935da [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjulienw
bugs1527737
milestone67.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 1527737 - Add tests for main thread io in the profiler; r=julienw Differential Revision: https://phabricator.services.mozilla.com/D20416
tools/profiler/tests/head_profiler.js
tools/profiler/tests/test_feature_mainthreadio.js
tools/profiler/tests/xpcshell.ini
--- a/tools/profiler/tests/head_profiler.js
+++ b/tools/profiler/tests/head_profiler.js
@@ -1,14 +1,56 @@
 /* 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/. */
 
 var {Services} = ChromeUtils.import("resource://gre/modules/Services.jsm");
 var {AppConstants} = ChromeUtils.import("resource://gre/modules/AppConstants.jsm");
+var {setTimeout} = ChromeUtils.import("resource://gre/modules/Timer.jsm");
+
+/**
+ * Get the payloads of a type recursively, including from all subprocesses.
+ *
+ * @param {Object} profile The gecko profile.
+ * @param {string} type The marker payload type, e.g. "DiskIO".
+ * @param {Array} payloadTarget The recursive list of payloads.
+ * @return {Array} The final payloads.
+ */
+function getAllPayloadsOfType(profile, type, payloadTarget = []) {
+  for (const {markers} of profile.threads) {
+    for (const markerTuple of markers.data) {
+      const payload = markerTuple[markers.schema.data];
+      if (payload && payload.type === type) {
+        payloadTarget.push(payload);
+      }
+    }
+  }
+
+  for (const subProcess of profile.processes) {
+    getAllPayloadsOfType(subProcess, type, payloadTarget);
+  }
+
+  return payloadTarget;
+}
+
+
+/**
+ * This is a helper function be able to run `await wait(500)`. Unfortunately this
+ * is needed as the act of collecting functions relies on the periodic sampling of
+ * the threads. See: https://bugzilla.mozilla.org/show_bug.cgi?id=1529053
+ *
+ * @param {number} time
+ * @returns {Promise}
+ */
+function wait(time) {
+  return new Promise(resolve => {
+    // eslint-disable-next-line mozilla/no-arbitrary-setTimeout
+    setTimeout(resolve, time);
+  });
+}
 
 function getInflatedStackLocations(thread, sample) {
   let stackTable = thread.stackTable;
   let frameTable = thread.frameTable;
   let stringTable = thread.stringTable;
   let SAMPLE_STACK_SLOT = thread.samples.schema.stack;
   let STACK_PREFIX_SLOT = stackTable.schema.prefix;
   let STACK_FRAME_SLOT = stackTable.schema.frame;
new file mode 100644
--- /dev/null
+++ b/tools/profiler/tests/test_feature_mainthreadio.js
@@ -0,0 +1,109 @@
+/* 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/. */
+
+const {FileUtils} = ChromeUtils.import("resource://gre/modules/FileUtils.jsm");
+
+/**
+ * Test that the IOInterposer is working correctly to capture main thread IO.
+ */
+add_task(async () => {
+  if (!AppConstants.MOZ_GECKO_PROFILER) {
+    return;
+  }
+
+  // Let the test harness settle, in order to avoid extraneous FileIO operations. This
+  // helps avoid false positives that we are actually triggering FileIO.
+  await wait(10);
+
+  {
+    const filename = "profiler-mainthreadio-test-firstrun";
+    const payloads = await startProfilerAndgetFileIOPayloads(["mainthreadio"], filename);
+
+    greater(payloads.length, 0,
+       "FileIO markers were found when using the mainthreadio feature on the profiler.");
+
+    // It would be better to check on the filename, but Linux does not currently include
+    // it. See https://bugzilla.mozilla.org/show_bug.cgi?id=1533531
+    // ok(hasWritePayload(payloads, filename),
+    //    "A FileIO marker is found when using the mainthreadio feature on the profiler.");
+  }
+
+  {
+    const filename = "profiler-mainthreadio-test-no-instrumentation";
+    const payloads = await startProfilerAndgetFileIOPayloads([], filename);
+
+    equal(payloads.length, 0,
+          "No FileIO markers are found when the mainthreadio feature is not turned on " +
+          "in the profiler.");
+  }
+
+  {
+    const filename = "profiler-mainthreadio-test-secondrun";
+    const payloads = await startProfilerAndgetFileIOPayloads(["mainthreadio"], filename);
+
+    greater(payloads.length, 0,
+       "FileIO markers were found when re-starting the mainthreadio feature on the " +
+       "profiler.");
+    // It would be better to check on the filename, but Linux does not currently include
+    // it. See https://bugzilla.mozilla.org/show_bug.cgi?id=1533531
+    // ok(hasWritePayload(payloads, filename),
+    //    "Re-enabling the mainthreadio re-installs the interposer, and we can capture " +
+    //    "another FileIO payload.");
+  }
+});
+
+/**
+ * Start the profiler and get FileIO payloads.
+ * @param {Array} features The list of profiler features
+ * @param {string} filename A filename to trigger a write operation
+ */
+async function startProfilerAndgetFileIOPayloads(features, filename) {
+  const entries = 10000;
+  const interval = 10;
+  const threads = [];
+  Services.profiler.StartProfiler(entries, interval, features, features.length,
+                                  threads, threads.length);
+
+
+  const file = FileUtils.getFile("TmpD", [filename]);
+  if (file.exists()) {
+    console.warn(
+      "This test is triggering FileIO by writing to a file. However, the test found an " +
+      "existing file at the location it was trying to write to. This could happen " +
+      "because a previous run of the test failed to clean up after itself. This test " +
+      " will now clean up that file before running the test again."
+    );
+    file.remove(false);
+  }
+
+  const outputStream = FileUtils.openSafeFileOutputStream(file);
+
+  const data = "Test data.";
+  outputStream.write(data, data.length);
+  FileUtils.closeSafeFileOutputStream(outputStream);
+
+  file.remove(false);
+
+  // Wait for the profiler to collect a sample.
+  // See https://bugzilla.mozilla.org/show_bug.cgi?id=1529053
+  await wait(500);
+
+  // Pause the profiler as we don't need to collect more samples as we retrieve
+  // and serialize the profile.
+  Services.profiler.PauseSampling();
+
+  const profile = await Services.profiler.getProfileDataAsync();
+  Services.profiler.StopProfiler();
+  return getAllPayloadsOfType(profile, "FileIO");
+}
+
+/**
+ * See if a list of payloads has a write operation from a file.
+ *
+ * @param {Array<Object>} payloads The payloads captured from the profiler.
+ * @param {string} filename The filename used to test a write operation.
+ */
+function hasWritePayload(payloads, filename) {
+  return payloads.some(payload => payload.filename.endsWith(filename));
+}
--- a/tools/profiler/tests/xpcshell.ini
+++ b/tools/profiler/tests/xpcshell.ini
@@ -10,8 +10,9 @@ skip-if = true
 skip-if = true
 [test_pause.js]
 [test_enterjit_osr.js]
 [test_enterjit_osr_disabling.js]
 skip-if = !debug
 [test_enterjit_osr_enabling.js]
 skip-if = !debug
 [test_asm.js]
+[test_feature_mainthreadio.js]