160 lines
5.4 KiB
JavaScript
160 lines
5.4 KiB
JavaScript
/* 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})`
|
|
);
|
|
}
|