boomerang always comes back, except when it hits something.
Summary
boomerang is a JavaScript library that measures the page load time experienced by
real users, commonly called RUM (Real User Measurement). It has the ability to
send this data back to your server for further analysis. With boomerang, you
find out exactly how fast your users think your site is.
Apart from page load time, boomerang measures performance timings, metrics and
characteristics of your user's web browsing experience. All you have to do is
include it in your web pages and call the BOOMR.init()
method. Once the
performance data is captured, it will be beaconed to your chosen URL.
boomerang is designed to be a performant and flexible library that can be adapted
to your site's needs. It has an extensive plugin architecture, and works with
both traditional and modern websites (including Single Page Apps).
boomerang's goal is to not affect the load time of the page (avoiding the
Observer Effect).
It can be loaded in an asynchronous way that will not delay the page load even
if boomerang.js
is unavailable.
Features
- Supports:
- IE 6+, Edge, all major versions of Firefox, Chrome, Opera, and Safari
- Desktop and mobile devices
- Captures (all optional):
- Page characteristics such as the URL and Referrer
- Overall page load times (via NavigationTiming if available)
- DNS, TCP, Request and Response timings (via NavigationTiming)
- Browser characteristics such as screen size, orientation, memory usage, visibility state
- DOM characteristics such as the number of nodes, HTML length, number of images, scripts, etc
- ResourceTiming data (to reconstruct the page's Waterfall)
- Bandwidth
- Mobile connection data
- DNS latency
- JavaScript Errors
- XMLHttpRequest instrumentation
- Third-Party analytics providers IDs
- Single Page App interactions
Usage
boomerang can be included on your page in one of two ways: synchronously or asynchronously.
The asynchronous method is recommended.
The simple synchronous way
<script src="boomerang.js"></script>
<script src="plugins/rt.js"></script>
<script>
BOOMR.init({
beacon_url: "http://yoursite.com/beacon/"
});
</script>
Note: You must include at least one plugin (it doesn't have to be RT
) or
else the beacon will never fire.
Each plugin has its own configuration as well -- these configuration options
should be included in the BOOMR.init()
call:
BOOMR.init({
beacon_url: "http://yoursite.com/beacon/",
ResourceTiming: {
enabled: true,
clearOnBeacon: true
}
});
The faster, more involved, asynchronous way
Loading boomerang asynchronously ensures that even if boomerang.js
is
unavailable (or loads slowly), your host page will not be affected.
1. Add a plugin to init your code
Create a plugin (or use the sample zzz-last-plugin.js
) with a call
to BOOMR.init
:
BOOMR.init({
config: parameters,
...
});
BOOMR.t_end = new Date().getTime();
You could also include any other code you need. For example, you could include
a timer to measure when boomerang has finished loading (as above).
2. Build boomerang
The build process bundles boomerang.js
and all of the plugins
listed in plugins.json
(in that order).
To build boomerang with all of your desired plugins, you would run:
grunt clean build
This creates a deployable boomerang in the build
directory, e.g. build/boomerang-<version>.min.js
.
Install this file on your web server or origin server where your CDN can pick it
up. Set a far future
max-age
header for it. This file will never change.
3. Asynchronously include the script on your page
There are two methods of asynchronously including boomerang on your page: by
adding it to your main document, or via the IFRAME/Preload method.
The former method could block your onload
event (affecting the measured
performance of your page), so the later method is recommended.
3.1. Adding it to the main document
Include the following code at the top of your HTML document:
<script>
(function(d, s) {
var js = d.createElement(s),
sc = d.getElementsByTagName(s)[0];
js.src="http://your-cdn.host.com/path/to/boomerang-<version>.js";
sc.parentNode.insertBefore(js, sc);
}(document, "script"));
</script>
Best practices will suggest including all scripts at the bottom of your page.
However, that only applies to scripts that block downloading of other resources.
Including a script this way will not block other resources, however it will
block onload
.
Including the script at the top of your page gives it a good chance of loading
before the rest of your page does, thereby reducing the probability of it
blocking the onload
event.
If you don't want to block onload
either, use the following IFRAME/Preload method:
3.2. Adding it via an IFRAME/Preload
The method described in 3.1 will still block onload
on most browsers.
To avoid blocking onload
, we can load boomerang in an asynchronous IFRAME or via LINK preload (for browsers that support
it). The general process is documented on in
this blog post.
For boomerang, the asynchronous loader snippet you'll use is:
<script>
(function() {
if (window.BOOMR && (window.BOOMR.version || window.BOOMR.snippetExecuted)) {
return;
}
window.BOOMR = window.BOOMR || {};
window.BOOMR.snippetStart = new Date().getTime();
window.BOOMR.snippetExecuted = true;
window.BOOMR.snippetVersion = 15;
window.BOOMR.url = "";
var where = document.currentScript || document.getElementsByTagName("script")[0],
parentNode = where.parentNode,
promoted = false,
LOADER_TIMEOUT = 3000;
function promote() {
if (promoted) {
return;
}
var script = document.createElement("script");
script.id = "boomr-scr-as";
script.src = window.BOOMR.url;
script.async = true;
parentNode.appendChild(script);
promoted = true;
}
function iframeLoader(wasFallback) {
promoted = true;
var dom,
doc = document,
bootstrap, iframe, iframeStyle,
win = window;
window.BOOMR.snippetMethod = wasFallback ? "if" : "i";
bootstrap = function(parent, scriptId) {
var script = doc.createElement("script");
script.id = scriptId || "boomr-if-as";
script.src = window.BOOMR.url;
BOOMR_lstart = new Date().getTime();
parent = parent || doc.body;
parent.appendChild(script);
};
if (!window.addEventListener && window.attachEvent && navigator.userAgent.match(/MSIE [678]\./)) {
window.BOOMR.snippetMethod = "s";
bootstrap(parentNode, "boomr-async");
return;
}
iframe = document.createElement("IFRAME");
iframe.src = "about:blank";
iframe.title = "";
iframe.role = "presentation";
iframe.loading = "eager";
iframeStyle = (iframe.frameElement || iframe).style;
iframeStyle.width = 0;
iframeStyle.height = 0;
iframeStyle.border = 0;
iframeStyle.display = "none";
parentNode.appendChild(iframe);
try {
win = iframe.contentWindow;
doc = win.document.open();
}
catch (e) {
dom = document.domain;
iframe.src = "javascript:var d=document.open();d.domain='" + dom + "';void 0;";
win = iframe.contentWindow;
doc = win.document.open();
}
win._boomrl = function() {
bootstrap();
};
if (win.addEventListener) {
win.addEventListener("load", win._boomrl, false);
}
else if (win.attachEvent) {
win.attachEvent("onload", win._boomrl);
}
doc.close();
}
var link = document.createElement("link");
if (link.relList &&
typeof link.relList.supports === "function" &&
link.relList.supports("preload") &&
("as" in link)) {
window.BOOMR.snippetMethod = "p";
link.href = window.BOOMR.url;
link.rel = "preload";
link.as = "script";
link.addEventListener("load", promote);
link.addEventListener("error", function() {
iframeLoader(true);
});
setTimeout(function() {
if (!promoted) {
iframeLoader(true);
}
}, LOADER_TIMEOUT);
BOOMR_lstart = new Date().getTime();
parentNode.appendChild(link);
}
else {
iframeLoader(false);
}
function boomerangSaveLoadTime(e) {
window.BOOMR_onload = (e && e.timeStamp) || new Date().getTime();
}
if (window.addEventListener) {
window.addEventListener("load", boomerangSaveLoadTime, false);
}
else if (window.attachEvent) {
window.attachEvent("onload", boomerangSaveLoadTime);
}
})();
</script>
Minified:
<script>(function(){if(window.BOOMR&&(window.BOOMR.version||window.BOOMR.snippetExecuted)){return}window.BOOMR=window.BOOMR||{};window.BOOMR.snippetStart=(new Date).getTime();window.BOOMR.snippetExecuted=true;window.BOOMR.snippetVersion=15;window.BOOMR.url="";var e=document.currentScript||document.getElementsByTagName("script")[0],a=e.parentNode,s=false,t=3e3;function n(){if(s){return}var e=document.createElement("script");e.id="boomr-scr-as";e.src=window.BOOMR.url;e.async=true;a.appendChild(e);s=true}function i(e){s=true;var t,i=document,n,o,d,r=window;window.BOOMR.snippetMethod=e?"if":"i";n=function(e,t){var n=i.createElement("script");n.id=t||"boomr-if-as";n.src=window.BOOMR.url;BOOMR_lstart=(new Date).getTime();e=e||i.body;e.appendChild(n)};if(!window.addEventListener&&window.attachEvent&&navigator.userAgent.match(/MSIE [678]\./)){window.BOOMR.snippetMethod="s";n(a,"boomr-async");return}o=document.createElement("IFRAME");o.src="about:blank";o.title="";o.role="presentation";o.loading="eager";d=(o.frameElement||o).style;d.width=0;d.height=0;d.border=0;d.display="none";a.appendChild(o);try{r=o.contentWindow;i=r.document.open()}catch(e){t=document.domain;o.src="javascript:var d=document.open();d.domain='"+t+"';void 0;";r=o.contentWindow;i=r.document.open()}r._boomrl=function(){n()};if(r.addEventListener){r.addEventListener("load",r._boomrl,false)}else if(r.attachEvent){r.attachEvent("onload",r._boomrl)}i.close()}var o=document.createElement("link");if(o.relList&&typeof o.relList.supports==="function"&&o.relList.supports("preload")&&"as"in o){window.BOOMR.snippetMethod="p";o.href=window.BOOMR.url;o.rel="preload";o.as="script";o.addEventListener("load",n);o.addEventListener("error",function(){i(true)});setTimeout(function(){if(!s){i(true)}},t);BOOMR_lstart=(new Date).getTime();a.appendChild(o)}else{i(false)}function d(e){window.BOOMR_onload=e&&e.timeStamp||(new Date).getTime()}if(window.addEventListener){window.addEventListener("load",d,false)}else if(window.attachEvent){window.attachEvent("onload",d)}})();</script>
Change the boomerangUrl
to the location of Boomerang on your server.
The id
of the script node created by this code MUST be boomr-if-as
(for IFRAME mode) or boomr-scr-as
(for
Preload mode) as boomerang looks for those ids to determine if it's running within an IFRAME and to determine the
URL of the script.
boomerang will still export the BOOMR
object to the parent window if running
inside an IFRAME, so the rest of your code should remain unchanged.
3.3. Identifying when boomerang has loaded
If you load boomerang asynchronously, there's some uncertainty in when boomerang
has completed loading. To get around this, you can subscribe to the
onBoomerangLoaded
Custom Event on the document
object:
if (document.addEventListener) {
document.addEventListener("onBoomerangLoaded", function(e) {
});
}
else if (document.attachEvent) {
document.attachEvent("onpropertychange", function(e) {
if (!e) e=event;
if (e.propertyName === "onBoomerangLoaded") {
}
});
}
Note that this only works on browsers that support the CustomEvent interface,
which is Chrome (including Android), Firefox 6+ (including Android), Opera
(including Android, but not Opera Mini), Safari (including iOS), IE 6+
(but see the code above for the special way to listen for the event on IE6, 7 & 8).
boomerang also fires the onBeforeBoomerangBeacon
and onBoomerangBeacon
events just before and during beaconing.
Installation
There are several ways of including Boomerang in your project:
-
Boomerang can be downloaded from the official Boomerang Github repository.
-
NPM: npm install boomerangjs
-
Bower: bower install boomerang
Once fetched, see Building Boomerang
for more details on how to include the plugins you require.
Documentation
Documentation is in the docs/
directory. Boomerang documentation is
written in Markdown and is built via JSDoc.
You can build the current documentation by running Grunt:
grunt jsdoc
HTML files will be built under build/docs
.
Open-source Boomerang Documentation is currently published at
akamai.github.io/boomerang/.
The team at Akamai works on mPulse Boomerang, which contains a few mPulse-specific plugins and may have additional
changes being tested before being backported to the open-source Boomerang. mPulse Boomerang usage documentation is
available at docs.soasta.com/boomerang/ and mPulse Boomerang API documentation
is at developer.akamai.com/tools/boomerang/docs/.
Additional documentation:
Source code
The boomerang source code is primarily on GitHub at github.com/akamai/boomerang.
Feel free to fork it and contribute to it.
You can also get a check out the releases
or download a tarball or
zip of the code.
Support
We use GitHub Issues for discussions,
feature requests and bug reports.
Get in touch at github.com/akamai/boomerang/issues.
boomerang is supported by the developers at Akamai, and the
awesome community of open-source developers that use and hack it. That's you. Thank you!
Contributions
Boomerang is brought to you by:
To help out, please read our contributing page.
Copyright
- Copyright (c) 2011, Yahoo! Inc. All rights reserved.
- Copyright (c) 2011-2012, Log-Normal Inc. All rights reserved.
- Copyright (c) 2012-2017 SOASTA, Inc. All rights reserved.
- Copyright (c) 2017-2023, Akamai Technologies, Inc. All rights reserved.
- Copyrights licensed under the BSD License. See the accompanying LICENSE.txt file for terms.