1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
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})`
);
}
|