Bug 1182595 - Add common, e10s-friendly SPS Profiler scripts that Talos tests can use. r=jmaher
authorMike Conley <mconley@mozilla.com>
Tue, 15 Dec 2015 15:57:39 -0500
changeset 322037 ad4d966f23677027aa0bd08f195f1478dd9d6d8c
parent 322036 5f9f5bacc390e2abd9bf9acbb76bd399171900e9
child 322038 a1713c8c6746c073ed0bf08d3e4bfc05aa3face2
push id5913
push userjlund@mozilla.com
push dateMon, 25 Apr 2016 16:57:49 +0000
treeherdermozilla-beta@dcaf0a6fa115 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjmaher
bugs1182595
milestone47.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 1182595 - Add common, e10s-friendly SPS Profiler scripts that Talos tests can use. r=jmaher This introduces TalosContentProfiler.js, which can be used within content, and TalosParentProfiler.js, which can be used inside the parent process. MozReview-Commit-ID: 4L7rRuNALOy
testing/talos/talos/talos-powers/chrome.manifest
testing/talos/talos/talos-powers/chrome/talos-powers-content.js
testing/talos/talos/talos-powers/components/TalosPowersService.js
testing/talos/talos/talos-powers/content/TalosContentProfiler.js
testing/talos/talos/talos-powers/content/TalosParentProfiler.js
--- a/testing/talos/talos/talos-powers/chrome.manifest
+++ b/testing/talos/talos/talos-powers/chrome.manifest
@@ -1,4 +1,5 @@
 content talos-powers chrome/
+content talos-powers-content content/ contentaccessible=yes
 component {f5d53443-d58d-4a2f-8df0-98525d4f91ad} components/TalosPowersService.js
 contract @mozilla.org/talos/talos-powers-service;1 {f5d53443-d58d-4a2f-8df0-98525d4f91ad}
 category profile-after-change TalosPowersService @mozilla.org/talos/talos-powers-service;1
--- a/testing/talos/talos/talos-powers/chrome/talos-powers-content.js
+++ b/testing/talos/talos/talos-powers/chrome/talos-powers-content.js
@@ -1,13 +1,13 @@
 /* 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 { interfaces: Ci } = Components;
+var { interfaces: Ci, utils: Cu } = Components;
 
 /**
  * Content that wants to quit the whole session should
  * fire the TalosQuitApplication custom event. This will
  * attempt to force-quit the browser.
  */
 addEventListener("TalosQuitApplication", event => {
   // If we're loaded in a low-priority background process, like
@@ -17,8 +17,29 @@ addEventListener("TalosQuitApplication",
   let priority = docShell.QueryInterface(Ci.nsIDocumentLoader)
                          .loadGroup
                          .QueryInterface(Ci.nsISupportsPriority)
                          .priority;
   if (priority != Ci.nsISupportsPriority.PRIORITY_LOWEST) {
     sendAsyncMessage("Talos:ForceQuit", event.detail);
   }
 });
+
+addEventListener("TalosContentProfilerCommand", (e) => {
+  let name = e.detail.name;
+  let data = e.detail.data;
+  sendAsyncMessage("TalosContentProfiler:Command", { name, data });
+});
+
+addMessageListener("TalosContentProfiler:Response", (msg) => {
+  let name = msg.data.name;
+  let data = msg.data.data;
+
+  let event = Cu.cloneInto({
+    bubbles: true,
+    detail: {
+      name: name,
+      data: data,
+    },
+  }, content);
+  content.dispatchEvent(
+    new content.CustomEvent("TalosContentProfilerResponse", event));
+});
\ No newline at end of file
--- a/testing/talos/talos/talos-powers/components/TalosPowersService.js
+++ b/testing/talos/talos/talos-powers/components/TalosPowersService.js
@@ -2,63 +2,204 @@
  * 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 { interfaces: Ci, utils: Cu } = Components;
 
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "Services",
   "resource://gre/modules/Services.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "OS",
+  "resource://gre/modules/osfile.jsm");
 
 const FRAME_SCRIPT = "chrome://talos-powers/content/talos-powers-content.js";
 
