perfutils.js (3490B)
1 "use strict"; 2 3 const formatNumber = new Intl.NumberFormat("en-US", { 4 maximumSignificantDigits: 4, 5 }).format; 6 7 /** 8 * Given a map from test names to arrays of results, report perfherder metrics 9 * and log full results. 10 */ 11 function reportMetrics(journal) { 12 let metrics = {}; 13 let text = "\nResults (ms)\n"; 14 15 const names = Object.keys(journal); 16 const prefixLen = 1 + Math.max(...names.map(str => str.length)); 17 18 for (const name in journal) { 19 const med = median(journal[name]); 20 text += (name + ":").padEnd(prefixLen, " ") + stringify(journal[name]); 21 text += " median " + formatNumber(med) + "\n"; 22 metrics[name] = med; 23 } 24 25 dump(text); 26 info("perfMetrics", JSON.stringify(metrics)); 27 } 28 29 function median(arr) { 30 arr = [...arr].sort((a, b) => a - b); 31 const mid = Math.floor(arr.length / 2); 32 33 if (arr.length % 2) { 34 return arr[mid]; 35 } 36 37 return (arr[mid - 1] + arr[mid]) / 2; 38 } 39 40 function stringify(arr) { 41 function pad(str) { 42 str = str.padStart(7, " "); 43 if (str[0] != " ") { 44 str = " " + str; 45 } 46 return str; 47 } 48 49 return arr.reduce((acc, elem) => acc + pad(formatNumber(elem)), ""); 50 } 51 52 async function startProfiler() { 53 let script = SpecialPowers.loadChromeScript(async () => { 54 // See profiler doc via: $ MOZ_PROFILER_HELP=1 ./mach run 55 const settings = { 56 features: ["nomarkerstacks", "nostacksampling"], 57 threads: ["GeckoMain", "IPDL Background"], 58 }; 59 60 await Services.profiler.StartProfiler( 61 settings.entries, 62 settings.interval, 63 settings.features, 64 settings.threads 65 ); 66 67 sendAsyncMessage("started"); 68 }); 69 70 await script.promiseOneMessage("started"); 71 script.destroy(); 72 } 73 74 /** 75 * Returns profiler data 76 * https://github.com/firefox-devtools/profiler/blob/main/docs-developer/gecko-profile-format.md 77 */ 78 async function stopProfiler() { 79 let script = SpecialPowers.loadChromeScript(async () => { 80 await Services.profiler.Pause(); 81 const profileData = await Services.profiler.getProfileDataAsync(); 82 await Services.profiler.StopProfiler(); 83 sendAsyncMessage("done", profileData); 84 }); 85 86 const profile = await script.promiseOneMessage("done"); 87 script.destroy(); 88 return profile; 89 } 90 91 /** 92 * Look through profiler results for markers with name in names. 93 * Return the cumulative duration in ms. 94 */ 95 function inspectProfile(pdata, names) { 96 let unseen = new Set(names); 97 let duration = inspectProfileInternal(pdata, new Set(unseen), unseen); 98 // Error if we fail to see each name at least once 99 is( 100 unseen.size, 101 0, 102 `${unseen.size} missing markers ` + 103 [...unseen].join(", ") + 104 " (If this fails, check threads of interest in settings.threads)" 105 ); 106 return duration; 107 } 108 109 function inspectProfileInternal(pdata, names, unseen) { 110 let duration = 0; 111 112 for (let thread of pdata.threads) { 113 const nameIdx = thread.markers.schema.name; 114 const startTimeIdx = thread.markers.schema.startTime; 115 const endTimeIdx = thread.markers.schema.endTime; 116 117 for (let m of thread.markers.data) { 118 let markerName = thread.stringTable[m[nameIdx]]; 119 120 if (names.has(markerName)) { 121 let d = m[endTimeIdx] - m[startTimeIdx]; 122 duration += d; 123 info(`marker ${markerName}: ${formatNumber(d)} ms`); 124 unseen.delete(markerName); 125 } 126 } 127 } 128 129 for (let process of pdata.processes) { 130 // Look for markers in child processes 131 duration += inspectProfileInternal(process, names, unseen); 132 } 133 134 return duration; 135 }