From b82d3197ed71f43ba60fdc7db1cdd4fe26993fee Mon Sep 17 00:00:00 2001 From: Alexei Filippov Date: Tue, 10 Oct 2017 14:50:38 -0700 Subject: [PATCH] feat(Page): Support Page.getMetrics and metrics event. (#939) Provides access to the current page performance metrics. Allows to push page metrics from the page JavaScript with console.timeStamp() Fixes #309 --- docs/api.md | 32 +++++++++++++++++++++++++++++++ lib/Page.js | 54 ++++++++++++++++++++++++++++++++++++++++++++++++++++ test/test.js | 41 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 127 insertions(+) diff --git a/docs/api.md b/docs/api.md index 557d098bfc1..1298bb18396 100644 --- a/docs/api.md +++ b/docs/api.md @@ -25,6 +25,7 @@ + [event: 'framedetached'](#event-framedetached) + [event: 'framenavigated'](#event-framenavigated) + [event: 'load'](#event-load) + + [event: 'metrics'](#event-metrics) + [event: 'pageerror'](#event-pageerror) + [event: 'request'](#event-request) + [event: 'requestfailed'](#event-requestfailed) @@ -49,6 +50,7 @@ + [page.exposeFunction(name, puppeteerFunction)](#pageexposefunctionname-puppeteerfunction) + [page.focus(selector)](#pagefocusselector) + [page.frames()](#pageframes) + + [page.getMetrics()](#pagegetmetrics) + [page.goBack(options)](#pagegobackoptions) + [page.goForward(options)](#pagegoforwardoptions) + [page.goto(url, options)](#pagegotourl-options) @@ -326,6 +328,15 @@ Emitted when a frame is navigated to a new url. Emitted when the JavaScript [`load`](https://developer.mozilla.org/en-US/docs/Web/Events/load) event is dispatched. +#### event: 'metrics' +- <[Object]> + - `title` <[string]> The title passed to `console.timeStamp`. + - `metrics` <[Object]> Object containing metrics as key/value pairs. The values + of metrics are of <[number]> type. + +Emitted when the JavaScript code makes a call to `console.timeStamp`. For the list +of metrics see `page.getMetrics`. + #### event: 'pageerror' - <[string]> The exception message @@ -637,6 +648,27 @@ If there's no element matching `selector`, the method throws an error. #### page.frames() - returns: <[Array]<[Frame]>> An array of all frames attached to the page. +#### page.getMetrics() +- returns: <[Object]> Object containing metrics as key/value pairs. + - `Timestamp` <[number]> The timestamp when the metrics sample was taken. + - `DocumentCount` <[number]> Number of documents in the page. + - `FrameCount` <[number]> Number of frames in the page. + - `JSEventListenerCount` <[number]> Number of events in the page. + - `NodeCount` <[number]> Number of DOM nodes in the page. + - `LayoutCount` <[number]> Total number of full or partial page layout. + - `RecalcStyleCount` <[number]> Total number of page style recalculations. + - `LayoutDuration` <[number]> Combined durations of all page layouts. + - `RecalcStyleDuration` <[number]> Combined duration of all page style recalculations. + - `ScriptDuration` <[number]> Combined duration of JavaScript execution. + - `TaskDuration` <[number]> Combined duration of all tasks performed by the browser. + - `JSHeapUsedSize` <[number]> Used JavaScript heap size. + - `JSHeapTotalSize` <[number]> Total JavaScript heap size. + - `FirstMeaningfulPaint` <[number]> Timestamp of the first meaningful paint event. + - `DomContentLoaded` <[number]> Timestamp of the DOM content loaded event. + - `NavigationStart` <[number]> Timestamp of the navigation start event. + +> **NOTE** All timestamps are in monotonic time: monotonically increasing time in seconds since an arbitrary point in the past. + #### page.goBack(options) - `options` <[Object]> Navigation parameters which might have the following properties: - `timeout` <[number]> Maximum navigation time in milliseconds, defaults to 30 seconds, pass `0` to disable timeout. diff --git a/lib/Page.js b/lib/Page.js index 5d33c2cef2a..47b8ce8c7b4 100644 --- a/lib/Page.js +++ b/lib/Page.js @@ -40,6 +40,7 @@ class Page extends EventEmitter { client.send('Page.enable', {}), client.send('Runtime.enable', {}), client.send('Security.enable', {}), + client.send('Performance.enable', {}) ]); if (ignoreHTTPSErrors) await client.send('Security.setOverrideCertificateErrors', {override: true}); @@ -87,6 +88,7 @@ class Page extends EventEmitter { client.on('Runtime.exceptionThrown', exception => this._handleException(exception.exceptionDetails)); client.on('Security.certificateError', event => this._onCertificateError(event)); client.on('Inspector.targetCrashed', event => this._onTargetCrashed()); + client.on('Performance.metrics', event => this._emitMetrics(event)); } _onTargetCrashed() { @@ -304,6 +306,37 @@ class Page extends EventEmitter { return this._networkManager.setUserAgent(userAgent); } + /** + * @return {!Promise} + */ + async getMetrics() { + const response = await this._client.send('Performance.getMetrics'); + return this._buildMetricsObject(response.metrics); + } + + /** + * @param {*} event + */ + _emitMetrics(event) { + this.emit(Page.Events.Metrics, { + title: event.title, + metrics: this._buildMetricsObject(event.metrics) + }); + } + + /** + * @param {?Array} metrics + * @return {!Object} + */ + _buildMetricsObject(metrics) { + const result = {}; + for (const metric of metrics || []) { + if (supportedMetrics.has(metric.name)) + result[metric.name] = metric.value; + } + return result; + } + /** * @param {!Object} exceptionDetails */ @@ -776,6 +809,26 @@ class Page extends EventEmitter { } } +/** @type {!Set} */ +const supportedMetrics = new Set([ + 'Timestamp', + 'DocumentCount', + 'FrameCount', + 'JSEventListenerCount', + 'NodeCount', + 'LayoutCount', + 'RecalcStyleCount', + 'LayoutDuration', + 'RecalcStyleDuration', + 'ScriptDuration', + 'TaskDuration', + 'JSHeapUsedSize', + 'JSHeapTotalSize', + 'FirstMeaningfulPaint', + 'DomContentLoaded', + 'NavigationStart', +]); + /** @enum {string} */ Page.PaperFormats = { letter: {width: 8.5, height: 11}, @@ -844,6 +897,7 @@ Page.Events = { FrameDetached: 'framedetached', FrameNavigated: 'framenavigated', Load: 'load', + Metrics: 'metrics', }; /** diff --git a/test/test.js b/test/test.js index 895cfe38040..2e301271039 100644 --- a/test/test.js +++ b/test/test.js @@ -749,6 +749,47 @@ describe('Page', function() { })); }); + describe('Page.getMetrics', function() { + it('should get metrics from a page', SX(async function() { + await page.goto('about:blank'); + const metrics = await page.getMetrics(); + checkMetrics(metrics); + })); + it('metrics event fired on console.timeStamp', SX(async function() { + const metricsPromise = new Promise(fulfill => page.once('metrics', fulfill)); + await page.evaluate(() => console.timeStamp('test42')); + const metrics = await metricsPromise; + expect(metrics.title).toBe('test42'); + checkMetrics(metrics.metrics); + })); + function checkMetrics(metrics) { + const metricsToCheck = new Set([ + 'Timestamp', + 'DocumentCount', + 'FrameCount', + 'JSEventListenerCount', + 'NodeCount', + 'LayoutCount', + 'RecalcStyleCount', + 'LayoutDuration', + 'RecalcStyleDuration', + 'ScriptDuration', + 'TaskDuration', + 'JSHeapUsedSize', + 'JSHeapTotalSize', + 'FirstMeaningfulPaint', + 'DomContentLoaded', + 'NavigationStart', + ]); + for (const name in metrics) { + expect(metricsToCheck.has(name)).toBeTruthy(); + expect(metrics[name]).toBeGreaterThanOrEqual(0); + metricsToCheck.delete(name); + } + expect(metricsToCheck.size).toBe(0); + } + }); + describe('Page.goto', function() { it('should navigate to about:blank', SX(async function() { const response = await page.goto('about:blank');