diff --git a/lib/plugins/html/htmlBuilder.js b/lib/plugins/html/htmlBuilder.js
index 90297d863..30e713f53 100644
--- a/lib/plugins/html/htmlBuilder.js
+++ b/lib/plugins/html/htmlBuilder.js
@@ -342,7 +342,6 @@ class HTMLBuilder {
const pageRuns = this.pageRuns.filter(
run => !!get(pageInfo.data, [run.id, 'run'])
);
-
let rootPath = this.storageManager.rootPathFromUrl(url, daurlAlias);
let data = {
daurl: url,
@@ -380,6 +379,7 @@ class HTMLBuilder {
headers: this.summary,
version: packageInfo.version,
timestamp: runTimestamp,
+ friendlyNames,
context: this.context,
pageRuns
};
@@ -387,10 +387,18 @@ class HTMLBuilder {
for (const run of pageRuns) {
pugs[run.id] = renderer.renderTemplate(run.id, data);
}
+
data.pugs = pugs;
urlPageRenders.push(
this._renderUrlRunPage(url, parseInt(runIndex) + 1, data, daurlAlias)
);
+
+ // Do only once per URL
+ if (parseInt(runIndex) === 0) {
+ urlPageRenders.push(
+ this._renderMetricSummaryPage(url, 'metrics', data, daurlAlias)
+ );
+ }
}
}
@@ -486,6 +494,16 @@ class HTMLBuilder {
);
}
+ async _renderMetricSummaryPage(url, name, locals, alias) {
+ log.debug('Render URL metric page %s', name);
+ return this.storageManager.writeHtmlForUrl(
+ renderer.renderTemplate('url/summary/metrics/index', locals),
+ name + '.html',
+ url,
+ alias
+ );
+ }
+
async _renderSummaryPage(name, locals) {
log.debug('Render summary page %s', name);
diff --git a/lib/plugins/html/templates/url/summary/index.pug b/lib/plugins/html/templates/url/summary/index.pug
index b8e822234..287c95a98 100644
--- a/lib/plugins/html/templates/url/summary/index.pug
+++ b/lib/plugins/html/templates/url/summary/index.pug
@@ -27,9 +27,12 @@ block content
each val, index in runPages
- value = Number(index) + 1
a(href='./' + value + '.html') #{value}
- if (value !== Object.keys(runPages).length)
+ if (value === Object.keys(runPages).length)
| -
- |
+ a(href='metrics.html') (side by side)
+ else
+ | -
+ |
if pageInfo.errors
.errors
diff --git a/lib/plugins/html/templates/url/summary/metrics/index.pug b/lib/plugins/html/templates/url/summary/metrics/index.pug
new file mode 100644
index 000000000..40e6295da
--- /dev/null
+++ b/lib/plugins/html/templates/url/summary/metrics/index.pug
@@ -0,0 +1,39 @@
+extends ../layout.pug
+
+block content
+ h1 Metrics per run
+ - const daTitle = daurlAlias ? daurlAlias : daurl
+ h5.url
+ a(href=daurl) #{decodeURIComponent(daTitle)}
+
+ include ../../includes/pageRunInfo
+
+ - const tools = ['browsertime', 'pagexray']
+ - const metrics = {};
+ - for (let tool of Object.keys(friendlyNames)) {
+ - if (tools.indexOf(tool) > -1) {
+ - metrics[tool] = friendlyNames[tool]
+ - }
+ - }
+
+ h3 Side by side
+ .responsive
+ table
+ tr
+ - let run = 1
+ th Metric
+ each page in runPages
+ th
+ b #{run}
+ - run++
+ each tool in Object.keys(metrics)
+ each metricType in Object.keys(metrics[tool])
+ each metric in Object.keys(metrics[tool][metricType])
+ - const friendly = metrics[tool][metricType][metric]
+ - const m = get (runPages[0], 'data.' + tool + '.run.' + (friendly.runPath || friendly.path), 'hepp')
+ if (m !== 'hepp')
+ tr
+ td
+ b #{friendly.name}
+ each page in runPages
+ td #{friendly.format(get (page, 'data.' + tool + '.run.' + (friendly.runPath || friendly.path)))}
diff --git a/lib/support/friendlynames.js b/lib/support/friendlynames.js
index 14ef4df3c..52b1cd41d 100644
--- a/lib/support/friendlynames.js
+++ b/lib/support/friendlynames.js
@@ -1,5 +1,5 @@
'use strict';
-const { noop, size, time, co2 } = require('./helpers');
+const { noop, size, time, co2, httpErrors, decimals } = require('./helpers');
module.exports = {
browsertime: {
@@ -7,128 +7,149 @@ module.exports = {
firstContentfulPaint: {
path: "statistics.timings.paintTiming['first-contentful-paint'].median",
summaryPath: "paintTiming['first-contentful-paint']",
+ runPath: "timings.paintTiming['first-contentful-paint']",
name: 'First Contentful Paint',
format: time.ms
},
largestContentfulPaint: {
path: 'statistics.timings.largestContentfulPaint.renderTime.median',
summaryPath: 'timings.largestContentfulPaint',
+ runPath: 'timings.largestContentfulPaint.renderTime',
name: 'Largest Contentful Paint',
format: time.ms
},
totalBlockingTime: {
path: 'statistics.cpu.longTasks.totalBlockingTime.median',
summaryPath: 'cpu.longTasks.totalBlockingTime',
+ runPath: 'cpu.longTasks.totalBlockingTime',
name: 'Total Blocking Time',
format: time.ms
},
cumulativeLayoutShift: {
path: 'statistics.pageinfo.cumulativeLayoutShift.median',
+ runPath: 'pageinfo.cumulativeLayoutShift',
summaryPath: 'pageinfo.cumulativeLayoutShift',
name: 'Cumulative Layout Shift',
- format: noop
+ format: decimals
}
},
timings: {
firstPaint: {
path: 'statistics.timings.firstPaint.median',
summaryPath: 'firstPaint',
+ runPath: 'timings.firstPaint',
name: 'First Paint',
format: time.ms
},
firstContentfulPaint: {
path: "statistics.timings.paintTiming['first-contentful-paint'].median",
summaryPath: "paintTiming['first-contentful-paint']",
+ runPath: "timings.paintTiming['first-contentful-paint']",
name: 'First Contentful Paint',
format: time.ms
},
largestContentfulPaint: {
path: 'statistics.timings.largestContentfulPaint.renderTime.median',
summaryPath: 'timings.largestContentfulPaint',
+ runPath: 'timings.largestContentfulPaint.renderTime',
name: 'Largest Contentful Paint',
format: time.ms
},
loadEventEnd: {
path: 'statistics.timings.loadEventEnd.median',
summaryPath: 'loadEventEnd',
+ runPath: 'timings.loadEventEnd',
name: 'Load Event End',
format: time.ms
},
fullyLoaded: {
path: 'statistics.timings.fullyLoaded.median',
summaryPath: 'timings.fullyLoaded',
+ runPath: 'timings.fullyLoaded',
name: 'Fully Loaded',
format: time.ms
},
serverResponseTime: {
path: 'statistics.timings.pageTimings.serverResponseTime.median',
summaryPath: 'pageTimings.serverResponseTime',
+ runPath: 'timings.pageTimings.serverResponseTime',
name: 'Server Response Time',
format: time.ms
},
backEndTime: {
path: 'statistics.timings.pageTimings.backEndTime.median',
summaryPath: 'pageTimings.backEndTime',
+ runPath: 'timings.pageTimings.backEndTime',
name: 'TTFB',
format: time.ms
},
pageLoadTime: {
path: 'statistics.timings.pageTimings.pageLoadTime.median',
summaryPath: 'pageTimings.pageLoadTime',
+ runPath: 'timings.pageTimings.pageLoadTime',
name: 'Page Load Time',
format: time.ms
},
FirstVisualChange: {
path: 'statistics.visualMetrics.FirstVisualChange.median',
summaryPath: 'visualMetrics.FirstVisualChange',
+ runPath: 'visualMetrics.FirstVisualChange',
name: 'First Visual Change',
format: time.ms
},
LastVisualChange: {
path: 'statistics.visualMetrics.LastVisualChange.median',
- name: 'Last Visual Change',
summaryPath: 'visualMetrics.LastVisualChange',
+ runPath: 'visualMetrics.LastVisualChange',
+ name: 'Last Visual Change',
format: time.ms
},
SpeedIndex: {
path: 'statistics.visualMetrics.SpeedIndex.median',
summaryPath: 'visualMetrics.SpeedIndex',
+ runPath: 'visualMetrics.SpeedIndex',
name: 'Speed Index',
format: time.ms
},
ContentfulSpeedIndex: {
path: 'statistics.visualMetrics.ContentfulSpeedIndex.median',
summaryPath: 'visualMetrics.ContentfulSpeedIndex',
+ runPath: 'visualMetrics.ContentfulSpeedIndex',
name: 'Contentful Speed Index',
format: time.ms
},
PerceptualSpeedIndex: {
path: 'statistics.visualMetrics.PerceptualSpeedIndex.median',
summaryPath: 'visualMetrics.PerceptualSpeedIndex',
+ runPath: 'visualMetrics.PerceptualSpeedIndex',
name: 'Perceptual Speed Index',
format: time.ms
},
VisualReadiness: {
path: 'statistics.visualMetrics.VisualReadiness.median',
summaryPath: 'visualMetrics.VisualReadiness',
+ runPath: 'visualMetrics.VisualReadiness',
name: 'Visual Readiness',
format: time.ms
},
VisualComplete95: {
path: 'statistics.visualMetrics.VisualComplete95.median',
summaryPath: 'visualMetrics.VisualComplete95',
+ runPath: 'visualMetrics.VisualComplete95',
name: 'Visual Complete 95',
format: time.ms
},
VisualComplete99: {
path: 'statistics.visualMetrics.VisualComplete99.median',
summaryPath: 'visualMetrics.VisualComplete99',
+ runPath: 'visualMetrics.VisualComplete99',
name: 'Visual Complete 99',
format: time.ms
},
VisualComplete: {
path: 'statistics.visualMetrics.VisualComplete.median',
summaryPath: 'visualMetrics.VisualComplete',
+ runPath: 'visualMetrics.VisualComplete',
name: 'Visual Complete',
format: time.ms
}
@@ -137,33 +158,61 @@ module.exports = {
totalBlockingTime: {
path: 'statistics.cpu.longTasks.totalBlockingTime.median',
summaryPath: 'cpu.longTasks.totalBlockingTime',
+ runPath: 'cpu.longTasks.totalBlockingTime',
name: 'Total Blocking Time',
format: time.ms
},
maxPotentialFid: {
path: 'statistics.cpu.longTasks.maxPotentialFid.median',
summaryPath: 'cpu.longTasks.maxPotentialFid',
+ runPath: 'cpu.longTasks.maxPotentialFid',
name: 'Max Potential FID',
format: time.ms
},
longTasks: {
path: 'statistics.cpu.longTasks.tasks.median',
summaryPath: 'cpu.longTasks.tasks',
+ runPath: 'cpu.longTasks.tasks',
name: 'Number of Long Tasks',
format: noop
},
longTasksTotalDuration: {
path: 'statistics.cpu.longTasks.totalDuration.median',
summaryPath: 'cpu.longTasks.totalDuration',
+ runPath: 'cpu.longTasks.totalDuration',
name: 'Total Duration of Long Tasks',
format: time.ms
}
},
+ browser: {
+ cpuBenchmark: {
+ path: 'statistics.browser.cpuBenchmark.median',
+ summaryPath: 'browser.cpuBenchmark',
+ runPath: 'browser.cpuBenchmark',
+ name: 'CPU benchmark score',
+ format: time.ms
+ }
+ },
pageinfo: {
cumulativeLayoutShift: {
path: 'statistics.pageinfo.cumulativeLayoutShift.median',
summaryPath: 'pageinfo.cumulativeLayoutShift',
+ runPath: 'pageinfo.cumulativeLayoutShift',
name: 'Cumulative Layout Shift',
+ format: decimals
+ },
+ domElements: {
+ path: 'statistics.pageinfo.domElements.median',
+ summaryPath: 'pageinfo.domElements',
+ runPath: 'pageinfo.domElements',
+ name: 'DOM elements',
+ format: noop
+ },
+ documentHeight: {
+ path: 'statistics.pageinfo.documentHeight.median',
+ summaryPath: 'pageinfo.documentHeight',
+ runPath: 'pageinfo.documentHeight',
+ name: 'Document height',
format: noop
}
}
@@ -196,7 +245,11 @@ module.exports = {
name: 'Font Requests',
format: noop
},
- httpErrors: { path: 'responseCodes', name: 'HTTP Errors', format: noop }
+ httpErrors: {
+ path: 'responseCodes',
+ name: 'HTTP Errors',
+ format: httpErrors
+ }
},
transferSize: {
total: {
diff --git a/lib/support/helpers/decimals.js b/lib/support/helpers/decimals.js
new file mode 100644
index 000000000..68bb93d54
--- /dev/null
+++ b/lib/support/helpers/decimals.js
@@ -0,0 +1,8 @@
+'use strict';
+
+module.exports = function(decimals) {
+ let number = Number(decimals).toFixed(3);
+ if (number === '0.000') {
+ return 0;
+ } else return number;
+};
diff --git a/lib/support/helpers/httpErrors.js b/lib/support/helpers/httpErrors.js
new file mode 100644
index 000000000..6ea80d58d
--- /dev/null
+++ b/lib/support/helpers/httpErrors.js
@@ -0,0 +1,11 @@
+'use strict';
+
+module.exports = function(httpCodes) {
+ let data = '';
+ for (let code of Object.keys(httpCodes)) {
+ if (Number(code) > 399) {
+ data += `${code}: ${httpCodes[code]} `;
+ }
+ }
+ return data === '' ? '0' : data;
+};
diff --git a/lib/support/helpers/index.js b/lib/support/helpers/index.js
index fc565c2fb..4221ae756 100644
--- a/lib/support/helpers/index.js
+++ b/lib/support/helpers/index.js
@@ -12,5 +12,7 @@ module.exports = {
shortAsset: require('./shortAsset'),
co2: require('./co2'),
noop: require('./noop'),
- percent: require('./percent')
+ percent: require('./percent'),
+ httpErrors: require('./httpErrors'),
+ decimals: require('./decimals')
};