cronometro
Advanced tools
Comparing version 0.3.0 to 0.4.0
@@ -0,1 +1,8 @@ | ||
### 2020-04-22 / 0.4.0 | ||
- chore: Allow greater test timeouts. | ||
- feat: Removed useless debug infrastructure. | ||
- feat: Reimplemented warmup mode. | ||
- feat: Use worker_threads for execution. Tested everything. | ||
### 2020-03-05 / 0.3.0 | ||
@@ -2,0 +9,0 @@ |
190
lib/index.js
'use strict'; | ||
var __importDefault = (this && this.__importDefault) || function (mod) { | ||
return (mod && mod.__esModule) ? mod : { "default": mod }; | ||
}; | ||
function __export(m) { | ||
for (var p in m) if (!exports.hasOwnProperty(p)) exports[p] = m[p]; | ||
} | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
// @ts-ignore | ||
const native_hdr_histogram_1 = __importDefault(require("native-hdr-histogram")); | ||
const path_1 = require("path"); | ||
const worker_threads_1 = require("worker_threads"); | ||
const models_1 = require("./models"); | ||
const print_1 = require("./print"); | ||
const debug = (process.env.NODE_DEBUG || '').includes('cronometro'); | ||
function schedule(operation) { | ||
process.nextTick(operation); | ||
} | ||
function runIteration(context) { | ||
function trackResults(error) { | ||
// Handle error | ||
if (error) { | ||
context.results[context.current.name] = { success: false, error, mean: 0 }; | ||
schedule(() => processQueue(context)); | ||
return; | ||
} | ||
const { histogram } = context.current; | ||
if (histogram.record(Number(process.hrtime.bigint() - start))) { | ||
context.current.records++; | ||
} | ||
if (context.errorThreshold > 0) { | ||
const completedPercentage = Math.floor((1 - context.current.remaining / context.iterations) * 10000); | ||
// Check if abort the test earlier. It is checked every 5% after 10% of the iterations | ||
if (completedPercentage > 1000 && completedPercentage % 500 === 0) { | ||
const standardErrorPercentage = histogram.stddev() / Math.sqrt(context.current.records) / histogram.mean(); | ||
if (standardErrorPercentage < context.errorThreshold) { | ||
context.current.remaining = 0; | ||
} | ||
} | ||
} | ||
if (context.current.remaining === 0) { | ||
context.results[context.current.name] = { | ||
success: true, | ||
size: context.current.records, | ||
min: histogram.min(), | ||
max: histogram.min(), | ||
mean: histogram.mean(), | ||
stddev: histogram.stddev(), | ||
percentiles: histogram | ||
.percentiles() | ||
.reduce((accu, { percentile, value }) => { | ||
accu[percentile] = value; | ||
return accu; | ||
}, {}), | ||
standardError: histogram.stddev() / Math.sqrt(context.current.records) | ||
}; | ||
schedule(() => processQueue(context)); | ||
return; | ||
} | ||
context.current.remaining--; | ||
schedule(() => runIteration(context)); | ||
__export(require("./models")); | ||
function scheduleNextTest(context) { | ||
// We still have work to do | ||
if (context.current < context.tests.length) { | ||
return process.nextTick(() => run(context)); | ||
} | ||
if (debug) { | ||
console.debug(`cronometro: Executing test ${context.current.name}, ${context.current.remaining} iterations to go`); | ||
if (context.print) { | ||
const { colors, compare, compareMode } = { | ||
colors: true, | ||
compare: false, | ||
compareMode: 'base', | ||
...(context.print === true ? {} : context.print) | ||
}; | ||
print_1.printResults(context.results, colors, compare, compareMode); | ||
} | ||
const start = process.hrtime.bigint(); | ||
try { | ||
// Execute the function and get the response time - Handle also promises | ||
const callResult = context.current.test(trackResults); | ||
if (callResult && typeof callResult.then === 'function') { | ||
callResult.then(() => trackResults(null), trackResults); | ||
context.callback(null, context.results); | ||
} | ||
function run(context) { | ||
const name = context.tests[context.current][0]; | ||
const worker = new worker_threads_1.Worker(path_1.join(__dirname, '../lib/runner.js'), { | ||
workerData: { | ||
path: process.argv[1], | ||
index: context.current, | ||
iterations: context.iterations, | ||
warmup: context.warmup, | ||
errorThreshold: context.errorThreshold | ||
} | ||
else if (context.current.test.length === 0) { | ||
trackResults(null); | ||
} | ||
} | ||
catch (error) { | ||
context.results[context.current.name] = { success: false, error }; | ||
schedule(() => processQueue(context)); | ||
} | ||
}); | ||
worker.on('error', (error) => { | ||
context.results[name] = { | ||
success: false, | ||
error, | ||
size: 0, | ||
min: 0, | ||
max: 0, | ||
mean: 0, | ||
stddev: 0, | ||
percentiles: {}, | ||
standardError: 0 | ||
}; | ||
context.current++; | ||
scheduleNextTest(context); | ||
}); | ||
worker.on('message', (result) => { | ||
context.results[name] = result; | ||
context.current++; | ||
scheduleNextTest(context); | ||
}); | ||
} | ||
function processQueue(context) { | ||
// Get the next test to run | ||
const next = context.queue.shift(); | ||
if (!next) { | ||
return context.callback(null, context.results); | ||
function cronometro(tests, options, cb) { | ||
/* istanbul ignore next */ | ||
if (!worker_threads_1.isMainThread) { | ||
worker_threads_1.workerData.tests = Object.entries(tests); | ||
return; | ||
} | ||
const testContext = context; | ||
testContext.current = { | ||
name: next[0], | ||
test: next[1], | ||
remaining: context.iterations - 1, | ||
records: 0, | ||
histogram: new native_hdr_histogram_1.default(1, 1e9, 5) | ||
}; | ||
schedule(() => runIteration(testContext)); | ||
} | ||
function cronometro(tests, options, callback) { | ||
let promise; | ||
@@ -97,5 +69,6 @@ let promiseResolve; | ||
if (typeof options === 'function') { | ||
callback = options; | ||
cb = options; | ||
options = {}; | ||
} | ||
let callback = cb; | ||
if (!callback) { | ||
@@ -114,9 +87,3 @@ promise = new Promise((resolve, reject) => { | ||
// Parse and validate options | ||
const { iterations, errorThreshold, print, warmup } = { | ||
iterations: 1e4, | ||
warmup: true, | ||
errorThreshold: 1, | ||
print: true, | ||
...options | ||
}; | ||
const { iterations, errorThreshold, print, warmup } = { ...models_1.defaultOptions, ...options }; | ||
// tslint:disable-next-line strict-type-predicates | ||
@@ -132,39 +99,14 @@ if (typeof iterations !== 'number' || iterations < 1) { | ||
} | ||
// Process all tests | ||
// Prepare the test | ||
const context = { | ||
queue: Object.entries(tests), | ||
results: {}, | ||
warmup, | ||
iterations, | ||
errorThreshold: errorThreshold / 100, | ||
callback(error, results) { | ||
if (error) { | ||
callback(error); | ||
return; | ||
} | ||
if (print) { | ||
const { colors, compare, compareMode } = { | ||
colors: true, | ||
compare: false, | ||
compareMode: 'base', | ||
...(print === true ? {} : print) | ||
}; | ||
print_1.printResults(results, colors, compare, compareMode); | ||
} | ||
callback(null, results); | ||
} | ||
}; | ||
const boot = { | ||
queue: warmup ? context.queue.slice(0) : [], | ||
print, | ||
tests: Object.entries(tests), | ||
results: {}, | ||
iterations, | ||
errorThreshold: errorThreshold / 100, | ||
callback(error) { | ||
if (error) { | ||
callback(error); | ||
return; | ||
} | ||
schedule(() => processQueue(context)); | ||
} | ||
current: 0, | ||
callback | ||
}; | ||
schedule(() => processQueue(boot)); | ||
process.nextTick(() => run(context)); | ||
return promise; | ||
@@ -171,0 +113,0 @@ } |
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.defaultOptions = { | ||
iterations: 1e4, | ||
warmup: true, | ||
errorThreshold: 1, | ||
print: true | ||
}; | ||
exports.percentiles = [0.001, 0.01, 0.1, 1, 2.5, 10, 25, 50, 75, 90, 97.5, 99, 99.9, 99.99, 99.999]; |
@@ -6,2 +6,7 @@ "use strict"; | ||
const styles = ['red', 'green', 'yellow', 'blue', 'magenta', 'cyan', 'white', 'gray']; | ||
let currentLogger = console.log; | ||
function setLogger(logger) { | ||
currentLogger = logger; | ||
} | ||
exports.setLogger = setLogger; | ||
function printResults(results, colors, compare, mode) { | ||
@@ -14,6 +19,14 @@ const styler = colors ? acquerello_1.colorize : acquerello_1.clean; | ||
const entries = Object.entries(results) | ||
.sort((a, b) => b[1].mean - a[1].mean) | ||
.sort((a, b) => (!a[1].success ? -1 : b[1].mean - a[1].mean)) | ||
.map(([name, result]) => { | ||
if (!result.success) { | ||
return { name, error: result.error, throughput: '', standardError: '', relative: '', compared: '' }; | ||
return { | ||
name, | ||
size: 0, | ||
error: result.error, | ||
throughput: '', | ||
standardError: '', | ||
relative: '', | ||
compared: '' | ||
}; | ||
} | ||
@@ -36,3 +49,3 @@ const { size, mean, standardError } = result; | ||
name, | ||
size: size, | ||
size, | ||
error: null, | ||
@@ -57,2 +70,3 @@ throughput: (1e9 / mean).toFixed(2), | ||
} | ||
return row; | ||
} | ||
@@ -77,5 +91,5 @@ const { name, size, throughput, standardError, relative } = entry; | ||
}); | ||
const compareHeader = `Difference with ${mode === 'base' ? compared : 'previous'}`; | ||
const compareHeader = `Difference with ${mode === 'base' ? 'slowest' : 'previous'}`; | ||
rows.unshift([ | ||
styler('{{bold white}}Test{{-}}'), | ||
styler('{{bold white}}Slower tests{{-}}'), | ||
styler('{{bold white}}Samples{{-}}'), | ||
@@ -95,3 +109,3 @@ styler('{{bold white}}Result{{-}}'), | ||
} | ||
console.log(table_1.table(rows, { | ||
currentLogger(table_1.table(rows, { | ||
columns: { | ||
@@ -98,0 +112,0 @@ 0: { |
{ | ||
"name": "cronometro", | ||
"version": "0.3.0", | ||
"version": "0.4.0", | ||
"description": "Simple benchmarking suite powered by HDR histograms.", | ||
@@ -28,5 +28,5 @@ "homepage": "https://sw.cowtech.it/cronometro", | ||
"scripts": { | ||
"lint": "tslint --project tsconfig.test.json -t stylish src/*.ts test/*.ts", | ||
"ci": "yarn lint && tap --no-color --reporter=spec --coverage-report=json --coverage-report=text --branches 90 --functions 90 --lines 90 --statements 90 test/*.spec.ts", | ||
"test": "tap --reporter=spec --coverage-report=html --coverage-report=text --no-browser test/*.spec.ts", | ||
"lint": "eslint src/*.ts test/*.ts", | ||
"ci": "yarn lint && tap --timeout=60 --no-color --reporter=spec --coverage-report=json --coverage-report=text --branches 90 --functions 90 --lines 90 --statements 90 test/*.spec.ts", | ||
"test": "tap --timeout=60 --reporter=spec --coverage-report=html --coverage-report=text --no-browser test/*.spec.ts", | ||
"prebuild": "rm -rf lib/* types/* && yarn lint", | ||
@@ -39,17 +39,23 @@ "build": "tsc -p .", | ||
"acquerello": "^0.1.2", | ||
"native-hdr-histogram": "^0.7.0", | ||
"hdr-histogram-js": "^1.2.0", | ||
"table": "^5.4.6" | ||
}, | ||
"devDependencies": { | ||
"@cowtech/tslint-config": "^5.13.0", | ||
"@types/node": "^13.5.1", | ||
"@types/table": "^4.0.7", | ||
"prettier": "^1.19.1", | ||
"tap": "^14.10.6", | ||
"tslint": "^5.20.0", | ||
"@cowtech/eslint-config": "^6.8.5", | ||
"@types/node": "^13.13.1", | ||
"@types/sinon": "^9.0.0", | ||
"@types/table": "^5.0.0", | ||
"prettier": "^2.0.4", | ||
"proxyquire": "^2.1.3", | ||
"sinon": "^9.0.2", | ||
"tap": "^14.10.7", | ||
"typescript": "^3.7.5" | ||
}, | ||
"peerDependencies": { | ||
"ts-node": "^8.9.0", | ||
"typescript": "^3.8.3" | ||
}, | ||
"engines": { | ||
"node": ">= 12.0.0" | ||
"node": ">=12.15.0" | ||
} | ||
} |
143
README.md
@@ -12,2 +12,32 @@ # cronometro | ||
## Requirements | ||
Cronometro uses [worker_threads](https://nodejs.org/dist/latest-v12.x/docs/api/worker_threads.html) to run tests in a isolated V8 enviroments to offer the most accurate benchmark. This imposes the restrictions described in the subsections below. | ||
### Supported Node versions | ||
Only Node 12.x and above are supported. | ||
### Script invocation | ||
The main script which invokes cronometro must be executable without command line arguments, as it is how it will be called within a Worker Thread. | ||
If you need to configure the script at runtime, use environment variables and optionally configuration files. | ||
### TypeScript | ||
cronometro uses [ts-node](https://www.npmjs.com/package/ts-node) to compile TypeScript files on the fly. | ||
ts-node and TypeScript are not installed automatically by cronometro (as they are listed in `peerDependencies`) so you need to do it manually. | ||
To pass the `tsconfig.json` project file to use, use the `TS_NODE_PROJECT` environment variable. | ||
### API use | ||
If you use cronometro as an API and manipulate its return value, consider that the exact same code its executed in both the main thread and in worker threads. | ||
Inside worker threads, the cronometro function invocation will return no value and no callbacks are invoked. | ||
You can use `isMainThread` from Worker Threads API to check in which environment the script is running. | ||
## Usage | ||
@@ -59,6 +89,6 @@ | ||
const results = cronometro({ | ||
test1: function() { | ||
test1: function () { | ||
// Do something | ||
}, | ||
test2: function() { | ||
test2: function () { | ||
// Do something else | ||
@@ -88,9 +118,14 @@ } | ||
const pattern = /[123]/g | ||
const replacements: { [key: string]: string } = { 1: 'a', 2: 'b', 3: 'c' } | ||
const subject = '123123123123123123123123123123123123123123123123' | ||
const results = cronometro( | ||
{ | ||
test1: function() { | ||
// Do something | ||
single() { | ||
subject.replace(pattern, (m) => replacements[m]) | ||
}, | ||
test2: function() { | ||
// Do something else | ||
multiple() { | ||
subject.replace(/1/g, 'a').replace(/2/g, 'b').replace(/3/g, 'c') | ||
} | ||
@@ -113,61 +148,53 @@ }, | ||
{ | ||
"test1": { | ||
"single": { | ||
"success": true, | ||
"size": 100001, | ||
"min": 4732, | ||
"max": 4732, | ||
"mean": 7010.153258467415, | ||
"stddev": 66157.57904031666, | ||
"size": 5, | ||
"min": 29785, | ||
"max": 41506, | ||
"mean": 32894.2, | ||
"stddev": 4407.019555209621, | ||
"percentiles": { | ||
"0": 4732, | ||
"50": 5216, | ||
"75": 6077, | ||
"100": 20469632, | ||
"87.5": 6988, | ||
"93.75": 13986, | ||
"96.875": 20314, | ||
"98.4375": 25338, | ||
"99.21875": 27127, | ||
"99.609375": 32019, | ||
"99.8046875": 39667, | ||
"99.90234375": 57527, | ||
"99.951171875": 81029, | ||
"99.9755859375": 237553, | ||
"99.98779296875": 700244, | ||
"99.993896484375": 996392, | ||
"99.9969482421875": 1133848, | ||
"99.99847412109375": 2129600, | ||
"99.99923706054688": 20469632 | ||
"1": 29785, | ||
"10": 29785, | ||
"25": 29861, | ||
"50": 30942, | ||
"75": 32377, | ||
"90": 41506, | ||
"99": 41506, | ||
"0.001": 29785, | ||
"0.01": 29785, | ||
"0.1": 29785, | ||
"2.5": 29785, | ||
"97.5": 41506, | ||
"99.9": 41506, | ||
"99.99": 41506, | ||
"99.999": 41506 | ||
}, | ||
"standardError": 209.20758821469119 | ||
"standardError": 1970.87906072392 | ||
}, | ||
"test2": { | ||
"multiple": { | ||
"success": true, | ||
"size": 100001, | ||
"min": 1376, | ||
"max": 1376, | ||
"mean": 3810.3853361466386, | ||
"stddev": 50388.5658604418, | ||
"size": 5, | ||
"min": 21881, | ||
"max": 33368, | ||
"mean": 27646.4, | ||
"stddev": 4826.189494829228, | ||
"percentiles": { | ||
"0": 1376, | ||
"50": 1478, | ||
"75": 1587, | ||
"100": 3069392, | ||
"87.5": 1732, | ||
"93.75": 1811, | ||
"96.875": 2037, | ||
"98.4375": 15448, | ||
"99.21875": 20835, | ||
"99.609375": 23230, | ||
"99.8046875": 42270, | ||
"99.90234375": 1129616, | ||
"99.951171875": 1333168, | ||
"99.9755859375": 1429376, | ||
"99.98779296875": 1483120, | ||
"99.993896484375": 1743192, | ||
"99.9969482421875": 2598512, | ||
"99.99847412109375": 2910800, | ||
"99.99923706054688": 3069392 | ||
"1": 21881, | ||
"10": 21881, | ||
"25": 23142, | ||
"50": 26770, | ||
"75": 33071, | ||
"90": 33368, | ||
"99": 33368, | ||
"0.001": 21881, | ||
"0.01": 21881, | ||
"0.1": 21881, | ||
"2.5": 21881, | ||
"97.5": 33368, | ||
"99.9": 33368, | ||
"99.99": 33368, | ||
"99.999": 33368 | ||
}, | ||
"standardError": 159.34183944119272 | ||
"standardError": 2158.337556546705 | ||
} | ||
@@ -174,0 +201,0 @@ } |
import { Callback, Options, Results, Tests } from './models'; | ||
export * from './models'; | ||
export declare function cronometro(tests: Tests, options: Options | Callback, callback?: Callback): Promise<Results> | undefined; | ||
export declare function cronometro(tests: Tests): Promise<Results> | void; | ||
export declare function cronometro(tests: Tests, options: Partial<Options>): Promise<Results>; | ||
export declare function cronometro(tests: Tests, options: Partial<Options>, cb: Callback): undefined; | ||
export declare function cronometro(tests: Tests, options: Callback): void; |
@@ -0,1 +1,2 @@ | ||
import { AbstractHistogram } from 'hdr-histogram-js'; | ||
export interface PrintOptions { | ||
@@ -8,2 +9,3 @@ colors?: boolean; | ||
iterations: number; | ||
errorThreshold: number; | ||
print: boolean | PrintOptions; | ||
@@ -16,13 +18,5 @@ warmup: boolean; | ||
export declare type Test = StaticTest | AsyncTest | PromiseTest; | ||
export declare type Callback = (err?: Error | null, result?: Results) => any; | ||
export interface Histogram { | ||
record(value: number): boolean; | ||
min(): number; | ||
max(): number; | ||
mean(): number; | ||
stddev(): number; | ||
percentiles(): Array<{ | ||
percentile: number; | ||
value: number; | ||
}>; | ||
export declare type Callback = ((err: Error | null) => any) | ((err: null, results: Results) => any); | ||
export interface Percentiles { | ||
[key: string]: number; | ||
} | ||
@@ -32,11 +26,9 @@ export interface Result { | ||
error?: Error; | ||
size?: number; | ||
min?: number; | ||
max?: number; | ||
mean?: number; | ||
stddev?: number; | ||
standardError?: number; | ||
percentiles?: { | ||
[key: string]: number; | ||
}; | ||
size: number; | ||
min: number; | ||
max: number; | ||
mean: number; | ||
stddev: number; | ||
standardError: number; | ||
percentiles: Percentiles; | ||
} | ||
@@ -50,16 +42,37 @@ export interface Tests { | ||
export interface Context { | ||
warmup: boolean; | ||
iterations: number; | ||
errorThreshold: number; | ||
print: boolean | PrintOptions; | ||
tests: Array<[string, Test]>; | ||
results: Results; | ||
current: number; | ||
callback: Callback; | ||
queue: Array<[string, Test]>; | ||
results: Results; | ||
} | ||
export interface WorkerContext { | ||
path: string; | ||
tests: Array<[string, Test]>; | ||
index: number; | ||
iterations: number; | ||
warmup: boolean; | ||
errorThreshold: number; | ||
} | ||
export interface TestContext extends Context { | ||
current: { | ||
name: string; | ||
test: Test; | ||
remaining: number; | ||
records: number; | ||
histogram: Histogram; | ||
}; | ||
export interface TestContext { | ||
name: string; | ||
test: Test; | ||
errorThreshold: number; | ||
total: number; | ||
executed: number; | ||
histogram: AbstractHistogram; | ||
start: bigint; | ||
handler(error?: Error | null): void; | ||
notifier(value: any): void; | ||
callback(result: Result): void; | ||
} | ||
export declare const defaultOptions: { | ||
iterations: number; | ||
warmup: boolean; | ||
errorThreshold: number; | ||
print: boolean; | ||
}; | ||
export declare const percentiles: number[]; |
import { Results } from './models'; | ||
export declare function setLogger(logger: (message: string, ...params: Array<any>) => void): void; | ||
export declare function printResults(results: Results, colors: boolean, compare: boolean, mode: 'base' | 'previous'): void; |
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
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
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
Environment variable access
Supply chain riskPackage accesses environment variables, which may be a sign of credential stuffing or data theft.
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
Environment variable access
Supply chain riskPackage accesses environment variables, which may be a sign of credential stuffing or data theft.
Found 1 instance in 1 package
26890
14
472
214
5
9
3
1
+ Addedhdr-histogram-js@^1.2.0
+ Addedarg@4.1.3(transitive)
+ Addedbase64-js@1.5.1(transitive)
+ Addedbuffer-from@1.1.2(transitive)
+ Addeddiff@4.0.2(transitive)
+ Addedhdr-histogram-js@1.2.0(transitive)
+ Addedmake-error@1.3.6(transitive)
+ Addedpako@1.0.11(transitive)
+ Addedsource-map@0.6.1(transitive)
+ Addedsource-map-support@0.5.21(transitive)
+ Addedts-node@8.10.2(transitive)
+ Addedtypescript@3.9.10(transitive)
+ Addedyn@3.1.1(transitive)
- Removednative-hdr-histogram@^0.7.0
- Removedabbrev@1.1.1(transitive)
- Removedansi-regex@2.1.1(transitive)
- Removedaproba@1.2.0(transitive)
- Removedare-we-there-yet@1.1.7(transitive)
- Removedbalanced-match@1.0.2(transitive)
- Removedbrace-expansion@1.1.11(transitive)
- Removedchownr@1.1.4(transitive)
- Removedcode-point-at@1.1.0(transitive)
- Removedconcat-map@0.0.1(transitive)
- Removedconsole-control-strings@1.1.0(transitive)
- Removedcore-util-is@1.0.3(transitive)
- Removeddebug@3.2.7(transitive)
- Removeddeep-extend@0.6.0(transitive)
- Removeddelegates@1.0.0(transitive)
- Removeddetect-libc@1.0.3(transitive)
- Removedfs-minipass@1.2.7(transitive)
- Removedfs.realpath@1.0.0(transitive)
- Removedgauge@2.7.4(transitive)
- Removedglob@7.2.3(transitive)
- Removedhas-unicode@2.0.1(transitive)
- Removediconv-lite@0.4.24(transitive)
- Removedignore-walk@3.0.4(transitive)
- Removedinflight@1.0.6(transitive)
- Removedinherits@2.0.4(transitive)
- Removedini@1.3.8(transitive)
- Removedis-fullwidth-code-point@1.0.0(transitive)
- Removedisarray@1.0.0(transitive)
- Removedminimatch@3.1.2(transitive)
- Removedminimist@1.2.8(transitive)
- Removedminipass@2.9.0(transitive)
- Removedminizlib@1.3.3(transitive)
- Removedmkdirp@0.5.6(transitive)
- Removedms@2.1.3(transitive)
- Removednan@2.20.0(transitive)
- Removednative-hdr-histogram@0.7.0(transitive)
- Removedneedle@2.9.1(transitive)
- Removednode-pre-gyp@0.13.0(transitive)
- Removednopt@4.0.3(transitive)
- Removednpm-bundled@1.1.2(transitive)
- Removednpm-normalize-package-bin@1.0.1(transitive)
- Removednpm-packlist@1.4.8(transitive)
- Removednpmlog@4.1.2(transitive)
- Removednumber-is-nan@1.0.1(transitive)
- Removedobject-assign@4.1.1(transitive)
- Removedonce@1.4.0(transitive)
- Removedos-homedir@1.0.2(transitive)
- Removedos-tmpdir@1.0.2(transitive)
- Removedosenv@0.1.5(transitive)
- Removedpath-is-absolute@1.0.1(transitive)
- Removedprocess-nextick-args@2.0.1(transitive)
- Removedrc@1.2.8(transitive)
- Removedreadable-stream@2.3.8(transitive)
- Removedrimraf@2.7.1(transitive)
- Removedsafe-buffer@5.1.25.2.1(transitive)
- Removedsafer-buffer@2.1.2(transitive)
- Removedsax@1.4.1(transitive)
- Removedsemver@5.7.2(transitive)
- Removedset-blocking@2.0.0(transitive)
- Removedsignal-exit@3.0.7(transitive)
- Removedstring-width@1.0.2(transitive)
- Removedstring_decoder@1.1.1(transitive)
- Removedstrip-ansi@3.0.1(transitive)
- Removedstrip-json-comments@2.0.1(transitive)
- Removedtar@4.4.19(transitive)
- Removedutil-deprecate@1.0.2(transitive)
- Removedwide-align@1.1.5(transitive)
- Removedwrappy@1.0.2(transitive)
- Removedyallist@3.1.1(transitive)