mirror of
https://github.com/zrwusa/data-structure-typed.git
synced 2024-11-23 12:54:04 +00:00
[benchmark] beautiful report
This commit is contained in:
parent
f0777567f2
commit
9a87fe13b6
|
@ -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",
|
||||
|
|
|
@ -0,0 +1,18 @@
|
|||
import {BinaryTree} from '../../../../src';
|
||||
|
||||
import * as Benchmark from 'benchmark';
|
||||
|
||||
export const suite = new Benchmark.Suite();
|
||||
const bt = new BinaryTree<number>();
|
||||
|
||||
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);
|
||||
}
|
||||
});
|
|
@ -0,0 +1,18 @@
|
|||
import {BST} from '../../../../src';
|
||||
|
||||
import * as Benchmark from 'benchmark';
|
||||
|
||||
export const suite = new Benchmark.Suite();
|
||||
const bt = new BST<number>();
|
||||
|
||||
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);
|
||||
}
|
||||
});
|
|
@ -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 = `<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Title</title>
|
||||
<style>
|
||||
.content table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
margin-top: 10px;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.content table th,
|
||||
.content table td {
|
||||
padding: 8px 12px;
|
||||
text-align: left;
|
||||
border: 1px solid #ddd;
|
||||
}
|
||||
|
||||
.content table th {
|
||||
background-color: #f2f2f2;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.content table tr:nth-child(odd) {
|
||||
background-color: #ffffff;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div id="json-to-html">`;
|
||||
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 += `</div>
|
||||
</body>
|
||||
</html>`;
|
||||
fs.writeFileSync(htmlFilePath, html);
|
||||
console.log('Performance test report file generated');
|
||||
}
|
||||
})
|
||||
.run({async: true});
|
||||
}
|
||||
});
|
121
test/performance/reportor.ts
Normal file
121
test/performance/reportor.ts
Normal file
|
@ -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 = `<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>performance of data-structure-typed</title>
|
||||
<style>
|
||||
.content table {
|
||||
width: 100%;
|
||||
table-layout: fixed;
|
||||
border-collapse: collapse;
|
||||
margin-top: 10px;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.content table th,
|
||||
.content table td {
|
||||
padding: 8px 12px;
|
||||
text-align: left;
|
||||
border: 1px solid #ddd;
|
||||
}
|
||||
|
||||
.content table th {
|
||||
background-color: #f2f2f2;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.content table tr:nth-child(odd) {
|
||||
background-color: #ffffff;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div id="json-to-html">`;
|
||||
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 += `</div>
|
||||
</body>
|
||||
</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});
|
||||
}
|
||||
});
|
2
test/performance/types/index.ts
Normal file
2
test/performance/types/index.ts
Normal file
|
@ -0,0 +1,2 @@
|
|||
export * from './utils';
|
||||
export * from './reportor';
|
3
test/performance/types/reportor.ts
Normal file
3
test/performance/types/reportor.ts
Normal file
|
@ -0,0 +1,3 @@
|
|||
import * as Benchmark from 'benchmark';
|
||||
|
||||
export type PerformanceTest = {testName: string; suite: Benchmark.Suite; file: string};
|
1
test/performance/types/utils/index.ts
Normal file
1
test/performance/types/utils/index.ts
Normal file
|
@ -0,0 +1 @@
|
|||
export * from './json2html';
|
1
test/performance/types/utils/json2html.ts
Normal file
1
test/performance/types/utils/json2html.ts
Normal file
|
@ -0,0 +1 @@
|
|||
export type Json2htmlOptions = {plainHtml?: boolean} & Partial<{[key: string]: any}>;
|
30
test/performance/utils/console.ts
Normal file
30
test/performance/utils/console.ts
Normal file
|
@ -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'
|
||||
};
|
|
@ -1,3 +1,4 @@
|
|||
export * from './json2html';
|
||||
export * from './number';
|
||||
export * from './is';
|
||||
export * from './console';
|
||||
|
|
|
@ -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 `<div class='index'><span class='json-to-html-label'>${keyname} </span></div>`;
|
||||
} else if (typeof keyname === 'string') {
|
||||
function makeLabelDiv(options: any, level: number, keyName: string | number, datatype?: string): string {
|
||||
if (typeof keyName === 'number') {
|
||||
return `<div class='index'><span class='json-to-html-label'>${keyName} </span></div>`;
|
||||
} else if (typeof keyName === 'string') {
|
||||
if (datatype === 'array') {
|
||||
return `<div class='collapsible level${level}' ${toggleJS(
|
||||
options
|
||||
)}><span class='json-to-html-label'>${keyname}</span></div>`;
|
||||
)}><span class='json-to-html-label'>${keyName}</span></div>`;
|
||||
} else if (datatype === 'object') {
|
||||
return `<div class='attribute collapsible level${level}' ${toggleJS(
|
||||
options
|
||||
)}><span class='json-to-html-label'>${keyname}:</span></div>`;
|
||||
)}><span class='json-to-html-label'>${keyName}:</span></div>`;
|
||||
} else {
|
||||
return `<div class='leaf level${level}'><span class='json-to-html-label'>${keyname}:</span></div>`;
|
||||
return `<div class='leaf level${level}'><span class='json-to-html-label'>${keyName}:</span></div>`;
|
||||
}
|
||||
} 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 '<table>' + headingHtml + contentHtml + '</table>';
|
||||
}
|
||||
|
||||
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("</div><div class='altRows'>") +
|
||||
'</div>';
|
||||
}
|
||||
return `<div class="json-to-html-collapse clearfix ${altrow}">
|
||||
return `<div class="json-to-html-collapse clearfix ${altRow}">
|
||||
${title}
|
||||
<div class="${contentClass}">${subs}</div>
|
||||
</div>`;
|
||||
|
@ -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('</div><div>') +
|
||||
'</div>';
|
||||
const inner = `<div class="json-to-html-expand clearfix ${altrow}">
|
||||
const inner = `<div class="json-to-html-expand clearfix ${altRow}">
|
||||
${title}
|
||||
<div class="${contentClass}">${subs}</div>
|
||||
</div>`;
|
||||
|
@ -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 = `<style>
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
export function numberFix(number: number, decimalPlaces: number): string {
|
||||
if (number > 10000 || number < 0.001) {
|
||||
const [mantissa, exponent] = number.toExponential().split('e');
|
||||
export function numberFix(num: number, decimalPlaces: number): string {
|
||||
if (num > 10000 || num < 0.001) {
|
||||
const [mantissa, exponent] = num.toExponential().split('e');
|
||||
const formattedMantissa = Number(mantissa).toFixed(decimalPlaces);
|
||||
return `${formattedMantissa}e${exponent}`;
|
||||
} else {
|
||||
return number.toFixed(decimalPlaces);
|
||||
return num.toFixed(decimalPlaces);
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue