diff options
Diffstat (limited to '')
-rw-r--r-- | benchmark/bench/_util.js | 2 | ||||
-rw-r--r-- | benchmark/bench/cli-startup.js | 9 | ||||
-rw-r--r-- | benchmark/bench/codspeed.js | 50 | ||||
-rw-r--r-- | benchmark/bench/memory.js | 2 | ||||
-rw-r--r-- | benchmark/bench/render.js | 12 | ||||
-rw-r--r-- | benchmark/bench/server-stress.js | 22 | ||||
-rwxr-xr-x | benchmark/index.js | 33 | ||||
-rw-r--r-- | benchmark/make-project/render-bench.js | 132 | ||||
-rw-r--r-- | benchmark/package.json | 5 | ||||
-rw-r--r-- | benchmark/packages/adapter/README.md | 3 | ||||
-rw-r--r-- | benchmark/packages/adapter/package.json | 35 | ||||
-rw-r--r-- | benchmark/packages/adapter/src/index.ts | 32 | ||||
-rw-r--r-- | benchmark/packages/adapter/src/server.ts | 34 | ||||
-rw-r--r-- | benchmark/packages/adapter/tsconfig.json | 7 | ||||
-rw-r--r-- | benchmark/packages/timer/src/index.ts | 4 | ||||
-rw-r--r-- | benchmark/packages/timer/src/server.ts | 2 |
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(); - }, + } }; } |