diff --git a/lib/plugins/pagexray/index.js b/lib/plugins/pagexray/index.js index 88e7e924f..b85f9b363 100644 --- a/lib/plugins/pagexray/index.js +++ b/lib/plugins/pagexray/index.js @@ -105,7 +105,6 @@ export default class PageXrayPlugin extends SitespeedioPlugin { } } this.pageXrayAggregator.addToAggregate(pageSummary, group); - if (this.multi) { // The HAR file can have multiple URLs const sentURL = {}; @@ -116,6 +115,9 @@ export default class PageXrayPlugin extends SitespeedioPlugin { sentURL[summary.url] += 1; } else { sentURL[summary.url] = 1; + summary.statistics = this.pageXrayAggregator.summarizePerUrl( + summary.url + ); queue.postMessage( make('pagexray.pageSummary', summary, { url: summary.url, @@ -134,6 +136,9 @@ export default class PageXrayPlugin extends SitespeedioPlugin { ); } } else { + pageSummary[0].statistics = this.pageXrayAggregator.summarizePerUrl( + message.url + ); queue.postMessage( make('pagexray.pageSummary', pageSummary[0], { url: message.url, diff --git a/lib/plugins/pagexray/pagexrayAggregator.js b/lib/plugins/pagexray/pagexrayAggregator.js index 83b2d7661..3cfd2cc30 100644 --- a/lib/plugins/pagexray/pagexrayAggregator.js +++ b/lib/plugins/pagexray/pagexrayAggregator.js @@ -1,11 +1,16 @@ import forEach from 'lodash.foreach'; -import { pushGroupStats, setStatsSummary } from '../../support/statsHelpers.js'; +import { + pushGroupStats, + setStatsSummary, + pushStats +} from '../../support/statsHelpers.js'; const METRIC_NAMES = ['transferSize', 'contentSize', 'requests']; export class PageXrayAggregator { constructor() { + this.urls = {}; this.stats = {}; this.groups = {}; } @@ -19,6 +24,11 @@ export class PageXrayAggregator { let groups = this.groups; for (const summary of pageSummary) { + let url = summary.url; + if (this.urls[url] === undefined) { + this.urls[url] = {}; + } + // stats for the whole page for (const metric of METRIC_NAMES) { // There's a bug in Firefox/https://github.com/devtools-html/har-export-trigger @@ -26,6 +36,7 @@ export class PageXrayAggregator { // https://github.com/sitespeedio/sitespeed.io/issues/2090 if (!Number.isNaN(summary[metric])) { pushGroupStats(stats, groups[group], metric, summary[metric]); + pushStats(this.urls[url], metric, summary[metric]); } } @@ -41,6 +52,12 @@ export class PageXrayAggregator { 'contentTypes.' + contentType + '.' + metric, summary.contentTypes[contentType][metric] ); + + pushStats( + this.urls[url], + 'contentTypes.' + contentType + '.' + metric, + summary.contentTypes[contentType][metric] + ); } } } @@ -52,6 +69,12 @@ export class PageXrayAggregator { 'responseCodes.' + responseCode, summary.responseCodes[responseCode] ); + + pushStats( + this.urls[url], + 'responseCodes.' + responseCode, + summary.responseCodes[responseCode] + ); } /* for (const responseCode of Object.keys(summary.responseCodes)) { @@ -73,6 +96,11 @@ export class PageXrayAggregator { 'firstParty' + '.' + metric, summary.firstParty[metric] ); + pushStats( + this.urls[url], + 'firstParty' + '.' + metric, + summary.firstParty[metric] + ); } if (summary.thirdParty[metric] !== undefined) { pushGroupStats( @@ -81,6 +109,12 @@ export class PageXrayAggregator { 'thirdParty' + '.' + metric, summary.thirdParty[metric] ); + + pushStats( + this.urls[url], + 'thirdParty' + '.' + metric, + summary.thirdParty[metric] + ); } } } @@ -107,6 +141,9 @@ export class PageXrayAggregator { }); } } + summarizePerUrl(url) { + return this.summarizePerObject(this.urls[url]); + } summarize() { if (Object.keys(this.stats).length === 0) { return; diff --git a/lib/support/friendlynames.js b/lib/support/friendlynames.js index c7d77f128..aaa30bcdf 100644 --- a/lib/support/friendlynames.js +++ b/lib/support/friendlynames.js @@ -239,177 +239,249 @@ export default { }, pagexray: { requests: { - total: { path: 'requests', name: 'Total Requests', format: noop }, + total: { + path: 'statistics.requests.median', + summaryPath: 'requests', + runPath: 'requests', + name: 'Total Requests', + format: noop + }, html: { - path: 'contentTypes.html.requests', + path: 'statistics.contentTypes.html.requests.median', + summaryPath: 'contentTypes.html.requests', + runPath: 'contentTypes.html.requests', name: 'HTML Requests', format: noop }, javascript: { - path: 'rontentTypes.javascript.requests', + path: 'statistics.contentTypes.javascript.requests.median', + summaryPath: 'contentTypes.javascript.requests', + runPath: 'contentTypes.javascript.requests', name: 'JavaScript Requests', format: noop }, css: { - path: 'contentTypes.css.request', + path: 'statistics.contentTypes.css.requests.median', + summaryPath: 'contentTypes.css.requests', + runPath: 'contentTypes.css.requests', name: 'CSS Requests', format: noop }, image: { - path: 'contentTypes.image.requests', + path: 'statistics.contentTypes.images.requests.median', + summaryPath: 'contentTypes.images.requests', + runPath: 'contentTypes.images.requests', name: 'Image Requests', format: noop }, font: { - path: 'contentTypes.font.requests', + path: 'statistics.contentTypes.font.requests.median', + summaryPath: 'contentTypes.font.requests', + runPath: 'contentTypes.font.requests', name: 'Font Requests', format: noop }, httpErrors: { - path: 'responseCodes', + path: 'statistics.responseCodes.median', + summaryPath: 'responseCodes', + runPath: 'responseCodes', name: 'HTTP Errors', format: httpErrors } }, transferSize: { total: { - path: 'transferSize', + path: 'statistics.transferSize.median', + summaryPath: 'transferSize', + runPath: 'transferSize', name: 'Total Transfer Size', format: size.format }, html: { - path: 'contentTypes.html.transferSize', + path: 'statistics.contentTypes.html.transferSize.median', + summaryPath: 'contentTypes.html.transferSize', + runPath: 'contentTypes.html.transferSize', name: 'HTML Transfer Size', format: size.format }, javascript: { - path: 'contentTypes.javascript.transferSize', + path: 'statistics.contentTypes.javascript.transferSize.median', + summaryPath: 'contentTypes.javascript.transferSize', + runPath: 'contentTypes.javascript.transferSize', name: 'JavaScript Transfer Size', format: size.format }, css: { - path: 'contentTypes.css.transferSize', + path: 'statistics.contentTypes.css.transferSize.median', + summaryPath: 'contentTypes.css.transferSize', + runPath: 'contentTypes.css.transferSize', name: 'CSS Transfer Size', format: size.format }, image: { - path: 'contentTypes.image.transferSize', + path: 'statistics.contentTypes.image.transferSize.median', + summaryPath: 'contentTypes.image.transferSize', + runPath: 'contentTypes.iamge.transferSize', name: 'Image Transfer Size', format: size.format }, font: { - path: 'contentTypes.font.transferSize', + path: 'statistics.contentTypes.font.transferSize.median', + summaryPath: 'contentTypes.font.transferSize', + runPath: 'contentTypes.font.transferSize', name: 'Font Transfer Size', format: size.format }, favicon: { - path: 'contentTypes.favicon.transferSize', + path: 'statistics.contentTypes.favicon.transferSize.median', + summaryPath: 'contentTypes.favicon.transferSize', + runPath: 'contentTypes.favicon.transferSize', name: 'Favicon Transfer Size', format: size.format }, json: { - path: 'contentTypes.json.transferSize', + path: 'statistics.contentTypes.json.transferSize.median', + summaryPath: 'contentTypes.json.transferSize', + runPath: 'contentTypes.json.transferSize', name: 'JSON Transfer Size', format: size.format }, other: { - path: 'contentTypes.other.transferSize', + path: 'statistics.contentTypes.other.transferSize.median', + summaryPath: 'contentTypes.other.transferSize', + runPath: 'contentTypes.other.transferSize', name: 'Other Transfer Size', format: size.format }, plain: { - path: 'contentTypes.plain.transferSize', + path: 'statistics.contentTypes.plain.transferSize.median', + summaryPath: 'contentTypes.plain.transferSize', + runPath: 'contentTypes.plain.transferSize', name: 'Plain Transfer Size', format: size.format }, svg: { - path: 'contentTypes.svg.transferSize', + path: 'statistics.contentTypes.svg.transferSize.median', + summaryPath: 'contentTypes.svg.transferSize', + runPath: 'contentTypes.svg.transferSize', name: 'SVG Transfer Size', format: size.format } }, contentSize: { total: { - path: 'contentSize', + path: 'statistics.contentSize.median', + summaryPath: 'contentSize', + runPath: 'contentSize', name: 'Total Content Size', format: size.format }, html: { - path: 'contentTypes.html.contentSize', + path: 'statistics.contentTypes.html.contentSize.median', + summaryPath: 'contentTypes.html.contentSize', + runPath: 'contentTypes.html.contentSize', name: 'HTML Content Size', format: size.format }, javascript: { - path: 'contentTypes.javascript.contentSize', + path: 'statistics.contentTypes.javascript.contentSize.median', + summaryPath: 'contentTypes.javascript.contentSize', + runPath: 'contentTypes.javascript.contentSize', name: 'JavaScript Content Size', format: size.format }, css: { - path: 'contentTypes.css.contentSize', + path: 'statistics.contentTypes.css.contentSize.median', + summaryPath: 'contentTypes.css.contentSize', + runPath: 'contentTypes.css.contentSize', name: 'CSS Content Size', format: size.format }, image: { - path: 'contentTypes.image.contentSize', + path: 'statistics.contentTypes.image.contentSize.median', + summaryPath: 'contentTypes.image.contentSize', + runPath: 'contentTypes.image.contentSize', name: 'Image Content Size', format: size.format }, font: { - path: 'contentTypes.font.contentSize', + path: 'statistics.contentTypes.font.contentSize.median', + summaryPath: 'contentTypes.font.contentSize', + runPath: 'contentTypes.font.contentSize', name: 'Font Content Size', format: size.format }, favicon: { - path: 'contentTypes.favicon.contentSize', + path: 'statistics.contentTypes.favicon.contentSize.median', + summaryPath: 'contentTypes.favicon.contentSize', + runPath: 'contentTypes.favicon.contentSize', name: 'Favicon Content Size', format: size.format }, json: { - path: 'contentTypes.json.contentSize', + path: 'statistics.contentTypes.json.contentSize.median', + summaryPath: 'contentTypes.json.contentSize', + runPath: 'contentTypes.json.contentSize', name: 'JSON Content Size', format: size.format }, other: { - path: 'contentTypes.other.contentSize', + path: 'statistics.contentTypes.other.contentSize.median', + summaryPath: 'contentTypes.other.contentSize', + runPath: 'contentTypes.other.contentSize', name: 'Other Content Size', format: size.format }, plain: { - path: 'contentTypes.plain.contentSize', + path: 'statistics.contentTypes.plain.contentSize.median', + summaryPath: 'contentTypes.plain.contentSize', + runPath: 'contentTypes.plain.contentSize', name: 'Plain Content Size', format: size.format }, svg: { - path: 'contentTypes.svg.contentSize', + path: 'statistics.contentTypes.svg.contentSize.median', + summaryPath: 'contentTypes.svg.contentSize', + runPath: 'contentTypes.svg.contentSize', name: 'SVG Content Size', format: size.format } }, thirdParty: { transferSize: { - path: 'thirdParty.transferSize', + path: 'statistics.thirdParty.transferSize.median', + summaryPath: 'thirdParty.transferSize', + runPath: 'thirdParty.transferSize', name: 'Third Party Transfer Size', format: size.format }, requests: { - path: 'thirdParty.requests', + path: 'statistics.thirdParty.requests.median', + summaryPath: 'thirdParty.requests', + runPath: 'thirdParty.requests', name: 'Third Party Requests', format: noop } }, firstParty: { requests: { - path: 'firstParty.requests', + path: 'statistics.firstParty.requests.median', + summaryPath: 'firstParty.requests', + runPath: 'firstParty.requests', name: 'First Party Requests', format: noop }, transferSize: { - path: 'firstParty.transferSize', + path: 'statistics.firstParty.transferSize.median', + summaryPath: 'firstParty.transferSize', + runPath: 'firstParty.transferSize', name: 'First Party Total Transfer Size', format: size.format }, contentSize: { - path: 'firstParty.contentSize', + path: 'statistics.firstParty.contentSize.median', + summaryPath: 'firstParty.contentSize', + runPath: 'firstParty.contentSize', name: 'First Party Total Content Size', format: size.format } diff --git a/lib/support/statsHelpers.js b/lib/support/statsHelpers.js index b76209438..25d28d2fa 100644 --- a/lib/support/statsHelpers.js +++ b/lib/support/statsHelpers.js @@ -48,7 +48,12 @@ export function summarizeStats(stats, options) { let data = { median: Number.parseFloat(stats.median().toFixed(decimals)), - mean: Number.parseFloat(stats.amean().toFixed(decimals)) + mean: Number.parseFloat(stats.amean().toFixed(decimals)), + rsd: + stats.stddev() > 0 + ? Number.parseFloat((100 * stats.stddev()) / stats.amean()) + : 0, // Relative standard deviation + stddev: Number.parseFloat(stats.stddev().toFixed(decimals)) }; for (const p of percentiles) { let name = percentileName(p);