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

ROOM service and SPAs with boomerang

ROOM service and SPAs with boomerang

Avatar for Philip Tellis

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
 }