From 9a87fe13b634209b0bbb83211141a73ec1793480 Mon Sep 17 00:00:00 2001 From: Revone Date: Thu, 2 Nov 2023 16:10:41 +0800 Subject: [PATCH] [benchmark] beautiful report --- package.json | 2 +- .../binary-tree/binary-tree.test.ts | 18 +++ .../data-structures/binary-tree/bst.test.ts | 18 +++ test/performance/index.ts | 111 ---------------- test/performance/reportor.ts | 121 ++++++++++++++++++ test/performance/types/index.ts | 2 + test/performance/types/reportor.ts | 3 + test/performance/types/utils/index.ts | 1 + test/performance/types/utils/json2html.ts | 1 + test/performance/utils/console.ts | 30 +++++ test/performance/utils/index.ts | 1 + test/performance/utils/json2html.ts | 33 ++--- test/performance/utils/number.ts | 8 +- 13 files changed, 217 insertions(+), 132 deletions(-) delete mode 100644 test/performance/index.ts create mode 100644 test/performance/reportor.ts create mode 100644 test/performance/types/index.ts create mode 100644 test/performance/types/reportor.ts create mode 100644 test/performance/types/utils/index.ts create mode 100644 test/performance/types/utils/json2html.ts create mode 100644 test/performance/utils/console.ts diff --git a/package.json b/package.json index 759a730..9ef4802 100644 --- a/package.json +++ b/package.json @@ -33,7 +33,7 @@ "install:all-subs": "npm i avl-tree-typed binary-tree-typed bst-typed deque-typed directed-graph-typed doubly-linked-list-typed graph-typed heap-typed linked-list-typed max-heap-typed max-priority-queue-typed min-heap-typed min-priority-queue-typed priority-queue-typed singly-linked-list-typed stack-typed tree-multiset-typed trie-typed undirected-graph-typed queue-typed --save-dev", "test": "jest", "test:integration": "npm run update:subs && jest --config jest.integration.config.js", - "benchmark": "ts-node test/performance/index.ts", + "benchmark": "ts-node test/performance/reportor.ts", "check:deps": "dependency-cruiser src", "changelog": "auto-changelog", "coverage:badge": "istanbul-badges-readme", diff --git a/test/performance/data-structures/binary-tree/binary-tree.test.ts b/test/performance/data-structures/binary-tree/binary-tree.test.ts index e69de29..8d7e0b5 100644 --- a/test/performance/data-structures/binary-tree/binary-tree.test.ts +++ b/test/performance/data-structures/binary-tree/binary-tree.test.ts @@ -0,0 +1,18 @@ +import {BinaryTree} from '../../../../src'; + +import * as Benchmark from 'benchmark'; + +export const suite = new Benchmark.Suite(); +const bt = new BinaryTree(); + +suite + .add('add 1000', () => { + for (let i = 0; i < 1000; i++) { + bt.add(i); + } + }) + .add('add & delete 1000', () => { + for (let i = 0; i < 1000; i++) { + bt.delete(i); + } + }); diff --git a/test/performance/data-structures/binary-tree/bst.test.ts b/test/performance/data-structures/binary-tree/bst.test.ts index e69de29..f16440d 100644 --- a/test/performance/data-structures/binary-tree/bst.test.ts +++ b/test/performance/data-structures/binary-tree/bst.test.ts @@ -0,0 +1,18 @@ +import {BST} from '../../../../src'; + +import * as Benchmark from 'benchmark'; + +export const suite = new Benchmark.Suite(); +const bt = new BST(); + +suite + .add('add 1000', () => { + for (let i = 0; i < 1000; i++) { + bt.add(i); + } + }) + .add('add & delete 1000', () => { + for (let i = 0; i < 1000; i++) { + bt.delete(i); + } + }); diff --git a/test/performance/index.ts b/test/performance/index.ts deleted file mode 100644 index 29f07b5..0000000 --- a/test/performance/index.ts +++ /dev/null @@ -1,111 +0,0 @@ -import * as Benchmark from 'benchmark'; -import * as path from 'path'; -import * as fs from 'fs'; -import * as fastGlob from 'fast-glob'; -import {numberFix, render} from './utils'; - -const reportDistPath = 'benchmark'; -const testDir = path.join(__dirname, 'data-structures'); -const testFiles = fastGlob.sync(path.join(testDir, '**', '*.test.ts')); - -const report: {[key: string]: any} = {}; - -let testFileCount = 0, - suiteCount = 0, - completedCount = 0; -testFiles.forEach((file: string) => { - testFileCount++; - console.log(`testing file ${file}`); - const testName = path.basename(file, '.test.ts'); - const testFunction = require(file); - const {suite} = testFunction; - - if (suite) { - suiteCount++; - suite.on('cycle', (event: any) => { - console.log(event.target.toString()); - }); - - suite - .on('complete', function (this: Benchmark.Suite) { - completedCount++; - console.log('Fastest is ' + this.filter('fastest').map('name')); - report[testName] = this.map((test: Benchmark) => ({ - 'test name': test.name, - 'time consumption (ms)': numberFix(test.times.period * 1000, 2), - 'executions per second': numberFix(test.hz, 2), - 'executed times': numberFix(test.count, 2), - 'sample arithmetic mean (secs)': numberFix(test.stats.mean, 2), - 'sample deviation': numberFix(test.stats.deviation, 2) - })); - - // report[testName] = this; - console.log( - `test file count: ${testFileCount}. suite count: ${suiteCount}. completed suite count: ${completedCount}` - ); - if (completedCount === suiteCount) { - if (!fs.existsSync(reportDistPath)) fs.mkdirSync(reportDistPath, {recursive: true}); - - const filePath = path.join(reportDistPath, 'report.json'); - const htmlFilePath = path.join(reportDistPath, 'report.html'); - fs.writeFileSync(filePath, JSON.stringify(report, null, 2)); - let html = ` - - - - Title - - - -
`; - for (const r in report) { - if (report.hasOwnProperty(r)) { - html += render(report[r], { - '<>': 'table', - html: [ - { - '<>': 'tr', - html: [ - {'<>': 'td', html: '${name}'}, - {'<>': 'td', html: '${periodMS}'}, - {'<>': 'td', html: '${mean}'} - ] - } - ] - }); - } - } - - html += `
- - `; - fs.writeFileSync(htmlFilePath, html); - console.log('Performance test report file generated'); - } - }) - .run({async: true}); - } -}); diff --git a/test/performance/reportor.ts b/test/performance/reportor.ts new file mode 100644 index 0000000..6f0841a --- /dev/null +++ b/test/performance/reportor.ts @@ -0,0 +1,121 @@ +import * as Benchmark from 'benchmark'; +import * as path from 'path'; +import * as fs from 'fs'; +import * as fastGlob from 'fast-glob'; +import {numberFix, render, Color} from './utils'; +import {PerformanceTest} from './types'; + +const reportDistPath = 'benchmark'; +const testDir = path.join(__dirname, 'data-structures'); +const testFiles = fastGlob.sync(path.join(testDir, '**', '*.test.ts')); + +const report: {[key: string]: any} = {}; + +let testFileCount = 0, + completedCount = 0; + +const performanceTests: PerformanceTest[] = []; +const {GREEN, BOLD, END} = Color; + +testFiles.forEach((file: string) => { + testFileCount++; + const testName = path.basename(file, '.test.ts'); + const testFunction = require(file); + const {suite} = testFunction; + if (suite) performanceTests.push({testName, suite, file}); +}); + +const composeReport = () => { + if (!fs.existsSync(reportDistPath)) fs.mkdirSync(reportDistPath, {recursive: true}); + + const filePath = path.join(reportDistPath, 'report.json'); + const htmlFilePath = path.join(reportDistPath, 'report.html'); + fs.writeFileSync(filePath, JSON.stringify(report, null, 2)); + let html = ` + + + + performance of data-structure-typed + + + +
`; + for (const r in report) { + if (report.hasOwnProperty(r)) { + html += render(report[r].testName, report[r].benchmarks, { + plainHtml: true, + '<>': 'table', + html: [ + { + '<>': 'tr', + html: [ + {'<>': 'td', html: '${name}'}, + {'<>': 'td', html: '${periodMS}'}, + {'<>': 'td', html: '${mean}'} + ] + } + ] + }); + } + } + + html += `
+ + `; + fs.writeFileSync(htmlFilePath, html); + console.log(`Performance ${BOLD}${GREEN}report${END} file generated`); +}; + +performanceTests.forEach(item => { + const {suite, testName, file} = item; + console.log(`testing file ${GREEN}${file}${END}`); + + if (suite) { + suite + .on('complete', function (this: Benchmark.Suite) { + completedCount++; + report[testName] = {}; + report[testName].benchmarks = this.map((benchmark: Benchmark) => ({ + 'test name': benchmark.name, + 'time taken (ms)': numberFix(benchmark.times.period * 1000, 2), + 'executions per sec': numberFix(benchmark.hz, 2), + // 'executed times': numberFix(benchmark.count, 2), + 'sample mean (secs)': numberFix(benchmark.stats.mean, 2), + 'sample deviation': numberFix(benchmark.stats.deviation, 2) + })); + report[testName].testName = testName; + + console.log( + `test files: ${GREEN}${testFileCount}${END}. suites: ${GREEN}${performanceTests.length}${END}. completed suites: ${GREEN}${completedCount}${END}` + ); + if (completedCount === performanceTests.length) { + composeReport(); + } + }) + .run({async: false}); + } +}); diff --git a/test/performance/types/index.ts b/test/performance/types/index.ts new file mode 100644 index 0000000..b82af2c --- /dev/null +++ b/test/performance/types/index.ts @@ -0,0 +1,2 @@ +export * from './utils'; +export * from './reportor'; diff --git a/test/performance/types/reportor.ts b/test/performance/types/reportor.ts new file mode 100644 index 0000000..4b65e75 --- /dev/null +++ b/test/performance/types/reportor.ts @@ -0,0 +1,3 @@ +import * as Benchmark from 'benchmark'; + +export type PerformanceTest = {testName: string; suite: Benchmark.Suite; file: string}; diff --git a/test/performance/types/utils/index.ts b/test/performance/types/utils/index.ts new file mode 100644 index 0000000..0f9b8d1 --- /dev/null +++ b/test/performance/types/utils/index.ts @@ -0,0 +1 @@ +export * from './json2html'; diff --git a/test/performance/types/utils/json2html.ts b/test/performance/types/utils/json2html.ts new file mode 100644 index 0000000..dbae264 --- /dev/null +++ b/test/performance/types/utils/json2html.ts @@ -0,0 +1 @@ +export type Json2htmlOptions = {plainHtml?: boolean} & Partial<{[key: string]: any}>; diff --git a/test/performance/utils/console.ts b/test/performance/utils/console.ts new file mode 100644 index 0000000..d0badad --- /dev/null +++ b/test/performance/utils/console.ts @@ -0,0 +1,30 @@ +export const Color = { + END: '\x1b[0m', + BOLD: '\x1b[1m', + DIM: '\x1b[2m', + ITALIC: '\x1b[3m', + UNDERLINE: '\x1b[4m', + INVERSE: '\x1b[7m', + STRIKETHROUGH: '\x1b[9m', + NO_BOLD: '\x1b[22m', + NO_ITALIC: '\x1b[23m', + NO_UNDERLINE: '\x1b[24m', + NO_INVERSE: '\x1b[27m', + NO_STRIKETHROUGH: '\x1b[29m', + BLACK: '\x1b[30m', + RED: '\x1b[31m', + GREEN: '\x1b[32m', + YELLOW: '\x1b[33m', + BLUE: '\x1b[34m', + MAGENTA: '\x1b[35m', + CYAN: '\x1b[36m', + WHITE: '\x1b[37m', + BG_BLACK: '\x1b[40m', + BG_RED: '\x1b[41m', + BG_GREEN: '\x1b[42m', + BG_YELLOW: '\x1b[43m', + BG_BLUE: '\x1b[44m', + BG_MAGENTA: '\x1b[45m', + BG_CYAN: '\x1b[46m', + BG_WHITE: '\x1b[47m' +}; diff --git a/test/performance/utils/index.ts b/test/performance/utils/index.ts index feb65e4..0b96caf 100644 --- a/test/performance/utils/index.ts +++ b/test/performance/utils/index.ts @@ -1,3 +1,4 @@ export * from './json2html'; export * from './number'; export * from './is'; +export * from './console'; diff --git a/test/performance/utils/json2html.ts b/test/performance/utils/json2html.ts index 7648fe2..0c50cd8 100644 --- a/test/performance/utils/json2html.ts +++ b/test/performance/utils/json2html.ts @@ -1,6 +1,7 @@ import * as _ from './is'; +import {Json2htmlOptions} from '../types'; -function toggleJS(options?: {plainHtml?: boolean}): string { +function toggleJS(options?: Json2htmlOptions): string { if (options?.plainHtml) { return ''; } else { @@ -8,28 +9,28 @@ function toggleJS(options?: {plainHtml?: boolean}): string { } } -function makeLabelDiv(options: any, level: number, keyname: string | number, datatype?: string): string { - if (typeof keyname === 'number') { - return `
${keyname} 
`; - } else if (typeof keyname === 'string') { +function makeLabelDiv(options: any, level: number, keyName: string | number, datatype?: string): string { + if (typeof keyName === 'number') { + return `
${keyName} 
`; + } else if (typeof keyName === 'string') { if (datatype === 'array') { return `
${keyname}
`; + )}>${keyName}`; } else if (datatype === 'object') { return `
${keyname}:
`; + )}>${keyName}:`; } else { - return `
${keyname}:
`; + return `
${keyName}:
`; } } else { return ''; } } -function getContentClass(keyname: string | number): string { - if (typeof keyname === 'string') { +function getContentClass(keyName: string | number): string { + if (typeof keyName === 'string') { return 'content'; } else { return ''; @@ -110,10 +111,10 @@ function drawTable(arr: any[]): string { return '' + headingHtml + contentHtml + '
'; } -function _render(name: string, data: any, options: any, level: number, altrow: number): string { +function _render(name: string, data: any, options: Json2htmlOptions, level: number, altRow: number): string { const contentClass = getContentClass(name); if (_.isArray(data)) { - const title = makeLabelDiv(options, level, `${name} (${data.length})`, 'array'); + const title = makeLabelDiv(options, level, `${name}`, 'array'); let subs: string; if (isTable(data)) { subs = drawTable(data); @@ -125,7 +126,7 @@ function _render(name: string, data: any, options: any, level: number, altrow: n .join("
") + '
'; } - return `
+ return `
${title}
${subs}
`; @@ -151,7 +152,7 @@ function _render(name: string, data: any, options: any, level: number, altrow: n .map(([key, val]) => _render(key, val, options, level + 1, count++ % 2)) .join('
') + '
'; - const inner = `
+ const inner = `
${title}
${subs}
`; @@ -161,9 +162,9 @@ function _render(name: string, data: any, options: any, level: number, altrow: n } } -export function render(json: any, options: any): string { +export function render(name: string, json: any, options: Json2htmlOptions): string { // return `${head}${_render('', json, options, 0, 0)}`; - return `${_render('', json, options, 0, 0)}`; + return `${_render(name, json, options, 0, 0)}`; } // const head = `