diff --git a/.github/workflows/benchmark.yml b/.github/workflows/benchmark.yml index 123abef..de5281e 100644 --- a/.github/workflows/benchmark.yml +++ b/.github/workflows/benchmark.yml @@ -11,38 +11,143 @@ on: permissions: contents: read - id-token: write + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true jobs: - benchmarks: - name: Run benchmarks + build: + name: Build runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 with: submodules: true - name: Setup Deno - uses: denoland/setup-deno@v2 + uses: denoland/setup-deno@667a34cdef165d8d2b2e98dde39547c9daac7282 # v2 with: deno-version: v2.x - - name: Setup Node - uses: actions/setup-node@v4 + - name: Cache WASM + uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4 + id: wasm-cache with: - node-version: 22 + path: | + clayterm.wasm + wasm.ts + key: wasm-${{ hashFiles('Makefile', 'src/**', 'tasks/bundle-wasm.ts') }} - name: Build WASM + if: steps.wasm-cache.outputs.cache-hit != 'true' run: make + - name: Cache dependencies + uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4 + id: deno-cache + with: + path: node_modules + key: deno-${{ hashFiles('deno.lock') }} + - name: Install dependencies + if: steps.deno-cache.outputs.cache-hit != 'true' + run: deno install + + - name: Upload build artifact + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 + with: + name: bench-build + retention-days: 1 + path: wasm.ts + + simulation: + name: Run benchmarks (simulation) + needs: build + runs-on: ubuntu-latest + permissions: + contents: read + id-token: write + + steps: + - name: Checkout + uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 + + - name: Setup Deno + uses: denoland/setup-deno@667a34cdef165d8d2b2e98dde39547c9daac7282 # v2 + with: + deno-version: v2.x + + - name: Setup Node + uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4 + with: + node-version: 24 + + - name: Download build artifact + uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4 + with: + name: bench-build + + - name: Restore dependencies + uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4 + id: deno-cache + with: + path: node_modules + key: deno-${{ hashFiles('deno.lock') }} + + - name: Install dependencies + if: steps.deno-cache.outputs.cache-hit != 'true' run: deno install - name: Run benchmarks - uses: CodSpeedHQ/action@v4 + uses: CodSpeedHQ/action@9d332c4d90b43981c3e55ae8e38e68709996240f # v4 with: mode: simulation # IMPORTANT! deno task bench fails in CI due to incompatible V8 bindings run: node bench/mod.ts + + walltime: + name: Run benchmarks (walltime) + needs: build + runs-on: codspeed-macro + permissions: + contents: read + id-token: write + + steps: + - name: Checkout + uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 + + - name: Setup Deno + uses: denoland/setup-deno@667a34cdef165d8d2b2e98dde39547c9daac7282 # v2 + with: + deno-version: v2.x + + - name: Setup Node + uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4 + with: + node-version: 24 + + - name: Download build artifact + uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4 + with: + name: bench-build + + - name: Restore dependencies + uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4 + id: deno-cache + with: + path: node_modules + key: deno-codspeed-${{ runner.arch }}-${{ hashFiles('deno.lock') }} + + - name: Install dependencies + if: steps.deno-cache.outputs.cache-hit != 'true' + run: deno install + + - name: Run process startup benchmarks + uses: CodSpeedHQ/action@9d332c4d90b43981c3e55ae8e38e68709996240f # v4 + with: + mode: walltime + run: node bench/startup.bench.ts diff --git a/bench/fixtures/create-term/mod.ts b/bench/fixtures/create-term/mod.ts new file mode 100644 index 0000000..b391f21 --- /dev/null +++ b/bench/fixtures/create-term/mod.ts @@ -0,0 +1,3 @@ +import { createTerm } from "../../../term.ts"; + +await createTerm({ width: 80, height: 24 }); diff --git a/bench/fixtures/render-minimal/mod.ts b/bench/fixtures/render-minimal/mod.ts new file mode 100644 index 0000000..13b52bb --- /dev/null +++ b/bench/fixtures/render-minimal/mod.ts @@ -0,0 +1,9 @@ +import { createTerm } from "../../../term.ts"; +import { close, grow, open, text } from "../../../ops.ts"; + +let term = await createTerm({ width: 80, height: 24 }); +term.render([ + open("root", { layout: { width: grow(), height: grow(), direction: "ttb" } }), + text("Hello, World!"), + close(), +]); diff --git a/bench/fixtures/utils.ts b/bench/fixtures/utils.ts new file mode 100644 index 0000000..68bf77d --- /dev/null +++ b/bench/fixtures/utils.ts @@ -0,0 +1,14 @@ +import { fileURLToPath } from "node:url"; +import { spawn } from "node:child_process"; + +export const fixture = (name: string) => { + return new URL(`./${name}/mod.ts`, import.meta.url); +}; + +export const spawnFixture = (name: string): Promise => + new Promise((resolve) => { + spawn(process.execPath, [ + ...process.execArgv, + fileURLToPath(fixture(name)), + ], { stdio: "ignore" }).on("close", resolve); + }); diff --git a/bench/startup.bench.ts b/bench/startup.bench.ts new file mode 100644 index 0000000..5a559d0 --- /dev/null +++ b/bench/startup.bench.ts @@ -0,0 +1,12 @@ +import { Bench } from "tinybench"; +import { withCodSpeed } from "@codspeed/tinybench-plugin"; +import { spawnFixture } from "./fixtures/utils.ts"; + +let bench = withCodSpeed(new Bench({ name: "startup" })); + +bench + .add("createTerm", () => spawnFixture("create-term")) + .add("time to first render", () => spawnFixture("render-minimal")); + +await bench.run(); +console.table(bench.table());