summaryrefslogtreecommitdiff
path: root/benchmark
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--benchmark/bench/_util.js2
-rw-r--r--benchmark/bench/cli-startup.js9
-rw-r--r--benchmark/bench/codspeed.js50
-rw-r--r--benchmark/bench/memory.js2
-rw-r--r--benchmark/bench/render.js12
-rw-r--r--benchmark/bench/server-stress.js22
-rwxr-xr-xbenchmark/index.js33
-rw-r--r--benchmark/make-project/render-bench.js132
-rw-r--r--benchmark/package.json5
-rw-r--r--benchmark/packages/adapter/README.md3
-rw-r--r--benchmark/packages/adapter/package.json35
-rw-r--r--benchmark/packages/adapter/src/index.ts32
-rw-r--r--benchmark/packages/adapter/src/server.ts34
-rw-r--r--benchmark/packages/adapter/tsconfig.json7
-rw-r--r--benchmark/packages/timer/src/index.ts4
-rw-r--r--benchmark/packages/timer/src/server.ts2
16 files changed, 350 insertions, 34 deletions
diff --git a/benchmark/bench/_util.js b/benchmark/bench/_util.js
index 23c472604..b16a16e1c 100644
--- a/benchmark/bench/_util.js
+++ b/benchmark/bench/_util.js
@@ -14,7 +14,7 @@ export const astroBin = path.resolve(astroPkgPath, '../astro.js');
export function calculateStat(numbers) {
const avg = numbers.reduce((a, b) => a + b, 0) / numbers.length;
const stdev = Math.sqrt(
- numbers.map((x) => Math.pow(x - avg, 2)).reduce((a, b) => a + b, 0) / numbers.length
+ numbers.map((x) => Math.pow(x - avg, 2)).reduce((a, b) => a + b, 0) / numbers.length,
);
const max = Math.max(...numbers);
return { avg, stdev, max };
diff --git a/benchmark/bench/cli-startup.js b/benchmark/bench/cli-startup.js
index 2e9eccf64..9144797d7 100644
--- a/benchmark/bench/cli-startup.js
+++ b/benchmark/bench/cli-startup.js
@@ -1,6 +1,6 @@
import { fileURLToPath } from 'node:url';
-import { exec } from 'tinyexec';
import { markdownTable } from 'markdown-table';
+import { exec } from 'tinyexec';
import { astroBin, calculateStat } from './_util.js';
/** Default project to run for this benchmark if not specified */
@@ -8,9 +8,8 @@ export const defaultProject = 'render-default';
/**
* @param {URL} projectDir
- * @param {URL} outputFile
*/
-export async function run(projectDir, outputFile) {
+export async function run(projectDir) {
const root = fileURLToPath(projectDir);
console.log('Benchmarking `astro --help`...');
@@ -28,7 +27,7 @@ export async function run(projectDir, outputFile) {
printResult({
'astro --help': helpStat,
'astro info': infoStat,
- })
+ }),
);
console.log('='.repeat(10));
}
@@ -69,6 +68,6 @@ function printResult(result) {
],
{
align: ['l', 'r', 'r', 'r'],
- }
+ },
);
}
diff --git a/benchmark/bench/codspeed.js b/benchmark/bench/codspeed.js
new file mode 100644
index 000000000..2ad783e8a
--- /dev/null
+++ b/benchmark/bench/codspeed.js
@@ -0,0 +1,50 @@
+import path from 'node:path';
+import { withCodSpeed } from '@codspeed/tinybench-plugin';
+import { Bench } from 'tinybench';
+import { exec } from 'tinyexec';
+import { renderPages } from '../make-project/render-default.js';
+import { astroBin } from './_util.js';
+
+export async function run({ memory: _memory, render, stress: _stress }) {
+ const options = {
+ iterations: 10,
+ };
+ const bench = process.env.CODSPEED ? withCodSpeed(new Bench(options)) : new Bench(options);
+ let app;
+ bench.add(
+ 'Rendering',
+ async () => {
+ console.info('Start task.');
+ const result = {};
+ for (const fileName of renderPages) {
+ const pathname = '/' + fileName.slice(0, -path.extname(fileName).length);
+ const request = new Request(new URL(pathname, 'http://exmpale.com'));
+ const response = await app.render(request);
+ const html = await response.text();
+ if (!result[pathname]) result[pathname] = [];
+ result[pathname].push(html);
+ }
+ console.info('Finish task.');
+ return result;
+ },
+ {
+ async beforeAll() {
+ // build for rendering
+ await exec(astroBin, ['build'], {
+ nodeOptions: {
+ cwd: render.root,
+ stdio: 'inherit',
+ },
+ });
+
+ const entry = new URL('./dist/server/entry.mjs', `file://${render.root}`);
+ const { manifest, createApp } = await import(entry);
+ app = createApp(manifest);
+ app.manifest = manifest;
+ },
+ },
+ );
+
+ await bench.run();
+ console.table(bench.table());
+}
diff --git a/benchmark/bench/memory.js b/benchmark/bench/memory.js
index 9a3f94bc7..5c746870e 100644
--- a/benchmark/bench/memory.js
+++ b/benchmark/bench/memory.js
@@ -56,6 +56,6 @@ function printResult(output) {
],
{
align: ['l', 'r', 'r', 'r'],
- }
+ },
);
}
diff --git a/benchmark/bench/render.js b/benchmark/bench/render.js
index 8dfd47fbb..02f75a73b 100644
--- a/benchmark/bench/render.js
+++ b/benchmark/bench/render.js
@@ -1,12 +1,12 @@
-import { exec } from 'tinyexec';
-import { markdownTable } from 'markdown-table';
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 { calculateStat, astroBin } from './_util.js';
+import { exec } from 'tinyexec';
import { renderPages } from '../make-project/render-default.js';
+import { astroBin, calculateStat } from './_util.js';
const port = 4322;
@@ -60,14 +60,14 @@ export async function run(projectDir, outputFile) {
console.log('Done!');
}
-async function benchmarkRenderTime() {
+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:${port}${pathname}`);
+ const renderTime = await fetchRenderTime(`http://localhost:${portToListen}${pathname}`);
if (!result[pathname]) result[pathname] = [];
result[pathname].push(renderTime);
}
@@ -97,7 +97,7 @@ function printResult(result) {
],
{
align: ['l', 'r', 'r', 'r'],
- }
+ },
);
}
diff --git a/benchmark/bench/server-stress.js b/benchmark/bench/server-stress.js
index 9e93c4cd9..5bcaa6963 100644
--- a/benchmark/bench/server-stress.js
+++ b/benchmark/bench/server-stress.js
@@ -1,10 +1,10 @@
-import autocannon from 'autocannon';
-import { exec } from 'tinyexec';
-import { markdownTable } from 'markdown-table';
import fs from 'node:fs/promises';
import { fileURLToPath } from 'node:url';
+import autocannon from 'autocannon';
+import { markdownTable } from 'markdown-table';
import { waitUntilBusy } from 'port-authority';
import pb from 'pretty-bytes';
+import { exec } from 'tinyexec';
import { astroBin } from './_util.js';
const port = 4321;
@@ -28,9 +28,11 @@ export async function run(projectDir, outputFile) {
});
console.log('Previewing...');
- const previewProcess = execaCommand(`${astroBin} preview --port ${port}`, {
- cwd: root,
- stdio: 'inherit',
+ const previewProcess = await exec(astroBin, ['preview', '--port', port], {
+ nodeOptions: {
+ cwd: root,
+ stdio: 'inherit',
+ },
});
console.log('Waiting for server ready...');
@@ -59,7 +61,7 @@ export async function run(projectDir, outputFile) {
/**
* @returns {Promise<import('autocannon').Result>}
*/
-async function benchmarkCannon() {
+export async function benchmarkCannon() {
return new Promise((resolve, reject) => {
const instance = autocannon(
{
@@ -76,7 +78,7 @@ async function benchmarkCannon() {
instance.stop();
resolve(result);
}
- }
+ },
);
autocannon.track(instance, { renderResultsTable: false });
});
@@ -95,7 +97,7 @@ function printResult(output) {
],
{
align: ['l', 'r', 'r', 'r'],
- }
+ },
);
const reqAndBytesTable = markdownTable(
@@ -106,7 +108,7 @@ function printResult(output) {
],
{
align: ['l', 'r', 'r', 'r', 'r'],
- }
+ },
);
return `${latencyTable}\n\n${reqAndBytesTable}`;
diff --git a/benchmark/index.js b/benchmark/index.js
index 1c38993b1..2f2846e1d 100755
--- a/benchmark/index.js
+++ b/benchmark/index.js
@@ -1,7 +1,7 @@
import mri from 'mri';
import fs from 'node:fs/promises';
import path from 'node:path';
-import { pathToFileURL } from 'node:url';
+import {fileURLToPath, pathToFileURL} from 'node:url';
const args = mri(process.argv.slice(2));
@@ -14,6 +14,7 @@ Command
memory Run build memory and speed test
render Run rendering speed test
server-stress Run server stress test
+ codspeed Run codspeed test
cli-startup Run CLI startup speed test
Options
@@ -29,6 +30,7 @@ const benchmarks = {
render: () => import('./bench/render.js'),
'server-stress': () => import('./bench/server-stress.js'),
'cli-startup': () => import('./bench/cli-startup.js'),
+ codspeed: () => import('./bench/codspeed.js')
};
if (commandName && !(commandName in benchmarks)) {
@@ -37,12 +39,26 @@ if (commandName && !(commandName in benchmarks)) {
}
if (commandName) {
- // Run single benchmark
- const bench = benchmarks[commandName];
- const benchMod = await bench();
- const projectDir = await makeProject(args.project || benchMod.defaultProject);
- const outputFile = await getOutputFile(commandName);
- await benchMod.run(projectDir, outputFile);
+ if (commandName === 'codspeed') {
+ const render = await makeProject('render-bench');
+ const rootRender = fileURLToPath(render);
+ const bench = benchmarks[commandName];
+ const benchMod = await bench();
+ const payload = {
+ render: {
+ root: rootRender,
+ output: await getOutputFile('render')
+ },
+ };
+ await benchMod.run(payload);
+ } else {
+ // Run single benchmark
+ const bench = benchmarks[commandName];
+ const benchMod = await bench();
+ const projectDir = await makeProject(args.project || benchMod.defaultProject);
+ const outputFile = await getOutputFile(commandName);
+ await benchMod.run(projectDir, outputFile);
+ }
} else {
// Run all benchmarks
for (const name in benchmarks) {
@@ -54,7 +70,7 @@ if (commandName) {
}
}
-async function makeProject(name) {
+export async function makeProject(name) {
console.log('Making project:', name);
const projectDir = new URL(`./projects/${name}/`, import.meta.url);
@@ -78,6 +94,5 @@ async function getOutputFile(benchmarkName) {
// Prepare output file directory
await fs.mkdir(new URL('./', file), { recursive: true });
-
return file;
}
diff --git a/benchmark/make-project/render-bench.js b/benchmark/make-project/render-bench.js
new file mode 100644
index 000000000..9d10d9bf5
--- /dev/null
+++ b/benchmark/make-project/render-bench.js
@@ -0,0 +1,132 @@
+import fs from 'node:fs/promises';
+import { loremIpsumHtml, loremIpsumMd } from './_util.js';
+
+// Map of files to be generated and tested for rendering.
+// Ideally each content should be similar for comparison.
+const renderFiles = {
+ 'components/ListItem.astro': `\
+---
+const { className, item, attrs } = Astro.props;
+const nested = item !== 0;
+---
+ <li class={className}>
+ <a
+ href={item}
+ aria-current={item === 0}
+ class:list={[{ large: !nested }, className]}
+ {...attrs}
+ >
+ <span>{item}</span>
+ </a>
+ </li>
+ `,
+ 'components/Sublist.astro': `\
+---
+import ListItem from '../components/ListItem.astro';
+const { items } = Astro.props;
+const className = "text-red-500";
+const style = { color: "red" };
+---
+<ul style={style}>
+{items.map((item) => (
+ <ListItem className={className} item={item} attrs={{}} />
+))}
+</ul>
+ `,
+ 'pages/astro.astro': `\
+---
+const className = "text-red-500";
+const style = { color: "red" };
+const items = Array.from({ length: 10000 }, (_, i) => ({i}));
+---
+<html>
+ <head>
+ <title>My Site</title>
+ </head>
+ <body>
+ <h1 class={className + ' text-lg'}>List</h1>
+ <ul style={style}>
+ {items.map((item) => (
+ <li class={className}>
+ <a
+ href={item.i}
+ aria-current={item.i === 0}
+ class:list={[{ large: item.i === 0 }, className]}
+ {...({})}
+ >
+ <span>{item.i}</span>
+ </a>
+ </li>
+ ))}
+ </ul>
+ ${Array.from({ length: 1000 })
+ .map(() => `<p>${loremIpsumHtml}</p>`)
+ .join('\n')}
+ </body>
+</html>`,
+ 'pages/md.md': `\
+# List
+
+${Array.from({ length: 1000 }, (_, i) => i)
+ .map((v) => `- ${v}`)
+ .join('\n')}
+
+${Array.from({ length: 1000 })
+ .map(() => loremIpsumMd)
+ .join('\n\n')}
+`,
+ 'pages/mdx.mdx': `\
+export const className = "text-red-500";
+export const style = { color: "red" };
+export const items = Array.from({ length: 1000 }, (_, i) => i);
+
+# List
+
+<ul style={style}>
+ {items.map((item) => (
+ <li class={className}>{item}</li>
+ ))}
+</ul>
+
+${Array.from({ length: 1000 })
+ .map(() => loremIpsumMd)
+ .join('\n\n')}
+`,
+};
+
+export const renderPages = [];
+for (const file of Object.keys(renderFiles)) {
+ if (file.startsWith('pages/')) {
+ renderPages.push(file.replace('pages/', ''));
+ }
+}
+
+/**
+ * @param {URL} projectDir
+ */
+export async function run(projectDir) {
+ await fs.rm(projectDir, { recursive: true, force: true });
+ await fs.mkdir(new URL('./src/pages', projectDir), { recursive: true });
+ await fs.mkdir(new URL('./src/components', projectDir), { recursive: true });
+
+ await Promise.all(
+ Object.entries(renderFiles).map(([name, content]) => {
+ return fs.writeFile(new URL(`./src/${name}`, projectDir), content, 'utf-8');
+ })
+ );
+
+ await fs.writeFile(
+ new URL('./astro.config.js', projectDir),
+ `\
+import { defineConfig } from 'astro/config';
+import adapter from '@benchmark/adapter';
+import mdx from '@astrojs/mdx';
+
+export default defineConfig({
+ integrations: [mdx()],
+ output: 'server',
+ adapter: adapter(),
+});`,
+ 'utf-8'
+ );
+}
diff --git a/benchmark/package.json b/benchmark/package.json
index 2fe6ba7b9..428afe56d 100644
--- a/benchmark/package.json
+++ b/benchmark/package.json
@@ -10,6 +10,7 @@
"@astrojs/mdx": "workspace:*",
"@astrojs/node": "^8.3.4",
"@benchmark/timer": "workspace:*",
+ "@benchmark/adapter": "workspace:*",
"astro": "workspace:*",
"autocannon": "^7.15.0",
"markdown-table": "^3.0.4",
@@ -18,5 +19,9 @@
"pretty-bytes": "^6.1.1",
"sharp": "^0.33.3",
"tinyexec": "^0.3.1"
+ },
+ "devDependencies": {
+ "@codspeed/tinybench-plugin": "^3.1.1",
+ "tinybench": "^2.9.0"
}
}
diff --git a/benchmark/packages/adapter/README.md b/benchmark/packages/adapter/README.md
new file mode 100644
index 000000000..5b8e33ed4
--- /dev/null
+++ b/benchmark/packages/adapter/README.md
@@ -0,0 +1,3 @@
+# @benchmark/timer
+
+Like `@astrojs/node`, but returns the rendered time in milliseconds for the page instead of the page content itself. This is used for internal benchmarks only.
diff --git a/benchmark/packages/adapter/package.json b/benchmark/packages/adapter/package.json
new file mode 100644
index 000000000..2bdb73ce9
--- /dev/null
+++ b/benchmark/packages/adapter/package.json
@@ -0,0 +1,35 @@
+{
+ "name": "@benchmark/adapter",
+ "description": "Bench adapter",
+ "private": true,
+ "version": "0.0.0",
+ "type": "module",
+ "types": "./dist/index.d.ts",
+ "author": "withastro",
+ "license": "MIT",
+ "keywords": [
+ "withastro",
+ "astro-adapter"
+ ],
+ "exports": {
+ ".": "./dist/index.js",
+ "./server.js": "./dist/server.js",
+ "./package.json": "./package.json"
+ },
+ "scripts": {
+ "build": "astro-scripts build \"src/**/*.ts\" && tsc",
+ "build:ci": "astro-scripts build \"src/**/*.ts\"",
+ "dev": "astro-scripts dev \"src/**/*.ts\""
+ },
+ "dependencies": {
+ "server-destroy": "^1.0.1"
+ },
+ "peerDependencies": {
+ "astro": "workspace:*"
+ },
+ "devDependencies": {
+ "@types/server-destroy": "^1.0.4",
+ "astro": "workspace:*",
+ "astro-scripts": "workspace:*"
+ }
+}
diff --git a/benchmark/packages/adapter/src/index.ts b/benchmark/packages/adapter/src/index.ts
new file mode 100644
index 000000000..f2345deb0
--- /dev/null
+++ b/benchmark/packages/adapter/src/index.ts
@@ -0,0 +1,32 @@
+import type { AstroAdapter, AstroIntegration } from 'astro';
+
+export default function createIntegration(): AstroIntegration {
+ return {
+ name: '@benchmark/timer',
+ hooks: {
+ 'astro:config:setup': ({ updateConfig }) => {
+ updateConfig({
+ vite: {
+ ssr: {
+ noExternal: ['@benchmark/timer'],
+ },
+ },
+ });
+ },
+ 'astro:config:done': ({ setAdapter }) => {
+ setAdapter({
+ name: '@benchmark/adapter',
+ serverEntrypoint: '@benchmark/adapter/server.js',
+ exports: ['manifest', 'createApp'],
+ supportedAstroFeatures: {
+ serverOutput: 'stable',
+ envGetSecret: 'experimental',
+ staticOutput: 'stable',
+ hybridOutput: 'stable',
+ i18nDomains: 'stable',
+ },
+ });
+ },
+ },
+ };
+}
diff --git a/benchmark/packages/adapter/src/server.ts b/benchmark/packages/adapter/src/server.ts
new file mode 100644
index 000000000..ca69fe28f
--- /dev/null
+++ b/benchmark/packages/adapter/src/server.ts
@@ -0,0 +1,34 @@
+import * as fs from 'node:fs';
+import type { SSRManifest } from 'astro';
+import { App } from 'astro/app';
+import { applyPolyfills } from 'astro/app/node';
+
+applyPolyfills();
+
+class MyApp extends App {
+ #manifest: SSRManifest | undefined;
+ #streaming: boolean;
+ constructor(manifest: SSRManifest, streaming = false) {
+ super(manifest, streaming);
+ this.#manifest = manifest;
+ this.#streaming = streaming;
+ }
+
+ async render(request: Request) {
+ const url = new URL(request.url);
+ if (this.#manifest?.assets.has(url.pathname)) {
+ const filePath = new URL('../../client/' + this.removeBase(url.pathname), import.meta.url);
+ const data = await fs.promises.readFile(filePath);
+ return new Response(data);
+ }
+
+ return super.render(request);
+ }
+}
+
+export function createExports(manifest: SSRManifest) {
+ return {
+ manifest,
+ createApp: (streaming: boolean) => new MyApp(manifest, streaming),
+ };
+}
diff --git a/benchmark/packages/adapter/tsconfig.json b/benchmark/packages/adapter/tsconfig.json
new file mode 100644
index 000000000..1504b4b6d
--- /dev/null
+++ b/benchmark/packages/adapter/tsconfig.json
@@ -0,0 +1,7 @@
+{
+ "extends": "../../../tsconfig.base.json",
+ "include": ["src"],
+ "compilerOptions": {
+ "outDir": "./dist"
+ }
+}
diff --git a/benchmark/packages/timer/src/index.ts b/benchmark/packages/timer/src/index.ts
index 1c54e3727..f83a61c36 100644
--- a/benchmark/packages/timer/src/index.ts
+++ b/benchmark/packages/timer/src/index.ts
@@ -6,7 +6,9 @@ export function getAdapter(): AstroAdapter {
serverEntrypoint: '@benchmark/timer/server.js',
previewEntrypoint: '@benchmark/timer/preview.js',
exports: ['handler'],
- supportedAstroFeatures: {},
+ supportedAstroFeatures: {
+ serverOutput: 'stable',
+ },
};
}
diff --git a/benchmark/packages/timer/src/server.ts b/benchmark/packages/timer/src/server.ts
index 9905a627b..edcfaa248 100644
--- a/benchmark/packages/timer/src/server.ts
+++ b/benchmark/packages/timer/src/server.ts
@@ -13,6 +13,6 @@ export function createExports(manifest: SSRManifest) {
const end = performance.now();
res.write(end - start + '');
res.end();
- },
+ }
};
}