Skip to content

Commit eb656d0

Browse files
author
jialan
committed
feat: support languages benchmark
1 parent 63df067 commit eb656d0

File tree

7 files changed

+833
-13
lines changed

7 files changed

+833
-13
lines changed

.gitignore

+2-1
Original file line numberDiff line numberDiff line change
@@ -8,4 +8,5 @@ src/**/.antlr
88
coverage
99
.idea
1010
gen/
11-
src/**/*.iml
11+
src/**/*.iml
12+
benchmark/reports/*

benchmark/markdownWritter.ts

+87
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
import fs from 'fs';
2+
3+
export class MarkdownWritter {
4+
constructor(writePath: string) {
5+
this.writePath = writePath;
6+
}
7+
8+
public writePath: string = '';
9+
10+
public markdown: string = '';
11+
12+
private _MIN_TABLE_PAD_LENGTH = 8;
13+
14+
/**
15+
* Fill string to maxLength.
16+
*/
17+
private padWith(str: string, char: string, maxLength: number = this._MIN_TABLE_PAD_LENGTH) {
18+
if (str.length >= maxLength) return str;
19+
const diff = maxLength - str.length;
20+
const leftPadLength = Math.ceil(diff / 2);
21+
return str.padStart(str.length + leftPadLength, char).padEnd(maxLength, char);
22+
}
23+
24+
writeHeader(text: string, level: number = 1) {
25+
const header = `${new Array(level).fill('#').join('')} ${text}`;
26+
this.markdown += header;
27+
this.writeLine();
28+
}
29+
30+
writeText(text: string) {
31+
this.markdown += text;
32+
this.writeLine();
33+
}
34+
35+
writeLine() {
36+
this.markdown += '\r\n';
37+
}
38+
39+
writeTable(columns: { name: string; title: string }[], data: any[]) {
40+
let tableHeader = '';
41+
let tableBody = '';
42+
const colLengthMap = new Map<string, number>();
43+
44+
if (!columns.length) return;
45+
46+
// Calculate column width
47+
columns.forEach(({ title, name }) => {
48+
let max = title.length;
49+
data.forEach((item) => {
50+
max = Math.max(max, item[name]?.length || 0);
51+
});
52+
max = Math.max(max, this._MIN_TABLE_PAD_LENGTH);
53+
colLengthMap.set(name, max);
54+
});
55+
56+
columns.forEach(({ title, name }) => {
57+
tableHeader += '|' + this.padWith(title, ' ', colLengthMap.get(name));
58+
});
59+
tableHeader += '| \n';
60+
61+
columns.forEach(({ name }) => {
62+
tableHeader += '|' + this.padWith('-', '-', colLengthMap.get(name));
63+
});
64+
tableHeader += '| \n';
65+
66+
data.forEach((item) => {
67+
columns.forEach(({ name }) => {
68+
const text = item[name] || '';
69+
tableBody += '|' + this.padWith(text.toString(), ' ', colLengthMap.get(name));
70+
});
71+
tableBody += '| \n';
72+
});
73+
this.markdown += tableHeader + tableBody;
74+
75+
this.writeLine();
76+
}
77+
78+
writeHiddenInput(data: any) {
79+
const dataStr = JSON.stringify(data);
80+
this.markdown += `<input type="hidden" value='${dataStr}'/>`;
81+
this.writeLine();
82+
}
83+
84+
save() {
85+
fs.writeFileSync(this.writePath, this.markdown, { encoding: 'utf-8' });
86+
}
87+
}

benchmark/run.ts

