SlideShare a Scribd company logo
BEYOND PAGE LEVEL METRICS
Buddy Brewer 
@bbrewer 
Philip Tellis 
@bluesmoon
GITHUB 
git clone <clone url> 
https://github.com/lognormal/ 
beyond-page-metrics 
https://github.com/lognormal/ 
boomerang
WHAT DOES A PAGE LOOK LIKE ON 
THE NETWORK?
HOW DO DIFFERENT BROWSERS 
HANDLE PARALLELIZATION?
WHICH PAGE COMPONENTS 
AFFECT PERCEIVED LATENCY?
ARE ANY OF THEM SPOFS? 
• Static JavaScript files, external CSS files 
• Anything that blocks onload if you have scripts that 
run on onload
CAN’T WE GET THIS 
ALREADY? 
WHY DO WE 
NEED RUM?
San Francisco 
London 
Paris 
Gilroy 
Tellisford 
Eze
Fast 
Connections 
Slow 
Connections
Common Browsers 
Uncommon Browsers
≠
PERFORMANCE TIMING 
NAVIGATION TIMING
NAVIGATION TIMING 
AVAI LABI L ITY 
• IE >= 9 
• FF >= 7 
• Chrome >= 6 
• Opera >= 15 
• Latest Android, Blackberry, 
Opera Mobile, Chrome for 
Android, Firefox for 
Android, IE Mobile
NAVIGATION TIMING EXAMPLE 
var loadEventDuration = performance.timing.loadEventEnd - ! 
performance.timing.loadEventStart;
PERFORMANCE TIMELINE 
RESOURCE TIMING
RESOURCE TIMING AVAILABILITY 
• IE >= 10 
• Chrome 
• Opera >= 16 
• Latest Opera Mobile, Chrome for Android, IE Mobile
RESOURCE TIMING GETS US 
INTERESTING THINGS 
• Generate a complete waterfall 
https://github.com/andydavies/waterfall 
• Calculate a cache-hit-ratio per resource 
• Identify problem resources
CORS: CROSS-ORIGIN RESOURCE 
SHARING 
• Cross-domain resources only tell you start & end time 
• Timing-Allow-Origin: *
LIMITATIONS OF RESOURCE TIMING 
• Does not report resources that error out, which is one 
of the things we care about 
• Doesn’t tell you if a response is a 304 or 200
CAVEAT ABOUT TESTING 
WINDOW.PERFORMANCE 
• On Firefox 31, checking window.performance in an 
anonymous iframe throws an exception 
• So we tried: 
if (“performance” in window) {}
CAVEAT ABOUT TESTING 
WINDOW.PERFORMANCE 
• But jslint complains about that 
• So we switched to: 
if (window.hasOwnProperty(“performance")) { 
// I know right? 
}
CAVEAT ABOUT TESTING 
WINDOW.PERFORMANCE 
• Which does not work on Internet Explorer 10+!# 
• So we ended up with: 
try { 
if ("performance" in window && window.performance) 
... 
} 
catch(e) { 
// WTF 
}
MEASURING XHRS 
!! 
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;! 
}
MEASURING XHRS 
!! 
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 
In Short: 
Proxy XMLHttpRequest 
Capture open(),send() 
and events 
! var ! 
!! ! req 
! ! orig_open 
!! ! orig_send 
! ! req 
! ! ! 
! ! ! ! req 
! ! ! ! ! perf 
! ! ! ! 
!! ! ! 
! ! ! req 
! ! ! ! perf 
! ! ! ! perf 
! ! ! 
! ! ! req 
! ! ! req 
!! ! ! req 
! ! ! perf 
!! ! ! perf 
! ! ! 
! ! ! 
!! ! }; 
! ! req 
!! ! ! perf 
! ! ! 
! ! ! 
!! ! }; 
!! ! req 
! ! return 
!! };! 
! window.XMLHttpRequest 
}
MEASURING A SINGLE OBJECT 
var url = 'http://www.buddybrewer.com/images/buddy.png';! 
var me = performance.getEntriesByName(url)[0];! 
var timings = { ! 
loadTime: me.duration, ! 
dns: me.domainLookupEnd - me.domainLookupStart, ! 
tcp: me.connectEnd - me.connectStart, ! 
waiting: me.responseStart - me.requestStart, ! 
fetch: me.responseEnd - me.responseStart! 
}
MEASURING A COLLECTION OF 
OBJECTS 
var i, first, last, entries = performance.getEntries();! 
for (i=0; i<entries.length; i++) {! 
if (entries[i].name.indexOf('platform.twitter.com') != -1) {! 
if (first === undefined) ! 
first = entries[i];! 
if (last === undefined) ! 
last = entries[i];! 
if (entries[i].startTime < first.startTime) ! 
first = entries[i];! 
if (entries[i].responseEnd > last.responseEnd) ! 
last = entries[i];! 
}! 
}! 
console.log('Took ' + (last.responseEnd - first.startTime) + ' ms');
TIME BY INITIATOR TYPE 
function timeByInitiatorType() {! 
var type, res = performance.getEntriesByType("resource"), o = {};! 
for (var i=0;i<res.length;i++) {! 
if (o[res[i].initiatorType]) {! 
o[res[i].initiatorType].duration += res[i].duration;! 
if (res[i].duration > o[res[i].initiatorType].max) o[res[i].initiatorType].max 
= res[i].duration;! 
if (res[i].duration < o[res[i].initiatorType].min) o[res[i].initiatorType].min 
= res[i].duration;! 
o[res[i].initiatorType].resources += 1;! 
o[res[i].initiatorType].avg = o[res[i].initiatorType].duration / 
o[res[i].initiatorType].resources;! 
} else {! 
o[res[i].initiatorType] = {"duration": res[i].duration, "resources": 1, "avg": 
res[i].duration, "max": res[i].duration, "min": res[i].duration};! 
}! 
}! 
return o;! 
}
FIND THE SLOWEST RESOURCES ON 
THE PAGE 
function findSlowResources(ms, num) {! 
var res = performance.getEntriesByType("resource"), arr = [], i;! 
for (i=0; i<res.length; i++) {! 
if (res[i].duration > ms) arr.push(res[i]);! 
}! 
arr.sort(function(a,b){ return b.duration - a.duration });! 
return arr.slice(0, num);! 
}
FIND POTENTIAL SPOFS 
function findPossibleSpofs(ms) {! 
var res = performance.getEntriesByType("resource"), spofs = [];! 
for (var i=0;i<res.length;i++) {! 
var isSpof = true;! 
for (var j=0;j<res.length;j++) {! 
if (res[i].name != res[j].name && ! 
(res[j].startTime > res[i].startTime && res[j].startTime < res[i].responseEnd) ||! 
(res[j].endTime > res[i].startTime && res[j].endTime < res[i].responseEnd) ||! 
(res[j].startTime < res[i].startTime && res[j].endTime > res[i].responseEnd)) {! 
isSpof = false;! 
}! 
}! 
if (isSpof && res[i].duration > ms) spofs.push(res[i]);! 
}! 
return spofs;! 
} 
This code is just an example, however it has O(n2) complexity, which might be very slow 
running in production.
FIND SLOW HOSTS 
function findPerfByHost() {! 
var res = performance.getEntriesByType("resource"), obj={};! 
for (var i=0;i<res.length;i++) {! 
var start = res[i].name.indexOf("://")+3,! 
host = res[i].name.substring(start),! 
end = host.indexOf("/");! 
host = host.substring(0,end);! 
if (obj[host]) {! 
obj[host].resources += 1;! 
obj[host].duration += res[i].duration;! 
if (res[i].duration < obj[host].min) obj[host].min = res[i].duration;! 
if (res[i].duration > obj[host].max) obj[host].max = res[i].duration;! 
obj[host].avg = obj[host].duration / obj[host].resources;! 
}! 
else {! 
obj[host] = {"duration": res[i].duration, "min": res[i].duration, "max": res[i].duration, 
"avg": res[i].duration, "resources": 1};! 
}! 
}! 
return obj;! 
}
PERFORMANCE TIMING 
USER TIMING
USER TIMING 
AVAI LABI L ITY 
• IE >= 10 
• Chrome >= 25 
• Opera >= 15 
• Latest Opera Mobile, 
Chrome for Android, IE 
Mobile
USER TIMING EXAMPLE 
performance.mark(‘event_start');! 
! 
setTimeout(function() {! 
performance.mark('event_end');! 
performance.measure(‘time_to_event’);! 
performance.measure('event_duration','event_start',‘event_end');! 
console.log('Event took ' + ! 
performance.getEntriesByName(‘event_duration')[0].duration + ! 
' ms');! 
}, 1000);
PERFORMANCE MANAGEMENT IN 
THREE STEPS 
How Fast Am I? How Fast Should I Be? How Do I Get There?
HOW FAST SHOULD I BE?
WHAT IS A CONVERSION? 
TRACKING CONVERSIONS 
Orders 
Shares, Likes, Comments 
Page Views 
Subscriptions 
Signups 
Card Additions 
Video Plays
SPEED STRONGLY CORRELATES TO CONVERSIONS 
MEASURING THE IMPACT OF SPEED
THIS MEANS WE 
CAN MEASURE 
PATIENCE
EXAMPLE 
Time Range: 1 Month 
Median Load Time: 4.12 
Visits: 25M 
Conversion Rate: 2.71% 
Average Order: $100
SPEED INCREASES DRIVE BUSINESS IMPROVEMENTS 
CAN WE DO BETTER? 
Median Load Time: 4.12 
Total Conversion Rate: 2.71% 
Conversion Rate @ 3.0s: 4.88%
WHAT ARE WE PLAYING FOR? 
Total Conversion Rate: 2.71% 
Best Case Conversion Rate: 4.88% 
Conversion Gap: 2.32% 
Visits: 25M 
AOV: $100
(4.88% - 2.71%) * 25M * $100 = $54.25M
1 second = $54M
BUT
POTENTIAL VS REALISTIC GOALS 
100TH PERCENTILE? 
Median Load Time: 4.12 
Total Conversion Rate: 2.71% 
Conversion Rate @ 3.0s: 4.88%
REALISTIC, ITERATIVE GOALS 
Target Load Time: 4 seconds (vs 3 seconds) 
Percentile at 4 sec: 49th 
Target Percentile: 60th (vs 100th percentile) 
Percentile Gap: 11%
(4.88% - 2.71%) * (11% * 25M) * $100 = $6M
Improving from 
4.12 sec @ 50th percentile 
to 
4.0 sec @ 60th percentile 
= 
$6M / month
Beyond Page Level Metrics
Thank You
ATTRIBUTIONS 
https://secure.flickr.com/photos/torkildr/3462607995 (servers) 
https://secure.flickr.com/photos/hackny/8038587477 (real users) 
https://secure.flickr.com/photos/isherwoodchris/3096255994 (NYC) 
https://secure.flickr.com/photos/motoxgirl/11972577704 (Countryside) 
https://secure.flickr.com/photos/98640399@N08/9287370881 (Fiber Optic) 
https://secure.flickr.com/photos/secretlondon/2592690167 (Acoustic Coupler) 
https://secure.flickr.com/photos/jenny-pics/2904201123 (Rum Bottle) 
https://secure.flickr.com/photos/bekathwia/2415018504 (Privacy Sweater) 
https://secure.flickr.com/photos/zigzaglens/3566054676 (Star Field)

More Related Content

PDF
Testing http calls with Webmock and VCR
KEY
TDD of HTTP Clients With WebMock
PDF
Go Concurrency
PPTX
Good karma: UX Patterns and Unit Testing in Angular with Karma
PDF
Google Back To Front: From Gears to App Engine and Beyond
PDF
Service workers
PDF
Effective Benchmarks
PDF
Selenium sandwich-3: Being where you aren't.
Testing http calls with Webmock and VCR
TDD of HTTP Clients With WebMock
Go Concurrency
Good karma: UX Patterns and Unit Testing in Angular with Karma
Google Back To Front: From Gears to App Engine and Beyond
Service workers
Effective Benchmarks
Selenium sandwich-3: Being where you aren't.

What's hot (20)

PDF
[1C1]Service Workers
ZIP
Palestra VCR
PDF
You do not need automation engineer - Sqa Days - 2015 - EN
PPT
Service Workers for Performance
PDF
PWA 與 Service Worker
PDF
Packaging Software, Puppet Labs Style - PuppetConf 2014
PDF
Making the most of your Test Suite
PPT
Real Time Event Dispatcher
PDF
Deploying on the cutting edge
PDF
Selenide alternative in Python - Introducing Selene [SeleniumCamp 2016]
KEY
Php resque
PDF
Webscraping with asyncio
PPT
Testing Javascript with Jasmine
KEY
Background Jobs with Resque
PDF
Designing net-aws-glacier
PDF
Trying Continuous Delivery - pyconjp 2012
PPTX
PDF
Get Grulping with JavaScript Task Runners
PDF
Jasmine - why JS tests don't smell fishy
PDF
DC |> Elixir Meetup - Going off the Rails into Elixir - Dan Ivovich
[1C1]Service Workers
Palestra VCR
You do not need automation engineer - Sqa Days - 2015 - EN
Service Workers for Performance
PWA 與 Service Worker
Packaging Software, Puppet Labs Style - PuppetConf 2014
Making the most of your Test Suite
Real Time Event Dispatcher
Deploying on the cutting edge
Selenide alternative in Python - Introducing Selene [SeleniumCamp 2016]
Php resque
Webscraping with asyncio
Testing Javascript with Jasmine
Background Jobs with Resque
Designing net-aws-glacier
Trying Continuous Delivery - pyconjp 2012
Get Grulping with JavaScript Task Runners
Jasmine - why JS tests don't smell fishy
DC |> Elixir Meetup - Going off the Rails into Elixir - Dan Ivovich
Ad

Similar to Beyond Page Level Metrics (20)

PDF
JavaScript Promise
PDF
FrontInBahia 2014: 10 dicas de desempenho para apps mobile híbridas
PDF
Stackup New Languages Talk: Ember is for Everybody
PDF
Velocity EU 2014 — Offline-first web apps
PDF
Unit Testing JavaScript Applications
PPTX
Make BDD great again
PDF
Go Concurrency
KEY
Node.js
PPTX
Callbacks, Promises, and Coroutines (oh my!): Asynchronous Programming Patter...
PDF
Unit Testing Express Middleware
PDF
How to test complex SaaS applications - The family july 2014
PDF
Performance patterns
PDF
Build web application by express
PDF
Monitoring for the masses
PDF
Functional Reactive Programming / Compositional Event Systems
PDF
Future of Web Apps: Google Gears
KEY
NodeJS
PDF
An Introduction to Go
PDF
Matthew Eernisse, NodeJs, .toster {webdev}
PDF
Load Testing with RedLine13: Or getting paid to DoS your own systems
JavaScript Promise
FrontInBahia 2014: 10 dicas de desempenho para apps mobile híbridas
Stackup New Languages Talk: Ember is for Everybody
Velocity EU 2014 — Offline-first web apps
Unit Testing JavaScript Applications
Make BDD great again
Go Concurrency
Node.js
Callbacks, Promises, and Coroutines (oh my!): Asynchronous Programming Patter...
Unit Testing Express Middleware
How to test complex SaaS applications - The family july 2014
Performance patterns
Build web application by express
Monitoring for the masses
Functional Reactive Programming / Compositional Event Systems
Future of Web Apps: Google Gears
NodeJS
An Introduction to Go
Matthew Eernisse, NodeJs, .toster {webdev}
Load Testing with RedLine13: Or getting paid to DoS your own systems
Ad

More from Philip Tellis (20)

PDF
Improving D3 Performance with CANVAS and other Hacks
PDF
Frontend Performance: Beginner to Expert to Crazy Person
PDF
Frontend Performance: De débutant à Expert à Fou Furieux
PDF
Frontend Performance: Expert to Crazy Person
PDF
Frontend Performance: Beginner to Expert to Crazy Person (San Diego Web Perf ...
PDF
Frontend Performance: Beginner to Expert to Crazy Person
PDF
Frontend Performance: Beginner to Expert to Crazy Person
PDF
Frontend Performance: Beginner to Expert to Crazy Person
PDF
mmm... beacons
PDF
RUM Distillation 101 -- Part I
PDF
Improving 3rd Party Script Performance With IFrames
PDF
Extending Boomerang
PDF
Abusing JavaScript to measure Web Performance, or, "how does boomerang work?"
PDF
The Statistics of Web Performance Analysis
PDF
Abusing JavaScript to Measure Web Performance
PDF
Rum for Breakfast
PDF
Analysing network characteristics with JavaScript
PDF
A Node.JS bag of goodies for analyzing Web Traffic
PDF
Input sanitization
PDF
Messing with JavaScript and the DOM to measure network characteristics
Improving D3 Performance with CANVAS and other Hacks
Frontend Performance: Beginner to Expert to Crazy Person
Frontend Performance: De débutant à Expert à Fou Furieux
Frontend Performance: Expert to Crazy Person
Frontend Performance: Beginner to Expert to Crazy Person (San Diego Web Perf ...
Frontend Performance: Beginner to Expert to Crazy Person
Frontend Performance: Beginner to Expert to Crazy Person
Frontend Performance: Beginner to Expert to Crazy Person
mmm... beacons
RUM Distillation 101 -- Part I
Improving 3rd Party Script Performance With IFrames
Extending Boomerang
Abusing JavaScript to measure Web Performance, or, "how does boomerang work?"
The Statistics of Web Performance Analysis
Abusing JavaScript to Measure Web Performance
Rum for Breakfast
Analysing network characteristics with JavaScript
A Node.JS bag of goodies for analyzing Web Traffic
Input sanitization
Messing with JavaScript and the DOM to measure network characteristics

Recently uploaded (20)

PDF
Network Security Unit 5.pdf for BCA BBA.
PDF
MIND Revenue Release Quarter 2 2025 Press Release
PDF
Architecting across the Boundaries of two Complex Domains - Healthcare & Tech...
PDF
Video forgery: An extensive analysis of inter-and intra-frame manipulation al...
PDF
Profit Center Accounting in SAP S/4HANA, S4F28 Col11
PDF
Electronic commerce courselecture one. Pdf
PDF
Approach and Philosophy of On baking technology
PDF
Encapsulation theory and applications.pdf
PDF
Encapsulation_ Review paper, used for researhc scholars
PPTX
20250228 LYD VKU AI Blended-Learning.pptx
PDF
Per capita expenditure prediction using model stacking based on satellite ima...
PPTX
MYSQL Presentation for SQL database connectivity
PPTX
Digital-Transformation-Roadmap-for-Companies.pptx
PDF
Mobile App Security Testing_ A Comprehensive Guide.pdf
PPTX
Big Data Technologies - Introduction.pptx
PPTX
Tartificialntelligence_presentation.pptx
PPTX
Programs and apps: productivity, graphics, security and other tools
PPTX
KOM of Painting work and Equipment Insulation REV00 update 25-dec.pptx
PDF
Reach Out and Touch Someone: Haptics and Empathic Computing
PDF
gpt5_lecture_notes_comprehensive_20250812015547.pdf
Network Security Unit 5.pdf for BCA BBA.
MIND Revenue Release Quarter 2 2025 Press Release
Architecting across the Boundaries of two Complex Domains - Healthcare & Tech...
Video forgery: An extensive analysis of inter-and intra-frame manipulation al...
Profit Center Accounting in SAP S/4HANA, S4F28 Col11
Electronic commerce courselecture one. Pdf
Approach and Philosophy of On baking technology
Encapsulation theory and applications.pdf
Encapsulation_ Review paper, used for researhc scholars
20250228 LYD VKU AI Blended-Learning.pptx
Per capita expenditure prediction using model stacking based on satellite ima...
MYSQL Presentation for SQL database connectivity
Digital-Transformation-Roadmap-for-Companies.pptx
Mobile App Security Testing_ A Comprehensive Guide.pdf
Big Data Technologies - Introduction.pptx
Tartificialntelligence_presentation.pptx
Programs and apps: productivity, graphics, security and other tools
KOM of Painting work and Equipment Insulation REV00 update 25-dec.pptx
Reach Out and Touch Someone: Haptics and Empathic Computing
gpt5_lecture_notes_comprehensive_20250812015547.pdf

Beyond Page Level Metrics

  • 2. Buddy Brewer @bbrewer Philip Tellis @bluesmoon
  • 3. GITHUB git clone <clone url> https://github.com/lognormal/ beyond-page-metrics https://github.com/lognormal/ boomerang
  • 4. WHAT DOES A PAGE LOOK LIKE ON THE NETWORK?
  • 5. HOW DO DIFFERENT BROWSERS HANDLE PARALLELIZATION?
  • 6. WHICH PAGE COMPONENTS AFFECT PERCEIVED LATENCY?
  • 7. ARE ANY OF THEM SPOFS? • Static JavaScript files, external CSS files • Anything that blocks onload if you have scripts that run on onload
  • 8. CAN’T WE GET THIS ALREADY? WHY DO WE NEED RUM?
  • 9. San Francisco London Paris Gilroy Tellisford Eze
  • 10. Fast Connections Slow Connections
  • 12.
  • 14. NAVIGATION TIMING AVAI LABI L ITY • IE >= 9 • FF >= 7 • Chrome >= 6 • Opera >= 15 • Latest Android, Blackberry, Opera Mobile, Chrome for Android, Firefox for Android, IE Mobile
  • 15. NAVIGATION TIMING EXAMPLE var loadEventDuration = performance.timing.loadEventEnd - ! performance.timing.loadEventStart;
  • 17. RESOURCE TIMING AVAILABILITY • IE >= 10 • Chrome • Opera >= 16 • Latest Opera Mobile, Chrome for Android, IE Mobile
  • 18. RESOURCE TIMING GETS US INTERESTING THINGS • Generate a complete waterfall https://github.com/andydavies/waterfall • Calculate a cache-hit-ratio per resource • Identify problem resources
  • 19. CORS: CROSS-ORIGIN RESOURCE SHARING • Cross-domain resources only tell you start & end time • Timing-Allow-Origin: *
  • 20. LIMITATIONS OF RESOURCE TIMING • Does not report resources that error out, which is one of the things we care about • Doesn’t tell you if a response is a 304 or 200
  • 21. CAVEAT ABOUT TESTING WINDOW.PERFORMANCE • On Firefox 31, checking window.performance in an anonymous iframe throws an exception • So we tried: if (“performance” in window) {}
  • 22. CAVEAT ABOUT TESTING WINDOW.PERFORMANCE • But jslint complains about that • So we switched to: if (window.hasOwnProperty(“performance")) { // I know right? }
  • 23. CAVEAT ABOUT TESTING WINDOW.PERFORMANCE • Which does not work on Internet Explorer 10+!# • So we ended up with: try { if ("performance" in window && window.performance) ... } catch(e) { // WTF }
  • 24. MEASURING XHRS !! 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;! }
  • 25. MEASURING XHRS !! 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 In Short: Proxy XMLHttpRequest Capture open(),send() and events ! var ! !! ! req ! ! orig_open !! ! orig_send ! ! req ! ! ! ! ! ! ! req ! ! ! ! ! perf ! ! ! ! !! ! ! ! ! ! req ! ! ! ! perf ! ! ! ! perf ! ! ! ! ! ! req ! ! ! req !! ! ! req ! ! ! perf !! ! ! perf ! ! ! ! ! ! !! ! }; ! ! req !! ! ! perf ! ! ! ! ! ! !! ! }; !! ! req ! ! return !! };! ! window.XMLHttpRequest }
  • 26. MEASURING A SINGLE OBJECT var url = 'http://www.buddybrewer.com/images/buddy.png';! var me = performance.getEntriesByName(url)[0];! var timings = { ! loadTime: me.duration, ! dns: me.domainLookupEnd - me.domainLookupStart, ! tcp: me.connectEnd - me.connectStart, ! waiting: me.responseStart - me.requestStart, ! fetch: me.responseEnd - me.responseStart! }
  • 27. MEASURING A COLLECTION OF OBJECTS var i, first, last, entries = performance.getEntries();! for (i=0; i<entries.length; i++) {! if (entries[i].name.indexOf('platform.twitter.com') != -1) {! if (first === undefined) ! first = entries[i];! if (last === undefined) ! last = entries[i];! if (entries[i].startTime < first.startTime) ! first = entries[i];! if (entries[i].responseEnd > last.responseEnd) ! last = entries[i];! }! }! console.log('Took ' + (last.responseEnd - first.startTime) + ' ms');
  • 28. TIME BY INITIATOR TYPE function timeByInitiatorType() {! var type, res = performance.getEntriesByType("resource"), o = {};! for (var i=0;i<res.length;i++) {! if (o[res[i].initiatorType]) {! o[res[i].initiatorType].duration += res[i].duration;! if (res[i].duration > o[res[i].initiatorType].max) o[res[i].initiatorType].max = res[i].duration;! if (res[i].duration < o[res[i].initiatorType].min) o[res[i].initiatorType].min = res[i].duration;! o[res[i].initiatorType].resources += 1;! o[res[i].initiatorType].avg = o[res[i].initiatorType].duration / o[res[i].initiatorType].resources;! } else {! o[res[i].initiatorType] = {"duration": res[i].duration, "resources": 1, "avg": res[i].duration, "max": res[i].duration, "min": res[i].duration};! }! }! return o;! }
  • 29. FIND THE SLOWEST RESOURCES ON THE PAGE function findSlowResources(ms, num) {! var res = performance.getEntriesByType("resource"), arr = [], i;! for (i=0; i<res.length; i++) {! if (res[i].duration > ms) arr.push(res[i]);! }! arr.sort(function(a,b){ return b.duration - a.duration });! return arr.slice(0, num);! }
  • 30. FIND POTENTIAL SPOFS function findPossibleSpofs(ms) {! var res = performance.getEntriesByType("resource"), spofs = [];! for (var i=0;i<res.length;i++) {! var isSpof = true;! for (var j=0;j<res.length;j++) {! if (res[i].name != res[j].name && ! (res[j].startTime > res[i].startTime && res[j].startTime < res[i].responseEnd) ||! (res[j].endTime > res[i].startTime && res[j].endTime < res[i].responseEnd) ||! (res[j].startTime < res[i].startTime && res[j].endTime > res[i].responseEnd)) {! isSpof = false;! }! }! if (isSpof && res[i].duration > ms) spofs.push(res[i]);! }! return spofs;! } This code is just an example, however it has O(n2) complexity, which might be very slow running in production.
  • 31. FIND SLOW HOSTS function findPerfByHost() {! var res = performance.getEntriesByType("resource"), obj={};! for (var i=0;i<res.length;i++) {! var start = res[i].name.indexOf("://")+3,! host = res[i].name.substring(start),! end = host.indexOf("/");! host = host.substring(0,end);! if (obj[host]) {! obj[host].resources += 1;! obj[host].duration += res[i].duration;! if (res[i].duration < obj[host].min) obj[host].min = res[i].duration;! if (res[i].duration > obj[host].max) obj[host].max = res[i].duration;! obj[host].avg = obj[host].duration / obj[host].resources;! }! else {! obj[host] = {"duration": res[i].duration, "min": res[i].duration, "max": res[i].duration, "avg": res[i].duration, "resources": 1};! }! }! return obj;! }
  • 33. USER TIMING AVAI LABI L ITY • IE >= 10 • Chrome >= 25 • Opera >= 15 • Latest Opera Mobile, Chrome for Android, IE Mobile
  • 34. USER TIMING EXAMPLE performance.mark(‘event_start');! ! setTimeout(function() {! performance.mark('event_end');! performance.measure(‘time_to_event’);! performance.measure('event_duration','event_start',‘event_end');! console.log('Event took ' + ! performance.getEntriesByName(‘event_duration')[0].duration + ! ' ms');! }, 1000);
  • 35. PERFORMANCE MANAGEMENT IN THREE STEPS How Fast Am I? How Fast Should I Be? How Do I Get There?
  • 37. WHAT IS A CONVERSION? TRACKING CONVERSIONS Orders Shares, Likes, Comments Page Views Subscriptions Signups Card Additions Video Plays
  • 38. SPEED STRONGLY CORRELATES TO CONVERSIONS MEASURING THE IMPACT OF SPEED
  • 39. THIS MEANS WE CAN MEASURE PATIENCE
  • 40. EXAMPLE Time Range: 1 Month Median Load Time: 4.12 Visits: 25M Conversion Rate: 2.71% Average Order: $100
  • 41. SPEED INCREASES DRIVE BUSINESS IMPROVEMENTS CAN WE DO BETTER? Median Load Time: 4.12 Total Conversion Rate: 2.71% Conversion Rate @ 3.0s: 4.88%
  • 42. WHAT ARE WE PLAYING FOR? Total Conversion Rate: 2.71% Best Case Conversion Rate: 4.88% Conversion Gap: 2.32% Visits: 25M AOV: $100
  • 43. (4.88% - 2.71%) * 25M * $100 = $54.25M
  • 44. 1 second = $54M
  • 45. BUT
  • 46. POTENTIAL VS REALISTIC GOALS 100TH PERCENTILE? Median Load Time: 4.12 Total Conversion Rate: 2.71% Conversion Rate @ 3.0s: 4.88%
  • 47. REALISTIC, ITERATIVE GOALS Target Load Time: 4 seconds (vs 3 seconds) Percentile at 4 sec: 49th Target Percentile: 60th (vs 100th percentile) Percentile Gap: 11%
  • 48. (4.88% - 2.71%) * (11% * 25M) * $100 = $6M
  • 49. Improving from 4.12 sec @ 50th percentile to 4.0 sec @ 60th percentile = $6M / month
  • 52. ATTRIBUTIONS https://secure.flickr.com/photos/torkildr/3462607995 (servers) https://secure.flickr.com/photos/hackny/8038587477 (real users) https://secure.flickr.com/photos/isherwoodchris/3096255994 (NYC) https://secure.flickr.com/photos/motoxgirl/11972577704 (Countryside) https://secure.flickr.com/photos/98640399@N08/9287370881 (Fiber Optic) https://secure.flickr.com/photos/secretlondon/2592690167 (Acoustic Coupler) https://secure.flickr.com/photos/jenny-pics/2904201123 (Rum Bottle) https://secure.flickr.com/photos/bekathwia/2415018504 (Privacy Sweater) https://secure.flickr.com/photos/zigzaglens/3566054676 (Star Field)