Browse Source

Merge pull request #5870 from saulshanabrook/lighthouse

Profile with lighthouse
Steven Silvester 5 years ago
parent
commit
10ea36fb59

+ 190 - 0
CONTRIBUTING.md

@@ -216,6 +216,196 @@ directly. You can also use `jlpm test --testNamePattern=<regex>` to specify spec
 suite names, and `jlpm test --testPathPattern=<regex>` to specify specific test module names. In order to watch the code, add a `debugger` line in your code and run `jlpm watch`. This will start a node V8 debugger, which can be debugged
 in Chrome by browsing to `chrome://inspect/` and launching the remote session.
 
+## Performance Testing
+
+If you are making a change that might affect how long it takes to load JupyterLab in the browser,
+we recommend doing some performance testing using [Lighthouse](https://github.com/GoogleChrome/lighthouse).
+It let's you easily compute a number of metrics, like page load time, for the site.
+
+To use it, first build JupyterLab in dev mode:
+
+```bash
+jlpm run build:dev
+```
+
+Then, start JupyterLab using the dev build:
+
+```bash
+jupyter lab --dev --NotebookApp.token=''  --no-browser
+```
+
+Now run Lighthouse against this local server and show the results:
+
+```bash
+jlpm run lighthouse --view
+```
+
+![](./docs/source/images/lighthouse.png)
+
+### Using throttling
+
+Lighthouse recommends using the system level [`comcast`](https://github.com/tylertreat/comcast) tool to throttle your network connection
+and emulate different scenarios. To use it, first install that tool using `go`:
+
+```bash
+go get github.com/tylertreat/comcast
+```
+
+Then, before you run Lighthouse, enable the throttling (this requires sudo):
+
+```bash
+jlpm run lighthouse:throttling:start
+```
+
+This enables the "WIFI (good)" preset of comcast, which should emulate
+loading JupyterLab over a local network.
+
+Then run the lighthouse tests:
+
+```bash
+jlpm run lighthouse [...]
+```
+
+Then disable the throttling after you are done:
+
+```bash
+jlpm run lighthouse:throttling:stop
+```
+
+### Comparing results
+
+Performance results are usually only useful in comparison to other results.
+For that reason, we have included a comparison script that can take two
+lighthouse results and show the changes between them.
+
+Let's say we want to compare the results of the production build of JupyterLab with the normal build. The production build minifies all the JavaScript, so should load a bit faster.
+
+First, we build JupyterLab normally, start it up, profile it and save the results:
+
+```bash
+jlpm build:dev
+jupyter lab --dev --NotebookApp.token='' --no-browser
+
+# in new window
+jlpm run lighthouse --output json --output-path normal.json
+```
+
+Then rebuild with the production build and retest:
+
+```bash
+jlpm run build:dev:prod
+jupyter lab --dev --NotebookApp.token='' --no-browser
+
+# in new window
+jlpm run lighthouse --output json --output-path prod.json
+```
+
+Now we can use compare the two outputs:
+
+```bash
+jlpm run lighthouse:compare normal.json prod.json
+```
+
+This gives us a report of the relative differences between the audits in the two reports:
+
+> `normal.json` -> `prod.json`
+>
+> **First Contentful Paint**
+>
+> - -62% Δ
+> - 1.9 s -> 0.7 s
+> - First Contentful Paint marks the time at which the first text or image is painted. [Learn more](https://developers.google.com/web/tools/lighthouse/audits/first-contentful-paint).
+>
+> **First Meaningful Paint**
+>
+> - -50% Δ
+> - 2.5 s -> 1.3 s
+> - First Meaningful Paint measures when the primary content of a page is visible. [Learn more](https://developers.google.com/web/tools/lighthouse/audits/first-meaningful-paint).
+>
+> **Speed Index**
+>
+> - -48% Δ
+> - 2.6 s -> 1.3 s
+> - Speed Index shows how quickly the contents of a page are visibly populated. [Learn more](https://developers.google.com/web/tools/lighthouse/audits/speed-index).
+>
+> **Estimated Input Latency**
+>
+> - 0% Δ
+> - 20 ms -> 20 ms
+> - Estimated Input Latency is an estimate of how long your app takes to respond to user input, in milliseconds, during the busiest 5s window of page load. If your latency is higher than 50 ms, users may perceive your app as laggy. [Learn more](https://developers.google.com/web/tools/lighthouse/audits/estimated-input-latency).
+>
+> **Max Potential First Input Delay**
+>
+> - 9% Δ
+> - 200 ms -> 210 ms
+> - The maximum potential First Input Delay that your users could experience is the duration, in milliseconds, of the longest task. [Learn more](https://developers.google.com/web/updates/2018/05/first-input-delay).
+>
+> **First CPU Idle**
+>
+> - -50% Δ
+> - 2.5 s -> 1.3 s
+> - First CPU Idle marks the first time at which the page's main thread is quiet enough to handle input. [Learn more](https://developers.google.com/web/tools/lighthouse/audits/first-interactive).
+>
+> **Time to Interactive**
+>
+> - -52% Δ
+> - 2.5 s -> 1.2 s
+> - Time to interactive is the amount of time it takes for the page to become fully interactive. [Learn more](https://developers.google.com/web/tools/lighthouse/audits/consistently-interactive).
+>
+> **Avoid multiple page redirects**
+>
+> - -2% Δ
+> - Potential savings of 10 ms -> Potential savings of 10 ms
+> - Redirects introduce additional delays before the page can be loaded. [Learn more](https://developers.google.com/web/tools/lighthouse/audits/redirects).
+>
+> **Minimize main-thread work**
+>
+> - -54% Δ
+> - 2.1 s -> 1.0 s
+> - Consider reducing the time spent parsing, compiling and executing JS. You may find delivering smaller JS payloads helps with this.
+>
+> **JavaScript execution time**
+>
+> - -49% Δ
+> - 1.1 s -> 0.6 s
+> - Consider reducing the time spent parsing, compiling, and executing JS. You may find delivering smaller JS payloads helps with this. [Learn more](https://developers.google.com/web/tools/lighthouse/audits/bootup).
+>
+> **Preload key requests**
+>
+> - -100% Δ
+> - Potential savings of 240 ms ->
+> - Consider using <link rel=preload> to prioritize fetching resources that are currently requested later in page load. [Learn more](https://developers.google.com/web/tools/lighthouse/audits/preload).
+>
+> **Uses efficient cache policy on static assets**
+>
+> - 0% Δ
+> - 1 resource found -> 1 resource found
+> - A long cache lifetime can speed up repeat visits to your page. [Learn more](https://developers.google.com/web/tools/lighthouse/audits/cache-policy).
+>
+> **Avoid enormous network payloads**
+>
+> - -86% Δ
+> - Total size was 30,131 KB -> Total size was 4,294 KB
+> - Large network payloads cost users real money and are highly correlated with long load times. [Learn more](https://developers.google.com/web/tools/lighthouse/audits/network-payloads).
+>
+> **Minify JavaScript**
+>
+> - -100% Δ
+> - Potential savings of 23,041 KB ->
+> - Minifying JavaScript files can reduce payload sizes and script parse time. [Learn more](https://developers.google.com/speed/docs/insights/MinifyResources).
+>
+> **Enable text compression**
+>
+> - -86% Δ
+> - Potential savings of 23,088 KB -> Potential savings of 3,112 KB
+> - Text-based resources should be served with compression (gzip, deflate or brotli) to minimize total network bytes. [Learn more](https://developers.google.com/web/tools/lighthouse/audits/text-compression).
+>
+> **Avoid an excessive DOM size**
+>
+> - 0% Δ
+> - 1,268 elements -> 1,268 elements
+> - Browser engineers recommend pages contain fewer than ~1,500 DOM elements. The sweet spot is a tree depth < 32 elements and fewer than 60 children/parent element. A large DOM can increase memory usage, cause longer [style calculations](https://developers.google.com/web/fundamentals/performance/rendering/reduce-the-scope-and-complexity-of-style-calculations), and produce costly [layout reflows](https://developers.google.com/speed/articles/reflow). [Learn more](https://developers.google.com/web/tools/lighthouse/audits/dom-size).
+
 ### Build and run the stand-alone examples
 
 To install and build the examples in the `examples` directory:

+ 1 - 0
buildutils/src/ensure-repo.ts

@@ -20,6 +20,7 @@ type Dict<T> = { [key: string]: T };
 // Data to ignore.
 let MISSING: Dict<string[]> = {
   '@jupyterlab/buildutils': ['path'],
+  '@jupyterlab/testutils': ['fs'],
   '@jupyterlab/vega4-extension': ['vega-embed'],
   '@jupyterlab/vega5-extension': ['vega-embed']
 };

BIN
docs/source/images/lighthouse.png


+ 4 - 0
package.json

@@ -57,6 +57,10 @@
     "get:dependency": "node buildutils/lib/get-dependency.js",
     "postinstall": "node scripts/ensure-buildutils.js",
     "integrity": "node buildutils/lib/ensure-repo.js",
+    "lighthouse": "lighthouse http://localhost:8888/ --chrome-flags='--headless' --emulated-form-factor=none --throttling-method=provided --only-categories performance",
+    "lighthouse:compare": "node testutils/lib/compare-lighthouse.js",
+    "lighthouse:throttling:start": "comcast --latency=40 --target-bw=30000 --packet-loss=0.2%",
+    "lighthouse:throttling:stop": "comcast --stop",
     "lint": "jlpm && jlpm run prettier && jlpm run eslint && jlpm run tslint",
     "lint:check": "jlpm run prettier:check && jlpm run eslint:check && jlpm run tslint:check",
     "patch:release": "node buildutils/lib/patch-release.js",

+ 1 - 0
testutils/package.json

@@ -50,6 +50,7 @@
   },
   "devDependencies": {
     "@types/node-fetch": "^2.3.4",
+    "lighthouse": "5.1.0",
     "typescript": "~3.5.1"
   }
 }

+ 49 - 0
testutils/src/compare-lighthouse.ts

@@ -0,0 +1,49 @@
+/**
+ * Compares two files lighthouse outputs, listing changes between the numeric audits.
+ *
+ * Outputs in Markdown for easy posting in Github.
+ */
+import { readFileSync } from 'fs';
+
+const firstFilePath = process.argv[2];
+const secondFilePath = process.argv[3];
+
+console.log(`\`${firstFilePath}\` -> \`${secondFilePath}\`\n\n`);
+interface IOutput {
+  audits: {
+    [name: string]: {
+      id: string;
+      title: string;
+      description: string;
+      scoreDisplayMode: string;
+      displayValue: string;
+      numericValue: number;
+    };
+  };
+}
+
+const first: IOutput = JSON.parse(readFileSync(firstFilePath).toString());
+const second: IOutput = JSON.parse(readFileSync(secondFilePath).toString());
+
+for (const auditName in first.audits) {
+  const firstAudit = first.audits[auditName];
+
+  // only compare numeric audits
+  if (firstAudit.scoreDisplayMode !== 'numeric') {
+    continue;
+  }
+  const secondAudit = second.audits[auditName];
+  const percentChange =
+    ((secondAudit.numericValue - firstAudit.numericValue) /
+      firstAudit.numericValue) *
+    100;
+
+  if (isNaN(percentChange)) {
+    continue;
+  }
+  console.log(
+    `**${firstAudit.title}**\n* ${percentChange.toFixed(0)}% Δ\n* ${
+      firstAudit.displayValue
+    } -> ${secondAudit.displayValue}\n* ${firstAudit.description}\n`
+  );
+}

File diff suppressed because it is too large
+ 491 - 15
yarn.lock


Some files were not shown because too many files changed in this diff