@benchsdk/client 0.2.1 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +49 -98
- package/dist/index.cjs +17 -962
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +7 -723
- package/dist/index.d.ts +7 -723
- package/dist/index.js +17 -943
- package/dist/index.js.map +1 -1
- package/package.json +18 -16
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 ComputeSDK
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -10,89 +10,69 @@ This package talks to the platform-owned benchmark/run/participant/worker API. I
|
|
|
10
10
|
npm install @benchsdk/client
|
|
11
11
|
```
|
|
12
12
|
|
|
13
|
-
|
|
13
|
+
> Higher-level benchmark authoring (`defineBenchmarkConfig` / `defineTask` and
|
|
14
|
+
> the local orchestrator) lives in
|
|
15
|
+
> [`@benchsdk/runner`](../benchsdk-runner). This package is REST transport plus
|
|
16
|
+
> the worker engine only.
|
|
17
|
+
|
|
18
|
+
## Authentication
|
|
19
|
+
|
|
20
|
+
`createBenchmarkClient` requires a platform API key or OAuth token. Provide
|
|
21
|
+
`apiKey`/`token` directly, or set `BENCHMARKS_PLATFORM_API_KEY` or
|
|
22
|
+
`BENCHMARKS_PLATFORM_TOKEN` in the environment.
|
|
23
|
+
|
|
24
|
+
## Run A Worker
|
|
14
25
|
|
|
15
26
|
```ts
|
|
16
|
-
import {
|
|
27
|
+
import { createBenchmarkClient } from '@benchsdk/client';
|
|
17
28
|
import { compute } from 'computesdk';
|
|
18
29
|
|
|
19
|
-
const
|
|
30
|
+
const client = createBenchmarkClient({
|
|
31
|
+
apiKey: process.env.BENCHMARKS_PLATFORM_API_KEY,
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
// `task` is a raw function; declare named steps imperatively via `step(...)`,
|
|
35
|
+
// so values flow between steps with closures and cleanup runs in a `finally`.
|
|
36
|
+
const { assignment, records } = await client.runWorker({
|
|
20
37
|
benchmarkSlug: 'scale',
|
|
21
38
|
runId: process.env.BENCHMARK_RUN_ID!,
|
|
22
39
|
participantSlug: 'e2b',
|
|
23
40
|
processKind: 'container',
|
|
24
41
|
processKey: process.env.HOSTNAME,
|
|
25
42
|
concurrency: 100,
|
|
26
|
-
task:
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
}),
|
|
42
|
-
defineStep('destroy', async ({ state }) => {
|
|
43
|
-
await (state.sandbox as any).destroy();
|
|
44
|
-
}),
|
|
45
|
-
]),
|
|
43
|
+
task: async ({ assignment, step }) => {
|
|
44
|
+
const sandbox = await step('create', () =>
|
|
45
|
+
compute.sandbox.create({ provider: assignment.provider ?? 'e2b' }),
|
|
46
|
+
);
|
|
47
|
+
try {
|
|
48
|
+
await step('readiness', () => sandbox.runCommand('true'), { readiness: 'internal' });
|
|
49
|
+
await step('exec.first-command', () => sandbox.runCommand('node -v'));
|
|
50
|
+
// A `readiness: 'poll'` step reports active concurrency and waits until
|
|
51
|
+
// the platform reports the participant's step is ready (a barrier).
|
|
52
|
+
await step('pause', () => {}, { readiness: 'poll' });
|
|
53
|
+
return { sandboxId: sandbox.id };
|
|
54
|
+
} finally {
|
|
55
|
+
await step('destroy', () => sandbox.destroy());
|
|
56
|
+
}
|
|
57
|
+
},
|
|
46
58
|
});
|
|
47
|
-
|
|
48
|
-
await worker.run();
|
|
49
59
|
```
|
|
50
60
|
|
|
51
|
-
`
|
|
61
|
+
`client.runWorker(...)` claims the next pending platform assignment for the participant. If no work is available, it returns `{ assignment: null, records: [] }`.
|
|
52
62
|
|
|
53
63
|
Task results are flushed to the platform in batches of 1,000 records by default. Set `batchSize` to tune this per worker; the SDK validates the platform limit of 5,000 records per batch. Workers also flush partial batches every 30 seconds by default via `flushIntervalMs`, and always flush pending records during final completion or shutdown.
|
|
54
64
|
|
|
55
|
-
## Reuse A Bench Definition
|
|
56
|
-
|
|
57
|
-
```ts
|
|
58
|
-
import { defineBench, defineStep, defineTask } from '@benchsdk/client';
|
|
59
|
-
|
|
60
|
-
const lifecycleTask = defineTask('sandbox.lifecycle', [
|
|
61
|
-
defineStep('create', async ({ state }) => {
|
|
62
|
-
state.sandboxId = 'sandbox_123';
|
|
63
|
-
}),
|
|
64
|
-
defineStep('exec.first-command', async ({ state }) => ({
|
|
65
|
-
sandboxId: String(state.sandboxId),
|
|
66
|
-
})),
|
|
67
|
-
]);
|
|
68
|
-
|
|
69
|
-
const bench = defineBench({
|
|
70
|
-
slug: 'scale',
|
|
71
|
-
participantSlug: 'e2b',
|
|
72
|
-
concurrency: 100,
|
|
73
|
-
task: lifecycleTask,
|
|
74
|
-
});
|
|
75
|
-
|
|
76
|
-
const worker = bench.defineWorker({
|
|
77
|
-
runId: process.env.BENCHMARK_RUN_ID!,
|
|
78
|
-
processKey: process.env.HOSTNAME,
|
|
79
|
-
});
|
|
80
|
-
|
|
81
|
-
await worker.run();
|
|
82
|
-
```
|
|
83
|
-
|
|
84
65
|
## Create A Platform Run
|
|
85
66
|
|
|
86
67
|
```ts
|
|
87
68
|
import { createBenchmarkClient } from '@benchsdk/client';
|
|
88
69
|
|
|
89
70
|
const client = createBenchmarkClient({
|
|
90
|
-
apiKey: process.env.
|
|
71
|
+
apiKey: process.env.BENCHMARKS_PLATFORM_API_KEY,
|
|
91
72
|
});
|
|
92
73
|
|
|
93
74
|
await client.upsertBenchmark('scale', {
|
|
94
75
|
name: 'Scale',
|
|
95
|
-
kind: 'scale',
|
|
96
76
|
config: { timeoutMs: 120_000 },
|
|
97
77
|
});
|
|
98
78
|
|
|
@@ -110,55 +90,27 @@ await client.planWorkers('scale', run.id, 'modal');
|
|
|
110
90
|
console.log(run.id);
|
|
111
91
|
```
|
|
112
92
|
|
|
113
|
-
Workers must be planned before `
|
|
93
|
+
Workers must be planned before `client.runWorker(...)` can claim assignments.
|
|
114
94
|
|
|
115
95
|
## API
|
|
116
96
|
|
|
117
|
-
###
|
|
97
|
+
### Worker Engine
|
|
118
98
|
|
|
119
99
|
```ts
|
|
120
|
-
|
|
121
|
-
defineTask(name, steps)
|
|
122
|
-
defineWorker(options)
|
|
123
|
-
defineBench(options)
|
|
100
|
+
client.runWorker(options)
|
|
124
101
|
```
|
|
125
102
|
|
|
126
|
-
|
|
103
|
+
The `task` function receives:
|
|
127
104
|
|
|
128
105
|
| Field | Type | Description |
|
|
129
106
|
|-------|------|-------------|
|
|
130
107
|
| `assignment` | `BenchmarkAssignment` | Platform-owned assignment for this worker |
|
|
131
108
|
| `taskIndex` | `number` | Deterministic task index within the benchmark run |
|
|
132
|
-
| `
|
|
133
|
-
|
|
134
|
-
If a step returns a JSON object, it is merged into the task result `data` object. Defined tasks also include `taskName` in `data`.
|
|
135
|
-
|
|
136
|
-
`defineTask(name, steps, options)` supports task cleanup:
|
|
109
|
+
| `step` | `(name, fn, options?) => Promise<R>` | Runs `fn` as a named platform step and records its timing/outcome |
|
|
137
110
|
|
|
138
|
-
|
|
139
|
-
|--------|------|-------------|
|
|
140
|
-
| `cleanup` | `(context) => Promise<void> \| void` | Runs after the task finishes, whether steps succeeded or failed. Use shared `state` to tear down resources created by earlier steps. |
|
|
141
|
-
|
|
142
|
-
```ts
|
|
143
|
-
type SandboxState = {
|
|
144
|
-
sandbox?: Awaited<ReturnType<typeof compute.sandbox.create>>;
|
|
145
|
-
};
|
|
146
|
-
|
|
147
|
-
defineTask<SandboxState>('sandbox.lifecycle', [
|
|
148
|
-
defineStep<SandboxState>('create', async ({ state }) => {
|
|
149
|
-
state.sandbox = await compute.sandbox.create();
|
|
150
|
-
}),
|
|
151
|
-
defineStep<SandboxState>('exec', async ({ state }) => {
|
|
152
|
-
await state.sandbox.runCommand('node -v');
|
|
153
|
-
}),
|
|
154
|
-
], {
|
|
155
|
-
cleanup: async ({ state }) => {
|
|
156
|
-
await state.sandbox?.destroy?.();
|
|
157
|
-
},
|
|
158
|
-
});
|
|
159
|
-
```
|
|
111
|
+
If the task returns a JSON object, it is stored as the task result `data`.
|
|
160
112
|
|
|
161
|
-
`
|
|
113
|
+
`step(name, fn, options)` supports step-level progress coordination via `options`:
|
|
162
114
|
|
|
163
115
|
| Option | Type | Description |
|
|
164
116
|
|--------|------|-------------|
|
|
@@ -202,7 +154,7 @@ client.failWorker(benchmarkSlug, runId, workerId, attemptId, error)
|
|
|
202
154
|
client.runWorker(options)
|
|
203
155
|
```
|
|
204
156
|
|
|
205
|
-
For custom coordinators that do not fit `
|
|
157
|
+
For custom coordinators that do not fit `runWorker`, use the best-effort reporter wrapper:
|
|
206
158
|
|
|
207
159
|
```ts
|
|
208
160
|
const reporter = await BenchmarkReporter.claim({
|
|
@@ -227,10 +179,10 @@ await reporter?.finish(false);
|
|
|
227
179
|
|
|
228
180
|
`BenchmarkReporter` swallows platform telemetry failures for claim, heartbeat, result flushing, artifact upload, and finish calls. Benchmark work can continue even when reporting is temporarily unavailable.
|
|
229
181
|
|
|
230
|
-
For `
|
|
182
|
+
For `runWorker`, use `onFinish` to upload worker-level logs once, after final task results are flushed and before the worker attempt is completed or failed:
|
|
231
183
|
|
|
232
184
|
```ts
|
|
233
|
-
|
|
185
|
+
client.runWorker({
|
|
234
186
|
benchmarkSlug: 'scale',
|
|
235
187
|
runId,
|
|
236
188
|
participantSlug: 'e2b',
|
|
@@ -250,7 +202,7 @@ For coordinator health artifacts, sample system metrics:
|
|
|
250
202
|
|
|
251
203
|
```ts
|
|
252
204
|
const metrics = createSystemMetricsCollector();
|
|
253
|
-
const samples = [metrics.sample()];
|
|
205
|
+
const samples = [await metrics.sample()];
|
|
254
206
|
metrics.stop();
|
|
255
207
|
```
|
|
256
208
|
|
|
@@ -269,7 +221,7 @@ console.log(participant?.tasks.completionRatio);
|
|
|
269
221
|
console.log(participant?.concurrency.find((item) => item.step === 'pause')?.ready);
|
|
270
222
|
```
|
|
271
223
|
|
|
272
|
-
Most workers should use `
|
|
224
|
+
Most workers should use `client.runWorker(...)`.
|
|
273
225
|
|
|
274
226
|
## Task Result Shape
|
|
275
227
|
|
|
@@ -286,7 +238,6 @@ Most workers should use `defineWorker(...).run()`.
|
|
|
286
238
|
{ "name": "destroy", "status": "success", "startedAt": "...", "completedAt": "...", "latencyMs": 180 }
|
|
287
239
|
],
|
|
288
240
|
"data": {
|
|
289
|
-
"taskName": "sandbox.lifecycle",
|
|
290
241
|
"sandboxId": "..."
|
|
291
242
|
}
|
|
292
243
|
}
|