-function TalosPowersService() {};
+function TalosPowersService() {
+  this.wrappedJSObject = this;
+};
 
 TalosPowersService.prototype = {
   classDescription: "Talos Powers",
   classID: Components.ID("{f5d53443-d58d-4a2f-8df0-98525d4f91ad}"),
   contractID: "@mozilla.org/talos/talos-powers-service;1",
   QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver]),
 
   observe(subject, topic, data) {
     switch(topic) {
       case "profile-after-change":
         // Note that this observation is registered in the chrome.manifest
         // for this add-on.
         this.init();
         break;
-      case "sessionstore-windows-restored":
-        this.inject();
-        break;
       case "xpcom-shutdown":
         this.uninit();
         break;
     }
   },
 
   init() {
-    // We want to defer any kind of work until after sessionstore has
-    // finished in order not to skew sessionstore test numbers.
-    Services.obs.addObserver(this, "sessionstore-windows-restored", false);
+    Services.mm.loadFrameScript(FRAME_SCRIPT, true);
+    Services.mm.addMessageListener("Talos:ForceQuit", this);
+    Services.mm.addMessageListener("TalosContentProfiler:Command", this);
     Services.obs.addObserver(this, "xpcom-shutdown", false);
   },
 
   uninit() {
-    Services.obs.removeObserver(this, "sessionstore-windows-restored", false);
     Services.obs.removeObserver(this, "xpcom-shutdown", false);
   },
 
