Repository: GoogleChrome/lighthouse
Pull request #7052 (2019-01-17 21:24:34)

core(diagnostics): add diagnostic audits
**Summary**
Collect page vital stats into a single audit. Also adds two new user-facing audits for server latency by origin and RTT by origin.

**Related Issues/PRs**
#6775 


benschwarz: These are going to be hugely valuable! Great work so far @patrickhulce 
patrickhulce: > can we split this out?

sure

> I'm not sure why these are in this PR instead of their own :)

because those are also critical page vitals that I've been using to debug variance
patrickhulce: Can this get some priority when reviewing as folks come back from NY? :)

This is blocking my further efforts to exposure variance, so I'd love to get it merged ASAP
patrickhulce: > Proposed compromise: keep these audits in the PR but don't expose as user-facing. That can be done in a followup.

Deal!

Patch:
diff --git a/lighthouse-core/audits/diagnostics.js b/lighthouse-core/audits/diagnostics.js
new file mode 100644
index 000000000..481bb1bfb
--- /dev/null
+++ b/lighthouse-core/audits/diagnostics.js
@@ -0,0 +1,75 @@
+/**
+ * @license Copyright 2019 Google Inc. All Rights Reserved.
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
+ * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.
+ */
+'use strict';
+
+const Audit = require('./audit.js');
+const MainThreadTasksComputed = require('../computed/main-thread-tasks.js');
+const NetworkRecordsComputed = require('../computed/network-records.js');
+const NetworkAnalysisComputed = require('../computed/network-analysis.js');
+const NetworkAnalyzer = require('../lib/dependency-graph/simulator/network-analyzer.js');
+
+class Diagnostics extends Audit {
+  /**
+   * @return {LH.Audit.Meta}
+   */
+  static get meta() {
+    return {
+      id: 'diagnostics',
+      scoreDisplayMode: Audit.SCORING_MODES.INFORMATIVE,
+      title: 'Diagnostics',
+      description: 'Collection of useful page vitals.',
+      requiredArtifacts: ['traces', 'devtoolsLogs'],
+    };
+  }
+
+  /**
+   * @param {LH.Artifacts} artifacts
+   * @param {LH.Audit.Context} context
+   * @return {Promise<LH.Audit.Product>}
+   */
+  static async audit(artifacts, context) {
+    const trace = artifacts.traces[Audit.DEFAULT_PASS];
+    const devtoolsLog = artifacts.devtoolsLogs[Audit.DEFAULT_PASS];
+    const tasks = await MainThreadTasksComputed.request(trace, context);
+    const records = await NetworkRecordsComputed.request(devtoolsLog, context);
+    const analysis = await NetworkAnalysisComputed.request(devtoolsLog, context);
+
+    const toplevelTasks = tasks.filter(t => !t.parent);
+    const mainDocumentTransferSize = NetworkAnalyzer.findMainDocument(records).transferSize;
+    const totalByteWeight = records.reduce((sum, r) => sum + (r.transferSize || 0), 0);
+    const totalTaskTime = toplevelTasks.reduce((sum, t) => sum + (t.duration || 0), 0);
+    const maxRtt = Math.max(...analysis.additionalRttByOrigin.values()) + analysis.rtt;
+    const maxServerLatency = Math.max(...analysis.serverResponseTimeByOrigin.values());
+
+    const diagnostics = {
+      numRequests: records.length,
+      numScripts: records.filter(r => r.resourceType === 'Script').length,
+      numStylesheets: records.filter(r => r.resourceType === 'Stylesheet').length,
+      numFonts: records.filter(r => r.resourceType === 'Font').length,
+      numTasks: toplevelTasks.length,
+      numTasksOver10ms: toplevelTasks.filter(t => t.duration > 10).length,
+      numTasksOver25ms: toplevelTasks.filter(t => t.duration > 25).length,
+      numTasksOver50ms: toplevelTasks.filter(t => t.duration > 50).length,
+      numTasksOver100ms: toplevelTasks.filter(t => t.duration > 100).length,
+      numTasksOver500ms: toplevelTasks.filter(t => t.duration > 500).length,
+      rtt: analysis.rtt,
+      throughput: analysis.throughput,
+      maxRtt,
+      maxServerLatency,
+      totalByteWeight,
+      totalTaskTime,
+      mainDocumentTransferSize,
+    };
+
+    return {
+      score: 1,
+      rawValue: 1,
+      details: {items: [diagnostics]},
+    };
+  }
+}
+
+module.exports = Diagnostics;
diff --git a/lighthouse-core/audits/main-thread-tasks.js b/lighthouse-core/audits/main-thread-tasks.js
new file mode 100644
index 000000000..8b044c20c
--- /dev/null
+++ b/lighthouse-core/audits/main-thread-tasks.js
@@ -0,0 +1,60 @@
+/**
+ * @license Copyright 2019 Google Inc. All Rights Reserved.
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
+ * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.
+ */
+'use strict';
+
+const Audit = require('./audit.js');
+const MainThreadTasksComputed = require('../computed/main-thread-tasks.js');
+
+class MainThreadTasks extends Audit {
+  /**
+   * @return {LH.Audit.Meta}
+   */
+  static get meta() {
+    return {
+      id: 'main-thread-tasks',
+      scoreDisplayMode: Audit.SCORING_MODES.INFORMATIVE,
+      title: 'Tasks',
+      description: 'Lists the toplevel main thread tasks that executed during page load.',
+      requiredArtifacts: ['traces'],
+    };
+  }
+
+  /**
+   * @param {LH.Artifacts} artifacts
+   * @param {LH.Audit.Context} context
+   * @return {Promise<LH.Audit.Product>}
+   */
+  static async audit(artifacts, context) {
+    const trace = artifacts.traces[Audit.DEFAULT_PASS];
+    const tasks = await MainThreadTasksComputed.request(trace, context);
+
+    const results = tasks
+      .filter(task => task.duration > 5 && task.parent)
+      .map(task => {
+        return {
+          type: task.group.id,
+          duration: task.duration,
+          startTime: task.startTime,
+        };
+      });
+
+    const headings = [
+      {key: 'type', itemType: 'text', text: 'Task Type'},
+      {key: 'startTime', itemType: 'ms', granularity: 1, text: 'Start Time'},
+      {key: 'duration', itemType: 'ms', granularity: 1, text: 'End Time'},
+    ];
+
+    const tableDetails = Audit.makeTableDetails(headings, results);
+
+    return {
+      score: 1,
+      rawValue: results.length,
+      details: tableDetails,
+    };
+  }
+}
+
+module.exports = MainThreadTasks;
diff --git a/lighthouse-core/audits/metrics.js b/lighthouse-core/audits/metrics.js
index 35ab49f60..06f22c2e1 100644
--- a/lighthouse-core/audits/metrics.js
+++ b/lighthouse-core/audits/metrics.js
@@ -39,13 +39,25 @@ class Metrics extends Audit {
     const devtoolsLog = artifacts.devtoolsLogs[Audit.DEFAULT_PASS];
     const metricComputationData = {trace, devtoolsLog, settings: context.settings};
 
+
+    /**
+     * @template TArtifacts
+     * @template TReturn
+     * @param {{request: (artifact: TArtifacts, context: LH.Audit.Context) => Promise<TReturn>}} Artifact
+     * @param {TArtifacts} artifact
+     * @return {Promise<TReturn|undefined>}
+     */
+    const requestOrUndefined = (Artifact, artifact) => {
+      return Artifact.request(artifact, context).catch(_ => undefined);
+    };
+
     const traceOfTab = await TraceOfTab.request(trace, context);
     const speedline = await Speedline.request(trace, context);
     const firstContentfulPaint = await FirstContentfulPaint.request(metricComputationData, context);
     const firstMeaningfulPaint = await FirstMeaningfulPaint.request(metricComputationData, context);
-    const firstCPUIdle = await FirstCPUIdle.request(metricComputationData, context);
-    const interactive = await Interactive.request(metricComputationData, context);
-    const speedIndex = await SpeedIndex.request(metricComputationData, context);
+    const firstCPUIdle = await requestOrUndefined(FirstCPUIdle, metricComputationData);
+    const interactive = await requestOrUndefined(Interactive, metricComputationData);
+    const speedIndex = await requestOrUndefined(SpeedIndex, metricComputationData);
     const estimatedInputLatency = await EstimatedInputLatency.request(metricComputationData, context); // eslint-disable-line max-len
 
     /** @type {UberMetricsItem} */
@@ -55,12 +67,12 @@ class Metrics extends Audit {
       firstContentfulPaintTs: firstContentfulPaint.timestamp,
       firstMeaningfulPaint: firstMeaningfulPaint.timing,
       firstMeaningfulPaintTs: firstMeaningfulPaint.timestamp,
-      firstCPUIdle: firstCPUIdle.timing,
-      firstCPUIdleTs: firstCPUIdle.timestamp,
-      interactive: interactive.timing,
-      interactiveTs: interactive.timestamp,
-      speedIndex: speedIndex.timing,
-      speedIndexTs: speedIndex.timestamp,
+      firstCPUIdle: firstCPUIdle && firstCPUIdle.timing,
+      firstCPUIdleTs: firstCPUIdle && firstCPUIdle.timestamp,
+      interactive: interactive && interactive.timing,
+      interactiveTs: interactive && interactive.timestamp,
+      speedIndex: speedIndex && speedIndex.timing,
+      speedIndexTs: speedIndex && speedIndex.timestamp,
       estimatedInputLatency: estimatedInputLatency.timing,
       estimatedInputLatencyTs: estimatedInputLatency.timestamp,
 
@@ -101,7 +113,7 @@ class Metrics extends Audit {
 
     return {
       score: 1,
-      rawValue: interactive.timing,
+      rawValue: (interactive && interactive.timing) || 0,
       details,
     };
   }
@@ -113,11 +125,11 @@ class Metrics extends Audit {
  * @property {number=} firstContentfulPaintTs
  * @property {number} firstMeaningfulPaint
  * @property {number=} firstMeaningfulPaintTs
- * @property {number} firstCPUIdle
+ * @property {number=} firstCPUIdle
  * @property {number=} firstCPUIdleTs
- * @property {number} interactive
+ * @property {number=} interactive
  * @property {number=} interactiveTs
- * @property {number} speedIndex
+ * @property {number=} speedIndex
  * @property {number=} speedIndexTs
  * @property {number} estimatedInputLatency
  * @property {number=} estimatedInputLatencyTs
diff --git a/lighthouse-core/audits/network-rtt.js b/lighthouse-core/audits/network-rtt.js
new file mode 100644
index 000000000..9afa8ce17
--- /dev/null
+++ b/lighthouse-core/audits/network-rtt.js
@@ -0,0 +1,79 @@
+/**
+ * @license Copyright 2019 Google Inc. All Rights Reserved.
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
+ * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.
+ */
+'use strict';
+
+const Audit = require('./audit.js');
+const i18n = require('../lib/i18n/i18n.js');
+const NetworkAnalysisComputed = require('../computed/network-analysis.js');
+
+const UIStrings = {
+  /** Descriptive title of a Lighthouse audit that tells the user the round trip times to each origin the page connected to. This is displayed in a list of audit titles that Lighthouse generates. */
+  title: 'Network Round Trip Times',
+  /** Description of a Lighthouse audit that tells the user that a high network round trip time (RTT) can effect their website's performance because the server is physically far away from them thus making the RTT high. This is displayed after a user expands the section to see more. No character length limits. 'Learn More' becomes link text to additional documentation. */
+  description: 'Network round trip times (RTT) have a large impact on performance. ' +
+    'If the RTT to an origin is high, it\'s an indication that servers closer to the user could ' +
+    'improve performance. [Learn more](https://hpbn.co/primer-on-latency-and-bandwidth/).',
+};
+
+const str_ = i18n.createMessageInstanceIdFn(__filename, UIStrings);
+
+class NetworkRTT extends Audit {
+  /**
+   * @return {LH.Audit.Meta}
+   */
+  static get meta() {
+    return {
+      id: 'network-rtt',
+      scoreDisplayMode: Audit.SCORING_MODES.INFORMATIVE,
+      title: str_(UIStrings.title),
+      description: str_(UIStrings.description),
+      requiredArtifacts: ['devtoolsLogs'],
+    };
+  }
+
+  /**
+   * @param {LH.Artifacts} artifacts
+   * @param {LH.Audit.Context} context
+   * @return {Promise<LH.Audit.Product>}
+   */
+  static async audit(artifacts, context) {
+    const devtoolsLog = artifacts.devtoolsLogs[Audit.DEFAULT_PASS];
+    const analysis = await NetworkAnalysisComputed.request(devtoolsLog, context);
+
+    /** @type {number} */
+    let maxRtt = 0;
+    const baseRtt = analysis.rtt;
+    /** @type {Array<{origin: string, rtt: number}>} */
+    const results = [];
+    for (const [origin, additionalRtt] of analysis.additionalRttByOrigin.entries()) {
+      // Ignore entries that don't look like real origins, like the __SUMMARY__ entry.
+      if (!origin.startsWith('http')) continue;
+
+      const rtt = additionalRtt + baseRtt;
+      results.push({origin, rtt});
+      maxRtt = Number.isFinite(rtt) ? Math.max(rtt, maxRtt) : maxRtt;
+    }
+
+    results.sort((a, b) => b.rtt - a.rtt);
+
+    const headings = [
+      {key: 'origin', itemType: 'text', text: str_(i18n.UIStrings.columnURL)},
+      {key: 'rtt', itemType: 'ms', granularity: 1, text: str_(i18n.UIStrings.columnTimeSpent)},
+    ];
+
+    const tableDetails = Audit.makeTableDetails(headings, results);
+
+    return {
+      score: 1,
+      rawValue: maxRtt,
+      displayValue: str_(i18n.UIStrings.ms, {timeInMs: maxRtt}),
+      details: tableDetails,
+    };
+  }
+}
+
+module.exports = NetworkRTT;
+module.exports.UIStrings = UIStrings;
diff --git a/lighthouse-core/audits/network-server-latency.js b/lighthouse-core/audits/network-server-latency.js
new file mode 100644
index 000000000..0a1748dd8
--- /dev/null
+++ b/lighthouse-core/audits/network-server-latency.js
@@ -0,0 +1,78 @@
+/**
+ * @license Copyright 2019 Google Inc. All Rights Reserved.
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
+ * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.
+ */
+'use strict';
+
+const Audit = require('./audit');
+const i18n = require('../lib/i18n/i18n.js');
+const NetworkAnalysisComputed = require('../computed/network-analysis.js');
+
+const UIStrings = {
+  /** Descriptive title of a Lighthouse audit that tells the user the server latencies observed from each origin the page connected to. This is displayed in a list of audit titles that Lighthouse generates. */
+  title: 'Server Backend Latencies',
+  /** Description of a Lighthouse audit that tells the user that server latency can effect their website's performance negatively. This is displayed after a user expands the section to see more. No character length limits. 'Learn More' becomes link text to additional documentation. */
+  description: 'Server latencies can impact web performance. ' +
+    'If the server latency of an origin is high, it\'s an indication the server is overloaded ' +
+    'or has poor backend performance. [Learn more](https://hpbn.co/primer-on-web-performance/#analyzing-the-resource-waterfall).',
+};
+
+const str_ = i18n.createMessageInstanceIdFn(__filename, UIStrings);
+
+class NetworkServerLatency extends Audit {
+  /**
+   * @return {LH.Audit.Meta}
+   */
+  static get meta() {
+    return {
+      id: 'network-server-latency',
+      scoreDisplayMode: Audit.SCORING_MODES.INFORMATIVE,
+      title: str_(UIStrings.title),
+      description: str_(UIStrings.description),
+      requiredArtifacts: ['devtoolsLogs'],
+    };
+  }
+
+  /**
+   * @param {LH.Artifacts} artifacts
+   * @param {LH.Audit.Context} context
+   * @return {Promise<LH.Audit.Product>}
+   */
+  static async audit(artifacts, context) {
+    const devtoolsLog = artifacts.devtoolsLogs[Audit.DEFAULT_PASS];
+    const analysis = await NetworkAnalysisComputed.request(devtoolsLog, context);
+
+    /** @type {number} */
+    let maxLatency = 0;
+    /** @type {Array<{origin: string, serverReponseTime: number}>} */
+    const results = [];
+    for (const [origin, serverReponseTime] of analysis.serverResponseTimeByOrigin.entries()) {
+      // Ignore entries that don't look like real origins, like the __SUMMARY__ entry.
+      if (!origin.startsWith('http')) continue;
+
+      maxLatency = Math.max(serverReponseTime, maxLatency);
+      results.push({origin, serverReponseTime});
+    }
+
+    results.sort((a, b) => b.serverReponseTime - a.serverReponseTime);
+
+    const headings = [
+      {key: 'origin', itemType: 'text', text: str_(i18n.UIStrings.columnURL)},
+      {key: 'serverReponseTime', itemType: 'ms', granularity: 1,
+        text: str_(i18n.UIStrings.columnTimeSpent)},
+    ];
+
+    const tableDetails = Audit.makeTableDetails(headings, results);
+
+    return {
+      score: Math.max(1 - (maxLatency / 500), 0),
+      rawValue: maxLatency,
+      displayValue: str_(i18n.UIStrings.ms, {timeInMs: maxLatency}),
+      details: tableDetails,
+    };
+  }
+}
+
+module.exports = NetworkServerLatency;
+module.exports.UIStrings = UIStrings;
diff --git a/lighthouse-core/config/default-config.js b/lighthouse-core/config/default-config.js
index 093447ab4..bf2e8cc95 100644
--- a/lighthouse-core/config/default-config.js
+++ b/lighthouse-core/config/default-config.js
@@ -31,6 +31,12 @@ const UIStrings = {
   diagnosticsGroupTitle: 'Diagnostics',
   /** Description of the diagnostics section of the Performance category. Within this section are audits with non-imperative titles that provide more detail on the page's page load performance characteristics. Whereas the 'Opportunities' suggest an action along with expected time savings, diagnostics do not. Within this section, the user may read the details and deduce additional actions they could take. */
   diagnosticsGroupDescription: 'More information about the performance of your application.',
+  /** Title of the Accessibility category of audits. This section contains audits focused on making web content accessible to users with disabilities. Also used as a label of a score gauge; try to limit to 20 characters. */
+  a11yCategoryTitle: 'Accessibility',
+  /** Description of the Accessibility category. This is displayed at the top of a list of audits focused on making web content accessible to users with disabilities. No character length limits. 'improve the accessibility of your web app' becomes link text to additional documentation. */
+  a11yCategoryDescription: 'These checks highlight opportunities to [improve the accessibility of your web app](https://developers.google.com/web/fundamentals/accessibility). Only a subset of accessibility issues can be automatically detected so manual testing is also encouraged.',
+  /** Description of the Accessibility manual checks category. This description is displayed above a list of accessibility audits that currently have no automated test and so must be verified manually by the user. No character length limits. 'conducting an accessibility review' becomes link text to additional documentation. */
+  a11yCategoryManualDescription: 'These items address areas which an automated testing tool cannot cover. Learn more in our guide on [conducting an accessibility review](https://developers.google.com/web/fundamentals/accessibility/how-to-review).',
   /* Title of the color contrast section within the Accessibility category. Within this section are audits with descriptive titles that highlight the color and vision aspects of the page's accessibility that are passing or failing. */
   a11yColorContrastGroupTitle: 'Color Contrast Is Satisfactory',
   /* Description of the color contrast section within the Accessibility category. Within this section are audits with descriptive titles that highlight the color and vision aspects of the page's accessibility that are passing or failing. */
@@ -161,7 +167,11 @@ const defaultConfig = {
     'uses-rel-preload',
     'uses-rel-preconnect',
     'font-display',
+    'diagnostics',
     'network-requests',
+    'network-rtt',
+    'network-server-latency',
+    'main-thread-tasks',
     'metrics',
     'offline-start-url',
     'manual/pwa-cross-browser',
@@ -348,20 +358,25 @@ const defaultConfig = {
         {id: 'uses-long-cache-ttl', weight: 0, group: 'diagnostics'},
         {id: 'dom-size', weight: 0, group: 'diagnostics'},
         {id: 'critical-request-chains', weight: 0, group: 'diagnostics'},
-        {id: 'network-requests', weight: 0},
-        {id: 'metrics', weight: 0},
         {id: 'user-timings', weight: 0, group: 'diagnostics'},
         {id: 'bootup-time', weight: 0, group: 'diagnostics'},
-        {id: 'screenshot-thumbnails', weight: 0},
-        {id: 'final-screenshot', weight: 0},
         {id: 'mainthread-work-breakdown', weight: 0, group: 'diagnostics'},
         {id: 'font-display', weight: 0, group: 'diagnostics'},
+        // Audits past this point don't belong to a group and will not be shown automatically
+        {id: 'network-requests', weight: 0},
+        {id: 'network-rtt', weight: 0},
+        {id: 'network-server-latency', weight: 0},
+        {id: 'main-thread-tasks', weight: 0},
+        {id: 'diagnostics', weight: 0},
+        {id: 'metrics', weight: 0},
+        {id: 'screenshot-thumbnails', weight: 0},
+        {id: 'final-screenshot', weight: 0},
       ],
     },
     'accessibility': {
-      title: 'Accessibility',
-      description: 'These checks highlight opportunities to [improve the accessibility of your web app](https://developers.google.com/web/fundamentals/accessibility). Only a subset of accessibility issues can be automatically detected so manual testing is also encouraged.',
-      manualDescription: 'These items address areas which an automated testing tool cannot cover. Learn more in our guide on [conducting an accessibility review](https://developers.google.com/web/fundamentals/accessibility/how-to-review).',
+      title: str_(UIStrings.a11yCategoryTitle),
+      description: str_(UIStrings.a11yCategoryDescription),
+      manualDescription: str_(UIStrings.a11yCategoryManualDescription),
       auditRefs: [
         {id: 'aria-allowed-attr', weight: 3, group: 'a11y-aria'},
         {id: 'aria-required-attr', weight: 2, group: 'a11y-aria'},
diff --git a/lighthouse-core/lib/i18n/en-US.json b/lighthouse-core/lib/i18n/en-US.json
index 06f2b46da..359a2917e 100644
--- a/lighthouse-core/lib/i18n/en-US.json
+++ b/lighthouse-core/lib/i18n/en-US.json
@@ -723,6 +723,22 @@
     "message": "Speed Index",
     "description": "The name of the metric that summarizes how quickly the page looked visually complete. The name of this metric is largely abstract and can be loosely translated. Shown to users as the label for the numeric metric value. Ideally fits within a ~40 character limit."
   },
+  "lighthouse-core/audits/network-rtt.js | description": {
+    "message": "Network round trip times (RTT) have a large impact on performance. If the RTT to an origin is high, it's an indication that servers closer to the user could improve performance. [Learn more](https://hpbn.co/primer-on-latency-and-bandwidth/).",
+    "description": "Description of a Lighthouse audit that tells the user that a high network round trip time (RTT) can effect their website's performance because the server is physically far away from them thus making the RTT high. This is displayed after a user expands the section to see more. No character length limits. 'Learn More' becomes link text to additional documentation."
+  },
+  "lighthouse-core/audits/network-rtt.js | title": {
+    "message": "Network Round Trip Times",
+    "description": "Descriptive title of a Lighthouse audit that tells the user the round trip times to each origin the page connected to. This is displayed in a list of audit titles that Lighthouse generates."
+  },
+  "lighthouse-core/audits/network-server-latency.js | description": {
+    "message": "Server latencies can impact web performance. If the server latency of an origin is high, it's an indication the server is overloaded or has poor backend performance. [Learn more](https://hpbn.co/primer-on-web-performance/#analyzing-the-resource-waterfall).",
+    "description": "Description of a Lighthouse audit that tells the user that server latency can effect their website's performance negatively. This is displayed after a user expands the section to see more. No character length limits. 'Learn More' becomes link text to additional documentation."
+  },
+  "lighthouse-core/audits/network-server-latency.js | title": {
+    "message": "Server Backend Latencies",
+    "description": "Descriptive title of a Lighthouse audit that tells the user the server latencies observed from each origin the page connected to. This is displayed in a list of audit titles that Lighthouse generates."
+  },
   "lighthouse-core/audits/redirects.js | description": {
     "message": "Redirects introduce additional delays before the page can be loaded. [Learn more](https://developers.google.com/web/tools/lighthouse/audits/redirects).",
     "description": "Description of a Lighthouse audit that tells users why they should reduce the number of server-side redirects on their page. This is displayed after a user expands the section to see more. No character length limits. 'Learn More' becomes link text to additional documentation."
@@ -823,6 +839,18 @@
     "message": "ARIA Attributes Follow Best Practices",
     "description": "Title of the ARIA validity section within the Accessibility category. Within this section are audits with descriptive titles that highlight if whether all the aria- HTML attributes have been used properly."
   },
+  "lighthouse-core/config/default-config.js | a11yCategoryDescription": {
+    "message": "These checks highlight opportunities to [improve the accessibility of your web app](https://developers.google.com/web/fundamentals/accessibility). Only a subset of accessibility issues can be automatically detected so manual testing is also encouraged.",
+    "description": "Description of the Accessibility category. This is displayed at the top of a list of audits focused on making web content accessible to users with disabilities. No character length limits. 'improve the accessibility of your web app' becomes link text to additional documentation."
+  },
+  "lighthouse-core/config/default-config.js | a11yCategoryManualDescription": {
+    "message": "These items address areas which an automated testing tool cannot cover. Learn more in our guide on [conducting an accessibility review](https://developers.google.com/web/fundamentals/accessibility/how-to-review).",
+    "description": "Description of the Accessibility manual checks category. This description is displayed above a list of accessibility audits that currently have no automated test and so must be verified manually by the user. No character length limits. 'conducting an accessibility review' becomes link text to additional documentation."
+  },
+  "lighthouse-core/config/default-config.js | a11yCategoryTitle": {
+    "message": "Accessibility",
+    "description": "Title of the Accessibility category of audits. This section contains audits focused on making web content accessible to users with disabilities. Also used as a label of a score gauge; try to limit to 20 characters."
+  },
   "lighthouse-core/config/default-config.js | a11yColorContrastGroupDescription": {
     "message": "These are opportunities to improve the legibility of your content.",
     "description": "Description of the color contrast section within the Accessibility category. Within this section are audits with descriptive titles that highlight the color and vision aspects of the page's accessibility that are passing or failing."
diff --git a/proto/sample_v2_round_trip.json b/proto/sample_v2_round_trip.json
index 55f727fee..983063b7c 100644
--- a/proto/sample_v2_round_trip.json
+++ b/proto/sample_v2_round_trip.json
@@ -419,6 +419,36 @@
             "scoreDisplayMode": "binary", 
             "title": "Uses deprecated APIs"
         }, 
+        "diagnostics": {
+            "description": "Collection of useful page vitals.", 
+            "details": {
+                "items": [
+                    {
+                        "mainDocumentTransferSize": 12640.0, 
+                        "maxRtt": 1.7249999999999943, 
+                        "maxServerLatency": 572.829, 
+                        "numFonts": 0.0, 
+                        "numRequests": 18.0, 
+                        "numScripts": 4.0, 
+                        "numStylesheets": 7.0, 
+                        "numTasks": 107.0, 
+                        "numTasksOver100ms": 3.0, 
+                        "numTasksOver10ms": 7.0, 
+                        "numTasksOver25ms": 5.0, 
+                        "numTasksOver500ms": 1.0, 
+                        "numTasksOver50ms": 4.0, 
+                        "rtt": 0.8639999999999999, 
+                        "throughput": 1398339.461891128, 
+                        "totalByteWeight": 160738.0, 
+                        "totalTaskTime": 1548.5690000000002
+                    }
+                ]
+            }, 
+            "id": "diagnostics", 
+            "score": null, 
+            "scoreDisplayMode": "informative", 
+            "title": "Diagnostics"
+        }, 
         "dlitem": {
             "description": "Definition list items (`<dt>` and `<dd>`) must be wrapped in a parent `<dl>` element to ensure that screen readers can properly announce them. [Learn more](https://dequeuniversity.com/rules/axe/3.1/dlitem?application=lighthouse).", 
             "id": "dlitem", 
@@ -1186,6 +1216,232 @@
             "scoreDisplayMode": "manual", 
             "title": "The page has a logical tab order"
         }, 
+        "main-thread-tasks": {
+            "description": "Lists the toplevel main thread tasks that executed during page load.", 
+            "details": {
+                "headings": [
+                    {
+                        "itemType": "text", 
+                        "key": "type", 
+                        "text": "Task Type"
+                    }, 
+                    {
+                        "granularity": 1.0, 
+                        "itemType": "ms", 
+                        "key": "startTime", 
+                        "text": "Start Time"
+                    }, 
+                    {
+                        "granularity": 1.0, 
+                        "itemType": "ms", 
+                        "key": "duration", 
+                        "text": "End Time"
+                    }
+                ], 
+                "items": [
+                    {
+                        "duration": 26.823, 
+                        "startTime": 604.125, 
+                        "type": "other"
+                    }, 
+                    {
+                        "duration": 23.175, 
+                        "startTime": 606.246, 
+                        "type": "other"
+                    }, 
+                    {
+                        "duration": 5.89, 
+                        "startTime": 631.458, 
+                        "type": "other"
+                    }, 
+                    {
+                        "duration": 18.85, 
+                        "startTime": 638.562, 
+                        "type": "other"
+                    }, 
+                    {
+                        "duration": 16.58, 
+                        "startTime": 638.577, 
+                        "type": "parseHTML"
+                    }, 
+                    {
+                        "duration": 12.522, 
+                        "startTime": 638.586, 
+                        "type": "parseHTML"
+                    }, 
+                    {
+                        "duration": 5.897, 
+                        "startTime": 668.051, 
+                        "type": "other"
+                    }, 
+                    {
+                        "duration": 5.046, 
+                        "startTime": 668.09, 
+                        "type": "other"
+                    }, 
+                    {
+                        "duration": 5.516, 
+                        "startTime": 2643.396, 
+                        "type": "other"
+                    }, 
+                    {
+                        "duration": 979.165, 
+                        "startTime": 2857.942, 
+                        "type": "other"
+                    }, 
+                    {
+                        "duration": 5.803, 
+                        "startTime": 2858.62, 
+                        "type": "scriptEvaluation"
+                    }, 
+                    {
+                        "duration": 970.698, 
+                        "startTime": 2865.033, 
+                        "type": "parseHTML"
+                    }, 
+                    {
+                        "duration": 19.463, 
+                        "startTime": 2866.527, 
+                        "type": "parseHTML"
+                    }, 
+                    {
+                        "duration": 19.21, 
+                        "startTime": 2866.571, 
+                        "type": "scriptEvaluation"
+                    }, 
+                    {
+                        "duration": 945.898, 
+                        "startTime": 2889.816, 
+                        "type": "parseHTML"
+                    }, 
+                    {
+                        "duration": 944.888, 
+                        "startTime": 2890.118, 
+                        "type": "scriptEvaluation"
+                    }, 
+                    {
+                        "duration": 9.047, 
+                        "startTime": 3023.783, 
+                        "type": "scriptEvaluation"
+                    }, 
+                    {
+                        "duration": 5.97, 
+                        "startTime": 3023.842, 
+                        "type": "garbageCollection"
+                    }, 
+                    {
+                        "duration": 706.85, 
+                        "startTime": 3070.433, 
+                        "type": "scriptEvaluation"
+                    }, 
+                    {
+                        "duration": 123.208, 
+                        "startTime": 3840.307, 
+                        "type": "other"
+                    }, 
+                    {
+                        "duration": 123.116, 
+                        "startTime": 3840.313, 
+                        "type": "other"
+                    }, 
+                    {
+                        "duration": 30.142, 
+                        "startTime": 3840.667, 
+                        "type": "styleLayout"
+                    }, 
+                    {
+                        "duration": 87.039, 
+                        "startTime": 3870.824, 
+                        "type": "other"
+                    }, 
+                    {
+                        "duration": 86.959, 
+                        "startTime": 3870.844, 
+                        "type": "styleLayout"
+                    }, 
+                    {
+                        "duration": 85.493, 
+                        "startTime": 3871.238, 
+                        "type": "styleLayout"
+                    }, 
+                    {
+                        "duration": 94.261, 
+                        "startTime": 3996.555, 
+                        "type": "other"
+                    }, 
+                    {
+                        "duration": 90.663, 
+                        "startTime": 3997.797, 
+                        "type": "scriptEvaluation"
+                    }, 
+                    {
+                        "duration": 5.709, 
+                        "startTime": 4019.279, 
+                        "type": "garbageCollection"
+                    }, 
+                    {
+                        "duration": 5.151, 
+                        "startTime": 4098.541, 
+                        "type": "other"
+                    }, 
+                    {
+                        "duration": 122.537, 
+                        "startTime": 4808.624, 
+                        "type": "other"
+                    }, 
+                    {
+                        "duration": 82.741, 
+                        "startTime": 4810.0, 
+                        "type": "scriptEvaluation"
+                    }, 
+                    {
+                        "duration": 36.919, 
+                        "startTime": 4893.397, 
+                        "type": "parseHTML"
+                    }, 
+                    {
+                        "duration": 36.866, 
+                        "startTime": 4893.44, 
+                        "type": "parseHTML"
+                    }, 
+                    {
+                        "duration": 10.58, 
+                        "startTime": 4894.119, 
+                        "type": "scriptEvaluation"
+                    }, 
+                    {
+                        "duration": 9.353, 
+                        "startTime": 4894.151, 
+                        "type": "scriptEvaluation"
+                    }, 
+                    {
+                        "duration": 22.297, 
+                        "startTime": 4905.447, 
+                        "type": "parseHTML"
+                    }, 
+                    {
+                        "duration": 6.849, 
+                        "startTime": 8603.435, 
+                        "type": "other"
+                    }, 
+                    {
+                        "duration": 7.076, 
+                        "startTime": 10219.463, 
+                        "type": "other"
+                    }, 
+                    {
+                        "duration": 6.983, 
+                        "startTime": 10219.523, 
+                        "type": "scriptEvaluation"
+                    }
+                ], 
+                "type": "table"
+            }, 
+            "id": "main-thread-tasks", 
+            "score": null, 
+            "scoreDisplayMode": "informative", 
+            "title": "Tasks"
+        }, 
         "mainthread-work-breakdown": {
             "description": "Consider reducing the time spent parsing, compiling and executing JS. You may find delivering smaller JS payloads helps with this.", 
             "details": {
@@ -1579,6 +1835,74 @@
             "scoreDisplayMode": "informative", 
             "title": "Network Requests"
         }, 
+        "network-rtt": {
+            "description": "Network round trip times (RTT) have a large impact on performance. If the RTT to an origin is high, it's an indication that servers closer to the user could improve performance. [Learn more](https://hpbn.co/primer-on-latency-and-bandwidth/).", 
+            "details": {
+                "headings": [
+                    {
+                        "itemType": "text", 
+                        "key": "origin", 
+                        "text": "URL"
+                    }, 
+                    {
+                        "granularity": 1.0, 
+                        "itemType": "ms", 
+                        "key": "rtt", 
+                        "text": "Time Spent"
+                    }
+                ], 
+                "items": [
+                    {
+                        "origin": "http://ajax.googleapis.com", 
+                        "rtt": 1.7249999999999943
+                    }, 
+                    {
+                        "origin": "http://localhost:10200", 
+                        "rtt": 0.8639999999999999
+                    }
+                ], 
+                "type": "table"
+            }, 
+            "displayValue": "0\u00a0ms", 
+            "id": "network-rtt", 
+            "score": null, 
+            "scoreDisplayMode": "informative", 
+            "title": "Network Round Trip Times"
+        }, 
+        "network-server-latency": {
+            "description": "Server latencies can impact web performance. If the server latency of an origin is high, it's an indication the server is overloaded or has poor backend performance. [Learn more](https://hpbn.co/primer-on-web-performance/#analyzing-the-resource-waterfall).", 
+            "details": {
+                "headings": [
+                    {
+                        "itemType": "text", 
+                        "key": "origin", 
+                        "text": "URL"
+                    }, 
+                    {
+                        "granularity": 1.0, 
+                        "itemType": "ms", 
+                        "key": "serverReponseTime", 
+                        "text": "Time Spent"
+                    }
+                ], 
+                "items": [
+                    {
+                        "origin": "http://localhost:10200", 
+                        "serverReponseTime": 572.829
+                    }, 
+                    {
+                        "origin": "http://ajax.googleapis.com", 
+                        "serverReponseTime": 562.398
+                    }
+                ], 
+                "type": "table"
+            }, 
+            "displayValue": "570\u00a0ms", 
+            "id": "network-server-latency", 
+            "score": null, 
+            "scoreDisplayMode": "informative", 
+            "title": "Server Backend Latencies"
+        }, 
         "no-document-write": {
             "description": "For users on slow connections, external scripts dynamically injected via `document.write()` can delay page load by tens of seconds. [Learn more](https://developers.google.com/web/tools/lighthouse/audits/document-write).", 
             "details": {
@@ -3087,39 +3411,55 @@
                     "weight": 0.0
                 }, 
                 {
-                    "id": "network-requests", 
+                    "group": "diagnostics", 
+                    "id": "user-timings", 
                     "weight": 0.0
                 }, 
                 {
-                    "id": "metrics", 
+                    "group": "diagnostics", 
+                    "id": "bootup-time", 
                     "weight": 0.0
                 }, 
                 {
                     "group": "diagnostics", 
-                    "id": "user-timings", 
+                    "id": "mainthread-work-breakdown", 
                     "weight": 0.0
                 }, 
                 {
                     "group": "diagnostics", 
-                    "id": "bootup-time", 
+                    "id": "font-display", 
                     "weight": 0.0
                 }, 
                 {
-                    "id": "screenshot-thumbnails", 
+                    "id": "network-requests", 
                     "weight": 0.0
                 }, 
                 {
-                    "id": "final-screenshot", 
+                    "id": "network-rtt", 
                     "weight": 0.0
                 }, 
                 {
-                    "group": "diagnostics", 
-                    "id": "mainthread-work-breakdown", 
+                    "id": "network-server-latency", 
                     "weight": 0.0
                 }, 
                 {
-                    "group": "diagnostics", 
-                    "id": "font-display", 
+                    "id": "main-thread-tasks", 
+                    "weight": 0.0
+                }, 
+                {
+                    "id": "diagnostics", 
+                    "weight": 0.0
+                }, 
+                {
+                    "id": "metrics", 
+                    "weight": 0.0
+                }, 
+                {
+                    "id": "screenshot-thumbnails", 
+                    "weight": 0.0
+                }, 
+                {
+                    "id": "final-screenshot", 
                     "weight": 0.0
                 }
             ], 
@@ -3750,12 +4090,36 @@
                 "name": "lh:audit:font-display", 
                 "startTime": 0.0
             }, 
+            {
+                "duration": 100.0, 
+                "entryType": "measure", 
+                "name": "lh:audit:diagnostics", 
+                "startTime": 0.0
+            }, 
             {
                 "duration": 100.0, 
                 "entryType": "measure", 
                 "name": "lh:audit:network-requests", 
                 "startTime": 0.0
             }, 
+            {
+                "duration": 100.0, 
+                "entryType": "measure", 
+                "name": "lh:audit:network-rtt", 
+                "startTime": 0.0
+            }, 
+            {
+                "duration": 100.0, 
+                "entryType": "measure", 
+                "name": "lh:audit:network-server-latency", 
+                "startTime": 0.0
+            }, 
+            {
+                "duration": 100.0, 
+                "entryType": "measure", 
+                "name": "lh:audit:main-thread-tasks", 
+                "startTime": 0.0
+            }, 
             {
                 "duration": 100.0, 
                 "entryType": "measure", 
diff --git a/third-party/snyk/snapshot.json b/third-party/snyk/snapshot.json
index 18e7cff82..0309549fb 100644
--- a/third-party/snyk/snapshot.json
+++ b/third-party/snyk/snapshot.json
@@ -27,10 +27,11 @@
       {"id":"npm:backbone:20110701","severity":"medium","semver":{"vulnerable":["<0.5.0"]}}
     ],
     "bootstrap":[
+      {"id":"npm:bootstrap:20180529","severity":"medium","semver":{"vulnerable":[">=4.0.0 <4.1.2"]}},
       {"id":"SNYK-JS-BOOTSTRAP-72890","severity":"medium","semver":{"vulnerable":["<3.4.0"]}},
       {"id":"SNYK-JS-BOOTSTRAP-72889","severity":"medium","semver":{"vulnerable":["<3.4.0"]}},
       {"id":"npm:bootstrap:20180529","severity":"medium","semver":{"vulnerable":["<3.4.0",">=4.0.0 <4.1.2"]}},
-      {"id":"npm:bootstrap:20160627","severity":"medium","semver":{"vulnerable":["<3.4.0",">=4.0.0 <=4.0.0"]}},
+      {"id":"npm:bootstrap:20160627","severity":"medium","semver":{"vulnerable":["<3.4.0",">=4.0.0-alpha <4.0.0-beta.2"]}},
       {"id":"npm:bootstrap:20120510","severity":"medium","semver":{"vulnerable":["<2.1.0"]}}
     ],
     "dojo":[


Test patch:
diff --git a/lighthouse-cli/test/cli/__snapshots__/index-test.js.snap b/lighthouse-cli/test/cli/__snapshots__/index-test.js.snap
index 923dfc60c..1b75abf0f 100644
--- a/lighthouse-cli/test/cli/__snapshots__/index-test.js.snap
+++ b/lighthouse-cli/test/cli/__snapshots__/index-test.js.snap
@@ -99,9 +99,21 @@ Object {
     Object {
       "path": "font-display",
     },
+    Object {
+      "path": "diagnostics",
+    },
     Object {
       "path": "network-requests",
     },
+    Object {
+      "path": "network-rtt",
+    },
+    Object {
+      "path": "network-server-latency",
+    },
+    Object {
+      "path": "main-thread-tasks",
+    },
     Object {
       "path": "metrics",
     },
@@ -782,39 +794,55 @@ Object {
           "weight": 0,
         },
         Object {
-          "id": "network-requests",
+          "group": "diagnostics",
+          "id": "user-timings",
           "weight": 0,
         },
         Object {
-          "id": "metrics",
+          "group": "diagnostics",
+          "id": "bootup-time",
           "weight": 0,
         },
         Object {
           "group": "diagnostics",
-          "id": "user-timings",
+          "id": "mainthread-work-breakdown",
           "weight": 0,
         },
         Object {
           "group": "diagnostics",
-          "id": "bootup-time",
+          "id": "font-display",
           "weight": 0,
         },
         Object {
-          "id": "screenshot-thumbnails",
+          "id": "network-requests",
           "weight": 0,
         },
         Object {
-          "id": "final-screenshot",
+          "id": "network-rtt",
           "weight": 0,
         },
         Object {
-          "group": "diagnostics",
-          "id": "mainthread-work-breakdown",
+          "id": "network-server-latency",
           "weight": 0,
         },
         Object {
-          "group": "diagnostics",
-          "id": "font-display",
+          "id": "main-thread-tasks",
+          "weight": 0,
+        },
+        Object {
+          "id": "diagnostics",
+          "weight": 0,
+        },
+        Object {
+          "id": "metrics",
+          "weight": 0,
+        },
+        Object {
+          "id": "screenshot-thumbnails",
+          "weight": 0,
+        },
+        Object {
+          "id": "final-screenshot",
           "weight": 0,
         },
       ],
diff --git a/lighthouse-core/test/audits/diagnostics-test.js b/lighthouse-core/test/audits/diagnostics-test.js
new file mode 100644
index 000000000..f40e0c657
--- /dev/null
+++ b/lighthouse-core/test/audits/diagnostics-test.js
@@ -0,0 +1,43 @@
+/**
+ * @license Copyright 2019 Google Inc. All Rights Reserved.
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
+ * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.
+ */
+'use strict';
+
+const Diagnostics = require('../../audits/diagnostics.js');
+
+const acceptableTrace = require('../fixtures/traces/progressive-app-m60.json');
+const acceptableDevToolsLog = require('../fixtures/traces/progressive-app-m60.devtools.log.json');
+
+/* eslint-env jest */
+
+describe('Diagnostics audit', () => {
+  it('should work', async () => {
+    const artifacts = {
+      traces: {defaultPass: acceptableTrace},
+      devtoolsLogs: {defaultPass: acceptableDevToolsLog},
+    };
+
+    const result = await Diagnostics.audit(artifacts, {computedCache: new Map()});
+    expect(result.details.items[0]).toEqual({
+      maxRtt: 3.6660000041592014,
+      maxServerLatency: 159.5549999910874,
+      numFonts: 1,
+      numRequests: 66,
+      numScripts: 6,
+      numStylesheets: 0,
+      numTasks: 547,
+      numTasksOver10ms: 13,
+      numTasksOver25ms: 8,
+      numTasksOver50ms: 4,
+      numTasksOver100ms: 1,
+      numTasksOver500ms: 0,
+      rtt: 2.6209999923595007,
+      throughput: 1628070.200017642,
+      totalByteWeight: 234053,
+      totalTaskTime: 1360.2630000000001,
+      mainDocumentTransferSize: 5368,
+    });
+  });
+});
diff --git a/lighthouse-core/test/audits/main-thread-tasks-test.js b/lighthouse-core/test/audits/main-thread-tasks-test.js
new file mode 100644
index 000000000..fe34b94f2
--- /dev/null
+++ b/lighthouse-core/test/audits/main-thread-tasks-test.js
@@ -0,0 +1,25 @@
+/**
+ * @license Copyright 2019 Google Inc. All Rights Reserved.
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
+ * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.
+ */
+'use strict';
+
+const MainThreadTasks = require('../../audits/main-thread-tasks.js');
+
+const acceptableTrace = require('../fixtures/traces/progressive-app-m60.json');
+
+/* eslint-env jest */
+
+describe('Main thread tasks audit', () => {
+  it('should work', async () => {
+    const artifacts = {traces: {defaultPass: acceptableTrace}};
+    const result = await MainThreadTasks.audit(artifacts, {computedCache: new Map()});
+    expect(result.details.items).toHaveLength(76);
+
+    for (const item of result.details.items) {
+      expect(Number.isFinite(item.startTime)).toBeTruthy();
+      expect(Number.isFinite(item.duration)).toBeTruthy();
+    }
+  });
+});
diff --git a/lighthouse-core/test/audits/metrics-test.js b/lighthouse-core/test/audits/metrics-test.js
index a433b141d..09c364214 100644
--- a/lighthouse-core/test/audits/metrics-test.js
+++ b/lighthouse-core/test/audits/metrics-test.js
@@ -6,6 +6,7 @@
 'use strict';
 
 const MetricsAudit = require('../../audits/metrics.js');
+const TTIComputed = require('../../computed/metrics/interactive.js');
 
 const pwaTrace = require('../fixtures/traces/progressive-app-m60.json');
 const pwaDevtoolsLog = require('../fixtures/traces/progressive-app-m60.devtools.log.json');
@@ -27,4 +28,21 @@ describe('Performance: metrics', () => {
     const result = await MetricsAudit.audit(artifacts, context);
     expect(result.details.items[0]).toMatchSnapshot();
   });
+
+  it('does to fail the entire audit when TTI errors', async () => {
+    const artifacts = {
+      traces: {
+        [MetricsAudit.DEFAULT_PASS]: pwaTrace,
+      },
+      devtoolsLogs: {
+        [MetricsAudit.DEFAULT_PASS]: pwaDevtoolsLog,
+      },
+    };
+
+    const mockTTIFn = jest.spyOn(TTIComputed, 'request');
+    mockTTIFn.mockRejectedValueOnce(new Error('TTI failed'));
+    const context = {settings: {throttlingMethod: 'simulate'}, computedCache: new Map()};
+    const result = await MetricsAudit.audit(artifacts, context);
+    expect(result.details.items[0].interactive).toEqual(undefined);
+  });
 });
diff --git a/lighthouse-core/test/audits/network-rtt-test.js b/lighthouse-core/test/audits/network-rtt-test.js
new file mode 100644
index 000000000..925779e01
--- /dev/null
+++ b/lighthouse-core/test/audits/network-rtt-test.js
@@ -0,0 +1,35 @@
+/**
+ * @license Copyright 2019 Google Inc. All Rights Reserved.
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
+ * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.
+ */
+'use strict';
+
+const NetworkRTT = require('../../audits/network-rtt.js');
+
+const acceptableDevToolsLog = require('../fixtures/traces/progressive-app-m60.devtools.log.json');
+
+/* eslint-env jest */
+
+describe('Network RTT audit', () => {
+  it('should work', async () => {
+    const artifacts = {devtoolsLogs: {defaultPass: acceptableDevToolsLog}};
+    const result = await NetworkRTT.audit(artifacts, {computedCache: new Map()});
+    result.details.items.forEach(item => (item.rtt = Math.round(item.rtt * 100) / 100));
+
+    expect(result.details.items).toEqual([
+      {
+        origin: 'https://www.google-analytics.com',
+        rtt: 3.67,
+      },
+      {
+        origin: 'https://pwa.rocks',
+        rtt: 3.02,
+      },
+      {
+        origin: 'https://www.googletagmanager.com',
+        rtt: 2.62,
+      },
+    ]);
+  });
+});
diff --git a/lighthouse-core/test/audits/network-server-latency-test.js b/lighthouse-core/test/audits/network-server-latency-test.js
new file mode 100644
index 000000000..37535f00d
--- /dev/null
+++ b/lighthouse-core/test/audits/network-server-latency-test.js
@@ -0,0 +1,39 @@
+/**
+ * @license Copyright 2019 Google Inc. All Rights Reserved.
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
+ * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.
+ */
+'use strict';
+
+const ServerLatency = require('../../audits/network-server-latency.js');
+
+const acceptableDevToolsLog = require('../fixtures/traces/progressive-app-m60.devtools.log.json');
+
+/* eslint-env jest */
+
+describe('Network Server Latency audit', () => {
+  it('should work', async () => {
+    const artifacts = {devtoolsLogs: {defaultPass: acceptableDevToolsLog}};
+    const result = await ServerLatency.audit(artifacts, {computedCache: new Map()});
+    result.details.items.forEach(
+      item => (item.serverReponseTime = Math.round(item.serverReponseTime * 100) / 100)
+    );
+
+    // These were all from a trace that used our ancient 150ms devtools throttling which appears as
+    // artifical response time = Math.max(real response time, 150ms)
+    expect(result.details.items).toEqual([
+      {
+        origin: 'https://www.google-analytics.com',
+        serverReponseTime: 159.55,
+      },
+      {
+        origin: 'https://pwa.rocks',
+        serverReponseTime: 159.42,
+      },
+      {
+        origin: 'https://www.googletagmanager.com',
+        serverReponseTime: 153.03,
+      },
+    ]);
+  });
+});
diff --git a/lighthouse-core/test/results/sample_v2.json b/lighthouse-core/test/results/sample_v2.json
index 90e3297f5..f59c9ce8a 100644
--- a/lighthouse-core/test/results/sample_v2.json
+++ b/lighthouse-core/test/results/sample_v2.json
@@ -796,6 +796,37 @@
         "items": []
       }
     },
+    "diagnostics": {
+      "id": "diagnostics",
+      "title": "Diagnostics",
+      "description": "Collection of useful page vitals.",
+      "score": null,
+      "scoreDisplayMode": "informative",
+      "rawValue": 1,
+      "details": {
+        "items": [
+          {
+            "numRequests": 18,
+            "numScripts": 4,
+            "numStylesheets": 7,
+            "numFonts": 0,
+            "numTasks": 107,
+            "numTasksOver10ms": 7,
+            "numTasksOver25ms": 5,
+            "numTasksOver50ms": 4,
+            "numTasksOver100ms": 3,
+            "numTasksOver500ms": 1,
+            "rtt": 0.8639999999999999,
+            "throughput": 1398339.461891128,
+            "maxRtt": 1.7249999999999943,
+            "maxServerLatency": 572.829,
+            "totalByteWeight": 160738,
+            "totalTaskTime": 1548.5690000000002,
+            "mainDocumentTransferSize": 12640
+          }
+        ]
+      }
+    },
     "network-requests": {
       "id": "network-requests",
       "title": "Network Requests",
@@ -1037,6 +1068,303 @@
         ]
       }
     },
