diff --git a/.github/workflows/benchmark.yml b/.github/workflows/benchmark.yml index cb7fc1e..cca1753 100644 --- a/.github/workflows/benchmark.yml +++ b/.github/workflows/benchmark.yml @@ -16,12 +16,11 @@ concurrency: cancel-in-progress: true jobs: - benchmarks: - name: Run benchmarks + build: + name: Build runs-on: ubuntu-latest permissions: contents: read # clone repo - id-token: write # upload benchmark results to codspeed steps: - name: Checkout uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 @@ -34,15 +33,73 @@ jobs: with: deno-version: v2.x - - name: Setup Node - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 + - name: Cache WASM + uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0 + 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.3.0 + 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.6.2 + with: + name: bench-build + retention-days: 1 + path: wasm.ts + + simulation: + name: Run benchmarks (simulation) + needs: build + runs-on: ubuntu-latest + permissions: + contents: read # clone repo + id-token: write # upload benchmark results to codspeed + + steps: + - name: Checkout + uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1 + + - name: Setup Deno + uses: denoland/setup-deno@667a34cdef165d8d2b2e98dde39547c9daac7282 # v2.0.4 + with: + deno-version: v2.x + + - name: Setup Node + uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 + with: + node-version: 24 + + - name: Download build artifact + uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0 + with: + name: bench-build + + - name: Restore dependencies + uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0 + 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 @@ -51,3 +108,47 @@ jobs: 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 # clone repo + id-token: write # upload benchmark results to codspeed + + steps: + - name: Checkout + uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1 + + - name: Setup Deno + uses: denoland/setup-deno@667a34cdef165d8d2b2e98dde39547c9daac7282 # v2.0.4 + with: + deno-version: v2.x + + - name: Setup Node + uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0 + with: + node-version: 24 + + - name: Download build artifact + uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0 + with: + name: bench-build + + - name: Restore dependencies + uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0 + 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.17.0 + 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());