@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.js
CHANGED
|
@@ -3,14 +3,55 @@ var TaskError = class extends Error {
|
|
|
3
3
|
code;
|
|
4
4
|
data;
|
|
5
5
|
steps;
|
|
6
|
+
step;
|
|
7
|
+
timeoutMs;
|
|
6
8
|
constructor(message, opts) {
|
|
7
9
|
super(message);
|
|
8
10
|
this.name = "TaskError";
|
|
9
11
|
this.code = opts?.code;
|
|
10
12
|
this.data = opts?.data;
|
|
11
13
|
this.steps = opts?.steps;
|
|
14
|
+
this.step = opts?.step;
|
|
15
|
+
this.timeoutMs = opts?.timeoutMs;
|
|
16
|
+
}
|
|
17
|
+
toString() {
|
|
18
|
+
let s = `[${this.name}${this.code ? ` (${this.code})` : ""}] ${this.message}`;
|
|
19
|
+
if (this.step) {
|
|
20
|
+
s += `
|
|
21
|
+
step: ${this.step}`;
|
|
22
|
+
}
|
|
23
|
+
if (this.timeoutMs !== void 0) {
|
|
24
|
+
s += `
|
|
25
|
+
timeoutMs: ${this.timeoutMs}`;
|
|
26
|
+
}
|
|
27
|
+
if (this.data && Object.keys(this.data).length > 0) {
|
|
28
|
+
s += `
|
|
29
|
+
data: ${JSON.stringify(this.data, null, 2)}`;
|
|
30
|
+
}
|
|
31
|
+
return s;
|
|
32
|
+
}
|
|
33
|
+
};
|
|
34
|
+
var BenchmarkConfigError = class _BenchmarkConfigError extends Error {
|
|
35
|
+
issues;
|
|
36
|
+
constructor(issues) {
|
|
37
|
+
super(_BenchmarkConfigError.formatIssues(issues));
|
|
38
|
+
this.name = "BenchmarkConfigError";
|
|
39
|
+
this.issues = issues;
|
|
40
|
+
}
|
|
41
|
+
static formatIssues(issues) {
|
|
42
|
+
const lines = issues.map((i) => ` - ${i.field}: ${i.message}`);
|
|
43
|
+
return `Invalid benchmark config:
|
|
44
|
+
${lines.join("\n")}
|
|
45
|
+
|
|
46
|
+
Fix the fields above and try again.`;
|
|
12
47
|
}
|
|
13
48
|
};
|
|
49
|
+
function assertFiniteNumber(value, field) {
|
|
50
|
+
if (typeof value !== "number" || !Number.isFinite(value)) {
|
|
51
|
+
throw new Error(`${field} must be a finite number (got ${value})`);
|
|
52
|
+
}
|
|
53
|
+
return value;
|
|
54
|
+
}
|
|
14
55
|
function assertNonEmptyString(value, field) {
|
|
15
56
|
if (typeof value !== "string" || value.trim() === "") {
|
|
16
57
|
throw new Error(`${field} must be a non-empty string`);
|
|
@@ -24,18 +65,6 @@ function assertOnlyAllowedKeys(value, allowed, field) {
|
|
|
24
65
|
}
|
|
25
66
|
}
|
|
26
67
|
}
|
|
27
|
-
function assertPositiveInt(value, field) {
|
|
28
|
-
if (value === void 0) return;
|
|
29
|
-
if (!Number.isInteger(value) || value < 1) {
|
|
30
|
-
throw new Error(`${field} must be an integer >= 1 (got ${value})`);
|
|
31
|
-
}
|
|
32
|
-
}
|
|
33
|
-
function assertFiniteNumber(value, field) {
|
|
34
|
-
if (typeof value !== "number" || !Number.isFinite(value)) {
|
|
35
|
-
throw new Error(`${field} must be a finite number (got ${value})`);
|
|
36
|
-
}
|
|
37
|
-
return value;
|
|
38
|
-
}
|
|
39
68
|
function validateBenchmarkScoringConfig(scoring, display) {
|
|
40
69
|
if (!Array.isArray(scoring.metrics) || scoring.metrics.length === 0) {
|
|
41
70
|
throw new Error("scoring.metrics must be a non-empty array");
|
|
@@ -106,154 +135,236 @@ function validateBenchmarkScoringConfig(scoring, display) {
|
|
|
106
135
|
throw new Error(`scoring metric weights must sum to 1.0 (got ${totalWeight.toFixed(3)})`);
|
|
107
136
|
}
|
|
108
137
|
}
|
|
138
|
+
function validateBenchmarkDisplayConfig(display) {
|
|
139
|
+
if (typeof display !== "object" || display === null || Array.isArray(display)) {
|
|
140
|
+
throw new Error("display must be an object");
|
|
141
|
+
}
|
|
142
|
+
assertOnlyAllowedKeys(display, ["metrics", "steps", "overview"], "display");
|
|
143
|
+
const displayMetricKeys = /* @__PURE__ */ new Set();
|
|
144
|
+
if (display.metrics !== void 0) {
|
|
145
|
+
if (!Array.isArray(display.metrics)) {
|
|
146
|
+
throw new Error("display.metrics must be an array");
|
|
147
|
+
}
|
|
148
|
+
for (let i = 0; i < display.metrics.length; i++) {
|
|
149
|
+
const metric = display.metrics[i];
|
|
150
|
+
if (metric === null || typeof metric !== "object" || Array.isArray(metric)) {
|
|
151
|
+
throw new Error(`display.metrics[${i}] must be an object`);
|
|
152
|
+
}
|
|
153
|
+
assertOnlyAllowedKeys(metric, ["key", "label", "unit", "direction", "decimals", "order"], `display.metrics[${i}]`);
|
|
154
|
+
const key = assertNonEmptyString(metric.key, `display.metrics[${i}].key`);
|
|
155
|
+
if (displayMetricKeys.has(key)) {
|
|
156
|
+
throw new Error(`duplicate display metric key: ${key}`);
|
|
157
|
+
}
|
|
158
|
+
displayMetricKeys.add(key);
|
|
159
|
+
assertNonEmptyString(metric.label, `display.metrics[${i}].label`);
|
|
160
|
+
if (metric.unit !== void 0 && typeof metric.unit !== "string") {
|
|
161
|
+
throw new Error(`display.metrics[${i}].unit must be a string`);
|
|
162
|
+
}
|
|
163
|
+
if (metric.direction !== void 0 && metric.direction !== "higher-better" && metric.direction !== "lower-better") {
|
|
164
|
+
throw new Error(`display.metrics[${i}].direction must be 'higher-better' or 'lower-better'`);
|
|
165
|
+
}
|
|
166
|
+
if (metric.decimals !== void 0 && (!Number.isInteger(metric.decimals) || metric.decimals < 0)) {
|
|
167
|
+
throw new Error(`display.metrics[${i}].decimals must be a non-negative integer`);
|
|
168
|
+
}
|
|
169
|
+
if (metric.order !== void 0 && (!Number.isInteger(metric.order) || metric.order < 0)) {
|
|
170
|
+
throw new Error(`display.metrics[${i}].order must be a non-negative integer`);
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
if (display.steps !== void 0) {
|
|
175
|
+
if (!Array.isArray(display.steps)) {
|
|
176
|
+
throw new Error("display.steps must be an array");
|
|
177
|
+
}
|
|
178
|
+
const seenStepKeys = /* @__PURE__ */ new Set();
|
|
179
|
+
for (let i = 0; i < display.steps.length; i++) {
|
|
180
|
+
const step = display.steps[i];
|
|
181
|
+
if (step === null || typeof step !== "object" || Array.isArray(step)) {
|
|
182
|
+
throw new Error(`display.steps[${i}] must be an object`);
|
|
183
|
+
}
|
|
184
|
+
assertOnlyAllowedKeys(step, ["key", "label", "order"], `display.steps[${i}]`);
|
|
185
|
+
const key = assertNonEmptyString(step.key, `display.steps[${i}].key`);
|
|
186
|
+
if (seenStepKeys.has(key)) {
|
|
187
|
+
throw new Error(`duplicate display step key: ${key}`);
|
|
188
|
+
}
|
|
189
|
+
seenStepKeys.add(key);
|
|
190
|
+
assertNonEmptyString(step.label, `display.steps[${i}].label`);
|
|
191
|
+
if (step.order !== void 0 && (!Number.isInteger(step.order) || step.order < 0)) {
|
|
192
|
+
throw new Error(`display.steps[${i}].order must be a non-negative integer`);
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
if (display.overview !== void 0) {
|
|
197
|
+
if (typeof display.overview !== "object" || display.overview === null || Array.isArray(display.overview)) {
|
|
198
|
+
throw new Error("display.overview must be an object");
|
|
199
|
+
}
|
|
200
|
+
assertOnlyAllowedKeys(display.overview, ["defaultMetric", "defaultLayout"], "display.overview");
|
|
201
|
+
const { defaultMetric, defaultLayout } = display.overview;
|
|
202
|
+
if (defaultMetric !== void 0) {
|
|
203
|
+
const metric = assertNonEmptyString(defaultMetric, "display.overview.defaultMetric");
|
|
204
|
+
const validDefaultMetrics = new Set(displayMetricKeys);
|
|
205
|
+
validDefaultMetrics.add("compositeScore");
|
|
206
|
+
validDefaultMetrics.add("task");
|
|
207
|
+
if (display.metrics !== void 0 && !validDefaultMetrics.has(metric)) {
|
|
208
|
+
throw new Error(`display.overview.defaultMetric '${metric}' is not declared in display.metrics and is not a known default (compositeScore, task)`);
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
if (defaultLayout !== void 0 && !["ranking", "cards", "chart", "leaderboard"].includes(defaultLayout)) {
|
|
212
|
+
throw new Error("display.overview.defaultLayout must be 'ranking', 'cards', 'chart', or 'leaderboard'");
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
}
|
|
109
216
|
function defineBenchmarkConfig(config) {
|
|
217
|
+
const issues = validateBenchmarkConfig(config);
|
|
218
|
+
if (config.scoring !== void 0) {
|
|
219
|
+
try {
|
|
220
|
+
validateBenchmarkScoringConfig(config.scoring, config.display);
|
|
221
|
+
} catch (error) {
|
|
222
|
+
issues.push({
|
|
223
|
+
field: "scoring",
|
|
224
|
+
message: error instanceof Error ? error.message : String(error)
|
|
225
|
+
});
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
if (issues.length > 0) {
|
|
229
|
+
throw new BenchmarkConfigError(issues);
|
|
230
|
+
}
|
|
231
|
+
return config;
|
|
232
|
+
}
|
|
233
|
+
function defineTask(task) {
|
|
234
|
+
if (typeof task !== "function") {
|
|
235
|
+
throw new Error("defineTask requires a task function.");
|
|
236
|
+
}
|
|
237
|
+
return task;
|
|
238
|
+
}
|
|
239
|
+
function validateBenchmarkConfig(config) {
|
|
240
|
+
const issues = [];
|
|
110
241
|
if (!config.benchmarkSlug || typeof config.benchmarkSlug !== "string") {
|
|
111
|
-
|
|
242
|
+
issues.push({ field: "benchmarkSlug", message: "is required" });
|
|
112
243
|
}
|
|
113
244
|
if (!config.benchmarkName || typeof config.benchmarkName !== "string") {
|
|
114
|
-
|
|
245
|
+
issues.push({ field: "benchmarkName", message: "is required" });
|
|
246
|
+
}
|
|
247
|
+
if (!Array.isArray(config.participants) || config.participants.length === 0) {
|
|
248
|
+
issues.push({ field: "participants", message: "must be a non-empty array" });
|
|
249
|
+
} else {
|
|
250
|
+
const seenParticipants = /* @__PURE__ */ new Set();
|
|
251
|
+
for (let i = 0; i < config.participants.length; i++) {
|
|
252
|
+
const p = config.participants[i];
|
|
253
|
+
if (p === null || typeof p !== "object" || Array.isArray(p)) {
|
|
254
|
+
issues.push({ field: `participants[${i}]`, message: "must be an object" });
|
|
255
|
+
continue;
|
|
256
|
+
}
|
|
257
|
+
const participant = p;
|
|
258
|
+
if (typeof participant.name !== "string" || participant.name.trim() === "") {
|
|
259
|
+
issues.push({ field: `participants[${i}].name`, message: "must be a non-empty string" });
|
|
260
|
+
} else if (seenParticipants.has(participant.name)) {
|
|
261
|
+
issues.push({ field: `participants[${i}].name`, message: `duplicate participant name: ${participant.name}` });
|
|
262
|
+
} else {
|
|
263
|
+
seenParticipants.add(participant.name);
|
|
264
|
+
}
|
|
265
|
+
if (participant.requiredEnvVars !== void 0 && (!Array.isArray(participant.requiredEnvVars) || !participant.requiredEnvVars.every((v) => typeof v === "string"))) {
|
|
266
|
+
issues.push({ field: `participants[${i}].requiredEnvVars`, message: "must be an array of strings" });
|
|
267
|
+
}
|
|
268
|
+
}
|
|
115
269
|
}
|
|
116
270
|
if (config.phases !== void 0) {
|
|
117
271
|
if (config.iterations !== void 0) {
|
|
118
|
-
|
|
272
|
+
issues.push({ field: "iterations", message: "phases and iterations are mutually exclusive" });
|
|
119
273
|
}
|
|
120
274
|
if (!Array.isArray(config.phases) || config.phases.length === 0) {
|
|
121
|
-
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
275
|
+
issues.push({ field: "phases", message: "must be a non-empty array" });
|
|
276
|
+
} else {
|
|
277
|
+
const seen = /* @__PURE__ */ new Set();
|
|
278
|
+
for (let i = 0; i < config.phases.length; i++) {
|
|
279
|
+
const phase = config.phases[i];
|
|
280
|
+
if (phase === null || typeof phase !== "object" || Array.isArray(phase)) {
|
|
281
|
+
issues.push({ field: `phases[${i}]`, message: "must be an object" });
|
|
282
|
+
continue;
|
|
283
|
+
}
|
|
284
|
+
const phaseObj = phase;
|
|
285
|
+
if (typeof phaseObj.name !== "string" || phaseObj.name.trim() === "") {
|
|
286
|
+
issues.push({ field: `phases[${i}]`, message: "must have a non-empty string name" });
|
|
287
|
+
} else {
|
|
288
|
+
const name = phaseObj.name;
|
|
289
|
+
if (seen.has(name)) {
|
|
290
|
+
issues.push({ field: `phases['${name}']`, message: `duplicate phase name: ${name}` });
|
|
291
|
+
}
|
|
292
|
+
seen.add(name);
|
|
293
|
+
const iterations = phaseObj.iterations;
|
|
294
|
+
if (typeof iterations !== "number" || !Number.isInteger(iterations) || iterations < 1) {
|
|
295
|
+
issues.push({ field: `phases['${name}'].iterations`, message: `must be an integer >= 1 (got ${iterations})` });
|
|
296
|
+
}
|
|
297
|
+
}
|
|
130
298
|
}
|
|
131
|
-
seen.add(phase.name);
|
|
132
|
-
assertPositiveInt(phase.iterations, `phase '${phase.name}' iterations`);
|
|
133
299
|
}
|
|
134
300
|
}
|
|
135
|
-
|
|
136
|
-
|
|
301
|
+
if (config.iterations !== void 0 && (!Number.isInteger(config.iterations) || config.iterations < 1)) {
|
|
302
|
+
issues.push({ field: "iterations", message: `must be an integer >= 1 (got ${config.iterations})` });
|
|
303
|
+
}
|
|
304
|
+
if (config.concurrency !== void 0 && (!Number.isInteger(config.concurrency) || config.concurrency < 1)) {
|
|
305
|
+
issues.push({ field: "concurrency", message: `must be an integer >= 1 (got ${config.concurrency})` });
|
|
306
|
+
}
|
|
137
307
|
if (config.staggerDelayMs !== void 0 && (!Number.isFinite(config.staggerDelayMs) || config.staggerDelayMs < 0)) {
|
|
138
|
-
|
|
308
|
+
issues.push({ field: "staggerDelayMs", message: `must be a number >= 0 (got ${config.staggerDelayMs})` });
|
|
139
309
|
}
|
|
140
310
|
if (config.groupBy !== void 0 && config.groupBy !== "participant" && config.groupBy !== "round") {
|
|
141
|
-
|
|
311
|
+
issues.push({ field: "groupBy", message: `must be 'participant' or 'round' (got ${config.groupBy})` });
|
|
142
312
|
}
|
|
143
313
|
if (config.shapes !== void 0) {
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
314
|
+
if (typeof config.shapes !== "object" || config.shapes === null || Array.isArray(config.shapes)) {
|
|
315
|
+
issues.push({ field: "shapes", message: "must be a plain object" });
|
|
316
|
+
} else {
|
|
317
|
+
for (const [shapeName, shape] of Object.entries(config.shapes)) {
|
|
318
|
+
if (shape === null || typeof shape !== "object" || Array.isArray(shape)) {
|
|
319
|
+
issues.push({ field: `shapes['${shapeName}']`, message: "must be an object" });
|
|
320
|
+
continue;
|
|
321
|
+
}
|
|
322
|
+
const shapeObj = shape;
|
|
323
|
+
const slug = shapeObj.slug;
|
|
324
|
+
if (typeof slug !== "string" || slug === "" || !/^[a-z0-9][a-z0-9-]*$/.test(slug)) {
|
|
325
|
+
issues.push({ field: `shapes['${shapeName}'].slug`, message: `needs a lowercase slug (got ${JSON.stringify(slug)})` });
|
|
326
|
+
}
|
|
327
|
+
const name = shapeObj.name;
|
|
328
|
+
if (name !== void 0 && (typeof name !== "string" || name.trim() === "")) {
|
|
329
|
+
issues.push({ field: `shapes['${shapeName}'].name`, message: "must be a non-empty string" });
|
|
330
|
+
}
|
|
331
|
+
const staggerDelayMs = shapeObj.staggerDelayMs;
|
|
332
|
+
if (staggerDelayMs !== void 0 && (typeof staggerDelayMs !== "number" || !Number.isFinite(staggerDelayMs) || staggerDelayMs < 0)) {
|
|
333
|
+
issues.push({ field: `shapes['${shapeName}'].staggerDelayMs`, message: `must be a number >= 0 (got ${staggerDelayMs})` });
|
|
334
|
+
}
|
|
153
335
|
}
|
|
154
336
|
}
|
|
155
337
|
}
|
|
156
338
|
if (config.dimensions !== void 0) {
|
|
157
339
|
if (config.dimensions === null || typeof config.dimensions !== "object" || Array.isArray(config.dimensions)) {
|
|
158
|
-
|
|
340
|
+
issues.push({ field: "dimensions", message: "must be a plain object" });
|
|
159
341
|
}
|
|
160
342
|
}
|
|
161
|
-
if (config.scoring !== void 0) {
|
|
162
|
-
validateBenchmarkScoringConfig(config.scoring, config.display);
|
|
163
|
-
}
|
|
164
343
|
if (config.customCliFlags !== void 0) {
|
|
165
344
|
if (!Array.isArray(config.customCliFlags) || !config.customCliFlags.every((f) => typeof f === "string" && f.startsWith("--"))) {
|
|
166
|
-
|
|
345
|
+
issues.push({ field: "customCliFlags", message: 'must be an array of strings starting with "--"' });
|
|
167
346
|
}
|
|
168
347
|
}
|
|
169
348
|
if (config.display !== void 0) {
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
throw new Error("display.metrics must be an array");
|
|
178
|
-
}
|
|
179
|
-
for (let i = 0; i < config.display.metrics.length; i++) {
|
|
180
|
-
const metric = config.display.metrics[i];
|
|
181
|
-
if (metric === null || typeof metric !== "object" || Array.isArray(metric)) {
|
|
182
|
-
throw new Error(`display.metrics[${i}] must be an object`);
|
|
183
|
-
}
|
|
184
|
-
assertOnlyAllowedKeys(metric, ["key", "label", "unit", "direction", "decimals", "order"], `display.metrics[${i}]`);
|
|
185
|
-
const key = assertNonEmptyString(metric.key, `display.metrics[${i}].key`);
|
|
186
|
-
if (displayMetricKeys.has(key)) {
|
|
187
|
-
throw new Error(`duplicate display metric key: ${key}`);
|
|
188
|
-
}
|
|
189
|
-
displayMetricKeys.add(key);
|
|
190
|
-
assertNonEmptyString(metric.label, `display.metrics[${i}].label`);
|
|
191
|
-
if (metric.unit !== void 0 && typeof metric.unit !== "string") {
|
|
192
|
-
throw new Error(`display.metrics[${i}].unit must be a string`);
|
|
193
|
-
}
|
|
194
|
-
if (metric.direction !== void 0 && metric.direction !== "higher-better" && metric.direction !== "lower-better") {
|
|
195
|
-
throw new Error(`display.metrics[${i}].direction must be 'higher-better' or 'lower-better'`);
|
|
196
|
-
}
|
|
197
|
-
if (metric.decimals !== void 0 && (!Number.isInteger(metric.decimals) || metric.decimals < 0)) {
|
|
198
|
-
throw new Error(`display.metrics[${i}].decimals must be a non-negative integer`);
|
|
199
|
-
}
|
|
200
|
-
if (metric.order !== void 0 && (!Number.isInteger(metric.order) || metric.order < 0)) {
|
|
201
|
-
throw new Error(`display.metrics[${i}].order must be a non-negative integer`);
|
|
202
|
-
}
|
|
203
|
-
}
|
|
204
|
-
}
|
|
205
|
-
if (config.display.steps !== void 0) {
|
|
206
|
-
if (!Array.isArray(config.display.steps)) {
|
|
207
|
-
throw new Error("display.steps must be an array");
|
|
208
|
-
}
|
|
209
|
-
const seenStepKeys = /* @__PURE__ */ new Set();
|
|
210
|
-
for (let i = 0; i < config.display.steps.length; i++) {
|
|
211
|
-
const step = config.display.steps[i];
|
|
212
|
-
if (step === null || typeof step !== "object" || Array.isArray(step)) {
|
|
213
|
-
throw new Error(`display.steps[${i}] must be an object`);
|
|
214
|
-
}
|
|
215
|
-
assertOnlyAllowedKeys(step, ["key", "label", "order"], `display.steps[${i}]`);
|
|
216
|
-
const key = assertNonEmptyString(step.key, `display.steps[${i}].key`);
|
|
217
|
-
if (seenStepKeys.has(key)) {
|
|
218
|
-
throw new Error(`duplicate display step key: ${key}`);
|
|
219
|
-
}
|
|
220
|
-
seenStepKeys.add(key);
|
|
221
|
-
assertNonEmptyString(step.label, `display.steps[${i}].label`);
|
|
222
|
-
if (step.order !== void 0 && (!Number.isInteger(step.order) || step.order < 0)) {
|
|
223
|
-
throw new Error(`display.steps[${i}].order must be a non-negative integer`);
|
|
224
|
-
}
|
|
225
|
-
}
|
|
226
|
-
}
|
|
227
|
-
if (config.display.overview !== void 0) {
|
|
228
|
-
if (typeof config.display.overview !== "object" || config.display.overview === null || Array.isArray(config.display.overview)) {
|
|
229
|
-
throw new Error("display.overview must be an object");
|
|
230
|
-
}
|
|
231
|
-
assertOnlyAllowedKeys(config.display.overview, ["defaultMetric", "defaultLayout"], "display.overview");
|
|
232
|
-
const { defaultMetric, defaultLayout } = config.display.overview;
|
|
233
|
-
if (defaultMetric !== void 0) {
|
|
234
|
-
const metric = assertNonEmptyString(defaultMetric, "display.overview.defaultMetric");
|
|
235
|
-
const validDefaultMetrics = new Set(displayMetricKeys);
|
|
236
|
-
validDefaultMetrics.add("compositeScore");
|
|
237
|
-
validDefaultMetrics.add("task");
|
|
238
|
-
if (config.display.metrics !== void 0 && !validDefaultMetrics.has(metric)) {
|
|
239
|
-
throw new Error(`display.overview.defaultMetric '${metric}' is not declared in display.metrics and is not a known default (compositeScore, task)`);
|
|
240
|
-
}
|
|
241
|
-
}
|
|
242
|
-
if (defaultLayout !== void 0 && !["ranking", "cards", "chart", "leaderboard"].includes(defaultLayout)) {
|
|
243
|
-
throw new Error("display.overview.defaultLayout must be 'ranking', 'cards', 'chart', or 'leaderboard'");
|
|
244
|
-
}
|
|
349
|
+
try {
|
|
350
|
+
validateBenchmarkDisplayConfig(config.display);
|
|
351
|
+
} catch (error) {
|
|
352
|
+
issues.push({
|
|
353
|
+
field: "display",
|
|
354
|
+
message: error instanceof Error ? error.message : String(error)
|
|
355
|
+
});
|
|
245
356
|
}
|
|
246
357
|
}
|
|
247
358
|
if (config.display?.overview?.defaultMetric === "compositeScore" && config.scoring === void 0 && config.onScore === void 0) {
|
|
248
|
-
|
|
359
|
+
issues.push({
|
|
360
|
+
field: "display.overview.defaultMetric",
|
|
361
|
+
message: "cannot be 'compositeScore' without config.scoring or config.onScore"
|
|
362
|
+
});
|
|
249
363
|
}
|
|
250
|
-
return
|
|
364
|
+
return issues;
|
|
251
365
|
}
|
|
252
|
-
function
|
|
253
|
-
|
|
254
|
-
throw new Error("defineTask requires a task function.");
|
|
255
|
-
}
|
|
256
|
-
return task;
|
|
366
|
+
function defineOnComplete(onComplete) {
|
|
367
|
+
return onComplete;
|
|
257
368
|
}
|
|
258
369
|
|
|
259
370
|
// src/no-available-participants.ts
|
|
@@ -271,7 +382,7 @@ var NoAvailableParticipantsError = class extends Error {
|
|
|
271
382
|
// src/runner.ts
|
|
272
383
|
import { execSync } from "child_process";
|
|
273
384
|
import os from "os";
|
|
274
|
-
import { createBenchmarkClient } from "@benchsdk/api";
|
|
385
|
+
import { createBenchmarkClient, BenchmarkApiError } from "@benchsdk/api";
|
|
275
386
|
import { resolveAuth } from "@benchsdk/cli";
|
|
276
387
|
import {
|
|
277
388
|
BenchmarkReporter,
|
|
@@ -545,12 +656,18 @@ function isStepOutcome(value) {
|
|
|
545
656
|
if (!keys.every((k) => STEP_OUTCOME_KEYS.has(k))) return false;
|
|
546
657
|
return typeof o.stdout === "string" || typeof o.stderr === "string" || typeof o.error === "string";
|
|
547
658
|
}
|
|
548
|
-
function withTimeout(promise,
|
|
659
|
+
function withTimeout(promise, { stepName, timeoutMs, participantSlug }) {
|
|
549
660
|
return new Promise((resolve2, reject) => {
|
|
550
|
-
const timer = setTimeout(
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
661
|
+
const timer = setTimeout(() => {
|
|
662
|
+
const participant = participantSlug ? ` for participant "${participantSlug}"` : "";
|
|
663
|
+
reject(
|
|
664
|
+
new TaskError(`Step "${stepName}" timed out after ${timeoutMs}ms${participant}`, {
|
|
665
|
+
code: "step_timeout",
|
|
666
|
+
step: stepName,
|
|
667
|
+
timeoutMs
|
|
668
|
+
})
|
|
669
|
+
);
|
|
670
|
+
}, timeoutMs);
|
|
554
671
|
promise.then(
|
|
555
672
|
(value) => {
|
|
556
673
|
clearTimeout(timer);
|
|
@@ -563,20 +680,23 @@ function withTimeout(promise, ms, name) {
|
|
|
563
680
|
);
|
|
564
681
|
});
|
|
565
682
|
}
|
|
566
|
-
async function runStepInvocations(name, fn, options) {
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
683
|
+
async function runStepInvocations(name, fn, options, participantSlug) {
|
|
684
|
+
if (options?.concurrency !== void 0) {
|
|
685
|
+
console.warn(`[benchsdk] step "${name}" option "concurrency" is deprecated; use "parallelInvocations"`);
|
|
686
|
+
}
|
|
687
|
+
const requestedParallelism = options?.parallelInvocations ?? options?.concurrency;
|
|
688
|
+
if (requestedParallelism !== void 0 && (!Number.isInteger(requestedParallelism) || requestedParallelism < 1)) {
|
|
689
|
+
throw new Error(`step "${name}" parallelInvocations must be an integer >= 1 (got ${requestedParallelism})`);
|
|
570
690
|
}
|
|
571
691
|
const timeoutMs = options?.timeoutMs;
|
|
572
692
|
if (timeoutMs !== void 0 && (!Number.isFinite(timeoutMs) || timeoutMs < 0)) {
|
|
573
693
|
throw new Error(`step "${name}" timeoutMs must be a number >= 0 (got ${timeoutMs})`);
|
|
574
694
|
}
|
|
575
|
-
const count =
|
|
695
|
+
const count = requestedParallelism ?? 1;
|
|
576
696
|
const invocations = Array.from({ length: count }, () => {
|
|
577
697
|
const promise = Promise.resolve().then(() => fn());
|
|
578
698
|
if (timeoutMs === void 0) return promise;
|
|
579
|
-
return withTimeout(promise, timeoutMs,
|
|
699
|
+
return withTimeout(promise, { stepName: name, timeoutMs, participantSlug });
|
|
580
700
|
});
|
|
581
701
|
if (count === 1) {
|
|
582
702
|
return invocations[0];
|
|
@@ -594,14 +714,14 @@ async function runStepInvocations(name, fn, options) {
|
|
|
594
714
|
if (firstError !== void 0) throw firstError;
|
|
595
715
|
return results;
|
|
596
716
|
}
|
|
597
|
-
async function runStepWithClient(clientStep, name, fn, options) {
|
|
598
|
-
const { concurrency:
|
|
717
|
+
async function runStepWithClient(clientStep, name, fn, options, participantSlug) {
|
|
718
|
+
const { parallelInvocations: runnerParallelism, concurrency: deprecatedConcurrency, timeoutMs, ...clientOptions } = options ?? {};
|
|
599
719
|
const clientStepOptions = {
|
|
600
720
|
...clientOptions,
|
|
601
721
|
timeoutMs,
|
|
602
|
-
stepConcurrency:
|
|
722
|
+
stepConcurrency: runnerParallelism ?? deprecatedConcurrency
|
|
603
723
|
};
|
|
604
|
-
const result = await clientStep(name, () => runStepInvocations(name, fn, options), clientStepOptions);
|
|
724
|
+
const result = await clientStep(name, () => runStepInvocations(name, fn, options, participantSlug), clientStepOptions);
|
|
605
725
|
return result;
|
|
606
726
|
}
|
|
607
727
|
function parseCliArgs(argv, allowedCustomFlags) {
|
|
@@ -769,7 +889,9 @@ function defaultOnResult(record, meta) {
|
|
|
769
889
|
const data = record.data && Object.keys(record.data).length > 0 ? ` ${JSON.stringify(record.data)}` : "";
|
|
770
890
|
console.log(` [${meta.participant}] Task ${n}/${meta.iterations}: success${data}`);
|
|
771
891
|
} else {
|
|
772
|
-
|
|
892
|
+
const detail = record.data?.errorMessage ?? record.data?.error;
|
|
893
|
+
const suffix = typeof detail === "string" && detail.length > 0 ? `: ${detail}` : "";
|
|
894
|
+
console.log(` [${meta.participant}] Task ${n}/${meta.iterations}: FAILED \u2014 ${record.errorCode ?? "unknown error"}${suffix}`);
|
|
773
895
|
}
|
|
774
896
|
}
|
|
775
897
|
function resolveShape(config, shapeName) {
|
|
@@ -810,7 +932,23 @@ function resolveParticipants(config, resolved) {
|
|
|
810
932
|
if (available.length === 0) throw new NoAvailableParticipantsError(skipped);
|
|
811
933
|
return available;
|
|
812
934
|
}
|
|
813
|
-
function
|
|
935
|
+
function resolveTriggerSource(env = process.env) {
|
|
936
|
+
const source = env.BENCH_TRIGGER_SOURCE?.trim();
|
|
937
|
+
if (source) return source;
|
|
938
|
+
return env.GITHUB_EVENT_NAME?.trim() || "manual";
|
|
939
|
+
}
|
|
940
|
+
function triggerToJson(env = process.env) {
|
|
941
|
+
const requestedBy = env.BENCH_TRIGGER_REQUESTED_BY?.trim();
|
|
942
|
+
const requestId = env.BENCH_TRIGGER_REQUEST_ID?.trim();
|
|
943
|
+
const event = env.GITHUB_EVENT_NAME?.trim();
|
|
944
|
+
return {
|
|
945
|
+
source: resolveTriggerSource(env),
|
|
946
|
+
...event ? { event } : {},
|
|
947
|
+
...requestedBy ? { requestedBy } : {},
|
|
948
|
+
...requestId ? { requestId } : {}
|
|
949
|
+
};
|
|
950
|
+
}
|
|
951
|
+
function runConfigToJson(config, resolved, participants, env = process.env) {
|
|
814
952
|
const phases = config.phases?.map((phase) => ({
|
|
815
953
|
name: phase.name,
|
|
816
954
|
iterations: resolved.phaseIterations ?? phase.iterations
|
|
@@ -826,24 +964,38 @@ function runConfigToJson(config, resolved, participants) {
|
|
|
826
964
|
groupBy: resolved.groupBy,
|
|
827
965
|
...config.dimensions ? { dimensions: config.dimensions } : {},
|
|
828
966
|
...config.scoring ? { scoring: config.scoring } : {},
|
|
829
|
-
participants
|
|
967
|
+
participants,
|
|
968
|
+
trigger: triggerToJson(env)
|
|
830
969
|
};
|
|
831
970
|
return JSON.parse(JSON.stringify(runConfig));
|
|
832
971
|
}
|
|
833
|
-
async function runBenchmark(fileConfig, task, argv = []) {
|
|
972
|
+
async function runBenchmark(fileConfig, task, argv = [], options = {}) {
|
|
834
973
|
const args = parseCliArgs(argv, fileConfig.customCliFlags);
|
|
835
974
|
const noIngest = args.noIngest ?? isEnvNoIngest();
|
|
836
975
|
const shaped = applyShape(fileConfig, resolveShape(fileConfig, args.shape));
|
|
837
976
|
const config = applyIdentityOverrides(shaped, args);
|
|
838
977
|
const resolved = mergeConfig(config, args);
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
978
|
+
let baseUrl = "";
|
|
979
|
+
let apiKey = "";
|
|
980
|
+
let token;
|
|
981
|
+
let orgSlug;
|
|
982
|
+
let orgId;
|
|
983
|
+
let client = null;
|
|
984
|
+
const auth = noIngest ? await resolveAuth({ baseUrl: options.baseUrl, apiKey: options.apiKey }).catch(() => null) : await resolveAuth({ baseUrl: options.baseUrl, apiKey: options.apiKey });
|
|
985
|
+
if (auth) {
|
|
986
|
+
baseUrl = auth.apiBaseUrl;
|
|
987
|
+
apiKey = auth.apiKey ?? "";
|
|
988
|
+
token = auth.token;
|
|
989
|
+
orgSlug = auth.orgSlug;
|
|
990
|
+
orgId = auth.orgId;
|
|
991
|
+
client = createBenchmarkClient({
|
|
992
|
+
baseUrl: auth.apiBaseUrl,
|
|
993
|
+
apiKey: auth.apiKey,
|
|
994
|
+
token: auth.token,
|
|
995
|
+
orgSlug: auth.orgSlug,
|
|
996
|
+
orgId: auth.orgId
|
|
997
|
+
});
|
|
998
|
+
}
|
|
847
999
|
const available = resolveParticipants(config, resolved);
|
|
848
1000
|
const schedule = buildSchedule(config, resolved, task);
|
|
849
1001
|
const totalTasks = schedule.length;
|
|
@@ -864,15 +1016,26 @@ async function runBenchmark(fileConfig, task, argv = []) {
|
|
|
864
1016
|
runId = "no-ingest";
|
|
865
1017
|
dashboardUrl = "";
|
|
866
1018
|
} else {
|
|
867
|
-
|
|
1019
|
+
{
|
|
868
1020
|
const benchmarkConfig = {
|
|
869
1021
|
...config.scoring ? { scoring: config.scoring } : {},
|
|
870
1022
|
...config.display ? { display: config.display } : {}
|
|
871
1023
|
};
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
1024
|
+
let initializeTarget = identityIsOurs;
|
|
1025
|
+
if (!initializeTarget) {
|
|
1026
|
+
try {
|
|
1027
|
+
await client.getBenchmark(config.benchmarkSlug);
|
|
1028
|
+
} catch (error) {
|
|
1029
|
+
if (!(error instanceof BenchmarkApiError && error.status === 404)) throw error;
|
|
1030
|
+
initializeTarget = true;
|
|
1031
|
+
}
|
|
1032
|
+
}
|
|
1033
|
+
const upsertInput = {};
|
|
1034
|
+
if (initializeTarget) {
|
|
1035
|
+
upsertInput.name = config.benchmarkName;
|
|
1036
|
+
if (Object.keys(benchmarkConfig).length > 0) upsertInput.config = benchmarkConfig;
|
|
1037
|
+
}
|
|
1038
|
+
await client.upsertBenchmark(config.benchmarkSlug, upsertInput);
|
|
876
1039
|
}
|
|
877
1040
|
const runConfig = client ? runConfigToJson(config, resolved, available.map((p) => p.name)) : {};
|
|
878
1041
|
if (args.runKey) {
|
|
@@ -881,7 +1044,7 @@ async function runBenchmark(fileConfig, task, argv = []) {
|
|
|
881
1044
|
config: runConfig
|
|
882
1045
|
});
|
|
883
1046
|
runId = run2.id;
|
|
884
|
-
dashboardUrl = dashboardUrlFor(
|
|
1047
|
+
dashboardUrl = dashboardUrlFor(baseUrl, organizationSlug, config.benchmarkSlug, run2.id);
|
|
885
1048
|
for (const participant of available) {
|
|
886
1049
|
await client.upsertParticipant(config.benchmarkSlug, runId, participant.name, { totalTasks });
|
|
887
1050
|
}
|
|
@@ -896,7 +1059,7 @@ async function runBenchmark(fileConfig, task, argv = []) {
|
|
|
896
1059
|
config: runConfig
|
|
897
1060
|
});
|
|
898
1061
|
runId = run2.id;
|
|
899
|
-
dashboardUrl = dashboardUrlFor(
|
|
1062
|
+
dashboardUrl = dashboardUrlFor(baseUrl, organizationSlug, config.benchmarkSlug, run2.id);
|
|
900
1063
|
console.log(`Run created: ${run2.name} (${runId})`);
|
|
901
1064
|
console.log(`View at: ${dashboardUrl}
|
|
902
1065
|
`);
|
|
@@ -905,7 +1068,7 @@ async function runBenchmark(fileConfig, task, argv = []) {
|
|
|
905
1068
|
const onResult = defaultOnResult;
|
|
906
1069
|
let participantRecords;
|
|
907
1070
|
if (resolved.groupBy === "round") {
|
|
908
|
-
participantRecords = await runGroupedByRound(config, schedule, available, resolved, client, runId,
|
|
1071
|
+
participantRecords = await runGroupedByRound(config, schedule, available, resolved, client, runId, baseUrl, apiKey, token, orgSlug, orgId, onResult, noIngest);
|
|
909
1072
|
} else {
|
|
910
1073
|
participantRecords = await runGroupedByParticipant(config, schedule, available, resolved, client, runId, onResult, noIngest);
|
|
911
1074
|
}
|
|
@@ -916,14 +1079,14 @@ async function runBenchmark(fileConfig, task, argv = []) {
|
|
|
916
1079
|
participants: participantRecords,
|
|
917
1080
|
config: resolved
|
|
918
1081
|
};
|
|
919
|
-
if (!noIngest && (config.onScore || config.scoring)) {
|
|
1082
|
+
if (!noIngest && client && (config.onScore || config.scoring)) {
|
|
920
1083
|
try {
|
|
921
1084
|
const spec = config.onScore ? await config.onScore(lowerIsBetter, higherIsBetter) : scoringConfigToSpec(config.scoring, config.dimensions, config.display);
|
|
922
1085
|
const scored = score(outcome, spec, config.display?.metrics);
|
|
923
1086
|
const run2 = {
|
|
924
1087
|
gitSha: process.env.GITHUB_SHA ?? getGitSha(),
|
|
925
1088
|
gitRef: process.env.GITHUB_REF_NAME ?? process.env.GITHUB_REF ?? getGitRef(),
|
|
926
|
-
triggeredBy:
|
|
1089
|
+
triggeredBy: resolveTriggerSource(),
|
|
927
1090
|
nodeVersion: process.version,
|
|
928
1091
|
platform: os.platform(),
|
|
929
1092
|
arch: os.arch()
|
|
@@ -942,6 +1105,22 @@ async function runBenchmark(fileConfig, task, argv = []) {
|
|
|
942
1105
|
if (config.onComplete) await config.onComplete(outcome);
|
|
943
1106
|
return outcome;
|
|
944
1107
|
}
|
|
1108
|
+
async function runBenchmarkWorker(options) {
|
|
1109
|
+
const config = defineBenchmarkConfig({
|
|
1110
|
+
benchmarkSlug: options.benchmarkSlug,
|
|
1111
|
+
benchmarkName: options.benchmarkName ?? options.benchmarkSlug,
|
|
1112
|
+
participants: [options.participant],
|
|
1113
|
+
iterations: options.iterations ?? 1,
|
|
1114
|
+
concurrency: options.concurrency ?? 1,
|
|
1115
|
+
staggerDelayMs: options.staggerDelayMs ?? 0,
|
|
1116
|
+
groupBy: options.groupBy ?? "participant",
|
|
1117
|
+
defaultProviders: [options.participant.name]
|
|
1118
|
+
});
|
|
1119
|
+
const argv = [];
|
|
1120
|
+
if (options.runKey) argv.push("--run-key", options.runKey);
|
|
1121
|
+
if (options.noIngest) argv.push("--dry-run");
|
|
1122
|
+
return runBenchmark(config, options.task, argv);
|
|
1123
|
+
}
|
|
945
1124
|
function getGitSha() {
|
|
946
1125
|
if (process.env.GITHUB_SHA) return process.env.GITHUB_SHA;
|
|
947
1126
|
try {
|
|
@@ -1016,7 +1195,7 @@ async function runGroupedByParticipant(config, schedule, available, resolved, cl
|
|
|
1016
1195
|
participant,
|
|
1017
1196
|
taskIndex: scheduleIndex,
|
|
1018
1197
|
phase: slot.phase,
|
|
1019
|
-
step: (name, fn, options) => runStepWithClient(ctx.step, name, fn, options),
|
|
1198
|
+
step: (name, fn, options) => runStepWithClient(ctx.step, name, fn, options, participant.name),
|
|
1020
1199
|
measure: ctx.measure,
|
|
1021
1200
|
log: ctx.log
|
|
1022
1201
|
});
|
|
@@ -1173,12 +1352,13 @@ async function runTaskRecord(task, participant, scheduleIndex, taskIndex, phase,
|
|
|
1173
1352
|
completedAt: new Date(stepStartedAtMs).toISOString(),
|
|
1174
1353
|
latencyMs: 0
|
|
1175
1354
|
};
|
|
1176
|
-
|
|
1355
|
+
const requestedParallelism = options?.parallelInvocations ?? options?.concurrency;
|
|
1356
|
+
if (requestedParallelism !== void 0) stepRecord.concurrency = requestedParallelism;
|
|
1177
1357
|
if (options?.timeoutMs !== void 0) stepRecord.timeoutMs = options.timeoutMs;
|
|
1178
1358
|
const previousStep = activeStep;
|
|
1179
1359
|
activeStep = stepRecord;
|
|
1180
1360
|
try {
|
|
1181
|
-
const result2 = await runStepInvocations(name, fn, options);
|
|
1361
|
+
const result2 = await runStepInvocations(name, fn, options, participant.name);
|
|
1182
1362
|
const outcome = options?.captureOutput !== false && !Array.isArray(result2) && isStepOutcome(result2) ? result2 : {};
|
|
1183
1363
|
logBuffer.step(taskIndex, name, outcome);
|
|
1184
1364
|
return result2;
|
|
@@ -1247,17 +1427,145 @@ async function runTaskRecord(task, participant, scheduleIndex, taskIndex, phase,
|
|
|
1247
1427
|
// src/cli.ts
|
|
1248
1428
|
import { resolve } from "path";
|
|
1249
1429
|
import { pathToFileURL } from "url";
|
|
1250
|
-
import { run as runPlatformCli } from "@benchsdk/cli";
|
|
1251
|
-
|
|
1430
|
+
import { run as runPlatformCli, resolveAuth as resolveAuth2 } from "@benchsdk/cli";
|
|
1431
|
+
import { createBenchmarkClient as createBenchmarkClient2 } from "@benchsdk/api";
|
|
1432
|
+
import { filterParticipantsByEnv as filterParticipantsByEnv2, selectParticipants as selectParticipants2 } from "@benchsdk/worker";
|
|
1433
|
+
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>]';
|
|
1252
1434
|
function isBenchmarkConfig(value) {
|
|
1253
1435
|
if (typeof value !== "object" || value === null) return false;
|
|
1254
1436
|
const candidate = value;
|
|
1255
1437
|
return typeof candidate.benchmarkSlug === "string" && Array.isArray(candidate.participants);
|
|
1256
1438
|
}
|
|
1439
|
+
function shiftFlag(argv, name) {
|
|
1440
|
+
const prefix = `--${name}`;
|
|
1441
|
+
const prefixEq = `${prefix}=`;
|
|
1442
|
+
const result = [];
|
|
1443
|
+
let value;
|
|
1444
|
+
for (let i = 0; i < argv.length; i++) {
|
|
1445
|
+
const arg = argv[i];
|
|
1446
|
+
if (arg === prefix) {
|
|
1447
|
+
const next = argv[++i];
|
|
1448
|
+
if (!next || next.startsWith("--")) throw new Error(USAGE);
|
|
1449
|
+
value = next;
|
|
1450
|
+
continue;
|
|
1451
|
+
}
|
|
1452
|
+
if (arg.startsWith(prefixEq)) {
|
|
1453
|
+
const eqValue = arg.slice(prefixEq.length);
|
|
1454
|
+
if (!eqValue) throw new Error(USAGE);
|
|
1455
|
+
value = eqValue;
|
|
1456
|
+
continue;
|
|
1457
|
+
}
|
|
1458
|
+
result.push(arg);
|
|
1459
|
+
}
|
|
1460
|
+
return { value, argv: result };
|
|
1461
|
+
}
|
|
1462
|
+
function shiftPlatformFlags(flags) {
|
|
1463
|
+
const { value: baseUrl, argv: withoutBaseUrl } = shiftFlag(flags, "base-url");
|
|
1464
|
+
const { value: apiKey, argv: rest } = shiftFlag(withoutBaseUrl, "api-key");
|
|
1465
|
+
return { baseUrl, apiKey, flags: rest };
|
|
1466
|
+
}
|
|
1467
|
+
async function runCheck(argv) {
|
|
1468
|
+
const [command, ...rest] = argv;
|
|
1469
|
+
const [file, ...flags] = rest;
|
|
1470
|
+
if (command !== "check" || !file || file.startsWith("-")) throw new Error(USAGE);
|
|
1471
|
+
const mod = await import(pathToFileURL(resolve(process.cwd(), file)).href);
|
|
1472
|
+
const config = mod.config;
|
|
1473
|
+
const task = mod.task ?? mod.default;
|
|
1474
|
+
if (!isBenchmarkConfig(config)) {
|
|
1475
|
+
throw new Error(`${file} must export a \`config\` created with defineBenchmarkConfig (with participants).`);
|
|
1476
|
+
}
|
|
1477
|
+
if (typeof task !== "function") {
|
|
1478
|
+
throw new Error(`${file} must export a \`task\` created with defineTask.`);
|
|
1479
|
+
}
|
|
1480
|
+
const cfg = config;
|
|
1481
|
+
const configIssues = validateBenchmarkConfig(cfg);
|
|
1482
|
+
if (configIssues.length > 0) {
|
|
1483
|
+
throw new BenchmarkConfigError(configIssues);
|
|
1484
|
+
}
|
|
1485
|
+
const { baseUrl, apiKey, flags: runnerFlags } = shiftPlatformFlags(flags);
|
|
1486
|
+
const parsed = parseCliArgs(runnerFlags, cfg.customCliFlags ?? []);
|
|
1487
|
+
resolveShape(cfg, parsed.shape);
|
|
1488
|
+
const dryRun = parsed.noIngest ?? false;
|
|
1489
|
+
let client;
|
|
1490
|
+
let apiOk = dryRun;
|
|
1491
|
+
let auth = null;
|
|
1492
|
+
if (!dryRun) {
|
|
1493
|
+
try {
|
|
1494
|
+
auth = await resolveAuth2({ baseUrl, apiKey });
|
|
1495
|
+
client = createBenchmarkClient2({
|
|
1496
|
+
baseUrl: auth.apiBaseUrl,
|
|
1497
|
+
apiKey: auth.apiKey,
|
|
1498
|
+
token: auth.token,
|
|
1499
|
+
orgSlug: auth.orgSlug,
|
|
1500
|
+
orgId: auth.orgId
|
|
1501
|
+
});
|
|
1502
|
+
await client.listBenchmarks({ limit: 1 });
|
|
1503
|
+
apiOk = true;
|
|
1504
|
+
} catch (err) {
|
|
1505
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
1506
|
+
console.warn(`[benchsdk] API connectivity check failed: ${message}`);
|
|
1507
|
+
}
|
|
1508
|
+
}
|
|
1509
|
+
const effectiveProviderNames = parsed.providers ?? cfg.defaultProviders;
|
|
1510
|
+
let selected;
|
|
1511
|
+
try {
|
|
1512
|
+
selected = selectParticipants2(cfg.participants, effectiveProviderNames);
|
|
1513
|
+
} catch (err) {
|
|
1514
|
+
throw new Error(`Participant selection failed: ${err instanceof Error ? err.message : err}`);
|
|
1515
|
+
}
|
|
1516
|
+
const { available, skipped } = filterParticipantsByEnv2(selected);
|
|
1517
|
+
let scoringOk = true;
|
|
1518
|
+
if (cfg.onScore) {
|
|
1519
|
+
try {
|
|
1520
|
+
const spec = await cfg.onScore(lowerIsBetter, higherIsBetter);
|
|
1521
|
+
validateScoringSpec(spec);
|
|
1522
|
+
} catch (err) {
|
|
1523
|
+
scoringOk = false;
|
|
1524
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
1525
|
+
console.warn(`[benchsdk] Scoring validation failed: ${message}`);
|
|
1526
|
+
}
|
|
1527
|
+
} else if (cfg.scoring) {
|
|
1528
|
+
try {
|
|
1529
|
+
const spec = scoringConfigToSpec(cfg.scoring, cfg.dimensions);
|
|
1530
|
+
validateScoringSpec(spec);
|
|
1531
|
+
} catch (err) {
|
|
1532
|
+
scoringOk = false;
|
|
1533
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
1534
|
+
console.warn(`[benchsdk] Scoring validation failed: ${message}`);
|
|
1535
|
+
}
|
|
1536
|
+
}
|
|
1537
|
+
const missingPlatformAuth = dryRun ? [] : auth ? [] : [["BENCHMARKS_PLATFORM_API_KEY or BENCHMARKS_PLATFORM_TOKEN", void 0]];
|
|
1538
|
+
for (const [name] of missingPlatformAuth) {
|
|
1539
|
+
console.warn(`[benchsdk] ${name} is not set`);
|
|
1540
|
+
}
|
|
1541
|
+
const report = {
|
|
1542
|
+
file,
|
|
1543
|
+
benchmarkSlug: cfg.benchmarkSlug,
|
|
1544
|
+
apiOk,
|
|
1545
|
+
envOk: dryRun || missingPlatformAuth.length === 0,
|
|
1546
|
+
participants: {
|
|
1547
|
+
requested: selected.map((p) => p.name),
|
|
1548
|
+
available: available.map((p) => p.name),
|
|
1549
|
+
skipped: skipped.map((s) => ({ name: s.name, missing: s.missing }))
|
|
1550
|
+
},
|
|
1551
|
+
scoringOk: cfg.scoring || cfg.onScore ? scoringOk : void 0
|
|
1552
|
+
};
|
|
1553
|
+
console.log(JSON.stringify(report, null, 2));
|
|
1554
|
+
const authFailure = !dryRun && missingPlatformAuth.length > 0;
|
|
1555
|
+
if (!apiOk || available.length === 0 || scoringOk === false || authFailure) {
|
|
1556
|
+
throw new Error("Benchmark check failed. See warnings above for details.");
|
|
1557
|
+
}
|
|
1558
|
+
}
|
|
1257
1559
|
async function runBenchmarkFile(argv) {
|
|
1258
1560
|
const [command, ...rest] = argv;
|
|
1259
1561
|
const [file, ...flags] = rest;
|
|
1260
1562
|
if (command !== "run" || !file || file.startsWith("-")) throw new Error(USAGE);
|
|
1563
|
+
const check = flags.includes("--check") || flags.includes("--validate");
|
|
1564
|
+
if (check) {
|
|
1565
|
+
const checkFlags = flags.filter((f) => f !== "--check" && f !== "--validate");
|
|
1566
|
+
return runCheck(["check", file, ...checkFlags]);
|
|
1567
|
+
}
|
|
1568
|
+
const { baseUrl, apiKey, flags: runnerFlags } = shiftPlatformFlags(flags);
|
|
1261
1569
|
const mod = await import(pathToFileURL(resolve(process.cwd(), file)).href);
|
|
1262
1570
|
const config = mod.config;
|
|
1263
1571
|
const task = mod.task ?? mod.default;
|
|
@@ -1267,43 +1575,84 @@ async function runBenchmarkFile(argv) {
|
|
|
1267
1575
|
if (typeof task !== "function") {
|
|
1268
1576
|
throw new Error(`${file} must export a \`task\` created with defineTask.`);
|
|
1269
1577
|
}
|
|
1270
|
-
|
|
1578
|
+
const envFlags = [];
|
|
1579
|
+
if (process.env.BENCHMARK_SLUG) {
|
|
1580
|
+
envFlags.push("--benchmark", process.env.BENCHMARK_SLUG);
|
|
1581
|
+
}
|
|
1582
|
+
if (process.env.BENCHMARK_NAME) {
|
|
1583
|
+
envFlags.push("--name", process.env.BENCHMARK_NAME);
|
|
1584
|
+
}
|
|
1585
|
+
await runBenchmark(
|
|
1586
|
+
config,
|
|
1587
|
+
task,
|
|
1588
|
+
[...envFlags, ...runnerFlags],
|
|
1589
|
+
{ baseUrl, apiKey }
|
|
1590
|
+
);
|
|
1271
1591
|
}
|
|
1272
1592
|
async function run(argv) {
|
|
1273
|
-
|
|
1274
|
-
return runPlatformCli(argv);
|
|
1275
|
-
}
|
|
1593
|
+
const [command, ...rest] = argv;
|
|
1276
1594
|
try {
|
|
1277
|
-
|
|
1595
|
+
if (command === "run") {
|
|
1596
|
+
await runBenchmarkFile(argv);
|
|
1597
|
+
} else if (command === "check") {
|
|
1598
|
+
await runCheck(argv);
|
|
1599
|
+
} else {
|
|
1600
|
+
return runPlatformCli(argv);
|
|
1601
|
+
}
|
|
1278
1602
|
process.exit(0);
|
|
1279
1603
|
} catch (err) {
|
|
1280
1604
|
if (err instanceof NoAvailableParticipantsError) {
|
|
1281
1605
|
console.log(err.message);
|
|
1282
1606
|
process.exit(0);
|
|
1283
1607
|
}
|
|
1284
|
-
console.error("Benchmark failed:", err
|
|
1608
|
+
console.error("Benchmark failed:", String(err));
|
|
1285
1609
|
process.exit(1);
|
|
1286
1610
|
}
|
|
1287
1611
|
}
|
|
1288
1612
|
|
|
1289
1613
|
// src/index.ts
|
|
1290
|
-
|
|
1614
|
+
import { resolveAuth as resolveAuth3, createApiClient, AuthError } from "@benchsdk/cli";
|
|
1615
|
+
import {
|
|
1616
|
+
runWorker as runWorker2,
|
|
1617
|
+
BenchmarkReporter as BenchmarkReporter2,
|
|
1618
|
+
claimBenchmarkReporter,
|
|
1619
|
+
createSystemMetricsCollector as createSystemMetricsCollector2,
|
|
1620
|
+
filterParticipantsByEnv as filterParticipantsByEnv3,
|
|
1621
|
+
selectParticipants as selectParticipants3
|
|
1622
|
+
} from "@benchsdk/worker";
|
|
1623
|
+
import { BenchmarkApiError as BenchmarkApiError2, createBenchmarkClient as createBenchmarkClient3 } from "@benchsdk/api";
|
|
1624
|
+
var BENCHSDK_RUNNER_VERSION = "0.5.2";
|
|
1291
1625
|
export {
|
|
1626
|
+
AuthError,
|
|
1292
1627
|
BENCHSDK_RUNNER_VERSION,
|
|
1628
|
+
BenchmarkApiError2 as BenchmarkApiError,
|
|
1629
|
+
BenchmarkConfigError,
|
|
1630
|
+
BenchmarkReporter2 as BenchmarkReporter,
|
|
1293
1631
|
NoAvailableParticipantsError,
|
|
1294
1632
|
ScoringSpecError,
|
|
1295
1633
|
TaskError,
|
|
1634
|
+
claimBenchmarkReporter,
|
|
1635
|
+
createApiClient,
|
|
1636
|
+
createBenchmarkClient3 as createBenchmarkClient,
|
|
1637
|
+
createSystemMetricsCollector2 as createSystemMetricsCollector,
|
|
1296
1638
|
defineBenchmarkConfig,
|
|
1639
|
+
defineOnComplete,
|
|
1297
1640
|
defineTask,
|
|
1641
|
+
filterParticipantsByEnv3 as filterParticipantsByEnv,
|
|
1298
1642
|
higherIsBetter,
|
|
1299
1643
|
lowerIsBetter,
|
|
1300
1644
|
mergeConfig,
|
|
1301
1645
|
parseCliArgs,
|
|
1646
|
+
resolveAuth3 as resolveAuth,
|
|
1302
1647
|
run,
|
|
1303
1648
|
runBenchmark,
|
|
1304
1649
|
runBenchmarkFile,
|
|
1650
|
+
runBenchmarkWorker,
|
|
1651
|
+
runWorker2 as runWorker,
|
|
1305
1652
|
score,
|
|
1306
1653
|
scoringConfigToSpec,
|
|
1654
|
+
selectParticipants3 as selectParticipants,
|
|
1655
|
+
validateBenchmarkConfig,
|
|
1307
1656
|
validateScoringSpec
|
|
1308
1657
|
};
|
|
1309
1658
|
//# sourceMappingURL=index.js.map
|