Skip to content

Commit

Permalink
Merge pull request #5 from philbooth/restiming
Browse files Browse the repository at this point in the history
Reviewed, and it looks good.  I'm pulling this into my personal repo, and will run a few more tests before I push it to the lognormal repo.
  • Loading branch information
bluesmoon committed Mar 12, 2014
2 parents 2d71138 + 44b92de commit 2e2f35f
Show file tree
Hide file tree
Showing 5 changed files with 278 additions and 24 deletions.
108 changes: 84 additions & 24 deletions boomerang.js
Expand Up @@ -113,6 +113,10 @@ BOOMR.window = w;
impl = {
// properties
beacon_url: "",
// beacon request method, either GET, POST or AUTO. AUTO will check the
// request size then use GET if the request URL is less than 2000 chars
// otherwise it will fall back to a POST request.
beacon_type: "AUTO",
// strip out everything except last two parts of hostname.
// This doesn't work well for domains that end with a country tld,
// but we allow the developer to override site_domain for that.
Expand Down Expand Up @@ -358,12 +362,69 @@ boomr = {
} else {
el.detachEvent('on' + type, fn);
}
},

pushVars: function (arr, vars, prefix) {
var k, i, n=0;

for(k in vars) {
if(vars.hasOwnProperty(k)) {
if(Object.prototype.toString.call(vars[k]) === "[object Array]") {
for(i = 0; i < vars[k].length; ++i) {
n += BOOMR.utils.pushVars(arr, vars[k][i], k + "[" + i + "]");
}
} else {
++n;
arr.push(
encodeURIComponent(prefix ? (prefix + "[" + k + "]") : k)
+ "="
+ (vars[k]===undefined || vars[k]===null ? '' : encodeURIComponent(vars[k]))
);
}
}
}

return n;
},

postData: function (urlenc) {
var iframe = document.createElement("iframe"),
form = document.createElement("form"),
input = document.createElement("input");

iframe.name = "boomerang_post";
iframe.style.display = form.style.display = "none";

form.method = "POST";
form.action = impl.beacon_url;
form.target = iframe.name;

input.name = "data";

if (window.JSON) {
form.enctype = "text/plain";
input.value = JSON.stringify(impl.vars);
} else {
form.enctype = "application/x-www-form-urlencoded";
input.value = urlenc;
}

document.body.appendChild(iframe);
form.appendChild(input);
document.body.appendChild(form);

BOOMR.utils.addListener(iframe, "load", function() {
document.body.removeChild(form);
document.body.removeChild(iframe);
});

form.submit();
}
},

