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)

Beyond Page Level Metrics

  • 1.
  • 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 APAGE LOOK LIKE ON THE NETWORK?
  • 5.
    HOW DO DIFFERENTBROWSERS HANDLE PARALLELIZATION?
  • 6.
    WHICH PAGE COMPONENTS AFFECT PERCEIVED LATENCY?
  • 7.
    ARE ANY OFTHEM SPOFS? • Static JavaScript files, external CSS files • Anything that blocks onload if you have scripts that run on onload
  • 8.
    CAN’T WE GETTHIS ALREADY? WHY DO WE NEED RUM?
  • 9.
    San Francisco London Paris Gilroy Tellisford Eze
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
    NAVIGATION TIMING AVAILABI 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;
  • 16.
  • 17.
    RESOURCE TIMING AVAILABILITY • IE >= 10 • Chrome • Opera >= 16 • Latest Opera Mobile, Chrome for Android, IE Mobile
  • 18.
    RESOURCE TIMING GETSUS 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 RESOURCETIMING • 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 SINGLEOBJECT 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 COLLECTIONOF 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 INITIATORTYPE 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 SLOWESTRESOURCES 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;! }
  • 32.
  • 33.
    USER TIMING AVAILABI 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?
  • 36.
  • 37.
    WHAT IS ACONVERSION? TRACKING CONVERSIONS Orders Shares, Likes, Comments Page Views Subscriptions Signups Card Additions Video Plays
  • 38.
    SPEED STRONGLY CORRELATESTO 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 DRIVEBUSINESS IMPROVEMENTS CAN WE DO BETTER? Median Load Time: 4.12 Total Conversion Rate: 2.71% Conversion Rate @ 3.0s: 4.88%
  • 42.
    WHAT ARE WEPLAYING 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.
  • 45.
  • 46.
    POTENTIAL VS REALISTICGOALS 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.12sec @ 50th percentile to 4.0 sec @ 60th percentile = $6M / month
  • 51.
  • 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)