Upgrade to Pro — share decks privately, control downloads, hide ads and more …

ROOM service and SPAs with boomerang

ROOM service and SPAs with boomerang

Philip Tellis

June 10, 2015
Tweet

More Decks by Philip Tellis

Other Decks in Technology

Transcript

  1. R O O M S E R V I C

    E & S PA S BayJax @ Intuit 2015-06-10
  2. W H AT D O E S A PA G

    E L O O K L I K E O N T H E N E T W O R K ?
  3. H O W D O D I F F E

    R E N T B R O W S E R S H A N D L E PA R A L L E L I Z AT I O N ?
  4. W H I C H PA G E C O

    M P O N E N T S A F F E C T P E R C E I V E D L AT E N C Y ?
  5. A R E A N Y O F T H

    E M S P O F S ? • Static JavaScript files, external CSS files • Anything that blocks onload if you have scripts that run on onload
  6. N AV I G AT I O N T I

    M I N G P E R F O R M A N C E T I M I N G
  7. N AV I G AT I O N T I

    M I N G AVA I L A B I L I T Y • IE >= 9 • FF >= 7 • Chrome >= 6 • Safari >= 8 • Opera >= 15 • Latest Android, Blackberry, Opera Mobile, Chrome for Android, Firefox for Android, IE Mobile
  8. N AV I G AT I O N T I

    M I N G E X A M P L E var loadEventDuration = performance.timing.loadEventEnd - performance.timing.loadEventStart;
  9. R E S O U R C E T I

    M I N G P E R F O R M A N C E T I M E L I N E
  10. R E S O U R C E T I

    M I N G AVA I L A B I L I T Y • IE >= 10 • Chrome • Firefox >= 37 • Opera >= 16 • Latest Opera Mobile, Chrome for Android, IE Mobile
  11. F U N W I T H R E S

    O U R C E T I M I N G • Generate a RUM waterfall • Or a performance heatmap 
 https://github.com/zeman/perfmap • Calculate a cache-hit-ratio per resource • Identify problem resources
  12. L I M I TAT I O N S O

    F R E S O U R C E T I M I N G • Does not report on Domain Lookup, Connect, Timeout or Security errors • Inconsistent reporting of 4xx/5xx errors • Chrome: No ResourceTiming information • Firefox: All timestamps are non-zero but duration is 0 • Internet Explorer: All timestamps are non-zero • Doesn’t tell you if a response is a 304 or 200
  13. C O R S • Cross-domain resources only tell you

    start & end time • Set the Timing-Allow-Origin: * response header on the resource to report all timings • Do this for resources served from your CDN or if you’re a 3rd party C R O S S - O R I G I N R E S O U R C E S H A R I N G
  14. M E A S U R I N G X

    H R S function instrumentXHR() { var proxy_XMLHttpRequest, orig_XMLHttpRequest = window.XMLHttpRequest, readyStateMap; if (!orig_XMLHttpRequest) { // Nothing to instrument return; } readyStateMap = [ "uninitialized", "open", "responseStart", "domInteractive", "responseEnd" ]; // We could also inherit from window.XMLHttpRequest, but for this implementation, // we'll use composition proxy_XMLHttpRequest = function() { var req, perf = { timing: {}, resource: {} }, orig_open, orig_send; req = new orig_XMLHttpRequest; orig_open = req.open; orig_send = req.send; req.open = function(method, url, async) { if (async) { req.addEventListener('readystatechange', function() { perf.timing[readyStateMap[req.readyState]] = new Date().getTime(); }, false); } req.addEventListener('load', function() { perf.timing["loadEventEnd"] = new Date().getTime(); perf.resource.status = req.status; }, false); req.addEventListener('timeout', function() { perf.timing["timeout"] = new Date().getTime(); }, false); req.addEventListener('error', function() { perf.timing["error"] = new Date().getTime(); }, false); req.addEventListener('abort', function() { perf.timing["abort"] = new Date().getTime(); }, false); perf.resource.name = url; perf.resource.method = method; // call the original open method return orig_open.apply(req, arguments); }; req.send = function() { perf.timing["requestStart"] = new Date().getTime(); // call the original send method return orig_send.apply(req, arguments); }; req.performance = perf; return req; }; window.XMLHttpRequest = proxy_XMLHttpRequest; }
  15. function instrumentXHR { var proxy_XMLHttpRequest orig_XMLHttpRequest readyStateMap if (!orig_XMLHttpRequest //

    Nothing to instrument return } readyStateMap // We could also inherit from window.XMLHttpRequest, but for this implementation, // we'll use composition proxy_XMLHttpRequest var req orig_open orig_send req req perf req perf perf req req req perf perf }; req perf }; req return }; window.XMLHttpRequest } M E A S U R I N G X H R S TL;DR: Proxy XMLHttpRequest Intercept open() & send() and capture events
  16. A C T U A L I M P L

    E M E N TAT I O N I S M O R E C O M P L I C AT E D , W H AT F O L L O W S I S S I M P L I F I E D , B U T R E P R E S E N TAT I V E WA R N I N G ! S I M P L I F I E D C O D E F O L L O W S Complete code at https://github.com/lognormal/boomerang/blob/master/plugins/auto_xhr.js
  17. L E T ’ S B R E A K

    I T D O W N — P R O X Y var orig_XMLHttpRequest = window.XMLHttpRequest; var proxy_XMLHttpRequest = function() { var req, resource = { timing: {}, initiator: “xhr” }, orig_open, orig_send; req = new orig_XMLHttpRequest(); orig_open = req.open; orig_send = req.send; // define new open // define new send req.resource = resource; return req; }; window.XMLHttpRequest = proxy_XMLHttpRequest;
  18. L E T ’ S B R E A K

    I T D O W N — S E N D req.send = function() { resource.timing.requestStart = BOOMR.now(); // call the original send method return orig_send.apply(req, arguments); };
  19. L E T ’ S B R E A K

    I T D O W N — O P E N var a = document.createElement(“A”) req.open = function(method, url, async) { a.href = url; // resolve relative URLs // Default value of async is true if (async === undefined) { async = true; } if (async) { // add listener on readystatechange } // add listeners on load, timeout, error & abort resource.url = a.href; resource.method = method; // call the original open method return orig_open.apply(req, arguments); };
  20. R E A D Y S TAT E C H

    A N G E F O R A S Y N C R E Q U E S T S • 0 == uninitialized • 1 == open • 2 == responseStart • 3 == domInteractive • 4 == responseEnd
  21. B U T W E C A N D O

    M O R E O N M O D E R N B R O W S E R S res = performance.getEntriesByName(a.href); /* res.redirectStart, res.redirectEnd, res.fetchStart, res.domainLookupStart, res.domainLookupEnd, res.connectStart, res.connectEnd, res.requestStart, res.responseStart, res.responseEnd */
  22. I T ’ S 2 0 1 5 . T

    H E S I N S PA D O E S N O T S TA N D F O R S I M P L E !
  23. • The initial XHR fetches XML or JSON • This

    may reference Images, styles or JS Modules that need to be fetched • The combination of all these resources initiated by a user action is what we really want to measure
  24. E N T E R T H E 
 M

    U TAT I O N O B S E R V E R
  25. M U TAT I O N O B S E

    R V E R var o = {observer: null, timer: null}; function done(mutations) { if (o.timer) { clearTimeout(o.timer); o.timer = null; } if (callback) { callback.call(callback_ctx, mutations, callback_data); callback = null; } if (o.observer) { o.observer.disconnect(); o.observer = null; } } o.observer = new MutationObserver(done); if (timeout) { o.timer = setTimeout(done, o.timeout); } o.observer.observe(el, config);
  26. The rest of the code is complicated, you can see

    it at https://github.com/lognormal/boomerang/blob/master/plugins/auto_xhr.js but tl;dr…
  27. M U TAT I O N O B S E

    R V E R • A MutationObserver listens for DOM mutations • Start listening when an XHR returns; set a short timeout • Attach load and error event handlers to and set timeouts on any IMG, SCRIPT, LINK & IFRAME nodes. • Cached resources may not fire events at all, so also check ResourceTiming • The load event does not tell you when the element became visible
  28. M U TAT I O N O B S E

    R V E R S U P P O R T
  29. I T G E T S M O R E

    C O M P L I C AT E D W H E N 2 + X H R S S TA R T C O N C U R R E N T LY
  30. S PA F R A M E W O R

    K S • Angular.js • Backbone.js • Ember.js • React.js • It’s Wednesday, here’s a new framework.js
  31. A N G U L A R . J S

    1. Listen for Angular.js routing events like $routeChangeStart 2. Call out to boomerang in your Angular app angular.module("app") .run(["$rootScope", function($rootScope) { var hadRouteChange = false; $rootScope.$on("$routeChangeStart", function() { hadRouteChange = true; }); function hookAngularBoomerang() { if (window.BOOMR && BOOMR.version) { if (BOOMR.plugins && BOOMR.plugins.Angular) { BOOMR.plugins.Angular.hook($rootScope, hadRouteChange); } return true; } } if (!hookAngularBoomerang()) { document.addEventListener("onBoomerangLoaded", hookAngularBoomerang); } }]);
  32. O T H E R F R A M E

    W O R K S A R E S I M I L A R
  33. T H E E N D R E S U

    LT I S V I S I B L E I N M P U L S E
  34. C AV E AT A B O U T T

    E S T I N G W I N D O W. P E R F O R M A N C E • On Firefox 31, checking window.performance in an anonymous iframe throws an exception • So we tried:
 if (“performance” in window) {}
  35. C AV E AT A B O U T T

    E S T I N G W I N D O W. P E R F O R M A N C E • But jslint complains about that • So we switched to:
 if (window.hasOwnProperty(“performance")) {
 // I know right?
 }
  36. C AV E AT A B O U T T

    E S T I N G W I N D O W. P E R F O R M A N C E • Which does not work on Internet Explorer 10+!# • So we ended up with:
 try {
 if ("performance" in window && window.performance)
 ...
 }
 catch(e) {
 // WTF
 }