Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
68 changes: 68 additions & 0 deletions .github/workflows/memory-benchmarks.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
name: Memory Benchmarks

on:
push:
branches:
- main
paths:
- 'packages/**'
- 'benchmarks/**'
- '.github/workflows/memory-benchmarks.yml'
pull_request:
paths:
- 'packages/**'
- 'benchmarks/**'
- '.github/workflows/memory-benchmarks.yml'
workflow_dispatch:

concurrency:
group: ${{ github.workflow }}-${{ github.event.number || github.ref }}
cancel-in-progress: true

permissions:
contents: write

env:
NX_NO_CLOUD: true
MEMORY_BENCH_ITERATIONS: 2000
MEMORY_BENCH_WARMUP_ITERATIONS: 200
MEMORY_BENCH_BATCH_SIZE: 100

jobs:
benchmarks:
name: Run Memory Benchmarks
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
fetch-depth: 0
persist-credentials: false

- name: Setup Tools
uses: TanStack/config/.github/setup@e4b48f16568324f76f467aa4c2aac2f05db632c3 # main

- name: Install Chromium
run: pnpm exec playwright install chromium

- name: Run React Memory Benchmarks
run: CI=1 NX_DAEMON=false pnpm nx run @benchmarks/memory:test:memory:react --outputStyle=stream --skipRemoteCache --parallel=2

- name: Build Benchmark Report
run: CI=1 NX_DAEMON=false pnpm nx run @benchmarks/memory:report --outputStyle=stream --skipRemoteCache

- name: Publish Benchmark Dashboard
if: github.event_name == 'push' && github.ref == 'refs/heads/main' && github.repository_owner == 'TanStack'
uses: benchmark-action/github-action-benchmark@52576c92bccf6ac60c8223ec7eb2565637cae9ba # v1.22.1
with:
tool: customSmallerIsBetter
name: Memory Usage (retained heap)
output-file-path: benchmarks/memory/results/benchmark-action.json
github-token: ${{ secrets.GITHUB_TOKEN }}
auto-push: true
gh-pages-branch: gh-pages
benchmark-data-dir-path: benchmarks/memory
max-items-in-chart: 200
summary-always: true
comment-on-alert: false
fail-on-alert: false
1 change: 1 addition & 0 deletions benchmarks/memory/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
scenarios/**/dist
62 changes: 62 additions & 0 deletions benchmarks/memory/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
# Memory Benchmarks

Memory benchmarks are organized as Nx leaf targets. Each leaf target runs one scenario, prints the result to stdout, and writes one isolated result artifact under `results/scenarios/`.

## Run

Run all memory benchmarks:

```bash
CI=1 NX_DAEMON=false pnpm nx run @benchmarks/memory:test:memory --outputStyle=stream --skipRemoteCache --parallel=2
```

Run React scenarios:

```bash
CI=1 NX_DAEMON=false pnpm nx run @benchmarks/memory:test:memory:react --outputStyle=stream --skipRemoteCache --parallel=2
```

Run one scenario:

```bash
CI=1 NX_DAEMON=false pnpm nx run @benchmarks/memory:test:memory:react:client:repeated-navigation --outputStyle=stream --skipRemoteCache
CI=1 NX_DAEMON=false pnpm nx run @benchmarks/memory:test:memory:react:ssr:repeated-requests --outputStyle=stream --skipRemoteCache
```

Tune local run sizes with environment variables:

```bash
MEMORY_BENCH_ITERATIONS=10000 MEMORY_BENCH_WARMUP_ITERATIONS=1000 pnpm nx run @benchmarks/memory:test:memory:react:ssr:repeated-requests
```

The SSR benchmark also waits for pending macrotasks before retained-heap GC
sampling. Tune this with `MEMORY_BENCH_SETTLE_TICKS` when investigating runtime
scheduler behavior.

## Report

After leaf targets have run, merge isolated scenario artifacts into benchmark-action compatible outputs:

```bash
pnpm nx run @benchmarks/memory:report
```

This writes:

- `benchmarks/memory/results/current.json`
- `benchmarks/memory/results/benchmark-action.json`

## CI

`.github/workflows/memory-benchmarks.yml` runs the React memory benchmark group with Nx parallelism, builds the report, and publishes the main-branch history to GitHub Pages via `benchmark-action/github-action-benchmark`.

The workflow uses a larger run size than quick local smoke tests:

```bash
MEMORY_BENCH_ITERATIONS=2000 MEMORY_BENCH_WARMUP_ITERATIONS=200 MEMORY_BENCH_BATCH_SIZE=100
```

## Initial Scenarios

