tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

test_render_perf.html (7342B)


      1 <!DOCTYPE HTML>
      2 <html lang="en">
      3 <head>
      4  <meta charset="utf8">
      5  <title>Test for getRepeatId()</title>
      6  <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
      7  <script type="application/javascript" src="head.js"></script>
      8  <!-- Any copyright is dedicated to the Public Domain.
      9     - http://creativecommons.org/publicdomain/zero/1.0/ -->
     10 </head>
     11 <body>
     12 <p>Test for render perf</p>
     13 <div id="output"></div>
     14 
     15 <script type="text/javascript">
     16 "use strict";
     17 
     18 // To analyze the profile results:
     19 // > ./mach mochitest test_render_perf.html
     20 // Then open https://profiler.firefox.com and drag the json file printed at the end of
     21 // this test
     22 
     23 const NUM_MESSAGES = 4000;
     24 const NUM_STREAMING = 100;
     25 const {FileUtils} = ChromeUtils.importESModule(
     26  "resource://gre/modules/FileUtils.sys.mjs"
     27 );
     28 Services.prefs.setIntPref("devtools.hud.loglimit", NUM_MESSAGES);
     29 const WebConsoleWrapper = browserRequire("devtools/client/webconsole/webconsole-wrapper");
     30 const actions =
     31  browserRequire("devtools/client/webconsole/actions/index");
     32 const EventEmitter = browserRequire("devtools/shared/event-emitter");
     33 const testPackets = Array.from({length: NUM_MESSAGES}).map((el, id) => ({
     34  "from": "server1.conn4.child1/consoleActor2",
     35  "type": "consoleAPICall",
     36  "message": {
     37    "arguments": [
     38      "foobar",
     39      `${id % 2 === 0 ? "Even" : "Odd"} text`,
     40      id,
     41    ],
     42    "columnNumber": 1,
     43    "counter": null,
     44    "filename": "file:///test.html",
     45    "functionName": "",
     46    "groupName": "",
     47    "level": "log",
     48    "lineNumber": 1,
     49    "private": false,
     50    "styles": [],
     51    "timeStamp": 1455064271115 + id,
     52    "timer": null,
     53    "workerType": "none",
     54    "category": "webdev",
     55  },
     56 }));
     57 const lastPacket = testPackets.pop();
     58 
     59 function startMeasure(label) {
     60  const startLabel = label + "start";
     61  performance.mark(startLabel);
     62  return {
     63    stop: (clear = true) => {
     64      performance.measure(label, startLabel);
     65      const entries = performance.getEntriesByName(label);
     66      if (clear) {
     67        performance.clearMeasures(label);
     68      }
     69      return entries[entries.length - 1];
     70    },
     71  };
     72 }
     73 
     74 function wait(time) {
     75  return new Promise(resolve => setTimeout(resolve, time));
     76 }
     77 
     78 async function addAllMessages(wrapper) {
     79  testPackets.forEach((packet) => wrapper.dispatchMessageAdd(packet));
     80  // Only wait for the last packet to minimize work.
     81  await wrapper.dispatchMessageAdd(lastPacket, true);
     82  await new Promise(resolve => requestAnimationFrame(resolve));
     83 }
     84 
     85 async function addMessage(wrapper, message) {
     86  await wrapper.dispatchMessageAdd(message, true);
     87 }
     88 
     89 function getTimes(times) {
     90  times = times.sort();
     91  const totalTime = times.reduce((sum, t) => sum + t);
     92  const avg = totalTime / times.length;
     93  const median = times.length % 2 !== 0
     94    ? times[Math.floor(times.length / 2)]
     95    : (times[(times.length / 2) - 1] + times[times.length / 2]) / 2;
     96  return {avg, median};
     97 }
     98 
     99 async function clearMessages(wrapper) {
    100  wrapper.dispatchMessagesClear();
    101  await new Promise(resolve => requestAnimationFrame(resolve));
    102 }
    103 
    104 async function testBulkLogging(wrapper) {
    105  await clearMessages(wrapper);
    106  const bulkTimes = [];
    107  const iterations = 5;
    108  for (let i = 0; i < iterations; i++) {
    109    const measure = startMeasure("bulk log");
    110    await addAllMessages(wrapper);
    111    const {duration} = measure.stop();
    112 
    113    info(`took ${duration} ms to render bulk messages (iteration ${i})`);
    114    bulkTimes.push(duration);
    115 
    116    // Do not clear the store on last iteration so the next test can use the messages.
    117    if (i !== iterations - 1) {
    118      // Wait before clearing messages so those events are more spotable on the profile.
    119      await wait(500);
    120      await clearMessages(wrapper);
    121      await wait(500);
    122    }
    123  }
    124  const {avg, median} = getTimes(bulkTimes);
    125 
    126  info(`BULK: On average, it took ${avg} ms (median ${median} ms) ` +
    127       `to render ${NUM_MESSAGES} messages`);
    128 }
    129 
    130 async function testFiltering(wrapper) {
    131  const measureFilter = startMeasure("filtering");
    132 
    133  const measureFilterOff = startMeasure("filtering off");
    134  await wrapper.getStore().dispatch(actions.filterToggle("log"));
    135  const measureFilterOffEntry = measureFilterOff.stop();
    136  info(`Filter toggle time (off): ${measureFilterOffEntry.duration}`);
    137 
    138  const measureFilterOn = startMeasure("filtering on");
    139  await wrapper.getStore().dispatch(actions.filterToggle("log"));
    140  const measureFilterOnEntry = measureFilterOn.stop();
    141  info(`Filter toggle time (on): ${measureFilterOnEntry.duration}`);
    142  measureFilter.stop();
    143 }
    144 
    145 async function testStreamLogging(wrapper, clear = true) {
    146  const streamMeasureLabel = "stream" + (clear === false ? " and prune" : "");
    147  const streamMeasure = startMeasure(streamMeasureLabel);
    148  if (clear === true) {
    149    await clearMessages(wrapper);
    150  }
    151 
    152  const measureLabel = "stream - add single message";
    153  for (let i = 0; i < NUM_STREAMING; i++) {
    154    const measure = startMeasure(measureLabel);
    155    await addMessage(wrapper, testPackets[i]);
    156    measure.stop(false);
    157    await new Promise(resolve => setTimeout(resolve, 100));
    158  }
    159 
    160  const streamTimes = performance.getEntriesByName(measureLabel)
    161    .map(entry => entry.duration);
    162  performance.clearMeasures(measureLabel);
    163  const { avg, median } = getTimes(streamTimes);
    164  info(`STREAMING${clear === false ? " AND PRUNING" : ""}: On average, ` +
    165    `it took ${avg} ms (median ${median} ms) for each message`);
    166  streamMeasure.stop();
    167 }
    168 
    169 window.onload = async function() {
    170  // This test does costly work multiple times to have better performance data.
    171  // It doesn't run in automation
    172  SimpleTest.requestLongerTimeout(3);
    173 
    174  const wrapper = new WebConsoleWrapper(
    175    document.getElementById("output"),
    176    {hud: EventEmitter.decorate({proxy: {}}), focus: () => {}},
    177    {},
    178    null,
    179    document,
    180  );
    181  wrapper.init();
    182 
    183  // From https://github.com/firefox-devtools/profiler/blob/b73eb73df04c7df51464fa50eeadef3dc7f5d4e2/docs/gecko-profile-format.md#L21
    184  const settings = {
    185    entries: 100000000,
    186    interval: 1,
    187    features: ["js"],
    188    threads: ["GeckoMain"],
    189  };
    190  Services.profiler.StartProfiler(
    191    settings.entries,
    192    settings.interval,
    193    settings.features,
    194    settings.threads,
    195  );
    196  info("Profiler has started");
    197 
    198  await wait(500);
    199 
    200  await testBulkLogging(wrapper);
    201 
    202  await wait(500);
    203 
    204  await testFiltering(wrapper);
    205 
    206  await wait(500);
    207 
    208  // first pass, without clearing the store.
    209  await testStreamLogging(wrapper, false);
    210 
    211  await wait(500);
    212  // second pass, with an empty store.
    213  await testStreamLogging(wrapper, true);
    214 
    215  ok(true, "Tests finished");
    216 
    217  const file = new FileUtils.File(PathUtils.join(PathUtils.tempDir, `test_render_perf_${Date.now()}.json`));
    218  Services.profiler.dumpProfileToFile(file.path);
    219  Services.profiler.StopProfiler();
    220 
    221  info(`
    222 
    223 SAVING PROFILE: ${file.path}
    224 
    225 To upload the profile, run the following commands:
    226 
    227  gzip ${file.path}
    228  curl -O https://raw.githubusercontent.com/firefox-devtools/profiler-server/master/tools/decode_jwt_payload.py
    229  curl 'https://api.profiler.firefox.com/compressed-store' --compressed --data-binary @${file.path}.gz --header 'Accept: application/vnd.firefox-profiler+json;version=1' \\
    230    | python3 decode_jwt_payload.py \\
    231    | awk '{print "Hosted at: https://profiler.firefox.com/public/"$1}'
    232 
    233 
    234 `);
    235 
    236  SimpleTest.finish();
    237 };
    238 </script>
    239 </body>
    240 </html>