|
@@ -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);
|
|
|
+ });
|
|
|
+};
|