+154
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,154 @@
1+
import path from 'path';
2+
import argsParser from 'yargs-parser';
3+
import fs from 'fs';
4+
import SqlBenchmark, { languageNameMap } from './sqlBenchmark';
5+
import inquirer from 'inquirer';
6+
import chalk from 'chalk';
7+
import { Table } from 'console-table-printer';
8+
9+
const languages: string[] = fs.readdirSync(path.resolve(__dirname, '../src/grammar'));
10+
const argv = argsParser(process.argv.slice(2));
11+
const isChooseAll = argv.lang === 'all';
12+
13+
type TestFile = {
14+
/** Benchmark Name */
15+
name: string;
16+
/** Test sql path */
17+
path: string;
18+
/** Test run times */
19+
loopTimes?: number;
20+
/** Test method name of parser */
21+
testTypes: string[];
22+
/** Exclude languages */
23+
excludes?: string[];
24+
/** Include languages */
25+
includes?: string[];
26+
};
27+
28+
const testFiles: TestFile[] = [
29+
{
30+
name: 'Query Collection (100 Rows)',
31+
path: 'select.sql',
32+
loopTimes: 3,
33+
testTypes: ['validate', 'getAllTokens'],
34+
excludes: ['pgsql', 'mysql'],
35+
},
36+
{
37+
name: 'Create Table (100 Rows)',
38+
path: 'create.sql',
39+
loopTimes: 3,
40+
testTypes: ['validate'],
41+
excludes: ['pgsql', 'mysql'],
42+
},
43+
];
44+
45+
let benchmarkResults: SqlBenchmark[] = [];
46+
47+
const readSql = (fileName: string, lang: string) => {
48+
const sqlPath = path.join(__dirname, `./data/${lang}/${fileName}`);
49+
if (!fs.existsSync(sqlPath)) return '';
50+
return fs.readFileSync(sqlPath, 'utf-8');
51+
};
52+
53+
const askForSaveResult = () => {
54+
inquirer
55+
.prompt([
56+
{
57+
type: 'input',
58+
name: 'save',
59+
message: 'Do you want to save this benchmark report? (y/N) default is false',
60+
filter: (val) => {
61+
if (!val) return false;
62+
if (['y', 'yes'].includes(val.toLowerCase())) return true;
63+
if (['n', 'no'].includes(val.toLowerCase())) return false;
64+
},
65+
},
66+
])
67+
.then((answer) => {
68+
if (answer.save) {
69+
if (isChooseAll) {
70+
Promise.all(
71+
benchmarkResults.map((sqlBenchmark) => sqlBenchmark.saveResults())
72+
).then(() => {
73+
console.log(chalk.green(`All Reports saved successfully.`));
74+
});
75+
} else {
76+
const [sqlBenchmark] = benchmarkResults;
77+
sqlBenchmark?.saveResults().then((path) => {
78+
console.log(chalk.green(`Report saved successfully. See ${path}`));
79+
});
80+
}
81+
}
82+
});
83+
};
84+
85+
/**
86+
* If choose all language, generate and print summary benchmark report.
87+
*
88+
* You can compare the performance differences between different sql languages.
89+
*/
90+
const printSummaryReport = () => {
91+
const rows: any = [];
92+
const costTimeMap = new Map<string, number>();
93+
94+
benchmarkResults.forEach((sqlBenchmark) => {
95+
sqlBenchmark.results.forEach(({ name, type, avgTime }) => {
96+
costTimeMap.set([sqlBenchmark.language, name, type].join('_'), avgTime);
97+
});
98+
});
99+
100+
testFiles.forEach(({ testTypes, name }) => {
101+
testTypes.forEach((testType) => {
102+
const langsCostTime: Record<string, string | number> = {};
103+
languages.forEach((lang) => {
104+
const costTime = costTimeMap.get([lang, name, testType].join('_'));
105+
langsCostTime[lang] = costTime ?? '-';
106+
});
107+
108+
rows.push({
109+
name: name,
110+
testType,
111+
...langsCostTime,
112+
});
113+
});
114+
});
115+
116+
const table = new Table({
117+
title: 'Summary Benchmark',
118+
columns: [
119+
{ name: 'name', title: 'Benchmark Name' },
120+
{ name: 'testType', title: 'Type' },
121+
...languages.map((lang) => ({ name: lang, title: languageNameMap[lang] })),
122+
],
123+
});
124+
table.addRows(rows);
125+
table.printTable();
126+
};
127+
128+
const benchmark = (lang: string) => {
129+
const sqlBenchmark = new SqlBenchmark(lang);
130+
testFiles.forEach((testInfo) => {
131+
const { name, path, testTypes, loopTimes, excludes, includes } = testInfo;
132+
if (excludes?.includes(lang) || (includes?.length && !includes.includes(lang))) return;
133+
const sql = readSql(path, lang);
134+
testTypes.forEach((type) => sqlBenchmark.run(type, name, sql, loopTimes));
135+
});
136+
sqlBenchmark.printResults();
137+
benchmarkResults.push(sqlBenchmark);
138+
};
139+
140+
function run() {
141+
if (isChooseAll) {
142+
for (const lang of languages) {
143+
benchmark(lang);
144+
}
145+
printSummaryReport();
146+
} else {
147+
benchmark(argv.lang);
148+
}
149+
askForSaveResult();
150+
}
151+
152+
process.on('uncaughtException', (err) => console.log(err));
153+
154+
run();

0 commit comments

Comments
 (0)