Skip to content
Draft
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
42 changes: 42 additions & 0 deletions .github/workflows/perf-nightly.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
name: Perf nightly

# The nightly regression run (WEB-SCENARIOS.md §7): the nightly set against the live test app, on
# the dedicated perf machine only. That machine holds chatperf (CHATPERF_DIR), Ruby, and the test
# app's credentials in its own environment (~/.config/chatperf/env); none of them are GitHub
# secrets. Results accumulate in PERF_RESULTS_DIR outside the checkout, and `perf compare` fails
# the job when a row is more than 15 % worse than its 7-day median.

on:
schedule:
- cron: '0 2 * * *'
workflow_dispatch:

permissions:
contents: read

concurrency:
group: perf-nightly
cancel-in-progress: false

jobs:
nightly:
runs-on: [self-hosted, perf-nightly]
timeout-minutes: 240
env:
PERF_RESULTS_DIR: ${{ vars.PERF_RESULTS_DIR }}
CHATPERF_DIR: ${{ vars.CHATPERF_DIR }}
steps:
- uses: actions/checkout@v6

- uses: ./.github/actions/setup-node

- run: yarn build

- name: Nightly set
shell: bash
run: |
set -a; source ~/.config/chatperf/env; set +a
yarn workspace @stream-io/react-perf-runner perf run --set nightly -n 5

- name: Compare with the last 7 days
run: yarn workspace @stream-io/react-perf-runner perf compare
55 changes: 55 additions & 0 deletions .github/workflows/perf.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
name: Perf

# The web performance harness's PR checks (perf/README.md). Neither job talks to a Stream app or
# needs a secret, so both also run for pull requests from forks:
# - contract: the runner's scenario files against the pinned chatperf registry
# - renders: M-RENDERS replayed from recorded fixtures against their recorded backend; fails if
# untargeted rows per commit rise above the baseline committed from master

on:
pull_request:
types:
- opened
- reopened
- synchronize
paths:
- 'src/**'
- 'perf/**'
- 'package.json'
- 'yarn.lock'
- '.github/workflows/perf.yml'

permissions:
contents: read

jobs:
contract:
runs-on: ubuntu-latest
name: Scenario contract
steps:
- uses: actions/checkout@v6

- uses: ./.github/actions/setup-node

- run: yarn workspace @stream-io/react-perf-runner test

renders:
runs-on: ubuntu-latest
name: Render gate (ML-09, ML-11, CL-07, ML-08)
steps:
- uses: actions/checkout@v6

- uses: ./.github/actions/setup-node

- name: Build the SDK (the perf host consumes dist/)
run: yarn build

- name: Install Chromium
run: yarn workspace @stream-io/react-perf-runner exec playwright install --with-deps chromium

