@benchsdk/client 0.2.1 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +41 -96
- package/dist/index.cjs +125 -140
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +106 -73
- package/dist/index.d.ts +106 -73
- package/dist/index.js +123 -135
- package/dist/index.js.map +1 -1
- package/package.json +14 -15
package/dist/index.js
CHANGED
|
@@ -7,6 +7,7 @@ var DEFAULT_READY_POLL_INTERVAL_MS = 1e3;
|
|
|
7
7
|
var MAX_TASK_RESULT_RECORDS = 5e3;
|
|
8
8
|
var MAX_TASK_RECORD_STEPS = 100;
|
|
9
9
|
var MAX_HEARTBEAT_CONCURRENCY_SAMPLES = 20;
|
|
10
|
+
var MAX_WORKER_LOG_LINES = 1e5;
|
|
10
11
|
var BenchmarkApiError = class extends Error {
|
|
11
12
|
constructor(message, status, body) {
|
|
12
13
|
super(message);
|
|
@@ -35,6 +36,9 @@ function getApiKey(input) {
|
|
|
35
36
|
return input ?? (typeof process !== "undefined" ? process.env.COMPUTESDK_ADMIN_API_KEY ?? process.env.COMPUTESDK_API_KEY : void 0);
|
|
36
37
|
}
|
|
37
38
|
function getErrorCode(error) {
|
|
39
|
+
if (error instanceof Error && "code" in error && typeof error.code === "string" && error.code) {
|
|
40
|
+
return error.code;
|
|
41
|
+
}
|
|
38
42
|
if (error instanceof Error && error.name) return error.name;
|
|
39
43
|
return "ERROR";
|
|
40
44
|
}
|
|
@@ -42,6 +46,21 @@ function toJsonObject(value) {
|
|
|
42
46
|
if (!value || typeof value !== "object" || Array.isArray(value)) return void 0;
|
|
43
47
|
return value;
|
|
44
48
|
}
|
|
49
|
+
function mergeMeasures(measures, returned) {
|
|
50
|
+
const merged = { ...measures, ...returned ?? {} };
|
|
51
|
+
return Object.keys(merged).length > 0 ? merged : void 0;
|
|
52
|
+
}
|
|
53
|
+
function implicitTaskStep(record, measures) {
|
|
54
|
+
return {
|
|
55
|
+
name: "task",
|
|
56
|
+
status: record.status === "success" ? "success" : "error",
|
|
57
|
+
startedAt: record.startedAt,
|
|
58
|
+
completedAt: record.completedAt,
|
|
59
|
+
latencyMs: record.latencyMs,
|
|
60
|
+
errorCode: record.errorCode ?? null,
|
|
61
|
+
data: Object.keys(measures).length > 0 ? { ...measures } : void 0
|
|
62
|
+
};
|
|
63
|
+
}
|
|
45
64
|
function validateTaskResults(input) {
|
|
46
65
|
if (input.records.length > MAX_TASK_RESULT_RECORDS) {
|
|
47
66
|
throw new Error(`Benchmark task result batches are limited to ${MAX_TASK_RESULT_RECORDS} records.`);
|
|
@@ -87,53 +106,9 @@ function bodySizeBytes(body) {
|
|
|
87
106
|
if (body instanceof URLSearchParams) return new TextEncoder().encode(body.toString()).byteLength;
|
|
88
107
|
return void 0;
|
|
89
108
|
}
|
|
90
|
-
function isDefinedTask(task) {
|
|
91
|
-
return typeof task === "object" && task !== null && Array.isArray(task.steps);
|
|
92
|
-
}
|
|
93
|
-
function mergeJsonObjects(target, source) {
|
|
94
|
-
if (!source) return;
|
|
95
|
-
Object.assign(target, source);
|
|
96
|
-
}
|
|
97
109
|
function sleep(ms) {
|
|
98
110
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
99
111
|
}
|
|
100
|
-
async function runWorkerTask(task, context) {
|
|
101
|
-
if (!isDefinedTask(task)) {
|
|
102
|
-
return task(context);
|
|
103
|
-
}
|
|
104
|
-
const state = {};
|
|
105
|
-
const data = { taskName: task.name };
|
|
106
|
-
try {
|
|
107
|
-
for (const definedStep of task.steps) {
|
|
108
|
-
const stepData = await context.step(
|
|
109
|
-
definedStep.name,
|
|
110
|
-
() => definedStep.fn({
|
|
111
|
-
assignment: context.assignment,
|
|
112
|
-
taskIndex: context.taskIndex,
|
|
113
|
-
state
|
|
114
|
-
}),
|
|
115
|
-
definedStep.options
|
|
116
|
-
);
|
|
117
|
-
mergeJsonObjects(data, stepData);
|
|
118
|
-
}
|
|
119
|
-
} catch (error) {
|
|
120
|
-
try {
|
|
121
|
-
await task.options?.cleanup?.({
|
|
122
|
-
assignment: context.assignment,
|
|
123
|
-
taskIndex: context.taskIndex,
|
|
124
|
-
state
|
|
125
|
-
});
|
|
126
|
-
} catch {
|
|
127
|
-
}
|
|
128
|
-
throw error;
|
|
129
|
-
}
|
|
130
|
-
await task.options?.cleanup?.({
|
|
131
|
-
assignment: context.assignment,
|
|
132
|
-
taskIndex: context.taskIndex,
|
|
133
|
-
state
|
|
134
|
-
});
|
|
135
|
-
return data;
|
|
136
|
-
}
|
|
137
112
|
async function mapPool(items, concurrency, fn) {
|
|
138
113
|
let nextIndex = 0;
|
|
139
114
|
const workers = Array.from({ length: Math.min(concurrency, items.length) }, async () => {
|
|
@@ -410,6 +385,13 @@ function createBenchmarkClient(config = {}) {
|
|
|
410
385
|
`/benchmarks/${encodePath(benchmarkSlug)}/runs/${encodePath(runId)}/results/imports`
|
|
411
386
|
);
|
|
412
387
|
},
|
|
388
|
+
async submitRunSummary(benchmarkSlug, runId, input) {
|
|
389
|
+
await request(
|
|
390
|
+
"POST",
|
|
391
|
+
`/benchmarks/${encodePath(benchmarkSlug)}/runs/${encodePath(runId)}/summary`,
|
|
392
|
+
input
|
|
393
|
+
);
|
|
394
|
+
},
|
|
413
395
|
async runWorker(options) {
|
|
414
396
|
if (options.concurrency !== void 0) validatePositiveInteger("concurrency", options.concurrency);
|
|
415
397
|
if (options.batchSize !== void 0) validateBatchSize(options.batchSize);
|
|
@@ -521,6 +503,18 @@ function createBenchmarkClient(config = {}) {
|
|
|
521
503
|
});
|
|
522
504
|
}, options.flushIntervalMs ?? DEFAULT_FLUSH_INTERVAL_MS);
|
|
523
505
|
resultFlush.unref?.();
|
|
506
|
+
const workerLogLines = [];
|
|
507
|
+
let workerLogTruncated = false;
|
|
508
|
+
function appendWorkerLog(line) {
|
|
509
|
+
if (workerLogLines.length >= MAX_WORKER_LOG_LINES) {
|
|
510
|
+
if (!workerLogTruncated) {
|
|
511
|
+
workerLogTruncated = true;
|
|
512
|
+
workerLogLines.push("... (worker log truncated)");
|
|
513
|
+
}
|
|
514
|
+
return;
|
|
515
|
+
}
|
|
516
|
+
workerLogLines.push(line);
|
|
517
|
+
}
|
|
524
518
|
async function runFinishHook(status) {
|
|
525
519
|
await options.onFinish?.({
|
|
526
520
|
assignment: claimed,
|
|
@@ -535,6 +529,19 @@ function createBenchmarkClient(config = {}) {
|
|
|
535
529
|
}
|
|
536
530
|
});
|
|
537
531
|
}
|
|
532
|
+
async function uploadWorkerLogArtifact() {
|
|
533
|
+
if (workerLogLines.length === 0) return;
|
|
534
|
+
try {
|
|
535
|
+
await client.uploadWorkerArtifact(options.benchmarkSlug, options.runId, claimed.workerId, {
|
|
536
|
+
attemptId: claimed.attemptId,
|
|
537
|
+
kind: "coordinator.log",
|
|
538
|
+
contentType: "text/plain",
|
|
539
|
+
name: "worker.log",
|
|
540
|
+
body: workerLogLines.join("\n") + "\n"
|
|
541
|
+
});
|
|
542
|
+
} catch {
|
|
543
|
+
}
|
|
544
|
+
}
|
|
538
545
|
try {
|
|
539
546
|
await sendHeartbeat().catch(() => {
|
|
540
547
|
});
|
|
@@ -548,6 +555,19 @@ function createBenchmarkClient(config = {}) {
|
|
|
548
555
|
startedAt: startedAtDate.toISOString()
|
|
549
556
|
};
|
|
550
557
|
const steps = [];
|
|
558
|
+
const taskMeasures = {};
|
|
559
|
+
let activeStep = null;
|
|
560
|
+
function measure(data) {
|
|
561
|
+
if (activeStep) {
|
|
562
|
+
activeStep.data = { ...activeStep.data ?? {}, ...data };
|
|
563
|
+
} else {
|
|
564
|
+
Object.assign(taskMeasures, data);
|
|
565
|
+
}
|
|
566
|
+
}
|
|
567
|
+
function log(message, meta) {
|
|
568
|
+
const suffix = meta && Object.keys(meta).length > 0 ? ` ${JSON.stringify(meta)}` : "";
|
|
569
|
+
appendWorkerLog(`${(/* @__PURE__ */ new Date()).toISOString()} [task ${taskIndex}] ${message}${suffix}`);
|
|
570
|
+
}
|
|
551
571
|
async function step(name, fn, stepOptions = {}) {
|
|
552
572
|
const stepStartedAtMs = Date.now();
|
|
553
573
|
const stepRecord = {
|
|
@@ -557,6 +577,8 @@ function createBenchmarkClient(config = {}) {
|
|
|
557
577
|
completedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
558
578
|
latencyMs: 0
|
|
559
579
|
};
|
|
580
|
+
if (stepOptions.timeoutMs !== void 0) stepRecord.timeoutMs = stepOptions.timeoutMs;
|
|
581
|
+
if (stepOptions.stepConcurrency !== void 0) stepRecord.concurrency = stepOptions.stepConcurrency;
|
|
560
582
|
const shouldReportConcurrency = stepOptions.reportConcurrency ?? true;
|
|
561
583
|
if (shouldReportConcurrency) {
|
|
562
584
|
const stepConcurrency = stepOptions.concurrency ?? workerConcurrency;
|
|
@@ -565,16 +587,23 @@ function createBenchmarkClient(config = {}) {
|
|
|
565
587
|
activeByStep.set(name, (activeByStep.get(name) ?? 0) + 1);
|
|
566
588
|
requestHeartbeat();
|
|
567
589
|
}
|
|
590
|
+
const previousStep = activeStep;
|
|
591
|
+
activeStep = stepRecord;
|
|
568
592
|
try {
|
|
569
593
|
if (stepOptions.readiness === "poll") {
|
|
570
594
|
await waitForStepReady(name, stepOptions);
|
|
571
595
|
}
|
|
572
|
-
|
|
596
|
+
const value = await fn();
|
|
597
|
+
appendWorkerLog(`${(/* @__PURE__ */ new Date()).toISOString()} [task ${taskIndex}] ${name}`);
|
|
598
|
+
return value;
|
|
573
599
|
} catch (error) {
|
|
574
600
|
stepRecord.status = "error";
|
|
575
601
|
stepRecord.errorCode = getErrorCode(error);
|
|
602
|
+
appendWorkerLog(`${(/* @__PURE__ */ new Date()).toISOString()} [task ${taskIndex}] ${name}`);
|
|
603
|
+
appendWorkerLog(` error: ${error instanceof Error ? error.message : String(error)}`);
|
|
576
604
|
throw error;
|
|
577
605
|
} finally {
|
|
606
|
+
activeStep = previousStep;
|
|
578
607
|
stepRecord.completedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
579
608
|
stepRecord.latencyMs = Date.now() - stepStartedAtMs;
|
|
580
609
|
steps.push(stepRecord);
|
|
@@ -591,16 +620,19 @@ function createBenchmarkClient(config = {}) {
|
|
|
591
620
|
}
|
|
592
621
|
}
|
|
593
622
|
try {
|
|
594
|
-
const data = await
|
|
595
|
-
record.data = toJsonObject(data);
|
|
623
|
+
const data = await options.task({ assignment: claimed, taskIndex, step, measure, log });
|
|
624
|
+
record.data = mergeMeasures(taskMeasures, toJsonObject(data));
|
|
596
625
|
} catch (error) {
|
|
597
626
|
record.status = "error";
|
|
598
627
|
record.errorCode = getErrorCode(error);
|
|
599
|
-
record.data = { errorMessage: error instanceof Error ? error.message : String(error) };
|
|
628
|
+
record.data = mergeMeasures(taskMeasures, { errorMessage: error instanceof Error ? error.message : String(error) });
|
|
600
629
|
} finally {
|
|
601
630
|
record.completedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
602
631
|
record.latencyMs = Date.now() - startedAtMs;
|
|
603
|
-
|
|
632
|
+
if (steps.length === 0) {
|
|
633
|
+
steps.push(implicitTaskStep(record, taskMeasures));
|
|
634
|
+
}
|
|
635
|
+
record.steps = steps;
|
|
604
636
|
doneCount += 1;
|
|
605
637
|
inFlightCount = Math.max(0, inFlightCount - 1);
|
|
606
638
|
if (record.status !== "success") errorCount += 1;
|
|
@@ -632,6 +664,7 @@ function createBenchmarkClient(config = {}) {
|
|
|
632
664
|
});
|
|
633
665
|
throw error;
|
|
634
666
|
} finally {
|
|
667
|
+
await uploadWorkerLogArtifact();
|
|
635
668
|
clearInterval(heartbeat);
|
|
636
669
|
clearInterval(resultFlush);
|
|
637
670
|
}
|
|
@@ -639,84 +672,6 @@ function createBenchmarkClient(config = {}) {
|
|
|
639
672
|
};
|
|
640
673
|
return client;
|
|
641
674
|
}
|
|
642
|
-
async function runBenchmarkWorker(config, options) {
|
|
643
|
-
return createBenchmarkClient(config).runWorker(options);
|
|
644
|
-
}
|
|
645
|
-
function defineStep(name, optionsOrFn, maybeFn) {
|
|
646
|
-
if (name.trim() === "") {
|
|
647
|
-
throw new Error("Benchmark step name must be non-empty.");
|
|
648
|
-
}
|
|
649
|
-
const hasOptions = typeof optionsOrFn !== "function";
|
|
650
|
-
const fn = hasOptions ? maybeFn : optionsOrFn;
|
|
651
|
-
if (!fn) {
|
|
652
|
-
throw new Error("Benchmark step function is required.");
|
|
653
|
-
}
|
|
654
|
-
return { name, options: hasOptions ? optionsOrFn : void 0, fn };
|
|
655
|
-
}
|
|
656
|
-
function defineTask(name, steps, options) {
|
|
657
|
-
if (name.trim() === "") {
|
|
658
|
-
throw new Error("Benchmark task name must be non-empty.");
|
|
659
|
-
}
|
|
660
|
-
if (steps.length === 0) {
|
|
661
|
-
throw new Error("Benchmark task must define at least one step.");
|
|
662
|
-
}
|
|
663
|
-
const names = /* @__PURE__ */ new Set();
|
|
664
|
-
for (const step of steps) {
|
|
665
|
-
if (names.has(step.name)) {
|
|
666
|
-
throw new Error(`Benchmark task step names must be unique. Duplicate step: "${step.name}".`);
|
|
667
|
-
}
|
|
668
|
-
names.add(step.name);
|
|
669
|
-
}
|
|
670
|
-
return { name, steps, options };
|
|
671
|
-
}
|
|
672
|
-
function defineWorker(options) {
|
|
673
|
-
const client = options.client ?? createBenchmarkClient();
|
|
674
|
-
return {
|
|
675
|
-
run(overrides = {}) {
|
|
676
|
-
return client.runWorker({
|
|
677
|
-
benchmarkSlug: options.benchmarkSlug,
|
|
678
|
-
runId: options.runId,
|
|
679
|
-
participantSlug: options.participantSlug,
|
|
680
|
-
processKind: options.processKind,
|
|
681
|
-
processKey: options.processKey,
|
|
682
|
-
concurrency: overrides.concurrency ?? options.concurrency,
|
|
683
|
-
batchSize: overrides.batchSize ?? options.batchSize,
|
|
684
|
-
flushIntervalMs: overrides.flushIntervalMs ?? options.flushIntervalMs,
|
|
685
|
-
heartbeatIntervalMs: overrides.heartbeatIntervalMs ?? options.heartbeatIntervalMs,
|
|
686
|
-
readyPollIntervalMs: overrides.readyPollIntervalMs ?? options.readyPollIntervalMs,
|
|
687
|
-
onFinish: options.onFinish,
|
|
688
|
-
task: options.task
|
|
689
|
-
});
|
|
690
|
-
}
|
|
691
|
-
};
|
|
692
|
-
}
|
|
693
|
-
function defineBench(options) {
|
|
694
|
-
return {
|
|
695
|
-
slug: options.slug,
|
|
696
|
-
task: options.task,
|
|
697
|
-
defineWorker(workerOptions) {
|
|
698
|
-
const participantSlug = workerOptions.participantSlug ?? options.participantSlug;
|
|
699
|
-
if (!participantSlug) {
|
|
700
|
-
throw new Error("Benchmark worker participantSlug is required.");
|
|
701
|
-
}
|
|
702
|
-
return defineWorker({
|
|
703
|
-
benchmarkSlug: options.slug,
|
|
704
|
-
runId: workerOptions.runId,
|
|
705
|
-
participantSlug,
|
|
706
|
-
processKind: workerOptions.processKind,
|
|
707
|
-
processKey: workerOptions.processKey,
|
|
708
|
-
concurrency: workerOptions.concurrency ?? options.concurrency,
|
|
709
|
-
batchSize: workerOptions.batchSize ?? options.batchSize,
|
|
710
|
-
flushIntervalMs: workerOptions.flushIntervalMs ?? options.flushIntervalMs,
|
|
711
|
-
heartbeatIntervalMs: workerOptions.heartbeatIntervalMs ?? options.heartbeatIntervalMs,
|
|
712
|
-
readyPollIntervalMs: workerOptions.readyPollIntervalMs ?? options.readyPollIntervalMs,
|
|
713
|
-
onFinish: workerOptions.onFinish,
|
|
714
|
-
client: workerOptions.client ?? options.client,
|
|
715
|
-
task: workerOptions.task ?? options.task
|
|
716
|
-
});
|
|
717
|
-
}
|
|
718
|
-
};
|
|
719
|
-
}
|
|
720
675
|
|
|
721
676
|
// src/reporter.ts
|
|
722
677
|
var DEFAULT_REPORTER_BATCH_SIZE = 500;
|
|
@@ -749,7 +704,10 @@ var BenchmarkReporter = class _BenchmarkReporter {
|
|
|
749
704
|
processKey: cfg.processKey
|
|
750
705
|
});
|
|
751
706
|
return assignment ? new _BenchmarkReporter(client, cfg, assignment) : null;
|
|
752
|
-
} catch {
|
|
707
|
+
} catch (error) {
|
|
708
|
+
console.warn(
|
|
709
|
+
`[benchsdk] failed to claim worker for ${cfg.benchmarkSlug}/${cfg.participantSlug}: ${error instanceof Error ? error.message : String(error)}`
|
|
710
|
+
);
|
|
753
711
|
return null;
|
|
754
712
|
}
|
|
755
713
|
}
|
|
@@ -824,7 +782,12 @@ var BenchmarkReporter = class _BenchmarkReporter {
|
|
|
824
782
|
contentType: input.contentType,
|
|
825
783
|
metadata: input.metadata,
|
|
826
784
|
body: input.body
|
|
827
|
-
}).catch(() =>
|
|
785
|
+
}).catch((error) => {
|
|
786
|
+
console.warn(
|
|
787
|
+
`[benchsdk] failed to upload ${input.kind} artifact for worker ${this.assignment.workerId}: ${error instanceof Error ? error.message : String(error)}`
|
|
788
|
+
);
|
|
789
|
+
return null;
|
|
790
|
+
});
|
|
828
791
|
}
|
|
829
792
|
flush(isFinal = false) {
|
|
830
793
|
this.flushChain = this.flushChain.then(async () => {
|
|
@@ -840,7 +803,10 @@ var BenchmarkReporter = class _BenchmarkReporter {
|
|
|
840
803
|
isFinal: isFinal && batch.length === this.pending.length,
|
|
841
804
|
records: batch
|
|
842
805
|
});
|
|
843
|
-
} catch {
|
|
806
|
+
} catch (error) {
|
|
807
|
+
console.warn(
|
|
808
|
+
`[benchsdk] dropping ${this.pending.length} unsent task result(s) for worker ${this.assignment.workerId}: ${error instanceof Error ? error.message : String(error)}`
|
|
809
|
+
);
|
|
844
810
|
break;
|
|
845
811
|
}
|
|
846
812
|
this.pending.splice(0, batch.length);
|
|
@@ -942,16 +908,38 @@ function createSystemMetricsCollector() {
|
|
|
942
908
|
}
|
|
943
909
|
};
|
|
944
910
|
}
|
|
911
|
+
|
|
912
|
+
// src/participants.ts
|
|
913
|
+
function filterParticipantsByEnv(participants) {
|
|
914
|
+
const available = [];
|
|
915
|
+
const skipped = [];
|
|
916
|
+
for (const p of participants) {
|
|
917
|
+
const missing = p.requiredEnvVars.filter((v) => !process.env[v]);
|
|
918
|
+
if (missing.length > 0) {
|
|
919
|
+
skipped.push({ name: p.name, missing });
|
|
920
|
+
} else {
|
|
921
|
+
available.push(p);
|
|
922
|
+
}
|
|
923
|
+
}
|
|
924
|
+
return { available, skipped };
|
|
925
|
+
}
|
|
926
|
+
function selectParticipants(all, names) {
|
|
927
|
+
if (!names) return all;
|
|
928
|
+
const unknown = names.filter((n) => !all.some((p) => p.name === n));
|
|
929
|
+
if (unknown.length > 0) {
|
|
930
|
+
console.error(`Unknown participant(s): ${unknown.join(", ")}`);
|
|
931
|
+
console.error(`Available: ${all.map((p) => p.name).join(", ")}`);
|
|
932
|
+
process.exit(1);
|
|
933
|
+
}
|
|
934
|
+
return all.filter((p) => names.includes(p.name));
|
|
935
|
+
}
|
|
945
936
|
export {
|
|
946
937
|
BenchmarkApiError,
|
|
947
938
|
BenchmarkReporter,
|
|
948
939
|
claimBenchmarkReporter,
|
|
949
940
|
createBenchmarkClient,
|
|
950
941
|
createSystemMetricsCollector,
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
defineTask,
|
|
954
|
-
defineWorker,
|
|
955
|
-
runBenchmarkWorker
|
|
942
|
+
filterParticipantsByEnv,
|
|
943
|
+
selectParticipants
|
|
956
944
|
};
|
|
957
945
|
//# sourceMappingURL=index.js.map
|