+    "network-rtt": {
+      "id": "network-rtt",
+      "title": "Network Round Trip Times",
+      "description": "Network round trip times (RTT) have a large impact on performance. If the RTT to an origin is high, it's an indication that servers closer to the user could improve performance. [Learn more](https://hpbn.co/primer-on-latency-and-bandwidth/).",
+      "score": null,
+      "scoreDisplayMode": "informative",
+      "rawValue": 1.7249999999999943,
+      "displayValue": "0 ms",
+      "details": {
+        "type": "table",
+        "headings": [
+          {
+            "key": "origin",
+            "itemType": "text",
+            "text": "URL"
+          },
+          {
+            "key": "rtt",
+            "itemType": "ms",
+            "granularity": 1,
+            "text": "Time Spent"
+          }
+        ],
+        "items": [
+          {
+            "origin": "http://ajax.googleapis.com",
+            "rtt": 1.7249999999999943
+          },
+          {
+            "origin": "http://localhost:10200",
+            "rtt": 0.8639999999999999
+          }
+        ]
+      }
+    },
+    "network-server-latency": {
+      "id": "network-server-latency",
+      "title": "Server Backend Latencies",
+      "description": "Server latencies can impact web performance. If the server latency of an origin is high, it's an indication the server is overloaded or has poor backend performance. [Learn more](https://hpbn.co/primer-on-web-performance/#analyzing-the-resource-waterfall).",
+      "score": null,
+      "scoreDisplayMode": "informative",
+      "rawValue": 572.829,
+      "displayValue": "570 ms",
+      "details": {
+        "type": "table",
+        "headings": [
+          {
+            "key": "origin",
+            "itemType": "text",
+            "text": "URL"
+          },
+          {
+            "key": "serverReponseTime",
+            "itemType": "ms",
+            "granularity": 1,
+            "text": "Time Spent"
+          }
+        ],
+        "items": [
+          {
+            "origin": "http://localhost:10200",
+            "serverReponseTime": 572.829
+          },
+          {
+            "origin": "http://ajax.googleapis.com",
+            "serverReponseTime": 562.398
+          }
+        ]
+      }
+    },
+    "main-thread-tasks": {
+      "id": "main-thread-tasks",
+      "title": "Tasks",
+      "description": "Lists the toplevel main thread tasks that executed during page load.",
+      "score": null,
+      "scoreDisplayMode": "informative",
+      "rawValue": 39,
+      "details": {
+        "type": "table",
+        "headings": [
+          {
+            "key": "type",
+            "itemType": "text",
+            "text": "Task Type"
+          },
+          {
+            "key": "startTime",
+            "itemType": "ms",
+            "granularity": 1,
+            "text": "Start Time"
+          },
+          {
+            "key": "duration",
+            "itemType": "ms",
+            "granularity": 1,
+            "text": "End Time"
+          }
+        ],
+        "items": [
+          {
+            "type": "other",
+            "duration": 26.823,
+            "startTime": 604.125
+          },
+          {
+            "type": "other",
+            "duration": 23.175,
+            "startTime": 606.246
+          },
+          {
+            "type": "other",
+            "duration": 5.89,
+            "startTime": 631.458
+          },
+          {
+            "type": "other",
+            "duration": 18.85,
+            "startTime": 638.562
+          },
+          {
+            "type": "parseHTML",
+            "duration": 16.58,
+            "startTime": 638.577
+          },
+          {
+            "type": "parseHTML",
+            "duration": 12.522,
+            "startTime": 638.586
+          },
+          {
+            "type": "other",
+            "duration": 5.897,
+            "startTime": 668.051
+          },
+          {
+            "type": "other",
+            "duration": 5.046,
+            "startTime": 668.09
+          },
+          {
+            "type": "other",
+            "duration": 5.516,
+            "startTime": 2643.396
+          },
+          {
+            "type": "other",
+            "duration": 979.165,
+            "startTime": 2857.942
+          },
+          {
+            "type": "scriptEvaluation",
+            "duration": 5.803,
+            "startTime": 2858.62
+          },
+          {
+            "type": "parseHTML",
+            "duration": 970.698,
+            "startTime": 2865.033
+          },
+          {
+            "type": "parseHTML",
+            "duration": 19.463,
+            "startTime": 2866.527
+          },
+          {
+            "type": "scriptEvaluation",
+            "duration": 19.21,
+            "startTime": 2866.571
+          },
+          {
+            "type": "parseHTML",
+            "duration": 945.898,
+            "startTime": 2889.816
+          },
+          {
+            "type": "scriptEvaluation",
+            "duration": 944.888,
+            "startTime": 2890.118
+          },
+          {
+            "type": "scriptEvaluation",
+            "duration": 9.047,
+            "startTime": 3023.783
+          },
+          {
+            "type": "garbageCollection",
+            "duration": 5.97,
+            "startTime": 3023.842
+          },
+          {
+            "type": "scriptEvaluation",
+            "duration": 706.85,
+            "startTime": 3070.433
+          },
+          {
+            "type": "other",
+            "duration": 123.208,
+            "startTime": 3840.307
+          },
+          {
+            "type": "other",
+            "duration": 123.116,
+            "startTime": 3840.313
+          },
+          {
+            "type": "styleLayout",
+            "duration": 30.142,
+            "startTime": 3840.667
+          },
+          {
+            "type": "other",
+            "duration": 87.039,
+            "startTime": 3870.824
+          },
+          {
+            "type": "styleLayout",
+            "duration": 86.959,
+            "startTime": 3870.844
+          },
+          {
+            "type": "styleLayout",
+            "duration": 85.493,
+            "startTime": 3871.238
+          },
+          {
+            "type": "other",
+            "duration": 94.261,
+            "startTime": 3996.555
+          },
+          {
+            "type": "scriptEvaluation",
+            "duration": 90.663,
+            "startTime": 3997.797
+          },
+          {
+            "type": "garbageCollection",
+            "duration": 5.709,
+            "startTime": 4019.279
+          },
+          {
+            "type": "other",
+            "duration": 5.151,
+            "startTime": 4098.541
+          },
+          {
+            "type": "other",
+            "duration": 122.537,
+            "startTime": 4808.624
+          },
+          {
+            "type": "scriptEvaluation",
+            "duration": 82.741,
+            "startTime": 4810
+          },
+          {
+            "type": "parseHTML",
+            "duration": 36.919,
+            "startTime": 4893.397
+          },
+          {
+            "type": "parseHTML",
+            "duration": 36.866,
+            "startTime": 4893.44
+          },
+          {
+            "type": "scriptEvaluation",
+            "duration": 10.58,
+            "startTime": 4894.119
+          },
+          {
+            "type": "scriptEvaluation",
+            "duration": 9.353,
+            "startTime": 4894.151
+          },
+          {
+            "type": "parseHTML",
+            "duration": 22.297,
+            "startTime": 4905.447
+          },
+          {
+            "type": "other",
+            "duration": 6.849,
+            "startTime": 8603.435
+          },
+          {
+            "type": "other",
+            "duration": 7.076,
+            "startTime": 10219.463
+          },
+          {
+            "type": "scriptEvaluation",
+            "duration": 6.983,
+            "startTime": 10219.523
+          }
+        ]
+      }
+    },
     "metrics": {
       "id": "metrics",
       "title": "Metrics",
@@ -2972,40 +3300,56 @@
           "group": "diagnostics"
         },
         {
-          "id": "network-requests",
-          "weight": 0
+          "id": "user-timings",
+          "weight": 0,
+          "group": "diagnostics"
         },
         {
-          "id": "metrics",
-          "weight": 0
+          "id": "bootup-time",
+          "weight": 0,
+          "group": "diagnostics"
         },
         {
-          "id": "user-timings",
+          "id": "mainthread-work-breakdown",
           "weight": 0,
           "group": "diagnostics"
         },
         {
-          "id": "bootup-time",
+          "id": "font-display",
           "weight": 0,
           "group": "diagnostics"
         },
         {
-          "id": "screenshot-thumbnails",
+          "id": "network-requests",
           "weight": 0
         },
         {
-          "id": "final-screenshot",
+          "id": "network-rtt",
           "weight": 0
         },
         {
-          "id": "mainthread-work-breakdown",
-          "weight": 0,
-          "group": "diagnostics"
+          "id": "network-server-latency",
+          "weight": 0
         },
         {
-          "id": "font-display",
-          "weight": 0,
-          "group": "diagnostics"
+          "id": "main-thread-tasks",
+          "weight": 0
+        },
+        {
+          "id": "diagnostics",
+          "weight": 0
+        },
+        {
+          "id": "metrics",
+          "weight": 0
+        },
+        {
+          "id": "screenshot-thumbnails",
+          "weight": 0
+        },
+        {
+          "id": "final-screenshot",
+          "weight": 0
         }
       ],
       "id": "performance",