-  inject() {
-    Services.mm.loadFrameScript(FRAME_SCRIPT, true);
-    Services.mm.addMessageListener("Talos:ForceQuit", this);
+  receiveMessage(message) {
+    switch(message.name) {
+      case "Talos:ForceQuit": {
+        this.forceQuit(message.data);
+        break;
+      }
+      case "TalosContentProfiler:Command": {
+        this.receiveProfileCommand(message);
+        break;
+      }
+    }
+  },
+
+  /**
+   * Enable the SPS profiler with some settings and then pause
+   * immediately.
+   *
+   * @param data (object)
+   *        A JavaScript object with the following properties:
+   *
+   *        entries (int):
+   *          The sampling buffer size in bytes.
+   *
+   *        interval (int):
+   *          The sampling interval in milliseconds.
+   *
+   *        threadsArray (array of strings):
+   *          The thread names to sample.
+   */
+  profilerBegin(data) {
+    Services.profiler
+            .StartProfiler(data.entries, data.interval,
+                           ["js", "leaf", "stackwalk", "threads"], 4,
+                           data.threadsArray, data.threadsArray.length);
+
+    Services.profiler.PauseSampling();
+  },
+
+  /**
+   * Assuming the Profiler is running, dumps the Profile from all sampled
+   * processes and threads to the disk. The Profiler will be stopped once
+   * the profiles have been dumped. This method returns a Promise that
+   * will resolve once this has occurred.
+   *
+   * @param profileFile (string)
+   *        The name of the file to write to.
+   *
+   * @returns Promise
+   */
+  profilerFinish(profileFile) {
+    return new Promise((resolve, reject) => {
+      Services.profiler.getProfileDataAsync().then((profile) => {
+        let encoder = new TextEncoder();
+        let array = encoder.encode(JSON.stringify(profile));
+
+        OS.File.writeAtomic(profileFile, array, {
+          tmpPath: profileFile + ".tmp",
+        }).then(() => {
+          Services.profiler.StopProfiler();
+          resolve();
+        });
+      }, (error) => {
+        Cu.reportError("Failed to gather profile: " + error);
+        // FIXME: We should probably send a message down to the
+        // child which causes it to reject the waiting Promise.
+        reject();
+      });
+    });
   },
 
-  receiveMessage(message) {
-    if (message.name == "Talos:ForceQuit") {
-      this.forceQuit(message.data);
+  /**
+   * Pauses the Profiler, optionally setting a parent process marker before
+   * doing so.
+   *
+   * @param marker (string, optional)
+   *        A marker to set before pausing.
+   */
+  profilerPause(marker=null) {
+    if (marker) {
+      Services.profiler.AddMarker(marker);
+    }
+
+    Services.profiler.PauseSampling();
+  },
+
+  /**
+   * Resumes a pausedProfiler, optionally setting a parent process marker
+   * after doing so.
+   *
+   * @param marker (string, optional)
+   *        A marker to set after resuming.
+   */
+  profilerResume(marker=null) {
+    Services.profiler.ResumeSampling();
+
+    if (marker) {
+      Services.profiler.AddMarker(marker);
+    }
+  },
+
+  /**
+   * Adds a marker to the Profile in the parent process.
+   */
+  profilerMarker(marker) {
+    Services.profiler.AddMarker(marker);
+  },
+
+  receiveProfileCommand(message) {
+    const ACK_NAME = "TalosContentProfiler:Response";
+    let mm = message.target.messageManager;
+    let name = message.data.name;
+    let data = message.data.data;
+
+    switch(name) {
+      case "Profiler:Begin": {
+        this.profilerBegin(data);
+        // profilerBegin will cause the parent to send an async message to any
+        // child processes to start profiling. Because messages are serviced
+        // in order, we know that by the time that the child services the
+        // ACK message, that the profiler has started in its process.
+        mm.sendAsyncMessage(ACK_NAME, { name });
+        break;
+      }
+
+      case "Profiler:Finish": {
+        // The test is done. Dump the profile.
+        let profileFile = data.profileFile;
+        this.profilerFinish(data.profileFile).then(() => {
+          mm.sendAsyncMessage(ACK_NAME, { name });
+        });
+        break;
+      }
+
+      case "Profiler:Pause": {
+        this.profilerPause(data.marker);
+        mm.sendAsyncMessage(ACK_NAME, { name });
+        break;
+      }
+
+      case "Profiler:Resume": {
+        this.profilerResume(data.marker);
+        mm.sendAsyncMessage(ACK_NAME, { name });
+        break;
+      }
+
+      case "Profiler:Marker": {
+        this.profilerMarker(data.marker);
+        mm.sendAsyncMessage(ACK_NAME, { name });
+      }
     }
   },
 
   forceQuit(messageData) {
     if (messageData && messageData.waitForSafeBrowsing) {
       let SafeBrowsing = Cu.import("resource://gre/modules/SafeBrowsing.jsm", {}).SafeBrowsing;
 
       let whenDone = () => {
new file mode 100644
--- /dev/null
+++ b/testing/talos/talos/talos-powers/content/TalosContentProfiler.js
@@ -0,0 +1,256 @@
+/* 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/. */
+
+/**
+ * This utility script is for instrumenting your Talos test for
+ * performance profiles while running within content. If your test
+ * is running in the parent process, you should use
+ * TalosParentProfiler.js instead to avoid the messaging overhead.
+ */
+
+var TalosContentProfiler;
+
+(function() {
+
+  // Whether or not this TalosContentProfiler object has had initFromObject
+  // or initFromURLQueryParams called on it. Any functions that will send
+  // events to the parent to change the behaviour of the SPS Profiler
+  // should only be called after calling either initFromObject or
+  // initFromURLQueryParams.
+  var initted = false;
+
+  // The subtest name that beginTest() was called with.
+  var currentTest = "unknown";
+
+  // Profiler settings.
+  var interval, entries, threadsArray, profileDir;
+
+  try {
+    // Outside of talos, this throws a security exception which no-op this file.
+    // (It's not required nor allowed for addons since Firefox 17)
+    // It's used inside talos from non-privileged pages (like during tscroll),
+    // and it works because talos disables all/most security measures.
+    netscape.security.PrivilegeManager.enablePrivilege('UniversalXPConnect');
+  } catch (e) {}
+
+  Components.utils.import("resource://gre/modules/Services.jsm");
+
+  /**
+   * Emits a TalosContentProfiler prefixed event and then returns a Promise
+   * that resolves once a corresponding acknowledgement event is
+   * dispatched on our document.
+   *
+   * @param name
+   *        The name of the event that will be TalosContentProfiler prefixed and
+   *        eventually sent to the parent.
+   * @param data (optional)
+   *        The data that will be sent to the parent.
+   * @returns Promise
+   *        Resolves when a corresponding acknowledgement event is dispatched
+   *        on this document.
+   */
+  function sendEventAndWait(name, data={}) {
+    return new Promise((resolve) => {
+      var event = new CustomEvent("TalosContentProfilerCommand", {
+        bubbles: true,
+        detail: {
+          name: name,
+          data: data,
+        }
+      });
+      document.dispatchEvent(event);
+
+      addEventListener("TalosContentProfilerResponse", function onResponse(event) {
+        if (event.detail.name != name) {
+          return;
+        }
+
+        removeEventListener("TalosContentProfilerResponse", onResponse);
+
+        resolve(event.detail.data);
+      });
+    });
+  }
+
+  /**
+   * Parses an url query string into a JS object.
+   *
+   * @param locationSearch (string)
+   *        The location string to parse.
+   * @returns Object
+   *        The GET params from the location string as
+   *        key-value pairs in the Object.
+   */
+  function searchToObject(locationSearch) {
+    var pairs = locationSearch.substring(1).split("&");
+    var result = {};
+
+    for (var i in pairs) {
+      if (pairs[i] !== "") {
+        var pair = pairs[i].split("=");
+        result[decodeURIComponent(pair[0])] = decodeURIComponent(pair[1] || "");
+      }
+    }
+
+    return result;
+  }
+
+  TalosContentProfiler = {
+    /**
+     * Initialize the profiler using profiler settings supplied in a JS object.
+     *
+     * @param obj (object)
+     *   The following properties on the object are respected:
+     *     sps_profile_interval (int)
+     *     sps_profile_entries (int)
+     *     sps_profile_threads (string, comma separated list of threads to filter with)
+     *     sps_profile_dir (string)
+     */
+    initFromObject(obj={}) {
+      if (!initted) {
+        if (("sps_profile_dir" in obj) && typeof obj.sps_profile_dir == "string" &&
+            ("sps_profile_interval" in obj) && Number.isFinite(obj.sps_profile_interval * 1) &&
+            ("sps_profile_entries" in obj) && Number.isFinite(obj.sps_profile_entries * 1) &&
+            ("sps_profile_threads" in obj) && typeof obj.sps_profile_threads == "string") {
+          interval = obj.sps_profile_interval;
+          entries = obj.sps_profile_entries;
+          threadsArray = obj.sps_profile_threads.split(",");
+          profileDir = obj.sps_profile_dir;
+          initted = true;
+        } else {
+          console.error("Profiler could not init with object: " + JSON.stringify(obj));
+        }
+      }
+    },
+
+    /**
+     * Initialize the profiler using a string from a location string.
+     *
+     * @param locationSearch (string)
+     *        The location string to initialize with.
+     */
+    initFromURLQueryParams(locationSearch) {
+      this.initFromObject(searchToObject(locationSearch));
+    },
+
+    /**
+     * A Talos test is about to start. This will return a Promise that
+     * resolves once the Profiler has been initialized. Note that the
+     * SPS profiler will be paused immediately after starting and that
+     * resume() should be called in order to collect samples.
+     *
+     * @param testName (string)
+     *        The name of the test to use in Profiler markers.
+     * @returns Promise
+     *        Resolves once the SPS profiler has been initialized and paused.
+     */
+    beginTest(testName) {
+      if (initted) {
+        currentTest = testName;
+        return sendEventAndWait("Profiler:Begin", {
+          interval,
+          entries,
+          threadsArray,
+        });
+      } else {
+        var msg = "You should not call beginTest without having first " +
+                  "initted the Profiler"
+        console.error(msg);
+        return Promise.reject(msg);
+      }
+    },
+
+    /**
+     * A Talos test has finished. This will stop the SPS profiler from sampling,
+     * and return a Promise that resolves once the Profiler has finished dumping
+     * the multi-process profile to disk.
+     *
+     * @returns Promise
+     *          Resolves once the profile has been dumped to disk. The test should
+     *          not try to quit the browser until this has resolved.
+     */
+    finishTest() {
+      if (initted) {
+        let profileFile = profileDir + "/" + currentTest + ".sps";
+        return sendEventAndWait("Profiler:Finish", { profileFile });
+      } else {
+        var msg = "You should not call finishTest without having first " +
+                  "initted the Profiler";
+        console.error(msg);
+        return Promise.reject(msg);
+      }
+    },
+
+    /**
+     * A start-up test has finished. Callers don't need to run beginTest or
+     * finishTest, but should pause the sampler as soon as possible, and call
+     * this function to dump the profile.
+     *
+     * @returns Promise
+     *          Resolves once the profile has been dumped to disk. The test should
+     *          not try to quit the browser until this has resolved.
+     */
+    finishStartupProfiling() {
+      if (initted) {
+        let profileFile = profileDir + "/startup.sps";
+        return sendEventAndWait("Profiler:Finish", { profileFile });
+      }
+      return Promise.resolve();
+    },
+
+    /**
+     * Resumes the SPS profiler sampler. Can also simultaneously set a marker.
+     *
+     * @returns Promise
+     *          Resolves once the SPS profiler has resumed.
+     */
+    resume(marker="") {
+      if (initted) {
+        return sendEventAndWait("Profiler:Resume", { marker });
+      }
+      return Promise.resolve();
+    },
+
+    /**
+     * Pauses the SPS profiler sampler. Can also simultaneously set a marker.
+     *
+     * @returns Promise
+     *          Resolves once the SPS profiler has paused.
+     */
+    pause(marker="") {
+      if (initted) {
+        return sendEventAndWait("Profiler:Pause", { marker });
+      }
+
+      return Promise.resolve();
+    },
+
+    /**
+     * Adds a marker to the profile.
+     *
+     * @returns Promise
+     *          Resolves once the marker has been set.
+     */
+    mark(marker) {
+      if (initted) {
+        // If marker is omitted, just use the test name
+        if (!marker) {
+          marker = currentTest;
+        }
+
+        return sendEventAndWait("Profiler:Marker", { marker });
+      }
+
+      return Promise.resolve();
+    },
+
+    /**
+     * Add a marker to the profile on the content process samples.
+     * This occurs synchronously.
+     */
+    contentMarker(marker) {
+      Services.profiler.AddMarker(marker);
+    },
+  };
+})();
\ No newline at end of file
new file mode 100644
--- /dev/null
+++ b/testing/talos/talos/talos-powers/content/TalosParentProfiler.js
@@ -0,0 +1,202 @@
+/* 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/. */
+
+/**
+ * This utility script is for instrumenting your Talos test for
+ * performance profiles while running within the parent process.
+ * Almost all of the functions that this script exposes to the
+ * SPS Profiler are synchronous, except for finishTest, since that
+ * involves requesting the profiles from any content processes and
+ * then writing to disk.
+ *
+ * If your test is running in the content process, you should use
+ * TalosContentProfiler.js instead.
+ */
+
+var TalosParentProfiler;
+
+(function() {
+
+  // Whether or not this TalosContentProfiler object has had initFromObject
+  // or initFromURLQueryParams called on it. Any functions that change the
+  // state of the SPS Profiler should only be called after calling either
+  // initFromObject or initFromURLQueryParams.
+  let initted = false;
+
+  // The subtest name that beginTest() was called with.
+  let currentTest = "unknown";
+
+  // Profiler settings.
+  let interval, entries, threadsArray, profileDir;
+
+  Components.utils.import("resource://gre/modules/Services.jsm");
+  Components.utils.import("resource://gre/modules/Console.jsm");
+
+  // Use a bit of XPCOM hackery to get at the Talos Powers service
+  // implementation...
+  let TalosPowers =
+    Components.classes["@mozilla.org/talos/talos-powers-service;1"]
+              .getService(Components.interfaces.nsISupports)
+              .wrappedJSObject;
+
+  /**
+   * Parses an url query string into a JS object.
+   *
+   * @param locationSearch (string)
+   *        The location string to parse.
+   * @returns Object
+   *        The GET params from the location string as
+   *        key-value pairs in the Object.
+   */
+  function searchToObject(locationSearch) {
+    let pairs = locationSearch.substring(1).split("&");
+    let result = {};
+
+    for (let i in pairs) {
+      if (pairs[i] !== "") {
+        let pair = pairs[i].split("=");
+        result[decodeURIComponent(pair[0])] = decodeURIComponent(pair[1] || "");
+      }
+    }
+
+    return result;
+  }
+
+  TalosParentProfiler = {
+    /**
+     * Initialize the profiler using profiler settings supplied in a JS object.
+     *
+     * @param obj (object)
+     *   The following properties on the object are respected:
+     *     sps_profile_interval (int)
+     *     sps_profile_entries (int)
+     *     sps_profile_threads (string, comma separated list of threads to filter with)
+     *     sps_profile_dir (string)
+     */
+    initFromObject(obj={}) {
+      if (!initted) {
+        if (("sps_profile_dir" in obj) && typeof obj.sps_profile_dir == "string" &&
+            ("sps_profile_interval" in obj) && Number.isFinite(obj.sps_profile_interval * 1) &&
+            ("sps_profile_entries" in obj) && Number.isFinite(obj.sps_profile_entries * 1) &&
+            ("sps_profile_threads" in obj) && typeof obj.sps_profile_threads == "string") {
+          interval = obj.sps_profile_interval;
+          entries = obj.sps_profile_entries;
+          threadsArray = obj.sps_profile_threads.split(",");
+          profileDir = obj.sps_profile_dir;
+          initted = true;
+        } else {
+          console.error("Profiler could not init with object: " + JSON.stringify(obj));
+        }
+      }
+    },
+
+    /**
+     * Initialize the profiler using a string from a location string.
+     *
+     * @param locationSearch (string)
+     *        The location string to initialize with.
+     */
+    initFromURLQueryParams(locationSearch) {
+      this.initFromObject(searchToObject(locationSearch));
+    },
+
+    /**
+     * A Talos test is about to start. Note that the SPS profiler will be
+     * paused immediately after starting and that resume() should be called
+     * in order to collect samples.
+     *
+     * @param testName (string)
+     *        The name of the test to use in Profiler markers.
+     */
+    beginTest(testName) {
+      if (initted) {
+        currentTest = testName;
+        TalosPowers.profilerBegin({ entries, interval, threadsArray });
+      } else {
+        let msg = "You should not call beginTest without having first " +
+                  "initted the Profiler"
+        console.error(msg);
+      }
+    },
+
+    /**
+     * A Talos test has finished. This will stop the SPS profiler from sampling,
+     * and return a Promise that resolves once the Profiler has finished dumping
+     * the multi-process profile to disk.
+     *
+     * @returns Promise
+     *          Resolves once the profile has been dumped to disk. The test should
+     *          not try to quit the browser until this has resolved.
+     */
+    finishTest() {
+      if (initted) {
+        let profileFile = profileDir + "/" + currentTest + ".sps";
+        return TalosPowers.profilerFinish(profileFile);
+      } else {
+        let msg = "You should not call finishTest without having first " +
+                  "initted the Profiler";
+        console.error(msg);
+        return Promise.reject(msg);
+      }
+    },
+
+    /**
+     * A start-up test has finished. Callers don't need to run beginTest or
+     * finishTest, but should pause the sampler as soon as possible, and call
+     * this function to dump the profile.
+     *
+     * @returns Promise
+     *          Resolves once the profile has been dumped to disk. The test should
+     *          not try to quit the browser until this has resolved.
+     */
+    finishStartupProfiling() {
+      if (initted) {
+        let profileFile = profileDir + "/startup.sps";
+        return TalosPowers.profilerFinish(profileFile);
+      }
+      return Promise.resolve();
+    },
+
+    /**
+     * Resumes the SPS profiler sampler. Can also simultaneously set a marker.
+     *
+     * @returns Promise
+     *          Resolves once the SPS profiler has resumed.
+     */
+    resume(marker="") {
+      if (initted) {
+        TalosPowers.profilerResume(marker);
+      }
+    },
+
+    /**
+     * Pauses the SPS profiler sampler. Can also simultaneously set a marker.
+     *
+     * @returns Promise
+     *          Resolves once the SPS profiler has paused.
+     */
+    pause(marker="") {
+      if (initted) {
+        TalosPowers.profilerPause(marker);
+      }
+    },
+
+    /**
+     * Adds a marker to the profile.
+     *
+     * @returns Promise
+     *          Resolves once the marker has been set.
+     */
+    mark(marker) {
+      if (initted) {
+        // If marker is omitted, just use the test name
+        if (!marker) {
+          marker = currentTest;
+        }
+
+        TalosPowers.profilerMarker(marker);
+      }
+    },
+  };
+})();
\ No newline at end of file