Transcript
Page 1: Beyond Page Level Metrics

B E Y O N D PA G E L E V E L M E T R I C S

Page 2: Beyond Page Level Metrics

Buddy Brewer @bbrewer

Philip Tellis @bluesmoon

Page 3: Beyond Page Level Metrics

G I T H U B

https://github.com/lognormal/beyond-page-metrics

https://github.com/lognormal/boomerang

git clone <clone url>

Page 4: Beyond Page Level Metrics

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 ?

Page 5: Beyond Page Level Metrics

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 ?

Page 6: Beyond Page Level Metrics

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 ?

Page 7: Beyond Page Level Metrics

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

Page 8: Beyond Page Level Metrics

W H Y D O W E N E E D R U M ?

C A N ’ T W E G E T T H I S A L R E A D Y ?

Page 9: Beyond Page Level Metrics

San Francisco London

Paris

Gilroy Tellisford

Eze

Page 10: Beyond Page Level Metrics

Fast Connections

Slow Connections

Page 11: Beyond Page Level Metrics

Common Browsers

Uncommon Browsers

Page 12: Beyond Page Level Metrics

Page 13: Beyond Page Level Metrics

N AV I G AT I O N T I M I N GP E R F O R M A N C E T I M I N G

Page 14: Beyond Page Level Metrics

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

• Opera >= 15

• Latest Android, Blackberry, Opera Mobile, Chrome for Android, Firefox for Android, IE Mobile

Page 15: Beyond Page Level Metrics

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;

Page 16: Beyond Page Level Metrics

R E S O U R C E T I M I N GP E R F O R M A N C E T I M E L I N E

Page 17: Beyond Page Level Metrics

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

• Opera >= 16

• Latest Opera Mobile, Chrome for Android, IE Mobile

Page 18: Beyond Page Level Metrics

R E S O U R C E T I M I N G G E T S U S I N T E R E S T I N G T H I N G S

• Generate a complete waterfall https://github.com/andydavies/waterfall

• Calculate a cache-hit-ratio per resource

• Identify problem resources

Page 19: Beyond Page Level Metrics

C O R S : 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

• Cross-domain resources only tell you start & end time

• Timing-Allow-Origin: *

Page 20: Beyond Page Level Metrics

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

Page 21: Beyond Page Level Metrics

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) {}

Page 22: Beyond Page Level Metrics

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

Page 23: Beyond Page Level Metrics

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 }

Page 24: Beyond Page Level Metrics

M E A S U R I N G X H R Sfunction 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;!}

Page 25: Beyond Page Level Metrics

M E A S U R I N G X H R Sfunction 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 }

In Short: Proxy XMLHttpRequest Capture open(),send() and events

Page 26: Beyond Page Level Metrics

M E A S U R I N G A S I N G L E O B J E C T

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

Page 27: Beyond Page Level Metrics

M E A S U R I N G A C O L L E C T I O N O F O B J E C T S

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');

Page 28: Beyond Page Level Metrics

T I M E B Y I N I T I AT O R T Y P E

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;!}

Page 29: Beyond Page Level Metrics

F I N D T H E S L O W E S T R E S O U R C E S O N T H E PA G E

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);!}

Page 30: Beyond Page Level Metrics

F I N D P O T E N T I A L S P O F S

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.

Page 31: Beyond Page Level Metrics

F I N D S L O W H O S T S

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;!}

Page 32: Beyond Page Level Metrics

U S E R T I M I N GP E R F O R M A N C E T I M I N G

Page 33: Beyond Page Level Metrics

U S E R T I M I N G AVA I L A B I L I T Y

• IE >= 10

• Chrome >= 25

• Opera >= 15

• Latest Opera Mobile, Chrome for Android, IE Mobile

Page 34: Beyond Page Level Metrics

U S E R T I M I N G E X A M P L E

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

Page 35: Beyond Page Level Metrics

P E R F O R M A N C E M A N A G E M E N T I N T H R E E S T E P S

How Fast Am I? How Fast Should I Be? How Do I Get There?

Page 36: Beyond Page Level Metrics

H O W FA S T S H O U L D I B E ?

Page 37: Beyond Page Level Metrics

T R A C K I N G C O N V E R S I O N SW H A T I S A C O N V E R S I O N ?

Orders Shares, Likes, Comments

Page Views Subscriptions

Signups Card Additions

Video Plays

Page 38: Beyond Page Level Metrics

M E A S U R I N G T H E I M PA C T O F S P E E DS P E E D S T R O N G LY C O R R E L A T E S T O C O N V E R S I O N S

Page 39: Beyond Page Level Metrics

T H I S M E A N S W E C A N M E A S U R E PAT I E N C E

Page 40: Beyond Page Level Metrics

E X A M P L E

Time Range: 1 Month

Median Load Time: 4.12

Visits: 25M

Conversion Rate: 2.71%

Average Order: $100

Page 41: Beyond Page Level Metrics

C A N W E D O B E T T E R ?S P E E D I N C R E A S E S D R I V E B U S I N E S S I M P R O V E M E N T S

Median Load Time: 4.12 Total Conversion Rate: 2.71% Conversion Rate @ 3.0s: 4.88%

Page 42: Beyond Page Level Metrics

W H AT A R E W E P L AY I N G F O R ?

Total Conversion Rate: 2.71%

Best Case Conversion Rate: 4.88%

Conversion Gap: 2.32%

Visits: 25M

AOV: $100

Page 43: Beyond Page Level Metrics

(4.88% - 2.71%) * 25M * $100 = $54.25M

Page 44: Beyond Page Level Metrics

1 second = $54M

Page 45: Beyond Page Level Metrics

BUT

Page 46: Beyond Page Level Metrics

1 0 0 T H P E R C E N T I L E ?P O T E N T I A L V S R E A L I S T I C G O A L S

Median Load Time: 4.12 Total Conversion Rate: 2.71% Conversion Rate @ 3.0s: 4.88%

Page 47: Beyond Page Level Metrics

R E A L I S T I C , I T E R AT I V E G O A L S

Target Load Time: 4 seconds (vs 3 seconds)

Percentile at 4 sec: 49th

Target Percentile: 60th (vs 100th percentile)

Percentile Gap: 11%

Page 48: Beyond Page Level Metrics

(4.88% - 2.71%) * (11% * 25M) * $100 = $6M

Page 49: Beyond Page Level Metrics

Improving from 4.12 sec @ 50th percentile

to 4.0 sec @ 60th percentile

= $6M / month

Page 50: Beyond Page Level Metrics
Page 51: Beyond Page Level Metrics

Thank You

Page 52: Beyond Page Level Metrics

AT T R I B U T I O N S

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)


Top Related