UNPKG

bench-chain

Version:

benchmark recording - averages & graphs.

281 lines (235 loc) 6.87 kB
const log = require('fliplog') const ChainedMap = require('flipchain/ChainedMapExtendable') let {average, flowmin, flowmax, flatten, mapown, mapObjArr} = require('../deps') const TagReporter = require('./TagReporter') const PercentReporter = require('./PercentReporter') const GraphReporter = require('./GraphReporter') /** * @TODO * - [ ] deepmerge multi results * - [ ] graph comparisons * @type {Class} */ module.exports = class Report extends ChainedMap { constructor(parent) { super(parent) const {debug, asyncMode, reasoning} = parent.entries() this.fastest = parent.fastest.bind(parent) this.getResults = (latest = false) => this.parent.getResults(latest) this.getNames = () => this.parent.get('testNames') this.getResultsWithNames = () => { return {names: this.getNames(), results: this.getResults()} } // @TODO improve this.reasoning = reasoning this.shouldEcho = true this.shouldFilter = false this.asyncMode = asyncMode this.debug = debug this.max = 0 this.min = 0 this.mean = 0 this.tagReporter = new TagReporter(this) this.percentReporter = new PercentReporter(this) this.graphReporter = new GraphReporter(this) this.echoAvgGraph = this.graphReporter.echoAvgGraph.bind(this.graphReporter) this.echoTrend = this.graphReporter.echoTrend.bind(this.graphReporter) this.echoPercent = () => { this.percentReporter.echoPercent() this.tagReporter.echoTagged() } } // --- transformers (fbp here makes sense) --- /** * @since 0.2.0 * @see BenchChain.asyncMode * @param {string} prop map to this property to average with that data * @return {Averages} averages */ avgs(prop = 'num') { const avgs = {} const {results, names} = this.getResultsWithNames() log.blue('this.results').data({results, names}).echo(this.debug) // results(keys[0]).timesFor if (this.asyncMode) { names.forEach(name => { const value = results[name] // skip for now if (!value || !value[0] || !value[0].timesFor) { log.yellow(name + ' had no value - yet').echo(this.debug) return } // gather const timesForMulti = value .map(entry => { log .data(entry, entry.timesFor) .red('ENTRY ' + entry.name) .echo(this.debug) if (entry.timesFor === undefined) { // log.quick(entry) return false } return entry.timesFor.map(t => t.diff) }) .filter(val => val) // flatten const timesFor = flatten(timesForMulti).pop() const max = flowmax(timesFor) const min = flowmax(timesFor) if (this.max < max) this.max = max if (this.min < min) this.min = min const avgavg = Math.abs(average(timesFor)) // more averages const resultsForProp = value.map(result => avgavg) const avg = average(resultsForProp) log .blue('averages') .data({name, resultsForProp, avg, avgavg, timesFor}) .echo(this.debug) avgs[name] = avg }) } else { return this.propAvg() } return avgs } /** * @desc same as avg, but using a specific prop * @since 0.4.1 * @param {String} [prop='num'] * @return {Array<number>} */ propAvg(prop = 'num') { const avgs = {} const {results, names} = this.getResultsWithNames() names.forEach(name => { const resultsForProp = results[name].map(result => Number(result[prop])) const avg = average(resultsForProp) log.blue('averages').data({name, resultsForProp, avg}).echo(this.debug) avgs[name] = avg }) return avgs } // --- echoing helpers 2 --- /** * @since 0.4.1 * @param {Function<value, key, object>} cb * @return {Array} mapped results */ loopResults(cb) { return mapown(this.getResults(), cb) } /** * @desc filters numbers outside of the usual range if needed * @param {number} nums * @param {number} min * @param {number} max * @return {Array<number>} */ filterIfNeeded({nums, min, max}) { if (!this.shouldFilter) return nums nums = nums.filter(nn => { const minp = min * 1.1 const maxp = max / 1.1 log .data({ nn, minp, maxp, max, min, passes: nn >= minp && nn <= maxp, }) .echo(false) return nn >= minp // && (nn <= maxp) }) return nums } /** * @param {number} avg * @return {string} color */ colorForAvg(avg) { const avgMin = avg > this.min * 1.1 const minmin = avg > this.min * 1.1 const minish = avg > this.min * 1.5 const abvAvg = avg > this.max / 1.3 const avgish = avg > this.max / 1.1 const wow = avg > this.max / 1.05 if (wow) return 'bold' if (avgish) return 'green' if (abvAvg || minish) return 'dim' if (avgMin) return 'yellow' if (minmin) return 'red' return 'red' } // --- simple echos --- /** * @see Record.avgs * @TODO transform data to trim * @return {Record} @chainable */ echoAvgs() { // in async mode, uses microtime diffs as ops are not as reliable let msg = 'lower is better, time taken in microseconds' // when in sync mode, it is ops/second instead if (!this.asyncMode) { msg = 'higher is better, operations per second' } log .color('dim.italic') .text(msg) .echo() log .fmtobj(this.avgs()) .bold('averages:') .echo(this.shouldEcho) return this } /** * @see Record.fastest * @return {Record} @chainable */ echoFastest() { log .verbose(this.fastest().shift()) .underline('Fastest is ') .echo(this.shouldEcho) return this } /** * @since 0.4.1 * @desc very long message echoing * for all cycles of all test echoing, * should filter * @return {Record} @chainable */ echoOps() { this.getResults() const {results, names} = this.getResultsWithNames() const msgs = [] msgs.push('-----') names.forEach(name => { const value = results[name].slice(0).reverse() let limit = 10 for (let i = 0; i < limit && i < value.length; i++) { if (!value[i].msg) { limit++ continue } msgs.push(value[i].msg) } msgs.push('-----') }) log.bold('\n\noperations per second\n').echo() msgs.forEach(msg => console.log(msg)) // works too, but harder to filter // const msgs = mapObjArr(this.getResults(), data => data.msg).reverse() return this } // --- -------------- --- // --- graph building --- // --- -------------- --- }