Skip to content

Commit aa8206a

Browse files
merge master into agent/add-tensorlake-storage
Resolve conflicts: adopt master's per-bench script paths and vault secret loading, keeping the Tensorlake matrix entries, scripts, and dependencies. Co-Authored-By: Noah Kiser <noah@computesdk.com>
2 parents 85c95d4 + 5b4de68 commit aa8206a

116 files changed

Lines changed: 9326 additions & 260780 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
Lines changed: 225 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,225 @@
1+
---
2+
name: local-platform-e2e
3+
description: Stand up benchmarks-platform locally (Postgres + MinIO + ClickHouse in docker) and run a real @benchsdk/cli benchmark against it, with no cloud or provider credentials. Use when testing @benchsdk/client / @benchsdk/cli against the platform end to end, or when debugging benchmark reporting, worker planning, artifacts, or dashboard results locally.
4+
---
5+
6+
# Local end-to-end: @benchsdk/cli ↔ benchmarks-platform
7+
8+
Goal: exercise upsert benchmark → create run → planWorkers → claim → heartbeat →
9+
task_results → artifact upload → complete → dashboard, with zero external
10+
credentials.
11+
12+
## 1. Infra (docker)
13+
14+
```bash
15+
docker run -d --name pg -p 5432:5432 -e POSTGRES_PASSWORD=postgres -e POSTGRES_DB=bench postgres:16
16+
docker run -d --name minio -p 9000:9000 -e MINIO_ROOT_USER=minioadmin -e MINIO_ROOT_PASSWORD=minioadmin \
17+
quay.io/minio/minio server /data
18+
docker run -d --name ch -p 8123:8123 -e CLICKHOUSE_PASSWORD=chpass clickhouse/clickhouse-server:25.6
19+
docker run --rm --network host --entrypoint sh quay.io/minio/mc -c \
20+
"mc alias set local http://127.0.0.1:9000 minioadmin minioadmin && mc mb -p local/bench"
21+
```
22+
23+
- Use ClickHouse **>= 25.x**: 24.8 fails `ch:migrate` with
24+
`TTL expression result column should have DateTime or Date type, but has DateTime64(3,'UTC')`.
25+
- The importer passes `clickhouse_settings: { date_time_input_format: 'best_effort' }`
26+
per insert, so a default-configured server works. If you hit
27+
`Cannot parse input: expected '"' before: 'Z"...'` (older code), work around it
28+
server-side, and remember to REMOVE the override before verifying an importer
29+
fix — otherwise the server setting masks it:
30+
```bash
31+
docker exec ch bash -c 'mkdir -p /etc/clickhouse-server/users.d && printf "<clickhouse><profiles><default><date_time_input_format>best_effort</date_time_input_format></default></profiles></clickhouse>" > /etc/clickhouse-server/users.d/besteffort.xml'
32+
docker restart ch
33+
# verify which mode is actually active:
34+
curl -s "http://127.0.0.1:8123/?user=default&password=chpass" \
35+
--data-binary "SELECT value FROM system.settings WHERE name='date_time_input_format'"
36+
```
37+
38+
## 2. benchmarks-platform `.env.local`
39+
40+
Point `TIGRIS_*` at MinIO — the events and artifacts routes require S3 or they
41+
return 502 on every batch. `region: "auto"` + presigned PUT works with MinIO.
42+
43+
```
44+
DATABASE_URL=postgresql://postgres:postgres@127.0.0.1:5432/bench
45+
DATABASE_URL_UNPOOLED=postgresql://postgres:postgres@127.0.0.1:5432/bench
46+
CLICKHOUSE_URL=http://127.0.0.1:8123
47+
CLICKHOUSE_DATABASE=default
48+
CLICKHOUSE_USER=default
49+
CLICKHOUSE_PASSWORD=chpass
50+
ADMIN_API_KEY=local-admin-key
51+
BETTER_AUTH_SECRET=<openssl rand -hex 32>
52+
BETTER_AUTH_URL=http://localhost:3000
53+
TIGRIS_ACCESS_KEY_ID=minioadmin
54+
TIGRIS_SECRET_ACCESS_KEY=minioadmin
55+
TIGRIS_STORAGE_ENDPOINT=http://127.0.0.1:9000
56+
TIGRIS_BUCKET=bench
57+
```
58+
59+
Then:
60+
```bash
61+
npm run db:migrate
62+
CLICKHOUSE_URL=http://127.0.0.1:8123 CLICKHOUSE_USER=default CLICKHOUSE_PASSWORD=chpass npm run ch:migrate
63+
npm run dev
64+
```
65+
`ch:migrate` does **not** read `.env.local`; without exported vars it silently
66+
prints "CLICKHOUSE_URL not set; skipping".
67+
68+
## 3. Mandatory seed (otherwise every run creation 500s on an FK)
69+
70+
`app/api/v1/benchmarks/[slug]/runs/route.ts` hardcodes a default org and user id
71+
for every created run. Insert those exact rows:
72+
73+
```sql
74+
INSERT INTO "user" (id,name,email,email_verified)
75+
VALUES ('mxYI5c90QNkRPhvuc2HvNHy5mM7MG7jG','David Tice','david@example.com',true);
76+
INSERT INTO organization (id,name,slug,created_at,owner_id)
77+
VALUES ('zMrSfAyEyVJ2eKIxgoBZtrMvEd6a78ad','ComputeSDK','computesdk',now(),'mxYI5c90QNkRPhvuc2HvNHy5mM7MG7jG');
78+
INSERT INTO member (id,organization_id,user_id,role,created_at)
79+
VALUES ('mem1','zMrSfAyEyVJ2eKIxgoBZtrMvEd6a78ad','mxYI5c90QNkRPhvuc2HvNHy5mM7MG7jG','owner',now());
80+
```
81+
Re-read those constants before seeding — they may change.
82+
83+
As of the org-scoped-auth change (`lib/api/api-auth.ts` `requireApiAuth`), this seed is
84+
only needed for the **admin-key** path: an org-scoped key supplies the run's
85+
`organizationId` itself and `userId` from the key's `created_by`. Admin-key runs with no
86+
`organizationId` in the body still fall back to those two hardcoded ids.
87+
88+
## 3b. Minting org-scoped `bp_` API keys locally
89+
90+
The org API-key HTTP route is **session**-scoped and answers `{"error":"Unauthenticated"}`
91+
to the admin key, so keys can only be created from the dashboard UI or directly. For
92+
scripted multi-tenant tests, mint them with the platform's own generator so the sha256
93+
hash/prefix/lastFour match what `verifyApiKey()` expects:
94+
95+
```ts
96+
// scripts/e2e-seed-orgs.ts (throwaway), run with:
97+
// npx tsx --env-file=.env.local scripts/e2e-seed-orgs.ts
98+
import { generateApiKey } from "@/lib/api-keys";
99+
import { apiKey, member, organization, user } from "@/db/auth-schema";
100+
const g = generateApiKey(); // g.plaintext is the bp_<prefix>_<secret> to send
101+
await db.insert(apiKey).values({
102+
id, organizationId, name, prefix: g.prefix, hashedKey: g.hashedKey,
103+
lastFour: g.lastFour, createdBy: someUserId, createdAt: new Date(),
104+
revokedAt: null, expiresAt: null, // set these to test revoked/expired → 401
105+
});
106+
```
107+
Each org needs `user` + `organization` (`owner_id`) + `member` rows first. To view an
108+
org's runs in the dashboard, add a `member` row for your dashboard user in that org —
109+
dashboard auth is session-based and completely separate from API keys.
110+
111+
## 4. Dashboard access
112+
113+
Sign up via `POST /api/auth/sign-up/email` (email+password is enabled), then add
114+
a `member` row for that user in the seeded org, and sign in at
115+
`http://localhost:3000/signin`. Run pages live at
116+
`/{orgSlug}/benchmarks/{benchmarkSlug}/runs/{runId}` (+ `/workers`).
117+
118+
## 5. Getting results into ClickHouse locally
119+
120+
`@vercel/queue.send()` fails locally (swallowed as a warning), so nothing
121+
imports automatically. Trigger it by hand — the cron route accepts the admin key
122+
when `CRON_SECRET` is unset:
123+
```bash
124+
curl -s "http://localhost:3000/api/cron/import-clickhouse?limit=50" -H "Authorization: Bearer local-admin-key"
125+
```
126+
Check `imported`/`failed`/`failureSamples` in the response.
127+
128+
## 6. Running a benchmark with no provider credentials
129+
130+
Build first (`packages/*/dist` is not committed): `pnpm install && pnpm -r --filter "./packages/**" build`.
131+
132+
Write a throwaway bench inside the repo (untracked, e.g. `e2e-local/local.bench.ts`)
133+
so pnpm workspace resolution finds `@benchsdk/cli`, with a fake participant:
134+
135+
```ts
136+
import { defineBenchmark, runBenchmark } from '@benchsdk/cli';
137+
const config = defineBenchmark({
138+
benchmarkSlug: 'e2e-local', benchmarkName: 'E2E', iterations: 4, concurrency: 1,
139+
task: async (ctx) => { await ctx.step('create', () => new Promise(r => setTimeout(r, 50))); },
140+
});
141+
runBenchmark(config, [{ name: 'local', requiredEnvVars: [] } as any], process.argv.slice(2));
142+
```
143+
144+
Run it:
145+
```bash
146+
BENCHMARKS_PLATFORM_URL=http://localhost:3000 COMPUTESDK_ADMIN_API_KEY=local-admin-key \
147+
npx tsx e2e-local/local.bench.ts --iterations 4 --concurrency 2
148+
```
149+
`BENCHMARKS_PLATFORM_URL` is the **root** URL (the runner appends `/api/v1`).
150+
151+
The runner reads `COMPUTESDK_ADMIN_API_KEY ?? COMPUTESDK_API_KEY`, so to prove the master key
152+
is not needed, pass an org `bp_` key as `COMPUTESDK_API_KEY` and strip the admin vars from the
153+
child env (`env -u COMPUTESDK_ADMIN_API_KEY -u ADMIN_API_KEY …`). Note the "View at:" URL the
154+
CLI prints uses `BENCHMARKS_PLATFORM_ORG_SLUG` (default `computesdk`), **not** the org that owns
155+
the key, so with an org key the printed link may 404 — set that env var to the key's org slug.
156+
157+
## 6b. Probing tenant isolation
158+
159+
With benchmarks-platform#47, all `app/api/v1/benchmarks/**` routes take either the global
160+
`ADMIN_API_KEY` or an org `bp_` key. Expected shapes when a key from org B addresses org A's run:
161+
`403 {"error":"API key is not authorized for this organization"}` (`getScopedRun` /
162+
`requireRunAccess`), `404` for an unknown run/benchmark, `401 Invalid API key` for
163+
revoked/expired/malformed tokens, `401 API key required` with no header. Listings
164+
(`GET /benchmarks/:slug/runs` and `/results`) narrow by `organizationScope(auth)`.
165+
166+
When probing the **heartbeat** in-process cache, the body must carry `currentStep`, all four
167+
`progressDone/InFlight/Errors/Total` fields **and** `concurrency` (top-level, not nested)
168+
or the cache is never populated and your "cache poisoning" probe proves nothing. A
169+
cache-served beat is recognisable because the response's `worker` object is minimal (no
170+
`status`/`benchmarkId` columns). Route timing metadata (`cacheCoalesced`) is only logged
171+
for requests slower than 1000 ms, so don't rely on the dev-server log for this.
172+
173+
## 7. Useful probes when testing lifecycle behaviour
174+
175+
- `psql` is not installed on the host — use `docker exec pg psql -U postgres -d bench ...`.
176+
- To catch transient run statuses (`planned``in_progress``completed`), poll
177+
in a background subshell **while** the bench runs, then `sort -u` the samples:
178+
```bash
179+
(for i in $(seq 1 60); do docker exec pg psql -U postgres -d bench -tA -c \
180+
"select r.status||'|'||p.status||'|'||w.status from ..."; sleep 0.4; done > /tmp/poll.txt) &
181+
```
182+
Note `exec`'s `&` backgrounds the whole `cd X && ...` chain — pass `workdir`
183+
instead of a leading `cd`, or the foreground command runs in the wrong dir.
184+
- To force a failed run, make the harness task throw for one taskIndex; the CLI
185+
calls `failWorker` when any task fails (runner.ts), which is what drives the
186+
run to `failed`.
187+
- Worker release / re-claim and oversized event batches are easiest to drive with
188+
raw curl/python against the v1 API using the admin key; the events body needs
189+
`{type:'task_results', sequenceNumber, isFinal, attemptId, records:[...]}`.
190+
191+
## 8. Known sharp edges (verify before blaming your setup)
192+
193+
- `--group-by round`: `targetConcurrency` is read by the platform as
194+
tasks-per-worker, so the runner must send the full `schedule.length`. If it
195+
sends 1, only one task is planned (worker range 0-0) while every record is
196+
still accepted — results look right but progress/ranges do not.
197+
- `benchmark_run_executions.status` should roll up from worker status
198+
(`in_progress` on first claim, `completed`/`failed` on the last worker). If a
199+
finished run still reads `planned`, the rollup is broken — the dashboard badge
200+
derives status separately and will hide it, so check Postgres and
201+
`/progress.run.status` vs `/progress.summary.status`, which must agree.
202+
- Barriers: custom step names work off the `concurrency` samples the heartbeat
203+
sends, but `worker.ready` and `sandbox.live` are derived from the worker's
204+
`progress_in_flight` column — for those you must call
205+
`reporter.setProgress({ inFlight: N, ... })` first or the barrier sees
206+
`active=0` and hangs until timeout.
207+
- `client.releaseWorker()` → 200 puts the worker back to `pending` and the
208+
attempt to `released`; a re-claim then gets `attemptNumber: 2`. A duplicate-key
209+
error on `unique(worker_id, attempt_number)` means the claim route hardcoded
210+
attempt number 1.
211+
- Body limits: the events route allows 32 MiB (`MAX_EVENT_BODY_BYTES`), all other
212+
v1 routes 1 MiB. 5,000 records × 3 steps ≈ 3.15 MiB, so full-size SDK batches
213+
should be 202. If you see 413 on events, the raised cap is not wired up. Always
214+
check `benchmark_event_batches.status = 'persisted'` too — a 202 only means the
215+
body was accepted.
216+
- Org-scoped `bp_…` keys on benchmark v1 routes arrived with
217+
benchmarks-platform#47 (see §3b/§6b). On a platform checkout without it those
218+
routes are admin-only and every `bp_…` key 401s there, even though the same key
219+
works on `/api/v1/organizations/...` — check which behaviour your checkout has
220+
before debugging a 401.
221+
222+
## Devin Secrets Needed
223+
224+
None. This entire flow runs offline with local docker containers and a
225+
self-chosen `ADMIN_API_KEY`.

