"use strict"; const formatNumber = new Intl.NumberFormat("en-US", { maximumSignificantDigits: 4, }).format; /** * Given a map from test names to arrays of results, report perfherder metrics * and log full results. */ function reportMetrics(journal) { let metrics = {}; let text = "\nResults (ms)\n"; const names = Object.keys(journal); const prefixLen = 1 + Math.max(...names.map(str => str.length)); for (const name in journal) { const med = median(journal[name]); text += (name + ":").padEnd(prefixLen, " ") + stringify(journal[name]); text += " median " + formatNumber(med) + "\n"; metrics[name] = med; } dump(text); info("perfMetrics", JSON.stringify(metrics)); } function median(arr) { arr = [...arr].sort((a, b) => a - b); const mid = Math.floor(arr.length / 2); if (arr.length % 2) { return arr[mid]; } return (arr[mid - 1] + arr[mid]) / 2; } function stringify(arr) { function pad(str) { str = str.padStart(7, " "); if (str[0] != " ") { str = " " + str; } return str; } return arr.reduce((acc, elem) => acc + pad(formatNumber(elem)), ""); } async function startProfiler() { let script = SpecialPowers.loadChromeScript(async () => { // See profiler doc via: $ MOZ_PROFILER_HELP=1 ./mach run const settings = { features: ["nomarkerstacks", "nostacksampling"], threads: ["GeckoMain", "IPDL Background"], }; await Services.profiler.StartProfiler( settings.entries, settings.interval, settings.features, settings.threads ); sendAsyncMessage("started"); }); await script.promiseOneMessage("started"); script.destroy(); } /** * Returns profiler data * https://github.com/firefox-devtools/profiler/blob/main/docs-developer/gecko-profile-format.md */ async function stopProfiler() { let script = SpecialPowers.loadChromeScript(async () => { await Services.profiler.Pause(); const profileData = await Services.profiler.getProfileDataAsync(); await Services.profiler.StopProfiler(); sendAsyncMessage("done", profileData); }); const profile = await script.promiseOneMessage("done"); script.destroy(); return profile; } /** * Look through profiler results for markers with name in names. * Return the cumulative duration in ms. */ function inspectProfile(pdata, names) { let unseen = new Set(names); let duration = inspectProfileInternal(pdata, new Set(unseen), unseen); // Error if we fail to see each name at least once is( unseen.size, 0, `${unseen.size} missing markers ` + [...unseen].join(", ") + " (If this fails, check threads of interest in settings.threads)" ); return duration; } function inspectProfileInternal(pdata, names, unseen) { let duration = 0; for (let thread of pdata.threads) { const nameIdx = thread.markers.schema.name; const startTimeIdx = thread.markers.schema.startTime; const endTimeIdx = thread.markers.schema.endTime; for (let m of thread.markers.data) { let markerName = thread.stringTable[m[nameIdx]]; if (names.has(markerName)) { let d = m[endTimeIdx] - m[startTimeIdx]; duration += d; info(`marker ${markerName}: ${formatNumber(d)} ms`); unseen.delete(markerName); } } } for (let process of pdata.processes) { // Look for markers in child processes duration += inspectProfileInternal(process, names, unseen); } return duration; }