autocannon-reporter
Advanced tools
Comparing version 0.0.5 to 0.0.6
@@ -9,2 +9,4 @@ Usage: autocannon-reporter [opts] | ||
The path to the json results. Required when not piping into this tool. | ||
-c/--compare FILES | ||
The paths to multiple json results to be compared to the input. | ||
-v/--version | ||
@@ -14,1 +16,4 @@ Print the version number. | ||
Print this menu. | ||
You can also pipe in ndjson results, the first will be considered the input and | ||
the rest are used for the comparison |
65
index.js
@@ -8,5 +8,6 @@ #! /usr/bin/env node | ||
const path = require('path') | ||
const concat = require('concat-stream') | ||
const buildReport = require('./template') | ||
const help = fs.readFileSync(path.join(__dirname, 'help.txt'), 'utf8') | ||
const ndjson = require('ndjson') | ||
const steed = require('steed') | ||
@@ -18,2 +19,3 @@ function start () { | ||
input: 'i', | ||
compare: 'c', | ||
version: 'v', | ||
@@ -26,7 +28,4 @@ help: 'h' | ||
argv.outputPath = path.join(process.cwd(), 'report.html') | ||
argv.outputPathFolder = path.dirname(argv.outputPath) | ||
if (argv.version) { | ||
console.log('autocannon', 'v' + require('./package').version) | ||
console.log('autocannon-reporter', 'v' + require('./package').version) | ||
console.log('node', process.version) | ||
@@ -41,2 +40,9 @@ return | ||
if (argv.compare) { | ||
argv._.push(argv.compare) | ||
argv.compare = argv._ | ||
} | ||
argv.outputPath = path.join(process.cwd(), 'report.html') | ||
argv.outputPathFolder = path.dirname(argv.outputPath) | ||
if (process.stdin.isTTY) { | ||
@@ -50,12 +56,14 @@ if (!argv.input) { | ||
argv.inputPath = path.isAbsolute(argv.input) ? argv.input : path.join(process.cwd(), argv.input) | ||
const results = require(argv.inputPath) | ||
const report = buildReport(results) | ||
writeReport(report, argv.outputPath, (err) => { | ||
if (err) console.err('Error writting report: ', err) | ||
else console.log('Report written to: ', argv.outputPath) | ||
}) | ||
} else { | ||
const concatStream = concat((res) => { | ||
const results = JSON.parse(res.toString()) | ||
const report = buildReport(results) | ||
argv.compare = argv.compare || [] | ||
steed.map(argv.compare, (val, cb) => { | ||
val = path.isAbsolute(val) ? val : path.join(process.cwd(), val) | ||
fs.access(val, fs.F_OK, function (err) { | ||
if (err) return cb(new Error('Can\'t access ' + val)) | ||
cb(null, require(val)) | ||
}) | ||
}, (err, compare) => { | ||
if (err) return console.log(err) | ||
compare = sort(compare) | ||
const results = require(argv.inputPath) | ||
const report = buildReport(results, compare) | ||
writeReport(report, argv.outputPath, (err) => { | ||
@@ -66,3 +74,15 @@ if (err) console.err('Error writting report: ', err) | ||
}) | ||
process.stdin.pipe(concatStream) | ||
} else { | ||
let compare = [] | ||
process.stdin | ||
.pipe(ndjson.parse()) | ||
.on('data', (json) => { compare.push(json) }) | ||
.on('finish', () => { | ||
compare = sort(compare) | ||
const report = buildReport(compare[0], compare) | ||
writeReport(report, argv.outputPath, (err) => { | ||
if (err) console.err('Error writting report: ', err) | ||
else console.log('Report written to: ', argv.outputPath) | ||
}) | ||
}) | ||
} | ||
@@ -81,1 +101,14 @@ } | ||
} | ||
function sort (array) { | ||
array.sort(function (a, b) { | ||
if (a.finish < b.finish) { | ||
return 1 | ||
} | ||
if (a.finish > b.finish) { | ||
return -1 | ||
} | ||
return 0 | ||
}) | ||
return array | ||
} |
{ | ||
"name": "autocannon-reporter", | ||
"version": "0.0.5", | ||
"version": "0.0.6", | ||
"description": "A tool for creating html reports for autocannon", | ||
@@ -42,3 +42,2 @@ "main": "index.js", | ||
"dependencies": { | ||
"concat-stream": "^1.5.1", | ||
"flexboxgrid": "^6.3.0", | ||
@@ -49,4 +48,6 @@ "hyperscript": "^1.4.7", | ||
"moment": "^2.14.1", | ||
"pretty-bytes": "^3.0.1" | ||
"ndjson": "^1.4.3", | ||
"pretty-bytes": "^3.0.1", | ||
"steed": "^1.1.3" | ||
} | ||
} |
@@ -0,3 +1,7 @@ | ||
![banner](autocannon-banner.png) | ||
# Autocannon-reporter | ||
[![Build Status](https://travis-ci.org/thekemkid/autocannon-reporter.svg?branch=master)](https://travis-ci.org/thekemkid/autocannon-reporter) | ||
A simple html reporter for autocannon. | ||
@@ -66,2 +70,4 @@ | ||
The path to the json results. Required when not piping into this tool. | ||
-c/--compare FILES | ||
The paths to multiple json results to be compared to the input. | ||
-v/--version | ||
@@ -71,2 +77,5 @@ Print the version number. | ||
Print this menu. | ||
You can also pipe in ndjson results, the first will be considered the input and | ||
the rest are used for the comparison | ||
``` | ||
@@ -76,7 +85,8 @@ | ||
#### buildReport(result) | ||
#### buildReport(result, compare) | ||
* `result`: The result of an autocannon run. `Object`. _Required_ | ||
* `compare`: An array of old autocannon results to compare against. `Array`. _optional_ | ||
Returns a string of html representing the results | ||
Returns a string of html representing the results and comparison | ||
@@ -90,11 +100,5 @@ | ||
## Todo | ||
## Acknowledgements | ||
- [ ] TESTS | ||
- [ ] Pretty styling | ||
- [ ] Generate pretty html for throughput and latency (use chartist?) | ||
- [ ] Generate charts for throughput and latency | ||
- [ ] Generate a bar chart for the reponse types (1xx, 2xx, 3xx, 4xx, 5xx) | ||
- [ ] Pie chart, showing the amount of error, and how many of them were timeouts | ||
- [ ] Possibly add the ability to enter multiple results and generate multiple reports on the same page, with comparisons between them? | ||
Sponsored by [nearForm](http://www.nearform.com) | ||
@@ -101,0 +105,0 @@ ## License |
@@ -11,4 +11,11 @@ 'use strict' | ||
module.exports = function (results) { | ||
const bodyTree = report(results) | ||
module.exports = function (results, compare) { | ||
if (compare && compare.length > 0) { | ||
for (let i = compare.length; i > 0; i--) { | ||
if (compare[i - 1].start === results.start && compare[i - 1].finish === results.finish) { | ||
compare.splice(i - 1, 1) | ||
} | ||
} | ||
} | ||
const bodyTree = report(results, compare) | ||
@@ -33,3 +40,3 @@ const fullBody = ` | ||
<script> | ||
${scripts(results)} | ||
${scripts(results, compare)} | ||
</script> | ||
@@ -36,0 +43,0 @@ </body> |
@@ -6,2 +6,4 @@ 'use strict' | ||
const moment = require('moment') | ||
const fs = require('fs') | ||
const path = require('path') | ||
function datestuff (date) { | ||
@@ -12,11 +14,16 @@ return moment(date).format('MMMM Do YYYY, h:mm:ss a') | ||
module.exports = function (results) { | ||
module.exports = function (results, compare) { | ||
const hx = hyperx(h) | ||
return reportBody(results, hx, compare) | ||
} | ||
function reportBody (results, hx, compare) { | ||
return hx` | ||
<div> | ||
<div class='title'> | ||
${results.title ? hx`<h1>Results for ${results.title}</h1>` : hx`<h1>Autocannon results</h1>`} | ||
<div class='header'> | ||
<img class="logo" src="data:image/png;base64,${fs.readFileSync(path.join(__dirname, '../assets/autocannon-logo.png')).toString('base64')}"/> | ||
${results.title ? hx`<h1>Results for ${results.title}</h1>` : hx`<h1>Results</h1>`} | ||
</div> | ||
<div class='report'> | ||
<div class ='object content no-border'> | ||
<div class ='object content no-border spaceout'> | ||
<ul class ='grid'> | ||
@@ -39,3 +46,4 @@ <li><b>Start Time:</b> ${datestuff(results.start)} </li> | ||
</div> | ||
${results['2xx'] + results.non2xx > 0 ? panels(results, hx) : warnPanel(results, hx)} | ||
${results['2xx'] + results.non2xx > 0 ? panels(results, hx, compare) : warnPanel(results, hx)} | ||
${compare && compare.length > 0 ? comparePanels(results, hx, compare) : ''} | ||
</div> | ||
@@ -46,8 +54,9 @@ </div> | ||
function panels (results, hx) { | ||
function panels (results, hx, compare) { | ||
return hx` | ||
<div class='panels'> | ||
<div class='standard-panels'> | ||
${responseBarPanel(results, hx)} | ||
${responsePiePanel(results, hx)} | ||
${latencyTablePanel(results, hx)} | ||
${throughputTablePanel(results, hx)} | ||
${results.errors === 0 && results.timeouts === 0 ? '' : errorPiePanel(results, hx)} | ||
${tablesPanel(results, hx)} | ||
</div> | ||
@@ -57,25 +66,12 @@ ` | ||
function latencyTablePanel (results, hx) { | ||
return hx` | ||
<div class='object latency'> | ||
function responseBarPanel (results, hx) { | ||
return hx ` | ||
<div class='object responseBar'> | ||
<div class='heading' onclick="growDiv(this)"> | ||
<h2 class='symbol'>-</h2> | ||
<h2>Latency</h2> | ||
<h2 class='symbol'>-</h2> | ||
<h2>Response Times Histogram</h2> | ||
</div> | ||
<div class='content'> | ||
<div class='content graph'> | ||
<div class='measuringWrapper'> | ||
<table class='table' style="width:100%"> | ||
<tr> | ||
<th>Stat</th> | ||
<th>Value</th> | ||
</tr> | ||
${ | ||
Object.keys(results.latency).map((key) => { | ||
return hx`<tr> | ||
<td>${key}</td> | ||
<td>${results.latency[key]}</td> | ||
</tr>` | ||
}) | ||
} | ||
</table> | ||
<div class="ct-bar"></div> | ||
</div> | ||
@@ -87,25 +83,46 @@ </div> | ||
function throughputTablePanel (results, hx) { | ||
function responsePiePanel (results, hx) { | ||
return hx ` | ||
<div class='object reponsePie'> | ||
<div class='heading' onclick="growDiv(this)"> | ||
<h2 class='symbol'>-</h2> | ||
<h2>Response Types Piechart</h2> | ||
</div> | ||
<div class='content graph'> | ||
<div class='measuringWrapper'> | ||
<div class="ct-chart-responses ct-perfect-fourth"></div> | ||
</div> | ||
</div> | ||
</div> | ||
` | ||
} | ||
function errorPiePanel (results, hx) { | ||
return hx ` | ||
<div class='object errorPie'> | ||
<div class='heading' onclick="growDiv(this)"> | ||
<h2 class='symbol'>-</h2> | ||
<h2>Error Piechart</h2> | ||
</div> | ||
<div class='content graph'> | ||
<div class='measuringWrapper'> | ||
<div class="ct-error-pie ct-perfect-fourth"></div> | ||
</div> | ||
</div> | ||
</div> | ||
` | ||
} | ||
function tablesPanel (results, hx) { | ||
return hx` | ||
<div class='object throughput'> | ||
<div class='object'> | ||
<div class='heading' onclick="growDiv(this)"> | ||
<h2 class='symbol'>-</h2> | ||
<h2>Throughput</h2> | ||
<h2>Requests, Latency and Throughput</h2> | ||
</div> | ||
<div class='content'> | ||
<div class='measuringWrapper'> | ||
<table class='table' style="width:100%"> | ||
<tr> | ||
<th>Stat</th> | ||
<th>Value</th> | ||
</tr> | ||
${ | ||
Object.keys(results.throughput).map((key) => { | ||
return hx`<tr> | ||
<td>${key}</td> | ||
<td>${prettyBytes(results.throughput[key])}</td> | ||
</tr>` | ||
}) | ||
} | ||
</table> | ||
<div class='measuringWrapper spaceout'> | ||
${makeTable('Requests', results.latency, key => hx`<tr><td>${key}</td><td>${results.latency[key]}</td></tr>`)} | ||
${makeTable('Latency', results.requests, key => hx`<tr><td>${key}</td><td>${results.requests[key]}</td></tr>`)} | ||
${makeTable('Throughput', results.throughput, key => hx`<tr><td>${key}</td><td>${prettyBytes(results.throughput[key])}</td></tr>`)} | ||
</div> | ||
@@ -115,2 +132,15 @@ </div> | ||
` | ||
function makeTable (title, obj, map) { | ||
return hx` | ||
<div> | ||
<h3>${title}</h3> | ||
<table class='table' style="width:100%"> | ||
<tr> | ||
<th>Stat</th> | ||
<th>Value</th> | ||
</tr> | ||
${Object.keys(obj).map(map)} | ||
</table> | ||
</div>` | ||
} | ||
} | ||
@@ -134,12 +164,23 @@ | ||
function responsePiePanel (results, hx) { | ||
return hx ` | ||
<div class='object reponsePie'> | ||
function comparePanels (results, hx, compare) { | ||
return hx` | ||
<div class='compare-panels'> | ||
${requestsPanel(results, hx)} | ||
${bandwidthPanel(results, hx)} | ||
${latencyPanel(results, hx)} | ||
${errorsPanel(results, hx)} | ||
</div> | ||
` | ||
} | ||
function requestsPanel (results, hx) { | ||
return hx` | ||
<div class='object requestsBar'> | ||
<div class='heading' onclick="growDiv(this)"> | ||
<h2 class='symbol'>-</h2> | ||
<h2>Response Types Piechart</h2> | ||
<h2>Requests Comparison Chart</h2> | ||
</div> | ||
<div class='content graph'> | ||
<div class='measuringWrapper'> | ||
<div class="ct-chart ct-perfect-fourth"></div> | ||
<div class="chart-request-linechart ct-perfect-fourth"></div> | ||
</div> | ||
@@ -150,1 +191,50 @@ </div> | ||
} | ||
function bandwidthPanel (results, hx) { | ||
return hx` | ||
<div class='object bandwidthBar'> | ||
<div class='heading' onclick="growDiv(this)"> | ||
<h2 class='symbol'>-</h2> | ||
<h2>Bandwidth Comparison Chart</h2> | ||
</div> | ||
<div class='content graph'> | ||
<div class='measuringWrapper'> | ||
<div class="chart-bandwidth-linechart ct-perfect-fourth"></div> | ||
</div> | ||
</div> | ||
</div> | ||
` | ||
} | ||
function latencyPanel (results, hx) { | ||
return hx` | ||
<div class='object latencyBar'> | ||
<div class='heading' onclick="growDiv(this)"> | ||
<h2 class='symbol'>-</h2> | ||
<h2>Latency Comparison Chart</h2> | ||
</div> | ||
<div class='content graph'> | ||
<div class='measuringWrapper'> | ||
<div class="chart-latency-linechart ct-perfect-fourth"></div> | ||
</div> | ||
</div> | ||
</div> | ||
` | ||
} | ||
function errorsPanel (results, hx) { | ||
return hx` | ||
<div class='object errorBar'> | ||
<div class='heading' onclick="growDiv(this)"> | ||
<h2 class='symbol'>-</h2> | ||
<h2>Error Comparison Chart</h2> | ||
</div> | ||
<div class='content graph'> | ||
<div class='measuringWrapper'> | ||
<div class='centeredText'><span class ='redText'>Red</span> = Errors, <span class='blueText'>Blue</span> = Timeouts</div> | ||
<div class="chart-error-barchart ct-perfect-fourth"></div> | ||
</div> | ||
</div> | ||
</div> | ||
` | ||
} |
'use strict' | ||
module.exports = function (results) { | ||
module.exports = function (results, compare) { | ||
return ` | ||
var results = ${JSON.stringify(results)} | ||
var compare = ${JSON.stringify(compare)} | ||
${prettyBytes.toString()} | ||
${growDiv.toString()} | ||
${main.toString()} | ||
main(Chartist, results) | ||
main(Chartist, results, compare) | ||
@@ -14,36 +14,161 @@ ` | ||
function main (chartist, results) { | ||
var labels = ['1xx', '2xx', '3xx', '4xx', '5xx', 'non2xx', 'timeouts'] | ||
function main (chartist, results, compare) { | ||
var labels = ['1xx', '2xx', '3xx', '4xx', '5xx', 'timeouts'] | ||
var nonZeros = [] | ||
var seriesValues = [] | ||
labels.forEach(function (label) { | ||
if (results[label] !== 0) { | ||
nonZeros.push(label) | ||
seriesValues.push(results[label]) | ||
} | ||
}) | ||
var total = seriesValues.reduce(function (v, x) { return v + x }, 0) | ||
var options = { | ||
fullWidth: true, | ||
height: 450, | ||
labelInterpolationFnc: function (value) { | ||
return value + ' (' + Math.round(results[value] / total * 100) + '%)' | ||
} | ||
} | ||
chartist.Pie('.ct-chart-responses', { | ||
labels: nonZeros, | ||
series: seriesValues | ||
}, options) | ||
var errorLabels = [] | ||
var errorValues = [] | ||
if (results.errors !== 0) { | ||
errorLabels.push('Errors (non-timeouts)') | ||
errorValues.push(results.errors) | ||
} | ||
if (results.timeouts !== 0) { | ||
errorLabels.push('Timeouts') | ||
errorValues.push(results.timeouts) | ||
} | ||
chartist.Pie('.ct-error-pie', { | ||
labels: errorLabels, | ||
series: errorValues | ||
}, { | ||
fullWidth: true, | ||
height: 450 | ||
}) | ||
var lineOptions = { | ||
fullWidth: true, | ||
height: 450, | ||
axisY: { | ||
offset: 70, | ||
labelInterpolationFnc: function (value) { | ||
return (value) + 'ms' | ||
} | ||
} | ||
} | ||
var responsiveOptions = [ | ||
['screen and (min-width: 640px)', { | ||
chartPadding: 30, | ||
labelOffset: 110, | ||
labelDirection: 'explode', | ||
var lineValues = [results.latency.min, results.latency.average, | ||
results.latency.p50, results.latency.p75, | ||
results.latency.p90, results.latency.p99, results.latency.p999, results.latency.p9999, results.latency.p99999] | ||
chartist.Bar('.ct-bar', { | ||
labels: ['min', 'average', '50%', '75%', '90%', '99%', '99.9%', '99.99%', '99.999%'], | ||
series: [lineValues] | ||
}, lineOptions) | ||
// if compare array isn't used, return early | ||
if (!compare) return | ||
var requestOptions = { | ||
fullWidth: true, | ||
height: 450, | ||
axisY: { | ||
offset: 100, | ||
labelInterpolationFnc: function (value) { | ||
return value | ||
return (value) + ' reqs/sec' | ||
} | ||
}], | ||
['screen and (min-width: 1024px)', { | ||
labelOffset: 90, | ||
chartPadding: 20 | ||
}] | ||
] | ||
labels.forEach(function (label) { | ||
if (results[label] !== 0) { | ||
nonZeros.push(label) | ||
}, | ||
low: 0 | ||
} | ||
var compareRequestValues = [] | ||
var compareRequestLabels = [] | ||
compareRequestValues.push(results.requests.average) | ||
compareRequestLabels.push(results.finish) | ||
compare.forEach(function (value) { | ||
compareRequestValues.push(value.requests.average) | ||
compareRequestLabels.push(value.finish) | ||
}) | ||
chartist.Line('.chart-request-linechart', { | ||
labels: compareRequestLabels.reverse(), | ||
series: [compareRequestValues.reverse()] | ||
}, requestOptions) | ||
var bandwidthOptions = { | ||
fullWidth: true, | ||
height: 450, | ||
axisY: { | ||
offset: 100, | ||
labelInterpolationFnc: function (value) { | ||
return prettyBytes(value) + '/sec' | ||
} | ||
}, | ||
low: 0 | ||
} | ||
var compareBandwidthValues = [] | ||
var compareBandwidthLabels = [] | ||
compareBandwidthValues.push(results.requests.average) | ||
compareBandwidthLabels.push(results.finish) | ||
compare.forEach(function (value) { | ||
compareBandwidthValues.push(value.requests.average) | ||
compareBandwidthLabels.push(value.finish) | ||
}) | ||
chartist.Line('.chart-bandwidth-linechart', { | ||
labels: compareBandwidthLabels.reverse(), | ||
series: [compareBandwidthValues.reverse()] | ||
}, bandwidthOptions) | ||
var latencyOptions = { | ||
fullWidth: true, | ||
height: 450, | ||
axisY: { | ||
offset: 100, | ||
labelInterpolationFnc: function (value) { | ||
return (Number(value).toPrecision(2) + ' ms') | ||
} | ||
}, | ||
low: 0 | ||
} | ||
var compareLatencyValues = [] | ||
var compareLatencyLabels = [] | ||
compareLatencyValues.push(results.latency.average) | ||
compareLatencyLabels.push(results.finish) | ||
compare.forEach(function (value) { | ||
compareLatencyValues.push(value.latency.average) | ||
compareLatencyLabels.push(value.finish) | ||
}) | ||
chartist.Line('.chart-latency-linechart', { | ||
labels: compareLatencyLabels.reverse(), | ||
series: [compareLatencyValues.reverse()] | ||
}, latencyOptions) | ||
var errorOptions = { | ||
stackBars: true, | ||
fullWidth: true, | ||
height: 450, | ||
chartPadding: { | ||
left: 30 | ||
} | ||
} | ||
var compareErrorValues = [] | ||
var compareErrorLabels = [] | ||
var compareTimeoutValues = [] | ||
compareErrorValues.push(results.errors - results.timeouts) | ||
compareTimeoutValues.push(results.timeouts) | ||
compareErrorLabels.push(results.finish) | ||
compare.forEach(function (value) { | ||
compareErrorValues.push(value.errors - value.timeouts) | ||
compareTimeoutValues.push(value.timeouts) | ||
compareErrorLabels.push(value.finish) | ||
}) | ||
nonZeros.forEach(function (value) { | ||
seriesValues.push(results[value]) | ||
}) | ||
chartist.Pie('.ct-chart', { | ||
labels: nonZeros, | ||
series: seriesValues | ||
}, options, responsiveOptions) | ||
chartist.Bar('.chart-error-barchart', { | ||
labels: compareErrorLabels.reverse(), | ||
series: [compareTimeoutValues.reverse(), compareErrorValues.reverse()] | ||
}, errorOptions) | ||
} | ||
@@ -56,10 +181,37 @@ | ||
var content = objectContainer.getElementsByClassName('content')[0] | ||
if (content.clientHeight !== 20) { | ||
var wrapper = content.getElementsByClassName('measuringWrapper')[0] | ||
if (content.clientHeight > 20) { | ||
content.style.height = 0 | ||
content.style.padding = '2px' | ||
symbol.innerText = '+' | ||
} else { | ||
var wrapper = content.getElementsByClassName('measuringWrapper')[0] | ||
content.style.height = wrapper.clientHeight + 'px' | ||
content.style.padding = '20px' | ||
symbol.innerText = '-' | ||
} | ||
} | ||
function prettyBytes (num) { | ||
if (typeof num !== 'number' || Number.isNaN(Number(num))) { | ||
throw new TypeError('Expected a number, got ' + typeof num) | ||
} | ||
var exponent | ||
var unit | ||
var neg = num < 0 | ||
var units = ['B', 'kB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'] | ||
if (neg) { | ||
num = -num | ||
} | ||
if (num < 1) { | ||
return (neg ? '-' : '') + num + ' B' | ||
} | ||
exponent = Math.min(Math.floor(Math.log(num) / Math.log(1000)), units.length - 1) | ||
num = Number((num / Math.pow(1000, exponent)).toFixed(2)) | ||
unit = units[exponent] | ||
return (neg ? '-' : '') + num + ' ' + unit | ||
} |
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Dynamic require
Supply chain riskDynamic require can indicate the package is performing dangerous or unsafe dynamic code execution.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
141151
20
969
104
8
5
+ Addedndjson@^1.4.3
+ Addedsteed@^1.1.3
+ Addedfastfall@1.5.1(transitive)
+ Addedfastparallel@2.4.1(transitive)
+ Addedfastq@1.17.1(transitive)
+ Addedfastseries@1.7.2(transitive)
+ Addedjson-stringify-safe@5.0.1(transitive)
+ Addedndjson@1.5.0(transitive)
+ Addedreusify@1.0.4(transitive)
+ Addedsplit2@2.2.0(transitive)
+ Addedsteed@1.1.3(transitive)
+ Addedthrough2@2.0.5(transitive)
+ Addedxtend@4.0.2(transitive)
- Removedconcat-stream@^1.5.1
- Removedbuffer-from@1.1.2(transitive)
- Removedconcat-stream@1.6.2(transitive)
- Removedtypedarray@0.0.6(transitive)