diff --git a/README.md b/README.md index 257b3ed..2d0ab99 100644 --- a/README.md +++ b/README.md @@ -45,9 +45,19 @@ Metrics are gathered in the format used by a statsd / graphite / grafana stack - ### What metrics are gathered? -* PageChange - When the page loads, or whenever a navigation event occurs, we log a counter for the page visit and a gauge for how long the page load took. The page load is a measure of the time between the navigation event firing, the event loop lagging for over 25ms and finally the event loop freeing up for 125ms. Basically we wait for the page to get busy, then we wait for it to finish being busy. -* PageLoad - We use `window.performance` to get details of the initial page load. -* XhrStats - We monkey-patch over `XMLHttpRequest` in order to identify every AJAX request, the destination, the transition between states, final response size and timing. +#### PageChanged +When the page loads, or whenever a navigation event occurs, we log a counter for the page visit and a gauge for how long the page load took. The page load is a measure of the time between the navigation event firing, the event loop lagging for over 25ms and finally the event loop freeing up for 125ms. Basically we wait for the page to get busy, then we wait for it to finish being busy. This provides a measure for how efficient single page applications are whilst out in the wild. The approach is framework agnostic. + +#### PageLoad +When the HTML document has loaded, we use `window.performance.{timing,navigation}` for details of the initial page load. They provide measures of network performance and initial DOM performance out in the wild. + +#### XhrStats +By subtly altering the behavious of `window.XMLHttpRequest` we can identify every AJAX request made, the destination, the transitioning between states and final response size. This provides a measure of real world API performance, as seen by end users. + +#### PageResources +Whenever the page has finished changing (see PageChanged above) we look back through all the new `PerformanceEntry` objects in `window.performance`, filter out the entries that match a user-defined rule set on (domain, path and/or type) and generate metrics for the user-defined events of interest. This provides adjustable measures of real-world performance sliced up in many different ways. Keep reading for how to configure this functionality. + +Timings are measured as an offset against the most recent `PageChanged` event, which occurs *after* the page has mutated, and as such all timings will be negative. The recorded values should be interpreted with `0` being the point at which the end user thinks the page is ready. This enables us to fairly measure metrics of all resources across a single page application. ### How are the metrics transmitted? @@ -72,6 +82,33 @@ window.barometer = { ``` Other scripts on the page can use this, drastically reducing the barriers to entry for logging application metrics. +### Configuring PageResource metrics + +At it's heart, simply attach an array to `window.barometer.resources`. Each object in the array is a filter which details which resources to match on, and which metrics to gather. + +``` +window.barometer.resources = [ + { + domain: 'example.com', + path: /\.js$/, + type: /.*/, + metrics: [ 'startTime', 'duration' ] + } +] +``` + +#### Filtering + +The following properties are all optional, you can include as many or as few as you like: + + * domain - A string or RegEx of the domain as seen by the end user. + * path - A string or RegEx of the URL path as seen by the end user. + * type - A string or RegEx of the type of `PerformanceEntry` object. + +#### Gathering + +The `metrics` property controls which metrics should be gathered. Possible options are: `connectEnd`, `connectStart`, `domainLookupEnd`, `domainLookupStart`, `duration`, `fetchStart`, `redirectEnd`, `redirectStart`, `requestStart`, `responseEnd`, `responseStart`, `secureConnectionStart`, `startTime`, `workerStart`. + ### What could a complete metrics stack look like? 1. [barometer](https://github.com/holidayextras/barometer) - client-side metric gathering tool diff --git a/dist/barometer-test.js b/dist/barometer-test.js index 574cc7a..fe86839 100644 --- a/dist/barometer-test.js +++ b/dist/barometer-test.js @@ -4,9 +4,10 @@ var tracker = window.barometer = module.exports = {} var transport = require('./transport.js') +var pageChange = require('./pageChange') require('./pageLoadStats.js') require('./xhrStats.js') -var pageChange = require('./pageChange') +require('./pageResources') tracker.url = null tracker.gauge = transport.gauge @@ -14,7 +15,7 @@ tracker.count = transport.count tracker.onPageChanged = pageChange.onPageChanged tracker.oncePageLoaded = pageChange.oncePageLoaded -},{"./pageChange":3,"./pageLoadStats.js":4,"./transport.js":5,"./xhrStats.js":7}],2:[function(require,module,exports){ +},{"./pageChange":3,"./pageLoadStats.js":4,"./pageResources":5,"./transport.js":6,"./xhrStats.js":8}],2:[function(require,module,exports){ /* global */ 'use strict' module.exports = function (obj, evt, fn) { @@ -125,7 +126,7 @@ pageChange.oncePageLoaded = function (cb) { }) } -},{"./event.js":2,"./pageLoadStats.js":4,"./transport.js":5}],4:[function(require,module,exports){ +},{"./event.js":2,"./pageLoadStats.js":4,"./transport.js":6}],4:[function(require,module,exports){ /* global document window */ 'use strict' var pageLoadStats = module.exports = {} @@ -176,7 +177,72 @@ if (typeof window.performance === 'object') { } } -},{"./event.js":2,"./transport.js":5,"./urlSanitiser.js":6}],5:[function(require,module,exports){ +},{"./event.js":2,"./transport.js":6,"./urlSanitiser.js":7}],5:[function(require,module,exports){ +/* global window */ +'use strict' +var pageResources = module.exports = {} + +var pageChange = require('./pageChange.js') +var transport = require('./transport.js') +var lastIndex = 0 +var timingOffset = 0 +var pageStart + +pageResources.handleResource = function (entry) { + var metrics = pageResources.findRule(entry) + if (!metrics) return + pageResources.track(entry, metrics) +} + +pageResources.findRule = function (entry) { + var config = window.barometer.resources + if (!config) return + + var name = entry.name.replace(/https?:\/\//i, '').split('/') + var domain = name.shift() + var path = name.join('/') + + for (var i = 0; i < config.length; i++) { + var rule = config[i] + if (rule.domain && !domain.match(rule.domain)) continue + if (rule.path && !path.match(rule.path)) continue + if (rule.type && entry.entryType !== rule.type) continue + return rule.metrics + } + return null +} + +pageResources.track = function (entry, metrics) { + var parts = entry.name.replace(/https?:\/\//i, '').split('/') + var prefix = 'resources.' + entry.entryType + '.' + prefix += parts.shift().replace(/[^a-z0-9_]/gi, '_') + prefix += '.' + prefix += parts.join('/').split('?')[0].replace(/[^a-z0-9_]/gi, '_') + + for (var i in metrics) { + i = metrics[i] + var value = entry[i] + if (value === 0) continue + if (i !== 'duration') value = timingOffset - value + transport.gauge(prefix + '.' + i, value) + } +} + +pageResources.initialise = function () { + if (!window.performance || !window.performance.getEntries) return + + pageStart = new Date() + pageChange.onPageChanged(function () { + timingOffset = (new Date() - pageStart) + var entries = window.performance.getEntries() + for (; lastIndex < entries.length; lastIndex++) { + pageResources.handleResource(entries[lastIndex]) + } + }) +} +pageResources.initialise() + +},{"./pageChange.js":3,"./transport.js":6}],6:[function(require,module,exports){ /* global window document */ 'use strict' var transport = module.exports = {} @@ -244,7 +310,7 @@ transport._flushBuffer = function (pageEnd) { } } -},{"./barometer.js":1,"./xhrStats":7}],6:[function(require,module,exports){ +},{"./barometer.js":1,"./xhrStats":8}],7:[function(require,module,exports){ 'use strict' module.exports = function (href, hash) { var safeUrl = (href || '').split('?')[0].replace(/^https?:\/\//i, '').replace(/[^a-z0-9-\/]/gi, '_') @@ -254,7 +320,7 @@ module.exports = function (href, hash) { return safeUrl // .replace(/\/\//g, '/') } -},{}],7:[function(require,module,exports){ +},{}],8:[function(require,module,exports){ /* global window */ 'use strict' var xhrStats = module.exports = {} @@ -344,7 +410,7 @@ window.XMLHttpRequest = function () { return req } -},{"./event.js":2,"./transport.js":5,"./urlSanitiser.js":6}],8:[function(require,module,exports){ +},{"./event.js":2,"./transport.js":6,"./urlSanitiser.js":7}],9:[function(require,module,exports){ // http://wiki.commonjs.org/wiki/Unit_Testing/1.0 // // THIS IS NOT TESTED NOR LIKELY TO WORK OUTSIDE V8! @@ -705,7 +771,7 @@ var objectKeys = Object.keys || function (obj) { return keys; }; -},{"util/":12}],9:[function(require,module,exports){ +},{"util/":13}],10:[function(require,module,exports){ if (typeof Object.create === 'function') { // implementation from standard node.js 'util' module module.exports = function inherits(ctor, superCtor) { @@ -730,7 +796,7 @@ if (typeof Object.create === 'function') { } } -},{}],10:[function(require,module,exports){ +},{}],11:[function(require,module,exports){ // shim for using process in browser var process = module.exports = {}; @@ -783,7 +849,7 @@ function drainQueue() { if (draining) { return; } - var timeout = cachedSetTimeout(cleanUpNextTick); + var timeout = cachedSetTimeout.call(null, cleanUpNextTick); draining = true; var len = queue.length; @@ -800,7 +866,7 @@ function drainQueue() { } currentQueue = null; draining = false; - cachedClearTimeout(timeout); + cachedClearTimeout.call(null, timeout); } process.nextTick = function (fun) { @@ -812,7 +878,7 @@ process.nextTick = function (fun) { } queue.push(new Item(fun, args)); if (queue.length === 1 && !draining) { - cachedSetTimeout(drainQueue, 0); + cachedSetTimeout.call(null, drainQueue, 0); } }; @@ -851,14 +917,14 @@ process.chdir = function (dir) { }; process.umask = function() { return 0; }; -},{}],11:[function(require,module,exports){ +},{}],12:[function(require,module,exports){ module.exports = function isBuffer(arg) { return arg && typeof arg === 'object' && typeof arg.copy === 'function' && typeof arg.fill === 'function' && typeof arg.readUInt8 === 'function'; } -},{}],12:[function(require,module,exports){ +},{}],13:[function(require,module,exports){ (function (process,global){ // Copyright Joyent, Inc. and other Node contributors. // @@ -1448,7 +1514,7 @@ function hasOwnProperty(obj, prop) { } }).call(this,require('_process'),typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) -},{"./support/isBuffer":11,"_process":10,"inherits":9}],13:[function(require,module,exports){ +},{"./support/isBuffer":12,"_process":11,"inherits":10}],14:[function(require,module,exports){ /** * Sinon core utilities. For internal use only. * @@ -1497,7 +1563,7 @@ var sinon = (function () { // eslint-disable-line no-unused-vars return sinonModule; }()); -},{"./sinon/assert":14,"./sinon/behavior":15,"./sinon/call":16,"./sinon/collection":17,"./sinon/extend":18,"./sinon/format":19,"./sinon/log_error":20,"./sinon/match":21,"./sinon/mock":22,"./sinon/sandbox":23,"./sinon/spy":24,"./sinon/stub":25,"./sinon/test":26,"./sinon/test_case":27,"./sinon/times_in_words":28,"./sinon/typeOf":29,"./sinon/util/core":30,"./sinon/walk":37}],14:[function(require,module,exports){ +},{"./sinon/assert":15,"./sinon/behavior":16,"./sinon/call":17,"./sinon/collection":18,"./sinon/extend":19,"./sinon/format":20,"./sinon/log_error":21,"./sinon/match":22,"./sinon/mock":23,"./sinon/sandbox":24,"./sinon/spy":25,"./sinon/stub":26,"./sinon/test":27,"./sinon/test_case":28,"./sinon/times_in_words":29,"./sinon/typeOf":30,"./sinon/util/core":31,"./sinon/walk":38}],15:[function(require,module,exports){ (function (global){ /** * @depend times_in_words.js @@ -1546,6 +1612,24 @@ var sinon = (function () { // eslint-disable-line no-unused-vars } } + function verifyIsValidAssertion(assertionMethod, assertionArgs) { + switch (assertionMethod) { + case "notCalled": + case "called": + case "calledOnce": + case "calledTwice": + case "calledThrice": + if (assertionArgs.length !== 0) { + assert.fail(assertionMethod + + " takes 1 argument but was called with " + + (assertionArgs.length + 1) + " arguments"); + } + break; + default: + break; + } + } + function failAssertion(object, msg) { object = object || global; var failMethod = object.fail || assert.fail; @@ -1562,6 +1646,8 @@ var sinon = (function () { // eslint-disable-line no-unused-vars verifyIsStub(fake); var args = slice.call(arguments, 1); + verifyIsValidAssertion(name, args); + var failed = false; if (typeof method === "function") { @@ -1727,7 +1813,7 @@ var sinon = (function () { // eslint-disable-line no-unused-vars )); }).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) -},{"./format":19,"./match":21,"./util/core":30}],15:[function(require,module,exports){ +},{"./format":20,"./match":22,"./util/core":31}],16:[function(require,module,exports){ (function (process){ /** * @depend util/core.js @@ -2102,7 +2188,7 @@ var sinon = (function () { // eslint-disable-line no-unused-vars )); }).call(this,require('_process')) -},{"./extend":18,"./util/core":30,"_process":10}],16:[function(require,module,exports){ +},{"./extend":19,"./util/core":31,"_process":11}],17:[function(require,module,exports){ /** * @depend util/core.js * @depend match.js @@ -2343,7 +2429,7 @@ var sinon = (function () { // eslint-disable-line no-unused-vars typeof sinon === "object" && sinon // eslint-disable-line no-undef )); -},{"./format":19,"./match":21,"./util/core":30}],17:[function(require,module,exports){ +},{"./format":20,"./match":22,"./util/core":31}],18:[function(require,module,exports){ /** * @depend util/core.js * @depend spy.js @@ -2518,7 +2604,7 @@ var sinon = (function () { // eslint-disable-line no-unused-vars typeof sinon === "object" && sinon // eslint-disable-line no-undef )); -},{"./mock":22,"./spy":24,"./stub":25,"./util/core":30}],18:[function(require,module,exports){ +},{"./mock":23,"./spy":25,"./stub":26,"./util/core":31}],19:[function(require,module,exports){ /** * @depend util/core.js */ @@ -2631,7 +2717,7 @@ var sinon = (function () { // eslint-disable-line no-unused-vars typeof sinon === "object" && sinon // eslint-disable-line no-undef )); -},{"./util/core":30}],19:[function(require,module,exports){ +},{"./util/core":31}],20:[function(require,module,exports){ /** * @depend util/core.js */ @@ -2727,7 +2813,7 @@ var sinon = (function () { // eslint-disable-line no-unused-vars typeof formatio === "object" && formatio // eslint-disable-line no-undef )); -},{"./util/core":30,"formatio":38,"util":12}],20:[function(require,module,exports){ +},{"./util/core":31,"formatio":39,"util":13}],21:[function(require,module,exports){ /** * @depend util/core.js */ @@ -2813,7 +2899,7 @@ var sinon = (function () { // eslint-disable-line no-unused-vars typeof sinon === "object" && sinon // eslint-disable-line no-undef )); -},{"./util/core":30}],21:[function(require,module,exports){ +},{"./util/core":31}],22:[function(require,module,exports){ /** * @depend util/core.js * @depend typeOf.js @@ -3076,7 +3162,7 @@ var sinon = (function () { // eslint-disable-line no-unused-vars typeof sinon === "object" && sinon // eslint-disable-line no-undef )); -},{"./typeOf":29,"./util/core":30}],22:[function(require,module,exports){ +},{"./typeOf":30,"./util/core":31}],23:[function(require,module,exports){ /** * @depend times_in_words.js * @depend util/core.js @@ -3569,7 +3655,7 @@ var sinon = (function () { // eslint-disable-line no-unused-vars typeof sinon === "object" && sinon // eslint-disable-line no-undef )); -},{"./call":16,"./extend":18,"./format":19,"./match":21,"./spy":24,"./stub":25,"./times_in_words":28,"./util/core":30}],23:[function(require,module,exports){ +},{"./call":17,"./extend":19,"./format":20,"./match":22,"./spy":25,"./stub":26,"./times_in_words":29,"./util/core":31}],24:[function(require,module,exports){ /** * @depend util/core.js * @depend extend.js @@ -3741,7 +3827,7 @@ var sinon = (function () { // eslint-disable-line no-unused-vars typeof sinon === "object" && sinon // eslint-disable-line no-undef )); -},{"./collection":17,"./extend":18,"./util/core":30,"./util/fake_server_with_clock":33,"./util/fake_timers":34}],24:[function(require,module,exports){ +},{"./collection":18,"./extend":19,"./util/core":31,"./util/fake_server_with_clock":34,"./util/fake_timers":35}],25:[function(require,module,exports){ /** * @depend times_in_words.js * @depend util/core.js @@ -4206,7 +4292,7 @@ var sinon = (function () { // eslint-disable-line no-unused-vars typeof sinon === "object" && sinon // eslint-disable-line no-undef )); -},{"./call":16,"./extend":18,"./format":19,"./times_in_words":28,"./util/core":30}],25:[function(require,module,exports){ +},{"./call":17,"./extend":19,"./format":20,"./times_in_words":29,"./util/core":31}],26:[function(require,module,exports){ /** * @depend util/core.js * @depend extend.js @@ -4408,7 +4494,7 @@ var sinon = (function () { // eslint-disable-line no-unused-vars typeof sinon === "object" && sinon // eslint-disable-line no-undef )); -},{"./behavior":15,"./extend":18,"./spy":24,"./util/core":30}],26:[function(require,module,exports){ +},{"./behavior":16,"./extend":19,"./spy":25,"./util/core":31}],27:[function(require,module,exports){ /** * @depend util/core.js * @depend sandbox.js @@ -4508,7 +4594,7 @@ var sinon = (function () { // eslint-disable-line no-unused-vars } }(typeof sinon === "object" && sinon || null)); // eslint-disable-line no-undef -},{"./sandbox":23,"./util/core":30}],27:[function(require,module,exports){ +},{"./sandbox":24,"./util/core":31}],28:[function(require,module,exports){ /** * @depend util/core.js * @depend test.js @@ -4616,7 +4702,7 @@ var sinon = (function () { // eslint-disable-line no-unused-vars typeof sinon === "object" && sinon // eslint-disable-line no-undef )); -},{"./test":26,"./util/core":30}],28:[function(require,module,exports){ +},{"./test":27,"./util/core":31}],29:[function(require,module,exports){ /** * @depend util/core.js */ @@ -4667,7 +4753,7 @@ var sinon = (function () { // eslint-disable-line no-unused-vars typeof sinon === "object" && sinon // eslint-disable-line no-undef )); -},{"./util/core":30}],29:[function(require,module,exports){ +},{"./util/core":31}],30:[function(require,module,exports){ /** * @depend util/core.js */ @@ -4722,7 +4808,7 @@ var sinon = (function () { // eslint-disable-line no-unused-vars typeof sinon === "object" && sinon // eslint-disable-line no-undef )); -},{"./util/core":30}],30:[function(require,module,exports){ +},{"./util/core":31}],31:[function(require,module,exports){ /** * @depend ../../sinon.js */ @@ -4945,7 +5031,7 @@ var sinon = (function () { // eslint-disable-line no-unused-vars } for (prop in a) { - if (a.hasOwnProperty(prop)) { + if (hasOwn.call(a, prop)) { aLength += 1; if (!(prop in b)) { @@ -4959,7 +5045,7 @@ var sinon = (function () { // eslint-disable-line no-unused-vars } for (prop in b) { - if (b.hasOwnProperty(prop)) { + if (hasOwn.call(b, prop)) { bLength += 1; } } @@ -5125,7 +5211,7 @@ var sinon = (function () { // eslint-disable-line no-unused-vars typeof sinon === "object" && sinon // eslint-disable-line no-undef )); -},{}],31:[function(require,module,exports){ +},{}],32:[function(require,module,exports){ /** * Minimal Event interface implementation * @@ -5238,7 +5324,7 @@ if (typeof sinon === "undefined") { } }()); -},{"./core":30}],32:[function(require,module,exports){ +},{"./core":31}],33:[function(require,module,exports){ /** * @depend fake_xdomain_request.js * @depend fake_xml_http_request.js @@ -5487,7 +5573,7 @@ if (typeof sinon === "undefined") { } }()); -},{"../format":19,"./core":30,"./fake_xdomain_request":35,"./fake_xml_http_request":36}],33:[function(require,module,exports){ +},{"../format":20,"./core":31,"./fake_xdomain_request":36,"./fake_xml_http_request":37}],34:[function(require,module,exports){ /** * @depend fake_server.js * @depend fake_timers.js @@ -5590,7 +5676,7 @@ if (typeof sinon === "undefined") { } }()); -},{"./core":30,"./fake_server":32,"./fake_timers":34}],34:[function(require,module,exports){ +},{"./core":31,"./fake_server":33,"./fake_timers":35}],35:[function(require,module,exports){ /** * Fake timer API * setTimeout @@ -5665,7 +5751,7 @@ if (typeof sinon === "undefined") { } }()); -},{"./core":30,"lolex":39}],35:[function(require,module,exports){ +},{"./core":31,"lolex":40}],36:[function(require,module,exports){ (function (global){ /** * @depend core.js @@ -5908,7 +5994,7 @@ if (typeof sinon === "undefined") { })(typeof global !== "undefined" ? global : self); }).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) -},{"../extend":18,"../log_error":20,"./core":30,"./event":31}],36:[function(require,module,exports){ +},{"../extend":19,"../log_error":21,"./core":31,"./event":32}],37:[function(require,module,exports){ (function (global){ /** * @depend core.js @@ -6037,7 +6123,7 @@ if (typeof sinon === "undefined") { } var xhr = this; - var events = ["loadstart", "load", "abort", "loadend"]; + var events = ["loadstart", "load", "abort", "error", "loadend"]; function addEventListener(eventName) { xhr.addEventListener(eventName, function (event) { @@ -6371,12 +6457,16 @@ if (typeof sinon === "undefined") { } if (this.readyState === FakeXMLHttpRequest.DONE) { - if (this.status < 200 || this.status > 299) { - progress = {loaded: 0, total: 0}; + // ensure loaded and total are numbers + progress = { + loaded: this.progress || 0, + total: this.progress || 0 + }; + + if (this.status === 0) { event = this.aborted ? "abort" : "error"; } else { - progress = {loaded: 100, total: 100}; event = "load"; } @@ -6469,6 +6559,15 @@ if (typeof sinon === "undefined") { this.readyState = FakeXMLHttpRequest.UNSENT; }, + error: function error() { + clearResponse(this); + this.errorFlag = true; + this.requestHeaders = {}; + this.responseHeaders = {}; + + this.readyStateChange(FakeXMLHttpRequest.DONE); + }, + getResponseHeader: function getResponseHeader(header) { if (this.readyState < FakeXMLHttpRequest.HEADERS_RECEIVED) { return null; @@ -6532,6 +6631,7 @@ if (typeof sinon === "undefined") { } else if (this.responseType === "" && isXmlContentType(contentType)) { this.responseXML = FakeXMLHttpRequest.parseXML(this.responseText); } + this.progress = body.length; this.readyStateChange(FakeXMLHttpRequest.DONE); }, @@ -6637,7 +6737,7 @@ if (typeof sinon === "undefined") { )); }).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) -},{"../extend":18,"../log_error":20,"./core":30,"./event":31}],37:[function(require,module,exports){ +},{"../extend":19,"../log_error":21,"./core":31,"./event":32}],38:[function(require,module,exports){ /** * @depend util/core.js */ @@ -6718,7 +6818,7 @@ if (typeof sinon === "undefined") { typeof sinon === "object" && sinon // eslint-disable-line no-undef )); -},{"./util/core":30}],38:[function(require,module,exports){ +},{"./util/core":31}],39:[function(require,module,exports){ (function (global){ ((typeof define === "function" && define.amd && function (m) { define("formatio", ["samsam"], m); @@ -6935,7 +7035,7 @@ if (typeof sinon === "undefined") { }); }).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) -},{"samsam":40}],39:[function(require,module,exports){ +},{"samsam":41}],40:[function(require,module,exports){ (function (global){ /*global global, window*/ /** @@ -7458,7 +7558,7 @@ if (typeof sinon === "undefined") { }(global || this)); }).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) -},{}],40:[function(require,module,exports){ +},{}],41:[function(require,module,exports){ ((typeof define === "function" && define.amd && function (m) { define("samsam", m); }) || (typeof module === "object" && function (m) { module.exports = m(); }) || // Node @@ -7859,7 +7959,7 @@ if (typeof sinon === "undefined") { }; }); -},{}],41:[function(require,module,exports){ +},{}],42:[function(require,module,exports){ (function (global){ require('sinon') if (typeof window === 'undefined') { @@ -7950,7 +8050,13 @@ require('../') window.barometer.url = 'https://localhost:16006' }).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) -},{"../":1,"sinon":13}],42:[function(require,module,exports){ +},{"../":1,"sinon":14}],43:[function(require,module,exports){ +'use strict' + +var context = require.context('./', true, /test/) +context.keys().forEach(context) + +},{}],44:[function(require,module,exports){ 'use strict' var assert = require('assert') var sinon = require('sinon') @@ -7995,7 +8101,7 @@ describe('Testing event', function () { }) }) -},{"../lib/event.js":2,"./_fakeDom.js":41,"assert":8,"sinon":13}],43:[function(require,module,exports){ +},{"../lib/event.js":2,"./_fakeDom.js":42,"assert":9,"sinon":14}],45:[function(require,module,exports){ /* global window */ 'use strict' var sinon = require('sinon') @@ -8045,7 +8151,7 @@ describe('Testing pageChange', function () { }) }) -},{"../lib/pageChange.js":3,"../lib/transport.js":5,"./_fakeDom.js":41,"assert":8,"sinon":13}],44:[function(require,module,exports){ +},{"../lib/pageChange.js":3,"../lib/transport.js":6,"./_fakeDom.js":42,"assert":9,"sinon":14}],46:[function(require,module,exports){ /* global window */ 'use strict' var assert = require('assert') @@ -8089,7 +8195,7 @@ describe('Testing pageLoadStats', function () { }) }) -},{"../lib/pageLoadStats.js":4,"../lib/transport.js":5,"./_fakeDom.js":41,"assert":8,"sinon":13}],45:[function(require,module,exports){ +},{"../lib/pageLoadStats.js":4,"../lib/transport.js":6,"./_fakeDom.js":42,"assert":9,"sinon":14}],47:[function(require,module,exports){ /* global window */ 'use strict' var assert = require('assert') @@ -8104,7 +8210,82 @@ describe('Testing barometer', function () { }) }) -},{"../lib/barometer.js":1,"./_fakeDom.js":41,"assert":8}],46:[function(require,module,exports){ +},{"../lib/barometer.js":1,"./_fakeDom.js":42,"assert":9}],48:[function(require,module,exports){ +/* global window */ +'use strict' +var sinon = require('sinon') +var assert = require('assert') +require('./_fakeDom.js') +var pageChange = require('../lib/pageChange.js') +var transport = require('../lib/transport.js') +var pageResources = require('../lib/pageResources.js') + +describe('Testing pageResources', function () { + before(function () { + sinon.stub(transport, 'gauge') + sinon.stub(pageChange, 'onPageChanged', function (callback) { + setTimeout(callback, 500) + return callback() + }) + window.clock = sinon.useFakeTimers() + var getEntries = sinon.stub() + window.performance.getEntries = getEntries + var entries = [ + { + name: 'http://foo.com/bar.js?blarg', + entryType: 'type1', + stat1: 123 + }, + { + name: 'http://bar.com/foo.js', + entryType: 'type2', + stat2: 223 + }, + { + name: 'http://example.com/bar.js', + entryType: 'type3', + stat2: 100 + } + ] + var subsequentEntries = [ + { + name: 'http://foo.com/bar.js', + entryType: 'type1', + stat1: 723 + }, + { + name: 'http://bar.com/foo.js', + entryType: 'type2', + stat2: 823 + } + ] + getEntries.onFirstCall().returns(entries) + getEntries.onSecondCall().returns(entries.concat(subsequentEntries)) + window.barometer.resources = [ + { domain: 'foo.com', metrics: [ 'stat1' ] }, + { path: /foo\.js$/, metrics: [ 'stat2' ] } + ] + pageResources.initialise() + }) + after(function () { + transport.gauge.restore() + pageChange.onPageChanged.restore() + window.performance.getEntries = null + window.clock.restore() + }) + + it('should measure resource timings', function () { + sinon.assert.calledWith(transport.gauge, 'resources.type1.foo_com.bar_js.stat1', -123) + sinon.assert.calledWith(transport.gauge, 'resources.type2.bar_com.foo_js.stat2', -223) + assert.equal(transport.gauge.callCount, 2) + window.clock.tick(600) + sinon.assert.calledWith(transport.gauge, 'resources.type1.foo_com.bar_js.stat1', -223) + sinon.assert.calledWith(transport.gauge, 'resources.type2.bar_com.foo_js.stat2', -323) + assert.equal(transport.gauge.callCount, 4) + }) +}) + +},{"../lib/pageChange.js":3,"../lib/pageResources.js":5,"../lib/transport.js":6,"./_fakeDom.js":42,"assert":9,"sinon":14}],49:[function(require,module,exports){ /* global window */ 'use strict' var assert = require('assert') @@ -8215,7 +8396,7 @@ describe('Testing transport', function () { }) }) -},{"../lib/transport.js":5,"./_fakeDom.js":41,"assert":8,"sinon":13}],47:[function(require,module,exports){ +},{"../lib/transport.js":6,"./_fakeDom.js":42,"assert":9,"sinon":14}],50:[function(require,module,exports){ /* global window */ 'use strict' var assert = require('assert') @@ -8243,7 +8424,7 @@ describe('Testing urlSanitiser', function () { }) }) -},{"../lib/urlSanitiser.js":6,"./_fakeDom.js":41,"assert":8}],48:[function(require,module,exports){ +},{"../lib/urlSanitiser.js":7,"./_fakeDom.js":42,"assert":9}],51:[function(require,module,exports){ /* global window */ 'use strict' var sinon = require('sinon') @@ -8277,4 +8458,4 @@ describe('Testing xhrStats', function () { }) }) -},{"../lib/transport.js":5,"../lib/xhrStats.js":7,"./_fakeDom.js":41,"sinon":13}]},{},[41,42,43,44,45,46,47,48]); +},{"../lib/transport.js":6,"../lib/xhrStats.js":8,"./_fakeDom.js":42,"sinon":14}]},{},[42,43,44,45,46,47,48,49,50,51]); diff --git a/dist/barometer.min.js b/dist/barometer.min.js index b1fe49a..c79473d 100644 --- a/dist/barometer.min.js +++ b/dist/barometer.min.js @@ -1,2 +1,2 @@ /*! barometer v1.0.0 */ -_='!e¢ t(aYn) n;r=n={¡:{id:a,†ed:!1}; e.call(r,r,r,tr.†ed“r}n={}; t.m=F.c=n,t.p="Zt(0)}([F,ne%1)=„2*n(6n(3*o%7*ž•,a =r ,=r.,a=o,=o.3o%1*ž="XXXXXZe$={–s:{ers:{}ƒ=0e( K=K||0,K++ Ze)a $FŠ=Š||[],Š.‡(t) –ZF)};u•;t$u`ƒ<100`‰uu=‘n,5e3)n$eY0Ÿƒ`o.žu`(‰uu•*’;e(*n=o.ž;if(‚)‚(n,Q*else{i=new r.a;i.op("POSTZn,!ei.‘RHeader("C@tt-TypeZ"applicWi@/js@"i.sd(Qi.timeout=4e3}}a=…;i=["UNSENTZ"OPENEDZ"HEADERS_RECEIVEDZ"LOADINGZ"DONE"];r "xhr."+eo$F,na={˜:n}r”e)"_"Ÿr[0]`0Ÿe[r]`(a[r]=e[r]-t* au t,n,r=u(#ži=o(#Times,#off‘,#˜)s”i)t="timing8‹ (n,i[s]*t="£s .(nt="size (n,(#r.£Textšˆ)…$e=new a;try{¥op,o=#sd;#op$o,utry{s=;r(e,"readystWec—Zt[i[#]]=}r(e,"†dZn u({type:o,ž:u,Times:t,off‘:s,˜:-s,r:e})})}cWch(c} n,#sd$ o}cWch(u} e,n?(t,n,!1):Œ`Œ("@"+t,n)n=(eš~Ž^https?:\\/\\//i,"")\\/k*(tš.mWch(/\\?/)`(n+=t~slice(1)\\/k)no ¥;e=#toJSON()a”e)""Ÿa`0Ÿe`(t=e-n* tr ’u(ref,ash*"page†."+t™ek)i$F=.timing||{n=o(t)r”n)e=r(ro (e,n[r]*u=(.i@||{}).œCount||0;e=r("œs"o (e,u)"object"==typeof `("complete"===documt.?i():r(,"†Zi)),r,o,u=e={i%4s%6c%2f=0,g=[];u.s$c (r,-a)u.f$c.(›visits")a`u.s(f++;e=,t=f,n=0,i=[9,9,9,9,9],l=!1,p=25,d=›dynamic"v$‰_o“a=,r=›gagemt"*’125;c (d,-e-t)n=0;nn`(n=r!lYtŸf) v(*i.shift(i.‡(r)o=0,u=0;u<5;u++)o+=i[u]; o<5`n>25?v():void ‘w,p)}p)};w()u.f(?(i(,"popstWeZu.feYe’¤;¤$ u.f(t}}()):i(,"hashc—Zu.fu g.‡(e)u.$eYo) e(*’!1;u(t||(t“e())})}}]*¢(F,n€avar buffer.¡returnwindownew DWe .– 8#r.stWu‹ ,t(ƒ++,„.debug`c@sol#log("Timeout(){.@PageC—d.apply(e,argumts)}count.Ž[^a-z0-9-),=e={r%a.readyStWe}F€e$F@cePageLoadedequest;for(},navigW.performanceen#e.$=%=n(*);8."+#type™r™@onFe,tK.ers[e]QJSON.stringify(t)WatYif(Z",`&&k]/gi,"_"~.split("?")[0].#addEvtLister€"use strict";}4o%2u%5*‚or.sdBeac@ƒSize„.barometer….XMLHttpR†load‡pushˆ.lgth‰clearŠ s[e]‹s,n=r(toŒ#WtachEvt.locWi@.hŽreplace(/.history[a]‘set’t=“=!0,” in •=null–gauge—hange˜total™+"."+š||"")›s.r("œredirecti@StartžurlŸ!== $e¡exports¢functi@£resp@se¤#‡StWe¥’{n=#';for(Y in $='¥¤£¢¡ Ÿžœ›š™˜—–•”“’‘ŽŒ‹Š‰ˆ‡†…„ƒ‚€~k`ZYWQKF@8*%$# ')with(_.split($[Y]))_=join(pop());eval(_) \ No newline at end of file +_='!e´ t(an¡) n¡;r=n¡={²:{id:a,“ed:!1}; e¡.call(r,r,r,tr.“ed¤r}n={}; t.m=¢.c=n,t.p="",t(0)}([¢,ne1)=Z@r2o6~n(7n(3n(8¯=¥,a =r ,=r.,ad=od,#=o.#@r3o1~¯="XXXXX",eW={¦s:{ers:{}Ž=0e(„=„||0,„++,",e)a ,t™=™||[],™.”(t¦",¢)};i=¥;tWiKŽ<100K˜ii=£n,5e3)n0žŽKo.¯iK(˜ii=¥~±;e(~n=o.¯;‹)‹(n,‚~else{u=new r.a;u.op("POST",n,!eu.£R*Header("C`tt-Type","applici`/js`"u.sd(‚u.timeout=4e3}}…a=’;u=["UNSENT","OPENED","HEADERS_RECEIVED","LOADING","DONE"];r"xhrQeo,t,na={¨:n}r•e)"_"žr[0]K0že°K(a°=e°-t~ ait,n,r=i(¯u=o(FTimes,off£,¨)s•u)t="timing‰› (n,u[s]~t="µs.(nt="size (n,(r*.µText©.‡)’We=new a;try{±{n=op,o=sd;op=o,itry{s=;r(e,"readystec§",t[u[F]]=}r(e,"“d",n i({–:o,¯:i,FTimes:t,off£:s,¨:-s,r*:e})})}c—c} n,sdW o}c—i} e,n†?†(t,n,!1):œKœ("`"+t,n)n=(e©$.ƒ^€ -\\/~(t©.m—/\\?/)K(n+=t$.slice(1) -\\/)n},r,o,i@u4s7c2f=0,g=[];i.uWc (r,-a)i.sWc.(ªvisits")aKi.u(f++;e=,t=f,n=0,u=[9,9,9,9,9],l=!1,p=25,d=ªdynamic"vW˜wo¤a=,r=ªgagemt"~±125;c (d,-e-t)n=0;nnK(n=r!ltžf) v(~uŠu.”(r)o=0,i=0;i<5;i++)o+=u[i]; o<5Kn>25?v():void £m,p)}p)};m()i.s( ?(u(,"popste",i.see±·;·W i.s(t}}( )):u(,"hashc§",i.sidg.”(e)i.#o) e(~±!1;id(t||(t¤e())})…o{n=®;e=toJSON()a•e)"®"žaK0že¡K(t¡=e¡-n~ tri(ref,ash~"page“Qt+"Qe -)fW¢= .timing||{n=o(t)r•n)e=r(ro (e,n°~i=( .Yi`||{}).¬Count||0;e=r("¬s"o (e,i)"object"==–of K("complete"===documt.F?f():r(,"“",f)),r@o6i2u=0,s=0;‘š(e~tK³(¢)šZ.rs;tfor(nŠro=0;o