- `react.client.repeated-navigation`: React Router in Chromium via Playwright.
- `react.ssr.repeated-requests`: React Start SSR in Node.
124 changes: 124 additions & 0 deletions benchmarks/memory/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
{
"name": "@benchmarks/memory",
"private": true,
"type": "module",
"scripts": {
"build:react:client:repeated-navigation": "NODE_ENV=production vite build --config ./scenarios/react/client/repeated-navigation/vite.config.ts",
"build:react:ssr:repeated-requests": "NODE_ENV=production vite build --config ./scenarios/react/ssr/repeated-requests/vite.config.ts",
"test:memory": "node ./tools/summary.ts",
"test:memory:react": "node ./tools/summary.ts --scope react",
"test:memory:react:client": "node ./tools/summary.ts --scope react.client",
"test:memory:react:ssr": "node ./tools/summary.ts --scope react.ssr",
"test:memory:react:client:repeated-navigation": "NODE_ENV=production playwright test --config ./scenarios/react/client/repeated-navigation/playwright.config.ts",
"test:memory:react:ssr:repeated-requests": "NODE_ENV=production node --expose-gc ./scenarios/react/ssr/repeated-requests/bench.ts",
"report": "node ./tools/report.ts",
"test:types": "pnpm run test:types:tools && pnpm run test:types:react:client:repeated-navigation && pnpm run test:types:react:ssr:repeated-requests",
"test:types:tools": "tsc -p ./tools/tsconfig.json --noEmit",
"test:types:react:client:repeated-navigation": "tsc -p ./scenarios/react/client/repeated-navigation/tsconfig.json --noEmit",
"test:types:react:ssr:repeated-requests": "tsc -p ./scenarios/react/ssr/repeated-requests/tsconfig.json --noEmit"
},
"dependencies": {
"@tanstack/react-router": "workspace:*",
"@tanstack/react-start": "workspace:*",
"react": "^19.2.3",
"react-dom": "^19.2.3"
},
"devDependencies": {
"@playwright/test": "^1.57.0",
"@types/node": "25.0.9",
"@types/react": "^19.2.8",
"@types/react-dom": "^19.2.3",
"@vitejs/plugin-react": "^6.0.1",
"typescript": "^6.0.2",
"vite": "^8.0.14"
},
"nx": {
"targets": {
"build:react:client:repeated-navigation": {
"cache": false,
"dependsOn": [
{
"projects": [
"@tanstack/react-router"
],
"target": "build"
}
],
"outputs": [
"{projectRoot}/scenarios/react/client/repeated-navigation/dist"
]
},
"build:react:ssr:repeated-requests": {
"cache": false,
"dependsOn": [
{
"projects": [
"@tanstack/react-start"
],
"target": "build"
}
],
"outputs": [
"{projectRoot}/scenarios/react/ssr/repeated-requests/dist"
]
},
"test:memory": {
"cache": false,
"dependsOn": [
"test:memory:react:client:repeated-navigation",
"test:memory:react:ssr:repeated-requests"
]
},
"test:memory:react": {
"cache": false,
"dependsOn": [
"test:memory:react:client:repeated-navigation",
"test:memory:react:ssr:repeated-requests"
]
},
"test:memory:react:client": {
"cache": false,
"dependsOn": [
"test:memory:react:client:repeated-navigation"
]
},
"test:memory:react:ssr": {
"cache": false,
"dependsOn": [
"test:memory:react:ssr:repeated-requests"
]
},
"test:memory:react:client:repeated-navigation": {
"cache": false,
"dependsOn": [
"build:react:client:repeated-navigation"
],
"outputs": [
"{projectRoot}/results/scenarios/react.client.repeated-navigation.json"
]
},
"test:memory:react:ssr:repeated-requests": {
"cache": false,
"dependsOn": [
"build:react:ssr:repeated-requests"
],
"outputs": [
"{projectRoot}/results/scenarios/react.ssr.repeated-requests.json"
]
},
"report": {
"cache": false,
"outputs": [
"{projectRoot}/results/current.json",
"{projectRoot}/results/benchmark-action.json"
]
},
"test:types": {
"cache": false,
"dependsOn": [
"^build"
]
}
}
}
}
5 changes: 5 additions & 0 deletions benchmarks/memory/results/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
*
!.gitignore
!scenarios/
scenarios/*
!scenarios/.gitkeep
Empty file.
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>React Router Memory Benchmark</title>
</head>
<body>
<div id="app"></div>
<script type="module" src="/src/main.tsx"></script>
</body>
</html>
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { defineConfig, devices } from '@playwright/test'

const port = Number.parseInt(
process.env.MEMORY_BENCH_REACT_CLIENT_PORT ?? '42101',
10,
)
const baseURL = `http://127.0.0.1:${port}`

export default defineConfig({
testDir: './tests',
timeout: 120_000,
workers: 1,
reporter: [['line']],
use: {
baseURL,
...devices['Desktop Chrome'],
},
webServer: {
command: `pnpm exec vite preview --host 127.0.0.1 --port ${port} --strictPort`,
url: baseURL,
reuseExistingServer: false,
stdout: 'pipe',
env: {
NODE_ENV: 'production',
},
},
projects: [
{
name: 'chromium',
use: {
...devices['Desktop Chrome'],
},
},
],
})
Loading
Loading