Bug 1633625 - Split out host-provided functionality (currently SpiderMonkey and Firefox) r=jonco
authorSteve Fink <sfink@mozilla.com>
Tue, 19 May 2020 21:55:26 +0000
changeset 530909 6c6d07b53773ce6307ce79df7b7a4a3695296e84
parent 530908 462f792e609d6eee84e2a79c7cc562ca44e09c46
child 530910 1a7da96bca6675b2a8e32bcb2f30c6a72df28a90
push id37434
push userabutkovits@mozilla.com
push dateWed, 20 May 2020 10:05:10 +0000
treeherdermozilla-central@005ef1c25992 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjonco
bugs1633625
milestone78.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 1633625 - Split out host-provided functionality (currently SpiderMonkey and Firefox) r=jonco Differential Revision: https://phabricator.services.mozilla.com/D75010
js/src/devtools/gc-ubench/harness.js
js/src/devtools/gc-ubench/perf.js
js/src/devtools/gc-ubench/shell-bench.js
js/src/devtools/gc-ubench/spidermonkey.js
js/src/devtools/gc-ubench/ui.js
--- a/js/src/devtools/gc-ubench/harness.js
+++ b/js/src/devtools/gc-ubench/harness.js
@@ -17,16 +17,54 @@ var gDefaultGarbagePerFrame = "8K";
 // the nursery will take care of those.
 //
 // If the per-frame garbage is K and the number of piles is P, then some of the
 // garbage will start getting tenured as long as P*K > size(nursery).
 var gDefaultGarbagePiles = "8";
 
 var gDefaultTestDuration = 8.0;
 
