windowPerfYLT.js 6.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144
  1. /**
  2. * Measure when the page reaches certain states
  3. *
  4. * @see http://w3c-test.org/webperf/specs/NavigationTiming/#dom-performancetiming-domloading
  5. * @see https://developers.google.com/web/fundamentals/performance/critical-rendering-path/measure-crp
  6. */
  7. /* global document: true, window: true */
  8. 'use strict';
  9. exports.version = '1.0.a';
  10. exports.module = function(phantomas) {
  11. // times below are calculated relative to performance.timing.responseEnd (#117)
  12. phantomas.setMetric('domInteractive'); // @desc time it took to parse the HTML and construct the DOM
  13. phantomas.setMetric('domContentLoaded'); // @desc time it took to construct both DOM and CSSOM, no stylesheets that are blocking JavaScript execution (i.e. onDOMReady)
  14. phantomas.setMetric('domContentLoadedEnd'); // @desc time it took to finish handling of onDOMReady event @unreliable
  15. phantomas.setMetric('domComplete'); // @desc time it took to load all page resources, the loading spinner has stopped spinning
  16. // backend vs frontend time
  17. phantomas.setMetric('timeBackend'); // @desc time to the first byte compared to the total loading time [%]
  18. phantomas.setMetric('timeFrontend'); // @desc time to window.load compared to the total loading time [%]
  19. // measure dom... metrics from the moment HTML response was fully received
  20. var responseEndTime = Date.now();
  21. phantomas.on('responseEnd', function() {
  22. responseEndTime = Date.now();
  23. phantomas.log('Performance timing: responseEnd = %d', responseEndTime);
  24. });
  25. phantomas.on('init', function() {
  26. phantomas.evaluate(function(responseEndTime) {
  27. (function(phantomas) {
  28. phantomas.spyEnabled(false, 'installing window.performance metrics');
  29. // extend window.performance
  30. // "init" event is sometimes fired twice, pass a value set by "responseEnd" event handler (fixes #192)
  31. if (typeof window.performance === 'undefined') {
  32. window.performance = {
  33. timing: {
  34. responseEnd: responseEndTime
  35. }
  36. };
  37. phantomas.log('Performance timing: emulating window.performance');
  38. }
  39. else {
  40. phantomas.log('Performance timing: using native window.performance');
  41. }
  42. // onDOMReady
  43. document.addEventListener("DOMContentLoaded", function() {
  44. setTimeout(function() {
  45. // use NavigationTiming if possible
  46. var time = window.performance.timing.domContentLoadedEventEnd ?
  47. (window.performance.timing.domContentLoadedEventEnd - window.performance.timing.responseEnd)
  48. :
  49. (Date.now() - responseEndTime);
  50. phantomas.setMetric('domContentLoadedEnd', time, true);
  51. phantomas.log('Performance timing: document reached "DOMContentLoadedEnd" state after %d ms', time);
  52. phantomas.pushContext({
  53. type: 'domContentLoadedEnd'
  54. });
  55. }, 0);
  56. var time = Date.now() - responseEndTime;
  57. phantomas.setMetric('domContentLoaded', time, true);
  58. phantomas.log('Performance timing: document reached "DOMContentLoaded" state after %d ms', time);
  59. phantomas.pushContext({
  60. type: 'domContentLoaded'
  61. });
  62. });
  63. // emulate Navigation Timing
  64. document.addEventListener('readystatechange', function() {
  65. var readyState = document.readyState,
  66. responseEndTime = window.performance.timing.responseEnd,
  67. time = Date.now() - responseEndTime,
  68. metricName;
  69. // @see http://www.w3.org/TR/html5/dom.html#documentreadystate
  70. switch(readyState) {
  71. // the browser has finished parsing all of the HTML and DOM construction is complete
  72. case 'interactive':
  73. metricName = 'domInteractive';
  74. break;
  75. // the processing is complete and all of the resources on the page have finished downloading
  76. case 'complete':
  77. metricName = 'domComplete';
  78. phantomas.log('Performance timing: %j', window.performance.timing);
  79. break;
  80. default:
  81. phantomas.log('Performance timing: unhandled "%s" state!', readyState);
  82. return;
  83. }
  84. phantomas.setMetric(metricName, time, true);
  85. phantomas.log('Performance timing: document reached "%s" state after %d ms', readyState, time);
  86. phantomas.pushContext({
  87. type: metricName
  88. });
  89. });
  90. phantomas.spyEnabled(true);
  91. })(window.__phantomas);
  92. }, responseEndTime);
  93. });
  94. /**
  95. * Emit metrics with backend vs frontend time
  96. *
  97. * Performance Golden Rule:
  98. * "80-90% of the end-user response time is spent on the frontend. Start there."
  99. *
  100. * @see http://www.stevesouders.com/blog/2012/02/10/the-performance-golden-rule/
  101. */
  102. phantomas.on('report', function() {
  103. // The “backend” time is the time it takes the server to get the first byte back to the client.
  104. // The “frontend” time is measured from the last byte of the response (responseEnd) until all resources are fetched (domComplete)
  105. var backendTime = parseInt(phantomas.getMetric('timeToFirstByte'), 10),
  106. frontendTime = parseInt(phantomas.getMetric('domComplete'), 10),
  107. totalTime = backendTime + frontendTime,
  108. backendTimePercentage;
  109. if (totalTime === 0) {
  110. return;
  111. }
  112. backendTimePercentage = Math.round(backendTime / totalTime * 100);
  113. phantomas.setMetric('timeBackend', backendTimePercentage);
  114. phantomas.setMetric('timeFrontend', 100 - backendTimePercentage);
  115. phantomas.log('Performance timing: backend vs frontend time - %d% / %d%', backendTimePercentage, 100 - backendTimePercentage);
  116. });
  117. };