Using Modern Brower APIs to Monitor and Improve the Performance of your Web Applications
nic jansma | nicj.net | @nicj
Nic Jansma
How do we measure performance?
(circa 2010)
var elapsedTime = Date.now() - startTime;
Founded 2010 to give developers the ability to assess and understand performance characteristics of their web apps
The mission of the Web Performance Working Group is to provide methods to measure aspects of application performance of user agent features and APIs
Microsoft, Google, Mozilla, Opera, Facebook, Netflix, etc
Date.now()
requestAnimationFrame()
setTimeout(...,0)
: setImmediate()
rel="preconnect" rel="preload"
lazyload
www.w3.org/TR/navigation-timing
Goal: Expose accurate performance metrics describing your visitor's page load experience
Current status: Recommendation
Upcoming: NavigationTiming2
(this isn't accurate)
<html><head><script>
var start = new Date().getTime();
function onLoad {
var pageLoadTime = (new Date().getTime()) - start;
}
body.addEventListener(“load”, onLoad, false);
</script>...</html>
Date().getTime()
is not reliableDOMHighResTimeStamp
Date | DOMHighResTimeStamp |
|
---|---|---|
Accessed Via | Date().getTime() |
performance.now() |
Resolution | millisecond | sub-millisecond |
Start | Unix epoch | navigationStart |
Monotonically Non-decreasing | No | Yes |
Affected by user's clock | Yes | No |
Example | 1420147524606 |
3392.275999998674 |
window.performance.navigation
interface PerformanceNavigation {
const unsigned short TYPE_NAVIGATE = 0;
const unsigned short TYPE_RELOAD = 1;
const unsigned short TYPE_BACK_FORWARD = 2;
const unsigned short TYPE_RESERVED = 255;
readonly attribute unsigned short type;
readonly attribute unsigned short redirectCount;
};
window.performance.timing
interface PerformanceTiming {
readonly attribute unsigned long long navigationStart;
readonly attribute unsigned long long unloadEventStart;
readonly attribute unsigned long long unloadEventEnd;
readonly attribute unsigned long long redirectStart;
readonly attribute unsigned long long redirectEnd;
readonly attribute unsigned long long fetchStart;
readonly attribute unsigned long long domainLookupStart;
readonly attribute unsigned long long domainLookupEnd;
readonly attribute unsigned long long connectStart;
readonly attribute unsigned long long connectEnd;
readonly attribute unsigned long long secureConnectionStart;
readonly attribute unsigned long long requestStart;
readonly attribute unsigned long long responseStart;
readonly attribute unsigned long long responseEnd;
readonly attribute unsigned long long domLoading;
readonly attribute unsigned long long domInteractive;
readonly attribute unsigned long long domContentLoadedEventStart;
readonly attribute unsigned long long domContentLoadedEventEnd;
readonly attribute unsigned long long domComplete;
readonly attribute unsigned long long loadEventStart;
readonly attribute unsigned long long loadEventEnd;
};
function onLoad() {
if ('performance' in window && 'timing' in window.performance) {
setTimeout(function() {
var t = window.performance.timing;
var ntData = {
redirect: t.redirectEnd - t.redirectStart,
dns: t.domainLookupEnd - t.domainLookupStart,
connect: t.connectEnd - t.connectStart,
ssl: t.secureConnectionStart ? (t.connectEnd - secureConnectionStart) : 0,
request: t.responseStart - t.requestStart,
response: t.responseEnd - t.responseStart,
dom: t.loadEventStart - t.responseEnd,
total: t.loadEventEnd - t.navigationStart
};
}, 0);
}
}
DIY / Open Source
Collects beacons + maps (statsd) + forwards (extensible)
Collects beacons
"generation time" = responseEnd - requestStart
Runs on top of WebPageTest
fetchStart
instead of navigationStart
unless you're interested in redirects, tab init time, etcloadEventEnd
will be 0 until after the body's load
event has finished (so you can't measure it in the load
event)requestEnd
" is invisible to us (the server sees it)secureConnectionStart
isn't available in IEresponseEnd
event may be 0 duration because some browsers speculatively pre-fetch home pages (and don't report the correct timings)onbeforeunload
isn't 100% reliable for sending datawww.w3.org/TR/navigation-timing-2
DRAFT
Builds on NavigationTiming:
Goal: Expose sub-resource performance metrics
Current status: Working Draft
For dynamically inserted content, you could time how long it took from DOM insertion to the element’s onLoad event
(this isn't practical for all content)
var start = new Date().getTime();
var image1 = new Image();
var resourceTiming = function() {
var now = new Date().getTime();
var latency = now - start;
alert("End to end resource fetch: " + latency);
};
image1.onload = resourceTiming;
image1.src = 'http://www.w3.org/Icons/w3c_main.png';
Date().getTime()
is not reliablewindow.performance.getEntries()
interface PerformanceEntry {
readonly attribute DOMString name;
readonly attribute DOMString entryType;
readonly attribute DOMHighResTimeStamp startTime;
readonly attribute DOMHighResTimeStamp duration;
};
interface PerformanceResourceTiming : PerformanceEntry {
readonly attribute DOMString initiatorType;
readonly attribute DOMHighResTimeStamp redirectStart;
readonly attribute DOMHighResTimeStamp redirectEnd;
readonly attribute DOMHighResTimeStamp fetchStart;
readonly attribute DOMHighResTimeStamp domainLookupStart;
readonly attribute DOMHighResTimeStamp domainLookupEnd;
readonly attribute DOMHighResTimeStamp connectStart;
readonly attribute DOMHighResTimeStamp connectEnd;
readonly attribute DOMHighResTimeStamp secureConnectionStart;
readonly attribute DOMHighResTimeStamp requestStart;
readonly attribute DOMHighResTimeStamp responseStart;
readonly attribute DOMHighResTimeStamp responseEnd;
};
www.w3.org/TR/performance-timeline
Goal: Unifying interface to access and retrieve performance metrics
Current status: Recommendation
window.performance
getEntries()
: Gets all entries in the timelinegetEntriesByType(type)
: Gets all entries of the specified type (eg resource
, mark
, measure
)getEntriesByName(name)
: Gets all entries with the specified name (eg URL or mark name)window.performance.getEntriesByType("resource")[0]
{
connectEnd: 566.357000003336,
connectStart: 566.357000003336,
domainLookupEnd: 566.357000003336,
domainLookupStart: 566.357000003336,
duration: 4.275999992387369,
entryType: "resource",
fetchStart: 566.357000003336,
initiatorType: "img",
name: "https://www.foo.com/foo.png",
redirectEnd: 0,
redirectStart: 0,
requestStart: 568.4959999925923,
responseEnd: 570.6329999957234,
responseStart: 569.4220000004862,
secureConnectionStart: 0,
startTime: 566.357000003336
}
initiatorType
localName
of that element:
img
link
script
css
: url()
, @import
xmlhttprequest
onresourcetimingbufferfull
eventsetResourceTimingBufferSize(n)
and clearResourceTimings()
can be used to modify itsetResourceTimingBufferSize(99999999)
as this can lead to browser memory growing
unboundJSON.stringify()
'dConverts:
{
"responseEnd":323.1100000002698,
"responseStart":300.5000000000000,
"requestStart":252.68599999981234,
"secureConnectionStart":0,
"connectEnd":0,
"connectStart":0,
"domainLookupEnd":0,
"domainLookupStart":0,
"fetchStart":252.68599999981234,
"redirectEnd":0,
"redirectStart":0,
"duration":71.42400000045745,
"startTime":252.68599999981234,
"entryType":"resource",
"initiatorType":"script",
"name":"http://foo.com/js/foo.js"
}
To:
{
"http://": {
"foo.com/": {
"js/foo.js": "370,1z,1c",
"css/foo.css": "48c,5k,14"
},
"moo.com/moo.gif": "312,34,56"
}
}
Overall, compresses ResourceTiming data down to 15% of its original size
fetchStart
and responseEnd
attributesTiming-Allow-Origin
headerTiming-Allow-Origin = "Timing-Allow-Origin" ":" origin-list-or-null | "*"
duration
includes Blocking time!duration
, but this is all you get with cross-origin resources.Calculate:
var waitTime = 0;
if (res.connectEnd && res.connectEnd === res.fetchStart)
{
waitTime = res.requestStart - res.connectEnd;
}
else if (res.domainLookupStart)
{
waitTime = res.domainLookupStart - res.fetchStart;
}
Timing-Allow-Origin
window.performance.timing
)duration
attribute includes Blocking time (when a resource is
behind other resources on the same socket)IFRAME
will have its own ResourceTiming data, and those resources
won't be included in the parent FRAME/document. So you'll need to traverse the
document frames to get all resources. See
github.com/nicjansma/resourcetiming-compression.js
for an exampleabout:blank
, javascript:
URLs will be seen in RT dataGoal: Standardized interface to note timestamps ("marks") and durations ("measures")
Current status: Recommendation
var start = new Date().getTime();
// do stuff
var now = new Date().getTime();
var duration = now - start;
Date().getTime()
is not reliablewindow.performance
partial interface Performance {
void mark(DOMString markName);
void clearMarks(optional DOMString markName);
void measure(DOMString measureName, optional DOMString startMark,
optional DOMString endMark);
void clearMeasures(optional DOMString measureName);
};
// mark
performance.mark("start");
performance.mark("end");
performance.mark("another");
performance.mark("another");
performance.mark("another");
// retrieve
performance.getEntriesByType("mark");
[
{
"duration":0,
"startTime":150384.48100000096,
"entryType":"mark",
"name":"start"
},
{
"duration":0,
"startTime":150600.5250000013,
"entryType":"mark",
"name":"end"
},
...
]
// measure
performance.mark("start");
// do work
performance.mark("start2");
// measure from "now" to the "start" mark
performance.measure("time to do stuff", "start");
// measure from "start2" to the "start" mark
performance.measure("time from start to start2", "start", "start2");
// retrieval - specific
performance.getEntriesByName("time from start to start2", "measure");
[
{
"duration":4809.890999997151,
"startTime":145287.66500000347,
"entryType":"measure",
"name":"time from start to start2"
}
]
PerformanceTimeline
, so marks
and measures
are in the PerformanceTimeline along with other eventsDOMHighResTimestamp
instead of Date
so sub-millisecond, monotonically non-decreasing,
etc
_trackTiming(...)
)