forked from YahooArchive/boomerang
-
Notifications
You must be signed in to change notification settings - Fork 29
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Plugin to beacon User Timing API mark and measure entries (#77)
- Loading branch information
1 parent
62ab83c
commit a302e7e
Showing
14 changed files
with
413 additions
and
3 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,55 @@ | ||
# UserTiming Plugin | ||
|
||
## Collect W3C UserTiming API performance marks and measures | ||
|
||
|
||
This plugin collects all W3C UserTiming API performance marks and measures that were added since navigation start or since the last beacon fired for the current navigation. The data is added to the beacon as the `usertiming` parameter. The value is a compressed string using Nic Jansma's [usertiming-compression.js](https://github.com/nicjansma/usertiming-compression.js) library. A decompression function is also available in the library. | ||
|
||
Timing data is rounded to the nearest millisecond. | ||
|
||
Please see the [W3C UserTiming API Reference](https://www.w3.org/TR/user-timing/) for details on how to use the UserTiming API. | ||
|
||
### Configuring Boomerang | ||
|
||
You can enable the `UserTiming` plugin with: | ||
```js | ||
BOOMR.init({ | ||
UserTiming: { | ||
'enabled': true | ||
} | ||
}) | ||
``` | ||
|
||
### Example | ||
|
||
```js | ||
performance.mark('mark1'); //mark current timestamp as mark1 | ||
performance.mark('mark2'); | ||
performance.measure('measure1', 'mark1', 'mark2'); //measure1 will be the delta between mark1 and mark2 timestamps | ||
performance.measure('measure2', 'mark2'); //measure2 will be the delta between the mark2 timestamp and the current time | ||
``` | ||
|
||
The compressed data added to the beacon will look similar to the following: | ||
|
||
`usertiming=~(m~(ark~(1~'2s~2~'5k)~easure~(1~'2s_2s~2~'5k_5k)))` | ||
|
||
|
||
Decompressing the above value will give us the original data for the marks and measures collected: | ||
```json | ||
[{"name":"mark1","startTime":100,"duration":0,"entryType":"mark"}, | ||
{"name":"measure1","startTime":100,"duration":100,"entryType":"measure"}, | ||
{"name":"mark2","startTime":200,"duration":0,"entryType":"mark"}, | ||
{"name":"measure2","startTime":200,"duration":200,"entryType":"measure"}] | ||
``` | ||
|
||
### Compatibility and Browser Support | ||
|
||
|
||
Many browsers [support](http://caniuse.com/#feat=user-timing) the UserTiming API, e.g.: | ||
* Chrome 25+ | ||
* Edge | ||
* Firefox 38+ | ||
* IE 10+ | ||
* Opera 15+ | ||
|
||
See Nic Jansma's [usertiming.js](https://github.com/nicjansma/usertiming.js) polyfill library to add UserTiming API support for browsers that don't implement it natively. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -8,6 +8,9 @@ | |
"doc": "doc", | ||
"test": "tests" | ||
}, | ||
"dependencies": { | ||
"usertiming-compression": "^0.1.4" | ||
}, | ||
"devDependencies": { | ||
"async": "^0.9.0", | ||
"bower": "*", | ||
|
@@ -178,6 +181,10 @@ | |
{ | ||
"name": "Ben Ripkens", | ||
"email": "[email protected]" | ||
}, | ||
{ | ||
"name": "Nigel Heron", | ||
"email": "[email protected]" | ||
} | ||
], | ||
"license": "BSD-3-Clause", | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,158 @@ | ||
/** | ||
* @module UserTiming | ||
* @desc | ||
* Plugin to collect metrics from the W3C User Timing API. | ||
* For more information about User Timing, | ||
* see: http://www.w3.org/TR/user-timing/ | ||
* | ||
* This plugin is dependent on the UserTimingCompression library | ||
* see: https://github.com/nicjansma/usertiming-compression.js | ||
* UserTimingCompression must be loaded before this plugin's init is called. | ||
*/ | ||
|
||
/*global UserTimingCompression*/ | ||
|
||
(function() { | ||
|
||
BOOMR = BOOMR || {}; | ||
BOOMR.plugins = BOOMR.plugins || {}; | ||
if (BOOMR.plugins.UserTiming) { | ||
return; | ||
} | ||
|
||
var impl = { | ||
complete: false, | ||
initialized: false, | ||
supported: false, | ||
options: {"from": 0, "window": BOOMR.window}, | ||
|
||
/** | ||
* Calls the UserTimingCompression library to get the compressed user timing data | ||
* that occurred since the last call | ||
* | ||
* @returns {string} compressed user timing data | ||
*/ | ||
getUserTiming: function() { | ||
var timings, res, now = BOOMR.now(); | ||
var utc = window.UserTimingCompression || BOOMR.window.UserTimingCompression; | ||
|
||
timings = utc.getCompressedUserTiming(impl.options); | ||
res = utc.compressForUri(timings); | ||
this.options.from = now; | ||
|
||
return res; | ||
}, | ||
|
||
/** | ||
* Callback for `before_beacon` boomerang event | ||
* Adds the `usertiming` param to the beacon | ||
*/ | ||
addEntriesToBeacon: function() { | ||
var r; | ||
|
||
if (this.complete) { | ||
return; | ||
} | ||
|
||
BOOMR.removeVar("usertiming"); | ||
r = this.getUserTiming(); | ||
if (r) { | ||
BOOMR.addVar({ | ||
"usertiming": r | ||
}); | ||
} | ||
|
||
this.complete = true; | ||
}, | ||
|
||
/** | ||
* Callback for `onbeacon` boomerang event | ||
* Clears the `usertiming` beacon param | ||
*/ | ||
clearMetrics: function(vars) { | ||
if (vars.hasOwnProperty("usertiming")) { | ||
BOOMR.removeVar("usertiming"); | ||
} | ||
this.complete = false; | ||
}, | ||
|
||
/** | ||
* Subscribe to boomerang events that will handle the `usertiming` beacon param | ||
*/ | ||
subscribe: function() { | ||
BOOMR.subscribe("before_beacon", this.addEntriesToBeacon, null, this); | ||
BOOMR.subscribe("onbeacon", this.clearMetrics, null, this); | ||
}, | ||
|
||
/** | ||
* Callback for boomerang page_ready event | ||
* At page_ready, all javascript should be loaded. We'll call `checkSupport` again | ||
* to see if a polyfill for User Timing is available | ||
*/ | ||
pageReady: function() { | ||
if (this.checkSupport()) { | ||
this.subscribe(); | ||
} | ||
}, | ||
|
||
/** | ||
* Checks if the browser supports the User Timing API and that the UserTimingCompression library is available | ||
* | ||
* @returns {boolean} true if supported, false if not | ||
*/ | ||
checkSupport: function() { | ||
if (this.supported) { | ||
return true; | ||
} | ||
|
||
// Check that the required UserTimingCompression library is available | ||
var utc = window.UserTimingCompression || BOOMR.window.UserTimingCompression; | ||
if (typeof utc === "undefined") { | ||
BOOMR.warn("UserTimingCompression library not found", "usertiming"); | ||
return false; | ||
} | ||
|
||
var p = BOOMR.getPerformance(); | ||
// Check that we have getEntriesByType | ||
if (p && typeof p.getEntriesByType === "function") { | ||
var marks = p.getEntriesByType("mark"); | ||
var measures = p.getEntriesByType("measure"); | ||
// Check that the results of getEntriesByType for marks and measures are Arrays | ||
// Some polyfill libraries may incorrectly implement this | ||
if (BOOMR.utils.isArray(marks) && BOOMR.utils.isArray(measures)) { | ||
BOOMR.info("Client supports User Timing API", "usertiming"); | ||
this.supported = true; | ||
return true; | ||
} | ||
} | ||
return false; | ||
} | ||
}; | ||
|
||
BOOMR.plugins.UserTiming = { | ||
init: function(config) { | ||
if (impl.initialized) { | ||
return this; | ||
} | ||
|
||
if (impl.checkSupport()) { | ||
impl.subscribe(); | ||
} | ||
else { | ||
// usertiming isn't supported by the browser or the UserTimingCompression library isn't loaded. | ||
// Let's check again when the page is ready to see if a polyfill was loaded. | ||
BOOMR.subscribe("page_ready", impl.pageReady, null, impl); | ||
} | ||
|
||
impl.initialized = true; | ||
return this; | ||
}, | ||
is_complete: function() { | ||
return true; | ||
}, | ||
is_supported: function() { | ||
return impl.initialized && impl.supported; | ||
} | ||
}; | ||
|
||
}()); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
12 changes: 12 additions & 0 deletions
12
tests/page-templates/18-usertiming/00-usertiming-none.html
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
<%= header %> | ||
<%= boomerangSnippet %> | ||
<script src="00-usertiming-none.js" type="text/javascript"></script> | ||
<script> | ||
BOOMR_test.init({ | ||
testAfterOnBeacon: true, | ||
UserTiming: { | ||
enabled: true | ||
} | ||
}); | ||
</script> | ||
<%= footer %> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
/*eslint-env mocha*/ | ||
/*global BOOMR_test,assert*/ | ||
|
||
describe("e2e/17-usertiming/00-usertiming-none", function() { | ||
var t = BOOMR_test; | ||
var tf = BOOMR.plugins.TestFramework; | ||
|
||
it("Should pass basic beacon validation", function(done) { | ||
t.validateBeaconWasSent(done); | ||
}); | ||
|
||
it("Should not have usertiming", function() { | ||
if (t.isUserTimingSupported()) { | ||
var b = tf.beacons[0]; | ||
assert.equal(b.usertiming, undefined); | ||
} | ||
}); | ||
}); |
24 changes: 24 additions & 0 deletions
24
tests/page-templates/18-usertiming/01-usertiming-basic.html
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
<%= header %> | ||
<%= boomerangSnippet %> | ||
<script src="01-usertiming-basic.js" type="text/javascript"></script> | ||
<script src="../../vendor/usertiming-compression/src/usertiming-decompression.js" type="text/javascript"></script> | ||
<script> | ||
if (BOOMR_test.isUserTimingSupported()) { | ||
window.performance.mark("mark1"); | ||
setTimeout(function() { | ||
window.performance.mark("mark2"); | ||
window.performance.measure("measure1", "mark1", "mark2"); | ||
}, 500); | ||
} | ||
</script> | ||
<script> | ||
setTimeout(function() { | ||
BOOMR_test.init({ | ||
testAfterOnBeacon: true, | ||
UserTiming: { | ||
enabled: true | ||
} | ||
}); | ||
}, 1000); | ||
</script> | ||
<%= footer %> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
/*eslint-env mocha*/ | ||
/*global BOOMR_test,assert*/ | ||
|
||
describe("e2e/17-usertiming/01-usertiming-basic", function() { | ||
var t = BOOMR_test; | ||
var tf = BOOMR.plugins.TestFramework; | ||
|
||
it("Should pass basic beacon validation", function(done) { | ||
t.validateBeaconWasSent(done); | ||
}); | ||
|
||
it("Should have usertiming (if UserTiming is supported)", function() { | ||
if (t.isUserTimingSupported()) { | ||
var b = tf.beacons[0]; | ||
assert.isString(b.usertiming); | ||
var data = UserTimingDecompression.decompressUserTiming(b.usertiming); | ||
var usertiming = {}; | ||
assert.equal(data.length, 3); | ||
for (var i = 0; i < data.length; i++) { | ||
usertiming[data[i].name] = data[i]; | ||
} | ||
assert.isTrue("mark1" in usertiming); | ||
assert.isTrue("mark2" in usertiming); | ||
assert.isTrue("measure1" in usertiming); | ||
} | ||
}); | ||
}); |
37 changes: 37 additions & 0 deletions
37
tests/page-templates/18-usertiming/02-usertiming-polyfill.html
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
<%= header %> | ||
<%= boomerangSnippet %> | ||
<script src="02-usertiming-polyfill.js" type="text/javascript"></script> | ||
<script src="../../vendor/usertiming-compression/src/usertiming-decompression.js" type="text/javascript"></script> | ||
<script> | ||
// | ||
// Not really polyfill, we'll hide window.performance.getEntriesByType | ||
// then bring it back later | ||
// | ||
if (BOOMR_test.isUserTimingSupported()) { | ||
window.getEntriesByTypeCopy = window.performance.getEntriesByType; | ||
window.performance.getEntriesByType = undefined; | ||
|
||
BOOMR_test.init({ | ||
testAfterOnBeacon: true, | ||
UserTiming: { | ||
enabled: true | ||
}, | ||
onBoomerangLoaded: function() { | ||
window.performance.getEntriesByType = window.getEntriesByTypeCopy; | ||
window.performance.mark("mark1"); | ||
window.performance.mark("mark2"); | ||
window.performance.measure("measure1", "mark1", "mark2"); | ||
} | ||
}); | ||
} | ||
else { | ||
BOOMR_test.init({ | ||
testAfterOnBeacon: true, | ||
UserTiming: { | ||
enabled: true | ||
} | ||
}); | ||
} | ||
</script> | ||
<div id="content"></div> | ||
<%= footer %> |
Oops, something went wrong.