[benchmark] beautiful report

This commit is contained in:
Revone 2023-11-02 16:10:41 +08:00
parent f0777567f2
commit 9a87fe13b6
13 changed files with 217 additions and 132 deletions

View file

@ -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",

View file

@ -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);
}
});

View file

@ -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);
}
});

View file

@ -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});
}
});

View 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});
}
});

View file

@ -0,0 +1,2 @@
export * from './utils';
export * from './reportor';

View file

@ -0,0 +1,3 @@
import * as Benchmark from 'benchmark';
export type PerformanceTest = {testName: string; suite: Benchmark.Suite; file: string};

View file

@ -0,0 +1 @@
export * from './json2html';

View file

@ -0,0 +1 @@
export type Json2htmlOptions = {plainHtml?: boolean} & Partial<{[key: string]: any}>;

View 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'
};

View file

@ -1,3 +1,4 @@
export * from './json2html';
export * from './number';
export * from './is';
export * from './console';

View file

@ -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}&nbsp;</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}&nbsp;</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>

View file

@ -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);
}
}