Bug 1439014 - Add a test that checks for the presence of JS tracked optimization info. r?julienw draft
authorMarkus Stange <mstange@themasta.com>
Mon, 26 Mar 2018 19:34:50 -0400
changeset 772805 ed4c097abe220da26b011f7a5b488cb5342e6c60
parent 772476 3d2252d42506c288d5f0703321ac421f7ffb00d7
child 772867 3abe19cead8127a3ef11e04ddbb49cafbf6ab7bb
push id104051
push userbmo:mstange@themasta.com
push dateMon, 26 Mar 2018 23:35:29 +0000
reviewersjulienw
bugs1439014
milestone61.0a1
Bug 1439014 - Add a test that checks for the presence of JS tracked optimization info. r?julienw MozReview-Commit-ID: ETJGZPhMfLv
tools/profiler/tests/chrome/chrome.ini
tools/profiler/tests/chrome/profiler_test_utils.js
tools/profiler/tests/chrome/test_profile_with_trackopts.html
--- a/tools/profiler/tests/chrome/chrome.ini
+++ b/tools/profiler/tests/chrome/chrome.ini
@@ -1,5 +1,6 @@
 [DEFAULT]
 support-files=profiler_test_utils.js
 
+[test_profile_with_trackopts.html]
 [test_profile_worker_bug_1428076.html]
 [test_profile_worker.html]
--- a/tools/profiler/tests/chrome/profiler_test_utils.js
+++ b/tools/profiler/tests/chrome/profiler_test_utils.js
@@ -37,22 +37,48 @@ function end(error) {
   if (error) {
     ok(false, `We got an error: ${error}`);
   } else {
     ok(true, "We ran the whole process");
   }
   SimpleTest.finish();
 }
 
-async function runTest(settings, workload) {
+function getBufferInfo() {
+  let position = {}, totalSize = {}, generation = {};
+  Services.profiler.GetBufferInfo(position, totalSize, generation);
+  return {
+    position: position.value,
+    totalSize: totalSize.value,
+    generation: generation.value
+  };
+}
+
+async function runTest(settings, workload,
+                       checkProfileCallback = function(profile) {}) {
   SimpleTest.waitForExplicitFinish();
   try {
     await startProfiler(settings);
-    await workload();
-    await getProfile();
+
+    // Run workload() one or more times until at least one sample has been taken.
+    const bufferInfoAtStart = getBufferInfo();
+    while (true) {
+      await workload();
+      const bufferInfoAfterWorkload = getBufferInfo();
+      if (bufferInfoAfterWorkload.generation > bufferInfoAtStart.generation ||
+          bufferInfoAfterWorkload.position > bufferInfoAtStart.position) {
+        // The buffer position advanced, so we've either added a marker or a
+        // sample. It would be better to have conclusive evidence that we
+        // actually have a sample...
+        break;
+      }
+    }
+
+    const profile = await getProfile();
+    await checkProfileCallback(profile);
     await stopProfiler();
     await end();
   } catch (e) {
     // By catching and handling the error, we're being nice to mochitest
     // runners: instead of waiting for the timeout, we fail right away.
     await end(e);
   }
 }
new file mode 100644
--- /dev/null
+++ b/tools/profiler/tests/chrome/test_profile_with_trackopts.html
@@ -0,0 +1,220 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1439014
+-->
+<head>
+  <meta charset="utf-8">
+  <title>Test for Bug 1439014</title>
+  <link rel="stylesheet" type="text/css" href="chrome://global/skin"/>
+  <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1439014">Mozilla Bug 1439014</a>
+
+<script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+<script type="application/javascript" src="profiler_test_utils.js"></script>
+<script type="application/javascript">
+/* globals runTest */
+
+"use strict";
+
+const settings = {
+  entries: 1000000, // 9MB
+  interval: 1, // ms
+  features: ["js", "threads", "trackopts"],
+  threads: ["GeckoMain"]
+};
+
+function innerFunction(x) {
+  return x * 0.7; // This is line 30.
+}
+
+function middleFunction(x) {
+  return innerFunction(x) * 1.4;
+}
+
+function outerFunction() {
+  let k = 0;
+  for (let i = 0; i < 1000000; i++) {
+    k += middleFunction(i); // This is line 40.
+  }
+  return k;
+}
+
+function workload() {
+  let m = 0;
+  for (let i = 0; i < 20; i++) {
+    m += outerFunction();
+  }
+  return m;
+}
+
+runTest(settings, workload, profile => {
+  const thisThread = profile.threads[0];
+  const { frameTable, stringTable } = thisThread;
+
+  function prettifyOptimizationSites(optimizationSites) {
+    return optimizationSites.map(optimizationSite => {
+      const result = {};
+      if (optimizationSite.site) {
+        result.site = stringTable[optimizationSite.site];
+      }
+      if (optimizationSite.mirType) {
+        result.mirType = stringTable[optimizationSite.mirType];
+      }
+      if (optimizationSite.typeset) {
+        result.typeset = optimizationSite.typeset.map(({ keyedBy, name }) => ({
+          keyedBy: stringTable[keyedBy],
+          name: stringTable[name],
+        }));
+      }
+      return result;
+    });
+  }
+  function prettifyAttempts(attempts) {
+    const { strategy, outcome } = attempts.schema;
+    return attempts.data.map(data => ({
+      strategy: stringTable[data[strategy]],
+      outcome: stringTable[data[outcome]],
+    }));
+  }
+  function prettifyOptimizations(optimizations) {
+    if (!optimizations) {
+      return null;
+    }
+    return {
+      types: prettifyOptimizationSites(optimizations.types),
+      attempts: prettifyAttempts(optimizations.attempts),
+      line: optimizations.line,
+      column: optimizations.column,
+    };
+  }
+  function framesForFunc(functionName) {
+    const { location, implementation, optimizations, line } = frameTable.schema;
+    return frameTable.data.filter(data => {
+      return stringTable[data[location]].startsWith(functionName + " ");
+    }).map(data => ({
+      implementation: stringTable[data[implementation]],
+      optimizations: prettifyOptimizations(data[optimizations]),
+      line: data[line],
+    }));
+  }
+
+  const outerFunctionFrames = framesForFunc("outerFunction");
+  const innerFunctionFrames = framesForFunc("innerFunction");
+
+  // console.log("outerFunction:", outerFunctionFrames);
+  // console.log("innerFunction:", innerFunctionFrames);
+  //
+  // Example output:
+  //
+  // console.log: "outerFunction:" [
+  //   {
+  //     implementation: "baseline",
+  //     optimizations: null,
+  //     line: undefined
+  //   },
+  //   {
+  //     implementation: "ion",
+  //     optimizations: null,
+  //     line: undefined
+  //   },
+  //   {
+  //     implementation: "ion",
+  //     optimizations: {
+  //       types: [
+  //         {
+  //           site: "Operand",
+  //           mirType: "Double",
+  //           typeset: [
+  //             { keyedBy: "primitive", name: "int" },
+  //             { keyedBy: "primitive", name: "float" }
+  //           ]
+  //         },
+  //         {
+  //           site: "Operand",
+  //           mirType: "Double"
+  //         }
+  //       ],
+  //       attempts: [
+  //         {
+  //           strategy: "BinaryArith_Concat",
+  //           outcome: "OperandNotString"
+  //         },
+  //         {
+  //           strategy: "BinaryArith_SpecializedTypes",
+  //           outcome: "GenericSuccess"
+  //         }
+  //       ],
+  //       line:40,
+  //       column:9
+  //     },
+  //     line: undefined
+  //   },
+  //   {
+  //     implementation: "ion",
+  //     optimizations: null,
+  //     line: undefined
+  //   }
+  // ]
+  // console.log: "innerFunction:" [
+  //   {
+  //     implementation: "ion",
+  //     optimizations: {
+  //       types: [
+  //         {
+  //           site: "Operand",
+  //           mirType: "Int32",
+  //           typeset: [
+  //             { keyedBy: "primitive", name: "int" }
+  //           ]
+  //         },
+  //         {
+  //           site: "Operand",
+  //           mirType: "Double"
+  //         }
+  //       ],
+  //       attempts: [
+  //         {
+  //           strategy: "BinaryArith_SpecializedTypes",
+  //           outcome: "GenericSuccess"
+  //         }
+  //       ],
+  //       line: 30,
+  //       column: 2
+  //     },
+  //     line: undefined
+  //   }
+  // ]
+
+  ok(outerFunctionFrames.length > 0, "should have sampled at least one frame of outerFunction() running");
+  const outerFunctionIonFrames = outerFunctionFrames.filter(frame => frame.implementation === "ion");
+  ok(outerFunctionIonFrames.length > 0, "should have observed outerFunction() running in ion");
+  const outerFunctionIonFramesWithOptimizations = outerFunctionIonFrames.filter(frame => frame.optimizations !== null);
+  ok(outerFunctionIonFramesWithOptimizations.length > 0, "should have optimizations info for some frames of outerFunction()");
+
+  // Try to check for one specific optimization. If the JS engine changes, this
+  // test may need changing. In this test we only care about the fact that we
+  // get useful optimization information, we don't care about how exactly this
+  // JS code was optimized.
+  ok(outerFunctionIonFramesWithOptimizations.some(frame => {
+    return frame.optimizations.line === 40 && frame.optimizations.attempts.some(attempt => {
+      return attempt.strategy === "BinaryArith_SpecializedTypes" &&
+             attempt.outcome === "GenericSuccess";
+    }) && frame.optimizations.types.some(optimizationSite => {
+      return optimizationSite.site === "Operand" &&
+             optimizationSite.mirType === "Double";
+    });
+  }), "should find a successful arithmetic specialization for the += operation on line 40");
+
+  ok(innerFunctionFrames.some(frame => {
+    return frame.implementation === "ion" &&
+           frame.optimizations !== null &&
+           frame.optimizations.line === 30;
+  }), "should find a piece of optimization info about innerFunction for line 30");
+});
+
+</script>
+</body>
+</html>