Comparing version 0.7.3 to 1.0.0-0
const Promise = require('bluebird'); | ||
const Progress = require('cli-progress'); | ||
@@ -11,3 +12,3 @@ const chromedriver = require('./chromedriver'); | ||
opts.scroll = opts.scroll || false; | ||
if (!opts.url) { | ||
if (!opts.url || opts.url.length === 0) { | ||
return Promise.reject(new Error('`url` option must be provided')); | ||
@@ -17,3 +18,20 @@ } | ||
return Promise.using(chromedriver(), () => { | ||
return runner(opts); | ||
if (typeof opts.url === 'string') { | ||
opts.url = [opts.url]; | ||
} | ||
if (opts.progress) { | ||
opts.progress = new Progress.Bar({ format: '[{bar}] {percentage}% | ETA: {eta_formatted}' }); | ||
opts.progress.start(opts.url.length * opts.count); | ||
} else { | ||
opts.progress = { increment: () => {}, stop: () => {} }; | ||
} | ||
return Promise.map(opts.url, (url) => { | ||
return runner(Object.assign({}, opts, { url: url })); | ||
}) | ||
.then((result) => { | ||
opts.progress.stop(); | ||
return result; | ||
}); | ||
}); | ||
@@ -20,0 +38,0 @@ } |
@@ -11,9 +11,2 @@ /** | ||
const host = url.parse(pageurl).host; | ||
lines = lines.map((line) => { | ||
try { | ||
line.message = JSON.parse(line.message); | ||
line.timestamp = line.message.message.params.timestamp || (line.message.message.params.ts / 1e6); | ||
} catch (e) {} | ||
return line; | ||
}); | ||
@@ -20,0 +13,0 @@ lines = lines.filter((l) => !isNaN(l.timestamp)); |
const wd = require('wd'); | ||
const Promise = require('bluebird'); | ||
const bl = require('bl'); | ||
const JSONStream = require('JSONStream'); | ||
const browser = require('./browser'); | ||
const normalise = require('./normalise-logs'); | ||
const blacklist = ['UpdateLayerTree', 'Layout', 'FunctionCall', 'UpdateLayoutTree', | ||
'EventDispatch', 'TimerFire', 'TimerInstall', 'TimerRemove', 'ResourceSendRequest', 'ResourceReceivedData']; | ||
function test (opts) { | ||
@@ -36,3 +41,24 @@ return function () { | ||
.sleep(opts.sleep) | ||
.log('performance') | ||
.then(() => { | ||
return new Promise((resolve, reject) => { | ||
session.logstream('performance') | ||
.pipe(JSONStream.parse('value.*', (item) => { | ||
item.message = JSON.parse(item.message); | ||
item.name = item.message.message.params.name; | ||
item.timestamp = item.message.message.params.timestamp || (item.message.message.params.ts / 1e6); | ||
if (item.message.message.params && item.message.message.params.cat === '__metadata') { | ||
return; | ||
} | ||
if (blacklist.indexOf(item.name) !== -1) { | ||
return; | ||
} | ||
return item; | ||
})) | ||
.pipe(JSONStream.stringify()) | ||
.pipe(bl((err, data) => { | ||
err ? reject(err) : resolve(JSON.parse(data.toString())); | ||
})); | ||
}); | ||
}) | ||
.then((logs) => { | ||
@@ -39,0 +65,0 @@ return normalise(logs, opts.url); |
@@ -57,3 +57,4 @@ const mean = require('lodash.meanby'); | ||
min: Math.min.apply(Math, timings.map(t => t[key]).filter(t => t)), | ||
max: Math.max.apply(Math, timings.map(t => t[key]).filter(t => t)) | ||
max: Math.max.apply(Math, timings.map(t => t[key]).filter(t => t)), | ||
raw: timings.map(t => t[key]).filter(t => t) | ||
}; | ||
@@ -63,11 +64,13 @@ } | ||
function basicReporter (data) { | ||
const timings = data.map(metrics); | ||
return data.map((d) => { | ||
const timings = d.map(metrics); | ||
return Object.keys(timings[0]) | ||
.reduce((list, key) => { | ||
list[key] = result(timings, key); | ||
return list; | ||
}, {}); | ||
return Object.keys(timings[0]) | ||
.reduce((list, key) => { | ||
list[key] = result(timings, key); | ||
return list; | ||
}, {}); | ||
}); | ||
} | ||
module.exports = basicReporter; |
@@ -32,3 +32,5 @@ const babar = require('babar'); | ||
module.exports = function (data) { | ||
return data.map(run => chart(fps(run))); | ||
return data.map(d => { | ||
return d.map(run => chart(fps(run))); | ||
}); | ||
}; |
@@ -0,17 +1,56 @@ | ||
'use strict'; | ||
const chalk = require('chalk'); | ||
const ttest = require('ttest'); | ||
const Table = require('cli-table'); | ||
const basic = require('./basic'); | ||
function table (data) { | ||
function table (data, urls) { | ||
const result = basic(data); | ||
const table = new Table({ head: ['metric', 'mean', 'min', 'max'] }); | ||
const rows = Object.keys(result) | ||
.map((metric) => [ | ||
metric, | ||
result[metric].mean.toFixed(3), | ||
result[metric].min.toFixed(3), | ||
result[metric].max.toFixed(3) | ||
]); | ||
let head, rows; | ||
if (urls.length === 1) { | ||
head = ['metric', 'mean', 'min', 'max']; | ||
rows = Object.keys(result[0]) | ||
.map((metric) => [ | ||
metric, | ||
result[0][metric].mean.toFixed(3), | ||
result[0][metric].min.toFixed(3), | ||
result[0][metric].max.toFixed(3) | ||
]); | ||
} else if (urls.length === 2) { | ||
head = ['metric']; | ||
urls.forEach(url => head.push(url)); | ||
head.push('p < 0.05'); | ||
rows = Object.keys(result[0]) | ||
.map((metric) => { | ||
const row = result.map((res) => { | ||
return res[metric].mean; | ||
}); | ||
const min = Math.min.apply(Math, row); | ||
const max = Math.max.apply(Math, row); | ||
const colored = row.reduce((r, m) => { | ||
if (m === min) { | ||
r.push(chalk.green(m.toFixed(3))); | ||
} else if (m === max) { | ||
r.push(chalk.red(m.toFixed(3))); | ||
} else { | ||
r.push(m.toFixed(3)); | ||
} | ||
return r; | ||
}, [metric]); | ||
const tt = ttest(result[0][metric].raw, result[1][metric].raw, { varEqual: true }); | ||
if (!tt.valid()) { | ||
colored.push(tt.pValue().toFixed(3) + ' ' + chalk.green('✔')); | ||
} else { | ||
colored.push(tt.pValue().toFixed(3) + ' ' + chalk.red('✘')); | ||
} | ||
return colored; | ||
}); | ||
} | ||
const table = new Table({ head: head }); | ||
table.push.apply(table, rows); | ||
return table.toString(); | ||
@@ -18,0 +57,0 @@ } |
@@ -6,8 +6,49 @@ const wd = require('wd'); | ||
const request = require('request'); | ||
const httpUtils = require('wd/lib/http-utils'); | ||
wd.Webdriver.prototype._stream = function (opts) { | ||
var _this = this; | ||
// http options init | ||
var httpOpts = httpUtils.newHttpOpts(opts.method, this._httpConfig); | ||
var url = httpUtils.buildJsonCallUrl(this.noAuthConfigUrl, this.sessionID, opts.relPath, opts.absPath); | ||
// building callback | ||
var cb = opts.cb; | ||
if (opts.emit) { | ||
// wrapping cb if we need to emit a message | ||
var _cb = cb; | ||
cb = function () { | ||
var args = [].slice.call(arguments, 0); | ||
_this.emit(opts.emit.event, opts.emit.message); | ||
if (_cb) { _cb.apply(_this, args); } | ||
}; | ||
} | ||
// logging | ||
httpUtils.emit(this, httpOpts.method, url, opts.data); | ||
// writting data | ||
var data = opts.data || {}; | ||
httpOpts.prepareToSend(url, data); | ||
// building request | ||
return request(httpOpts); | ||
}; | ||
wd.Webdriver.prototype.logstream = function (logType) { | ||
return this._stream({ | ||
method: 'POST', | ||
relPath: '/log', | ||
data: { type: logType } | ||
}); | ||
}; | ||
function runner (opts) { | ||
opts.driver = opts.driver || 'http://localhost:9515'; | ||
opts.browser = wd.remote(opts.driver, 'promiseChain'); | ||
return Promise.map(times(opts.count), page(opts), { concurrency: 1 }); | ||
const f = page(opts); | ||
return Promise.map(times(opts.count), () => f().then((r) => { opts.progress.increment(); return r; }), { concurrency: 1 }); | ||
} | ||
module.exports = runner; |
{ | ||
"name": "timeliner", | ||
"version": "0.7.3", | ||
"version": "1.0.0-0", | ||
"description": "Network Timeline Analyser", | ||
@@ -23,6 +23,9 @@ "main": "index.js", | ||
"bluebird": "^3.4.6", | ||
"chalk": "^1.1.3", | ||
"chromedriver": "^2.24.1", | ||
"cli-progress": "^1.2.0", | ||
"cli-table": "^0.3.1", | ||
"lodash.meanby": "^4.10.0", | ||
"minimist": "^1.2.0", | ||
"ttest": "^1.1.0", | ||
"wd": "^1.0.0" | ||
@@ -29,0 +32,0 @@ }, |
@@ -26,3 +26,26 @@ # timeliner | ||
### Side by side comparison: | ||
You can run timeliner against two websites in parallel which will return a comparison of metrics, with an indicator of whether the difference is statistically significant (p<0.05). | ||
```shell | ||
$ timeliner https://facebook.com https://twitter.com | ||
# outputs | ||
┌──────────────────┬──────────────────────┬─────────────────────┬──────────┐ | ||
│ metric │ https://facebook.com │ https://twitter.com │ p < 0.05 │ | ||
├──────────────────┼──────────────────────┼─────────────────────┼──────────┤ | ||
│ render │ 0.835 │ 1.441 │ 0.079 ✘ │ | ||
├──────────────────┼──────────────────────┼─────────────────────┼──────────┤ | ||
│ domcontentloaded │ 0.916 │ 1.492 │ 0.080 ✘ │ | ||
├──────────────────┼──────────────────────┼─────────────────────┼──────────┤ | ||
│ load │ 0.989 │ 3.036 │ 0.118 ✘ │ | ||
└──────────────────┴──────────────────────┴─────────────────────┴──────────┘ | ||
``` | ||
*Note: you may need to increase the count option (i.e. sample size) in order to see statistical significance.* | ||
### Scrolling performance: | ||
```shell | ||
# analyse scrolling performance on a long webpage | ||
@@ -62,2 +85,6 @@ $ timeliner http://buzzfeed.com --reporter fps --sleep 5000 | ||
### `progress` | ||
if set then a progress bar will be output to the console showing test execution progress | ||
### `scroll` | ||
@@ -64,0 +91,0 @@ |
Sorry, the diff of this file is not supported yet
20921
373
124
10
+ Addedchalk@^1.1.3
+ Addedcli-progress@^1.2.0
+ Addedttest@^1.1.0
+ Addedansi-regex@2.1.1(transitive)
+ Addedansi-styles@2.2.1(transitive)
+ Addedchalk@1.1.3(transitive)
+ Addedcli-progress@1.8.0(transitive)
+ Addedcolors@1.4.0(transitive)
+ Addeddistributions@1.1.0(transitive)
+ Addedescape-string-regexp@1.0.5(transitive)
+ Addedhas-ansi@2.0.0(transitive)
+ Addedmathfn@1.2.0(transitive)
+ Addedstrip-ansi@3.0.1(transitive)
+ Addedsummary@0.3.2(transitive)
+ Addedsupports-color@2.0.0(transitive)
+ Addedttest@1.1.0(transitive)