- name: Replay the fixtures
run: |
for id in ml-09 ml-11 cl-07 ml-08; do
yarn workspace @stream-io/react-perf-runner perf renders "$id" \
--replay --offline --build profiling --baseline check
done
5 changes: 5 additions & 0 deletions .prettierignore
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,8 @@ examples/*/next-env.d.ts
.agents/
.claude/skills/
skills-lock.json

# perf harness data: recorded event fixtures and run results (generated, not hand-edited)
perf/runner/fixtures/
perf/runner/contract/
perf/runner/results/
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -272,6 +272,7 @@
},
"packageManager": "yarn@4.15.0",
"workspaces": [
"examples/*"
"examples/*",
"perf/*"
]
}
5 changes: 5 additions & 0 deletions perf/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# Build output of the host's profiling mode (dist/ is ignored at the repo root).
dist-profiling/

# Runner output; fixtures/ is kept.
runner/results/
78 changes: 78 additions & 0 deletions perf/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
# perf

Web performance harness for `stream-chat-react`. Private workspaces, never published (the SDK
publishes `dist/` only).

The plan and protocol live in `stream-chat-perf-cli` (`WEB-PLAN.md`, `WEB-SCENARIOS.md`). The
backend state comes from that repo's `chatperf` CLI.

| Package | Path | What it is |
| ------------------------------ | --------- | ------------------------------------------------------------------------------- |
| `@stream-io/react-perf-host` | `host/` | A minimal chat page using the SDK under test, configured entirely by URL params |
| `@stream-io/react-perf-runner` | `runner/` | Seeds through `chatperf`, drives the host in Chromium, and writes result rows |

The names deliberately don't match `@stream-io/stream-chat-react-*`, which `yarn examples:build`
(and CI) builds.

## Host

```bash
yarn build # the SDK, once (the host consumes dist/)
yarn workspace @stream-io/react-perf-host dev # dev: http://localhost:5173
yarn workspace @stream-io/react-perf-host build && yarn workspace @stream-io/react-perf-host preview
# prod: http://localhost:4173
yarn workspace @stream-io/react-perf-host build:profiling && yarn workspace @stream-io/react-perf-host preview:profiling
# profiling (render counts only): http://localhost:4174
```

Open it with `?api_key=…&token=…&user_id=qatest1&list=standard|virtualized&type=messaging|livestream`
(`chatperf token qatest1` prints the token). Optional: `cap=<n>|none` (exploration only; the
protocol's cap follows `type`) and `page_size=<n>` (default 30).

## Runner

Needs `chatperf` (its checkout next to this repo, or `CHATPERF_DIR`), with `STREAM_KEY` /
`STREAM_SECRET` in the environment. Node runs the TypeScript directly; there is no build step.

```bash
perf() { yarn workspace @stream-io/react-perf-runner perf "$@"; }
perf run cl-10 [--build dev|prod] [--column chrome-desktop|chrome-4x] [-n 5]
perf run ml-08 --type messaging|livestream [--list standard|virtualized] [--no-seed] [--no-verify]
perf run --set nightly|signoff|channel-list|message-list|leaks|desktop
perf renders ml-09 --record|--replay [--build profiling|dev] [--offline] [--baseline write|check]
perf check ml-08-oldest [--seed]
perf contract [--update] # scenario files vs the pinned chatperf registry
perf compare # last 12 h of rows vs their 7-day median (the nightly check)
perf report # results/report-<date>.md from rows.csv
yarn workspace @stream-io/react-perf-runner test # contract + compare unit tests
```

- `run` seeds the scenario's dataset (unless `--no-seed`), verifies it, starts the host, runs N
iterations (the first is dropped), and appends one row per grid cell to `results/rows.csv`, with
every iteration in `results/run-*.json`. Iterations that fail a gate are discarded with their
reasons; if none is left, the evidence is kept in `results/run-*-discarded.json`. Memory
scenarios also write `results/mem-*-chart.html`.
- Live scenarios (ML-08 … ML-12, CL-06 … CL-09, TH-02, CP-01, SK-01, WB-01) mutate their dataset. A
batch is one seed and one verify: run later scenarios on the same seed with
`--no-seed --no-verify`, and ML-10 last (its deletes leave the newest page as placeholders).
Their `chatperf live` commands are data, in `src/scenarios/live-specs.ts`.
- `renders --offline` replays a fixture against the backend recorded with it (`src/world.ts`): no
Stream app, no credentials, no Ruby. It is what the PR job (`.github/workflows/perf.yml`) runs;
`--baseline check` fails when untargeted rows per commit exceed `fixtures/baseline.json`.
- Runs are headed (Chromium windows open) unless `--headless`; frame numbers depend on the
display's refresh rate. WB-01 launches its own Chrome over raw CDP (Playwright keeps attached
pages visible), so a second window opens for it.
- `PERF_RESULTS_DIR` moves results out of the checkout (the nightly machine keeps its history
there).

Validation switches (deliberate failures, to prove a gate or metric works; every row says so):

| Variable | Effect |
| ------------------------------------------------------ | ------------------------------------------------------------------ |
| `PERF_HOST_PARAMS='leak=messages'` / `'leak=detached'` | a deliberate leak in the host (M-MEM must show it) |
| `PERF_HOST_PARAMS='drop_events=<types>'` | the client never sees those events (e.g. `rows-changed` must fail) |
| `PERF_GATE_CHECK_OFFLINE=1` | ML-08 goes offline mid-window (`live-delivered` must fail) |
| `PERF_GATE_CHECK_ONLINE=1` | OF-02 skips going offline (`went-offline` must fail) |
| `PERF_GATE_CHECK_STALE=1` | no per-iteration re-seed (ML-12's `receipts-changed` must fail) |
| `PERF_GATE_CHECK_KIND=<kind>` | ML-07 looks for that kind with a selector that can't match |
| `PERF_AUTOMATION_CHECK=1` | CL-11 polls the page during the window (M-FRAMES must get worse) |
12 changes: 12 additions & 0 deletions perf/host/index.html
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>Stream Chat React — perf host</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/main.tsx"></script>
</body>
</html>
29 changes: 29 additions & 0 deletions perf/host/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
{
"name": "@stream-io/react-perf-host",
"private": true,
"version": "0.0.0",
"type": "module",
"installConfig": {
"hoistingLimits": "workspaces"
},
"scripts": {
"dev": "vite --port 5173 --strictPort",
"build": "tsc -b && vite build",
"build:profiling": "tsc -b && vite build --mode profiling --outDir dist-profiling",
"preview": "vite preview --port 4173 --strictPort",
"preview:profiling": "vite preview --port 4174 --strictPort --outDir dist-profiling"
},
"dependencies": {
"react": "^19.2.6",
"react-dom": "^19.2.6",
"stream-chat": "^10.0.0-rc.22",
"stream-chat-react": "workspace:^"
},
"devDependencies": {
"@types/react": "^19.2.15",
"@types/react-dom": "^19.2.3",
"@vitejs/plugin-react": "^6.0.3",
"typescript": "^6.0.3",
"vite": "^8.1.3"
}
}
Loading
Loading