@benchsdk/runner 0.3.0 → 0.5.2
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 +5 -2
- package/dist/index.cjs +531 -174
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +108 -51
- package/dist/index.d.ts +108 -51
- package/dist/index.js +525 -176
- package/dist/index.js.map +1 -1
- package/package.json +9 -9
package/dist/index.cjs
CHANGED
|
@@ -30,21 +30,36 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
30
30
|
// src/index.ts
|
|
31
31
|
var src_exports = {};
|
|
32
32
|
__export(src_exports, {
|
|
33
|
+
AuthError: () => import_cli4.AuthError,
|
|
33
34
|
BENCHSDK_RUNNER_VERSION: () => BENCHSDK_RUNNER_VERSION,
|
|
35
|
+
BenchmarkApiError: () => import_api3.BenchmarkApiError,
|
|
36
|
+
BenchmarkConfigError: () => BenchmarkConfigError,
|
|
37
|
+
BenchmarkReporter: () => import_worker3.BenchmarkReporter,
|
|
34
38
|
NoAvailableParticipantsError: () => NoAvailableParticipantsError,
|
|
35
39
|
ScoringSpecError: () => ScoringSpecError,
|
|
36
40
|
TaskError: () => TaskError,
|
|
41
|
+
claimBenchmarkReporter: () => import_worker3.claimBenchmarkReporter,
|
|
42
|
+
createApiClient: () => import_cli4.createApiClient,
|
|
43
|
+
createBenchmarkClient: () => import_api3.createBenchmarkClient,
|
|
44
|
+
createSystemMetricsCollector: () => import_worker3.createSystemMetricsCollector,
|
|
37
45
|
defineBenchmarkConfig: () => defineBenchmarkConfig,
|
|
46
|
+
defineOnComplete: () => defineOnComplete,
|
|
38
47
|
defineTask: () => defineTask,
|
|
48
|
+
filterParticipantsByEnv: () => import_worker3.filterParticipantsByEnv,
|
|
39
49
|
higherIsBetter: () => higherIsBetter,
|
|
40
50
|
lowerIsBetter: () => lowerIsBetter,
|
|
41
51
|
mergeConfig: () => mergeConfig,
|
|
42
52
|
parseCliArgs: () => parseCliArgs,
|
|
53
|
+
resolveAuth: () => import_cli4.resolveAuth,
|
|
43
54
|
run: () => run,
|
|
44
55
|
runBenchmark: () => runBenchmark,
|
|
45
56
|
runBenchmarkFile: () => runBenchmarkFile,
|
|
57
|
+
runBenchmarkWorker: () => runBenchmarkWorker,
|
|
58
|
+
runWorker: () => import_worker3.runWorker,
|
|
46
59
|
score: () => score,
|
|
47
60
|
scoringConfigToSpec: () => scoringConfigToSpec,
|
|
61
|
+
selectParticipants: () => import_worker3.selectParticipants,
|
|
62
|
+
validateBenchmarkConfig: () => validateBenchmarkConfig,
|
|
48
63
|
validateScoringSpec: () => validateScoringSpec
|
|
49
64
|
});
|
|
50
65
|
module.exports = __toCommonJS(src_exports);
|
|
@@ -54,14 +69,55 @@ var TaskError = class extends Error {
|
|
|
54
69
|
code;
|
|
55
70
|
data;
|
|
56
71
|
steps;
|
|
72
|
+
step;
|
|
73
|
+
timeoutMs;
|
|
57
74
|
constructor(message, opts) {
|
|
58
75
|
super(message);
|
|
59
76
|
this.name = "TaskError";
|
|
60
77
|
this.code = opts?.code;
|
|
61
78
|
this.data = opts?.data;
|
|
62
79
|
this.steps = opts?.steps;
|
|
80
|
+
this.step = opts?.step;
|
|
81
|
+
this.timeoutMs = opts?.timeoutMs;
|
|
82
|
+
}
|
|
83
|
+
toString() {
|
|
84
|
+
let s = `[${this.name}${this.code ? ` (${this.code})` : ""}] ${this.message}`;
|
|
85
|
+
if (this.step) {
|
|
86
|
+
s += `
|
|
87
|
+
step: ${this.step}`;
|
|
88
|
+
}
|
|
89
|
+
if (this.timeoutMs !== void 0) {
|
|
90
|
+
s += `
|
|
91
|
+
timeoutMs: ${this.timeoutMs}`;
|
|
92
|
+
}
|
|
93
|
+
if (this.data && Object.keys(this.data).length > 0) {
|
|
94
|
+
s += `
|
|
95
|
+
data: ${JSON.stringify(this.data, null, 2)}`;
|
|
96
|
+
}
|
|
97
|
+
return s;
|
|
98
|
+
}
|
|
99
|
+
};
|
|
100
|
+
var BenchmarkConfigError = class _BenchmarkConfigError extends Error {
|
|
101
|
+
issues;
|
|
102
|
+
constructor(issues) {
|
|
103
|
+
super(_BenchmarkConfigError.formatIssues(issues));
|
|
104
|
+
this.name = "BenchmarkConfigError";
|
|
105
|
+
this.issues = issues;
|
|
106
|
+
}
|
|
107
|
+
static formatIssues(issues) {
|
|
108
|
+
const lines = issues.map((i) => ` - ${i.field}: ${i.message}`);
|
|
109
|
+
return `Invalid benchmark config:
|
|
110
|
+
${lines.join("\n")}
|
|
111
|
+
|
|
112
|
+
Fix the fields above and try again.`;
|
|
63
113
|
}
|
|
64
114
|
};
|
|
115
|
+
function assertFiniteNumber(value, field) {
|
|
116
|
+
if (typeof value !== "number" || !Number.isFinite(value)) {
|
|
117
|
+
throw new Error(`${field} must be a finite number (got ${value})`);
|
|
118
|
+
}
|
|
119
|
+
return value;
|
|
120
|
+
}
|
|
65
121
|
function assertNonEmptyString(value, field) {
|
|
66
122
|
if (typeof value !== "string" || value.trim() === "") {
|
|
67
123
|
throw new Error(`${field} must be a non-empty string`);
|
|
@@ -75,18 +131,6 @@ function assertOnlyAllowedKeys(value, allowed, field) {
|
|
|
75
131
|
}
|
|
76
132
|
}
|
|
77
133
|
}
|
|
78
|
-
function assertPositiveInt(value, field) {
|
|
79
|
-
if (value === void 0) return;
|
|
80
|
-
if (!Number.isInteger(value) || value < 1) {
|
|
81
|
-
throw new Error(`${field} must be an integer >= 1 (got ${value})`);
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
|
-
function assertFiniteNumber(value, field) {
|
|
85
|
-
if (typeof value !== "number" || !Number.isFinite(value)) {
|
|
86
|
-
throw new Error(`${field} must be a finite number (got ${value})`);
|
|
87
|
-
}
|
|
88
|
-
return value;
|
|
89
|
-
}
|
|
90
134
|
function validateBenchmarkScoringConfig(scoring, display) {
|
|
91
135
|
if (!Array.isArray(scoring.metrics) || scoring.metrics.length === 0) {
|
|
92
136
|
throw new Error("scoring.metrics must be a non-empty array");
|
|
@@ -157,154 +201,236 @@ function validateBenchmarkScoringConfig(scoring, display) {
|
|
|
157
201
|
throw new Error(`scoring metric weights must sum to 1.0 (got ${totalWeight.toFixed(3)})`);
|
|
158
202
|
}
|
|
159
203
|
}
|
|
204
|
+
function validateBenchmarkDisplayConfig(display) {
|
|
205
|
+
if (typeof display !== "object" || display === null || Array.isArray(display)) {
|
|
206
|
+
throw new Error("display must be an object");
|
|
207
|
+
}
|
|
208
|
+
assertOnlyAllowedKeys(display, ["metrics", "steps", "overview"], "display");
|
|
209
|
+
const displayMetricKeys = /* @__PURE__ */ new Set();
|
|
210
|
+
if (display.metrics !== void 0) {
|
|
211
|
+
if (!Array.isArray(display.metrics)) {
|
|
212
|
+
throw new Error("display.metrics must be an array");
|
|
213
|
+
}
|
|
214
|
+
for (let i = 0; i < display.metrics.length; i++) {
|
|
215
|
+
const metric = display.metrics[i];
|
|
216
|
+
if (metric === null || typeof metric !== "object" || Array.isArray(metric)) {
|
|
217
|
+
throw new Error(`display.metrics[${i}] must be an object`);
|
|
218
|
+
}
|
|
219
|
+
assertOnlyAllowedKeys(metric, ["key", "label", "unit", "direction", "decimals", "order"], `display.metrics[${i}]`);
|
|
220
|
+
const key = assertNonEmptyString(metric.key, `display.metrics[${i}].key`);
|
|
221
|
+
if (displayMetricKeys.has(key)) {
|
|
222
|
+
throw new Error(`duplicate display metric key: ${key}`);
|
|
223
|
+
}
|
|
224
|
+
displayMetricKeys.add(key);
|
|
225
|
+
assertNonEmptyString(metric.label, `display.metrics[${i}].label`);
|
|
226
|
+
if (metric.unit !== void 0 && typeof metric.unit !== "string") {
|
|
227
|
+
throw new Error(`display.metrics[${i}].unit must be a string`);
|
|
228
|
+
}
|
|
229
|
+
if (metric.direction !== void 0 && metric.direction !== "higher-better" && metric.direction !== "lower-better") {
|
|
230
|
+
throw new Error(`display.metrics[${i}].direction must be 'higher-better' or 'lower-better'`);
|
|
231
|
+
}
|
|
232
|
+
if (metric.decimals !== void 0 && (!Number.isInteger(metric.decimals) || metric.decimals < 0)) {
|
|
233
|
+
throw new Error(`display.metrics[${i}].decimals must be a non-negative integer`);
|
|
234
|
+
}
|
|
235
|
+
if (metric.order !== void 0 && (!Number.isInteger(metric.order) || metric.order < 0)) {
|
|
236
|
+
throw new Error(`display.metrics[${i}].order must be a non-negative integer`);
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
if (display.steps !== void 0) {
|
|
241
|
+
if (!Array.isArray(display.steps)) {
|
|
242
|
+
throw new Error("display.steps must be an array");
|
|
243
|
+
}
|
|
244
|
+
const seenStepKeys = /* @__PURE__ */ new Set();
|
|
245
|
+
for (let i = 0; i < display.steps.length; i++) {
|
|
246
|
+
const step = display.steps[i];
|
|
247
|
+
if (step === null || typeof step !== "object" || Array.isArray(step)) {
|
|
248
|
+
throw new Error(`display.steps[${i}] must be an object`);
|
|
249
|
+
}
|
|
250
|
+
assertOnlyAllowedKeys(step, ["key", "label", "order"], `display.steps[${i}]`);
|
|
251
|
+
const key = assertNonEmptyString(step.key, `display.steps[${i}].key`);
|
|
252
|
+
if (seenStepKeys.has(key)) {
|
|
253
|
+
throw new Error(`duplicate display step key: ${key}`);
|
|
254
|
+
}
|
|
255
|
+
seenStepKeys.add(key);
|
|
256
|
+
assertNonEmptyString(step.label, `display.steps[${i}].label`);
|
|
257
|
+
if (step.order !== void 0 && (!Number.isInteger(step.order) || step.order < 0)) {
|
|
258
|
+
throw new Error(`display.steps[${i}].order must be a non-negative integer`);
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
if (display.overview !== void 0) {
|
|
263
|
+
if (typeof display.overview !== "object" || display.overview === null || Array.isArray(display.overview)) {
|
|
264
|
+
throw new Error("display.overview must be an object");
|
|
265
|
+
}
|
|
266
|
+
assertOnlyAllowedKeys(display.overview, ["defaultMetric", "defaultLayout"], "display.overview");
|
|
267
|
+
const { defaultMetric, defaultLayout } = display.overview;
|
|
268
|
+
if (defaultMetric !== void 0) {
|
|
269
|
+
const metric = assertNonEmptyString(defaultMetric, "display.overview.defaultMetric");
|
|
270
|
+
const validDefaultMetrics = new Set(displayMetricKeys);
|
|
271
|
+
validDefaultMetrics.add("compositeScore");
|
|
272
|
+
validDefaultMetrics.add("task");
|
|
273
|
+
if (display.metrics !== void 0 && !validDefaultMetrics.has(metric)) {
|
|
274
|
+
throw new Error(`display.overview.defaultMetric '${metric}' is not declared in display.metrics and is not a known default (compositeScore, task)`);
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
if (defaultLayout !== void 0 && !["ranking", "cards", "chart", "leaderboard"].includes(defaultLayout)) {
|
|
278
|
+
throw new Error("display.overview.defaultLayout must be 'ranking', 'cards', 'chart', or 'leaderboard'");
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
}
|
|
160
282
|
function defineBenchmarkConfig(config) {
|
|
283
|
+
const issues = validateBenchmarkConfig(config);
|
|
284
|
+
if (config.scoring !== void 0) {
|
|
285
|
+
try {
|
|
286
|
+
validateBenchmarkScoringConfig(config.scoring, config.display);
|
|
287
|
+
} catch (error) {
|
|
288
|
+
issues.push({
|
|
289
|
+
field: "scoring",
|
|
290
|
+
message: error instanceof Error ? error.message : String(error)
|
|
291
|
+
});
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
if (issues.length > 0) {
|
|
295
|
+
throw new BenchmarkConfigError(issues);
|
|
296
|
+
}
|
|
297
|
+
return config;
|
|
298
|
+
}
|
|
299
|
+
function defineTask(task) {
|
|
300
|
+
if (typeof task !== "function") {
|
|
301
|
+
throw new Error("defineTask requires a task function.");
|
|
302
|
+
}
|
|
303
|
+
return task;
|
|
304
|
+
}
|
|
305
|
+
function validateBenchmarkConfig(config) {
|
|
306
|
+
const issues = [];
|
|
161
307
|
if (!config.benchmarkSlug || typeof config.benchmarkSlug !== "string") {
|
|
162
|
-
|
|
308
|
+
issues.push({ field: "benchmarkSlug", message: "is required" });
|
|
163
309
|
}
|
|
164
310
|
if (!config.benchmarkName || typeof config.benchmarkName !== "string") {
|
|
165
|
-
|
|
311
|
+
issues.push({ field: "benchmarkName", message: "is required" });
|
|
312
|
+
}
|
|
313
|
+
if (!Array.isArray(config.participants) || config.participants.length === 0) {
|
|
314
|
+
issues.push({ field: "participants", message: "must be a non-empty array" });
|
|
315
|
+
} else {
|
|
316
|
+
const seenParticipants = /* @__PURE__ */ new Set();
|
|
317
|
+
for (let i = 0; i < config.participants.length; i++) {
|
|
318
|
+
const p = config.participants[i];
|
|
319
|
+
if (p === null || typeof p !== "object" || Array.isArray(p)) {
|
|
320
|
+
issues.push({ field: `participants[${i}]`, message: "must be an object" });
|
|
321
|
+
continue;
|
|
322
|
+
}
|
|
323
|
+
const participant = p;
|
|
324
|
+
if (typeof participant.name !== "string" || participant.name.trim() === "") {
|
|
325
|
+
issues.push({ field: `participants[${i}].name`, message: "must be a non-empty string" });
|
|
326
|
+
} else if (seenParticipants.has(participant.name)) {
|
|
327
|
+
issues.push({ field: `participants[${i}].name`, message: `duplicate participant name: ${participant.name}` });
|
|
328
|
+
} else {
|
|
329
|
+
seenParticipants.add(participant.name);
|
|
330
|
+
}
|
|
331
|
+
if (participant.requiredEnvVars !== void 0 && (!Array.isArray(participant.requiredEnvVars) || !participant.requiredEnvVars.every((v) => typeof v === "string"))) {
|
|
332
|
+
issues.push({ field: `participants[${i}].requiredEnvVars`, message: "must be an array of strings" });
|
|
333
|
+
}
|
|
334
|
+
}
|
|
166
335
|
}
|
|
167
336
|
if (config.phases !== void 0) {
|
|
168
337
|
if (config.iterations !== void 0) {
|
|
169
|
-
|
|
338
|
+
issues.push({ field: "iterations", message: "phases and iterations are mutually exclusive" });
|
|
170
339
|
}
|
|
171
340
|
if (!Array.isArray(config.phases) || config.phases.length === 0) {
|
|
172
|
-
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
341
|
+
issues.push({ field: "phases", message: "must be a non-empty array" });
|
|
342
|
+
} else {
|
|
343
|
+
const seen = /* @__PURE__ */ new Set();
|
|
344
|
+
for (let i = 0; i < config.phases.length; i++) {
|
|
345
|
+
const phase = config.phases[i];
|
|
346
|
+
if (phase === null || typeof phase !== "object" || Array.isArray(phase)) {
|
|
347
|
+
issues.push({ field: `phases[${i}]`, message: "must be an object" });
|
|
348
|
+
continue;
|
|
349
|
+
}
|
|
350
|
+
const phaseObj = phase;
|
|
351
|
+
if (typeof phaseObj.name !== "string" || phaseObj.name.trim() === "") {
|
|
352
|
+
issues.push({ field: `phases[${i}]`, message: "must have a non-empty string name" });
|
|
353
|
+
} else {
|
|
354
|
+
const name = phaseObj.name;
|
|
355
|
+
if (seen.has(name)) {
|
|
356
|
+
issues.push({ field: `phases['${name}']`, message: `duplicate phase name: ${name}` });
|
|
357
|
+
}
|
|
358
|
+
seen.add(name);
|
|
359
|
+
const iterations = phaseObj.iterations;
|
|
360
|
+
if (typeof iterations !== "number" || !Number.isInteger(iterations) || iterations < 1) {
|
|
361
|
+
issues.push({ field: `phases['${name}'].iterations`, message: `must be an integer >= 1 (got ${iterations})` });
|
|
362
|
+
}
|
|
363
|
+
}
|
|
181
364
|
}
|
|
182
|
-
seen.add(phase.name);
|
|
183
|
-
assertPositiveInt(phase.iterations, `phase '${phase.name}' iterations`);
|
|
184
365
|
}
|
|
185
366
|
}
|
|
186
|
-
|
|
187
|
-
|
|
367
|
+
if (config.iterations !== void 0 && (!Number.isInteger(config.iterations) || config.iterations < 1)) {
|
|
368
|
+
issues.push({ field: "iterations", message: `must be an integer >= 1 (got ${config.iterations})` });
|
|
369
|
+
}
|
|
370
|
+
if (config.concurrency !== void 0 && (!Number.isInteger(config.concurrency) || config.concurrency < 1)) {
|
|
371
|
+
issues.push({ field: "concurrency", message: `must be an integer >= 1 (got ${config.concurrency})` });
|
|
372
|
+
}
|
|
188
373
|
if (config.staggerDelayMs !== void 0 && (!Number.isFinite(config.staggerDelayMs) || config.staggerDelayMs < 0)) {
|
|
189
|
-
|
|
374
|
+
issues.push({ field: "staggerDelayMs", message: `must be a number >= 0 (got ${config.staggerDelayMs})` });
|
|
190
375
|
}
|
|
191
376
|
if (config.groupBy !== void 0 && config.groupBy !== "participant" && config.groupBy !== "round") {
|
|
192
|
-
|
|
377
|
+
issues.push({ field: "groupBy", message: `must be 'participant' or 'round' (got ${config.groupBy})` });
|
|
193
378
|
}
|
|
194
379
|
if (config.shapes !== void 0) {
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
380
|
+
if (typeof config.shapes !== "object" || config.shapes === null || Array.isArray(config.shapes)) {
|
|
381
|
+
issues.push({ field: "shapes", message: "must be a plain object" });
|
|
382
|
+
} else {
|
|
383
|
+
for (const [shapeName, shape] of Object.entries(config.shapes)) {
|
|
384
|
+
if (shape === null || typeof shape !== "object" || Array.isArray(shape)) {
|
|
385
|
+
issues.push({ field: `shapes['${shapeName}']`, message: "must be an object" });
|
|
386
|
+
continue;
|
|
387
|
+
}
|
|
388
|
+
const shapeObj = shape;
|
|
389
|
+
const slug = shapeObj.slug;
|
|
390
|
+
if (typeof slug !== "string" || slug === "" || !/^[a-z0-9][a-z0-9-]*$/.test(slug)) {
|
|
391
|
+
issues.push({ field: `shapes['${shapeName}'].slug`, message: `needs a lowercase slug (got ${JSON.stringify(slug)})` });
|
|
392
|
+
}
|
|
393
|
+
const name = shapeObj.name;
|
|
394
|
+
if (name !== void 0 && (typeof name !== "string" || name.trim() === "")) {
|
|
395
|
+
issues.push({ field: `shapes['${shapeName}'].name`, message: "must be a non-empty string" });
|
|
396
|
+
}
|
|
397
|
+
const staggerDelayMs = shapeObj.staggerDelayMs;
|
|
398
|
+
if (staggerDelayMs !== void 0 && (typeof staggerDelayMs !== "number" || !Number.isFinite(staggerDelayMs) || staggerDelayMs < 0)) {
|
|
399
|
+
issues.push({ field: `shapes['${shapeName}'].staggerDelayMs`, message: `must be a number >= 0 (got ${staggerDelayMs})` });
|
|
400
|
+
}
|
|
204
401
|
}
|
|
205
402
|
}
|
|
206
403
|
}
|
|
207
404
|
if (config.dimensions !== void 0) {
|
|
208
405
|
if (config.dimensions === null || typeof config.dimensions !== "object" || Array.isArray(config.dimensions)) {
|
|
209
|
-
|
|
406
|
+
issues.push({ field: "dimensions", message: "must be a plain object" });
|
|
210
407
|
}
|
|
211
408
|
}
|
|
212
|
-
if (config.scoring !== void 0) {
|
|
213
|
-
validateBenchmarkScoringConfig(config.scoring, config.display);
|
|
214
|
-
}
|
|
215
409
|
if (config.customCliFlags !== void 0) {
|
|
216
410
|
if (!Array.isArray(config.customCliFlags) || !config.customCliFlags.every((f) => typeof f === "string" && f.startsWith("--"))) {
|
|
217
|
-
|
|
411
|
+
issues.push({ field: "customCliFlags", message: 'must be an array of strings starting with "--"' });
|
|
218
412
|
}
|
|
219
413
|
}
|
|
220
414
|
if (config.display !== void 0) {
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
throw new Error("display.metrics must be an array");
|
|
229
|
-
}
|
|
230
|
-
for (let i = 0; i < config.display.metrics.length; i++) {
|
|
231
|
-
const metric = config.display.metrics[i];
|
|
232
|
-
if (metric === null || typeof metric !== "object" || Array.isArray(metric)) {
|
|
233
|
-
throw new Error(`display.metrics[${i}] must be an object`);
|
|
234
|
-
}
|
|
235
|
-
assertOnlyAllowedKeys(metric, ["key", "label", "unit", "direction", "decimals", "order"], `display.metrics[${i}]`);
|
|
236
|
-
const key = assertNonEmptyString(metric.key, `display.metrics[${i}].key`);
|
|
237
|
-
if (displayMetricKeys.has(key)) {
|
|
238
|
-
throw new Error(`duplicate display metric key: ${key}`);
|
|
239
|
-
}
|
|
240
|
-
displayMetricKeys.add(key);
|
|
241
|
-
assertNonEmptyString(metric.label, `display.metrics[${i}].label`);
|
|
242
|
-
if (metric.unit !== void 0 && typeof metric.unit !== "string") {
|
|
243
|
-
throw new Error(`display.metrics[${i}].unit must be a string`);
|
|
244
|
-
}
|
|
245
|
-
if (metric.direction !== void 0 && metric.direction !== "higher-better" && metric.direction !== "lower-better") {
|
|
246
|
-
throw new Error(`display.metrics[${i}].direction must be 'higher-better' or 'lower-better'`);
|
|
247
|
-
}
|
|
248
|
-
if (metric.decimals !== void 0 && (!Number.isInteger(metric.decimals) || metric.decimals < 0)) {
|
|
249
|
-
throw new Error(`display.metrics[${i}].decimals must be a non-negative integer`);
|
|
250
|
-
}
|
|
251
|
-
if (metric.order !== void 0 && (!Number.isInteger(metric.order) || metric.order < 0)) {
|
|
252
|
-
throw new Error(`display.metrics[${i}].order must be a non-negative integer`);
|
|
253
|
-
}
|
|
254
|
-
}
|
|
255
|
-
}
|
|
256
|
-
if (config.display.steps !== void 0) {
|
|
257
|
-
if (!Array.isArray(config.display.steps)) {
|
|
258
|
-
throw new Error("display.steps must be an array");
|
|
259
|
-
}
|
|
260
|
-
const seenStepKeys = /* @__PURE__ */ new Set();
|
|
261
|
-
for (let i = 0; i < config.display.steps.length; i++) {
|
|
262
|
-
const step = config.display.steps[i];
|
|
263
|
-
if (step === null || typeof step !== "object" || Array.isArray(step)) {
|
|
264
|
-
throw new Error(`display.steps[${i}] must be an object`);
|
|
265
|
-
}
|
|
266
|
-
assertOnlyAllowedKeys(step, ["key", "label", "order"], `display.steps[${i}]`);
|
|
267
|
-
const key = assertNonEmptyString(step.key, `display.steps[${i}].key`);
|
|
268
|
-
if (seenStepKeys.has(key)) {
|
|
269
|
-
throw new Error(`duplicate display step key: ${key}`);
|
|
270
|
-
}
|
|
271
|
-
seenStepKeys.add(key);
|
|
272
|
-
assertNonEmptyString(step.label, `display.steps[${i}].label`);
|
|
273
|
-
if (step.order !== void 0 && (!Number.isInteger(step.order) || step.order < 0)) {
|
|
274
|
-
throw new Error(`display.steps[${i}].order must be a non-negative integer`);
|
|
275
|
-
}
|
|
276
|
-
}
|
|
277
|
-
}
|
|
278
|
-
if (config.display.overview !== void 0) {
|
|
279
|
-
if (typeof config.display.overview !== "object" || config.display.overview === null || Array.isArray(config.display.overview)) {
|
|
280
|
-
throw new Error("display.overview must be an object");
|
|
281
|
-
}
|
|
282
|
-
assertOnlyAllowedKeys(config.display.overview, ["defaultMetric", "defaultLayout"], "display.overview");
|
|
283
|
-
const { defaultMetric, defaultLayout } = config.display.overview;
|
|
284
|
-
if (defaultMetric !== void 0) {
|
|
285
|
-
const metric = assertNonEmptyString(defaultMetric, "display.overview.defaultMetric");
|
|
286
|
-
const validDefaultMetrics = new Set(displayMetricKeys);
|
|
287
|
-
validDefaultMetrics.add("compositeScore");
|
|
288
|
-
validDefaultMetrics.add("task");
|
|
289
|
-
if (config.display.metrics !== void 0 && !validDefaultMetrics.has(metric)) {
|
|
290
|
-
throw new Error(`display.overview.defaultMetric '${metric}' is not declared in display.metrics and is not a known default (compositeScore, task)`);
|
|
291
|
-
}
|
|
292
|
-
}
|
|
293
|
-
if (defaultLayout !== void 0 && !["ranking", "cards", "chart", "leaderboard"].includes(defaultLayout)) {
|
|
294
|
-
throw new Error("display.overview.defaultLayout must be 'ranking', 'cards', 'chart', or 'leaderboard'");
|
|
295
|
-
}
|
|
415
|
+
try {
|
|
416
|
+
validateBenchmarkDisplayConfig(config.display);
|
|
417
|
+
} catch (error) {
|
|
418
|
+
issues.push({
|
|
419
|
+
field: "display",
|
|
420
|
+
message: error instanceof Error ? error.message : String(error)
|
|
421
|
+
});
|
|
296
422
|
}
|
|
297
423
|
}
|
|
298
424
|
if (config.display?.overview?.defaultMetric === "compositeScore" && config.scoring === void 0 && config.onScore === void 0) {
|
|
299
|
-
|
|
425
|
+
issues.push({
|
|
426
|
+
field: "display.overview.defaultMetric",
|
|
427
|
+
message: "cannot be 'compositeScore' without config.scoring or config.onScore"
|
|
428
|
+
});
|
|
300
429
|
}
|
|
301
|
-
return
|
|
430
|
+
return issues;
|
|
302
431
|
}
|
|
303
|
-
function
|
|
304
|
-
|
|
305
|
-
throw new Error("defineTask requires a task function.");
|
|
306
|
-
}
|
|
307
|
-
return task;
|
|
432
|
+
function defineOnComplete(onComplete) {
|
|
433
|
+
return onComplete;
|
|
308
434
|
}
|
|
309
435
|
|
|
310
436
|
// src/no-available-participants.ts
|
|
@@ -590,12 +716,18 @@ function isStepOutcome(value) {
|
|
|
590
716
|
if (!keys.every((k) => STEP_OUTCOME_KEYS.has(k))) return false;
|
|
591
717
|
return typeof o.stdout === "string" || typeof o.stderr === "string" || typeof o.error === "string";
|
|
592
718
|
}
|
|
593
|
-
function withTimeout(promise,
|
|
719
|
+
function withTimeout(promise, { stepName, timeoutMs, participantSlug }) {
|
|
594
720
|
return new Promise((resolve2, reject) => {
|
|
595
|
-
const timer = setTimeout(
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
721
|
+
const timer = setTimeout(() => {
|
|
722
|
+
const participant = participantSlug ? ` for participant "${participantSlug}"` : "";
|
|
723
|
+
reject(
|
|
724
|
+
new TaskError(`Step "${stepName}" timed out after ${timeoutMs}ms${participant}`, {
|
|
725
|
+
code: "step_timeout",
|
|
726
|
+
step: stepName,
|
|
727
|
+
timeoutMs
|
|
728
|
+
})
|
|
729
|
+
);
|
|
730
|
+
}, timeoutMs);
|
|
599
731
|
promise.then(
|
|
600
732
|
(value) => {
|
|
601
733
|
clearTimeout(timer);
|
|
@@ -608,20 +740,23 @@ function withTimeout(promise, ms, name) {
|
|
|
608
740
|
);
|
|
609
741
|
});
|
|
610
742
|
}
|
|
611
|
-
async function runStepInvocations(name, fn, options) {
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
743
|
+
async function runStepInvocations(name, fn, options, participantSlug) {
|
|
744
|
+
if (options?.concurrency !== void 0) {
|
|
745
|
+
console.warn(`[benchsdk] step "${name}" option "concurrency" is deprecated; use "parallelInvocations"`);
|
|
746
|
+
}
|
|
747
|
+
const requestedParallelism = options?.parallelInvocations ?? options?.concurrency;
|
|
748
|
+
if (requestedParallelism !== void 0 && (!Number.isInteger(requestedParallelism) || requestedParallelism < 1)) {
|
|
749
|
+
throw new Error(`step "${name}" parallelInvocations must be an integer >= 1 (got ${requestedParallelism})`);
|
|
615
750
|
}
|
|
616
751
|
const timeoutMs = options?.timeoutMs;
|
|
617
752
|
if (timeoutMs !== void 0 && (!Number.isFinite(timeoutMs) || timeoutMs < 0)) {
|
|
618
753
|
throw new Error(`step "${name}" timeoutMs must be a number >= 0 (got ${timeoutMs})`);
|
|
619
754
|
}
|
|
620
|
-
const count =
|
|
755
|
+
const count = requestedParallelism ?? 1;
|
|
621
756
|
const invocations = Array.from({ length: count }, () => {
|
|
622
757
|
const promise = Promise.resolve().then(() => fn());
|
|
623
758
|
if (timeoutMs === void 0) return promise;
|
|
624
|
-
return withTimeout(promise, timeoutMs,
|
|
759
|
+
return withTimeout(promise, { stepName: name, timeoutMs, participantSlug });
|
|
625
760
|
});
|
|
626
761
|
if (count === 1) {
|
|
627
762
|
return invocations[0];
|
|
@@ -639,14 +774,14 @@ async function runStepInvocations(name, fn, options) {
|
|
|
639
774
|
if (firstError !== void 0) throw firstError;
|
|
640
775
|
return results;
|
|
641
776
|
}
|
|
642
|
-
async function runStepWithClient(clientStep, name, fn, options) {
|
|
643
|
-
const { concurrency:
|
|
777
|
+
async function runStepWithClient(clientStep, name, fn, options, participantSlug) {
|
|
778
|
+
const { parallelInvocations: runnerParallelism, concurrency: deprecatedConcurrency, timeoutMs, ...clientOptions } = options ?? {};
|
|
644
779
|
const clientStepOptions = {
|
|
645
780
|
...clientOptions,
|
|
646
781
|
timeoutMs,
|
|
647
|
-
stepConcurrency:
|
|
782
|
+
stepConcurrency: runnerParallelism ?? deprecatedConcurrency
|
|
648
783
|
};
|
|
649
|
-
const result = await clientStep(name, () => runStepInvocations(name, fn, options), clientStepOptions);
|
|
784
|
+
const result = await clientStep(name, () => runStepInvocations(name, fn, options, participantSlug), clientStepOptions);
|
|
650
785
|
return result;
|
|
651
786
|
}
|
|
652
787
|
function parseCliArgs(argv, allowedCustomFlags) {
|
|
@@ -814,7 +949,9 @@ function defaultOnResult(record, meta) {
|
|
|
814
949
|
const data = record.data && Object.keys(record.data).length > 0 ? ` ${JSON.stringify(record.data)}` : "";
|
|
815
950
|
console.log(` [${meta.participant}] Task ${n}/${meta.iterations}: success${data}`);
|
|
816
951
|
} else {
|
|
817
|
-
|
|
952
|
+
const detail = record.data?.errorMessage ?? record.data?.error;
|
|
953
|
+
const suffix = typeof detail === "string" && detail.length > 0 ? `: ${detail}` : "";
|
|
954
|
+
console.log(` [${meta.participant}] Task ${n}/${meta.iterations}: FAILED \u2014 ${record.errorCode ?? "unknown error"}${suffix}`);
|
|
818
955
|
}
|
|
819
956
|
}
|
|
820
957
|
function resolveShape(config, shapeName) {
|
|
@@ -855,7 +992,23 @@ function resolveParticipants(config, resolved) {
|
|
|
855
992
|
if (available.length === 0) throw new NoAvailableParticipantsError(skipped);
|
|
856
993
|
return available;
|
|
857
994
|
}
|
|
858
|
-
function
|
|
995
|
+
function resolveTriggerSource(env = process.env) {
|
|
996
|
+
const source = env.BENCH_TRIGGER_SOURCE?.trim();
|
|
997
|
+
if (source) return source;
|
|
998
|
+
return env.GITHUB_EVENT_NAME?.trim() || "manual";
|
|
999
|
+
}
|
|
1000
|
+
function triggerToJson(env = process.env) {
|
|
1001
|
+
const requestedBy = env.BENCH_TRIGGER_REQUESTED_BY?.trim();
|
|
1002
|
+
const requestId = env.BENCH_TRIGGER_REQUEST_ID?.trim();
|
|
1003
|
+
const event = env.GITHUB_EVENT_NAME?.trim();
|
|
1004
|
+
return {
|
|
1005
|
+
source: resolveTriggerSource(env),
|
|
1006
|
+
...event ? { event } : {},
|
|
1007
|
+
...requestedBy ? { requestedBy } : {},
|
|
1008
|
+
...requestId ? { requestId } : {}
|
|
1009
|
+
};
|
|
1010
|
+
}
|
|
1011
|
+
function runConfigToJson(config, resolved, participants, env = process.env) {
|
|
859
1012
|
const phases = config.phases?.map((phase) => ({
|
|
860
1013
|
name: phase.name,
|
|
861
1014
|
iterations: resolved.phaseIterations ?? phase.iterations
|
|
@@ -871,24 +1024,38 @@ function runConfigToJson(config, resolved, participants) {
|
|
|
871
1024
|
groupBy: resolved.groupBy,
|
|
872
1025
|
...config.dimensions ? { dimensions: config.dimensions } : {},
|
|
873
1026
|
...config.scoring ? { scoring: config.scoring } : {},
|
|
874
|
-
participants
|
|
1027
|
+
participants,
|
|
1028
|
+
trigger: triggerToJson(env)
|
|
875
1029
|
};
|
|
876
1030
|
return JSON.parse(JSON.stringify(runConfig));
|
|
877
1031
|
}
|
|
878
|
-
async function runBenchmark(fileConfig, task, argv = []) {
|
|
1032
|
+
async function runBenchmark(fileConfig, task, argv = [], options = {}) {
|
|
879
1033
|
const args = parseCliArgs(argv, fileConfig.customCliFlags);
|
|
880
1034
|
const noIngest = args.noIngest ?? isEnvNoIngest();
|
|
881
1035
|
const shaped = applyShape(fileConfig, resolveShape(fileConfig, args.shape));
|
|
882
1036
|
const config = applyIdentityOverrides(shaped, args);
|
|
883
1037
|
const resolved = mergeConfig(config, args);
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
1038
|
+
let baseUrl = "";
|
|
1039
|
+
let apiKey = "";
|
|
1040
|
+
let token;
|
|
1041
|
+
let orgSlug;
|
|
1042
|
+
let orgId;
|
|
1043
|
+
let client = null;
|
|
1044
|
+
const auth = noIngest ? await (0, import_cli.resolveAuth)({ baseUrl: options.baseUrl, apiKey: options.apiKey }).catch(() => null) : await (0, import_cli.resolveAuth)({ baseUrl: options.baseUrl, apiKey: options.apiKey });
|
|
1045
|
+
if (auth) {
|
|
1046
|
+
baseUrl = auth.apiBaseUrl;
|
|
1047
|
+
apiKey = auth.apiKey ?? "";
|
|
1048
|
+
token = auth.token;
|
|
1049
|
+
orgSlug = auth.orgSlug;
|
|
1050
|
+
orgId = auth.orgId;
|
|
1051
|
+
client = (0, import_api.createBenchmarkClient)({
|
|
1052
|
+
baseUrl: auth.apiBaseUrl,
|
|
1053
|
+
apiKey: auth.apiKey,
|
|
1054
|
+
token: auth.token,
|
|
1055
|
+
orgSlug: auth.orgSlug,
|
|
1056
|
+
orgId: auth.orgId
|
|
1057
|
+
});
|
|
1058
|
+
}
|
|
892
1059
|
const available = resolveParticipants(config, resolved);
|
|
893
1060
|
const schedule = buildSchedule(config, resolved, task);
|
|
894
1061
|
const totalTasks = schedule.length;
|
|
@@ -909,15 +1076,26 @@ async function runBenchmark(fileConfig, task, argv = []) {
|
|
|
909
1076
|
runId = "no-ingest";
|
|
910
1077
|
dashboardUrl = "";
|
|
911
1078
|
} else {
|
|
912
|
-
|
|
1079
|
+
{
|
|
913
1080
|
const benchmarkConfig = {
|
|
914
1081
|
...config.scoring ? { scoring: config.scoring } : {},
|
|
915
1082
|
...config.display ? { display: config.display } : {}
|
|
916
1083
|
};
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
1084
|
+
let initializeTarget = identityIsOurs;
|
|
1085
|
+
if (!initializeTarget) {
|
|
1086
|
+
try {
|
|
1087
|
+
await client.getBenchmark(config.benchmarkSlug);
|
|
1088
|
+
} catch (error) {
|
|
1089
|
+
if (!(error instanceof import_api.BenchmarkApiError && error.status === 404)) throw error;
|
|
1090
|
+
initializeTarget = true;
|
|
1091
|
+
}
|
|
1092
|
+
}
|
|
1093
|
+
const upsertInput = {};
|
|
1094
|
+
if (initializeTarget) {
|
|
1095
|
+
upsertInput.name = config.benchmarkName;
|
|
1096
|
+
if (Object.keys(benchmarkConfig).length > 0) upsertInput.config = benchmarkConfig;
|
|
1097
|
+
}
|
|
1098
|
+
await client.upsertBenchmark(config.benchmarkSlug, upsertInput);
|
|
921
1099
|
}
|
|
922
1100
|
const runConfig = client ? runConfigToJson(config, resolved, available.map((p) => p.name)) : {};
|
|
923
1101
|
if (args.runKey) {
|
|
@@ -926,7 +1104,7 @@ async function runBenchmark(fileConfig, task, argv = []) {
|
|
|
926
1104
|
config: runConfig
|
|
927
1105
|
});
|
|
928
1106
|
runId = run2.id;
|
|
929
|
-
dashboardUrl = dashboardUrlFor(
|
|
1107
|
+
dashboardUrl = dashboardUrlFor(baseUrl, organizationSlug, config.benchmarkSlug, run2.id);
|
|
930
1108
|
for (const participant of available) {
|
|
931
1109
|
await client.upsertParticipant(config.benchmarkSlug, runId, participant.name, { totalTasks });
|
|
932
1110
|
}
|
|
@@ -941,7 +1119,7 @@ async function runBenchmark(fileConfig, task, argv = []) {
|
|
|
941
1119
|
config: runConfig
|
|
942
1120
|
});
|
|
943
1121
|
runId = run2.id;
|
|
944
|
-
dashboardUrl = dashboardUrlFor(
|
|
1122
|
+
dashboardUrl = dashboardUrlFor(baseUrl, organizationSlug, config.benchmarkSlug, run2.id);
|
|
945
1123
|
console.log(`Run created: ${run2.name} (${runId})`);
|
|
946
1124
|
console.log(`View at: ${dashboardUrl}
|
|
947
1125
|
`);
|
|
@@ -950,7 +1128,7 @@ async function runBenchmark(fileConfig, task, argv = []) {
|
|
|
950
1128
|
const onResult = defaultOnResult;
|
|
951
1129
|
let participantRecords;
|
|
952
1130
|
if (resolved.groupBy === "round") {
|
|
953
|
-
participantRecords = await runGroupedByRound(config, schedule, available, resolved, client, runId,
|
|
1131
|
+
participantRecords = await runGroupedByRound(config, schedule, available, resolved, client, runId, baseUrl, apiKey, token, orgSlug, orgId, onResult, noIngest);
|
|
954
1132
|
} else {
|
|
955
1133
|
participantRecords = await runGroupedByParticipant(config, schedule, available, resolved, client, runId, onResult, noIngest);
|
|
956
1134
|
}
|
|
@@ -961,14 +1139,14 @@ async function runBenchmark(fileConfig, task, argv = []) {
|
|
|
961
1139
|
participants: participantRecords,
|
|
962
1140
|
config: resolved
|
|
963
1141
|
};
|
|
964
|
-
if (!noIngest && (config.onScore || config.scoring)) {
|
|
1142
|
+
if (!noIngest && client && (config.onScore || config.scoring)) {
|
|
965
1143
|
try {
|
|
966
1144
|
const spec = config.onScore ? await config.onScore(lowerIsBetter, higherIsBetter) : scoringConfigToSpec(config.scoring, config.dimensions, config.display);
|
|
967
1145
|
const scored = score(outcome, spec, config.display?.metrics);
|
|
968
1146
|
const run2 = {
|
|
969
1147
|
gitSha: process.env.GITHUB_SHA ?? getGitSha(),
|
|
970
1148
|
gitRef: process.env.GITHUB_REF_NAME ?? process.env.GITHUB_REF ?? getGitRef(),
|
|
971
|
-
triggeredBy:
|
|
1149
|
+
triggeredBy: resolveTriggerSource(),
|
|
972
1150
|
nodeVersion: process.version,
|
|
973
1151
|
platform: import_node_os.default.platform(),
|
|
974
1152
|
arch: import_node_os.default.arch()
|
|
@@ -987,6 +1165,22 @@ async function runBenchmark(fileConfig, task, argv = []) {
|
|
|
987
1165
|
if (config.onComplete) await config.onComplete(outcome);
|
|
988
1166
|
return outcome;
|
|
989
1167
|
}
|
|
1168
|
+
async function runBenchmarkWorker(options) {
|
|
1169
|
+
const config = defineBenchmarkConfig({
|
|
1170
|
+
benchmarkSlug: options.benchmarkSlug,
|
|
1171
|
+
benchmarkName: options.benchmarkName ?? options.benchmarkSlug,
|
|
1172
|
+
participants: [options.participant],
|
|
1173
|
+
iterations: options.iterations ?? 1,
|
|
1174
|
+
concurrency: options.concurrency ?? 1,
|
|
1175
|
+
staggerDelayMs: options.staggerDelayMs ?? 0,
|
|
1176
|
+
groupBy: options.groupBy ?? "participant",
|
|
1177
|
+
defaultProviders: [options.participant.name]
|
|
1178
|
+
});
|
|
1179
|
+
const argv = [];
|
|
1180
|
+
if (options.runKey) argv.push("--run-key", options.runKey);
|
|
1181
|
+
if (options.noIngest) argv.push("--dry-run");
|
|
1182
|
+
return runBenchmark(config, options.task, argv);
|
|
1183
|
+
}
|
|
990
1184
|
function getGitSha() {
|
|
991
1185
|
if (process.env.GITHUB_SHA) return process.env.GITHUB_SHA;
|
|
992
1186
|
try {
|
|
@@ -1061,7 +1255,7 @@ async function runGroupedByParticipant(config, schedule, available, resolved, cl
|
|
|
1061
1255
|
participant,
|
|
1062
1256
|
taskIndex: scheduleIndex,
|
|
1063
1257
|
phase: slot.phase,
|
|
1064
|
-
step: (name, fn, options) => runStepWithClient(ctx.step, name, fn, options),
|
|
1258
|
+
step: (name, fn, options) => runStepWithClient(ctx.step, name, fn, options, participant.name),
|
|
1065
1259
|
measure: ctx.measure,
|
|
1066
1260
|
log: ctx.log
|
|
1067
1261
|
});
|
|
@@ -1218,12 +1412,13 @@ async function runTaskRecord(task, participant, scheduleIndex, taskIndex, phase,
|
|
|
1218
1412
|
completedAt: new Date(stepStartedAtMs).toISOString(),
|
|
1219
1413
|
latencyMs: 0
|
|
1220
1414
|
};
|
|
1221
|
-
|
|
1415
|
+
const requestedParallelism = options?.parallelInvocations ?? options?.concurrency;
|
|
1416
|
+
if (requestedParallelism !== void 0) stepRecord.concurrency = requestedParallelism;
|
|
1222
1417
|
if (options?.timeoutMs !== void 0) stepRecord.timeoutMs = options.timeoutMs;
|
|
1223
1418
|
const previousStep = activeStep;
|
|
1224
1419
|
activeStep = stepRecord;
|
|
1225
1420
|
try {
|
|
1226
|
-
const result2 = await runStepInvocations(name, fn, options);
|
|
1421
|
+
const result2 = await runStepInvocations(name, fn, options, participant.name);
|
|
1227
1422
|
const outcome = options?.captureOutput !== false && !Array.isArray(result2) && isStepOutcome(result2) ? result2 : {};
|
|
1228
1423
|
logBuffer.step(taskIndex, name, outcome);
|
|
1229
1424
|
return result2;
|
|
@@ -1293,16 +1488,144 @@ async function runTaskRecord(task, participant, scheduleIndex, taskIndex, phase,
|
|
|
1293
1488
|
var import_node_path = require("path");
|
|
1294
1489
|
var import_node_url = require("url");
|
|
1295
1490
|
var import_cli2 = require("@benchsdk/cli");
|
|
1296
|
-
var
|
|
1491
|
+
var import_api2 = require("@benchsdk/api");
|
|
1492
|
+
var import_worker2 = require("@benchsdk/worker");
|
|
1493
|
+
var USAGE = 'Usage:\n bench run <file.bench.ts> [--shape name] [--provider a,b] [--run-key key]\n [--benchmark slug] [--name "My benchmark"]\n [--iterations N] [--concurrency N] [--stagger-delay-ms N] [--group-by participant|round]\n [--no-ingest | --dry-run] [--check]\n bench check <file.bench.ts> [--base-url <url>] [--api-key <key>]';
|
|
1297
1494
|
function isBenchmarkConfig(value) {
|
|
1298
1495
|
if (typeof value !== "object" || value === null) return false;
|
|
1299
1496
|
const candidate = value;
|
|
1300
1497
|
return typeof candidate.benchmarkSlug === "string" && Array.isArray(candidate.participants);
|
|
1301
1498
|
}
|
|
1499
|
+
function shiftFlag(argv, name) {
|
|
1500
|
+
const prefix = `--${name}`;
|
|
1501
|
+
const prefixEq = `${prefix}=`;
|
|
1502
|
+
const result = [];
|
|
1503
|
+
let value;
|
|
1504
|
+
for (let i = 0; i < argv.length; i++) {
|
|
1505
|
+
const arg = argv[i];
|
|
1506
|
+
if (arg === prefix) {
|
|
1507
|
+
const next = argv[++i];
|
|
1508
|
+
if (!next || next.startsWith("--")) throw new Error(USAGE);
|
|
1509
|
+
value = next;
|
|
1510
|
+
continue;
|
|
1511
|
+
}
|
|
1512
|
+
if (arg.startsWith(prefixEq)) {
|
|
1513
|
+
const eqValue = arg.slice(prefixEq.length);
|
|
1514
|
+
if (!eqValue) throw new Error(USAGE);
|
|
1515
|
+
value = eqValue;
|
|
1516
|
+
continue;
|
|
1517
|
+
}
|
|
1518
|
+
result.push(arg);
|
|
1519
|
+
}
|
|
1520
|
+
return { value, argv: result };
|
|
1521
|
+
}
|
|
1522
|
+
function shiftPlatformFlags(flags) {
|
|
1523
|
+
const { value: baseUrl, argv: withoutBaseUrl } = shiftFlag(flags, "base-url");
|
|
1524
|
+
const { value: apiKey, argv: rest } = shiftFlag(withoutBaseUrl, "api-key");
|
|
1525
|
+
return { baseUrl, apiKey, flags: rest };
|
|
1526
|
+
}
|
|
1527
|
+
async function runCheck(argv) {
|
|
1528
|
+
const [command, ...rest] = argv;
|
|
1529
|
+
const [file, ...flags] = rest;
|
|
1530
|
+
if (command !== "check" || !file || file.startsWith("-")) throw new Error(USAGE);
|
|
1531
|
+
const mod = await import((0, import_node_url.pathToFileURL)((0, import_node_path.resolve)(process.cwd(), file)).href);
|
|
1532
|
+
const config = mod.config;
|
|
1533
|
+
const task = mod.task ?? mod.default;
|
|
1534
|
+
if (!isBenchmarkConfig(config)) {
|
|
1535
|
+
throw new Error(`${file} must export a \`config\` created with defineBenchmarkConfig (with participants).`);
|
|
1536
|
+
}
|
|
1537
|
+
if (typeof task !== "function") {
|
|
1538
|
+
throw new Error(`${file} must export a \`task\` created with defineTask.`);
|
|
1539
|
+
}
|
|
1540
|
+
const cfg = config;
|
|
1541
|
+
const configIssues = validateBenchmarkConfig(cfg);
|
|
1542
|
+
if (configIssues.length > 0) {
|
|
1543
|
+
throw new BenchmarkConfigError(configIssues);
|
|
1544
|
+
}
|
|
1545
|
+
const { baseUrl, apiKey, flags: runnerFlags } = shiftPlatformFlags(flags);
|
|
1546
|
+
const parsed = parseCliArgs(runnerFlags, cfg.customCliFlags ?? []);
|
|
1547
|
+
resolveShape(cfg, parsed.shape);
|
|
1548
|
+
const dryRun = parsed.noIngest ?? false;
|
|
1549
|
+
let client;
|
|
1550
|
+
let apiOk = dryRun;
|
|
1551
|
+
let auth = null;
|
|
1552
|
+
if (!dryRun) {
|
|
1553
|
+
try {
|
|
1554
|
+
auth = await (0, import_cli2.resolveAuth)({ baseUrl, apiKey });
|
|
1555
|
+
client = (0, import_api2.createBenchmarkClient)({
|
|
1556
|
+
baseUrl: auth.apiBaseUrl,
|
|
1557
|
+
apiKey: auth.apiKey,
|
|
1558
|
+
token: auth.token,
|
|
1559
|
+
orgSlug: auth.orgSlug,
|
|
1560
|
+
orgId: auth.orgId
|
|
1561
|
+
});
|
|
1562
|
+
await client.listBenchmarks({ limit: 1 });
|
|
1563
|
+
apiOk = true;
|
|
1564
|
+
} catch (err) {
|
|
1565
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
1566
|
+
console.warn(`[benchsdk] API connectivity check failed: ${message}`);
|
|
1567
|
+
}
|
|
1568
|
+
}
|
|
1569
|
+
const effectiveProviderNames = parsed.providers ?? cfg.defaultProviders;
|
|
1570
|
+
let selected;
|
|
1571
|
+
try {
|
|
1572
|
+
selected = (0, import_worker2.selectParticipants)(cfg.participants, effectiveProviderNames);
|
|
1573
|
+
} catch (err) {
|
|
1574
|
+
throw new Error(`Participant selection failed: ${err instanceof Error ? err.message : err}`);
|
|
1575
|
+
}
|
|
1576
|
+
const { available, skipped } = (0, import_worker2.filterParticipantsByEnv)(selected);
|
|
1577
|
+
let scoringOk = true;
|
|
1578
|
+
if (cfg.onScore) {
|
|
1579
|
+
try {
|
|
1580
|
+
const spec = await cfg.onScore(lowerIsBetter, higherIsBetter);
|
|
1581
|
+
validateScoringSpec(spec);
|
|
1582
|
+
} catch (err) {
|
|
1583
|
+
scoringOk = false;
|
|
1584
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
1585
|
+
console.warn(`[benchsdk] Scoring validation failed: ${message}`);
|
|
1586
|
+
}
|
|
1587
|
+
} else if (cfg.scoring) {
|
|
1588
|
+
try {
|
|
1589
|
+
const spec = scoringConfigToSpec(cfg.scoring, cfg.dimensions);
|
|
1590
|
+
validateScoringSpec(spec);
|
|
1591
|
+
} catch (err) {
|
|
1592
|
+
scoringOk = false;
|
|
1593
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
1594
|
+
console.warn(`[benchsdk] Scoring validation failed: ${message}`);
|
|
1595
|
+
}
|
|
1596
|
+
}
|
|
1597
|
+
const missingPlatformAuth = dryRun ? [] : auth ? [] : [["BENCHMARKS_PLATFORM_API_KEY or BENCHMARKS_PLATFORM_TOKEN", void 0]];
|
|
1598
|
+
for (const [name] of missingPlatformAuth) {
|
|
1599
|
+
console.warn(`[benchsdk] ${name} is not set`);
|
|
1600
|
+
}
|
|
1601
|
+
const report = {
|
|
1602
|
+
file,
|
|
1603
|
+
benchmarkSlug: cfg.benchmarkSlug,
|
|
1604
|
+
apiOk,
|
|
1605
|
+
envOk: dryRun || missingPlatformAuth.length === 0,
|
|
1606
|
+
participants: {
|
|
1607
|
+
requested: selected.map((p) => p.name),
|
|
1608
|
+
available: available.map((p) => p.name),
|
|
1609
|
+
skipped: skipped.map((s) => ({ name: s.name, missing: s.missing }))
|
|
1610
|
+
},
|
|
1611
|
+
scoringOk: cfg.scoring || cfg.onScore ? scoringOk : void 0
|
|
1612
|
+
};
|
|
1613
|
+
console.log(JSON.stringify(report, null, 2));
|
|
1614
|
+
const authFailure = !dryRun && missingPlatformAuth.length > 0;
|
|
1615
|
+
if (!apiOk || available.length === 0 || scoringOk === false || authFailure) {
|
|
1616
|
+
throw new Error("Benchmark check failed. See warnings above for details.");
|
|
1617
|
+
}
|
|
1618
|
+
}
|
|
1302
1619
|
async function runBenchmarkFile(argv) {
|
|
1303
1620
|
const [command, ...rest] = argv;
|
|
1304
1621
|
const [file, ...flags] = rest;
|
|
1305
1622
|
if (command !== "run" || !file || file.startsWith("-")) throw new Error(USAGE);
|
|
1623
|
+
const check = flags.includes("--check") || flags.includes("--validate");
|
|
1624
|
+
if (check) {
|
|
1625
|
+
const checkFlags = flags.filter((f) => f !== "--check" && f !== "--validate");
|
|
1626
|
+
return runCheck(["check", file, ...checkFlags]);
|
|
1627
|
+
}
|
|
1628
|
+
const { baseUrl, apiKey, flags: runnerFlags } = shiftPlatformFlags(flags);
|
|
1306
1629
|
const mod = await import((0, import_node_url.pathToFileURL)((0, import_node_path.resolve)(process.cwd(), file)).href);
|
|
1307
1630
|
const config = mod.config;
|
|
1308
1631
|
const task = mod.task ?? mod.default;
|
|
@@ -1312,44 +1635,78 @@ async function runBenchmarkFile(argv) {
|
|
|
1312
1635
|
if (typeof task !== "function") {
|
|
1313
1636
|
throw new Error(`${file} must export a \`task\` created with defineTask.`);
|
|
1314
1637
|
}
|
|
1315
|
-
|
|
1638
|
+
const envFlags = [];
|
|
1639
|
+
if (process.env.BENCHMARK_SLUG) {
|
|
1640
|
+
envFlags.push("--benchmark", process.env.BENCHMARK_SLUG);
|
|
1641
|
+
}
|
|
1642
|
+
if (process.env.BENCHMARK_NAME) {
|
|
1643
|
+
envFlags.push("--name", process.env.BENCHMARK_NAME);
|
|
1644
|
+
}
|
|
1645
|
+
await runBenchmark(
|
|
1646
|
+
config,
|
|
1647
|
+
task,
|
|
1648
|
+
[...envFlags, ...runnerFlags],
|
|
1649
|
+
{ baseUrl, apiKey }
|
|
1650
|
+
);
|
|
1316
1651
|
}
|
|
1317
1652
|
async function run(argv) {
|
|
1318
|
-
|
|
1319
|
-
return (0, import_cli2.run)(argv);
|
|
1320
|
-
}
|
|
1653
|
+
const [command, ...rest] = argv;
|
|
1321
1654
|
try {
|
|
1322
|
-
|
|
1655
|
+
if (command === "run") {
|
|
1656
|
+
await runBenchmarkFile(argv);
|
|
1657
|
+
} else if (command === "check") {
|
|
1658
|
+
await runCheck(argv);
|
|
1659
|
+
} else {
|
|
1660
|
+
return (0, import_cli2.run)(argv);
|
|
1661
|
+
}
|
|
1323
1662
|
process.exit(0);
|
|
1324
1663
|
} catch (err) {
|
|
1325
1664
|
if (err instanceof NoAvailableParticipantsError) {
|
|
1326
1665
|
console.log(err.message);
|
|
1327
1666
|
process.exit(0);
|
|
1328
1667
|
}
|
|
1329
|
-
console.error("Benchmark failed:", err
|
|
1668
|
+
console.error("Benchmark failed:", String(err));
|
|
1330
1669
|
process.exit(1);
|
|
1331
1670
|
}
|
|
1332
1671
|
}
|
|
1333
1672
|
|
|
1334
1673
|
// src/index.ts
|
|
1335
|
-
var
|
|
1674
|
+
var import_cli4 = require("@benchsdk/cli");
|
|
1675
|
+
var import_worker3 = require("@benchsdk/worker");
|
|
1676
|
+
var import_api3 = require("@benchsdk/api");
|
|
1677
|
+
var BENCHSDK_RUNNER_VERSION = "0.5.2";
|
|
1336
1678
|
// Annotate the CommonJS export names for ESM import in node:
|
|
1337
1679
|
0 && (module.exports = {
|
|
1680
|
+
AuthError,
|
|
1338
1681
|
BENCHSDK_RUNNER_VERSION,
|
|
1682
|
+
BenchmarkApiError,
|
|
1683
|
+
BenchmarkConfigError,
|
|
1684
|
+
BenchmarkReporter,
|
|
1339
1685
|
NoAvailableParticipantsError,
|
|
1340
1686
|
ScoringSpecError,
|
|
1341
1687
|
TaskError,
|
|
1688
|
+
claimBenchmarkReporter,
|
|
1689
|
+
createApiClient,
|
|
1690
|
+
createBenchmarkClient,
|
|
1691
|
+
createSystemMetricsCollector,
|
|
1342
1692
|
defineBenchmarkConfig,
|
|
1693
|
+
defineOnComplete,
|
|
1343
1694
|
defineTask,
|
|
1695
|
+
filterParticipantsByEnv,
|
|
1344
1696
|
higherIsBetter,
|
|
1345
1697
|
lowerIsBetter,
|
|
1346
1698
|
mergeConfig,
|
|
1347
1699
|
parseCliArgs,
|
|
1700
|
+
resolveAuth,
|
|
1348
1701
|
run,
|
|
1349
1702
|
runBenchmark,
|
|
1350
1703
|
runBenchmarkFile,
|
|
1704
|
+
runBenchmarkWorker,
|
|
1705
|
+
runWorker,
|
|
1351
1706
|
score,
|
|
1352
1707
|
scoringConfigToSpec,
|
|
1708
|
+
selectParticipants,
|
|
1709
|
+
validateBenchmarkConfig,
|
|
1353
1710
|
validateScoringSpec
|
|
1354
1711
|
});
|
|
1355
1712
|
//# sourceMappingURL=index.cjs.map
|