init: function(config) {
var i, k,
properties = ["beacon_url", "site_domain", "user_ip", "strip_query_string"];
properties = ["beacon_url", "beacon_type", "site_domain", "user_ip", "strip_query_string"];

BOOMR_check_doc_domain();

Expand Down Expand Up @@ -605,7 +666,7 @@ boomr = {
},

sendBeacon: function() {
var k, url, img, nparams=0;
var k, data, url, img, nparams;

BOOMR.debug("Checking if we can send beacon");

Expand Down Expand Up @@ -642,33 +703,32 @@ boomr = {
return this;
}

// if there are already url parameters in the beacon url,
// change the first parameter prefix for the boomerang url parameters to &
data = [];
nparams = BOOMR.utils.pushVars(data, impl.vars);

url = [];

for(k in impl.vars) {
if(impl.vars.hasOwnProperty(k)) {
nparams++;
url.push(encodeURIComponent(k)
+ "="
+ (
impl.vars[k]===undefined || impl.vars[k]===null
? ''
: encodeURIComponent(impl.vars[k])
)
);
}
if(!nparams) {
// do not make the request if there is no data
return this;
}

url = impl.beacon_url + ((impl.beacon_url.indexOf('?') > -1)?'&':'?') + url.join('&');
data = data.join('&');

BOOMR.debug("Sending url: " + url.replace(/&/g, "\n\t"));
if(impl.beacon_type === 'POST') {
BOOMR.utils.postData(data);
} else {
// if there are already url parameters in the beacon url,
// change the first parameter prefix for the boomerang url parameters to &
url = impl.beacon_url + ((impl.beacon_url.indexOf('?') > -1)?'&':'?') + data;

// only send beacon if we actually have something to beacon back
if(nparams) {
img = new Image();
img.src=url;
// using 2000 here as a de facto maximum URL length based on:
// http://stackoverflow.com/questions/417142/what-is-the-maximum-length-of-a-url-in-different-browsers
if(url.length > 2000 && impl.beacon_type === "AUTO") {
BOOMR.utils.postData(data);
} else {
BOOMR.debug("Sending url: " + url.replace(/&/g, "\n\t"));
img = new Image();
img.src=url;
}
}

return this;
Expand Down
14 changes: 14 additions & 0 deletions doc/api/BOOMR.html
Expand Up @@ -73,6 +73,20 @@ <h2 id="config">Configuration</h2>
There is no default value for this parameter. If not set, no beacon will be sent.
</dd>

<dt>beacon_type</dt>
<dd>
<strong>[optional]</strong>
Specify the HTTP method for beacon requests, may be <code>'GET'</code>, <code>'POST'</code> or <code>'AUTO'</code> (the default).
The AUTO setting will make a GET request unless the combined beacon URL plus query string
exceeds 2000 characters, in which case it will issue a POST. In modern browsers, POST requests
will encode the data as JSON, with a <code>Content-Type</code> of <code>text/plain</code>.
In browsers where <code>JSON.stringify</code> is not available, the data will be
<code>application/x-www-form-urlencoded</code>. To ensure reliable POST behaviour in old browsers, a
<a href="https://developer.mozilla.org/en-US/docs/Web/Guide/HTML/Forms/Sending_forms_through_JavaScript">hidden form submission</a>
is used, meaning that the request body will look like <code>data={"foo":"bar","baz":"qux"}</code>
or <code>data=foo%3Dbar%26baz%3Dqux</code>, depending on the content type.
</dd>

<dt>site_domain</dt>
<dd>
<strong>[recommended]</strong>
Expand Down
3 changes: 3 additions & 0 deletions doc/api/index.html
Expand Up @@ -27,6 +27,9 @@ <h2>plugins</h2>
<li><a href="navtiming.html">BOOMR.plugins.NavigationTiming</a> &mdash; A plugin that collects performance data from user
agents that implement the W3C Navigation Timing specification.</li>

<li><a href="restiming.html">BOOMR.plugins.ResourceTiming</a> &mdash; A plugin that collects performance data from user
agents that implement the W3C Resource Timing specification.</li>

<li><a href="cache_reload.html">BOOMR.plugins.CACHE_RELOAD</a> &mdash; The Cache Reload plugin that forces a browser to update
its cached version of boomerang.</li>
</ul>
Expand Down
102 changes: 102 additions & 0 deletions doc/api/restiming.html
@@ -0,0 +1,102 @@
<!DOCTYPE HTML>
<html>
<head>
<title>Resource Timing plugin API</title>
<meta http-equiv="Content-type" content="text/html; charset=utf-8">
<link rel="stylesheet" type="text/css" href="../boomerang-docs.css">
</head>
<body>
<span style="float:right;"><a href="../">All Docs</a> | <a href="index.html">Index</a></span>
<h1>Resource Timing plugin API</h1>
<p>
The Resource Timing plugin collects metrics
from modern user agents that support the W3C
<a href="http://w3c-test.org/webperf/specs/ResourceTiming/">Resource Timing specification</a>.
The Resource Timing API is encapsulated
within the <code>BOOMR.plugins.ResourceTiming</code> namespace.
</p>

<p>
<strong>Note</strong> that the Resource Timing plugin isn't included by default in boomerang.js.
See <a href="../howtos/howto-9.html">Howto #9</a>
for details on how to include the plugin in your boomerang deployment.
</p>

<h2 id="methods">Methods</h2>

<dl class="api">

<dt>init()</dt>
<dd>
<p>
Called by <a href="BOOMR.html#init">BOOMR.init()</a>
to configure the Resource Timing plugin.
The Resource Timing plugin doesn't require any configuration parameters,
since it simply reads values from
the browser's <code>window.performance</code> object (if available)
and adds them to the beacon request data.
</p>

<h3>Returns</h3>
<p>
a reference to the <code>BOOMR.plugins.ResourceTiming</code> object.
</p>
<h3>Note</h3>
<p>
If the executing user agent
doesn't implement the Resource Timing specification,
the plugin won't add any data to the beacon.
</p>
</dd>

<dt>is_complete()</dt>
<dd>
<p>
Called by <a href="BOOMR.html#sendBeacon">BOOMR.sendBeacon()</a>
to determine if the Resource Timing plugin has finished.
</p>
<h3>Returns</h3>
<ul>
<li><code>true</code> if the plugin has completed.</li>
<li><code>false</code> if the plugin has not completed.</li>
</ul>
</dd>

</dl>

<h2 id="beacon">Beacon Parameters</h2>
<p>
The ResourceTiming plugin adds an array named <code>restiming</code> to the beacon data,
items in the array representing resources on the page in the order that they were loaded.
Each item contains properties that correspond to attributes from the
<a href="http://www.w3.org/TR/resource-timing/#performanceresourcetiming">PerformanceResourceTiming interface</a>.
Note that some of the metrics are restricted and will not be provided cross-origin
unless the <a href="http://www.w3.org/TR/resource-timing/#timing-allow-origin">Timing-Allow-Origin header</a> permits.
</p>
<table>
<tr>
<th><code>restiming</code> array item property</th>
<th>Resource Timing attribute</th>
</tr>
<tr><td><code>rt_name</code></td><td><code>name</code></td></tr>
<tr><td><code>rt_st</code></td><td><code>startTime</code></td></tr>
<tr><td><code>rt_dur</code></td><td><code>duration</code></td></tr>
<tr><td><code>rt_red_st</code></td><td><code>redirectStart</code></td></tr>
<tr><td><code>rt_red_end</code></td><td><code>redirectEnd</code></td></tr>
<tr><td><code>rt_fet_st</code></td><td><code>fetchStart</code></td></tr>
<tr><td><code>rt_dns_st</code></td><td><code>domainLookupStart</code></td></tr>
<tr><td><code>rt_dns_end</code></td><td><code>domainLookupEnd</code></td></tr>
<tr><td><code>rt_con_st</code></td><td><code>connectStart</code></td></tr>
<tr><td><code>rt_con_end</code></td><td><code>connectEnd</code></td></tr>
<tr><td><code>rt_scon_st</code></td><td><code>secureConnectionStart</code></td></tr>
<tr><td><code>rt_req_st</code></td><td><code>requestStart</code></td></tr>
<tr><td><code>rt_res_st</code></td><td><code>responseStart</code></td></tr>
<tr><td><code>rt_res_end</code></td><td><code>responseEnd</code></td></tr>
</table>

<p class="perma-link">
The latest code and docs are available on <a href="http://github.com/lognormal/boomerang/">github.com/lognormal/boomerang</a>
</p>

</body>
</html>
75 changes: 75 additions & 0 deletions plugins/restiming.js
@@ -0,0 +1,75 @@
/**
\file restiming.js
Plugin to collect metrics from the W3C Resource Timing API.
For more information about Resource Timing,
see: http://www.w3.org/TR/resource-timing/
*/

(function() {

BOOMR = BOOMR || {};
BOOMR.plugins = BOOMR.plugins || {};

var restricted = {
redirectStart: "rt_red_st",
redirectEnd: "rt_red_end",
domainLookupStart: "rt_dns_st",
domainLookupEnd: "rt_dns_end",
connectStart: "rt_con_st",
connectEnd: "rt_con_end",
secureConnectionStart: "rt_scon_st",
requestStart: "rt_req_st",
responseStart: "rt_res_st"
},

impl = {
complete: false,
done: function() {
var p = BOOMR.window.performance, r, data, i, k;
if(impl.complete) {
return;
}
BOOMR.removeVar("restiming");
if(p && typeof p.getEntriesByType === "function") {
r = p.getEntriesByType("resource");
if(r) {
BOOMR.info("Client supports Resource Timing API", "restiming");
data = {
restiming: new Array(r.length)
};
for(i = 0; i < r.length; ++i) {
data.restiming[i] = {
rt_name: r[i].name,
// reinstate this if entryType is ever something other than "resource"
//rt_type: r[i].entryType,
rt_st: r[i].startTime,
rt_dur: r[i].duration,
rt_fet_st: r[i].fetchStart,
rt_res_end: r[i].responseEnd
};
for(k in restricted) {
if(restricted.hasOwnProperty(k) && r[i][k] > 0) {
data.restiming[i][restricted[k]] = r[i][k];
}
}
}
BOOMR.addVar(data);
}
}
this.complete = true;
BOOMR.sendBeacon();
}
};

BOOMR.plugins.ResourceTiming = {
init: function() {
BOOMR.subscribe("page_ready", impl.done, null, impl);
return this;
},
is_complete: function() {
return impl.complete;
}
};

}());

0 comments on commit 2e2f35f

Please sign in to comment.