diff --git a/.eslintrc.cjs b/.eslintrc.cjs index 2652a454e..a7ec9c8ea 100644 --- a/.eslintrc.cjs +++ b/.eslintrc.cjs @@ -38,5 +38,11 @@ module.exports = { 'no-console': ['error', { allow: ['warn', 'error', 'info', 'debug'] }], }, }, + { + files: ['benchmark/**/*.js'], + rules: { + 'no-console': 'off', + }, + }, ], }; diff --git a/.github/workflows/benchmark.yml b/.github/workflows/benchmark.yml index 54b33e9d3..0dcfd6105 100644 --- a/.github/workflows/benchmark.yml +++ b/.github/workflows/benchmark.yml @@ -16,19 +16,14 @@ jobs: permissions: contents: read outputs: - PR-BENCH-16: ${{ steps.benchmark-pr.outputs.BENCH_RESULT16 }} - PR-BENCH-18: ${{ steps.benchmark-pr.outputs.BENCH_RESULT18 }} - MAIN-BENCH-16: ${{ steps.benchmark-main.outputs.BENCH_RESULT16 }} - MAIN-BENCH-18: ${{ steps.benchmark-main.outputs.BENCH_RESULT18 }} - strategy: - matrix: - node-version: [16, 18] + PR-BENCH: ${{ steps.benchmark-pr.outputs.BENCH_RESULT }} + MAIN-BENCH: ${{ steps.benchmark-main.outputs.BENCH_RESULT }} steps: + # https://github.com/actions/checkout/issues/331#issuecomment-1438220926 - uses: actions/checkout@v3 with: persist-credentials: false - ref: ${{github.event.pull_request.head.sha}} - repository: ${{github.event.pull_request.head.repo.full_name}} + ref: refs/pull/${{ github.event.issue.number }}/head - name: Setup PNPM uses: pnpm/action-setup@v2 @@ -45,13 +40,22 @@ jobs: - name: Build Packages run: pnpm run build + - name: Get bench command + id: bench-command + run: | + benchcmd=$(echo "${{ github.event.comment.body }}" | grep '!bench' | awk -F ' ' '{print $2}') + echo "bench=$benchcmd" >> $GITHUB_OUTPUT + shell: bash + - name: Run benchmark id: benchmark-pr run: | - pnpm run --silent benchmark 2> ./bench-result.md - result=$(awk '/requests in/' ./bench-result.md) - echo "::set-output name=BENCH_RESULT${{matrix.node-version}}::$result" - echo "$result" + result=$(pnpm run --silent benchmark ${{ steps.bench-command.outputs.bench }}) + processed=$(node ./benchmark/ci-helper.js "$result") + echo "BENCH_RESULT<> $GITHUB_OUTPUT + echo "### PR Benchmark" >> $GITHUB_OUTPUT + echo "$processed" >> $GITHUB_OUTPUT + echo "BENCHEOF" >> $GITHUB_OUTPUT shell: bash # main benchmark @@ -70,10 +74,12 @@ jobs: - name: Run benchmark id: benchmark-main run: | - pnpm run --silent benchmark 2> ./bench-result.md - result=$(awk '/requests in/' ./bench-result.md) - echo "::set-output name=BENCH_RESULT${{matrix.node-version}}::$result" - echo "$result" + result=$(pnpm run --silent benchmark ${{ steps.bench-command.outputs.bench }}) + processed=$(node ./benchmark/ci-helper.js "$result") + echo "BENCH_RESULT<> $GITHUB_OUTPUT + echo "### Main Benchmark" >> $GITHUB_OUTPUT + echo "$processed" >> $GITHUB_OUTPUT + echo "BENCHEOF" >> $GITHUB_OUTPUT shell: bash output-benchmark: @@ -89,12 +95,6 @@ jobs: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} pr_number: ${{ github.event.issue.number }} message: | - **Node**: 16 - **PR**: ${{ needs.benchmark.outputs.PR-BENCH-16 }} - **MAIN**: ${{ needs.benchmark.outputs.MAIN-BENCH-16 }} + ${{ needs.benchmark.outputs.PR-BENCH }} - --- - - **Node**: 18 - **PR**: ${{ needs.benchmark.outputs.PR-BENCH-18 }} - **MAIN**: ${{ needs.benchmark.outputs.MAIN-BENCH-18 }} + ${{ needs.benchmark.outputs.MAIN-BENCH }} diff --git a/.gitignore b/.gitignore index df661ac83..00a4a7723 100644 --- a/.gitignore +++ b/.gitignore @@ -6,6 +6,8 @@ dist/ _site/ scripts/smoke/*-main/ scripts/memory/project/src/pages/ +benchmark/projects/ +benchmark/results/ *.log package-lock.json .turbo/ diff --git a/benchmark/README.md b/benchmark/README.md new file mode 100644 index 000000000..79d63f4da --- /dev/null +++ b/benchmark/README.md @@ -0,0 +1,5 @@ +# benchmark + +Astro's main benchmark suite. It exposes the `astro-benchmark` CLI command. Run `astro-benchmark --help` to see all available commands! + +If you'd like to understand how the benchmark works, check out the other READMEs in the subfolders. diff --git a/benchmark/bench/README.md b/benchmark/bench/README.md new file mode 100644 index 000000000..9d3312880 --- /dev/null +++ b/benchmark/bench/README.md @@ -0,0 +1,7 @@ +# bench + +This `bench` folder contains different benchmarking files that you can run via `astro-benchmark `, e.g. `astro-benchmark memory`. Files that start with an underscore are not benchmarking files. + +Benchmarking files will run against a project to measure its performance, and write the results down as JSON in the `results` folder. The `results` folder is gitignored and its result files can be safely deleted if you're not using them. + +You can duplicate `_template.js` to start a new benchmark test. All shared utilities are kept in `_util.js`. diff --git a/benchmark/bench/_template.js b/benchmark/bench/_template.js new file mode 100644 index 000000000..867ecf13b --- /dev/null +++ b/benchmark/bench/_template.js @@ -0,0 +1,12 @@ +/** Default project to run for this benchmark if not specified */ +export const defaultProject = 'project-name'; + +/** + * Run benchmark on `projectDir` and write results to `outputFile`. + * Use `console.log` to report the results too. Logs that start with 10 `=` + * and end with 10 `=` will be extracted by CI to display in the PR comment. + * Usually after the first 10 `=` you'll want to add a title like `#### Test`. + * @param {URL} projectDir + * @param {URL} outputFile + */ +export async function run(projectDir, outputFile) {} diff --git a/benchmark/bench/_util.js b/benchmark/bench/_util.js new file mode 100644 index 000000000..b61c79a78 --- /dev/null +++ b/benchmark/bench/_util.js @@ -0,0 +1,3 @@ +import { createRequire } from 'module'; + +export const astroBin = createRequire(import.meta.url).resolve('astro'); diff --git a/benchmark/bench/memory.js b/benchmark/bench/memory.js new file mode 100644 index 000000000..1f0d8ab8b --- /dev/null +++ b/benchmark/bench/memory.js @@ -0,0 +1,58 @@ +import fs from 'fs/promises'; +import { fileURLToPath } from 'url'; +import { execaCommand } from 'execa'; +import { markdownTable } from 'markdown-table'; +import { astroBin } from './_util.js'; + +/** @typedef {Record} AstroTimerStat */ + +/** Default project to run for this benchmark if not specified */ +export const defaultProject = 'memory-default'; + +/** + * @param {URL} projectDir + * @param {URL} outputFile + */ +export async function run(projectDir, outputFile) { + const root = fileURLToPath(projectDir); + const outputFilePath = fileURLToPath(outputFile); + + console.log('Building and benchmarking...'); + await execaCommand(`node --expose-gc --max_old_space_size=256 ${astroBin} build`, { + cwd: root, + stdio: 'inherit', + env: { + ASTRO_TIMER_PATH: outputFilePath, + }, + }); + + console.log('Raw results written to', outputFilePath); + + console.log('Result preview:'); + console.log('='.repeat(10)); + console.log(`#### Memory\n\n`); + console.log(printResult(JSON.parse(await fs.readFile(outputFilePath, 'utf-8')))); + console.log('='.repeat(10)); + + console.log('Done!'); +} + +/** + * @param {AstroTimerStat} output + */ +function printResult(output) { + return markdownTable( + [ + ['', 'Elapsed time (s)', 'Memory used (MB)', 'Final memory (MB)'], + ...Object.entries(output).map(([name, stat]) => [ + name, + (stat.elapsedTime / 1000).toFixed(2), + (stat.heapUsedChange / 1024 / 1024).toFixed(2), + (stat.heapUsedTotal / 1024 / 1024).toFixed(2), + ]), + ], + { + align: ['l', 'r', 'r', 'r'], + } + ); +} diff --git a/benchmark/bench/server-stress.js b/benchmark/bench/server-stress.js new file mode 100644 index 000000000..6237b2e5f --- /dev/null +++ b/benchmark/bench/server-stress.js @@ -0,0 +1,85 @@ +import fs from 'fs/promises'; +import { fileURLToPath } from 'url'; +import autocannon from 'autocannon'; +import { execaCommand } from 'execa'; +import { waitUntilBusy } from 'port-authority'; +import { astroBin } from './_util.js'; + +const port = 4321; + +export const defaultProject = 'server-stress-default'; + +/** + * @param {URL} projectDir + * @param {URL} outputFile + */ +export async function run(projectDir, outputFile) { + const root = fileURLToPath(projectDir); + + console.log('Building...'); + await execaCommand(`${astroBin} build`, { + cwd: root, + stdio: 'inherit', + }); + + console.log('Previewing...'); + const previewProcess = execaCommand(`${astroBin} preview --port ${port}`, { + cwd: root, + stdio: 'inherit', + }); + + console.log('Waiting for server ready...'); + await waitUntilBusy(port, { timeout: 5000 }); + + console.log('Running benchmark...'); + const result = await benchmarkCannon(); + + 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(`#### Server stress\n\n`); + let text = autocannon.printResult(result); + // Truncate the logs in CI so that the generated comment from the `!bench` command + // is shortened. Also we only need this information when comparing runs. + // Full log example: https://github.com/mcollina/autocannon#command-line + if (process.env.CI) { + text = text.match(/^.*?requests in.*?read$/m)?.[0]; + } + console.log(text); + console.log('='.repeat(10)); + + console.log('Done!'); +} + +/** + * @returns {Promise} + */ +async function benchmarkCannon() { + return new Promise((resolve, reject) => { + const instance = autocannon( + { + url: `http://localhost:${port}`, + connections: 100, + duration: 30, + pipelining: 10, + }, + (err, result) => { + if (err) { + reject(err); + } else { + // @ts-expect-error untyped but documented + instance.stop(); + resolve(result); + } + } + ); + autocannon.track(instance, { renderResultsTable: false }); + }); +} diff --git a/benchmark/ci-helper.js b/benchmark/ci-helper.js new file mode 100644 index 000000000..2dbdf5acf --- /dev/null +++ b/benchmark/ci-helper.js @@ -0,0 +1,13 @@ +// This script helps extract the benchmark logs that are between the `==========` lines. +// They are a convention defined in the `./bench/_template.js` file, which are used to log +// out with the `!bench` command. See `/.github/workflows/benchmark.yml` to see how it's used. +const benchLogs = process.argv[2]; +const resultRegex = /==========(.*?)==========/gs; + +let processedLog = ''; +let m; +while ((m = resultRegex.exec(benchLogs))) { + processedLog += m[1] + '\n'; +} + +console.log(processedLog); diff --git a/benchmark/index.js b/benchmark/index.js new file mode 100755 index 000000000..6ac76759c --- /dev/null +++ b/benchmark/index.js @@ -0,0 +1,79 @@ +import fs from 'fs/promises'; +import path from 'path'; +import { pathToFileURL } from 'url'; +import mri from 'mri'; + +const args = mri(process.argv.slice(2)); + +if (args.help || args.h) { + console.log(`\ +astro-benchmark [options] + +Command + [empty] Run all benchmarks + memory Run build memory and speed test + server-stress Run server stress test + +Options + --project Project to use for benchmark, see benchmark/make-project/ for available names + --output Output file to write results to +`); + process.exit(0); +} + +const commandName = args._[0]; +const benchmarks = { + memory: () => import('./bench/memory.js'), + 'server-stress': () => import('./bench/server-stress.js'), +}; + +if (commandName && !(commandName in benchmarks)) { + console.error(`Invalid benchmark name: ${commandName}`); + process.exit(1); +} + +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); +} else { + // Run all benchmarks + for (const name in benchmarks) { + const bench = benchmarks[name]; + const benchMod = await bench(); + const projectDir = await makeProject(args.project || benchMod.defaultProject); + const outputFile = await getOutputFile(name); + await benchMod.run(projectDir, outputFile); + } +} + +async function makeProject(name) { + console.log('Making project:', name); + const projectDir = new URL(`./projects/${name}/`, import.meta.url); + + const makeProjectMod = await import(`./make-project/${name}.js`); + await makeProjectMod.run(projectDir); + + console.log('Finished making project:', name); + return projectDir; +} + +/** + * @param {string} benchmarkName + */ +async function getOutputFile(benchmarkName) { + let file; + if (args.output) { + file = pathToFileURL(path.resolve(args.output)); + } else { + file = new URL(`./results/${benchmarkName}-bench-${Date.now()}.json`, import.meta.url); + } + + // Prepare output file directory + await fs.mkdir(new URL('./', file), { recursive: true }); + + return file; +} diff --git a/benchmark/make-project/README.md b/benchmark/make-project/README.md new file mode 100644 index 000000000..3199d1b7a --- /dev/null +++ b/benchmark/make-project/README.md @@ -0,0 +1,7 @@ +# make-project + +This `make-project` folder contains different files to programmatically create a new Astro project. They are created inside the `projects` folder and are gitignored. These projects are used by benchmarks for testing. + +Each benchmark can specify the default project to run in its `defaultProject` export, but it can be overriden if `--project ` is passed through the CLI. + +You can duplicate `_template.js` to start a new project script. All shared utilities are kept in `_util.js`. diff --git a/benchmark/make-project/_template.js b/benchmark/make-project/_template.js new file mode 100644 index 000000000..00ebdc473 --- /dev/null +++ b/benchmark/make-project/_template.js @@ -0,0 +1,6 @@ +/** + * Create a new project in the `projectDir` directory. Make sure to clean up the + * previous artifacts here before generating files. + * @param {URL} projectDir + */ +export async function run(projectDir) {} diff --git a/benchmark/make-project/_util.js b/benchmark/make-project/_util.js new file mode 100644 index 000000000..c0e17965b --- /dev/null +++ b/benchmark/make-project/_util.js @@ -0,0 +1,2 @@ +export const loremIpsum = + "Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum. Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum."; diff --git a/benchmark/make-project/memory-default.js b/benchmark/make-project/memory-default.js new file mode 100644 index 000000000..021a42b0f --- /dev/null +++ b/benchmark/make-project/memory-default.js @@ -0,0 +1,59 @@ +import fs from 'fs/promises'; +import { loremIpsum } from './_util.js'; + +/** + * @param {URL} projectDir + */ +export async function run(projectDir) { + await fs.rm(projectDir, { recursive: true, force: true }); + await fs.mkdir(new URL('./src/pages/blog', projectDir), { recursive: true }); + await fs.mkdir(new URL('./src/content/blog', projectDir), { recursive: true }); + + const promises = []; + + for (let i = 0; i < 100; i++) { + const content = `\ +--- +const i = ${i}; +--- + +{i} +`; + promises.push( + fs.writeFile(new URL(`./src/pages/page-${i}.astro`, projectDir), content, 'utf-8') + ); + } + + for (let i = 0; i < 100; i++) { + const content = `\ +# Article ${i} + +${loremIpsum} +`; + promises.push( + fs.writeFile(new URL(`./src/content/blog/article-${i}.md`, projectDir), content, 'utf-8') + ); + } + + await fs.writeFile( + new URL(`./src/pages/blog/[...slug].astro`, projectDir), + `\ +--- +import { getCollection } from 'astro:content'; +export async function getStaticPaths() { + const blogEntries = await getCollection('blog'); + return blogEntries.map(entry => ({ + params: { slug: entry.slug }, props: { entry }, + })); +} +const { entry } = Astro.props; +const { Content } = await entry.render(); +--- +

