summaryrefslogtreecommitdiff
path: root/benchmark/bench/render.js
diff options
context:
space:
mode:
Diffstat (limited to 'benchmark/bench/render.js')
-rw-r--r--benchmark/bench/render.js121
1 files changed, 121 insertions, 0 deletions
diff --git a/benchmark/bench/render.js b/benchmark/bench/render.js
new file mode 100644
index 000000000..02f75a73b
--- /dev/null
+++ b/benchmark/bench/render.js
@@ -0,0 +1,121 @@
+import fs from 'node:fs/promises';
+import http from 'node:http';
+import path from 'node:path';
+import { fileURLToPath } from 'node:url';
+import { markdownTable } from 'markdown-table';
+import { waitUntilBusy } from 'port-authority';
+import { exec } from 'tinyexec';
+import { renderPages } from '../make-project/render-default.js';
+import { astroBin, calculateStat } from './_util.js';
+
+const port = 4322;
+
+export const defaultProject = 'render-default';
+
+/**
+ * @param {URL} projectDir
+ * @param {URL} outputFile
+ */
+export async function run(projectDir, outputFile) {
+ const root = fileURLToPath(projectDir);
+
+ console.log('Building...');
+ await exec(astroBin, ['build'], {
+ nodeOptions: {
+ cwd: root,
+ stdio: 'inherit',
+ },
+ throwOnError: true,
+ });
+
+ console.log('Previewing...');
+ const previewProcess = exec(astroBin, ['preview', '--port', port], {
+ nodeOptions: {
+ cwd: root,
+ stdio: 'inherit',
+ },
+ throwOnError: true,
+ });
+
+ console.log('Waiting for server ready...');
+ await waitUntilBusy(port, { timeout: 5000 });
+
+ console.log('Running benchmark...');
+ const result = await benchmarkRenderTime();
+
+ console.log('Killing server...');
+ if (!previewProcess.kill('SIGTERM')) {
+ console.warn('Failed to kill server process id:', previewProcess.pid);
+ }
+
+ console.log('Writing results to', fileURLToPath(outputFile));
+ await fs.writeFile(outputFile, JSON.stringify(result, null, 2));
+
+ console.log('Result preview:');
+ console.log('='.repeat(10));
+ console.log(`#### Render\n\n`);
+ console.log(printResult(result));
+ console.log('='.repeat(10));
+
+ console.log('Done!');
+}
+
+export async function benchmarkRenderTime(portToListen = port) {
+ /** @type {Record<string, number[]>} */
+ const result = {};
+ for (const fileName of renderPages) {
+ // Render each file 100 times and push to an array
+ for (let i = 0; i < 100; i++) {
+ const pathname = '/' + fileName.slice(0, -path.extname(fileName).length);
+ const renderTime = await fetchRenderTime(`http://localhost:${portToListen}${pathname}`);
+ if (!result[pathname]) result[pathname] = [];
+ result[pathname].push(renderTime);
+ }
+ }
+ /** @type {Record<string, import('./_util.js').Stat>} */
+ const processedResult = {};
+ for (const [pathname, times] of Object.entries(result)) {
+ // From the 100 results, calculate average, standard deviation, and max value
+ processedResult[pathname] = calculateStat(times);
+ }
+ return processedResult;
+}
+
+/**
+ * @param {Record<string, import('./_util.js').Stat>} result
+ */
+function printResult(result) {
+ return markdownTable(
+ [
+ ['Page', 'Avg (ms)', 'Stdev (ms)', 'Max (ms)'],
+ ...Object.entries(result).map(([pathname, { avg, stdev, max }]) => [
+ pathname,
+ avg.toFixed(2),
+ stdev.toFixed(2),
+ max.toFixed(2),
+ ]),
+ ],
+ {
+ align: ['l', 'r', 'r', 'r'],
+ },
+ );
+}
+
+/**
+ * Simple fetch utility to get the render time sent by `@benchmark/timer` in plain text
+ * @param {string} url
+ * @returns {Promise<number>}
+ */
+function fetchRenderTime(url) {
+ return new Promise((resolve, reject) => {
+ const req = http.request(url, (res) => {
+ res.setEncoding('utf8');
+ let data = '';
+ res.on('data', (chunk) => (data += chunk));
+ res.on('error', (e) => reject(e));
+ res.on('end', () => resolve(+data));
+ });
+ req.on('error', (e) => reject(e));
+ req.end();
+ });
+}