├── .gitignore
├── .travis.yml
├── README.md
├── docs
├── CHANGELOG.md
├── docs.md
└── todo.md
├── example
├── asyncs.js
├── basic.js
├── bench.js
├── configstore.js
├── math
│ ├── async-first-faster.js
│ ├── async-first-slower.js
│ ├── sync-first-faster.js
│ └── sync-first-slower.js
├── results.json
└── single.js
├── package.json
├── src
├── BenchChain.js
├── Results.js
├── UI.js
├── battery.js
├── deps.js
├── index.js
└── reports
│ ├── GraphReporter.js
│ ├── PercentReporter.js
│ ├── Report.js
│ └── TagReporter.js
└── test
├── adding.js
├── chain.js
├── cli.js
├── index.js
├── persistance.js
└── running.js
/.gitignore:
--------------------------------------------------------------------------------
1 | # Optional
2 | .nyc_output
3 | .fliphub
4 | coverage
5 |
6 | # Caches
7 | tmp
8 | _tmp
9 | .tmp
10 | .happypack
11 |
12 | # Dist/build output
13 | dist
14 | built
15 | build
16 | public
17 |
18 |
19 | # Numerous always-ignore extensions
20 | *.diff
21 | *.err
22 | *.orig
23 | *.log
24 | *.rej
25 | *.swo
26 | *.swp
27 | *.vi
28 | *~
29 | *.sass-cache
30 |
31 |
32 | # OS or Editor folders
33 | .DS_Store
34 | .cache
35 | .project
36 | .settings
37 | .tmproj
38 | nbproject
39 | Thumbs.db
40 | .eslintcache
41 | .idea
42 | .vscode
43 | .vmconfig
44 | .tmConfig
45 |
46 |
47 | # NPM packages folder.
48 | node_modules/
49 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: node_js
2 | sudo: false
3 |
4 | # https://docs.travis-ci.com/user/caching/#Clearing-Caches
5 | # cache:
6 | # apt: true
7 | # directories:
8 | # - node_modules
9 |
10 | # test with these versions
11 | node_js:
12 | - 7
13 |
14 | install:
15 | - npm install
16 |
17 | # @TODO: swap to yarn when needed
18 | # once installed, run these pkg scripts
19 | script:
20 | # - npm run build
21 | # - npm run lint
22 | # - npm run flow
23 | - npm test
24 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # 🏋️⛓ bench-chain
2 |
3 | > chainable benchmark recording - averages & graphs.
4 |
5 | [![Build Status][travis-image]][travis-url]
6 | [![NPM version][bench-chain-npm-image]][bench-chain-npm-url]
7 | [![MIT License][license-image]][license-url]
8 | [![bench-chain][gitter-badge]][gitter-url]
9 | [![Dependencies][david-deps-img]][david-deps-url]
10 | [![fluents][fluents-image]][fluents-url]
11 |
12 | [fluents-image]: https://img.shields.io/badge/⛓-fluent-9659F7.svg
13 | [fluents-url]: https://www.npmjs.com/package/flipchain
14 |
15 | [bench-chain-npm-image]: https://img.shields.io/npm/v/bench-chain.svg
16 | [bench-chain-npm-url]: https://npmjs.org/package/bench-chain
17 | [license-image]: http://img.shields.io/badge/license-mit-blue.svg?style=flat
18 | [license-url]: https://spdx.org/licenses/mit
19 | [gitter-badge]: https://img.shields.io/gitter/room/bench-chain/pink.svg
20 | [gitter-url]: https://gitter.im/bench-chain/Lobby
21 |
22 | [travis-image]: https://travis-ci.org/fluents/bench-chain.svg?branch=master
23 | [travis-url]: https://travis-ci.org/fluents/bench-chain
24 |
25 | [david-deps-img]: https://david-dm.org/bench-chain/bench-chain.svg
26 | [david-deps-url]: https://david-dm.org/bench-chain/bench-chain
27 |
28 |
29 |
30 |
35 |
36 | extension of [benchmark.js](https://benchmarkjs.com/)
37 |
38 | ## 📦 install
39 | ```bash
40 | yarn add bench-chain
41 | npm i bench-chain --save
42 | ```
43 |
44 | ```js
45 | const Bench = require('bench-chain')
46 | ```
47 |
48 | ## [🌐 documentation](./docs)
49 | ## [🔬 tests](./tests)
50 | ## [📘 examples](./examples)
51 |
52 | ### 👋 basic
53 | ```js
54 | const Bench = require('bench-chain')
55 |
56 | Bench
57 | // location to store benchmarks
58 | .init(__dirname, 'basic.json')
59 | // tag current benchmarks with, to mark what changes caused differences
60 | .tags('v1')
61 | // actual benchmarks
62 | .add('1 * 1', () => 1 * 1)
63 | .add('1 + 1', () => 1 + 1)
64 | .run()
65 | ```
66 |
67 | ### 💍 async
68 | ```js
69 | const Bench = require('bench-chain')
70 |
71 | const sleep = sleepDuration => new Promise(resolve => setTimeout(resolve, sleepDuration))
72 |
73 | Bench
74 | .init().dir(__dirname).filename('asyncs.json').setup()
75 | .name('sleepy')
76 | .tags('v1,v2')
77 |
78 | // can also use .add, and then .runAsync()
79 | .addAsync('sleep1', async done => {
80 | await sleep(1000)
81 | done()
82 | })
83 | .addAsync('sleep2', async done => {
84 | await sleep(2000)
85 | done()
86 | })
87 | .run()
88 | ```
89 |
90 |
91 | ### 🚩 flags
92 |
93 | [using funwithflags](https://github.com/aretecode/funwithflags)
94 |
95 | * `--graph` will show only the graph reporting, rather than run the benchmarks
96 | * `--run-times=10` will run the test `10` times
97 |
98 |
99 | ### 📇 metadata
100 |
101 |
102 | 🔋 battery parsing when available
103 | - will be used for comparing more benchmark results with averages
104 | - amperage (number)
105 | - currentCapacity (number)
106 | - percent (number)
107 | - charging (boolean)
108 | - temp (number)
109 |
110 |
111 | - **mem**: operating system memory, nodejs memory
112 | - **num**: operations a second from benchmarkjs hertz
113 | - **sampled**: total runs samples from benchmarkjs
114 | - **variation**: variation from benchmarkjs
115 | - **timesFor**: microtime | performance.now times for beginning & end of each run
116 | - **now**: Date.now for changes over time
117 |
118 | ### graphs for trends and variation
119 |
120 |
121 |
122 |
123 | ### grouped by tags
124 |
125 |
126 |
127 |
128 | ### progress bars
129 | 
130 |
131 |
132 |
133 |
134 |
--------------------------------------------------------------------------------
/docs/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # 0.5.1
2 | - ☮️ compatibility: update fliptime with microtime polyfil that prefers microtime dep when it is available and accepts flags when available
3 | - ℹ️️ add cli --help flag
4 | - 🚩 add --reasoning as a flag to show math calculations for showing
5 | - 🏛️% refactor percentage reporting part 3
6 | - +- which is faster and slower,
7 | - %x whether to use percent or times,
8 | - <> whether to flip the values,
9 | - .. the digits on the numbers
10 | - 📘 add examples to display all 4 of those possibilities (since it compares in groups of 2, async and sync)
11 | - 🔬📝 test todos
12 |
13 | # 0.5.0
14 | - ⛓ put suite in store - breaking
15 | - 📦⬇ optional deps
16 |
17 | # 0.4.4
18 | - 📊 added current results to not use every single result which can max things out
19 | - ℹ️️ jsdocs for data adding with Results
20 | - 🛁 some configstore cleaning in Results
21 | - ⚒🔢 temp fix for slower/faster numbers
22 | - 🎀 prettier name for suiteName when using path, prettier objformat
23 | - 🖼️📦⬇ use mozilla polyfil for padEnd inline, less deps
24 | - 📈 added reporting of ops, slicing only most recent messages
25 | - 📜 script running examples
26 | - %📊 pct report improvements & 🛁 clean!
27 | - 📘 example of using for just a single benchmark
28 |
29 | # 0.4.3
30 | - 📛 fix tag reporting with referencing parent instead of reporter
31 |
32 | # 0.4.2
33 | - 🌀 fix ending spinner missed timeout alongside the interval
34 | - ⚙ fix json parsing in obj-chain-plugin-config
35 |
36 | # 0.4.1
37 |
38 | - 📒🚚 move memory helpers and other data into deps file
39 | - 📦⬆ added deps (table, some lodash, script-chain for auto-progress, obj-chain for config)
40 | - 🔬 adding more tests
41 | - ⚙💽 adding config store
42 | - 🖼️ add screenshots
43 | - 🏛️🏰 refactor reporting from Reporter, into
44 | - % PercentReporter
45 | - 📊 GraphReporter
46 | - # TagReporter
47 | - 🛁 take out avggraphsinone until it works
48 | - 🌀 add spinner, split into UI
49 | - 🤸 split BenchChain out of index into a file
50 | - 🆙📘 examples
51 | - 🔗 update docs links
52 | - 👂 add more subscribing methods
53 | - 👾 simplify some ops
54 | - ⛓ convert to more chainable store methods
55 | - 🚩 more cli options
56 | - ℹ️️ more jsdocs
57 |
58 | # 0.4.0
59 | - ⚒💍 fix async reporting some properties only being added to last cycle, just added to all now (bigger data but simpler code)
60 | - [...] auto-progress bars
61 |
62 | # 0.3.0
63 | - add name to suite
64 | - add more fluent
65 | - add docs for new stuff
66 | - added percent calc, likely needs improvements
67 | - more examples
68 |
69 | # 0.2.0 🔋⏱⛓📊👾
70 | - 🔋 battery parsing from plist when available
71 | - 🔋 battery in benchmark recordings
72 | - ⏱ microtime in recordings
73 | - ⛓ extend chainable
74 | - 📊 format microtime recordings
75 | - 👾 simplify async benchmarks
76 | - 🤸 split reports into another class
77 | - 🔬 fix test folder name and add another super simple basic instantiation test
78 | - 📈📉 more nice graphs
79 |
--------------------------------------------------------------------------------
/docs/docs.md:
--------------------------------------------------------------------------------
1 | #
2 |
3 |
4 |
5 | ### src/index.js
6 |
7 |
8 | #### flow(funcs)
9 |
10 |
11 |
12 |
13 |
14 |
15 | ##### Parameters
16 |
17 | | Name | Type | Description | |
18 | | ---- | ---- | ----------- | -------- |
19 | | funcs | `Array.<Function>` | functions to flow left to right | |
20 |
21 |
22 |
23 |
24 | ##### Returns
25 |
26 |
27 | - `Function` passes args through the functions, bound to this
28 |
29 |
30 |
31 | #### module.exports()
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 | ##### Returns
41 |
42 |
43 | - `Void`
44 |
45 |
46 |
47 | #### formatNumber(number)
48 |
49 | Converts a number to a more readable comma-separated string representation.
50 |
51 |
52 |
53 |
54 | ##### Parameters
55 |
56 | | Name | Type | Description | |
57 | | ---- | ---- | ----------- | -------- |
58 | | number | `number` | The number to convert. | |
59 |
60 |
61 |
62 |
63 | ##### Returns
64 |
65 |
66 | - `string` The more readable string representation.
67 |
68 |
69 |
70 | #### constructor(dir)
71 |
72 |
73 |
74 |
75 |
76 |
77 | ##### Parameters
78 |
79 | | Name | Type | Description | |
80 | | ---- | ---- | ----------- | -------- |
81 | | dir | `string` | directory for the file with the record | |
82 |
83 |
84 |
85 |
86 | ##### Returns
87 |
88 |
89 | - `Void`
90 |
91 |
92 |
93 | #### cycle(event) *private method*
94 |
95 |
96 |
97 |
98 |
99 |
100 | ##### Parameters
101 |
102 | | Name | Type | Description | |
103 | | ---- | ---- | ----------- | -------- |
104 | | event | `Benchmark.Event` | | |
105 |
106 |
107 |
108 |
109 | ##### Returns
110 |
111 |
112 | - `Record` @chainable
113 |
114 |
115 |
116 | #### filename()
117 |
118 |
119 |
120 |
121 |
122 |
123 | ##### Parameters
124 |
125 | | Name | Type | Description | |
126 | | ---- | ---- | ----------- | -------- |
127 | | filename='./results.json' | `String` | | *Optional* |
128 |
129 |
130 |
131 |
132 | ##### Returns
133 |
134 |
135 | - `Record` @chainable
136 |
137 |
138 |
139 | #### load([force=false])
140 |
141 |
142 |
143 |
144 |
145 |
146 | ##### Parameters
147 |
148 | | Name | Type | Description | |
149 | | ---- | ---- | ----------- | -------- |
150 | | force=false | `Boolean` | force reload | *Optional* |
151 |
152 |
153 |
154 |
155 | ##### Returns
156 |
157 |
158 | - `Record` @chainable
159 |
160 |
161 |
162 | #### save()
163 |
164 |
165 |
166 |
167 |
168 |
169 |
170 |
171 | ##### Returns
172 |
173 |
174 | - `Record` @chainable
175 |
176 |
177 |
178 | #### getDiv(max)
179 |
180 |
181 |
182 |
183 |
184 |
185 | ##### Parameters
186 |
187 | | Name | Type | Description | |
188 | | ---- | ---- | ----------- | -------- |
189 | | max | `number` | | |
190 |
191 |
192 |
193 |
194 | ##### Returns
195 |
196 |
197 | - `number`
198 |
199 |
200 |
201 | #### trend()
202 |
203 |
204 |
205 |
206 |
207 |
208 |
209 |
210 | ##### Returns
211 |
212 |
213 | - `Object.<points, max, min>` trend graph data
214 |
215 |
216 |
217 | #### avgs(prop)
218 |
219 |
220 |
221 |
222 |
223 |
224 | ##### Parameters
225 |
226 | | Name | Type | Description | |
227 | | ---- | ---- | ----------- | -------- |
228 | | prop | `string` | map to this property to average with that data | |
229 |
230 |
231 |
232 |
233 | ##### Returns
234 |
235 |
236 | - `Averages` averages
237 |
238 |
239 |
240 | #### avg(data)
241 |
242 |
243 |
244 |
245 |
246 |
247 | ##### Parameters
248 |
249 | | Name | Type | Description | |
250 | | ---- | ---- | ----------- | -------- |
251 | | data | `Array.<number>` | | |
252 |
253 |
254 |
255 |
256 | ##### Returns
257 |
258 |
259 | - `number` average
260 |
261 |
262 |
263 | #### fastest()
264 |
265 |
266 |
267 |
268 |
269 |
270 |
271 |
272 | ##### Returns
273 |
274 |
275 | - `Array.<string>` test case name
276 |
277 |
278 |
279 | #### echoAvgs()
280 |
281 |
282 |
283 |
284 |
285 |
286 |
287 |
288 | ##### Returns
289 |
290 |
291 | - `Record` @chainable
292 |
293 |
294 |
295 | #### echoFastest()
296 |
297 |
298 |
299 |
300 |
301 |
302 |
303 |
304 | ##### Returns
305 |
306 |
307 | - `Record` @chainable
308 |
309 |
310 |
311 | #### echoTrend()
312 |
313 |
314 |
315 |
316 |
317 |
318 |
319 |
320 | ##### Returns
321 |
322 |
323 | - `Record` @chainable
324 |
325 |
326 |
327 | #### suite(dir[, auto=false])
328 |
329 |
330 |
331 |
332 |
333 |
334 | ##### Parameters
335 |
336 | | Name | Type | Description | |
337 | | ---- | ---- | ----------- | -------- |
338 | | dir | `string` | | |
339 | | auto=false | `Boolean` | | *Optional* |
340 |
341 |
342 |
343 |
344 | ##### Returns
345 |
346 |
347 | - `Object` {suite, record}
348 |
349 |
350 |
351 | #### suite([auto=false])
352 |
353 |
354 |
355 |
356 |
357 |
358 | ##### Parameters
359 |
360 | | Name | Type | Description | |
361 | | ---- | ---- | ----------- | -------- |
362 | | auto=false | `Boolean` | | *Optional* |
363 |
364 |
365 |
366 |
367 | ##### Returns
368 |
369 |
370 | - `Benchmark.Suite`
371 |
372 |
373 |
374 | #### setup([auto=true])
375 |
376 |
377 |
378 |
379 |
380 |
381 | ##### Parameters
382 |
383 | | Name | Type | Description | |
384 | | ---- | ---- | ----------- | -------- |
385 | | auto=true | `Boolean` | automatically sets up echoing and saving | *Optional* |
386 |
387 |
388 |
389 |
390 | ##### Returns
391 |
392 |
393 | - `Record` @chainable
394 |
395 |
396 |
397 | #### add(name, cb)
398 |
399 |
400 |
401 |
402 |
403 |
404 | ##### Parameters
405 |
406 | | Name | Type | Description | |
407 | | ---- | ---- | ----------- | -------- |
408 | | name | `string` | | |
409 | | cb | `Function` | | |
410 |
411 |
412 |
413 |
414 | ##### Returns
415 |
416 |
417 | - `Record` @chainable
418 |
419 |
420 |
421 | #### run(async)
422 |
423 |
424 |
425 |
426 |
427 |
428 | ##### Parameters
429 |
430 | | Name | Type | Description | |
431 | | ---- | ---- | ----------- | -------- |
432 | | async | `boolean` | | |
433 |
434 |
435 |
436 |
437 | ##### Returns
438 |
439 |
440 | - `Record` @chainable
441 |
442 |
443 |
444 | #### runAsync(async)
445 |
446 |
447 |
448 |
449 |
450 |
451 | ##### Parameters
452 |
453 | | Name | Type | Description | |
454 | | ---- | ---- | ----------- | -------- |
455 | | async | `boolean` | | |
456 |
457 |
458 |
459 |
460 | ##### Returns
461 |
462 |
463 | - `Record` @chainable
464 |
465 |
466 |
467 | #### runTimes([times=10])
468 |
469 |
470 |
471 |
472 |
473 |
474 | ##### Parameters
475 |
476 | | Name | Type | Description | |
477 | | ---- | ---- | ----------- | -------- |
478 | | times=10 | `Number` | | *Optional* |
479 |
480 |
481 |
482 |
483 | ##### Returns
484 |
485 |
486 | - `Record` @chainable
487 |
488 |
489 |
490 |
491 | *Documentation generated with [doxdox](https://github.com/neogeek/doxdox).*
492 |
--------------------------------------------------------------------------------
/docs/todo.md:
--------------------------------------------------------------------------------
1 | ## 📝 todo
2 | https://gist.github.com/springmeyer/a67d8d77057d30fc4bbe
3 | https://mathiasbynens.be/notes/javascript-benchmarking
4 | https://github.com/JamieMason/karma-benchmark/blob/master/src/run-benchmarks.js
5 | https://github.com/avajs/ava/issues/816
6 | https://github.com/avajs/ava/blob/master/bench/compare.js
7 | https://benchmarkjs.com/docs#prototype_compare
8 | https://github.com/ngryman/speedracer
9 | https://esbench.com/bench/5908b65d99634800a0347e7b
10 | https://github.com/logicalparadox/matcha
11 |
--------------------------------------------------------------------------------
/example/asyncs.js:
--------------------------------------------------------------------------------
1 | const Bench = require('../')
2 |
3 | const sleep = sleepDuration =>
4 | new Promise(resolve => setTimeout(resolve, sleepDuration))
5 |
6 | /* prettier-ignore */
7 |
8 | Bench
9 | .init().dir(__dirname).filename('asyncs4.json').setup()
10 | .name('sleepy4')
11 | .tags('fresh,MORETAGS,uniqd,4th')
12 |
13 | // can also use .add, and then .runAsync()
14 | .addAsync('sleep1', async done => {
15 | await sleep(100)
16 | done()
17 | })
18 | .addAsync('sleep2', async done => {
19 | await sleep(200)
20 | done()
21 | })
22 | .addAsync('sleep3', async done => {
23 | await sleep(300)
24 | done()
25 | })
26 | .addAsync('sleep200 4', async done => {
27 | await sleep(200)
28 | done()
29 | })
30 | .run()
31 |
--------------------------------------------------------------------------------
/example/basic.js:
--------------------------------------------------------------------------------
1 | const Bench = require('../src')
2 |
3 | Bench
4 | // location to store benchmarks
5 | .init(__dirname, 'basic.json')
6 | // tag current benchmarks with, to mark what changes caused differences
7 | .tags('v1')
8 | // actual benchmarks
9 | .add('1 * 1', () => 1 * 1)
10 | .add('1 + 1', () => 1 + 1)
11 | .run()
12 |
13 | // require('fliplog').quick(b)
14 |
--------------------------------------------------------------------------------
/example/bench.js:
--------------------------------------------------------------------------------
1 | const {resolve} = require('path')
2 | const Bench = require('../')
3 | // const Bench = require('bench-chain')
4 |
5 | const {record, suite} = Bench.suite(__dirname, true)
6 |
7 | /* prettier-ignore */
8 |
9 | suite
10 | .add('1 * 1', () => 1 * 1)
11 | .add('1 + 1', () => 1 + 1)
12 | .run()
13 |
14 | // true auto calls the following functions:
15 | record.setup(true)
16 |
17 | // suite.on('complete', () => {
18 | // record.echoFastest().save().echoAvgs().echoTrend()
19 | // })
20 |
--------------------------------------------------------------------------------
/example/configstore.js:
--------------------------------------------------------------------------------
1 | const Bench = require('../src')
2 |
3 | Bench
4 | // location to store benchmarks
5 | // .init(__dirname, 'configstore.basic.json')
6 | .init(__dirname, 'basic-configstore.json')
7 | .name('configstore-basic')
8 | // tag current benchmarks with, to mark what changes caused differences
9 | .tags('v0.4.1,configstore')
10 | // actual benchmarks
11 | .add('1 * 1', () => 1 * 1)
12 | .add('1 + 1', () => 1 + 1)
13 | .run()
14 |
--------------------------------------------------------------------------------
/example/math/async-first-faster.js:
--------------------------------------------------------------------------------
1 | const Bench = require('../../')
2 |
3 | const sleep = sleepDuration =>
4 | new Promise(resolve => setTimeout(resolve, sleepDuration))
5 |
6 | /* prettier-ignore */
7 |
8 | Bench
9 | .init().dir(__dirname).filename('async-first-faster.json').setup()
10 | .addAsync('zoomzoom', async done => {
11 | await sleep(10)
12 | done()
13 | })
14 | .addAsync('snail', async done => {
15 | await sleep(200)
16 | done()
17 | })
18 | .run()
19 |
--------------------------------------------------------------------------------
/example/math/async-first-slower.js:
--------------------------------------------------------------------------------
1 | const Bench = require('../../')
2 |
3 | const sleep = sleepDuration =>
4 | new Promise(resolve => setTimeout(resolve, sleepDuration))
5 |
6 | /* prettier-ignore */
7 |
8 | Bench
9 | .init().dir(__dirname).filename('async-first-faster2.json').setup()
10 | .addAsync('snail2', async done => {
11 | await sleep(200)
12 | done()
13 | })
14 | .addAsync('zoomzoom2', async done => {
15 | await sleep(10)
16 | done()
17 | })
18 | .run()
19 |
--------------------------------------------------------------------------------
/example/math/sync-first-faster.js:
--------------------------------------------------------------------------------
1 | const Bench = require('../../src')
2 | const sleepfor = require('sleepfor')
3 |
4 | Bench
5 | .init(__dirname, 'sync-first-faster.json')
6 | .add('zoomzoom', () => sleepfor(100))
7 | .add('snail', () => sleepfor(500))
8 | .run()
9 |
--------------------------------------------------------------------------------
/example/math/sync-first-slower.js:
--------------------------------------------------------------------------------
1 | const Bench = require('../../src')
2 | const sleepfor = require('sleepfor')
3 |
4 | Bench
5 | .init(__dirname, 'sync-first-slower.json')
6 | .add('snail2', () => sleepfor(500))
7 | .add('zoomzoom2', () => sleepfor(100))
8 | .run()
9 |
--------------------------------------------------------------------------------
/example/results.json:
--------------------------------------------------------------------------------
1 | {
2 | "1 * 1": [
3 | {
4 | "name": "1 * 1",
5 | "num": 79094295,
6 | "sampled": "73",
7 | "variation": "3.99",
8 | "now": 1493067493121,
9 | "mem": {
10 | "rss": 82219008,
11 | "heapTotal": 57655296,
12 | "heapUsed": 41964928,
13 | "external": 9284
14 | }
15 | },
16 | {
17 | "name": "1 * 1",
18 | "num": 91186901,
19 | "sampled": "85",
20 | "variation": "1.59",
21 | "now": 1493067591310,
22 | "mem": {
23 | "rss": 85262336,
24 | "heapTotal": 61325312,
25 | "heapUsed": 43454624,
26 | "external": 9284
27 | }
28 | },
29 | {
30 | "name": "1 * 1",
31 | "num": 52382049,
32 | "sampled": "63",
33 | "variation": "15.01",
34 | "now": 1493067724276,
35 | "mem": {
36 | "rss": 80969728,
37 | "heapTotal": 55558144,
38 | "heapUsed": 44062424,
39 | "external": 9284
40 | }
41 | },
42 | {
43 | "name": "1 * 1",
44 | "num": 53357073,
45 | "sampled": "56",
46 | "variation": "15.19",
47 | "now": 1493067939578,
48 | "mem": {
49 | "rss": 79732736,
50 | "heapTotal": 55558144,
51 | "heapUsed": 43459944,
52 | "external": 9284
53 | }
54 | },
55 | {
56 | "name": "1 * 1",
57 | "num": 74593115,
58 | "sampled": "80",
59 | "variation": "4.85",
60 | "now": 1493067969786,
61 | "mem": {
62 | "rss": 67575808,
63 | "heapTotal": 52400128,
64 | "heapUsed": 36618672,
65 | "external": 17476
66 | }
67 | },
68 | {
69 | "name": "1 * 1",
70 | "num": 57964191,
71 | "sampled": "64",
72 | "variation": "13.83",
73 | "now": 1493068024073,
74 | "mem": {
75 | "rss": 68554752,
76 | "heapTotal": 52936704,
77 | "heapUsed": 37567840,
78 | "external": 17476
79 | }
80 | },
81 | {
82 | "name": "1 * 1",
83 | "num": 58423933,
84 | "sampled": "70",
85 | "variation": "4.39",
86 | "now": 1493068091798,
87 | "mem": {
88 | "rss": 64589824,
89 | "heapTotal": 41402368,
90 | "heapUsed": 33064328,
91 | "external": 17476
92 | }
93 | },
94 | {
95 | "name": "1 * 1",
96 | "num": 50831388,
97 | "sampled": "61",
98 | "variation": "15.94",
99 | "now": 1493068143464,
100 | "mem": {
101 | "rss": 66433024,
102 | "heapTotal": 51363840,
103 | "heapUsed": 33463776,
104 | "external": 17476
105 | }
106 | },
107 | {
108 | "name": "1 * 1",
109 | "num": 70361357,
110 | "sampled": "77",
111 | "variation": "4.16",
112 | "now": 1493068220291,
113 | "mem": {
114 | "rss": 69046272,
115 | "heapTotal": 52936704,
116 | "heapUsed": 38118088,
117 | "external": 17476
118 | }
119 | },
120 | {
121 | "name": "1 * 1",
122 | "num": 49500886,
123 | "sampled": "60",
124 | "variation": "12.07",
125 | "now": 1493068257622,
126 | "mem": {
127 | "rss": 67936256,
128 | "heapTotal": 51888128,
129 | "heapUsed": 36051824,
130 | "external": 17476
131 | }
132 | },
133 | {
134 | "name": "1 * 1",
135 | "num": 79975791,
136 | "sampled": "81",
137 | "variation": "1.82",
138 | "now": 1493068314314,
139 | "mem": {
140 | "rss": 75505664,
141 | "heapTotal": 56082432,
142 | "heapUsed": 40194640,
143 | "external": 17476
144 | }
145 | },
146 | {
147 | "name": "1 * 1",
148 | "num": 61770987,
149 | "sampled": "66",
150 | "variation": "13.07",
151 | "now": 1493068342530,
152 | "mem": {
153 | "rss": 67264512,
154 | "heapTotal": 51888128,
155 | "heapUsed": 35191864,
156 | "external": 17476
157 | }
158 | },
159 | {
160 | "name": "1 * 1",
161 | "num": 73044801,
162 | "sampled": "77",
163 | "variation": "3.34",
164 | "now": 1493068354900,
165 | "mem": {
166 | "rss": 74080256,
167 | "heapTotal": 54497280,
168 | "heapUsed": 39144592,
169 | "external": 17476
170 | }
171 | },
172 | {
173 | "name": "1 * 1",
174 | "num": 43940364,
175 | "sampled": "57",
176 | "variation": "10.19",
177 | "now": 1493068379182,
178 | "mem": {
179 | "rss": 66449408,
180 | "heapTotal": 51363840,
181 | "heapUsed": 34421864,
182 | "external": 17476
183 | }
184 | },
185 | {
186 | "name": "1 * 1",
187 | "num": 61120489,
188 | "sampled": "67",
189 | "variation": "13.70",
190 | "now": 1493068417856,
191 | "mem": {
192 | "rss": 73781248,
193 | "heapTotal": 53985280,
194 | "heapUsed": 37096560,
195 | "external": 17476
196 | }
197 | },
198 | {
199 | "name": "1 * 1",
200 | "num": 73289987,
201 | "sampled": "78",
202 | "variation": "3.69",
203 | "now": 1493068448748,
204 | "mem": {
205 | "rss": 75587584,
206 | "heapTotal": 56082432,
207 | "heapUsed": 37737376,
208 | "external": 17476
209 | }
210 | },
211 | {
212 | "name": "1 * 1",
213 | "num": 52369948,
214 | "sampled": "65",
215 | "variation": "13.10",
216 | "now": 1493068507562,
217 | "mem": {
218 | "rss": 69550080,
219 | "heapTotal": 52936704,
220 | "heapUsed": 38969664,
221 | "external": 17476
222 | }
223 | },
224 | {
225 | "name": "1 * 1",
226 | "num": 55871468,
227 | "sampled": "62",
228 | "variation": "13.29",
229 | "now": 1493068570470,
230 | "mem": {
231 | "rss": 69984256,
232 | "heapTotal": 53460992,
233 | "heapUsed": 39207864,
234 | "external": 17476
235 | }
236 | },
237 | {
238 | "name": "1 * 1",
239 | "num": 78520221,
240 | "sampled": "82",
241 | "variation": "1.98",
242 | "now": 1493068591710,
243 | "mem": {
244 | "rss": 71430144,
245 | "heapTotal": 53985280,
246 | "heapUsed": 41143832,
247 | "external": 17476
248 | }
249 | },
250 | {
251 | "name": "1 * 1",
252 | "num": 40949505,
253 | "sampled": "58",
254 | "variation": "7.12",
255 | "now": 1493068685252,
256 | "mem": {
257 | "rss": 64327680,
258 | "heapTotal": 40353792,
259 | "heapUsed": 32006976,
260 | "external": 17476
261 | }
262 | },
263 | {
264 | "name": "1 * 1",
265 | "num": 50787392,
266 | "sampled": "61",
267 | "variation": "11.67",
268 | "now": 1493068767334,
269 | "mem": {
270 | "rss": 61997056,
271 | "heapTotal": 38244352,
272 | "heapUsed": 30969384,
273 | "external": 17476
274 | }
275 | },
276 | {
277 | "name": "1 * 1",
278 | "num": 53756610,
279 | "sampled": "56",
280 | "variation": "14.22",
281 | "now": 1493068915439,
282 | "mem": {
283 | "rss": 64872448,
284 | "heapTotal": 41402368,
285 | "heapUsed": 33628392,
286 | "external": 17476
287 | }
288 | },
289 | {
290 | "name": "1 * 1",
291 | "num": 53123132,
292 | "sampled": "56",
293 | "variation": "6.28",
294 | "now": 1493069003587,
295 | "mem": {
296 | "rss": 61743104,
297 | "heapTotal": 38244352,
298 | "heapUsed": 31829896,
299 | "external": 17476
300 | }
301 | },
302 | {
303 | "name": "1 * 1",
304 | "num": 52404639,
305 | "sampled": "60",
306 | "variation": "15.13",
307 | "now": 1493069032016,
308 | "mem": {
309 | "rss": 65892352,
310 | "heapTotal": 50315264,
311 | "heapUsed": 31701368,
312 | "external": 17476
313 | }
314 | },
315 | {
316 | "name": "1 * 1",
317 | "num": 53248751,
318 | "sampled": "59",
319 | "variation": "13.99",
320 | "now": 1493069059515,
321 | "mem": {
322 | "rss": 66351104,
323 | "heapTotal": 50315264,
324 | "heapUsed": 31947616,
325 | "external": 17476
326 | }
327 | },
328 | {
329 | "name": "1 * 1",
330 | "num": 61700280,
331 | "sampled": "76",
332 | "variation": "7.58",
333 | "now": 1493069088390,
334 | "mem": {
335 | "rss": 71606272,
336 | "heapTotal": 53448704,
337 | "heapUsed": 40707656,
338 | "external": 17476
339 | }
340 | },
341 | {
342 | "name": "1 * 1",
343 | "num": 65624344,
344 | "sampled": "79",
345 | "variation": "4.30",
346 | "now": 1493069112803,
347 | "mem": {
348 | "rss": 69386240,
349 | "heapTotal": 52936704,
350 | "heapUsed": 38642248,
351 | "external": 17476
352 | }
353 | },
354 | {
355 | "name": "1 * 1",
356 | "num": 50967660,
357 | "sampled": "67",
358 | "variation": "7.83",
359 | "now": 1493069144235,
360 | "mem": {
361 | "rss": 69914624,
362 | "heapTotal": 52936704,
363 | "heapUsed": 38938656,
364 | "external": 17476
365 | }
366 | },
367 | {
368 | "name": "1 * 1",
369 | "num": 42916885,
370 | "sampled": "55",
371 | "variation": "11.36",
372 | "now": 1493069171680,
373 | "mem": {
374 | "rss": 61714432,
375 | "heapTotal": 38256640,
376 | "heapUsed": 29467568,
377 | "external": 17476
378 | }
379 | },
380 | {
381 | "name": "1 * 1",
382 | "num": 75251902,
383 | "sampled": "82",
384 | "variation": "1.64",
385 | "now": 1493069194338,
386 | "mem": {
387 | "rss": 76365824,
388 | "heapTotal": 57131008,
389 | "heapUsed": 41807544,
390 | "external": 17476
391 | }
392 | },
393 | {
394 | "name": "1 * 1",
395 | "num": 48437372,
396 | "sampled": "59",
397 | "variation": "5.72",
398 | "now": 1493069227577,
399 | "mem": {
400 | "rss": 58163200,
401 | "heapTotal": 34574336,
402 | "heapUsed": 24153776,
403 | "external": 17476
404 | }
405 | },
406 | {
407 | "name": "1 * 1",
408 | "num": 52728026,
409 | "sampled": "63",
410 | "variation": "13.66",
411 | "now": 1493069294498,
412 | "mem": {
413 | "rss": 66928640,
414 | "heapTotal": 51363840,
415 | "heapUsed": 34598360,
416 | "external": 17476
417 | }
418 | },
419 | {
420 | "name": "1 * 1",
421 | "num": 34584743,
422 | "sampled": "65",
423 | "variation": "56.17",
424 | "now": 1493069352772,
425 | "mem": {
426 | "rss": 66973696,
427 | "heapTotal": 51888128,
428 | "heapUsed": 34662968,
429 | "external": 17476
430 | }
431 | }
432 | ],
433 | "1 + 1": [
434 | {
435 | "name": "1 + 1",
436 | "num": 59165867,
437 | "sampled": "72",
438 | "variation": "14.62",
439 | "now": 1493067493121,
440 | "mem": {
441 | "rss": 82219008,
442 | "heapTotal": 57655296,
443 | "heapUsed": 41964928,
444 | "external": 9284
445 | }
446 | },
447 | {
448 | "name": "1 + 1",
449 | "num": 91655924,
450 | "sampled": "87",
451 | "variation": "1.22",
452 | "now": 1493067591310,
453 | "mem": {
454 | "rss": 85262336,
455 | "heapTotal": 61325312,
456 | "heapUsed": 43454624,
457 | "external": 9284
458 | }
459 | },
460 | {
461 | "name": "1 + 1",
462 | "num": 66144838,
463 | "sampled": "73",
464 | "variation": "4.00",
465 | "now": 1493067724276,
466 | "mem": {
467 | "rss": 80969728,
468 | "heapTotal": 55558144,
469 | "heapUsed": 44062424,
470 | "external": 9284
471 | }
472 | },
473 | {
474 | "name": "1 + 1",
475 | "num": 59377437,
476 | "sampled": "73",
477 | "variation": "3.33",
478 | "now": 1493067939578,
479 | "mem": {
480 | "rss": 79732736,
481 | "heapTotal": 55558144,
482 | "heapUsed": 43459944,
483 | "external": 9284
484 | }
485 | },
486 | {
487 | "name": "1 + 1",
488 | "num": 58642794,
489 | "sampled": "60",
490 | "variation": "8.36",
491 | "now": 1493067969786,
492 | "mem": {
493 | "rss": 67575808,
494 | "heapTotal": 52400128,
495 | "heapUsed": 36618672,
496 | "external": 17476
497 | }
498 | },
499 | {
500 | "name": "1 + 1",
501 | "num": 57099483,
502 | "sampled": "74",
503 | "variation": "5.50",
504 | "now": 1493068024073,
505 | "mem": {
506 | "rss": 68554752,
507 | "heapTotal": 52936704,
508 | "heapUsed": 37567840,
509 | "external": 17476
510 | }
511 | },
512 | {
513 | "name": "1 + 1",
514 | "num": 43356850,
515 | "sampled": "57",
516 | "variation": "15.75",
517 | "now": 1493068091798,
518 | "mem": {
519 | "rss": 64589824,
520 | "heapTotal": 41402368,
521 | "heapUsed": 33064328,
522 | "external": 17476
523 | }
524 | },
525 | {
526 | "name": "1 + 1",
527 | "num": 49999569,
528 | "sampled": "77",
529 | "variation": "4.57",
530 | "now": 1493068143464,
531 | "mem": {
532 | "rss": 66433024,
533 | "heapTotal": 51363840,
534 | "heapUsed": 33463776,
535 | "external": 17476
536 | }
537 | },
538 | {
539 | "name": "1 + 1",
540 | "num": 60424351,
541 | "sampled": "73",
542 | "variation": "3.41",
543 | "now": 1493068220291,
544 | "mem": {
545 | "rss": 69046272,
546 | "heapTotal": 52936704,
547 | "heapUsed": 38118088,
548 | "external": 17476
549 | }
550 | },
551 | {
552 | "name": "1 + 1",
553 | "num": 66459294,
554 | "sampled": "78",
555 | "variation": "2.03",
556 | "now": 1493068257622,
557 | "mem": {
558 | "rss": 67936256,
559 | "heapTotal": 51888128,
560 | "heapUsed": 36051824,
561 | "external": 17476
562 | }
563 | },
564 | {
565 | "name": "1 + 1",
566 | "num": 79748994,
567 | "sampled": "82",
568 | "variation": "2.72",
569 | "now": 1493068314314,
570 | "mem": {
571 | "rss": 75505664,
572 | "heapTotal": 56082432,
573 | "heapUsed": 40194640,
574 | "external": 17476
575 | }
576 | },
577 | {
578 | "name": "1 + 1",
579 | "num": 63401871,
580 | "sampled": "76",
581 | "variation": "2.76",
582 | "now": 1493068342530,
583 | "mem": {
584 | "rss": 67264512,
585 | "heapTotal": 51888128,
586 | "heapUsed": 35191864,
587 | "external": 17476
588 | }
589 | },
590 | {
591 | "name": "1 + 1",
592 | "num": 80030701,
593 | "sampled": "81",
594 | "variation": "1.55",
595 | "now": 1493068354900,
596 | "mem": {
597 | "rss": 74080256,
598 | "heapTotal": 54497280,
599 | "heapUsed": 39144592,
600 | "external": 17476
601 | }
602 | },
603 | {
604 | "name": "1 + 1",
605 | "num": 76179932,
606 | "sampled": "79",
607 | "variation": "2.45",
608 | "now": 1493068379182,
609 | "mem": {
610 | "rss": 66449408,
611 | "heapTotal": 51363840,
612 | "heapUsed": 34421864,
613 | "external": 17476
614 | }
615 | },
616 | {
617 | "name": "1 + 1",
618 | "num": 74538076,
619 | "sampled": "77",
620 | "variation": "2.93",
621 | "now": 1493068417856,
622 | "mem": {
623 | "rss": 73781248,
624 | "heapTotal": 53985280,
625 | "heapUsed": 37096560,
626 | "external": 17476
627 | }
628 | },
629 | {
630 | "name": "1 + 1",
631 | "num": 76542551,
632 | "sampled": "82",
633 | "variation": "1.99",
634 | "now": 1493068448748,
635 | "mem": {
636 | "rss": 75587584,
637 | "heapTotal": 56082432,
638 | "heapUsed": 37737376,
639 | "external": 17476
640 | }
641 | },
642 | {
643 | "name": "1 + 1",
644 | "num": 80617372,
645 | "sampled": "81",
646 | "variation": "2.16",
647 | "now": 1493068507562,
648 | "mem": {
649 | "rss": 69550080,
650 | "heapTotal": 52936704,
651 | "heapUsed": 38969664,
652 | "external": 17476
653 | }
654 | },
655 | {
656 | "name": "1 + 1",
657 | "num": 80933812,
658 | "sampled": "82",
659 | "variation": "1.83",
660 | "now": 1493068570470,
661 | "mem": {
662 | "rss": 69984256,
663 | "heapTotal": 53460992,
664 | "heapUsed": 39207864,
665 | "external": 17476
666 | }
667 | },
668 | {
669 | "name": "1 + 1",
670 | "num": 67793190,
671 | "sampled": "71",
672 | "variation": "4.09",
673 | "now": 1493068591710,
674 | "mem": {
675 | "rss": 71430144,
676 | "heapTotal": 53985280,
677 | "heapUsed": 41143832,
678 | "external": 17476
679 | }
680 | },
681 | {
682 | "name": "1 + 1",
683 | "num": 59353877,
684 | "sampled": "71",
685 | "variation": "3.20",
686 | "now": 1493068685252,
687 | "mem": {
688 | "rss": 64327680,
689 | "heapTotal": 40353792,
690 | "heapUsed": 32006976,
691 | "external": 17476
692 | }
693 | },
694 | {
695 | "name": "1 + 1",
696 | "num": 53019368,
697 | "sampled": "59",
698 | "variation": "8.39",
699 | "now": 1493068767334,
700 | "mem": {
701 | "rss": 61997056,
702 | "heapTotal": 38244352,
703 | "heapUsed": 30969384,
704 | "external": 17476
705 | }
706 | },
707 | {
708 | "name": "1 + 1",
709 | "num": 58823364,
710 | "sampled": "70",
711 | "variation": "5.00",
712 | "now": 1493068915439,
713 | "mem": {
714 | "rss": 64872448,
715 | "heapTotal": 41402368,
716 | "heapUsed": 33628392,
717 | "external": 17476
718 | }
719 | },
720 | {
721 | "name": "1 + 1",
722 | "num": 44496870,
723 | "sampled": "60",
724 | "variation": "12.00",
725 | "now": 1493069003587,
726 | "mem": {
727 | "rss": 61743104,
728 | "heapTotal": 38244352,
729 | "heapUsed": 31829896,
730 | "external": 17476
731 | }
732 | },
733 | {
734 | "name": "1 + 1",
735 | "num": 49677405,
736 | "sampled": "74",
737 | "variation": "3.67",
738 | "now": 1493069032016,
739 | "mem": {
740 | "rss": 65892352,
741 | "heapTotal": 50315264,
742 | "heapUsed": 31701368,
743 | "external": 17476
744 | }
745 | },
746 | {
747 | "name": "1 + 1",
748 | "num": 60804639,
749 | "sampled": "72",
750 | "variation": "3.66",
751 | "now": 1493069059515,
752 | "mem": {
753 | "rss": 66351104,
754 | "heapTotal": 50315264,
755 | "heapUsed": 31947616,
756 | "external": 17476
757 | }
758 | },
759 | {
760 | "name": "1 + 1",
761 | "num": 73333313,
762 | "sampled": "80",
763 | "variation": "2.12",
764 | "now": 1493069088390,
765 | "mem": {
766 | "rss": 71606272,
767 | "heapTotal": 53448704,
768 | "heapUsed": 40707656,
769 | "external": 17476
770 | }
771 | },
772 | {
773 | "name": "1 + 1",
774 | "num": 59740976,
775 | "sampled": "69",
776 | "variation": "13.41",
777 | "now": 1493069112803,
778 | "mem": {
779 | "rss": 69386240,
780 | "heapTotal": 52936704,
781 | "heapUsed": 38642248,
782 | "external": 17476
783 | }
784 | },
785 | {
786 | "name": "1 + 1",
787 | "num": 73999858,
788 | "sampled": "80",
789 | "variation": "1.56",
790 | "now": 1493069144235,
791 | "mem": {
792 | "rss": 69914624,
793 | "heapTotal": 52936704,
794 | "heapUsed": 38938656,
795 | "external": 17476
796 | }
797 | },
798 | {
799 | "name": "1 + 1",
800 | "num": 53260986,
801 | "sampled": "62",
802 | "variation": "11.20",
803 | "now": 1493069171680,
804 | "mem": {
805 | "rss": 61714432,
806 | "heapTotal": 38256640,
807 | "heapUsed": 29467568,
808 | "external": 17476
809 | }
810 | },
811 | {
812 | "name": "1 + 1",
813 | "num": 74950906,
814 | "sampled": "82",
815 | "variation": "1.95",
816 | "now": 1493069194338,
817 | "mem": {
818 | "rss": 76365824,
819 | "heapTotal": 57131008,
820 | "heapUsed": 41807544,
821 | "external": 17476
822 | }
823 | },
824 | {
825 | "name": "1 + 1",
826 | "num": 20145377,
827 | "sampled": "37",
828 | "variation": "14.99",
829 | "now": 1493069227577,
830 | "mem": {
831 | "rss": 58163200,
832 | "heapTotal": 34574336,
833 | "heapUsed": 24153776,
834 | "external": 17476
835 | }
836 | },
837 | {
838 | "name": "1 + 1",
839 | "num": 62084055,
840 | "sampled": "71",
841 | "variation": "6.46",
842 | "now": 1493069294498,
843 | "mem": {
844 | "rss": 66928640,
845 | "heapTotal": 51363840,
846 | "heapUsed": 34598360,
847 | "external": 17476
848 | }
849 | },
850 | {
851 | "name": "1 + 1",
852 | "num": 60876140,
853 | "sampled": "70",
854 | "variation": "3.56",
855 | "now": 1493069352772,
856 | "mem": {
857 | "rss": 66973696,
858 | "heapTotal": 51888128,
859 | "heapUsed": 34662968,
860 | "external": 17476
861 | }
862 | }
863 | ]
864 | }
--------------------------------------------------------------------------------
/example/single.js:
--------------------------------------------------------------------------------
1 | const Bench = require('../')
2 |
3 | const sleep = sleepDuration =>
4 | new Promise(resolve => setTimeout(resolve, sleepDuration))
5 |
6 | /* prettier-ignore */
7 |
8 | Bench
9 | .init(__dirname, 'single1')
10 | .addAsync('single1', async done => {
11 | await sleep(100)
12 | done()
13 | })
14 | .run()
15 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "version": "0.5.1",
3 | "name": "bench-chain",
4 | "description": "benchmark recording - averages & graphs.",
5 | "main": "src/index",
6 | "scripts": {
7 | "examples": "node example/asyncs && node example/basic && node example/configstore && node example/single",
8 | "test": "ava --verbose",
9 | "docs": "jsdoc"
10 | },
11 | "todoDependencies": {
12 | "fluent-skeleton": "*"
13 | },
14 | "optionalDependencies": {
15 | "microtime": "*"
16 | },
17 | "dependencies": {
18 | "obj-chain-core": "0.0.7",
19 | "obj-chain-plugin-config": "0.0.7",
20 | "benchmark": "2.1.4",
21 | "cli-table2": "^0.2.0",
22 | "flipchain": "*",
23 | "flipfile": "*",
24 | "fliplog": "^0.2.7",
25 | "fliptime": "^3.0.2",
26 | "funwithflags": "*",
27 | "lodash.debounce": "^4.0.8",
28 | "lodash.flatten": "^4.4.0",
29 | "lodash.forown": "^4.4.0",
30 | "script-chain": "*"
31 | },
32 | "devDependencies": {
33 | "ava": "*",
34 | "doxdox": "*",
35 | "fosho": "0.0.12",
36 | "jsdoc": "3.4.3",
37 | "jsdoc-api": "3.0.0",
38 | "jsdoc-babel": "^0.3.0",
39 | "tui-jsdoc-template": "^1.1.0",
40 | "fluent-skeleton": "*"
41 | },
42 | "keywords": [
43 | "benchmark",
44 | "ui",
45 | "average",
46 | "graph",
47 | "time",
48 | "record"
49 | ],
50 | "author": "James ",
51 | "license": "MIT",
52 | "bugs": {
53 | "url": "https://github.com/aretecode/bench-chain"
54 | },
55 | "homepage": "https://github.com/aretecode/bench-chain",
56 | "repository": {
57 | "type": "git",
58 | "url": "git+https://github.com/aretecode/bench-chain.git"
59 | },
60 | "jsdocs": {
61 | "source": {
62 | "include": [
63 | "readme.md",
64 | "src"
65 | ],
66 | "includePattern": ".+\\.js(doc)?$"
67 | },
68 | "opts": {
69 | "recurse": true,
70 | "destination": "./jsdocs",
71 | "template": "node_modules/tui-jsdoc-template",
72 | "package": "package.json"
73 | },
74 | "plugins": [
75 | "node_modules/jsdoc-babel",
76 | "plugins/markdown"
77 | ]
78 | }
79 | }
80 |
--------------------------------------------------------------------------------
/src/BenchChain.js:
--------------------------------------------------------------------------------
1 | /* eslint max-lines: "off" */
2 | /* eslint import/no-dynamic-require: "off" */
3 |
4 | const {Suite} = require('benchmark')
5 | const log = require('fliplog')
6 | const fliptime = require('fliptime')
7 | const Fun = require('funwithflags')
8 | const ChainedMap = require('flipchain/ChainedMapExtendable')
9 | const battery = require('./battery')
10 | const {getCurrentMemory, debounce} = require('./deps')
11 | const Interface = require('./UI')
12 | const Reporter = require('./reports/Report')
13 | const Results = require('./Results')
14 |
15 | const {microtime} = fliptime
16 |
17 | // cli arguments
18 | const argv = Fun(process.argv.slice(2), {
19 | default: {
20 | runTimes: 1,
21 | graph: false,
22 | dry: false,
23 | debug: false,
24 | noGraph: false,
25 | configStore: false,
26 | reasoning: false,
27 | help: false,
28 | },
29 | bool: [
30 | 'graph', 'debug',
31 | 'no-graph',
32 | 'silent',
33 | 'configStore',
34 | 'reasoning',
35 | 'help',
36 | ],
37 | alias: {
38 | noGraph: 'silent',
39 | configStore: ['file', 'config-store'],
40 | reasoning: ['calculations'],
41 | },
42 | camel: true,
43 | unknown(arg, fun) {
44 | if (fun.i === 0) fun.argv.runTimes = Number(arg)
45 | },
46 | })
47 | let {runTimes, graph, dry, debug, noGraph, configStore, reasoning, help} = argv
48 |
49 | if (help) {
50 | const chalk = log.chalk()
51 | log
52 | .underline('bench-chain: --help')
53 | .fmtobj({
54 | '--runTimes': {
55 | type: chalk.blue('Number'),
56 | default: 1,
57 | description: 'run the benchmarks multiple times',
58 | },
59 | '--graph': {
60 | type: chalk.blue('Boolean'),
61 | default: false,
62 | description: 'only show the graph',
63 | },
64 | '--noGraph': {
65 | type: chalk.blue('Boolean'),
66 | default: false,
67 | description: 'do not show the graph',
68 | },
69 | '--dry': {
70 | type: chalk.blue('Boolean'),
71 | default: false,
72 | description: 'do not run the graph',
73 | },
74 | '--debug': {
75 | type: chalk.blue('Boolean'),
76 | default: false,
77 | description: 'verbose debugging information',
78 | },
79 | '--configStore': {
80 | type: chalk.blue('Boolean'),
81 | default: false,
82 | description: 'use configstore instead of the json file in source',
83 | },
84 | '--reasoning': {
85 | type: chalk.blue('Boolean'),
86 | default: false,
87 | description: 'show math calculation reasoning for slower/faster',
88 | },
89 | })
90 | .echo()
91 | .exit()
92 |
93 | process.exit()
94 | }
95 |
96 | /**
97 | * @prop {string} store.dir directory
98 | * @prop {boolean} store.debug very verbose
99 | * @prop {Object} store.memory memory when started
100 | * @prop {number} store.testNames names of tests, useful for length
101 | * @prop {string} store.suiteName benchmarkjs suite name, defaults to filename
102 | * @prop {string} store.rel relative path to results json file
103 | * @prop {string} store.abs absolute path to results json file
104 | * @prop {Object} ui class for helping with spinners etc
105 | * @prop {Object} results class with json contents of file
106 | * @prop {Object} current current event target object
107 | * @prop {Array} timesFor microtime | performance.now times
108 | *
109 | * @TODO memoize subscriber cb check and normal for loop
110 | */
111 | class BenchChain extends ChainedMap {
112 | constructor() {
113 | super()
114 | this.timesFor = {}
115 | this.tag = ''
116 |
117 | /* prettier-ignore */
118 |
119 | this
120 | .extend([
121 | 'dir',
122 | 'debug',
123 | 'testNames',
124 | 'memory',
125 | 'subscribers',
126 | 'configStore',
127 | 'reasoning',
128 | ])
129 | .extendIncrement(['index'])
130 | .debug(debug)
131 | .reasoning(reasoning)
132 | .testNames([])
133 | .memory(getCurrentMemory())
134 | .subscribers({
135 | cycle: [],
136 | complete: [],
137 | allComplete: [],
138 | })
139 | .set('index', 0)
140 |
141 | this.echo = debounce(this.echo.bind(this), 2000)
142 |
143 | /* prettier-enable */
144 | }
145 |
146 | /**
147 | * @param {string} [dir=null] directory for the file with the record
148 | * @param {string} [filename=null] filename for benchmark
149 | * @param {string} [debugOverride=false] debugOverride
150 | * @return {BenchChain} @chainable
151 | */
152 | static init(dir = null, filename = null, debugOverride = false) {
153 | const bench = new BenchChain()
154 |
155 | if (debugOverride !== false) bench.debug(debugOverride)
156 | if (dir !== null) bench.dir(dir)
157 | if (filename !== null) bench.filename(filename)
158 |
159 | // for just use as a factory method
160 | if (!dir && !filename) return bench
161 |
162 | return bench.setup()
163 | }
164 |
165 | /**
166 | * @since 0.3.0
167 | * @param {string} name test name
168 | * @return {BenchChain} @chainable
169 | */
170 | name(name) {
171 | return this.set('suiteName', name)
172 | }
173 |
174 | /**
175 | * @since 0.2.0
176 | * @param {string} tags tag current benchmarks with
177 | * @return {BenchChain} @chainable
178 | */
179 | tags(tags) {
180 | return this.set('tags', tags)
181 | }
182 |
183 | // --- events ---
184 |
185 | /**
186 | * @event setup
187 | * @since 0.4.0
188 | * @param {Function} cb
189 | * @return {BenchChain} @chainable
190 | */
191 | onSetup(cb) {
192 | this.get('suite').on('setup', cb)
193 | return this
194 | }
195 |
196 | /**
197 | * @event cycle
198 | * @since 0.4.0
199 | * @param {Function} cb
200 | * @return {BenchChain} @chainable
201 | */
202 | onCycle(cb) {
203 | this.get('subscribers').cycle.push(cb)
204 | return this
205 | }
206 |
207 | /**
208 | * @event complete
209 | * @since 0.4.0
210 | * @param {Function} cb
211 | * @return {BenchChain} @chainable
212 | */
213 | onComplete(cb) {
214 | this.get('subscribers').complete.push(cb)
215 | return this
216 | }
217 |
218 | /**
219 | * @event allComplete
220 | * @since 0.4.0
221 | * @param {Function} cb
222 | * @return {BenchChain} @chainable
223 | */
224 | onAllComplete(cb) {
225 | this.get('subscribers').allComplete.push(cb)
226 | return this
227 | }
228 |
229 | /**
230 | * @event teardown
231 | * @since 0.4.0
232 | * @param {Function} cb
233 | * @return {BenchChain} @chainable
234 | */
235 | onTeardown(cb) {
236 | this.get('suite').on('teardown', cb)
237 | return this
238 | }
239 |
240 | // --- helpers ---
241 |
242 | /**
243 | * @protected
244 | * @since 0.4.0
245 | * @see BenchChain.testName
246 | * @param {boolean} [latest=false] only use latest data
247 | * @return {Object} results, with test name when available
248 | */
249 | getResults(latest = false) {
250 | return this.results.getForName(this.get('suiteName'), latest)
251 | }
252 |
253 | /**
254 | * @see BenchChain.suite
255 | * @desc filters benchmark results for fastest
256 | * @since 0.1.0
257 | * @return {Array} test case name
258 | */
259 | fastest() {
260 | return this.get('suite').filter('fastest').map('name')
261 | }
262 |
263 | // --- file ---
264 |
265 | /**
266 | * @desc save and load file for the results
267 | * @since 0.2.0
268 | * @param {String} [filename='./results.json']
269 | * @return {BenchChain} @chainable
270 | */
271 | filename(filename = './results.json') {
272 | this.results = Results.init(this, configStore)
273 | .setup(this.get('dir'), filename)
274 | .load()
275 |
276 | return this.setup()
277 | }
278 |
279 | // --- subscribers ---
280 |
281 | /**
282 | * @protected
283 | * @since 0.2.0
284 | * @desc handles benchmark cycle event
285 | * @see BenchChain.results, BenchChain.current
286 | * @param {Benchmark.Event} event
287 | * @return {BenchChain} @chainable
288 | */
289 | _onCycle(event) {
290 | const now = Date.now()
291 | const mem = {
292 | start: this.get('memory'),
293 | end: getCurrentMemory(),
294 | }
295 |
296 | const tags = this.get('tags')
297 | const suite = this.get('suiteName')
298 | const hz = event.target.hz < 100 ? 2 : 0
299 | const num = Number(event.target.hz.toFixed(hz))
300 |
301 | // @example "optimized x 42,951 ops/sec ±3.45% (65 runs sampled)"
302 | const msg = event.target.toString()
303 | const sampled = msg.split('% (').pop().split(' runs').shift()
304 | const variation = msg.split('±').pop().split('%').shift()
305 |
306 | const {target} = event
307 | const {stats, count, cycles, errors, name} = target
308 | const timesFor = this.timesFor[name]
309 |
310 | const result = {
311 | msg,
312 | name,
313 | num,
314 | sampled,
315 | variation,
316 | tags,
317 | suite: [suite],
318 | timesFor,
319 | now,
320 | mem,
321 | stats,
322 | count,
323 | hz: target.hz,
324 | time: stats.mean * 1000,
325 | cycles,
326 | }
327 |
328 | // optimize
329 | if (battery) result.battery = battery
330 | if (errors) result.errors = errors
331 |
332 | this.current = result
333 |
334 | this.results.add(this.get('suiteName'), name, result)
335 |
336 | return this
337 | }
338 |
339 | /**
340 | * @protected
341 | * @desc after all benchmarks
342 | * @since 0.4.0
343 | * @return {BenchChain} @chainable
344 | */
345 | _onAllCompleted() {
346 | const {subscribers, suiteName} = this.entries()
347 | subscribers.allComplete.forEach(cb => cb.call(this, this))
348 |
349 | log.cyan('finished! ' + JSON.stringify(suiteName)).echo(this.get('debug'))
350 |
351 | this.ui.onAllComplete(suiteName)
352 |
353 | this.results.save()
354 | this.echo()
355 |
356 | return this
357 | }
358 |
359 | /**
360 | * @protected
361 | * @since 0.4.0
362 | * @NOTE complete is called at the end of *EACH* bench
363 | * @param {Benchmark.Event} event
364 | * @return {BenchChain} @chainable
365 | */
366 | _onComplete(event) {
367 | const {testNames, index, subscribers} = this.entries()
368 | subscribers.complete.forEach(cb => cb.call(this, this))
369 |
370 | const indexSaysDone = index === testNames.length
371 | const eventSaysDone = event.currentTarget.length === testNames.length
372 |
373 | if (indexSaysDone || eventSaysDone) this._onAllCompleted(event)
374 | else this.index()
375 |
376 | // log.dim('completed ' + testNames[index]).json(event).echo(this.get('debug'))
377 | log
378 | .dim('completed ' + testNames[index])
379 | .data({
380 | current: index,
381 | total: testNames.length,
382 | targetLen: event.currentTarget.length,
383 | })
384 | .echo(this.get('debug'))
385 |
386 | return this
387 | }
388 |
389 | // --- suite ---
390 |
391 | /**
392 | * @see BenchChain.setup
393 | * @param {string} [override=null] defaults to this., or this.paths.abs
394 | * @return {Benchmark.Suite}
395 | */
396 | suite(override = null) {
397 | const suiteName =
398 | override || this.get('suiteName') || this.results.get('abs')
399 |
400 | this.name(suiteName)
401 |
402 | this.set('suite', new Suite(suiteName))
403 |
404 | return this.get('suite')
405 | }
406 |
407 | /**
408 | * @desc subscribes onCycle and onComplete
409 | * @since 0.1.0
410 | * @return {BenchChain} @chainable
411 | */
412 | setup() {
413 | if (!this.has('suite')) this.suite()
414 |
415 | // setup ui
416 | this.ui = new Interface(this)
417 |
418 | // setup file
419 | // @TODO
420 |
421 | // setup name
422 | if (!this.get('suiteName')) {
423 | const rel = this.results
424 | .get('rel')
425 | .replace('json', '')
426 | .replace(/[./]/g, '')
427 |
428 | this.name(rel)
429 | }
430 |
431 | // bind the callbacks
432 | const cycle = this._onCycle.bind(this)
433 | const onComplete = this._onComplete.bind(this)
434 |
435 | // subscribe
436 | this.get('suite').on('cycle', event => cycle(event))
437 | this.get('suite').on('complete', event => onComplete(event))
438 |
439 | return this
440 | }
441 |
442 | // --- operations / bench helpers when not using suite / ---
443 |
444 | /**
445 | * @param {boolean} [asyncs=true]
446 | * @return {BenchChain} @chainable
447 | */
448 | asyncMode(asyncs = true) {
449 | return this.set('asyncMode', asyncs)
450 | }
451 |
452 | /**
453 | * @protected
454 | * @since 0.4.0
455 | * @param {string} name
456 | * @return {BenchChain} @chainable
457 | */
458 | addRecorder(name) {
459 | const results = this.getResults()
460 | const latest = this.getResults(true)
461 |
462 | // use results object, or a new object
463 | if (results !== undefined && results[name] === undefined) results[name] = []
464 | else if (Array.isArray(results[name]) === false) results[name] = []
465 |
466 | // same for latest
467 | if (latest !== undefined && latest[name] === undefined) latest[name] = []
468 | else if (Array.isArray(latest[name]) === false) latest[name] = []
469 |
470 |
471 | this.get('testNames').push(name)
472 |
473 | return this
474 | }
475 |
476 | /**
477 | * @protected
478 | * @since 0.2.0
479 | * @desc should return empty calls to see baseline
480 | * empty bench to get more raw overhead
481 | *
482 | * @see BenchChain.addAsync
483 | * @param {string} name test name
484 | * @param {Function} fn function to call deferred
485 | * @return {BenchChain} @chainable
486 | */
487 | hijackAsync(name, fn) {
488 | return async cb => {
489 | if (!cb.reject) {
490 | cb.reject = e => {
491 | throw e
492 | }
493 | }
494 |
495 | const times = {
496 | start: null,
497 | end: null,
498 | }
499 |
500 | const hjResolve = arg => {
501 | times.end = microtime.now()
502 | times.diff = times.end - times.start
503 | return cb.resolve(arg)
504 | }
505 | const hjReject = arg => {
506 | times.end = microtime.now()
507 | times.diff = times.end - times.start
508 | delete times.end
509 | delete times.start
510 |
511 | return cb.reject(arg)
512 | }
513 |
514 | hjResolve.reject = hjReject
515 | hjResolve.resolve = hjResolve
516 |
517 | this.timesFor[name] = this.timesFor[name] || []
518 | this.timesFor[name].push(times)
519 |
520 | // start timer after setup
521 | times.start = microtime.now()
522 |
523 | const called = await fn(hjResolve, hjReject)
524 | if (called && called.then) {
525 | called.then(arg => cb.resolve(arg))
526 | }
527 | return called
528 | }
529 | }
530 |
531 | /**
532 | * @since 0.2.0
533 | * @desc add benchmark case (with defer)
534 | * @param {string} name
535 | * @param {Function} fn
536 | * @return {BenchChain} @chainable
537 | */
538 | addAsync(name, fn) {
539 | this.set('asyncMode', true)
540 | this.get('suite').add(name, {
541 | defer: true,
542 | fn: this.hijackAsync(name, fn),
543 | })
544 |
545 | return this.addRecorder(name)
546 | }
547 |
548 | /**
549 | * @desc add benchmark case
550 | * @since 0.1.0
551 | * @param {string} name
552 | * @param {Function} fn
553 | * @return {BenchChain} @chainable
554 | */
555 | add(name, fn) {
556 | this.set('asyncMode', false)
557 |
558 | this.get('suite').add(name, fn)
559 |
560 | return this.addRecorder(name)
561 | }
562 |
563 | // --- ops ---
564 |
565 | /**
566 | * @since 0.1.0
567 | * @desc calls setup, runs suite
568 | * @return {BenchChain} @chainable
569 | */
570 | run() {
571 | const {suiteName, asyncMode} = this.entries()
572 |
573 | if (dry) {
574 | log.warn('dry run').echo(this.get('debug'))
575 | return this
576 | }
577 |
578 | if (graph === true) {
579 | return this.echo()
580 | }
581 |
582 | log.cyan('starting! ' + JSON.stringify(suiteName)).echo(this.get('debug'))
583 |
584 | this.ui.onRun(suiteName)
585 | this.get('suite').run({async: asyncMode})
586 |
587 | return this
588 | }
589 |
590 | /**
591 | * @TODO merge with .run, disable logs until end of all
592 | * @desc runs the suite test x times
593 | * @since 0.2.0
594 | * @param {Number} [times=runTimes] defaults to 1, allows first arg to be number of runs
595 | * @return {BenchChain} @chainable
596 | */
597 | runTimes(times = runTimes) {
598 | if (times === null) times = runTimes
599 |
600 | const total = log.colored(times, 'bold')
601 |
602 | for (let i = 0; i < times; i++) {
603 | const current = log.colored(i, 'bold')
604 | const running = log.colored('running ', 'dim')
605 | const msg = `${running} ${current}/${total}`
606 |
607 | log.yellow('reset suite: ').data(msg).echo()
608 |
609 | this.get('suite').reset()
610 | this.get('suite').run({async: this.get('asyncMode')})
611 | }
612 |
613 | return this
614 | }
615 |
616 | /**
617 | * @see this.filename
618 | * @NOTE debounced
619 | * @since 0.2.0
620 | * @desc instantiates Reporter, does echoing of numbers
621 | * @return {BenchChain} @chainable
622 | */
623 | echo() {
624 | if (noGraph) return this
625 |
626 | const reporter = new Reporter(this)
627 | console.log('\n')
628 | reporter.echoFastest()
629 | reporter.echoAvgs()
630 | reporter.echoPercent()
631 | reporter.echoAvgGraph()
632 | reporter.echoTrend()
633 | reporter.echoOps()
634 |
635 | return this
636 | }
637 | }
638 |
639 | module.exports = BenchChain
640 |
--------------------------------------------------------------------------------
/src/Results.js:
--------------------------------------------------------------------------------
1 | /* eslint import/no-dynamic-require: "off" */
2 | const {resolve} = require('path')
3 | const ChainedMap = require('flipchain/ChainedMapExtendable')
4 | const exists = require('flipfile/exists')
5 | const write = require('flipfile/write')
6 | const log = require('fliplog')
7 | const ObjChain = require('obj-chain-core')
8 |
9 | /**
10 | * @TODO: should use obj-chain for the file as well so just a file swap, same api
11 | */
12 | module.exports = class Results extends ChainedMap {
13 |
14 | /**
15 | * @param {BenchChain} parent
16 | * @param {boolean} [configStore=false] use configstore
17 | * @return {Results}
18 | */
19 | static init(parent, configStore = false) {
20 | return new Results(parent, configStore)
21 | }
22 |
23 | /**
24 | * @param {BenchChain} parent
25 | * @param {boolean} [configStore=false] use configstore
26 | */
27 | constructor(parent, configStore = false) {
28 | super(parent)
29 |
30 | if (configStore) {
31 | this.configStore(configStore)
32 | }
33 |
34 | if (parent && parent.has && parent.has('debug')) {
35 | this.debug(parent.get('debug'))
36 | }
37 | else {
38 | this.debug(false)
39 | }
40 | }
41 |
42 | /**
43 | * @desc use configstore via obj-chain (for easy escaping of `dot` syntax)
44 | * @since 0.4.4
45 | * @param {boolean} use
46 | * @return {BenchChain} @chainable
47 | */
48 | configStore(use = true) {
49 | const configStore = new ObjChain({}, ['config']).setup().dot(false)
50 | return this.set('configStore', configStore)
51 | }
52 |
53 | /**
54 | * @since 0.5.0
55 | * @desc add data to record
56 | * @param {string} suiteName bench suite name
57 | * @param {string} name name of test
58 | * @param {Object} result data to record
59 | * @return {Results} @chainable
60 | */
61 | add(suiteName, name, result) {
62 | const data1 = this.getForName(suiteName)
63 | const data2 = this.getForNameLatest(suiteName)
64 |
65 | if (!data1[name]) data1[name] = []
66 | if (!data2[name]) data2[name] = []
67 | data1[name].push(result)
68 | data2[name].push(result)
69 |
70 | return this
71 | }
72 |
73 | /**
74 | * @since 0.5.0
75 | * @desc latest run results only
76 | * @param {string} name suite name
77 | * @return {Object} results
78 | */
79 | getForNameLatest(name) {
80 | if (name !== undefined) {
81 | if (this.latest[name] === undefined) {
82 | this.latest[name] = {}
83 | }
84 | return this.latest[name]
85 | }
86 | return this.latest
87 | }
88 |
89 | /**
90 | * @since 0.4.1
91 | * @desc gets results from file keyed
92 | * @param {string} name suite name
93 | * @param {boolean} [latest=false] latest run results only
94 | * @return {Object} results
95 | */
96 | getForName(name, latest = false) {
97 | if (latest === true) {
98 | return this.getForNameLatest(name)
99 | }
100 |
101 | if (name !== undefined) {
102 | if (this.data[name] === undefined) {
103 | this.data[name] = {}
104 | }
105 | return this.data[name]
106 | }
107 | return this.data
108 | }
109 |
110 | /**
111 | * @desc resolve file, paths to file
112 | * sets abs: absolute path
113 | * sets rel: relative path
114 | * @since 0.4.1
115 | * @param {string} dir
116 | * @param {string} filename
117 | * @return {BenchChain} @chainable
118 | */
119 | setup(dir, filename) {
120 | if (filename && !filename.includes('.json') && !filename.includes('.js')) {
121 | filename = filename + '.json'
122 | }
123 | const rel = filename || './results.json'
124 | const abs = resolve(dir, rel)
125 | return this.set('abs', abs).set('rel', rel)
126 | }
127 |
128 | /**
129 | * @protected
130 | * @desc load from file or configstore (still a file but diff)
131 | * @since 0.2.0
132 | * @see BenchChain.results
133 | * @param {boolean} [force=false] force reload
134 | * @return {BenchChain} @chainable
135 | */
136 | load(force = false) {
137 | if (this.data && force === false) return this
138 |
139 | let {abs, configStore} = this.entries()
140 | this.latest = {}
141 |
142 | if (abs.includes('configstore') && !configStore) {
143 | configStore = this.configStore(true).get('configStore')
144 |
145 | log
146 | .green('results loaded from configstore: ')
147 | .json({'(cmd + click)': log.colored(configStore.path, 'underline')})
148 | .echo()
149 | }
150 |
151 | // use configstore
152 | if (configStore) {
153 | log.underline('using configstore').echo(this.get('debug'))
154 | if (!configStore.has(abs)) {
155 | configStore.set(abs, {})
156 | }
157 | this.data = configStore.get(abs) || {}
158 |
159 | return this
160 | }
161 |
162 | if (exists(abs) === false) write(abs, '{}')
163 |
164 | this.data = require(abs)
165 | log.green('loading').echo(this.get('debug'))
166 |
167 | return this
168 | }
169 |
170 | /**
171 | * @protected
172 | * @since 0.2.0
173 | * @desc saves to file or configstore
174 | * @see BenchChain.load, BenchChain.filename
175 | * @return {BenchChain} @chainable
176 | */
177 | save() {
178 | log.green('saving').echo(this.get('debug'))
179 | const {configStore, abs} = this.entries()
180 |
181 | if (configStore) {
182 | configStore.set(abs, JSON.stringify(this.data))
183 |
184 | log
185 | .green('results saved to: ')
186 | .json({'(cmd + click)': log.colored(configStore.path, 'underline')})
187 | .echo()
188 |
189 | return this
190 | }
191 |
192 | write(abs, JSON.stringify(this.data))
193 |
194 | return this
195 | }
196 | }
197 |
--------------------------------------------------------------------------------
/src/UI.js:
--------------------------------------------------------------------------------
1 | /* eslint lines-around-comment: "off" */
2 |
3 | const log = require('fliplog')
4 | const ChainedMap = require('flipchain/ChainedMapExtendable')
5 | const {Remember} = require('script-chain')
6 |
7 | /**
8 | * @see configstore
9 | * @see script-chain
10 | * @prop {Remember} remember script to remember durations in configstore
11 | */
12 | module.exports = class BenchChainUserInterface extends ChainedMap {
13 | constructor(parent) {
14 | super(parent)
15 | this.remember = new Remember()
16 | }
17 |
18 | /**
19 | * @event onAllComplete
20 | * @param {string} name
21 | * @return {BenchChainUserInterface} @chainable
22 | */
23 | onRun(name) {
24 | this.remember.start(name, true)
25 | this.spinner()
26 | return this
27 | }
28 |
29 | /**
30 | * @event onAllComplete
31 | * @param {string} name
32 | * @return {BenchChainUserInterface} @chainable
33 | */
34 | onAllComplete(name) {
35 | this.remember.finish(name, true)
36 | this.clearSpinners()
37 | return this
38 | }
39 |
40 | /**
41 | * @desc add a pseudo animated
42 | * @since 0.4.1
43 | * @return {BenchChain} @chainable
44 | */
45 | spinner() {
46 | /**
47 | * @see this.spinning
48 | * @desc adds spinner for benchmarking
49 | * @type {Function}
50 | */
51 | this.spin = () => {
52 | log.addSpinner('benchmarking', 'benchmarking')
53 | log.startSpinners([
54 | ' 🏋️',
55 | ' 🏋️',
56 | ' 🏎',
57 | ' 🏎 ',
58 | ' 🏎 ∞',
59 | ' 🏎 ∞ ',
60 | ' 🏎 ∞∞ ',
61 | ' 🏎 ∞∞ ',
62 | ' 🏎 ∞ ',
63 | '🏎 ∞∞ ',
64 | ])
65 | }
66 | this.spin()
67 |
68 | /**
69 | * @see this.spinning
70 | * @desc interval every 10 seconds swaps animated benchmarking for progress
71 | * @type {Function}
72 | */
73 | this.removeSpinner = () => {
74 | try {
75 | log.removeSpinner()
76 | }
77 | catch (e) {
78 | // ignore
79 | }
80 |
81 | // reset and clear terminal
82 | log.clear()
83 | }
84 |
85 | /**
86 | * @see this.spinning
87 | * @desc interval every 10 seconds swaps animated benchmarking for progress
88 | * @type {Function}
89 | */
90 | this.spinning = setInterval(() => {
91 | this.removeSpinner()
92 | this.spinAgain = setTimeout(() => this.spin(), 4000)
93 | }, 10000)
94 |
95 | /**
96 | * @see this.spinning
97 | * @desc function to clear interval and reset back
98 | * @type {Function}
99 | */
100 | this.clearSpinners = () => {
101 | clearInterval(this.spinning)
102 | clearTimeout(this.spinAgain)
103 | this.removeSpinner()
104 | }
105 |
106 | return this
107 | }
108 | }
109 |
--------------------------------------------------------------------------------
/src/battery.js:
--------------------------------------------------------------------------------
1 | const {execFileSync} = require('child_process')
2 |
3 | const cmd = 'ioreg'
4 | const args = ['-n', 'AppleSmartBattery', '-r', '-a']
5 |
6 | function strip(i) {
7 | return i.replace(/[\n\r\t]/gim, '').replace(/<\/?[^>]+(>|$)/g, '')
8 | }
9 |
10 | function plist(str) {
11 | const data = {}
12 | let current = {}
13 | str.split('\n').forEach(item => {
14 | if (item.includes('key') === true) {
15 | current.key = strip(item)
16 | }
17 | else {
18 | current.val = strip(item)
19 | data[current.key] = current.val
20 | current = {}
21 | }
22 | })
23 | return data
24 | }
25 |
26 | function battery() {
27 | if (process.platform !== 'darwin') return 'NA: ' + process.platform
28 | return plist(execFileSync(cmd, args).toString())
29 | }
30 |
31 | const b = battery()
32 |
33 | let Battery = {}
34 | if (process.platform === 'darwin') {
35 | Battery = {
36 | amperage: Number(b.Amperage),
37 | currentCapacity: Number(b.CurrentCapacity),
38 | percent: Math.floor(b.Current / b.Capacity * 100),
39 | charging: b.IsCharging,
40 | temp: Number(b.Temperature),
41 | }
42 | }
43 |
44 | module.exports = Battery
45 |
--------------------------------------------------------------------------------
/src/deps.js:
--------------------------------------------------------------------------------
1 | const os = require('os')
2 |
3 | function uniq(value, index, arr) {
4 | return arr.indexOf(value) === index
5 | }
6 |
7 | /**
8 | * @param {Function[]} funcs functions to flow left to right
9 | * @return {Function} passes args through the functions, bound to this
10 | */
11 | function flow(...funcs) {
12 | const length = funcs ? funcs.length : 0
13 | return function flowing(...args) {
14 | let index = 0
15 | // eslint-disable-next-line
16 | let result = length ? funcs[index].apply(this, args) : args[0]
17 | while (++index < length) {
18 | // eslint-disable-next-line
19 | result = funcs[index].call(this, result)
20 | }
21 | return result
22 | }
23 | }
24 |
25 | /**
26 | * Converts a number to a more readable comma-separated string representation.
27 | *
28 | * @static
29 | * @param {number} number The number to convert.
30 | * @return {string} The more readable string representation.
31 | */
32 | function formatNumber(number) {
33 | number = String(number).split('.')
34 | return (
35 | number[0].replace(/(?=(?:\d{3})+$)(?!\b)/g, ',') +
36 | (number[1] ? '.' + number[1] : '')
37 | )
38 | }
39 |
40 | /**
41 | * @tutorial http://stackoverflow.com/questions/5799055/calculate-percentage-saved-between-two-numbers
42 | * @param {number} value
43 | * @param {number} other
44 | * @return {number}
45 | */
46 | function calcTimes(value, other) {
47 | const diff = other / value
48 |
49 | // require('fliplog').quick({value, other, diff, fixed, end2, end3, end4, end5, end6, fixed2})
50 |
51 | return diff
52 | }
53 |
54 | /**
55 | * @tutorial http://www.randomsnippets.com/2009/07/12/dynamic-or-on-the-fly-percentage-calculations-with-javascript/
56 | * @param {number} oldval
57 | * @param {number} newval
58 | * @return {number}
59 | */
60 | function calcPercent(oldval, newval) {
61 | var percentsavings = ((oldval - newval) / oldval) * 100
62 | return Math.round(percentsavings * 100) / 100
63 | }
64 |
65 | /**
66 | * @NOTE mutates obj
67 | * @param {Function} cb
68 | * @return {Function} to call with callback obj
69 | */
70 | function flowVals(cb) {
71 | /**
72 | * @param {Object} obj
73 | * @return {Object}
74 | */
75 | return function flowCb(obj) {
76 | const keys = Object.keys(obj)
77 | for (let i = 0; i < keys.length; i++) {
78 | const val = obj[keys[i]].map(str => str.length)
79 | obj[keys[i]] = cb(val)
80 | }
81 | return obj
82 | }
83 | }
84 |
85 | /**
86 | * @param {Array} data
87 | * @return {number} average
88 | */
89 | function average(data) {
90 | const sum = data.reduce((prev, curr) => 0 + prev + curr, 0)
91 | return Math.floor(sum / data.length)
92 | }
93 |
94 | function standardDeviation(values) {
95 | const avg = average(values)
96 | const squareDiffs = values.map(value => {
97 | const diff = value - avg
98 | const sqrDiff = diff * diff
99 | return sqrDiff
100 | })
101 | const avgSquareDiff = average(squareDiffs)
102 | const stdDev = Math.sqrt(avgSquareDiff)
103 | return stdDev
104 | }
105 |
106 | /**
107 | * @private
108 | * @desc divide by this number for nicer numbers
109 | * @param {number} max
110 | * @return {number}
111 | */
112 | function getDiv(max) {
113 | switch (true) {
114 | case max > 1000:
115 | return 100
116 | case max > 10000:
117 | return 1000
118 | case max > 100000:
119 | return 10000
120 | case max > 1000000:
121 | return 100000
122 | case max > 10000000:
123 | return 1000000
124 | default:
125 | return 1
126 | }
127 | }
128 |
129 | // const flowmin = flow(Math.floor, Math.min)
130 | // const flowmax = flow(Math.floor, Math.max)
131 | const flowmin = nums => Math.floor(Math.min(...nums))
132 | const flowmax = nums => Math.floor(Math.max(...nums))
133 | // const flowmax = nums => {
134 | // if (!nums) return 0
135 | // return Math.floor(nums.reduce((a, b) => Math.max(a, b)))
136 | // }
137 | // const flowmin = nums => {
138 | // if (!nums) return 0
139 | // return Math.floor(nums.reduce((a, b) => Math.min(a, b)))
140 | // }
141 | //
142 | function getCurrentMemory(init = null) {
143 | return {
144 | process: process.memoryUsage(),
145 | os: os.freemem(),
146 | }
147 | }
148 |
149 | const _flatten = require('lodash.flatten')
150 | const flatten = arr => [].concat.apply(arr)
151 | const forown = require('lodash.forown')
152 |
153 | const mapown = (obj, cb) => {
154 | const mapped = []
155 | forown(obj, (value, key, o) => {
156 | mapped.push(cb(value, key, o))
157 | })
158 | return mapped
159 | }
160 |
161 | const mapObjArr = (obj, cb) => {
162 | const mapped = []
163 | forown(obj, (value, key, o) => {
164 | mapped.push(mapown(value, cb))
165 | // mapped.push(cb(value, key, o))
166 | })
167 | return _flatten(mapped)
168 | }
169 |
170 | function groupBy(arr, property) {
171 | return arr.reduce((memo, x) => {
172 | if (!memo[x[property]]) {
173 | memo[x[property]] = []
174 | }
175 | memo[x[property]].push(x)
176 | return memo
177 | }, {})
178 | }
179 |
180 | // https://github.com/netcode/node-prettydate/blob/master/index.js
181 | function createHandler(divisor, noun, restOfString) {
182 | return function(diff) {
183 | var n = Math.floor(diff / divisor)
184 | var pluralizedNoun = noun + (n > 1 ? 's' : '')
185 | return '' + n + ' ' + pluralizedNoun + ' ' + restOfString
186 | }
187 | }
188 |
189 | var formatters = [
190 | {threshold: -31535999, handler: createHandler(-31536000, 'year', 'from now')},
191 | {threshold: -2591999, handler: createHandler(-2592000, 'month', 'from now')},
192 | {threshold: -604799, handler: createHandler(-604800, 'week', 'from now')},
193 | {threshold: -172799, handler: createHandler(-86400, 'day', 'from now')},
194 | {
195 | threshold: -86399,
196 | handler() {
197 | return 'tomorrow'
198 | },
199 | },
200 | {threshold: -3599, handler: createHandler(-3600, 'hour', 'from now')},
201 | {threshold: -59, handler: createHandler(-60, 'minute', 'from now')},
202 | {threshold: -0.9999, handler: createHandler(-1, 'second', 'from now')},
203 | {
204 | threshold: 1,
205 | handler() {
206 | return 'just now'
207 | },
208 | },
209 | {threshold: 60, handler: createHandler(1, 'second', 'ago')},
210 | {threshold: 3600, handler: createHandler(60, 'minute', 'ago')},
211 | {threshold: 86400, handler: createHandler(3600, 'hour', 'ago')},
212 | {
213 | threshold: 172800,
214 | handler() {
215 | return 'yesterday'
216 | },
217 | },
218 | {threshold: 604800, handler: createHandler(86400, 'day', 'ago')},
219 | {threshold: 2592000, handler: createHandler(604800, 'week', 'ago')},
220 | {threshold: 31536000, handler: createHandler(2592000, 'month', 'ago')},
221 | {threshold: Infinity, handler: createHandler(31536000, 'year', 'ago')},
222 | ]
223 |
224 | function prettydate(date) {
225 | var diff = (new Date().getTime() - date.getTime()) / 1000
226 | for (var i = 0; i < formatters.length; i++) {
227 | if (diff < formatters[i].threshold) {
228 | return formatters[i].handler(diff)
229 | }
230 | }
231 | throw new Error('exhausted all formatter options, none found') // should never be reached
232 | }
233 |
234 | const debounce = require('lodash.debounce')
235 |
236 | // https://github.com/chalk/ansi-regex/blob/master/index.js
237 | const ansiRegex = /[\u001b\u009b][[()#;?]*(?:[0-9]{1,4}(?:;[0-9]{0,4})*)?[0-9A-PRZcf-nqry=><]/g
238 | const replaceAnsi = str => str.replace(ansiRegex, '')
239 |
240 | // https://github.com/uxitten/polyfill/blob/master/string.polyfill.js
241 | // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/repeat
242 | if (!String.prototype.padEnd) {
243 | String.prototype.padEnd = function padEnd(targetLength, padString) {
244 | targetLength = targetLength >> 0 // floor if number or convert non-number to 0;
245 | padString = String(padString || ' ')
246 | if (this.length > targetLength) {
247 | return String(this)
248 | }
249 | else {
250 | targetLength = targetLength - this.length
251 | if (targetLength > padString.length) {
252 | padString += padString.repeat(targetLength / padString.length) // append to original to ensure we are longer than needed
253 | }
254 | return String(this) + padString.slice(0, targetLength)
255 | }
256 | }
257 | }
258 |
259 | module.exports = {
260 | replaceAnsi,
261 | uniq,
262 | flow,
263 | calcTimes,
264 | calcPercent,
265 | flowVals,
266 | average,
267 | getDiv,
268 | standardDeviation,
269 | flowmin,
270 | flowmax,
271 | getCurrentMemory,
272 | flatten,
273 | forown,
274 | mapown,
275 | mapObjArr,
276 | _flatten,
277 | groupBy,
278 | prettydate,
279 | debounce,
280 | }
281 |
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | const log = require('fliplog')
2 | const fliptime = require('fliptime')
3 | const pkg = require('../package.json')
4 | const BenchChain = require('./BenchChain')
5 |
6 | log.registerCatch()
7 |
8 | // @TODO every time a bench is added, should register all here, for multi benches
9 | // const suites = {}
10 |
11 | BenchChain.Bench = BenchChain
12 | BenchChain.timer = fliptime
13 | BenchChain.log = log
14 | BenchChain.version = pkg.version
15 | module.exports = BenchChain
16 |
--------------------------------------------------------------------------------
/src/reports/GraphReporter.js:
--------------------------------------------------------------------------------
1 | const log = require('fliplog')
2 | const {tillNow} = require('fliptime')
3 | const ChainedMap = require('flipchain/ChainedMapExtendable')
4 | let {average, getDiv, flowmin, flowmax, flatten} = require('../deps')
5 |
6 | module.exports = class GraphReporter {
7 | constructor(parent) {
8 | this.parent = parent
9 | this.debug = parent.debug
10 | this.loopResults = parent.loopResults.bind(parent)
11 | this.filterIfNeeded = parent.filterIfNeeded.bind(parent)
12 | this.avgs = parent.avgs.bind(parent)
13 | // this.trend = parent.trend.bind(parent)
14 | }
15 |
16 | /**
17 | * @TODO: abstract this
18 | * @see deps/getDiv
19 | *
20 | * @desc go through results,
21 | * get max and min,
22 | * pretty print numbers
23 | *
24 | * @return {Object} trend graph data
25 | */
26 | trend() {
27 | const trend = {}
28 |
29 | this.loopResults((value, name) => {
30 | let timesFor
31 | let nums
32 |
33 | // skip for now
34 | if (!value || !value[0]) {
35 | log.yellow(name + ' had no value - yet').echo(this.debug)
36 | return
37 | }
38 |
39 | if (value[0].timesFor) {
40 | // remap
41 | timesFor = value
42 | .filter(v => v.timesFor)
43 | .map(entry => entry.timesFor.map(t => t.diff))
44 |
45 | // flatten
46 | let nums1 = flatten(timesFor)
47 |
48 | // average
49 | nums = nums1.map(numnum => average(numnum))
50 | }
51 | else {
52 | // log.quick('good')
53 | nums = value.map(entry => entry.num)
54 | }
55 |
56 | // min max
57 | let min = flowmin(nums)
58 | let max = flowmax(nums)
59 | const div = getDiv(max)
60 | if (this.max < max) this.max = max
61 | if (this.min < min) this.min = min
62 |
63 | // filter anomolies
64 | nums = this.filterIfNeeded({nums, min, max, div})
65 |
66 | log.json({max, min, div}).text('trendy').echo(this.debug)
67 |
68 | max = max / div
69 | min = min / div
70 |
71 | // into graph points
72 | const points = nums
73 | .map((r, i) => {
74 | if (Math.floor(r / (div || 1)) === 0) return 0
75 | return [i, Math.floor(r / (div || 1))]
76 | })
77 | .filter(r => r !== 0)
78 |
79 | // into graph points from date
80 | const datePoints = nums
81 | .map((r, i) => {
82 | let key = i
83 |
84 | const {ms, s, m, h, d, y} = tillNow(value[key].now)
85 | key = i
86 |
87 | if (m === 0) return 0
88 | return [key, m]
89 | })
90 | .filter(r => r !== 0)
91 |
92 | trend[name] = {points, datePoints, max, min}
93 | })
94 |
95 | // log.cyan('all trend data').verbose(100).data(trend).echo(this.debug)
96 |
97 | return trend
98 | }
99 |
100 | /**
101 | * @see Record.trend
102 | * @return {Record} @chainable
103 | */
104 | echoTrend() {
105 | const graphs = this.trend()
106 |
107 | Object.keys(graphs).forEach(name => {
108 | console.log('\n')
109 | const {points, datePoints, max, min} = graphs[name]
110 |
111 | log
112 | .magenta('verbose graph:')
113 | .verbose(100)
114 | .data(graphs[name])
115 | .echo(this.debug)
116 |
117 | log.data({points, datePoints, max, min}).echo(this.debug)
118 |
119 | log
120 | .barStyles({
121 | color: 'green',
122 | width: 150,
123 | height: 10,
124 | maxY: max,
125 | yFractions: 0,
126 | caption: name,
127 | })
128 | .bar(points)
129 | .echo(this.shouldEcho)
130 |
131 | log
132 | .barStyles({
133 | color: 'yellow',
134 | width: 150,
135 | height: 10,
136 | yFractions: 0,
137 | caption: name + ' over time' + log.colored(' (minutes):', 'dim'),
138 | })
139 | .bar(datePoints)
140 | .echo(false)
141 | // .echo(this.shouldEcho)
142 | })
143 |
144 | return this
145 | }
146 |
147 | /**
148 | * @since 0.0.2
149 | * @see Record.avgs
150 | * @TODO transform data to trim
151 | * @return {Record} @chainable
152 | */
153 | echoAvgGraph() {
154 | const avgs = this.avgs()
155 | const nums = Object.keys(avgs).map(name => Number(avgs[name]))
156 | const max = flowmax(nums)
157 | const min = flowmin(nums)
158 | const div = getDiv(max) // * 10
159 |
160 | if (this.max < max) this.max = max
161 | if (this.min < min) this.min = min
162 |
163 | // log.data({avgs, nums, max, min, div}).echo(thisdebug)
164 |
165 | const points = Object.keys(avgs).map((name, i) => {
166 | return [i, Math.floor(avgs[name] / div)]
167 | })
168 |
169 | // , {max, min, nums, points}
170 | log.blue('averages of: ').data(Object.keys(avgs)).echo(this.debug)
171 |
172 | log
173 | .barStyles({
174 | color: 'blue',
175 | maxY: Math.floor(max / div),
176 | minY: Math.floor(min / div),
177 | // width: 150,
178 | // height: 100,
179 | // yFractions: 0,
180 | // xFractions: 0,
181 | caption: 'averages of all:',
182 | })
183 | .bar(points)
184 | .echo(false)
185 | // .echo(this.shouldEcho)
186 |
187 | return this
188 | }
189 | }
190 |
--------------------------------------------------------------------------------
/src/reports/PercentReporter.js:
--------------------------------------------------------------------------------
1 | // %%% %%%%%%%%%%%%%%% %%%
2 | // %%% percent calcs %%%
3 | // %%% %%%%%%%%%%%%%%% %%%
4 |
5 | const log = require('fliplog')
6 | const Table = require('cli-table2')
7 | let {
8 | padEnd,
9 | calcTimes,
10 | flowVals,
11 | calcPercent,
12 | flowmax,
13 | forown,
14 | replaceAnsi,
15 | } = require('../deps')
16 |
17 | /**
18 | * @see Reporter.avgs
19 | * @see Reporter.asyncMode
20 | */
21 | module.exports = class PercentReporter {
22 | constructor(parent) {
23 | this.reasoning = parent.reasoning
24 | this.asyncMode = parent.asyncMode
25 | this.suiteName = parent.parent.get('suiteName')
26 | this.avgs = parent.avgs.bind(parent)
27 | }
28 |
29 | /**
30 | * @protected
31 | * @see [x] https://github.com/aretecode/bench-chain/issues/2
32 | *
33 | * @desc compares two numbers,
34 | * calculates times & percent,
35 | * adjusts wording based on results
36 | * calculate the positive and negatives * / x / times more/less
37 | *
38 | * @NOTE when in not-async mode,
39 | * it was using cycle ops instead of microtime diffs
40 | * but to simplify now both use the same
41 | * also because the hz in ops can be below 0
42 | * throwing off the calculations
43 | *
44 | * @since 0.4.1
45 | * @param {number} value
46 | * @param {number} other
47 | * @return {Object} {end, fixed, percent, word}
48 | */
49 | calculatePercent(value, other) {
50 | const higherIsBetter = !this.asyncMode
51 |
52 | const data = {
53 | higherIsBetter,
54 | firstIsMore: value > other,
55 | negative: false,
56 | xy: calcTimes(value, other),
57 | yx: calcTimes(other, value),
58 | xyf: Math.floor(calcTimes(value, other)),
59 | yxf: Math.floor(calcTimes(other, value)),
60 | yxpercent: Math.floor(calcPercent(other, value)),
61 | xypercent: Math.floor(calcPercent(value, other)),
62 | }
63 |
64 | // default usually
65 | const shouldUseTimes = () => {
66 | return data.xyf !== 0 && data.yxf === 0
67 | }
68 | // if higher is better, if the calculates are revered, fix
69 | const shouldUseTimesFlipped = () => {
70 | return data.xyf === 0 && data.yxf !== 0 // && higherIsBetter
71 | }
72 | // usually used if below 0 && higher is not better
73 | const shouldUsePercent = () => {
74 | return data.xyf === 1 || data.yxf === 1
75 | }
76 | // check the calculations to return formatted string
77 | const format = () => {
78 | if (shouldUseTimes()) {
79 | return data.xyf + 'X'
80 | }
81 | if (shouldUseTimesFlipped()) {
82 | return data.yxf + 'X'
83 | }
84 | if (shouldUsePercent()) {
85 | return data.yxpercent + '%'
86 | }
87 |
88 | return data.yxf + 'X'
89 | }
90 | const slowerOrFaster = () => {
91 | // all nots
92 | if (!higherIsBetter && !data.firstIsMore && !data.negative) {
93 | return 'faster'
94 | }
95 | if (higherIsBetter && data.firstIsMore && !data.negative) {
96 | return 'faster'
97 | }
98 |
99 | return 'slower'
100 | }
101 |
102 | data.calculated = {
103 | msg: format() + ' ' + slowerOrFaster(),
104 | slowerOrFaster: slowerOrFaster(),
105 | shouldUseTimes: shouldUseTimes(),
106 | shouldUseTimesFlipped: shouldUseTimesFlipped(),
107 | shouldUsePercent: shouldUsePercent(),
108 | formatted: format(),
109 | }
110 |
111 | log.bold('======\n').fmtobj(data).echo(this.reasoning)
112 |
113 | const word = slowerOrFaster()
114 | const end = format()
115 |
116 | return {end, word}
117 | }
118 |
119 | /**
120 | * @TODO needs cleaning
121 | *
122 | * @since 0.3.0
123 | * @desc
124 | * uses microtime recordings & benchmarkjs data
125 | * to go through an average of averages
126 | * then compare each result to each other to show how many times
127 | * faster/slower they are
128 | *
129 | * @return {Record} @chainable
130 | */
131 | echoPercent() {
132 | const avgs = this.avgs()
133 | const names = Object.keys(avgs)
134 | const values = Object.values(avgs)
135 | const pcts = []
136 | const parts = {
137 | name: [],
138 | val: [],
139 | diff: [],
140 | word: [],
141 | compare: [],
142 | otherVal: [],
143 | }
144 |
145 | // add each part in so we know the lengths of each to padd
146 | const addPart = part => {
147 | parts.name.push(part.name)
148 | parts.val.push(part.val)
149 | parts.word.push(part.word)
150 | parts.diff.push(part.diff)
151 | parts.compare.push(part.compare)
152 | parts.otherVal.push(part.otherVal)
153 | pcts.push(part)
154 | }
155 |
156 | // go through each name
157 | // then go through each other name to compare
158 | names.forEach((name, n) =>
159 | names.forEach((compare, i) => {
160 | if (compare === name) return
161 |
162 | const value = values[n]
163 | const other = avgs[compare]
164 | const {end, word} = this.calculatePercent(value, other)
165 |
166 | // format
167 | let vc = log.colored(value + '', 'green.underline')
168 | let oc = log.colored(other + '', 'green.underline')
169 | let ec = log.colored(end, 'bold')
170 | let wc = log.colored(word, 'italic') + ' than'
171 | let ns = [log.colored(name, 'cyan'), log.colored(compare, 'blue')]
172 |
173 | // wrap strings
174 | vc = `(${vc})`
175 | oc = `(${oc})`
176 |
177 | // put the parts into an array to format padding
178 | addPart({
179 | name: ns[0],
180 | val: vc,
181 | diff: ec,
182 | word: wc,
183 | compare: ns[1],
184 | otherVal: oc,
185 | })
186 | })
187 | )
188 |
189 | return this.echoPaddedAverages(pcts, names, parts).echoAvgTable(pcts, names)
190 | }
191 |
192 | /**
193 | * @protected
194 | * @since 0.4.1
195 | * @param {Array} pcts paddedColoredParts
196 | * @param {Array} names testnames
197 | * @param {Array