.changeset/README.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
# Changesets
2+
3+
Hello and welcome! This folder has been automatically generated by `@changesets/cli`, a build tool that works
4+
with multi-package repos, or single-package repos to help you version and publish your code. You can
5+
find the full documentation for it [in our repository](https://github.com/changesets/changesets)
6+
7+
We have a quick list of common questions to get you started engaging with this project in
8+
[our documentation](https://github.com/changesets/changesets/blob/main/docs/common-questions.md)

.changeset/benchsdk-cli-initial.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@benchsdk/cli": patch
3+
---
4+
5+
Initial publication of `@benchsdk/cli`, the benchmark runner framework. Provides `LogBuffer`, `loggedStep`, `uploadWorkerLog`, and `defineBenchmark` for building self-contained benchmark scripts that report to the benchmarks platform via `@benchsdk/client`.
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@benchsdk/cli": patch
3+
---
4+
5+
`runBenchmark()` now rejects with the exported `NoAvailableParticipantsError` (carrying the `skipped` participants and their missing env vars) instead of a plain `Error` when every participant is env-gated out, so callers can treat an unprovisioned provider as a skip rather than a failure.
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@benchsdk/client": minor
3+
---
4+
5+
Add participant helpers to the public API: the `BaseParticipant` type plus `selectParticipants()` (filter by `--provider` names) and `filterParticipantsByEnv()` (split participants by whether their `requiredEnvVars` are set).

.changeset/config.json

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
{
2+
"$schema": "https://unpkg.com/@changesets/config@3.1.4/schema.json",
3+
"changelog": "@changesets/cli/changelog",
4+
"commit": false,
5+
"fixed": [],
6+
"linked": [],
7+
"access": "public",
8+
"baseBranch": "master",
9+
"updateInternalDependencies": "patch",
10+
"ignore": []
11+
}

.github/actionlint.yaml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
self-hosted-runner:
2+
labels:
3+
- namespace-profile-default
4+
- namespace-profile-default;permissions.additional_grant=vault/object:*:list;permissions.additional_grant=vault/object:*:describe

0 commit comments

Comments
 (0)