Skip to content

Commit 2ce1c1e

Browse files
committed
Generate basic/complex tables for performance testing and debugging.
1 parent ed4afb7 commit 2ce1c1e

File tree

3 files changed

+425
-0
lines changed

3 files changed

+425
-0
lines changed

lib/generate.js

+80
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
const Table = require('..');
2+
3+
const cellContent = ({ x, y, colSpan = 1, rowSpan = 1 }) => {
4+
return `${y}-${x} (${rowSpan}x${colSpan})`;
5+
};
6+
7+
const generateBasicTable = (rows, cols, options = {}) => {
8+
const table = new Table(options);
9+
for (let y = 0; y < rows; y++) {
10+
let row = [];
11+
for (let x = 0; x < cols; x++) {
12+
row.push(cellContent({ y, x }));
13+
}
14+
table.push(row);
15+
}
16+
return table;
17+
};
18+
19+
const randomNumber = (min, max, op = 'round') => {
20+
return Math[op](Math.random() * (max - min) + min);
21+
};
22+
23+
const next = (alloc, idx, dir = 1) => {
24+
if (alloc[idx]) {
25+
return next(alloc, idx + 1 * dir);
26+
}
27+
return idx;
28+
};
29+
30+
const generateComplexRow = (y, spanX, cols, alloc, options = {}) => {
31+
let x = next(alloc, 0);
32+
const row = [];
33+
while (x < cols) {
34+
const { colSpans = {} } = options;
35+
const opt = {
36+
colSpan: colSpans[x] || next(alloc, randomNumber(x + 1, options.maxCols || cols, 'ceil'), -1) - x,
37+
rowSpan: randomNumber(1, spanX),
38+
};
39+
row.push({ content: cellContent({ y, x, ...opt }), ...opt });
40+
if (opt.rowSpan > 1) {
41+
for (let i = 0; i < opt.colSpan; i++) {
42+
alloc[x + i] = opt.rowSpan;
43+
}
44+
}
45+
46+
x = next(alloc, x + opt.colSpan);
47+
}
48+
return row;
49+
};
50+
51+
const generateComplexRows = (y, rows, cols, alloc = {}, options = {}) => {
52+
const remaining = rows - y;
53+
let spanX = remaining > 1 ? randomNumber(1, remaining) : 1;
54+
let lines = [];
55+
while (spanX > 0) {
56+
lines.push(generateComplexRow(y, spanX, cols, alloc, options));
57+
y++;
58+
spanX--;
59+
Object.keys(alloc).forEach((idx) => {
60+
alloc[idx]--;
61+
if (alloc[idx] <= 0) delete alloc[idx];
62+
});
63+
}
64+
return lines;
65+
};
66+
67+
const generateComplexTable = (rows, cols, options = {}) => {
68+
const table = new Table(options.tableOptions);
69+
while (table.length < rows) {
70+
let y = table.length || (table.options.head && 1) || 0;
71+
generateComplexRows(y, rows, cols, options).forEach((row) => table.push(row));
72+
}
73+
return table;
74+
};
75+
76+
module.exports = {
77+
generateBasicTable,
78+
generateComplexTable,
79+
generateComplexRow,
80+
};

scripts/generate.js