+// The Host interface that provides functionality needed by the test harnesses
+// (web + various shells). Subclasses should override with the appropriate
+// functionality. The methods that throw an error must be implemented. The ones
+// that return undefined are optional.
+//
+// Note that currently the web UI doesn't really use the scheduling pieces of
+// this.
+var Host = class {
+  constructor() {}
+  start_turn() {
+    throw new Error("unimplemented");
+  }
+  end_turn() {
+    throw new Error("unimplemented");
+  }
+  suspend(duration) {
+    throw new Error("unimplemented");
+  } // Shell driver only
+  now() {
+    return performance.now();
+  }
+
+  minorGCCount() {
+    return undefined;
+  }
+  majorGCCount() {
+    return undefined;
+  }
+  GCSliceCount() {
+    return undefined;
+  }
+
+  features = {
+    haveMemorySizes: false,
+    haveGCCounts: false,
+  };
+};
+
 function parse_units(v) {
   if (!v.length) {
     return NaN;
   }
   var lastChar = v[v.length - 1].toLowerCase();
   if (!isNaN(parseFloat(lastChar))) {
     return parseFloat(v);
   }
@@ -195,17 +233,17 @@ var AllocationLoadManager = class {
   }
 
   change_garbagePerFrame(amount) {
     if (this._active) {
       this._active.garbagePerFrame = amount;
     }
   }
 
-  tick(now = performance.now()) {
+  tick(now = gHost.now()) {
     this.lastActive = this._active;
     let events = this._eventsSinceLastTick;
     this._eventsSinceLastTick = 0;
 
     if (this.cycle) {
       const prev = this.cycle.current;
       this.cycle.tick(now);
       if (this.cycle.current != prev) {
@@ -237,17 +275,17 @@ var AllocationLoadManager = class {
     this.setActiveLoadByName(this.cycle.current);
     this._eventsSinceLastTick |= this.CYCLE_STARTED | this.LOAD_STARTED;
   }
 
   cycleStopped() {
     return !this.cycle || this.cycle.done();
   }
 
-  cycleCurrentLoadRemaining(now = performance.now()) {
+  currentLoadRemaining(now = gHost.now()) {
     return this.cycleStopped()
       ? 0
       : this.testDurationMS - this.cycle.currentLoadElapsed(now);
   }
 };
 
 // Current test state.
 var gLoadMgr = undefined;
--- a/js/src/devtools/gc-ubench/perf.js
+++ b/js/src/devtools/gc-ubench/perf.js
@@ -1,19 +1,14 @@
 /* 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/. */
 
 // Performance monitoring and calculation.
 
-var features = {
-  trackingSizes: "mozMemory" in performance,
-  showingGCs: "mozMemory" in performance,
-};
-
 // Class for inter-frame timing, which handles being paused and resumed.
 var FrameTimer = class {
   constructor() {
     // Start time of the current active test, adjusted for any time spent
     // stopped (so `now - this.start` is how long the current active test
     // has run for.)
     this.start = undefined;
 
@@ -23,34 +18,34 @@ var FrameTimer = class {
     // Timestamp when drawing was paused, or zero if drawing is active.
     this.stopped = 0;
   }
 
   is_stopped() {
     return this.stopped != 0;
   }
 
-  start_recording(now = performance.now()) {
+  start_recording(now = gHost.now()) {
     this.start = this.prev = now;
   }
 
-  on_frame_finished(now = performance.now()) {
+  on_frame_finished(now = gHost.now()) {
     const delay = now - this.prev;
     this.prev = now;
     return delay;
   }
 
-  pause(now = performance.now()) {
+  pause(now = gHost.now()) {
     this.stopped = now;
     // Abuse this.prev to store the time elapsed since the previous frame.
     // This will be used to adjust this.prev when we resume.
     this.prev = now - this.prev;
   }
 
-  resume(now = performance.now()) {
+  resume(now = gHost.now()) {
     this.prev = now - this.prev;
     const stop_duration = now - this.stopped;
     this.start += stop_duration;
     this.stopped = 0;
   }
 };
 
 // Per-frame time sampling infra.
@@ -73,17 +68,17 @@ var FrameHistory = class {
     this.minorGCs = new Array(numSamples);
     this.majorGCs = new Array(numSamples);
     this.slices = new Array(numSamples);
 
     sampleIndex = 0;
     this.reset();
   }
 
-  start(now = performance.now()) {
+  start(now = gHost.now()) {
     this._frameTimer.start_recording(now);
   }
 
   reset() {
     this.delays.fill(0);
     this.gcBytes.fill(0);
     this.mallocBytes.fill(0);
     this.gcs.fill(this.gcs[sampleIndex]);
@@ -109,43 +104,34 @@ var FrameHistory = class {
     }
     return maxIndex;
   }
 
   findMaxDelay() {
     return this.findMax(this.delays);
   }
 
-  on_frame(now = performance.now()) {
+  on_frame(now = gHost.now()) {
     const delay = this._frameTimer.on_frame_finished(now);
 
     // Total time elapsed while the active test has been running.
     var t = now - this._frameTimer.start;
     var newIndex = Math.round(t / sampleTime);
     while (sampleIndex < newIndex) {
       sampleIndex++;
       var idx = sampleIndex % this._numSamples;
       this.delays[idx] = delay;
-      if (features.trackingSizes) {
-        this.gcBytes[idx] = performance.mozMemory.gc.gcBytes;
-        this.mallocBytes[idx] = performance.mozMemory.gc.zone.mallocBytes;
+      if (gHost.features.haveMemorySizes) {
+        this.gcBytes[idx] = gHost.gcBytes;
+        this.mallocBytes[idx] = gHost.mallocBytes;
       }
-      if (features.showingGCs) {
-        this.gcs[idx] = performance.mozMemory.gc.gcNumber;
-        this.minorGCs[idx] = performance.mozMemory.gc.minorGCCount;
-        this.majorGCs[idx] = performance.mozMemory.gc.majorGCCount;
-
-        // Previous versions lacking sliceCount will fall back to
-        // assuming any GC activity was a major GC slice, even though
-        // that incorrectly includes minor GCs. Although this file is
-        // versioned with the code that implements the new sliceCount
-        // field, it is common to load the gc-ubench index.html with
-        // different browser versions.
-        this.slices[idx] =
-          performance.mozMemory.gc.sliceCount || performance.mozMemory.gc.gcNumber;
+      if (gHost.features.haveGCCounts) {
+        this.minorGCs[idx] = gHost.minorGCCount;
+        this.majorGCs[idx] = gHost.majorGCCount;
+        this.slices[idx] = gHost.GCSliceCount;
       }
     }
 
     return delay;
   }
 
   pause() {
     this._frameTimer.pause();
copy from js/src/devtools/gc-ubench/spidermonkey.js
copy to js/src/devtools/gc-ubench/shell-bench.js
--- a/js/src/devtools/gc-ubench/spidermonkey.js
+++ b/js/src/devtools/gc-ubench/shell-bench.js
@@ -1,57 +1,45 @@
+/* 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 FPS = 60;
 var gNumSamples = 500;
-
 var waited = 0;
 
-////////////// Host functionality /////////////////
-
-function start_turn() {}
-
-function end_turn() {
-  clearKeptObjects();
-}
-
-function suspend(duration) {
-  sleep(duration);
-}
-
-///////////////// Main /////////////////
+// This requires a gHost to have been created that provides host-specific
+// facilities. See eg spidermonkey.js.
 
 loadRelativeToScript("harness.js");
 loadRelativeToScript("perf.js");
 loadRelativeToScript("test_list.js");
 
 var tests = new Map();
 foreach_test_file(f => loadRelativeToScript(f));
 for (const [name, info] of tests.entries()) {
   if ("enabled" in info && !info.enabled) {
     tests.delete(name);
   }
 }
 
 function tick(loadMgr, timestamp) {
-  start_turn();
+  gHost.start_turn();
   const events = loadMgr.tick(timestamp);
-  end_turn();
+  gHost.end_turn();
   return events;
 }
 
-function now() {
-  return performance.now();
-}
-
 function wait_for_next_frame(t0, t1) {
   const elapsed = (t1 - t0) / 1000;
   const period = 1 / FPS;
   const used = elapsed % period;
   const delay = period - used;
   waited += delay;
-  suspend(delay);
+  gHost.suspend(delay);
 }
 
 function report_events(events, loadMgr) {
   let ended = false;
   if (events & loadMgr.LOAD_ENDED) {
     print(`${loadMgr.lastActive.name} ended`);
     ended = true;
   }
@@ -64,30 +52,27 @@ function report_events(events, loadMgr) 
 function run(loads) {
   const loadMgr = new AllocationLoadManager(tests);
   const perf = new FrameHistory(gNumSamples);
 
   loads = loads.length ? loads : tests.keys();
   loadMgr.startCycle(loads);
   perf.start();
 
-  const t0 = now();
+  const t0 = gHost.now();
 
   let possible = 0;
   let frames = 0;
   while (loadMgr.load_running()) {
-    const timestamp = now();
+    const timestamp = gHost.now();
     perf.on_frame(timestamp);
     const events = tick(loadMgr, timestamp);
     frames++;
     if (report_events(events, loadMgr)) {
       possible += (loadMgr.testDurationMS / 1000) * FPS;
       print(
-        `  observed ${frames} / ${possible} frames in ${(now() - t0) /
+        `  observed ${frames} / ${possible} frames in ${(gHost.now() - t0) /
           1000} seconds`
       );
     }
-    wait_for_next_frame(t0, now());
+    wait_for_next_frame(t0, gHost.now());
   }
 }
-
-run(scriptArgs);
-print(`Waited total of ${waited} seconds`);
--- a/js/src/devtools/gc-ubench/spidermonkey.js
+++ b/js/src/devtools/gc-ubench/spidermonkey.js
@@ -1,93 +1,47 @@
-var FPS = 60;
-var gNumSamples = 500;
-
-var waited = 0;
-
-////////////// Host functionality /////////////////
-
-function start_turn() {}
+/* 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 end_turn() {
-  clearKeptObjects();
-}
+// SpiderMonkey JS shell benchmark script
+//
+// Usage: run $JS spidermonkey.js --help
 
-function suspend(duration) {
-  sleep(duration);
-}
-
-///////////////// Main /////////////////
+loadRelativeToScript("shell-bench.js");
 
-loadRelativeToScript("harness.js");
-loadRelativeToScript("perf.js");
-loadRelativeToScript("test_list.js");
+var SpiderMonkey = class extends Host {
+  start_turn() {}
 
-var tests = new Map();
-foreach_test_file(f => loadRelativeToScript(f));
-for (const [name, info] of tests.entries()) {
-  if ("enabled" in info && !info.enabled) {
-    tests.delete(name);
+  end_turn() {
+    drainJobQueue();
+    clearKeptObjects();
   }
-}
 
-function tick(loadMgr, timestamp) {
-  start_turn();
-  const events = loadMgr.tick(timestamp);
-  end_turn();
-  return events;
-}
-
-function now() {
-  return performance.now();
-}
+  suspend(duration) {
+    sleep(duration);
+  }
 
-function wait_for_next_frame(t0, t1) {
-  const elapsed = (t1 - t0) / 1000;
-  const period = 1 / FPS;
-  const used = elapsed % period;
-  const delay = period - used;
-  waited += delay;
-  suspend(delay);
-}
-
-function report_events(events, loadMgr) {
-  let ended = false;
-  if (events & loadMgr.LOAD_ENDED) {
-    print(`${loadMgr.lastActive.name} ended`);
-    ended = true;
+  get minorGCCount() {
+    return performance.mozMemory.gc.minorGCCount;
+  }
+  get majorGCCount() {
+    return performance.mozMemory.gc.majorGCCount;
   }
-  if (events & loadMgr.LOAD_STARTED) {
-    print(`${loadMgr.activeLoad().name} starting`);
+  get GCSliceCount() {
+    return performance.mozMemory.gc.sliceCount;
   }
-  return ended;
-}
+  get gcBytes() {
+    return performance.mozMemory.gc.zone.gcBytes;
+  }
+  get gcAllocTrigger() {
+    return performance.mozMemory.gc.zone.gcAllocTrigger;
+  }
 
-function run(loads) {
-  const loadMgr = new AllocationLoadManager(tests);
-  const perf = new FrameHistory(gNumSamples);
-
-  loads = loads.length ? loads : tests.keys();
-  loadMgr.startCycle(loads);
-  perf.start();
-
-  const t0 = now();
-
-  let possible = 0;
-  let frames = 0;
-  while (loadMgr.load_running()) {
-    const timestamp = now();
-    perf.on_frame(timestamp);
-    const events = tick(loadMgr, timestamp);
-    frames++;
-    if (report_events(events, loadMgr)) {
-      possible += (loadMgr.testDurationMS / 1000) * FPS;
-      print(
-        `  observed ${frames} / ${possible} frames in ${(now() - t0) /
-          1000} seconds`
-      );
-    }
-    wait_for_next_frame(t0, now());
-  }
-}
+  features = {
+    haveMemorySizes: true,
+    haveGCCounts: true,
+  };
+};
+var gHost = new SpiderMonkey();
 
 run(scriptArgs);
 print(`Waited total of ${waited} seconds`);
--- a/js/src/devtools/gc-ubench/ui.js
+++ b/js/src/devtools/gc-ubench/ui.js
@@ -1,17 +1,12 @@
 /* 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 features = {
-  trackingSizes: "mozMemory" in performance,
-  showingGCs: "mozMemory" in performance,
-};
-
 var stroke = {
   gcslice: "rgb(255,100,0)",
   minor: "rgb(0,255,100)",
   initialMajor: "rgb(180,60,255)",
 };
 
 var numSamples = 500;
 
@@ -20,16 +15,57 @@ var gPerf = new FrameHistory(numSamples)
 
 var latencyGraph;
 var memoryGraph;
 var ctx;
 var memoryCtx;
 
 var loadState = "(init)"; // One of '(active)', '(inactive)', '(N/A)'
 var testState = "idle"; // One of 'idle' or 'running'.
+var enabled = { trackingSizes: false };
+
+var gMemory = performance.mozMemory?.gc || performance.mozMemory || {};
+
+var Firefox = class extends Host {
+  start_turn() {
+    // Handled by Gecko.
+  }
+
+  end_turn() {
+    // Handled by Gecko.
+  }
+
+  suspend(duration) {
+    // Not used; requestAnimationFrame takes its place.
+    throw new Error("unimplemented");
+  }
+
+  get minorGCCount() {
+    return gMemory.minorGCCount;
+  }
+  get majorGCCount() {
+    return gMemory.majorGCCount;
+  }
+  get GCSliceCount() {
+    return gMemory.sliceCount;
+  }
+  get gcBytes() {
+    return gMemory.zone.gcBytes;
+  }
+  get gcAllocTrigger() {
+    return gMemory.zone.gcAllocTrigger;
+  }
+
+  features = {
+    haveMemorySizes: 'gcBytes' in gMemory,
+    haveGCCounts: 'majorGCCount' in gMemory,
+  };
+};
+
+var gHost = new Firefox();
 
 function parse_units(v) {
   if (!v.length) {
     return NaN;
   }
   var lastChar = v[v.length - 1].toLowerCase();
   if (!isNaN(parseFloat(lastChar))) {
     return parseFloat(v);
@@ -163,17 +199,17 @@ var LatencyGraph = class extends Graph {
       if (gPerf.delays[i] >= worst) {
         worst = gPerf.delays[i];
         worstpos = i;
       }
     }
     ctx.stroke();
 
     // Draw vertical lines marking minor and major GCs
-    if (features.showingGCs) {
+    if (gHost.features.haveGCCounts) {
       ctx.strokeStyle = stroke.gcslice;
       let idx = sampleIndex % numSamples;
       const count = {
         major: gPerf.majorGCs[idx],
         minor: 0,
         slice: gPerf.slices[idx],
       };
       for (let i = 0; i < numSamples; i++) {
@@ -235,21 +271,18 @@ var LatencyGraph = class extends Graph {
 
     this.drawAxisLabels("Time", "Pause between frames (log scale)");
   }
 };
 
 var MemoryGraph = class extends Graph {
   constructor(ctx) {
     super(ctx);
-    this.worstEver = this.bestEver = performance.mozMemory.zone.gcBytes;
-    this.limit = Math.max(
-      this.worstEver,
-      performance.mozMemory.zone.gcAllocTrigger
-    );
+    this.worstEver = this.bestEver = gHost.gcBytes();
+    this.limit = Math.max(this.worstEver, gHost.gcAllocTrigger);
   }
 
   ypos(size) {
     const { height } = this.ctx.canvas;
 
     const range = this.limit - this.bestEver;
     const percent = (size - this.bestEver) / range;
 
@@ -288,35 +321,32 @@ var MemoryGraph = class extends Graph {
       }
       if (gPerf.gcBytes[i] < this.bestEver) {
         this.bestEver = gPerf.gcBytes[i];
       }
     }
 
     if (this.worstEver < worst) {
       this.worstEver = worst;
-      this.limit = Math.max(
-        this.worstEver,
-        performance.mozMemory.zone.gcAllocTrigger
-      );
+      this.limit = Math.max(this.worstEver, gHost.gcAllocTrigger);
     }
 
     this.drawHBar(
       this.bestEver,
       `${format_bytes(this.bestEver)} min`,
       "#00cf61"
     );
     this.drawHBar(
       this.worstEver,
       `${format_bytes(this.worstEver)} max`,
       "#cc1111"
     );
     this.drawHBar(
-      performance.mozMemory.zone.gcAllocTrigger,
-      `${format_bytes(performance.mozMemory.zone.gcAllocTrigger)} trigger`,
+      gHost.gcAllocTrigger,
+      `${format_bytes(gHost.gcAllocTrigger)} trigger`,
       "#cc11cc"
     );
 
     ctx.fillStyle = "rgb(255,0,0)";
     if (worst) {
       ctx.fillText(
         format_bytes(worst),
         this.xpos(worstpos) - 10,
@@ -383,18 +413,17 @@ function handler(timestamp) {
     if (!gLoadMgr.cycleStopped()) {
       start_test();
     }
     update_load_display();
   }
 
   if (testState == "running") {
     document.getElementById("test-progress").textContent =
-      (gLoadMgr.cycleCurrentLoadRemaining(timestamp) / 1000).toFixed(1) +
-      " sec";
+      (gLoadMgr.currentLoadRemaining(timestamp) / 1000).toFixed(1) + " sec";
   }
 
   const delay = gPerf.on_frame(timestamp);
 
   update_histogram(gHistogram, delay);
 
   latencyGraph.draw();
   if (memoryGraph) {
@@ -484,17 +513,17 @@ function onload() {
     window.webkitRequestAnimationFrame ||
     window.msRequestAnimationFrame;
   window.requestAnimationFrame = requestAnimationFrame;
 
   // Acquire our canvas.
   var canvas = document.getElementById("graph");
   latencyGraph = new LatencyGraph(canvas.getContext("2d"));
 
-  if (!performance.mozMemory || !performance.mozMemory.gc) {
+  if (!gHost.features.haveMemorySizes) {
     document.getElementById("memgraph-disabled").style.display = "block";
     document.getElementById("track-sizes-div").style.display = "none";
   }
 
   trackHeapSizes(document.getElementById("track-sizes").checked);
 
   update_load_state_indicator();
   gPerf.start();
@@ -516,17 +545,20 @@ function start_test_cycle(tests_to_run) 
   // Convert from an iterable to an array for pop.
   gLoadMgr.startCycle(tests_to_run);
   testState = "running";
   gHistogram.clear();
   reset_draw_state();
 }
 
 function update_load_state_indicator() {
-  if (!gLoadMgr.load_running() || gLoadMgr.active.name == "noAllocation") {
+  if (
+    !gLoadMgr.load_running() ||
+    gLoadMgr.activeLoad().name == "noAllocation"
+  ) {
     loadState = "(none)";
   } else if (gPerf.is_stopped() || gLoadMgr.paused) {
     loadState = "(inactive)";
   } else {
     loadState = "(active)";
   }
   document.getElementById("load-running").textContent = loadState;
 }
@@ -645,20 +677,20 @@ function garbage_per_frame_changed() {
       `Updated garbage-per-frame to ${
         gLoadMgr.activeLoad().garbagePerFrame
       } items`
     );
   }
 }
 
 function trackHeapSizes(track) {
-  features.trackingSizes = track;
+  enabled.trackingSizes = track && gHost.features.haveMemorySizes;
 
   var canvas = document.getElementById("memgraph");
 
-  if (features.trackingSizes) {
+  if (enabled.trackingSizes) {
     canvas.style.display = "block";
     memoryGraph = new MemoryGraph(canvas.getContext("2d"));
   } else {
     canvas.style.display = "none";
     memoryGraph = null;
   }
 }