@benchsdk/client 0.3.0 → 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/README.md +9 -3
- package/dist/index.cjs +15 -945
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +7 -756
- package/dist/index.d.ts +7 -756
- package/dist/index.js +15 -929
- package/dist/index.js.map +1 -1
- package/package.json +5 -2
package/dist/index.js
CHANGED
|
@@ -1,938 +1,23 @@
|
|
|
1
1
|
// src/client.ts
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
var DEFAULT_HEARTBEAT_INTERVAL_MS = 3e4;
|
|
5
|
-
var DEFAULT_FLUSH_INTERVAL_MS = 3e4;
|
|
6
|
-
var DEFAULT_READY_POLL_INTERVAL_MS = 1e3;
|
|
7
|
-
var MAX_TASK_RESULT_RECORDS = 5e3;
|
|
8
|
-
var MAX_TASK_RECORD_STEPS = 100;
|
|
9
|
-
var MAX_HEARTBEAT_CONCURRENCY_SAMPLES = 20;
|
|
10
|
-
var MAX_WORKER_LOG_LINES = 1e5;
|
|
11
|
-
var BenchmarkApiError = class extends Error {
|
|
12
|
-
constructor(message, status, body) {
|
|
13
|
-
super(message);
|
|
14
|
-
this.status = status;
|
|
15
|
-
this.body = body;
|
|
16
|
-
this.name = "BenchmarkApiError";
|
|
17
|
-
}
|
|
18
|
-
status;
|
|
19
|
-
body;
|
|
20
|
-
};
|
|
21
|
-
function trimTrailingSlash(value) {
|
|
22
|
-
return value.replace(/\/+$/, "");
|
|
23
|
-
}
|
|
24
|
-
function encodePath(value) {
|
|
25
|
-
return encodeURIComponent(value);
|
|
26
|
-
}
|
|
27
|
-
function queryString(input) {
|
|
28
|
-
const params = new URLSearchParams();
|
|
29
|
-
for (const [key, value2] of Object.entries(input)) {
|
|
30
|
-
if (value2 !== void 0) params.set(key, String(value2));
|
|
31
|
-
}
|
|
32
|
-
const value = params.toString();
|
|
33
|
-
return value ? `?${value}` : "";
|
|
34
|
-
}
|
|
35
|
-
function getApiKey(input) {
|
|
36
|
-
return input ?? (typeof process !== "undefined" ? process.env.COMPUTESDK_ADMIN_API_KEY ?? process.env.COMPUTESDK_API_KEY : void 0);
|
|
37
|
-
}
|
|
38
|
-
function getErrorCode(error) {
|
|
39
|
-
if (error instanceof Error && "code" in error && typeof error.code === "string" && error.code) {
|
|
40
|
-
return error.code;
|
|
41
|
-
}
|
|
42
|
-
if (error instanceof Error && error.name) return error.name;
|
|
43
|
-
return "ERROR";
|
|
44
|
-
}
|
|
45
|
-
function toJsonObject(value) {
|
|
46
|
-
if (!value || typeof value !== "object" || Array.isArray(value)) return void 0;
|
|
47
|
-
return value;
|
|
48
|
-
}
|
|
49
|
-
function mergeMeasures(measures, returned) {
|
|
50
|
-
const merged = { ...measures, ...returned ?? {} };
|
|
51
|
-
return Object.keys(merged).length > 0 ? merged : void 0;
|
|
52
|
-
}
|
|
53
|
-
function implicitTaskStep(record, measures) {
|
|
54
|
-
return {
|
|
55
|
-
name: "task",
|
|
56
|
-
status: record.status === "success" ? "success" : "error",
|
|
57
|
-
startedAt: record.startedAt,
|
|
58
|
-
completedAt: record.completedAt,
|
|
59
|
-
latencyMs: record.latencyMs,
|
|
60
|
-
errorCode: record.errorCode ?? null,
|
|
61
|
-
data: Object.keys(measures).length > 0 ? { ...measures } : void 0
|
|
62
|
-
};
|
|
63
|
-
}
|
|
64
|
-
function validateTaskResults(input) {
|
|
65
|
-
if (input.records.length > MAX_TASK_RESULT_RECORDS) {
|
|
66
|
-
throw new Error(`Benchmark task result batches are limited to ${MAX_TASK_RESULT_RECORDS} records.`);
|
|
67
|
-
}
|
|
68
|
-
for (const record of input.records) {
|
|
69
|
-
if ((record.steps?.length ?? 0) > MAX_TASK_RECORD_STEPS) {
|
|
70
|
-
throw new Error(`Benchmark task result records are limited to ${MAX_TASK_RECORD_STEPS} steps.`);
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
function validateHeartbeat(input) {
|
|
75
|
-
const concurrency = input.concurrency ?? [];
|
|
76
|
-
if (concurrency.length > MAX_HEARTBEAT_CONCURRENCY_SAMPLES) {
|
|
77
|
-
throw new Error(`Benchmark heartbeat concurrency is limited to ${MAX_HEARTBEAT_CONCURRENCY_SAMPLES} samples.`);
|
|
78
|
-
}
|
|
79
|
-
const steps = /* @__PURE__ */ new Set();
|
|
80
|
-
for (const sample of concurrency) {
|
|
81
|
-
if (steps.has(sample.step)) {
|
|
82
|
-
throw new Error(`Benchmark heartbeat concurrency step values must be unique per heartbeat.`);
|
|
83
|
-
}
|
|
84
|
-
steps.add(sample.step);
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
function validatePositiveInteger(name, value) {
|
|
88
|
-
if (!Number.isInteger(value) || value <= 0) {
|
|
89
|
-
throw new Error(`Benchmark ${name} must be a positive integer.`);
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
function validateBatchSize(value) {
|
|
93
|
-
validatePositiveInteger("batchSize", value);
|
|
94
|
-
if (value > MAX_TASK_RESULT_RECORDS) {
|
|
95
|
-
throw new Error(`Benchmark batchSize must be at most ${MAX_TASK_RESULT_RECORDS}.`);
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
|
-
function normalizeArtifacts(data) {
|
|
99
|
-
return data.items ?? data.artifacts ?? [];
|
|
100
|
-
}
|
|
101
|
-
function bodySizeBytes(body) {
|
|
102
|
-
if (typeof body === "string") return new TextEncoder().encode(body).byteLength;
|
|
103
|
-
if (body instanceof Blob) return body.size;
|
|
104
|
-
if (body instanceof ArrayBuffer) return body.byteLength;
|
|
105
|
-
if (ArrayBuffer.isView(body)) return body.byteLength;
|
|
106
|
-
if (body instanceof URLSearchParams) return new TextEncoder().encode(body.toString()).byteLength;
|
|
107
|
-
return void 0;
|
|
108
|
-
}
|
|
109
|
-
function sleep(ms) {
|
|
110
|
-
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
111
|
-
}
|
|
112
|
-
async function mapPool(items, concurrency, fn) {
|
|
113
|
-
let nextIndex = 0;
|
|
114
|
-
const workers = Array.from({ length: Math.min(concurrency, items.length) }, async () => {
|
|
115
|
-
while (nextIndex < items.length) {
|
|
116
|
-
const item = items[nextIndex];
|
|
117
|
-
nextIndex += 1;
|
|
118
|
-
await fn(item);
|
|
119
|
-
}
|
|
120
|
-
});
|
|
121
|
-
await Promise.all(workers);
|
|
122
|
-
}
|
|
2
|
+
import { createBenchmarkClient as createApiClient, BenchmarkApiError } from "@benchsdk/api";
|
|
3
|
+
import { runWorker } from "@benchsdk/worker";
|
|
123
4
|
function createBenchmarkClient(config = {}) {
|
|
124
|
-
const
|
|
125
|
-
const apiKey = getApiKey(config.apiKey);
|
|
126
|
-
const fetchImpl = config.fetch ?? (typeof fetch !== "undefined" ? fetch : void 0);
|
|
127
|
-
if (!fetchImpl) {
|
|
128
|
-
throw new Error("fetch is not available");
|
|
129
|
-
}
|
|
130
|
-
const doFetch = fetchImpl;
|
|
131
|
-
async function request(method, path, body) {
|
|
132
|
-
const headers = { "Content-Type": "application/json" };
|
|
133
|
-
if (apiKey) headers.Authorization = `Bearer ${apiKey}`;
|
|
134
|
-
const response = await doFetch(`${baseUrl}${path}`, {
|
|
135
|
-
method,
|
|
136
|
-
headers,
|
|
137
|
-
body: body === void 0 ? void 0 : JSON.stringify(body)
|
|
138
|
-
});
|
|
139
|
-
const text = await response.text();
|
|
140
|
-
if (!response.ok) {
|
|
141
|
-
throw new BenchmarkApiError(
|
|
142
|
-
`Benchmark API request failed: ${response.status} ${response.statusText}`,
|
|
143
|
-
response.status,
|
|
144
|
-
text
|
|
145
|
-
);
|
|
146
|
-
}
|
|
147
|
-
return text ? JSON.parse(text) : {};
|
|
148
|
-
}
|
|
149
|
-
async function sendTaskResults(input) {
|
|
150
|
-
if (input.records.length === 0) {
|
|
151
|
-
return {};
|
|
152
|
-
}
|
|
153
|
-
validateTaskResults(input);
|
|
154
|
-
return request(
|
|
155
|
-
"POST",
|
|
156
|
-
`/benchmarks/${encodePath(input.benchmarkSlug)}/runs/${encodePath(input.runId)}/workers/${encodePath(input.workerId)}/events`,
|
|
157
|
-
{
|
|
158
|
-
type: "task_results",
|
|
159
|
-
attemptId: input.attemptId,
|
|
160
|
-
sequenceNumber: input.sequenceNumber,
|
|
161
|
-
isFinal: input.isFinal,
|
|
162
|
-
records: input.records
|
|
163
|
-
}
|
|
164
|
-
);
|
|
165
|
-
}
|
|
166
|
-
async function updateWorkerLifecycle(action, benchmarkSlug, runId, workerId, attemptId, extra) {
|
|
167
|
-
return request(
|
|
168
|
-
"POST",
|
|
169
|
-
`/benchmarks/${encodePath(benchmarkSlug)}/runs/${encodePath(runId)}/workers/${encodePath(workerId)}/${action}`,
|
|
170
|
-
{ attemptId, ...extra ?? {} }
|
|
171
|
-
);
|
|
172
|
-
}
|
|
173
|
-
const client = {
|
|
174
|
-
async upsertBenchmark(slug, input) {
|
|
175
|
-
const data = await request("PUT", `/benchmarks/${encodePath(slug)}`, input);
|
|
176
|
-
return data.benchmark;
|
|
177
|
-
},
|
|
178
|
-
async getBenchmark(slug) {
|
|
179
|
-
const data = await request("GET", `/benchmarks/${encodePath(slug)}`);
|
|
180
|
-
return data.benchmark;
|
|
181
|
-
},
|
|
182
|
-
async updateBenchmark(slug, input) {
|
|
183
|
-
const data = await request("PATCH", `/benchmarks/${encodePath(slug)}`, input);
|
|
184
|
-
return data.benchmark;
|
|
185
|
-
},
|
|
186
|
-
async listBenchmarks() {
|
|
187
|
-
const data = await request("GET", "/benchmarks");
|
|
188
|
-
return data.items ?? data.benchmarks ?? [];
|
|
189
|
-
},
|
|
190
|
-
async createRun(benchmarkSlug, input) {
|
|
191
|
-
return request(
|
|
192
|
-
"POST",
|
|
193
|
-
`/benchmarks/${encodePath(benchmarkSlug)}/runs`,
|
|
194
|
-
input
|
|
195
|
-
);
|
|
196
|
-
},
|
|
197
|
-
async listRuns(benchmarkSlug) {
|
|
198
|
-
const data = await request("GET", `/benchmarks/${encodePath(benchmarkSlug)}/runs`);
|
|
199
|
-
return data.items;
|
|
200
|
-
},
|
|
201
|
-
async getRun(benchmarkSlug, runId) {
|
|
202
|
-
const data = await request("GET", `/benchmarks/${encodePath(benchmarkSlug)}/runs/${encodePath(runId)}`);
|
|
203
|
-
return data.run;
|
|
204
|
-
},
|
|
205
|
-
async updateRun(benchmarkSlug, runId, input) {
|
|
206
|
-
const data = await request(
|
|
207
|
-
"PATCH",
|
|
208
|
-
`/benchmarks/${encodePath(benchmarkSlug)}/runs/${encodePath(runId)}`,
|
|
209
|
-
input
|
|
210
|
-
);
|
|
211
|
-
return data.run;
|
|
212
|
-
},
|
|
213
|
-
async upsertParticipant(benchmarkSlug, runId, participantSlug, input = {}) {
|
|
214
|
-
const data = await request(
|
|
215
|
-
"PUT",
|
|
216
|
-
`/benchmarks/${encodePath(benchmarkSlug)}/runs/${encodePath(runId)}/participants/${encodePath(participantSlug)}`,
|
|
217
|
-
input
|
|
218
|
-
);
|
|
219
|
-
return data.participant;
|
|
220
|
-
},
|
|
221
|
-
async listParticipants(benchmarkSlug, runId) {
|
|
222
|
-
const data = await request(
|
|
223
|
-
"GET",
|
|
224
|
-
`/benchmarks/${encodePath(benchmarkSlug)}/runs/${encodePath(runId)}/participants`
|
|
225
|
-
);
|
|
226
|
-
return data.items ?? data.participants ?? [];
|
|
227
|
-
},
|
|
228
|
-
async getParticipant(benchmarkSlug, runId, participantSlug) {
|
|
229
|
-
const data = await request(
|
|
230
|
-
"GET",
|
|
231
|
-
`/benchmarks/${encodePath(benchmarkSlug)}/runs/${encodePath(runId)}/participants/${encodePath(participantSlug)}`
|
|
232
|
-
);
|
|
233
|
-
return data.participant;
|
|
234
|
-
},
|
|
235
|
-
async updateParticipant(benchmarkSlug, runId, participantSlug, input) {
|
|
236
|
-
const data = await request(
|
|
237
|
-
"PATCH",
|
|
238
|
-
`/benchmarks/${encodePath(benchmarkSlug)}/runs/${encodePath(runId)}/participants/${encodePath(participantSlug)}`,
|
|
239
|
-
input
|
|
240
|
-
);
|
|
241
|
-
return data.participant;
|
|
242
|
-
},
|
|
243
|
-
async getRunProgress(benchmarkSlug, runId) {
|
|
244
|
-
return request(
|
|
245
|
-
"GET",
|
|
246
|
-
`/benchmarks/${encodePath(benchmarkSlug)}/runs/${encodePath(runId)}/progress`
|
|
247
|
-
);
|
|
248
|
-
},
|
|
249
|
-
async listWorkers(benchmarkSlug, runId, participantSlug) {
|
|
250
|
-
const data = await request(
|
|
251
|
-
"GET",
|
|
252
|
-
`/benchmarks/${encodePath(benchmarkSlug)}/runs/${encodePath(runId)}/participants/${encodePath(participantSlug)}/workers`
|
|
253
|
-
);
|
|
254
|
-
return data.items ?? data.workers ?? [];
|
|
255
|
-
},
|
|
256
|
-
async planWorkers(benchmarkSlug, runId, participantSlug, input = {}) {
|
|
257
|
-
const data = await request(
|
|
258
|
-
"POST",
|
|
259
|
-
`/benchmarks/${encodePath(benchmarkSlug)}/runs/${encodePath(runId)}/participants/${encodePath(participantSlug)}/workers`,
|
|
260
|
-
input
|
|
261
|
-
);
|
|
262
|
-
return data.items ?? data.workers ?? [];
|
|
263
|
-
},
|
|
264
|
-
async getWorker(benchmarkSlug, runId, workerId) {
|
|
265
|
-
const data = await request(
|
|
266
|
-
"GET",
|
|
267
|
-
`/benchmarks/${encodePath(benchmarkSlug)}/runs/${encodePath(runId)}/workers/${encodePath(workerId)}`
|
|
268
|
-
);
|
|
269
|
-
return data.worker;
|
|
270
|
-
},
|
|
271
|
-
async updateWorker(benchmarkSlug, runId, workerId, input) {
|
|
272
|
-
const data = await request(
|
|
273
|
-
"PATCH",
|
|
274
|
-
`/benchmarks/${encodePath(benchmarkSlug)}/runs/${encodePath(runId)}/workers/${encodePath(workerId)}`,
|
|
275
|
-
input
|
|
276
|
-
);
|
|
277
|
-
return data.worker;
|
|
278
|
-
},
|
|
279
|
-
async claimWorker(benchmarkSlug, runId, participantSlug, input = {}) {
|
|
280
|
-
const data = await request(
|
|
281
|
-
"POST",
|
|
282
|
-
`/benchmarks/${encodePath(benchmarkSlug)}/runs/${encodePath(runId)}/participants/${encodePath(participantSlug)}/workers/claim`,
|
|
283
|
-
input
|
|
284
|
-
);
|
|
285
|
-
return data.assignment;
|
|
286
|
-
},
|
|
287
|
-
sendTaskResults,
|
|
288
|
-
async heartbeatWorker(benchmarkSlug, runId, workerId, input) {
|
|
289
|
-
validateHeartbeat(input);
|
|
290
|
-
const { attemptId, ...extra } = input;
|
|
291
|
-
if (extra.currentStep == null) {
|
|
292
|
-
delete extra.currentStep;
|
|
293
|
-
}
|
|
294
|
-
return updateWorkerLifecycle("heartbeat", benchmarkSlug, runId, workerId, attemptId, extra);
|
|
295
|
-
},
|
|
296
|
-
releaseWorker(benchmarkSlug, runId, workerId, attemptId) {
|
|
297
|
-
return updateWorkerLifecycle("release", benchmarkSlug, runId, workerId, attemptId);
|
|
298
|
-
},
|
|
299
|
-
completeWorker(benchmarkSlug, runId, workerId, attemptId) {
|
|
300
|
-
return updateWorkerLifecycle("complete", benchmarkSlug, runId, workerId, attemptId);
|
|
301
|
-
},
|
|
302
|
-
failWorker(benchmarkSlug, runId, workerId, attemptId, error) {
|
|
303
|
-
return updateWorkerLifecycle("fail", benchmarkSlug, runId, workerId, attemptId, {
|
|
304
|
-
errorCode: getErrorCode(error),
|
|
305
|
-
errorMessage: error instanceof Error ? error.message : String(error ?? "Unknown error")
|
|
306
|
-
});
|
|
307
|
-
},
|
|
308
|
-
async createWorkerArtifact(benchmarkSlug, runId, workerId, input) {
|
|
309
|
-
return request(
|
|
310
|
-
"POST",
|
|
311
|
-
`/benchmarks/${encodePath(benchmarkSlug)}/runs/${encodePath(runId)}/workers/${encodePath(workerId)}/artifacts`,
|
|
312
|
-
input
|
|
313
|
-
);
|
|
314
|
-
},
|
|
315
|
-
async uploadWorkerArtifact(benchmarkSlug, runId, workerId, input) {
|
|
316
|
-
const sizeBytes = bodySizeBytes(input.body);
|
|
317
|
-
const artifactInput = {
|
|
318
|
-
attemptId: input.attemptId,
|
|
319
|
-
kind: input.kind,
|
|
320
|
-
contentType: input.contentType,
|
|
321
|
-
name: input.name,
|
|
322
|
-
metadata: sizeBytes === void 0 ? input.metadata : { ...input.metadata, sizeBytes }
|
|
323
|
-
};
|
|
324
|
-
const response = await client.createWorkerArtifact(benchmarkSlug, runId, workerId, artifactInput);
|
|
325
|
-
const uploadUrl = response.uploadUrl ?? response.artifact?.uploadUrl;
|
|
326
|
-
if (!uploadUrl) {
|
|
327
|
-
throw new Error("Benchmark artifact upload URL is missing.");
|
|
328
|
-
}
|
|
329
|
-
const uploadResponse = await doFetch(uploadUrl, {
|
|
330
|
-
method: "PUT",
|
|
331
|
-
headers: input.contentType ? { "Content-Type": input.contentType } : void 0,
|
|
332
|
-
body: input.body
|
|
333
|
-
});
|
|
334
|
-
if (!uploadResponse.ok) {
|
|
335
|
-
const errorBody = await uploadResponse.text().catch(() => "");
|
|
336
|
-
throw new BenchmarkApiError(
|
|
337
|
-
`Benchmark artifact upload failed with ${uploadResponse.status}`,
|
|
338
|
-
uploadResponse.status,
|
|
339
|
-
errorBody
|
|
340
|
-
);
|
|
341
|
-
}
|
|
342
|
-
return response;
|
|
343
|
-
},
|
|
344
|
-
async listRunArtifacts(benchmarkSlug, runId) {
|
|
345
|
-
const data = await request(
|
|
346
|
-
"GET",
|
|
347
|
-
`/benchmarks/${encodePath(benchmarkSlug)}/runs/${encodePath(runId)}/artifacts`
|
|
348
|
-
);
|
|
349
|
-
return normalizeArtifacts(data);
|
|
350
|
-
},
|
|
351
|
-
async listWorkerArtifacts(benchmarkSlug, runId, workerId) {
|
|
352
|
-
const data = await request(
|
|
353
|
-
"GET",
|
|
354
|
-
`/benchmarks/${encodePath(benchmarkSlug)}/runs/${encodePath(runId)}/workers/${encodePath(workerId)}/artifacts`
|
|
355
|
-
);
|
|
356
|
-
return normalizeArtifacts(data);
|
|
357
|
-
},
|
|
358
|
-
async getBenchmarkResults(benchmarkSlug, input = {}) {
|
|
359
|
-
return request(
|
|
360
|
-
"GET",
|
|
361
|
-
`/benchmarks/${encodePath(benchmarkSlug)}/results${queryString({ limit: input.limit })}`
|
|
362
|
-
);
|
|
363
|
-
},
|
|
364
|
-
async getRunResults(benchmarkSlug, runId) {
|
|
365
|
-
return request(
|
|
366
|
-
"GET",
|
|
367
|
-
`/benchmarks/${encodePath(benchmarkSlug)}/runs/${encodePath(runId)}/results`
|
|
368
|
-
);
|
|
369
|
-
},
|
|
370
|
-
async getRunTaskResults(benchmarkSlug, runId, input = {}) {
|
|
371
|
-
return request(
|
|
372
|
-
"GET",
|
|
373
|
-
`/benchmarks/${encodePath(benchmarkSlug)}/runs/${encodePath(runId)}/results/tasks${queryString({ bucketSize: input.bucketSize, failureLimit: input.failureLimit })}`
|
|
374
|
-
);
|
|
375
|
-
},
|
|
376
|
-
async getRunTimeline(benchmarkSlug, runId, input = {}) {
|
|
377
|
-
return request(
|
|
378
|
-
"GET",
|
|
379
|
-
`/benchmarks/${encodePath(benchmarkSlug)}/runs/${encodePath(runId)}/results/timeline${queryString({ bucketMs: input.bucketMs })}`
|
|
380
|
-
);
|
|
381
|
-
},
|
|
382
|
-
async getRunImports(benchmarkSlug, runId) {
|
|
383
|
-
return request(
|
|
384
|
-
"GET",
|
|
385
|
-
`/benchmarks/${encodePath(benchmarkSlug)}/runs/${encodePath(runId)}/results/imports`
|
|
386
|
-
);
|
|
387
|
-
},
|
|
388
|
-
async submitRunSummary(benchmarkSlug, runId, input) {
|
|
389
|
-
await request(
|
|
390
|
-
"POST",
|
|
391
|
-
`/benchmarks/${encodePath(benchmarkSlug)}/runs/${encodePath(runId)}/summary`,
|
|
392
|
-
input
|
|
393
|
-
);
|
|
394
|
-
},
|
|
395
|
-
async runWorker(options) {
|
|
396
|
-
if (options.concurrency !== void 0) validatePositiveInteger("concurrency", options.concurrency);
|
|
397
|
-
if (options.batchSize !== void 0) validateBatchSize(options.batchSize);
|
|
398
|
-
if (options.flushIntervalMs !== void 0) validatePositiveInteger("flushIntervalMs", options.flushIntervalMs);
|
|
399
|
-
const assignment = await client.claimWorker(options.benchmarkSlug, options.runId, options.participantSlug, {
|
|
400
|
-
processKind: options.processKind,
|
|
401
|
-
processKey: options.processKey
|
|
402
|
-
});
|
|
403
|
-
if (!assignment) return { assignment: null, records: [] };
|
|
404
|
-
const claimed = assignment;
|
|
405
|
-
let sequenceNumber = 0;
|
|
406
|
-
const records = [];
|
|
407
|
-
const pending = [];
|
|
408
|
-
const batchSize = options.batchSize ?? DEFAULT_BATCH_SIZE;
|
|
409
|
-
const workerConcurrency = options.concurrency ?? claimed.targetConcurrency;
|
|
410
|
-
validatePositiveInteger("concurrency", workerConcurrency);
|
|
411
|
-
const taskIndices = Array.from({ length: claimed.taskRange.count }, (_, index) => claimed.taskRange.start + index);
|
|
412
|
-
const activeByStep = /* @__PURE__ */ new Map();
|
|
413
|
-
const targetByStep = /* @__PURE__ */ new Map();
|
|
414
|
-
const readyWaitByStep = /* @__PURE__ */ new Map();
|
|
415
|
-
let doneCount = 0;
|
|
416
|
-
let errorCount = 0;
|
|
417
|
-
let inFlightCount = 0;
|
|
418
|
-
let flushChain = Promise.resolve();
|
|
419
|
-
function concurrencySamples() {
|
|
420
|
-
return Array.from(activeByStep.entries()).filter(([, active]) => active > 0).map(([step, active]) => ({
|
|
421
|
-
step,
|
|
422
|
-
active,
|
|
423
|
-
target: targetByStep.get(step) ?? workerConcurrency
|
|
424
|
-
})).sort((a, b) => b.active - a.active).slice(0, MAX_HEARTBEAT_CONCURRENCY_SAMPLES);
|
|
425
|
-
}
|
|
426
|
-
async function sendHeartbeat() {
|
|
427
|
-
const concurrency = concurrencySamples();
|
|
428
|
-
const step = concurrency[0]?.step ?? null;
|
|
429
|
-
await client.heartbeatWorker(options.benchmarkSlug, options.runId, claimed.workerId, {
|
|
430
|
-
attemptId: claimed.attemptId,
|
|
431
|
-
progressDone: doneCount,
|
|
432
|
-
progressInFlight: inFlightCount,
|
|
433
|
-
progressErrors: errorCount,
|
|
434
|
-
progressTotal: taskIndices.length,
|
|
435
|
-
...step ? { currentStep: step } : {},
|
|
436
|
-
concurrency
|
|
437
|
-
});
|
|
438
|
-
}
|
|
439
|
-
let heartbeatInFlight = null;
|
|
440
|
-
let heartbeatRequested = false;
|
|
441
|
-
function requestHeartbeat() {
|
|
442
|
-
heartbeatRequested = true;
|
|
443
|
-
if (heartbeatInFlight) return;
|
|
444
|
-
heartbeatInFlight = (async () => {
|
|
445
|
-
while (heartbeatRequested) {
|
|
446
|
-
heartbeatRequested = false;
|
|
447
|
-
await sendHeartbeat().catch(() => {
|
|
448
|
-
});
|
|
449
|
-
}
|
|
450
|
-
})().finally(() => {
|
|
451
|
-
heartbeatInFlight = null;
|
|
452
|
-
if (heartbeatRequested) requestHeartbeat();
|
|
453
|
-
});
|
|
454
|
-
}
|
|
455
|
-
async function pollStepReady(stepName, stepOptions) {
|
|
456
|
-
const startedAt = Date.now();
|
|
457
|
-
const pollInterval = stepOptions.readyPollIntervalMs ?? options.readyPollIntervalMs ?? DEFAULT_READY_POLL_INTERVAL_MS;
|
|
458
|
-
while (true) {
|
|
459
|
-
const progress = await client.getRunProgress(options.benchmarkSlug, options.runId);
|
|
460
|
-
const participant = progress.participants.find((item) => item.slug === options.participantSlug);
|
|
461
|
-
const step = participant?.concurrency.find((item) => item.step === stepName);
|
|
462
|
-
if (step?.ready) return;
|
|
463
|
-
if (typeof stepOptions.readyTimeoutMs === "number" && Date.now() - startedAt >= stepOptions.readyTimeoutMs) {
|
|
464
|
-
throw new Error(`Timed out waiting for benchmark step "${stepName}" to become ready.`);
|
|
465
|
-
}
|
|
466
|
-
await sleep(pollInterval);
|
|
467
|
-
}
|
|
468
|
-
}
|
|
469
|
-
async function waitForStepReady(stepName, stepOptions) {
|
|
470
|
-
const existing = readyWaitByStep.get(stepName);
|
|
471
|
-
if (existing) return existing;
|
|
472
|
-
const wait = pollStepReady(stepName, stepOptions).finally(() => {
|
|
473
|
-
if (readyWaitByStep.get(stepName) === wait) readyWaitByStep.delete(stepName);
|
|
474
|
-
});
|
|
475
|
-
readyWaitByStep.set(stepName, wait);
|
|
476
|
-
return wait;
|
|
477
|
-
}
|
|
478
|
-
const heartbeat = setInterval(() => {
|
|
479
|
-
requestHeartbeat();
|
|
480
|
-
}, options.heartbeatIntervalMs ?? DEFAULT_HEARTBEAT_INTERVAL_MS);
|
|
481
|
-
heartbeat.unref?.();
|
|
482
|
-
async function flush(isFinal, force = false) {
|
|
483
|
-
flushChain = flushChain.then(async () => {
|
|
484
|
-
if (force && doneCount >= taskIndices.length) return;
|
|
485
|
-
while (pending.length >= batchSize || (isFinal || force) && pending.length > 0) {
|
|
486
|
-
const batch = pending.splice(0, batchSize);
|
|
487
|
-
await sendTaskResults({
|
|
488
|
-
benchmarkSlug: options.benchmarkSlug,
|
|
489
|
-
runId: options.runId,
|
|
490
|
-
workerId: claimed.workerId,
|
|
491
|
-
attemptId: claimed.attemptId,
|
|
492
|
-
sequenceNumber,
|
|
493
|
-
isFinal: isFinal && pending.length === 0,
|
|
494
|
-
records: batch
|
|
495
|
-
});
|
|
496
|
-
sequenceNumber += 1;
|
|
497
|
-
}
|
|
498
|
-
});
|
|
499
|
-
await flushChain;
|
|
500
|
-
}
|
|
501
|
-
const resultFlush = setInterval(() => {
|
|
502
|
-
if (doneCount < taskIndices.length) void flush(false, true).catch(() => {
|
|
503
|
-
});
|
|
504
|
-
}, options.flushIntervalMs ?? DEFAULT_FLUSH_INTERVAL_MS);
|
|
505
|
-
resultFlush.unref?.();
|
|
506
|
-
const workerLogLines = [];
|
|
507
|
-
let workerLogTruncated = false;
|
|
508
|
-
function appendWorkerLog(line) {
|
|
509
|
-
if (workerLogLines.length >= MAX_WORKER_LOG_LINES) {
|
|
510
|
-
if (!workerLogTruncated) {
|
|
511
|
-
workerLogTruncated = true;
|
|
512
|
-
workerLogLines.push("... (worker log truncated)");
|
|
513
|
-
}
|
|
514
|
-
return;
|
|
515
|
-
}
|
|
516
|
-
workerLogLines.push(line);
|
|
517
|
-
}
|
|
518
|
-
async function runFinishHook(status) {
|
|
519
|
-
await options.onFinish?.({
|
|
520
|
-
assignment: claimed,
|
|
521
|
-
records,
|
|
522
|
-
status,
|
|
523
|
-
client,
|
|
524
|
-
uploadArtifact(input) {
|
|
525
|
-
return client.uploadWorkerArtifact(options.benchmarkSlug, options.runId, claimed.workerId, {
|
|
526
|
-
...input,
|
|
527
|
-
attemptId: claimed.attemptId
|
|
528
|
-
});
|
|
529
|
-
}
|
|
530
|
-
});
|
|
531
|
-
}
|
|
532
|
-
async function uploadWorkerLogArtifact() {
|
|
533
|
-
if (workerLogLines.length === 0) return;
|
|
534
|
-
try {
|
|
535
|
-
await client.uploadWorkerArtifact(options.benchmarkSlug, options.runId, claimed.workerId, {
|
|
536
|
-
attemptId: claimed.attemptId,
|
|
537
|
-
kind: "coordinator.log",
|
|
538
|
-
contentType: "text/plain",
|
|
539
|
-
name: "worker.log",
|
|
540
|
-
body: workerLogLines.join("\n") + "\n"
|
|
541
|
-
});
|
|
542
|
-
} catch {
|
|
543
|
-
}
|
|
544
|
-
}
|
|
545
|
-
try {
|
|
546
|
-
await sendHeartbeat().catch(() => {
|
|
547
|
-
});
|
|
548
|
-
await mapPool(taskIndices, workerConcurrency, async (taskIndex) => {
|
|
549
|
-
inFlightCount += 1;
|
|
550
|
-
const startedAtDate = /* @__PURE__ */ new Date();
|
|
551
|
-
const startedAtMs = Date.now();
|
|
552
|
-
const record = {
|
|
553
|
-
taskIndex,
|
|
554
|
-
status: "success",
|
|
555
|
-
startedAt: startedAtDate.toISOString()
|
|
556
|
-
};
|
|
557
|
-
const steps = [];
|
|
558
|
-
const taskMeasures = {};
|
|
559
|
-
let activeStep = null;
|
|
560
|
-
function measure(data) {
|
|
561
|
-
if (activeStep) {
|
|
562
|
-
activeStep.data = { ...activeStep.data ?? {}, ...data };
|
|
563
|
-
} else {
|
|
564
|
-
Object.assign(taskMeasures, data);
|
|
565
|
-
}
|
|
566
|
-
}
|
|
567
|
-
function log(message, meta) {
|
|
568
|
-
const suffix = meta && Object.keys(meta).length > 0 ? ` ${JSON.stringify(meta)}` : "";
|
|
569
|
-
appendWorkerLog(`${(/* @__PURE__ */ new Date()).toISOString()} [task ${taskIndex}] ${message}${suffix}`);
|
|
570
|
-
}
|
|
571
|
-
async function step(name, fn, stepOptions = {}) {
|
|
572
|
-
const stepStartedAtMs = Date.now();
|
|
573
|
-
const stepRecord = {
|
|
574
|
-
name,
|
|
575
|
-
status: "success",
|
|
576
|
-
startedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
577
|
-
completedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
578
|
-
latencyMs: 0
|
|
579
|
-
};
|
|
580
|
-
if (stepOptions.timeoutMs !== void 0) stepRecord.timeoutMs = stepOptions.timeoutMs;
|
|
581
|
-
if (stepOptions.stepConcurrency !== void 0) stepRecord.concurrency = stepOptions.stepConcurrency;
|
|
582
|
-
const shouldReportConcurrency = stepOptions.reportConcurrency ?? true;
|
|
583
|
-
if (shouldReportConcurrency) {
|
|
584
|
-
const stepConcurrency = stepOptions.concurrency ?? workerConcurrency;
|
|
585
|
-
validatePositiveInteger(`step "${name}" concurrency`, stepConcurrency);
|
|
586
|
-
targetByStep.set(name, stepConcurrency);
|
|
587
|
-
activeByStep.set(name, (activeByStep.get(name) ?? 0) + 1);
|
|
588
|
-
requestHeartbeat();
|
|
589
|
-
}
|
|
590
|
-
const previousStep = activeStep;
|
|
591
|
-
activeStep = stepRecord;
|
|
592
|
-
try {
|
|
593
|
-
if (stepOptions.readiness === "poll") {
|
|
594
|
-
await waitForStepReady(name, stepOptions);
|
|
595
|
-
}
|
|
596
|
-
const value = await fn();
|
|
597
|
-
appendWorkerLog(`${(/* @__PURE__ */ new Date()).toISOString()} [task ${taskIndex}] ${name}`);
|
|
598
|
-
return value;
|
|
599
|
-
} catch (error) {
|
|
600
|
-
stepRecord.status = "error";
|
|
601
|
-
stepRecord.errorCode = getErrorCode(error);
|
|
602
|
-
appendWorkerLog(`${(/* @__PURE__ */ new Date()).toISOString()} [task ${taskIndex}] ${name}`);
|
|
603
|
-
appendWorkerLog(` error: ${error instanceof Error ? error.message : String(error)}`);
|
|
604
|
-
throw error;
|
|
605
|
-
} finally {
|
|
606
|
-
activeStep = previousStep;
|
|
607
|
-
stepRecord.completedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
608
|
-
stepRecord.latencyMs = Date.now() - stepStartedAtMs;
|
|
609
|
-
steps.push(stepRecord);
|
|
610
|
-
if (shouldReportConcurrency) {
|
|
611
|
-
const nextActive = Math.max(0, (activeByStep.get(name) ?? 0) - 1);
|
|
612
|
-
if (nextActive === 0) {
|
|
613
|
-
activeByStep.delete(name);
|
|
614
|
-
targetByStep.delete(name);
|
|
615
|
-
} else {
|
|
616
|
-
activeByStep.set(name, nextActive);
|
|
617
|
-
}
|
|
618
|
-
requestHeartbeat();
|
|
619
|
-
}
|
|
620
|
-
}
|
|
621
|
-
}
|
|
622
|
-
try {
|
|
623
|
-
const data = await options.task({ assignment: claimed, taskIndex, step, measure, log });
|
|
624
|
-
record.data = mergeMeasures(taskMeasures, toJsonObject(data));
|
|
625
|
-
} catch (error) {
|
|
626
|
-
record.status = "error";
|
|
627
|
-
record.errorCode = getErrorCode(error);
|
|
628
|
-
record.data = mergeMeasures(taskMeasures, { errorMessage: error instanceof Error ? error.message : String(error) });
|
|
629
|
-
} finally {
|
|
630
|
-
record.completedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
631
|
-
record.latencyMs = Date.now() - startedAtMs;
|
|
632
|
-
if (steps.length === 0) {
|
|
633
|
-
steps.push(implicitTaskStep(record, taskMeasures));
|
|
634
|
-
}
|
|
635
|
-
record.steps = steps;
|
|
636
|
-
doneCount += 1;
|
|
637
|
-
inFlightCount = Math.max(0, inFlightCount - 1);
|
|
638
|
-
if (record.status !== "success") errorCount += 1;
|
|
639
|
-
}
|
|
640
|
-
records.push(record);
|
|
641
|
-
pending.push(record);
|
|
642
|
-
options.onResult?.(record);
|
|
643
|
-
if (pending.length >= batchSize) await flush(false);
|
|
644
|
-
});
|
|
645
|
-
await flush(true);
|
|
646
|
-
const hasErrors = records.some((record) => record.status !== "success");
|
|
647
|
-
try {
|
|
648
|
-
await runFinishHook(hasErrors ? "error" : "success");
|
|
649
|
-
} catch (error) {
|
|
650
|
-
if (!hasErrors) throw error;
|
|
651
|
-
}
|
|
652
|
-
if (hasErrors) {
|
|
653
|
-
await client.failWorker(options.benchmarkSlug, options.runId, claimed.workerId, claimed.attemptId, new Error("One or more tasks failed"));
|
|
654
|
-
} else {
|
|
655
|
-
await client.completeWorker(options.benchmarkSlug, options.runId, claimed.workerId, claimed.attemptId);
|
|
656
|
-
}
|
|
657
|
-
return { assignment: claimed, records };
|
|
658
|
-
} catch (error) {
|
|
659
|
-
await flush(true).catch(() => {
|
|
660
|
-
});
|
|
661
|
-
await runFinishHook("error").catch(() => {
|
|
662
|
-
});
|
|
663
|
-
await client.failWorker(options.benchmarkSlug, options.runId, claimed.workerId, claimed.attemptId, error).catch(() => {
|
|
664
|
-
});
|
|
665
|
-
throw error;
|
|
666
|
-
} finally {
|
|
667
|
-
await uploadWorkerLogArtifact();
|
|
668
|
-
clearInterval(heartbeat);
|
|
669
|
-
clearInterval(resultFlush);
|
|
670
|
-
}
|
|
671
|
-
}
|
|
672
|
-
};
|
|
673
|
-
return client;
|
|
674
|
-
}
|
|
675
|
-
|
|
676
|
-
// src/reporter.ts
|
|
677
|
-
var DEFAULT_REPORTER_BATCH_SIZE = 500;
|
|
678
|
-
var DEFAULT_READY_POLL_INTERVAL_MS2 = 1e3;
|
|
679
|
-
var BenchmarkReporter = class _BenchmarkReporter {
|
|
680
|
-
client;
|
|
681
|
-
assignment;
|
|
682
|
-
cfg;
|
|
683
|
-
pending = [];
|
|
684
|
-
sequenceNumber = 0;
|
|
685
|
-
flushChain = Promise.resolve();
|
|
686
|
-
progress;
|
|
687
|
-
barrier = null;
|
|
688
|
-
constructor(client, cfg, assignment) {
|
|
689
|
-
this.client = client;
|
|
690
|
-
this.assignment = assignment;
|
|
691
|
-
this.cfg = {
|
|
692
|
-
benchmarkSlug: cfg.benchmarkSlug,
|
|
693
|
-
runId: cfg.runId,
|
|
694
|
-
participantSlug: cfg.participantSlug,
|
|
695
|
-
batchSize: cfg.batchSize ?? DEFAULT_REPORTER_BATCH_SIZE
|
|
696
|
-
};
|
|
697
|
-
this.progress = { done: 0, inFlight: 0, errors: 0, total: assignment.taskRange.count };
|
|
698
|
-
}
|
|
699
|
-
static async claim(cfg) {
|
|
700
|
-
const client = createBenchmarkClient(cfg);
|
|
701
|
-
try {
|
|
702
|
-
const assignment = await client.claimWorker(cfg.benchmarkSlug, cfg.runId, cfg.participantSlug, {
|
|
703
|
-
processKind: cfg.processKind,
|
|
704
|
-
processKey: cfg.processKey
|
|
705
|
-
});
|
|
706
|
-
return assignment ? new _BenchmarkReporter(client, cfg, assignment) : null;
|
|
707
|
-
} catch (error) {
|
|
708
|
-
console.warn(
|
|
709
|
-
`[benchsdk] failed to claim worker for ${cfg.benchmarkSlug}/${cfg.participantSlug}: ${error instanceof Error ? error.message : String(error)}`
|
|
710
|
-
);
|
|
711
|
-
return null;
|
|
712
|
-
}
|
|
713
|
-
}
|
|
714
|
-
get workerAssignment() {
|
|
715
|
-
return this.assignment;
|
|
716
|
-
}
|
|
717
|
-
get taskCount() {
|
|
718
|
-
return this.assignment.taskRange.count;
|
|
719
|
-
}
|
|
720
|
-
get taskIndexStart() {
|
|
721
|
-
return this.assignment.taskRange.start;
|
|
722
|
-
}
|
|
723
|
-
setProgress(progress) {
|
|
724
|
-
this.progress = { ...progress, total: progress.total ?? this.assignment.taskRange.count };
|
|
725
|
-
}
|
|
726
|
-
recordResult(record) {
|
|
727
|
-
this.pending.push(record);
|
|
728
|
-
if (this.pending.length >= this.cfg.batchSize) void this.flush(false);
|
|
729
|
-
}
|
|
730
|
-
async heartbeat(input = {}) {
|
|
731
|
-
const barrier = this.barrier;
|
|
732
|
-
const currentStep = barrier?.step ?? input.currentStep;
|
|
733
|
-
const concurrency = barrier?.concurrency ?? input.concurrency;
|
|
734
|
-
await this.client.heartbeatWorker(this.cfg.benchmarkSlug, this.cfg.runId, this.assignment.workerId, {
|
|
735
|
-
attemptId: this.assignment.attemptId,
|
|
736
|
-
progressDone: this.progress.done,
|
|
737
|
-
progressInFlight: this.progress.inFlight,
|
|
738
|
-
progressErrors: this.progress.errors,
|
|
739
|
-
progressTotal: this.progress.total,
|
|
740
|
-
...currentStep ? { currentStep } : {},
|
|
741
|
-
...concurrency ? { concurrency } : {}
|
|
742
|
-
}).catch(() => {
|
|
743
|
-
});
|
|
744
|
-
}
|
|
745
|
-
async waitForStepReady(input) {
|
|
746
|
-
const startedAt = Date.now();
|
|
747
|
-
const pollIntervalMs = input.pollIntervalMs ?? DEFAULT_READY_POLL_INTERVAL_MS2;
|
|
748
|
-
const concurrency = input.concurrency ?? [{
|
|
749
|
-
step: input.step,
|
|
750
|
-
active: input.active ?? this.assignment.taskRange.count,
|
|
751
|
-
target: input.target ?? this.assignment.taskRange.count
|
|
752
|
-
}];
|
|
753
|
-
this.barrier = { step: input.step, concurrency };
|
|
754
|
-
try {
|
|
755
|
-
while (true) {
|
|
756
|
-
await this.heartbeat({ currentStep: input.step, concurrency });
|
|
757
|
-
const progress = await this.client.getRunProgress(this.cfg.benchmarkSlug, this.cfg.runId).catch(() => null);
|
|
758
|
-
const participant = progress?.participants.find((item) => item.slug === this.cfg.participantSlug);
|
|
759
|
-
const step = participant?.concurrency.find((item) => item.step === input.step);
|
|
760
|
-
if (step?.ready) {
|
|
761
|
-
return {
|
|
762
|
-
active: step.active,
|
|
763
|
-
target: step.target,
|
|
764
|
-
ready: true,
|
|
765
|
-
measuredAt: progress?.generatedAt ?? (/* @__PURE__ */ new Date()).toISOString()
|
|
766
|
-
};
|
|
767
|
-
}
|
|
768
|
-
if (input.timeoutMs !== void 0 && Date.now() - startedAt >= input.timeoutMs) {
|
|
769
|
-
throw new Error(`Timed out waiting for benchmark step "${input.step}" to become ready.`);
|
|
770
|
-
}
|
|
771
|
-
await new Promise((resolve) => setTimeout(resolve, pollIntervalMs));
|
|
772
|
-
}
|
|
773
|
-
} finally {
|
|
774
|
-
this.barrier = null;
|
|
775
|
-
}
|
|
776
|
-
}
|
|
777
|
-
uploadArtifact(input) {
|
|
778
|
-
return this.client.uploadWorkerArtifact(this.cfg.benchmarkSlug, this.cfg.runId, this.assignment.workerId, {
|
|
779
|
-
attemptId: this.assignment.attemptId,
|
|
780
|
-
kind: input.kind,
|
|
781
|
-
name: input.name,
|
|
782
|
-
contentType: input.contentType,
|
|
783
|
-
metadata: input.metadata,
|
|
784
|
-
body: input.body
|
|
785
|
-
}).catch((error) => {
|
|
786
|
-
console.warn(
|
|
787
|
-
`[benchsdk] failed to upload ${input.kind} artifact for worker ${this.assignment.workerId}: ${error instanceof Error ? error.message : String(error)}`
|
|
788
|
-
);
|
|
789
|
-
return null;
|
|
790
|
-
});
|
|
791
|
-
}
|
|
792
|
-
flush(isFinal = false) {
|
|
793
|
-
this.flushChain = this.flushChain.then(async () => {
|
|
794
|
-
while (this.pending.length >= this.cfg.batchSize || isFinal && this.pending.length > 0) {
|
|
795
|
-
const batch = this.pending.slice(0, this.cfg.batchSize);
|
|
796
|
-
try {
|
|
797
|
-
await this.client.sendTaskResults({
|
|
798
|
-
benchmarkSlug: this.cfg.benchmarkSlug,
|
|
799
|
-
runId: this.cfg.runId,
|
|
800
|
-
workerId: this.assignment.workerId,
|
|
801
|
-
attemptId: this.assignment.attemptId,
|
|
802
|
-
sequenceNumber: this.sequenceNumber,
|
|
803
|
-
isFinal: isFinal && batch.length === this.pending.length,
|
|
804
|
-
records: batch
|
|
805
|
-
});
|
|
806
|
-
} catch (error) {
|
|
807
|
-
console.warn(
|
|
808
|
-
`[benchsdk] dropping ${this.pending.length} unsent task result(s) for worker ${this.assignment.workerId}: ${error instanceof Error ? error.message : String(error)}`
|
|
809
|
-
);
|
|
810
|
-
break;
|
|
811
|
-
}
|
|
812
|
-
this.pending.splice(0, batch.length);
|
|
813
|
-
this.sequenceNumber += 1;
|
|
814
|
-
}
|
|
815
|
-
});
|
|
816
|
-
return this.flushChain;
|
|
817
|
-
}
|
|
818
|
-
async finish(failed = false, error) {
|
|
819
|
-
await this.flush(true).catch(() => {
|
|
820
|
-
});
|
|
821
|
-
if (failed) {
|
|
822
|
-
await this.client.failWorker(
|
|
823
|
-
this.cfg.benchmarkSlug,
|
|
824
|
-
this.cfg.runId,
|
|
825
|
-
this.assignment.workerId,
|
|
826
|
-
this.assignment.attemptId,
|
|
827
|
-
error
|
|
828
|
-
).catch(() => {
|
|
829
|
-
});
|
|
830
|
-
return;
|
|
831
|
-
}
|
|
832
|
-
await this.client.completeWorker(
|
|
833
|
-
this.cfg.benchmarkSlug,
|
|
834
|
-
this.cfg.runId,
|
|
835
|
-
this.assignment.workerId,
|
|
836
|
-
this.assignment.attemptId
|
|
837
|
-
).catch(() => {
|
|
838
|
-
});
|
|
839
|
-
}
|
|
840
|
-
};
|
|
841
|
-
function claimBenchmarkReporter(config) {
|
|
842
|
-
return BenchmarkReporter.claim(config);
|
|
843
|
-
}
|
|
844
|
-
|
|
845
|
-
// src/metrics.ts
|
|
846
|
-
import * as fs from "fs";
|
|
847
|
-
import * as os from "os";
|
|
848
|
-
import { monitorEventLoopDelay } from "perf_hooks";
|
|
849
|
-
function readSockstat() {
|
|
850
|
-
try {
|
|
851
|
-
const data = fs.readFileSync("/proc/net/sockstat", "utf-8");
|
|
852
|
-
const out = {};
|
|
853
|
-
for (const line of data.split("\n")) {
|
|
854
|
-
const index = line.indexOf(":");
|
|
855
|
-
if (index < 0) continue;
|
|
856
|
-
const section = line.slice(0, index).trim().toLowerCase();
|
|
857
|
-
const parts = line.slice(index + 1).trim().split(/\s+/);
|
|
858
|
-
for (let i = 0; i + 1 < parts.length; i += 2) {
|
|
859
|
-
const value = Number.parseInt(parts[i + 1], 10);
|
|
860
|
-
if (!Number.isNaN(value)) out[`${section}_${parts[i]}`] = value;
|
|
861
|
-
}
|
|
862
|
-
}
|
|
863
|
-
return out;
|
|
864
|
-
} catch {
|
|
865
|
-
return null;
|
|
866
|
-
}
|
|
867
|
-
}
|
|
868
|
-
function countOpenFds() {
|
|
869
|
-
try {
|
|
870
|
-
return fs.readdirSync("/proc/self/fd").length;
|
|
871
|
-
} catch {
|
|
872
|
-
return null;
|
|
873
|
-
}
|
|
874
|
-
}
|
|
875
|
-
function createSystemMetricsCollector() {
|
|
876
|
-
const startedAt = Date.now();
|
|
877
|
-
const cpuBaseline = process.cpuUsage();
|
|
878
|
-
const eventLoop = monitorEventLoopDelay({ resolution: 20 });
|
|
879
|
-
eventLoop.enable();
|
|
5
|
+
const apiClient = createApiClient(config);
|
|
880
6
|
return {
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
const memory = process.memoryUsage();
|
|
884
|
-
const loadavg2 = os.loadavg();
|
|
885
|
-
const sample = {
|
|
886
|
-
ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
887
|
-
uptimeMs: Date.now() - startedAt,
|
|
888
|
-
cpuUserUs: cpu.user,
|
|
889
|
-
cpuSystemUs: cpu.system,
|
|
890
|
-
memRssMb: Math.round(memory.rss / 1024 / 1024),
|
|
891
|
-
memHeapUsedMb: Math.round(memory.heapUsed / 1024 / 1024),
|
|
892
|
-
memHeapTotalMb: Math.round(memory.heapTotal / 1024 / 1024),
|
|
893
|
-
memExternalMb: Math.round(memory.external / 1024 / 1024),
|
|
894
|
-
eventLoopP50Ms: eventLoop.percentile(50) / 1e6,
|
|
895
|
-
eventLoopP99Ms: eventLoop.percentile(99) / 1e6,
|
|
896
|
-
eventLoopMaxMs: eventLoop.max / 1e6,
|
|
897
|
-
loadavg1m: loadavg2[0],
|
|
898
|
-
loadavg5m: loadavg2[1],
|
|
899
|
-
loadavg15m: loadavg2[2],
|
|
900
|
-
openFds: countOpenFds(),
|
|
901
|
-
sockstat: readSockstat()
|
|
902
|
-
};
|
|
903
|
-
eventLoop.reset();
|
|
904
|
-
return sample;
|
|
905
|
-
},
|
|
906
|
-
stop() {
|
|
907
|
-
eventLoop.disable();
|
|
908
|
-
}
|
|
7
|
+
...apiClient,
|
|
8
|
+
runWorker: (options) => runWorker(apiClient, options)
|
|
909
9
|
};
|
|
910
10
|
}
|
|
911
11
|
|
|
912
|
-
// src/
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
available.push(p);
|
|
922
|
-
}
|
|
923
|
-
}
|
|
924
|
-
return { available, skipped };
|
|
925
|
-
}
|
|
926
|
-
function selectParticipants(all, names) {
|
|
927
|
-
if (!names) return all;
|
|
928
|
-
const unknown = names.filter((n) => !all.some((p) => p.name === n));
|
|
929
|
-
if (unknown.length > 0) {
|
|
930
|
-
console.error(`Unknown participant(s): ${unknown.join(", ")}`);
|
|
931
|
-
console.error(`Available: ${all.map((p) => p.name).join(", ")}`);
|
|
932
|
-
process.exit(1);
|
|
933
|
-
}
|
|
934
|
-
return all.filter((p) => names.includes(p.name));
|
|
935
|
-
}
|
|
12
|
+
// src/index.ts
|
|
13
|
+
import {
|
|
14
|
+
BenchmarkReporter,
|
|
15
|
+
claimBenchmarkReporter,
|
|
16
|
+
createSystemMetricsCollector,
|
|
17
|
+
filterParticipantsByEnv,
|
|
18
|
+
runWorker as runWorker2,
|
|
19
|
+
selectParticipants
|
|
20
|
+
} from "@benchsdk/worker";
|
|
936
21
|
export {
|
|
937
22
|
BenchmarkApiError,
|
|
938
23
|
BenchmarkReporter,
|
|
@@ -940,6 +25,7 @@ export {
|
|
|
940
25
|
createBenchmarkClient,
|
|
941
26
|
createSystemMetricsCollector,
|
|
942
27
|
filterParticipantsByEnv,
|
|
28
|
+
runWorker2 as runWorker,
|
|
943
29
|
selectParticipants
|
|
944
30
|
};
|
|
945
31
|
//# sourceMappingURL=index.js.map
|