+160
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,160 @@
1+
/**
2+
* See generate.md
3+
*/
4+
const { performance } = require('perf_hooks');
5+
const { generateBasicTable, generateComplexTable, generateComplexRow } = require('../lib/generate');
6+
7+
const argv = process.argv;
8+
9+
const timeScales = [
10+
['millisecond', 1000],
11+
['second', 60],
12+
['minute', 60],
13+
];
14+
const duration = (v, scales = [...timeScales]) => {
15+
const [unit, min] = scales.shift();
16+
if (v > min && scales.length) {
17+
return duration(v / min, scales);
18+
}
19+
let locale = undefined;
20+
if (process.env.LANG) {
21+
const userLocale = process.env.LANG.match(/[a-z]{2}_[A-Z]{2}/).shift();
22+
if (userLocale.match(/^[a-z]{2}[-_][A-Z]{2}$/)) {
23+
locale = userLocale.replace(/_/, '-');
24+
}
25+
}
26+
return v.toLocaleString(locale, { style: 'unit', unit });
27+
};
28+
29+
const argVal = (idx, def = 10) => {
30+
if (argv[idx] && argv[idx].match(/^[0-9]+$/)) {
31+
return parseInt(argv[idx], 10);
32+
}
33+
return def;
34+
};
35+
const optEnabled = (opt) => argv.indexOf(opt) > -1;
36+
const optValue = (opt) => {
37+
const idx = argv.indexOf(opt);
38+
return idx > -1 ? argv[idx + 1] : 0;
39+
};
40+
41+
const logMemory = (text = '') => {
42+
let suffix = 'kb';
43+
let used = process.memoryUsage().heapUsed / 1024;
44+
if (used % 1024 > 1) {
45+
used = used / 1024;
46+
suffix = 'mb';
47+
}
48+
return `Memory usage ${text}: ${used}${suffix}`;
49+
};
50+
51+
const printHelp = () => {
52+
console.log(`node scripts/generate [ROWS = 10] [COLS = 10]`);
53+
[
54+
['--print', 'Print the generated table to the screen.'],
55+
['--dump', 'Print the generated table code to the screen.'],
56+
['--complex', 'Generate a complex table (basic tables are generated by default)'],
57+
['--debug', 'Print table debugging output (warnings only).'],
58+
].forEach(([opt, desc]) => console.log(` ${opt} ${desc}`));
59+
};
60+
61+
const dumpTable = (t) => {
62+
const lines = [];
63+
lines.push(`const table = new Table();`);
64+
lines.push(`table.push(`);
65+
t.forEach((row) => {
66+
if (row.length) {
67+
let prefix = ' ';
68+
let suffix = '';
69+
const multiLine = row.length > 1 && row.some((v) => v.content !== undefined);
70+
if (multiLine) {
71+
lines.push(' [');
72+
}
73+
const cellLines = [];
74+
row.forEach((cell) => {
75+
if (cell.content) {
76+
const attrib = [];
77+
Object.entries(cell).forEach(([k, v]) => {
78+
if (!['style'].includes(k)) {
79+
attrib.push(`${k}: ${typeof v === 'string' ? `'${v}'` : v}`);
80+
}
81+
});
82+
cellLines.push(`{ ${attrib.join(', ')} },`);
83+
} else {
84+
cellLines.push(`${typeof cell === 'string' ? `'${cell}'` : cell}`);
85+
}
86+
});
87+
if (multiLine) {
88+
cellLines.forEach((cl) => lines.push([prefix, cl, suffix].join('')));
89+
lines.push(' ],');
90+
} else {
91+
lines.push(` [${cellLines.join(',')}]`);
92+
}
93+
} else {
94+
lines.push(' [],');
95+
}
96+
});
97+
lines.push(');');
98+
lines.push('console.log(table.toString());');
99+
return lines.forEach((line) => console.log(line));
100+
};
101+
102+
if (optEnabled('--help')) {
103+
printHelp();
104+
process.exit(0);
105+
}
106+
107+
const results = [];
108+
results.push(logMemory('at startup'));
109+
110+
const rows = argVal(2);
111+
const cols = argVal(3);
112+
113+
const maxRowSpan = rows > 10 ? Math.ceil(Math.round(rows * 0.1)) : Math.ceil(rows / 2);
114+
const maxColSpan = cols;
115+
116+
const complex = optEnabled('--complex');
117+
118+
console.log(`Generating ${complex ? 'complex' : 'basic'} table with ${rows} rows and ${cols} columns:`);
119+
120+
if (complex) {
121+
console.log(`Max rowSpan: ${maxRowSpan}`, `Max colSpan ${maxColSpan}`);
122+
}
123+
124+
const options = {
125+
tableOptions: {},
126+
};
127+
128+
if (optEnabled('--compact')) {
129+
options.tableOptions.style = { compact: true };
130+
}
131+
132+
if (optEnabled('--head')) {
133+
const head = generateComplexRow(0, 1, cols, {}, { maxCols: cols - 1 });
134+
options.tableOptions.head = head;
135+
}
136+
137+
const colWidth = optValue('--col-width');
138+
if (colWidth) {
139+
options.tableOptions.colWidths = [];
140+
for (let i = 0; i < cols; i++) {
141+
options.tableOptions.colWidths.push(parseInt(colWidth, 10));
142+
}
143+
}
144+
145+
// console.log(`table: ${rows} rows X ${cols} columns; ${rows * cols} total cells`);
146+
// console.time('build table');
147+
const buildStart = performance.now();
148+
const table = complex ? generateComplexTable(rows, cols, options) : generateBasicTable(rows, cols, options);
149+
// console.timeEnd('build table');
150+
results.push(logMemory('after table build'));
151+
152+
results.push(`table built in ${duration(performance.now() - buildStart)}`);
153+
154+
const start = performance.now();
155+
const output = table.toString();
156+
results.push(logMemory('after table rendered'));
157+
results.push(`table rendered in ${duration(performance.now() - start)}`);
158+
if (optEnabled('--print')) console.log(output);
159+
if (optEnabled('--dump')) dumpTable(table);
160+
results.forEach((result) => console.log(result));

0 commit comments

Comments
 (0)