@@ -3892,12 +4236,36 @@
         "duration": 100,
         "entryType": "measure"
       },
+      {
+        "startTime": 0,
+        "name": "lh:audit:diagnostics",
+        "duration": 100,
+        "entryType": "measure"
+      },
       {
         "startTime": 0,
         "name": "lh:audit:network-requests",
         "duration": 100,
         "entryType": "measure"
       },
+      {
+        "startTime": 0,
+        "name": "lh:audit:network-rtt",
+        "duration": 100,
+        "entryType": "measure"
+      },
+      {
+        "startTime": 0,
+        "name": "lh:audit:network-server-latency",
+        "duration": 100,
+        "entryType": "measure"
+      },
+      {
+        "startTime": 0,
+        "name": "lh:audit:main-thread-tasks",
+        "duration": 100,
+        "entryType": "measure"
+      },
       {
         "startTime": 0,
         "name": "lh:audit:metrics",
@@ -4548,6 +4916,18 @@
             "timeInMs": 122.537
           },
           "path": "audits[max-potential-fid].displayValue"
+        },
+        {
+          "values": {
+            "timeInMs": 1.7249999999999943
+          },
+          "path": "audits[network-rtt].displayValue"
+        },
+        {
+          "values": {
+            "timeInMs": 572.829
+          },
+          "path": "audits[network-server-latency].displayValue"
         }
       ],
       "lighthouse-core/audits/metrics/max-potential-fid.js | title": [
@@ -4618,7 +4998,9 @@
         "audits[mainthread-work-breakdown].details.headings[0].text"
       ],
       "lighthouse-core/lib/i18n/i18n.js | columnTimeSpent": [
-        "audits[mainthread-work-breakdown].details.headings[1].text"
+        "audits[mainthread-work-breakdown].details.headings[1].text",
+        "audits[network-rtt].details.headings[1].text",
+        "audits[network-server-latency].details.headings[1].text"
       ],
       "lighthouse-core/audits/bootup-time.js | title": [
         "audits[bootup-time].title"
@@ -4628,6 +5010,8 @@
       ],
       "lighthouse-core/lib/i18n/i18n.js | columnURL": [
         "audits[bootup-time].details.headings[0].text",
+        "audits[network-rtt].details.headings[0].text",
+        "audits[network-server-latency].details.headings[0].text",
         "audits[uses-long-cache-ttl].details.headings[0].text",
         "audits[total-byte-weight].details.headings[0].text",
         "audits[render-blocking-resources].details.headings[0].label",
@@ -4662,6 +5046,18 @@
       "lighthouse-core/audits/font-display.js | description": [
         "audits[font-display].description"
       ],
+      "lighthouse-core/audits/network-rtt.js | title": [
+        "audits[network-rtt].title"
+      ],
+      "lighthouse-core/audits/network-rtt.js | description": [
+        "audits[network-rtt].description"
+      ],
+      "lighthouse-core/audits/network-server-latency.js | title": [
+        "audits[network-server-latency].title"
+      ],
+      "lighthouse-core/audits/network-server-latency.js | description": [
+        "audits[network-server-latency].description"
+      ],
       "lighthouse-core/audits/accessibility/aria-allowed-attr.js | title": [
         "audits[aria-allowed-attr].title"
       ],
@@ -5064,6 +5460,15 @@
       "lighthouse-core/config/default-config.js | performanceCategoryTitle": [
         "categories.performance.title"
       ],
+      "lighthouse-core/config/default-config.js | a11yCategoryTitle": [
+        "categories.accessibility.title"
+      ],
+      "lighthouse-core/config/default-config.js | a11yCategoryDescription": [
+        "categories.accessibility.description"
+      ],
+      "lighthouse-core/config/default-config.js | a11yCategoryManualDescription": [
+        "categories.accessibility.manualDescription"
+      ],
       "lighthouse-core/config/default-config.js | metricGroupTitle": [
         "categoryGroups.metrics.title"
       ],