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
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

What's hot

Making the most of your Test Suite
Making the most of your Test SuiteMaking the most of your Test Suite
Making the most of your Test Suite
ericholscher
 
Trying Continuous Delivery - pyconjp 2012
Trying Continuous Delivery - pyconjp 2012Trying Continuous Delivery - pyconjp 2012
Trying Continuous Delivery - pyconjp 2012
Toru Furukawa
 

What's hot (20)

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

Similar to Beyond Page Level Metrics

Stackup New Languages Talk: Ember is for Everybody
Stackup New Languages Talk: Ember is for EverybodyStackup New Languages Talk: Ember is for Everybody
Stackup New Languages Talk: Ember is for Everybody
Cory Forsyth
 
Build web application by express
Build web application by expressBuild web application by express
Build web application by express
Shawn Meng
 
Functional Reactive Programming / Compositional Event Systems
Functional Reactive Programming / Compositional Event SystemsFunctional Reactive Programming / Compositional Event Systems
Functional Reactive Programming / Compositional Event Systems
Leonardo Borges
 
An Introduction to Go
An Introduction to GoAn Introduction to Go
An Introduction to Go
Cloudflare
 
Matthew Eernisse, NodeJs, .toster {webdev}
Matthew Eernisse, NodeJs, .toster {webdev}Matthew Eernisse, NodeJs, .toster {webdev}
Matthew Eernisse, NodeJs, .toster {webdev}
.toster
 

Similar to Beyond Page Level Metrics (20)

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

More from Philip Tellis

Improving 3rd Party Script Performance With IFrames
Improving 3rd Party Script Performance With IFramesImproving 3rd Party Script Performance With IFrames
Improving 3rd Party Script Performance With IFrames
Philip Tellis
 
Analysing network characteristics with JavaScript
Analysing network characteristics with JavaScriptAnalysing network characteristics with JavaScript
Analysing network characteristics with JavaScript
Philip Tellis
 
A Node.JS bag of goodies for analyzing Web Traffic
A Node.JS bag of goodies for analyzing Web TrafficA Node.JS bag of goodies for analyzing Web Traffic
A Node.JS bag of goodies for analyzing Web Traffic
Philip Tellis
 

More from Philip Tellis (20)

Improving D3 Performance with CANVAS and other Hacks
Improving D3 Performance with CANVAS and other HacksImproving D3 Performance with CANVAS and other Hacks
Improving D3 Performance with CANVAS and other Hacks
 
Frontend Performance: Beginner to Expert to Crazy Person
Frontend Performance: Beginner to Expert to Crazy PersonFrontend Performance: Beginner to Expert to Crazy Person
Frontend Performance: Beginner to Expert to Crazy Person
 
Frontend Performance: De débutant à Expert à Fou Furieux
Frontend Performance: De débutant à Expert à Fou FurieuxFrontend Performance: De débutant à Expert à Fou Furieux
Frontend Performance: De débutant à Expert à Fou Furieux
 