{entry.data.title}

+ +`, + 'utf-8' + ); + + await Promise.all(promises); +} diff --git a/benchmark/make-project/server-stress-default.js b/benchmark/make-project/server-stress-default.js new file mode 100644 index 000000000..c7ff6b2b4 --- /dev/null +++ b/benchmark/make-project/server-stress-default.js @@ -0,0 +1,47 @@ +import fs from 'fs/promises'; +import { loremIpsum } from './_util.js'; + +/** + * @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.writeFile( + new URL('./src/pages/index.astro', projectDir), + `\ +--- +const content = "${loremIpsum}" +--- + + + + + + + Astro + + +

Astro

+
+ ${Array.from({ length: 60 }).map(() => '

{content}

')} +
+ +`, + 'utf-8' + ); + + await fs.writeFile( + new URL('./astro.config.js', projectDir), + `\ +import { defineConfig } from 'astro/config'; +import nodejs from '@astrojs/node'; + +export default defineConfig({ + output: 'server', + adapter: nodejs({ mode: 'standalone' }), +});`, + 'utf-8' + ); +} diff --git a/benchmark/package.json b/benchmark/package.json new file mode 100644 index 000000000..064cd601f --- /dev/null +++ b/benchmark/package.json @@ -0,0 +1,18 @@ +{ + "name": "astro-benchmark", + "private": true, + "type": "module", + "version": "0.0.0", + "bin": { + "astro-benchmark": "./index.js" + }, + "dependencies": { + "@astrojs/node": "workspace:*", + "astro": "workspace:*", + "autocannon": "^7.10.0", + "execa": "^6.1.0", + "markdown-table": "^3.0.3", + "mri": "^1.2.0", + "port-authority": "^2.0.1" + } +} \ No newline at end of file diff --git a/package.json b/package.json index f119d34d0..7ca43f566 100644 --- a/package.json +++ b/package.json @@ -23,7 +23,7 @@ "test:vite-ci": "turbo run test --filter=astro --output-logs=new-only --no-deps --concurrency=1", "test:e2e": "cd packages/astro && pnpm playwright install && pnpm run test:e2e", "test:e2e:match": "cd packages/astro && pnpm playwright install && pnpm run test:e2e:match", - "benchmark": "pnpm --filter @benchmark/simple run build && pnpm dlx concurrently -k -s first --raw \"node packages/astro/test/benchmark/simple/server.mjs\" \"pnpm dlx autocannon -c 100 -d 30 -p 10 localhost:3002/\"", + "benchmark": "astro-benchmark", "lint": "eslint --cache .", "version": "changeset version && pnpm install --no-frozen-lockfile && pnpm run format", "preinstall": "npx only-allow pnpm" @@ -76,7 +76,8 @@ } }, "dependencies": { - "@astrojs/webapi": "workspace:*" + "@astrojs/webapi": "workspace:*", + "astro-benchmark": "workspace:*" }, "devDependencies": { "@changesets/changelog-github": "0.4.4", diff --git a/packages/astro/src/@types/astro.ts b/packages/astro/src/@types/astro.ts index 868ba7fc2..199250d21 100644 --- a/packages/astro/src/@types/astro.ts +++ b/packages/astro/src/@types/astro.ts @@ -16,6 +16,7 @@ import type { z } from 'zod'; import type { SerializedSSRManifest } from '../core/app/types'; import type { PageBuildData } from '../core/build/types'; import type { AstroConfigSchema } from '../core/config'; +import type { AstroTimer } from '../core/config/timer'; import type { AstroCookies } from '../core/cookies'; import type { AstroComponentFactory, AstroComponentInstance } from '../runtime/server'; import { SUPPORTED_MARKDOWN_FILE_EXTENSIONS } from './../core/constants.js'; @@ -992,6 +993,7 @@ export interface AstroSettings { tsConfigPath: string | undefined; watchFiles: string[]; forceDisableTelemetry: boolean; + timer: AstroTimer; } export type AsyncRendererComponentFn = ( diff --git a/packages/astro/src/core/build/index.ts b/packages/astro/src/core/build/index.ts index 206bed6e4..85a0b6536 100644 --- a/packages/astro/src/core/build/index.ts +++ b/packages/astro/src/core/build/index.ts @@ -167,6 +167,9 @@ class AstroBuilder { buildMode: this.settings.config.output, }); } + + // Benchmark results + this.settings.timer.writeStats(); } /** Build the given Astro project. */ diff --git a/packages/astro/src/core/build/static-build.ts b/packages/astro/src/core/build/static-build.ts index 1a6d3e367..2e39128f5 100644 --- a/packages/astro/src/core/build/static-build.ts +++ b/packages/astro/src/core/build/static-build.ts @@ -33,6 +33,8 @@ export async function staticBuild(opts: StaticBuildOptions) { throw new AstroError(AstroErrorData.NoAdapterInstalled); } + settings.timer.start('SSR build'); + // The pages to be built for rendering purposes. const pageInput = new Set(); @@ -43,10 +45,6 @@ export async function staticBuild(opts: StaticBuildOptions) { // Build internals needed by the CSS plugin const internals = createBuildInternals(); - const timer: Record = {}; - - timer.buildStart = performance.now(); - for (const [component, pageData] of Object.entries(allPages)) { const astroModuleURL = new URL('./' + component, settings.config.root); const astroModuleId = prependForwardSlash(component); @@ -70,10 +68,13 @@ export async function staticBuild(opts: StaticBuildOptions) { registerAllPlugins(container); // Build your project (SSR application code, assets, client JS, etc.) - timer.ssr = performance.now(); + const ssrTime = performance.now(); info(opts.logging, 'build', `Building ${settings.config.output} entrypoints...`); const ssrOutput = await ssrBuild(opts, internals, pageInput, container); - info(opts.logging, 'build', dim(`Completed in ${getTimeStat(timer.ssr, performance.now())}.`)); + info(opts.logging, 'build', dim(`Completed in ${getTimeStat(ssrTime, performance.now())}.`)); + + settings.timer.end('SSR build'); + settings.timer.start('Client build'); const rendererClientEntrypoints = settings.renderers .map((r) => r.clientEntrypoint) @@ -91,23 +92,27 @@ export async function staticBuild(opts: StaticBuildOptions) { } // Run client build first, so the assets can be fed into the SSR rendered version. - timer.clientBuild = performance.now(); const clientOutput = await clientBuild(opts, internals, clientInput, container); - timer.generate = performance.now(); await runPostBuildHooks(container, ssrOutput, clientOutput); + settings.timer.end('Client build'); + switch (settings.config.output) { case 'static': { + settings.timer.start('Static generate'); await generatePages(opts, internals); await cleanServerOutput(opts); + settings.timer.end('Static generate'); return; } case 'server': { + settings.timer.start('Server generate'); await generatePages(opts, internals); await cleanStaticOutput(opts, internals); info(opts.logging, null, `\n${bgMagenta(black(' finalizing server assets '))}\n`); await ssrMoveAssets(opts); + settings.timer.end('Server generate'); return; } } diff --git a/packages/astro/src/core/config/settings.ts b/packages/astro/src/core/config/settings.ts index c9ac9da5e..05e3da1c3 100644 --- a/packages/astro/src/core/config/settings.ts +++ b/packages/astro/src/core/config/settings.ts @@ -5,6 +5,7 @@ import { fileURLToPath, pathToFileURL } from 'url'; import jsxRenderer from '../../jsx/renderer.js'; import { createDefaultDevConfig } from './config.js'; import { loadTSConfig } from './tsconfig.js'; +import { AstroTimer } from './timer.js'; export function createBaseSettings(config: AstroConfig): AstroSettings { return { @@ -19,6 +20,7 @@ export function createBaseSettings(config: AstroConfig): AstroSettings { scripts: [], watchFiles: [], forceDisableTelemetry: false, + timer: new AstroTimer(), }; } diff --git a/packages/astro/src/core/config/timer.ts b/packages/astro/src/core/config/timer.ts new file mode 100644 index 000000000..7360b5510 --- /dev/null +++ b/packages/astro/src/core/config/timer.ts @@ -0,0 +1,65 @@ +import fs from 'fs'; + +// Type used by `bench-memory.js` +export interface Stat { + elapsedTime: number; + heapUsedChange: number; + heapUsedTotal: number; +} + +interface OngoingStat { + startTime: number; + startHeap: number; +} + +/** + * Timer to track certain operations' performance. Used by Astro's scripts only. + * Set `process.env.ASTRO_TIMER_PATH` truthy to enable. + */ +export class AstroTimer { + private enabled: boolean; + private ongoingTimers: Map = new Map(); + private stats: Record = {}; + + constructor() { + this.enabled = !!process.env.ASTRO_TIMER_PATH; + } + + /** + * Start a timer for a scope with a given name. + */ + start(name: string) { + if (!this.enabled) return; + globalThis.gc?.(); + this.ongoingTimers.set(name, { + startTime: performance.now(), + startHeap: process.memoryUsage().heapUsed, + }); + } + + /** + * End a timer for a scope with a given name. + */ + end(name: string) { + if (!this.enabled) return; + const stat = this.ongoingTimers.get(name); + if (!stat) return; + globalThis.gc?.(); + const endHeap = process.memoryUsage().heapUsed; + this.stats[name] = { + elapsedTime: performance.now() - stat.startTime, + heapUsedChange: endHeap - stat.startHeap, + heapUsedTotal: endHeap, + }; + this.ongoingTimers.delete(name); + } + + /** + * Write stats to `process.env.ASTRO_TIMER_PATH` + */ + writeStats() { + if (!this.enabled) return; + // @ts-expect-error + fs.writeFileSync(process.env.ASTRO_TIMER_PATH, JSON.stringify(this.stats, null, 2)); + } +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 825dd93f5..cd498df0f 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -21,6 +21,7 @@ importers: '@types/node': ^18.7.21 '@typescript-eslint/eslint-plugin': ^5.27.1 '@typescript-eslint/parser': ^5.27.1 + astro-benchmark: workspace:* del: ^7.0.0 esbuild: ^0.15.18 eslint: ^8.17.0 @@ -38,6 +39,7 @@ importers: typescript: ~4.7.3 dependencies: '@astrojs/webapi': link:packages/webapi + astro-benchmark: link:benchmark devDependencies: '@changesets/changelog-github': 0.4.4 '@changesets/cli': 2.23.0_kcozqtpxuwjzskw6zg5royevn4 @@ -61,6 +63,24 @@ importers: turbo: 1.2.5 typescript: 4.7.4 + benchmark: + specifiers: + '@astrojs/node': workspace:* + astro: workspace:* + autocannon: ^7.10.0 + execa: ^6.1.0 + markdown-table: ^3.0.3 + mri: ^1.2.0 + port-authority: ^2.0.1 + dependencies: + '@astrojs/node': link:../packages/integrations/node + astro: link:../packages/astro + autocannon: 7.10.0 + execa: 6.1.0 + markdown-table: 3.0.3 + mri: 1.2.0 + port-authority: 2.0.1 + examples/basics: specifiers: astro: ^2.0.16 @@ -3878,6 +3898,10 @@ packages: leven: 3.1.0 dev: false + /@assemblyscript/loader/0.19.23: + resolution: {integrity: sha512-ulkCYfFbYj01ie1MDOyxv2F6SpRN1TOj7fQxbP07D6HmeR+gr2JLSmINKjga2emB+b1L2KGrFKBTc+e00p54nw==} + dev: false + /@astro-community/astro-embed-integration/0.1.2_astro@packages+astro: resolution: {integrity: sha512-ONBDHkOUZ7ssQNzRc5XRZtBBJR0zC68Gm2FCm5w6fxxciDkRkU9Zn9BSssgaNrLPfsXycxFLtQZT3dX9ZPsAxw==} peerDependencies: @@ -5632,6 +5656,13 @@ packages: mime: 3.0.0 dev: true + /@colors/colors/1.5.0: + resolution: {integrity: sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==} + engines: {node: '>=0.1.90'} + requiresBuild: true + dev: false + optional: true + /@csstools/postcss-cascade-layers/1.1.1_postcss@8.4.21: resolution: {integrity: sha512-+KdYrpKC5TgomQr2DlZF4lDEpHcoxnj5IGddYYfBWJAKfj1JtuHUIqMa+E1pJJ+z3kvDViWMqyqPlG4Ja7amQA==} engines: {node: ^12 || ^14 || >=16} @@ -8139,11 +8170,44 @@ packages: resolution: {integrity: sha512-iAB+JbDEGXhyIUavoDl9WP/Jj106Kz9DEn1DPgYw5ruDn0e3Wgi3sKFm55sASdGBNOQB8F59d9qQ7deqrHA8wQ==} dev: false + /asynckit/0.4.0: + resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==} + dev: false + /at-least-node/1.0.0: resolution: {integrity: sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==} engines: {node: '>= 4.0.0'} dev: false + /autocannon/7.10.0: + resolution: {integrity: sha512-PY1UrXL4NHE7J0hA6GGN2r8xjiAePS/bii3Hz7NOvp4JO3xDNBgRftDjfAxj1t6FDWXiXEOuKF/pdDiisIS8ZA==} + hasBin: true + dependencies: + chalk: 4.1.2 + char-spinner: 1.0.1 + cli-table3: 0.6.3 + color-support: 1.1.3 + cross-argv: 2.0.0 + form-data: 4.0.0 + has-async-hooks: 1.0.0 + hdr-histogram-js: 3.0.0 + hdr-histogram-percentiles-obj: 3.0.0 + http-parser-js: 0.5.8 + hyperid: 3.1.1 + lodash.chunk: 4.2.0 + lodash.clonedeep: 4.5.0 + lodash.flatten: 4.4.0 + manage-path: 2.0.0 + on-net-listen: 1.1.2 + pretty-bytes: 5.6.0 + progress: 2.0.3 + reinterval: 1.1.0 + retimer: 3.0.0 + semver: 7.3.8 + subarg: 1.0.0 + timestring: 6.0.0 + dev: false + /autoprefixer/10.4.13_postcss@8.4.21: resolution: {integrity: sha512-49vKpMqcZYsJjwotvt4+h/BCjJVnhGwcLpDt5xkcaOG3eLrG/HUYLagrihYsQ+qrIBgIzX1Rw7a6L8I/ZA1Atg==} engines: {node: ^10 || ^12 || >=14} @@ -8524,6 +8588,10 @@ packages: engines: {node: ^12.17.0 || ^14.13 || >=16.0.0} dev: false + /char-spinner/1.0.1: + resolution: {integrity: sha512-acv43vqJ0+N0rD+Uw3pDHSxP30FHrywu2NO6/wBaHChJIizpDeBUd6NjqhNhy9LGaEAhZAXn46QzmlAvIWd16g==} + dev: false + /character-entities-html4/2.1.0: resolution: {integrity: sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA==} @@ -8622,6 +8690,15 @@ packages: engines: {node: '>=6'} dev: false + /cli-table3/0.6.3: + resolution: {integrity: sha512-w5Jac5SykAeZJKntOxJCrm63Eg5/4dhMWIcuTbo9rpE+brgaSZo0RuNJZeOyMgsUdhDeojvgyQLmjI+K50ZGyg==} + engines: {node: 10.* || >= 12.*} + dependencies: + string-width: 4.2.3 + optionalDependencies: + '@colors/colors': 1.5.0 + dev: false + /cliui/6.0.0: resolution: {integrity: sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==} dependencies: @@ -8696,6 +8773,13 @@ packages: resolution: {integrity: sha512-3tlv/dIP7FWvj3BsbHrGLJ6l/oKh1O3TcgBqMn+yyCagOxc23fyzDS6HypQbgxWbkpDnf52p1LuR4eWDQ/K9WQ==} dev: false + /combined-stream/1.0.8: + resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==} + engines: {node: '>= 0.8'} + dependencies: + delayed-stream: 1.0.0 + dev: false + /comma-separated-tokens/2.0.3: resolution: {integrity: sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==} dev: false @@ -8774,6 +8858,10 @@ packages: resolution: {integrity: sha512-izfGgKyzzIyLaeb1EtZ3KbglkS6AKp9cv7LxmiyoOu+fXfol1tQDC0Cof0enVZGNtudTHW+3lfuW9ZkLQss4Wg==} dev: true + /cross-argv/2.0.0: + resolution: {integrity: sha512-YIaY9TR5Nxeb8SMdtrU8asWVM4jqJDNDYlKV21LxtYcfNJhp1kEsgSa6qXwXgzN0WQWGODps0+TlGp2xQSHwOg==} + dev: false + /cross-spawn/5.1.0: resolution: {integrity: sha512-pTgQJ5KC0d2hcY8eyL1IzlBPYjTkyH72XRZPnLyKus2mBfNjQs3klqbJU2VILqZryAZUt9JOb3h/mWMy23/f5A==} dependencies: @@ -9046,6 +9134,11 @@ packages: slash: 4.0.0 dev: true + /delayed-stream/1.0.0: + resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==} + engines: {node: '>=0.4.0'} + dev: false + /delegates/1.0.0: resolution: {integrity: sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==} dev: false @@ -10030,6 +10123,15 @@ packages: dependencies: is-callable: 1.2.7 + /form-data/4.0.0: + resolution: {integrity: sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==} + engines: {node: '>= 6'} + dependencies: + asynckit: 0.4.0 + combined-stream: 1.0.8 + mime-types: 2.1.35 + dev: false + /format/0.2.2: resolution: {integrity: sha512-wzsgA6WOq+09wrU1tsJ09udeR/YZRaeArL9e1wPbFg3GG2yDnC2ldKpxs4xunpFF9DgqCqOIra3bc1HWrJ37Ww==} engines: {node: '>=0.4.x'} @@ -10376,6 +10478,10 @@ packages: engines: {node: '>=6'} dev: true + /has-async-hooks/1.0.0: + resolution: {integrity: sha512-YF0VPGjkxr7AyyQQNykX8zK4PvtEDsUJAPqwu06UFz1lb6EvI53sPh5H1kWxg8NXI5LsfRCZ8uX9NkYDZBb/mw==} + dev: false + /has-bigints/1.0.2: resolution: {integrity: sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==} @@ -10579,6 +10685,19 @@ packages: space-separated-tokens: 2.0.2 dev: false + /hdr-histogram-js/3.0.0: + resolution: {integrity: sha512-/EpvQI2/Z98mNFYEnlqJ8Ogful8OpArLG/6Tf2bPnkutBVLIeMVNHjk1ZDfshF2BUweipzbk+dB1hgSB7SIakw==} + engines: {node: '>=14'} + dependencies: + '@assemblyscript/loader': 0.19.23 + base64-js: 1.5.1 + pako: 1.0.11 + dev: false + + /hdr-histogram-percentiles-obj/3.0.0: + resolution: {integrity: sha512-7kIufnBqdsBGcSZLPJwqHT3yhk1QTsSlFsVD3kx5ixH/AlgBs9yM1q6DPhXZ8f8gtdqgh7N7/5btRLpQsS2gHw==} + dev: false + /he/1.2.0: resolution: {integrity: sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==} hasBin: true @@ -10629,6 +10748,10 @@ packages: statuses: 2.0.1 toidentifier: 1.0.1 + /http-parser-js/0.5.8: + resolution: {integrity: sha512-SGeBX54F94Wgu5RH3X5jsDtf4eHyRogWX1XGT3b4HuW3tQPM4AaBzoUji/4AAJNXCEOWZ5O0DgZmJw1947gD5Q==} + dev: false + /http-proxy-agent/4.0.1: resolution: {integrity: sha512-k0zdNgqWTGA6aeIRVpvfVob4fL52dTfaehylg0Y4UvSySvOq/Y+BOyPrgpUrA7HylqvU8vIZGsRuXmspskV0Tg==} engines: {node: '>= 6'} @@ -10662,6 +10785,13 @@ packages: resolution: {integrity: sha512-rQLskxnM/5OCldHo+wNXbpVgDn5A17CUoKX+7Sokwaknlq7CdSnphy0W39GU8dw59XiCXmFXDg4fRuckQRKewQ==} engines: {node: '>=12.20.0'} + /hyperid/3.1.1: + resolution: {integrity: sha512-RveV33kIksycSf7HLkq1sHB5wW0OwuX8ot8MYnY++gaaPXGFfKpBncHrAWxdpuEeRlazUMGWefwP1w6o6GaumA==} + dependencies: + uuid: 8.3.2 + uuid-parse: 1.1.0 + dev: false + /iconv-lite/0.4.24: resolution: {integrity: sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==} engines: {node: '>=0.10.0'} @@ -11265,10 +11395,22 @@ packages: dependencies: p-locate: 5.0.0 + /lodash.chunk/4.2.0: + resolution: {integrity: sha512-ZzydJKfUHJwHa+hF5X66zLFCBrWn5GeF28OHEr4WVWtNDXlQ/IjWKPBiikqKo2ne0+v6JgCgJ0GzJp8k8bHC7w==} + dev: false + + /lodash.clonedeep/4.5.0: + resolution: {integrity: sha512-H5ZhCF25riFd9uB5UCkVKo61m3S/xZk1x4wA6yp/L3RFP6Z/eHH1ymQcGLo7J3GMPfm0V/7m1tryHuGVxpqEBQ==} + dev: false + /lodash.debounce/4.0.8: resolution: {integrity: sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==} dev: false + /lodash.flatten/4.4.0: + resolution: {integrity: sha512-C5N2Z3DgnnKr0LOpv/hKCgKdb7ZZwafIrsesve6lmzvZIRZRGaZ/l6Q8+2W7NaT+ZwO3fFlSCzCzrDCFdJfZ4g==} + dev: false + /lodash.merge/4.6.2: resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==} dev: true @@ -11372,6 +11514,10 @@ packages: semver: 6.3.0 dev: false + /manage-path/2.0.0: + resolution: {integrity: sha512-NJhyB+PJYTpxhxZJ3lecIGgh4kwIY2RAh44XvAz9UlqthlQwtPBf62uBVR8XaD8CRuSjQ6TnZH2lNJkbLPZM2A==} + dev: false + /map-obj/1.0.1: resolution: {integrity: sha512-7N/q3lyZ+LVCp7PzuxrJr4KMbBE2hW7BT7YNia330OFxIf4d3r5zVpicP2650l7CPN6RM9zOJRl3NGpqSiw3Eg==} engines: {node: '>=0.10.0'} @@ -11974,14 +12120,12 @@ packages: /mime-db/1.52.0: resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==} engines: {node: '>= 0.6'} - dev: true /mime-types/2.1.35: resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==} engines: {node: '>= 0.6'} dependencies: mime-db: 1.52.0 - dev: true /mime/1.6.0: resolution: {integrity: sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==} @@ -12082,6 +12226,10 @@ packages: /minimist/1.2.7: resolution: {integrity: sha512-bzfL1YUZsP41gmu/qjrEk0Q6i2ix/cVeAhbCbqH9u3zYutS1cLg00qhrD0M2MVdCcx4Sc0UpP2eBWo9rotpq6g==} + /minimist/1.2.8: + resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==} + dev: false + /minipass/3.3.6: resolution: {integrity: sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==} engines: {node: '>=8'} @@ -12381,6 +12529,11 @@ packages: ee-first: 1.1.1 dev: false + /on-net-listen/1.1.2: + resolution: {integrity: sha512-y1HRYy8s/RlcBvDUwKXSmkODMdx4KSuIvloCnQYJ2LdBBC1asY4HtfhXwe3UWknLakATZDnbzht2Ijw3M1EqFg==} + engines: {node: '>=9.4.0 || ^8.9.4'} + dev: false + /once/1.4.0: resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} dependencies: @@ -12553,6 +12706,10 @@ packages: netmask: 2.0.2 dev: true + /pako/1.0.11: + resolution: {integrity: sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==} + dev: false + /parent-module/1.0.1: resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} engines: {node: '>=6'} @@ -12723,6 +12880,10 @@ packages: playwright-core: 1.30.0 dev: true + /port-authority/2.0.1: + resolution: {integrity: sha512-Hz/WvSNt5+7x+Rq1Cn6DetJOZxKtLDehJ1mLCYge6ju4QvSF/PHvRgy94e1SKJVI96AJTcqEdNwkkaAFad+TXQ==} + dev: false + /postcss-attribute-case-insensitive/5.0.2_postcss@8.4.21: resolution: {integrity: sha512-XIidXV8fDr0kKt28vqki84fRK8VW8eTuIa4PChv2MqKuT6C9UjmSKzen6KaWhWEoYvwxFCa7n/tC1SZ3tyq4SQ==} engines: {node: ^12 || ^14 || >=16} @@ -13227,6 +13388,11 @@ packages: engines: {node: '>=6'} dev: false + /progress/2.0.3: + resolution: {integrity: sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==} + engines: {node: '>=0.4.0'} + dev: false + /prompts/2.4.2: resolution: {integrity: sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==} engines: {node: '>= 6'} @@ -13529,6 +13695,10 @@ packages: unified: 10.1.2 dev: false + /reinterval/1.1.0: + resolution: {integrity: sha512-QIRet3SYrGp0HUHO88jVskiG6seqUGC5iAG7AwI/BV4ypGcuqk9Du6YQBUOUqm9c8pw1eyLoIaONifRua1lsEQ==} + dev: false + /remark-code-titles/0.1.2: resolution: {integrity: sha512-KsHQbaI4FX8Ozxqk7YErxwmBiveUqloKuVqyPG2YPLHojpgomodWgRfG4B+bOtmn/5bfJ8khw4rR0lvgVFl2Uw==} dependencies: @@ -13691,6 +13861,10 @@ packages: unified: 10.1.2 dev: false + /retimer/3.0.0: + resolution: {integrity: sha512-WKE0j11Pa0ZJI5YIk0nflGI7SQsfl2ljihVy7ogh7DeQSeYAUi0ubZ/yEueGtDfUPk6GH5LRw1hBdLq4IwUBWA==} + dev: false + /reusify/1.0.4: resolution: {integrity: sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==} engines: {iojs: '>=1.0.0', node: '>=0.10.0'} @@ -14334,6 +14508,12 @@ packages: inline-style-parser: 0.1.1 dev: false + /subarg/1.0.0: + resolution: {integrity: sha512-RIrIdRY0X1xojthNcVtgT9sjpOGagEUKpZdgBUi054OEPFo282yg+zE+t1Rj3+RqKq2xStL7uUHhY+AjbC4BXg==} + dependencies: + minimist: 1.2.8 + dev: false + /suf-log/2.5.3: resolution: {integrity: sha512-KvC8OPjzdNOe+xQ4XWJV2whQA0aM1kGVczMQ8+dStAO6KfEB140JEVQ9dE76ONZ0/Ylf67ni4tILPJB41U0eow==} dependencies: @@ -14513,6 +14693,11 @@ packages: engines: {node: '>=6'} dev: false + /timestring/6.0.0: + resolution: {integrity: sha512-wMctrWD2HZZLuIlchlkE2dfXJh7J2KDI9Dwl+2abPYg0mswQHfOAyQW3jJg1pY5VfttSINZuKcXoB3FGypVklA==} + engines: {node: '>=8'} + dev: false + /tiny-glob/0.2.9: resolution: {integrity: sha512-g/55ssRPUjShh+xkfx9UPDXqhckHEsHr4Vd9zX55oSdGZc/MD0m3sferOkwWtp98bv+kcVfEHtRJgBVJzelrzg==} dependencies: @@ -15087,6 +15272,15 @@ packages: /util-deprecate/1.0.2: resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} + /uuid-parse/1.1.0: + resolution: {integrity: sha512-OdmXxA8rDsQ7YpNVbKSJkNzTw2I+S5WsbMDnCtIWSQaosNAcWtFuI/YK1TjzUI6nbkgiqEyh8gWngfcv8Asd9A==} + dev: false + + /uuid/8.3.2: + resolution: {integrity: sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==} + hasBin: true + dev: false + /uvu/0.5.6: resolution: {integrity: sha512-+g8ENReyr8YsOc6fv/NVJs2vFdHBnBNdfE49rshrTzDWOlUx4Gq7KOS2GD8eqhy2j+Ejq29+SbKH8yjkAqXqoA==} engines: {node: '>=8'} diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index 937aa21ca..fb7deebf6 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -3,3 +3,4 @@ packages: - 'examples/**/*' - 'smoke/**/*' - 'scripts' + - 'benchmark'