From a9f6ac36b324d26dadb694b64369b87ad1d0724c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ga=C3=ABl=20M=C3=A9tais?= Date: Mon, 1 Sep 2014 19:43:44 +0200 Subject: [PATCH] Add window.performance to the execution tree --- app/lib/phantomasWrapper.js | 3 +- .../modules/windowPerfYLT/windowPerfYLT.js | 144 ++++++++++++++++++ 2 files changed, 146 insertions(+), 1 deletion(-) create mode 100644 phantomas_custom/modules/windowPerfYLT/windowPerfYLT.js diff --git a/app/lib/phantomasWrapper.js b/app/lib/phantomasWrapper.js index 8857320..e9dd120 100644 --- a/app/lib/phantomasWrapper.js +++ b/app/lib/phantomasWrapper.js @@ -52,7 +52,8 @@ var PhantomasWrapper = function() { 'screenshot', //'staticAssets', //'timeToFirst', - 'waitForSelector' + 'waitForSelector', + 'windowPerformance' ].join(','), 'include-dirs': [ 'phantomas_custom/core', diff --git a/phantomas_custom/modules/windowPerfYLT/windowPerfYLT.js b/phantomas_custom/modules/windowPerfYLT/windowPerfYLT.js new file mode 100644 index 0000000..c71ed45 --- /dev/null +++ b/phantomas_custom/modules/windowPerfYLT/windowPerfYLT.js @@ -0,0 +1,144 @@ +/** + * Measure when the page reaches certain states + * + * @see http://w3c-test.org/webperf/specs/NavigationTiming/#dom-performancetiming-domloading + * @see https://developers.google.com/web/fundamentals/performance/critical-rendering-path/measure-crp + */ +/* global document: true, window: true */ +'use strict'; + +exports.version = '1.0.a'; + +exports.module = function(phantomas) { + // times below are calculated relative to performance.timing.responseEnd (#117) + phantomas.setMetric('domInteractive'); // @desc time it took to parse the HTML and construct the DOM + phantomas.setMetric('domContentLoaded'); // @desc time it took to construct both DOM and CSSOM, no stylesheets that are blocking JavaScript execution (i.e. onDOMReady) + phantomas.setMetric('domContentLoadedEnd'); // @desc time it took to finish handling of onDOMReady event @unreliable + phantomas.setMetric('domComplete'); // @desc time it took to load all page resources, the loading spinner has stopped spinning + + // backend vs frontend time + phantomas.setMetric('timeBackend'); // @desc time to the first byte compared to the total loading time [%] + phantomas.setMetric('timeFrontend'); // @desc time to window.load compared to the total loading time [%] + + // measure dom... metrics from the moment HTML response was fully received + var responseEndTime = Date.now(); + + phantomas.on('responseEnd', function() { + responseEndTime = Date.now(); + phantomas.log('Performance timing: responseEnd = %d', responseEndTime); + }); + + phantomas.on('init', function() { + phantomas.evaluate(function(responseEndTime) { + (function(phantomas) { + phantomas.spyEnabled(false, 'installing window.performance metrics'); + + // extend window.performance + // "init" event is sometimes fired twice, pass a value set by "responseEnd" event handler (fixes #192) + if (typeof window.performance === 'undefined') { + window.performance = { + timing: { + responseEnd: responseEndTime + } + }; + + phantomas.log('Performance timing: emulating window.performance'); + } + else { + phantomas.log('Performance timing: using native window.performance'); + } + + // onDOMReady + document.addEventListener("DOMContentLoaded", function() { + + setTimeout(function() { + // use NavigationTiming if possible + var time = window.performance.timing.domContentLoadedEventEnd ? + (window.performance.timing.domContentLoadedEventEnd - window.performance.timing.responseEnd) + : + (Date.now() - responseEndTime); + + phantomas.setMetric('domContentLoadedEnd', time, true); + phantomas.log('Performance timing: document reached "DOMContentLoadedEnd" state after %d ms', time); + + phantomas.pushContext({ + type: 'domContentLoadedEnd' + }); + }, 0); + + var time = Date.now() - responseEndTime; + + phantomas.setMetric('domContentLoaded', time, true); + phantomas.log('Performance timing: document reached "DOMContentLoaded" state after %d ms', time); + + phantomas.pushContext({ + type: 'domContentLoaded' + }); + }); + + // emulate Navigation Timing + document.addEventListener('readystatechange', function() { + var readyState = document.readyState, + responseEndTime = window.performance.timing.responseEnd, + time = Date.now() - responseEndTime, + metricName; + + // @see http://www.w3.org/TR/html5/dom.html#documentreadystate + switch(readyState) { + // the browser has finished parsing all of the HTML and DOM construction is complete + case 'interactive': + metricName = 'domInteractive'; + break; + + // the processing is complete and all of the resources on the page have finished downloading + case 'complete': + metricName = 'domComplete'; + phantomas.log('Performance timing: %j', window.performance.timing); + break; + + default: + phantomas.log('Performance timing: unhandled "%s" state!', readyState); + return; + } + + phantomas.setMetric(metricName, time, true); + phantomas.log('Performance timing: document reached "%s" state after %d ms', readyState, time); + + phantomas.pushContext({ + type: metricName + }); + }); + + phantomas.spyEnabled(true); + })(window.__phantomas); + }, responseEndTime); + }); + + /** + * Emit metrics with backend vs frontend time + * + * Performance Golden Rule: + * "80-90% of the end-user response time is spent on the frontend. Start there." + * + * @see http://www.stevesouders.com/blog/2012/02/10/the-performance-golden-rule/ + */ + phantomas.on('report', function() { + // The “backend” time is the time it takes the server to get the first byte back to the client. + // The “frontend” time is measured from the last byte of the response (responseEnd) until all resources are fetched (domComplete) + var backendTime = parseInt(phantomas.getMetric('timeToFirstByte'), 10), + frontendTime = parseInt(phantomas.getMetric('domComplete'), 10), + totalTime = backendTime + frontendTime, + backendTimePercentage; + + if (totalTime === 0) { + return; + } + + backendTimePercentage = Math.round(backendTime / totalTime * 100); + + phantomas.setMetric('timeBackend', backendTimePercentage); + phantomas.setMetric('timeFrontend', 100 - backendTimePercentage); + + phantomas.log('Performance timing: backend vs frontend time - %d% / %d%', backendTimePercentage, 100 - backendTimePercentage); + }); +};