Frontend Performance: Expert to Crazy Person
Frontend Performance: Expert to Crazy PersonFrontend Performance: Expert to Crazy Person
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 (San Diego Web Perf ...Frontend Performance: Beginner to Expert to Crazy Person (San Diego Web Perf ...
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 PersonFrontend Performance: Beginner to Expert to Crazy Person
Frontend Performance: Beginner to Expert to Crazy Person
 
Frontend Performance: Beginner to Expert to Crazy Person
Frontend Performance: Beginner to Expert to Crazy PersonFrontend Performance: Beginner to Expert to Crazy Person
Frontend Performance: Beginner to Expert to Crazy Person
 
Frontend Performance: Beginner to Expert to Crazy Person
Frontend Performance: Beginner to Expert to Crazy PersonFrontend Performance: Beginner to Expert to Crazy Person
Frontend Performance: Beginner to Expert to Crazy Person
 
mmm... beacons
mmm... beaconsmmm... beacons
mmm... beacons
 
RUM Distillation 101 -- Part I
RUM Distillation 101 -- Part IRUM Distillation 101 -- Part I
RUM Distillation 101 -- Part I
 
Improving 3rd Party Script Performance With IFrames
Improving 3rd Party Script Performance With IFramesImproving 3rd Party Script Performance With IFrames
Improving 3rd Party Script Performance With IFrames
 
Extending Boomerang
Extending BoomerangExtending Boomerang
Extending Boomerang
 
Abusing JavaScript to measure Web Performance, or, "how does boomerang work?"
Abusing JavaScript to measure Web Performance, or, "how does boomerang work?"Abusing JavaScript to measure Web Performance, or, "how does boomerang work?"
Abusing JavaScript to measure Web Performance, or, "how does boomerang work?"
 
The Statistics of Web Performance Analysis
The Statistics of Web Performance AnalysisThe Statistics of Web Performance Analysis
The Statistics of Web Performance Analysis
 
Abusing JavaScript to Measure Web Performance
Abusing JavaScript to Measure Web PerformanceAbusing JavaScript to Measure Web Performance
Abusing JavaScript to Measure Web Performance
 
Rum for Breakfast
Rum for BreakfastRum for Breakfast
Rum for Breakfast
 
Analysing network characteristics with JavaScript
Analysing network characteristics with JavaScriptAnalysing network characteristics with JavaScript
Analysing network characteristics with JavaScript
 
A Node.JS bag of goodies for analyzing Web Traffic
A Node.JS bag of goodies for analyzing Web TrafficA Node.JS bag of goodies for analyzing Web Traffic
A Node.JS bag of goodies for analyzing Web Traffic
 
Input sanitization
Input sanitizationInput sanitization
Input sanitization
 
Messing with JavaScript and the DOM to measure network characteristics
Messing with JavaScript and the DOM to measure network characteristicsMessing with JavaScript and the DOM to measure network characteristics
Messing with JavaScript and the DOM to measure network characteristics
 

Recently uploaded

State of ICS and IoT Cyber Threat Landscape Report 2024 preview
State of ICS and IoT Cyber Threat Landscape Report 2024 previewState of ICS and IoT Cyber Threat Landscape Report 2024 preview
State of ICS and IoT Cyber Threat Landscape Report 2024 preview
Prayukth K V
 

Recently uploaded (20)

Kubernetes & AI - Beauty and the Beast !?! @KCD Istanbul 2024
Kubernetes & AI - Beauty and the Beast !?! @KCD Istanbul 2024Kubernetes & AI - Beauty and the Beast !?! @KCD Istanbul 2024
Kubernetes & AI - Beauty and the Beast !?! @KCD Istanbul 2024
 
FIDO Alliance Osaka Seminar: Overview.pdf
FIDO Alliance Osaka Seminar: Overview.pdfFIDO Alliance Osaka Seminar: Overview.pdf
FIDO Alliance Osaka Seminar: Overview.pdf
 
Software Delivery At the Speed of AI: Inflectra Invests In AI-Powered Quality
Software Delivery At the Speed of AI: Inflectra Invests In AI-Powered QualitySoftware Delivery At the Speed of AI: Inflectra Invests In AI-Powered Quality
Software Delivery At the Speed of AI: Inflectra Invests In AI-Powered Quality
 
Ransomware Mallox [EN].pdf
Ransomware         Mallox       [EN].pdfRansomware         Mallox       [EN].pdf
Ransomware Mallox [EN].pdf
 
GraphRAG is All You need? LLM & Knowledge Graph
GraphRAG is All You need? LLM & Knowledge GraphGraphRAG is All You need? LLM & Knowledge Graph
GraphRAG is All You need? LLM & Knowledge Graph
 
Dev Dives: Train smarter, not harder – active learning and UiPath LLMs for do...
Dev Dives: Train smarter, not harder – active learning and UiPath LLMs for do...Dev Dives: Train smarter, not harder – active learning and UiPath LLMs for do...
Dev Dives: Train smarter, not harder – active learning and UiPath LLMs for do...
 
UiPath Test Automation using UiPath Test Suite series, part 3
UiPath Test Automation using UiPath Test Suite series, part 3UiPath Test Automation using UiPath Test Suite series, part 3
UiPath Test Automation using UiPath Test Suite series, part 3
 
Connector Corner: Automate dynamic content and events by pushing a button
Connector Corner: Automate dynamic content and events by pushing a buttonConnector Corner: Automate dynamic content and events by pushing a button
Connector Corner: Automate dynamic content and events by pushing a button
 
FIDO Alliance Osaka Seminar: Passkeys and the Road Ahead.pdf
FIDO Alliance Osaka Seminar: Passkeys and the Road Ahead.pdfFIDO Alliance Osaka Seminar: Passkeys and the Road Ahead.pdf
FIDO Alliance Osaka Seminar: Passkeys and the Road Ahead.pdf
 
FIDO Alliance Osaka Seminar: Passkeys at Amazon.pdf
FIDO Alliance Osaka Seminar: Passkeys at Amazon.pdfFIDO Alliance Osaka Seminar: Passkeys at Amazon.pdf
FIDO Alliance Osaka Seminar: Passkeys at Amazon.pdf
 
Transcript: Selling digital books in 2024: Insights from industry leaders - T...
Transcript: Selling digital books in 2024: Insights from industry leaders - T...Transcript: Selling digital books in 2024: Insights from industry leaders - T...
Transcript: Selling digital books in 2024: Insights from industry leaders - T...
 
UiPath New York Community Day in-person event
UiPath New York Community Day in-person eventUiPath New York Community Day in-person event
UiPath New York Community Day in-person event
 
State of ICS and IoT Cyber Threat Landscape Report 2024 preview
State of ICS and IoT Cyber Threat Landscape Report 2024 previewState of ICS and IoT Cyber Threat Landscape Report 2024 preview
State of ICS and IoT Cyber Threat Landscape Report 2024 preview
 
Le nuove frontiere dell'AI nell'RPA con UiPath Autopilot™
Le nuove frontiere dell'AI nell'RPA con UiPath Autopilot™Le nuove frontiere dell'AI nell'RPA con UiPath Autopilot™
Le nuove frontiere dell'AI nell'RPA con UiPath Autopilot™
 
Slack (or Teams) Automation for Bonterra Impact Management (fka Social Soluti...
Slack (or Teams) Automation for Bonterra Impact Management (fka Social Soluti...Slack (or Teams) Automation for Bonterra Impact Management (fka Social Soluti...
Slack (or Teams) Automation for Bonterra Impact Management (fka Social Soluti...
 
Unsubscribed: Combat Subscription Fatigue With a Membership Mentality by Head...
Unsubscribed: Combat Subscription Fatigue With a Membership Mentality by Head...Unsubscribed: Combat Subscription Fatigue With a Membership Mentality by Head...
Unsubscribed: Combat Subscription Fatigue With a Membership Mentality by Head...
 
Designing Great Products: The Power of Design and Leadership by Chief Designe...
Designing Great Products: The Power of Design and Leadership by Chief Designe...Designing Great Products: The Power of Design and Leadership by Chief Designe...
Designing Great Products: The Power of Design and Leadership by Chief Designe...
 
Accelerate your Kubernetes clusters with Varnish Caching
Accelerate your Kubernetes clusters with Varnish CachingAccelerate your Kubernetes clusters with Varnish Caching
Accelerate your Kubernetes clusters with Varnish Caching
 
JMeter webinar - integration with InfluxDB and Grafana
JMeter webinar - integration with InfluxDB and GrafanaJMeter webinar - integration with InfluxDB and Grafana
JMeter webinar - integration with InfluxDB and Grafana
 
Mission to Decommission: Importance of Decommissioning Products to Increase E...
Mission to Decommission: Importance of Decommissioning Products to Increase E...Mission to Decommission: Importance of Decommissioning Products to Increase E...
Mission to Decommission: Importance of Decommissioning Products to Increase E...
 

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
  • 50.
  • 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)