summaryrefslogtreecommitdiffstats
path: root/tools/profiler/tests/browser/head.js
diff options
context:
space:
mode:
Diffstat (limited to 'tools/profiler/tests/browser/head.js')
-rw-r--r--tools/profiler/tests/browser/head.js159
1 files changed, 159 insertions, 0 deletions
diff --git a/tools/profiler/tests/browser/head.js b/tools/profiler/tests/browser/head.js
new file mode 100644
index 0000000000..ef0e3128c0
--- /dev/null
+++ b/tools/profiler/tests/browser/head.js
@@ -0,0 +1,159 @@
+/* import-globals-from ../shared-head.js */
+
+Services.scriptloader.loadSubScript(
+ "chrome://mochitests/content/browser/tools/profiler/tests/browser/shared-head.js",
+ this
+);
+
+const BASE_URL = "http://example.com/browser/tools/profiler/tests/browser/";
+const BASE_URL_HTTPS =
+ "https://example.com/browser/tools/profiler/tests/browser/";
+
+registerCleanupFunction(async () => {
+ if (Services.profiler.IsActive()) {
+ info(
+ "The profiler was found to still be running at the end of the test, which means that some error likely occured. Let's stop it to prevent issues with following tests!"
+ );
+ await Services.profiler.StopProfiler();
+ }
+});
+
+/**
+ * This is a helper function that will stop the profiler and returns the main
+ * threads for the parent process and the content process with PID contentPid.
+ * This happens immediately, without waiting for any sampling to happen or
+ * finish. Use waitSamplingAndStopProfilerAndGetThreads below instead to wait
+ * for samples before stopping.
+ * This returns also the full profile in case the caller wants more information.
+ *
+ * @param {number} contentPid
+ * @returns {Promise<{profile, parentThread, contentProcess, contentThread}>}
+ */
+async function stopProfilerNowAndGetThreads(contentPid) {
+ const profile = await stopNowAndGetProfile();
+
+ const parentThread = profile.threads[0];
+ const contentProcess = profile.processes.find(
+ p => p.threads[0].pid == contentPid
+ );
+ if (!contentProcess) {
+ throw new Error(
+ `Could not find the content process with given pid: ${contentPid}`
+ );
+ }
+
+ if (!parentThread) {
+ throw new Error("The parent thread was not found in the profile.");
+ }
+
+ const contentThread = contentProcess.threads[0];
+ if (!contentThread) {
+ throw new Error("The content thread was not found in the profile.");
+ }
+
+ return { profile, parentThread, contentProcess, contentThread };
+}
+
+/**
+ * This is a helper function that will stop the profiler and returns the main
+ * threads for the parent process and the content process with PID contentPid.
+ * As opposed to stopProfilerNowAndGetThreads (with "Now") above, the profiler
+ * in that PID will not stop until there is at least one periodic sample taken.
+ *
+ * @param {number} contentPid
+ * @returns {Promise<{profile, parentThread, contentProcess, contentThread}>}
+ */
+async function waitSamplingAndStopProfilerAndGetThreads(contentPid) {
+ await Services.profiler.waitOnePeriodicSampling();
+
+ return stopProfilerNowAndGetThreads(contentPid);
+}
+
+/** This tries to find the service worker thread by targeting a very specific
+ * UserTiming marker. Indeed we use performance.mark to add this marker from the
+ * service worker's events.
+ * Then from this thread we get its parent thread. Indeed the parent thread is
+ * where all network stuff happens, so this is useful for network marker tests.
+ *
+ * @param {Object} profile
+ * @returns {{ serviceWorkerThread: Object, serviceWorkerParentThread: Object }} the found threads
+ */
+function findServiceWorkerThreads(profile) {
+ const allThreads = [
+ profile.threads,
+ ...profile.processes.map(process => process.threads),
+ ].flat();
+
+ const serviceWorkerThread = allThreads.find(
+ ({ processType, markers }) =>
+ processType === "tab" &&
+ markers.data.some(markerTuple => {
+ const data = markerTuple[markers.schema.data];
+ return (
+ data &&
+ data.type === "UserTiming" &&
+ data.name === "__serviceworker_event"
+ );
+ })
+ );
+
+ if (!serviceWorkerThread) {
+ info(
+ "We couldn't find a service worker thread. Here are all the threads in this profile:"
+ );
+ allThreads.forEach(logInformationForThread.bind(null, ""));
+ return null;
+ }
+
+ const serviceWorkerParentThread = allThreads.find(
+ ({ name, pid }) => pid === serviceWorkerThread.pid && name === "GeckoMain"
+ );
+
+ if (!serviceWorkerParentThread) {
+ info(
+ `We couldn't find a parent thread for the service worker thread (pid: ${serviceWorkerThread.pid}, tid: ${serviceWorkerThread.tid}).`
+ );
+ info("Here are all the threads in this profile:");
+ allThreads.forEach(logInformationForThread.bind(null, ""));
+
+ // Let's write the profile on disk if MOZ_UPLOAD_DIR is present
+ const path = Services.env.get("MOZ_UPLOAD_DIR");
+ if (path) {
+ const profileName = `profile_${Date.now()}.json`;
+ const profilePath = PathUtils.join(path, profileName);
+ info(
+ `We wrote down the profile on disk as an artifact, with name ${profileName}.`
+ );
+ // This function returns a Promise, but we're not waiting on it because
+ // we're in a synchronous function. Hopefully writing will be finished
+ // when the process ends.
+ IOUtils.writeJSON(profilePath, profile).catch(err =>
+ console.error("An error happened when writing the profile on disk", err)
+ );
+ }
+ throw new Error(
+ "We couldn't find a parent thread for the service worker thread. Please read logs to find more information."
+ );
+ }
+
+ return { serviceWorkerThread, serviceWorkerParentThread };
+}
+
+/**
+ * This logs some basic information about the passed thread.
+ *
+ * @param {string} prefix
+ * @param {Object} thread
+ */
+function logInformationForThread(prefix, thread) {
+ if (!thread) {
+ info(prefix + ": thread is null or undefined.");
+ return;
+ }
+
+ const { name, pid, tid, processName, processType } = thread;
+ info(
+ `${prefix}: ` +
+ `name(${name}) pid(${pid}) tid(${tid}) processName(${processName}) processType(${processType})`
+ );
+}