deepline 0.1.210 → 0.1.212
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/dist/bundling-sources/apps/play-runner-workers/src/entry.ts +203 -12
- package/dist/bundling-sources/apps/play-runner-workers/src/runtime/customer-console.ts +23 -0
- package/dist/bundling-sources/apps/play-runner-workers/src/runtime/map-chunk-plan.ts +15 -0
- package/dist/bundling-sources/apps/play-runner-workers/src/runtime/map-latency-profile.ts +90 -0
- package/dist/bundling-sources/apps/play-runner-workers/src/runtime/tool-dispatch.ts +162 -100
- package/dist/bundling-sources/sdk/src/client.ts +118 -3
- package/dist/bundling-sources/sdk/src/play.ts +2 -0
- package/dist/bundling-sources/sdk/src/plays/bundle-play-file.ts +1 -1
- package/dist/bundling-sources/sdk/src/release.ts +2 -2
- package/dist/bundling-sources/sdk/src/types.ts +47 -0
- package/dist/bundling-sources/shared_libs/play-runtime/app-runtime-api.ts +253 -30
- package/dist/bundling-sources/shared_libs/play-runtime/context.ts +365 -103
- package/dist/bundling-sources/shared_libs/play-runtime/ctx-types.ts +12 -0
- package/dist/bundling-sources/shared_libs/play-runtime/dataset-id.ts +10 -4
- package/dist/bundling-sources/shared_libs/play-runtime/db-session.ts +9 -0
- package/dist/bundling-sources/shared_libs/play-runtime/durable-receipt-execution.ts +45 -8
- package/dist/bundling-sources/shared_libs/play-runtime/ledger-safe-payload.ts +14 -2
- package/dist/bundling-sources/shared_libs/play-runtime/output-size-limits.ts +4 -2
- package/dist/bundling-sources/shared_libs/play-runtime/receipt-sql.ts +21 -1
- package/dist/bundling-sources/shared_libs/play-runtime/run-ledger.ts +128 -6
- package/dist/bundling-sources/shared_libs/play-runtime/run-snapshot-stream.ts +7 -0
- package/dist/bundling-sources/shared_libs/play-runtime/runner-backends/backends/daytona-payload-transport.ts +5 -1
- package/dist/bundling-sources/shared_libs/play-runtime/runner-backends/backends/daytona.ts +37 -5
- package/dist/bundling-sources/shared_libs/play-runtime/runner-backends/types.ts +19 -10
- package/dist/bundling-sources/shared_libs/play-runtime/runtime-api.ts +63 -7
- package/dist/bundling-sources/shared_libs/play-runtime/secret-capability.ts +18 -4
- package/dist/bundling-sources/shared_libs/play-runtime/single-flight.ts +31 -0
- package/dist/bundling-sources/shared_libs/play-runtime/test-runtime-seams.ts +12 -1
- package/dist/bundling-sources/shared_libs/play-runtime/tool-execute-retry-policy.ts +29 -9
- package/dist/bundling-sources/shared_libs/play-runtime/work-receipt-state-machine.ts +22 -1
- package/dist/bundling-sources/shared_libs/plays/bundling/index.ts +79 -1
- package/dist/bundling-sources/shared_libs/plays/dataset.ts +41 -5
- package/dist/cli/index.js +728 -239
- package/dist/cli/index.mjs +728 -239
- package/dist/index.d.mts +93 -7
- package/dist/index.d.ts +93 -7
- package/dist/index.js +245 -46
- package/dist/index.mjs +245 -46
- package/dist/plays/bundle-play-file.mjs +65 -4
- package/package.json +1 -1
|
@@ -68,6 +68,7 @@ import {
|
|
|
68
68
|
} from './child-play-await';
|
|
69
69
|
import { submitChildPlayThroughCoordinator } from './child-play-submit';
|
|
70
70
|
import { isRetryableWorkerRuntimeApiError } from './runtime-api-retry';
|
|
71
|
+
import { formatCustomerConsoleValue } from './runtime/customer-console';
|
|
71
72
|
import {
|
|
72
73
|
attachToolResultListDataset,
|
|
73
74
|
createToolExecuteResult,
|
|
@@ -116,6 +117,7 @@ import {
|
|
|
116
117
|
isQueryResultDatasetTool,
|
|
117
118
|
} from '../../../shared_libs/play-runtime/query-result-dataset';
|
|
118
119
|
import { DEDUPE_DUPLICATE_KEY_SAMPLE_CAP } from '../../../shared_libs/play-runtime/map-row-identity';
|
|
120
|
+
import { createRuntimeDatasetId } from '../../../shared_libs/play-runtime/dataset-id';
|
|
119
121
|
import {
|
|
120
122
|
getTopLevelPipelineSubsteps,
|
|
121
123
|
getCompiledPipelineSubsteps,
|
|
@@ -166,6 +168,7 @@ import {
|
|
|
166
168
|
createWorkerWorkBudgetMeter,
|
|
167
169
|
type WorkerWorkBudgetMeter,
|
|
168
170
|
} from './runtime/work-budget';
|
|
171
|
+
import { MapLatencyProfile } from './runtime/map-latency-profile';
|
|
169
172
|
import {
|
|
170
173
|
WORKER_RUN_DISPATCHER_DEFAULT_MAX_RESIDENT_ROW_BYTES,
|
|
171
174
|
WorkerRunWorkDispatcher,
|
|
@@ -299,7 +302,7 @@ import type {
|
|
|
299
302
|
LiveNodeProgressMap,
|
|
300
303
|
LiveNodeProgressSnapshot,
|
|
301
304
|
} from './runtime/live-progress';
|
|
302
|
-
import { extractErrorBilling } from './runtime/tool-http-errors';
|
|
305
|
+
import { extractErrorBilling, ToolHttpError } from './runtime/tool-http-errors';
|
|
303
306
|
import {
|
|
304
307
|
WorkflowAbortError,
|
|
305
308
|
isAbortLikeError,
|
|
@@ -323,6 +326,55 @@ import type { PlayRunInputPayload } from '../../../shared_libs/play-runtime/sche
|
|
|
323
326
|
// @ts-ignore - virtual module substituted by esbuild plugin at bundle time.
|
|
324
327
|
import playFn from 'deepline-play-entry';
|
|
325
328
|
|
|
329
|
+
const customerConsoleStorage = new AsyncLocalStorage<
|
|
330
|
+
(message: string) => void
|
|
331
|
+
>();
|
|
332
|
+
|
|
333
|
+
type CustomerConsoleGlobal = typeof globalThis & {
|
|
334
|
+
__deeplineCaptureCustomerConsole?: (
|
|
335
|
+
method: string,
|
|
336
|
+
args: unknown[],
|
|
337
|
+
) => unknown;
|
|
338
|
+
};
|
|
339
|
+
|
|
340
|
+
(globalThis as CustomerConsoleGlobal).__deeplineCaptureCustomerConsole = (
|
|
341
|
+
method,
|
|
342
|
+
args,
|
|
343
|
+
) => {
|
|
344
|
+
const log = customerConsoleStorage.getStore();
|
|
345
|
+
if (log) {
|
|
346
|
+
assertNoSecretTaint(args, `console.${method}`);
|
|
347
|
+
const message = args.map(formatCustomerConsoleValue).join(' ');
|
|
348
|
+
log(`[console.${method}] ${message}`.slice(0, 2_000));
|
|
349
|
+
}
|
|
350
|
+
};
|
|
351
|
+
|
|
352
|
+
function runCustomerPlay(
|
|
353
|
+
ctx: unknown,
|
|
354
|
+
input: PlayRunInputPayload,
|
|
355
|
+
): Promise<unknown> {
|
|
356
|
+
return customerConsoleStorage.run(
|
|
357
|
+
(ctx as { log(message: string): void }).log,
|
|
358
|
+
() => (playFn as typeof runCustomerPlay)(ctx, input),
|
|
359
|
+
);
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
function runtimeDatasetIdentity(playName: string, tableNamespace: string) {
|
|
363
|
+
return {
|
|
364
|
+
datasetId: createRuntimeDatasetId(playName, tableNamespace),
|
|
365
|
+
path: `datasets.${tableNamespace}`,
|
|
366
|
+
tableNamespace,
|
|
367
|
+
};
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
function datasetAvailableLog(
|
|
371
|
+
runId: string,
|
|
372
|
+
dataset: ReturnType<typeof runtimeDatasetIdentity>,
|
|
373
|
+
count: number,
|
|
374
|
+
): string {
|
|
375
|
+
return `Dataset ${dataset.path} available: ${count} persisted rows; export: deepline runs export ${runId} --dataset ${dataset.path} --out ${dataset.tableNamespace}.csv`;
|
|
376
|
+
}
|
|
377
|
+
|
|
326
378
|
type RunRequest = {
|
|
327
379
|
runId: string;
|
|
328
380
|
callbackUrl: string;
|
|
@@ -998,6 +1050,17 @@ type WorkerCtxCallbacks = {
|
|
|
998
1050
|
}) => void | Promise<void>;
|
|
999
1051
|
onMapStarted?: (nodeId: string, at?: number) => void;
|
|
1000
1052
|
onMapCompleted?: (nodeId: string, at?: number) => void;
|
|
1053
|
+
onDatasetLifecycle?: (event: {
|
|
1054
|
+
datasetId: string;
|
|
1055
|
+
path: string;
|
|
1056
|
+
tableNamespace: string;
|
|
1057
|
+
phase: 'registered' | 'available' | 'failed';
|
|
1058
|
+
persistedRows?: number;
|
|
1059
|
+
succeededRows?: number;
|
|
1060
|
+
failedRows?: number;
|
|
1061
|
+
complete?: boolean;
|
|
1062
|
+
at: number;
|
|
1063
|
+
}) => void | Promise<void>;
|
|
1001
1064
|
onToolCalled?: (toolId: string, at?: number) => void;
|
|
1002
1065
|
onToolFailed?: (toolId: string, at?: number) => void;
|
|
1003
1066
|
};
|
|
@@ -2072,6 +2135,7 @@ async function callToolDirect(
|
|
|
2072
2135
|
while (true) {
|
|
2073
2136
|
requestAttempt += 1;
|
|
2074
2137
|
let res: Response;
|
|
2138
|
+
const providerCallStartedAt = nowMs();
|
|
2075
2139
|
try {
|
|
2076
2140
|
const runtimeApiTimeout = resolveToolRuntimeApiTimeout({
|
|
2077
2141
|
requestInput: input,
|
|
@@ -2160,8 +2224,11 @@ async function callToolDirect(
|
|
|
2160
2224
|
} catch (error) {
|
|
2161
2225
|
transportAttempt += 1;
|
|
2162
2226
|
const message = error instanceof Error ? error.message : String(error);
|
|
2163
|
-
lastError = new
|
|
2227
|
+
lastError = new ToolHttpError(
|
|
2164
2228
|
`Tool ${toolId} transport failed calling ${path} for run ${req.runId} on attempt ${transportAttempt}/${TOOL_EXECUTE_TRANSPORT_MAX_ATTEMPTS}: ${message}`,
|
|
2229
|
+
null,
|
|
2230
|
+
0,
|
|
2231
|
+
'repairable',
|
|
2165
2232
|
);
|
|
2166
2233
|
if (
|
|
2167
2234
|
transportAttempt >= TOOL_EXECUTE_TRANSPORT_MAX_ATTEMPTS ||
|
|
@@ -2211,6 +2278,7 @@ async function callToolDirect(
|
|
|
2211
2278
|
bodyText: text,
|
|
2212
2279
|
retryAfterHeader: res.headers.get('retry-after'),
|
|
2213
2280
|
transientHttpRetrySafe,
|
|
2281
|
+
providerLatencyMs: nowMs() - providerCallStartedAt,
|
|
2214
2282
|
});
|
|
2215
2283
|
lastError = failure.error;
|
|
2216
2284
|
if (failure.backpressureDelayMs !== null) {
|
|
@@ -4598,6 +4666,11 @@ function createMinimalWorkerCtx(
|
|
|
4598
4666
|
opts?: WorkerMapOptions,
|
|
4599
4667
|
): Promise<unknown> => {
|
|
4600
4668
|
const mapStartedAt = nowMs();
|
|
4669
|
+
const mapLatencyProfile = new MapLatencyProfile(
|
|
4670
|
+
req.testPolicyOverrides?.mapLatencyProfile === true,
|
|
4671
|
+
name,
|
|
4672
|
+
mapStartedAt,
|
|
4673
|
+
);
|
|
4601
4674
|
const mapNodeId = `map:${name}`;
|
|
4602
4675
|
const inputRows = rows;
|
|
4603
4676
|
const rowCountHint = datasetRowCountHint(inputRows);
|
|
@@ -4859,6 +4932,7 @@ function createMinimalWorkerCtx(
|
|
|
4859
4932
|
};
|
|
4860
4933
|
|
|
4861
4934
|
let totalRowsWritten = 0;
|
|
4935
|
+
let datasetLifecycleRegistered = false;
|
|
4862
4936
|
|
|
4863
4937
|
const volatileWorkflowChunkRows = new Map<
|
|
4864
4938
|
number,
|
|
@@ -4909,6 +4983,22 @@ function createMinimalWorkerCtx(
|
|
|
4909
4983
|
budgetMeter: workBudgetMeter,
|
|
4910
4984
|
force: !!req.force,
|
|
4911
4985
|
});
|
|
4986
|
+
if (!datasetLifecycleRegistered) {
|
|
4987
|
+
datasetLifecycleRegistered = true;
|
|
4988
|
+
const dataset = runtimeDatasetIdentity(req.playName, name);
|
|
4989
|
+
await callbacks?.onDatasetLifecycle?.({
|
|
4990
|
+
...dataset,
|
|
4991
|
+
phase: 'registered',
|
|
4992
|
+
complete: false,
|
|
4993
|
+
at: nowMs(),
|
|
4994
|
+
});
|
|
4995
|
+
emitEvent({
|
|
4996
|
+
type: 'log',
|
|
4997
|
+
level: 'info',
|
|
4998
|
+
message: `Dataset ${dataset.path} registered`,
|
|
4999
|
+
ts: nowMs(),
|
|
5000
|
+
});
|
|
5001
|
+
}
|
|
4912
5002
|
const activeRowAttempt = {
|
|
4913
5003
|
attemptId: prepared.attemptId ?? rowAttempt.attemptId,
|
|
4914
5004
|
attemptOwnerRunId:
|
|
@@ -4935,6 +5025,7 @@ function createMinimalWorkerCtx(
|
|
|
4935
5025
|
blockedRows: prepared.blockedRows.length,
|
|
4936
5026
|
},
|
|
4937
5027
|
});
|
|
5028
|
+
mapLatencyProfile.recordPhase('prepare_rows', nowMs() - prepareStartedAt);
|
|
4938
5029
|
const progressTotalRows = rowCountHint ?? chunkRows.length;
|
|
4939
5030
|
const preparedCompletedRows = Math.min(
|
|
4940
5031
|
progressTotalRows,
|
|
@@ -5215,7 +5306,10 @@ function createMinimalWorkerCtx(
|
|
|
5215
5306
|
deserializeDurableValue: deserializeDurableStepValue,
|
|
5216
5307
|
nowMs,
|
|
5217
5308
|
batchGraceMs: req.testPolicyOverrides?.batchGraceMs,
|
|
5218
|
-
recordPerfTrace: (trace) =>
|
|
5309
|
+
recordPerfTrace: (trace) => {
|
|
5310
|
+
mapLatencyProfile.recordToolTrace(trace);
|
|
5311
|
+
recordRunnerPerfTrace({ req, ...trace });
|
|
5312
|
+
},
|
|
5219
5313
|
abortSignal,
|
|
5220
5314
|
onRequestsSettled: reportSettledToolRequests,
|
|
5221
5315
|
callbacks,
|
|
@@ -5505,9 +5599,16 @@ function createMinimalWorkerCtx(
|
|
|
5505
5599
|
if (circuitError) throw circuitError;
|
|
5506
5600
|
const myIndex = idx++;
|
|
5507
5601
|
if (myIndex >= rowsToExecute.length) return;
|
|
5602
|
+
const rowQueueStartedAt = mapLatencyProfile.enabled ? nowMs() : 0;
|
|
5508
5603
|
const rowSlot = await governor.acquireRowSlot({
|
|
5509
5604
|
signal: abortSignal,
|
|
5510
5605
|
});
|
|
5606
|
+
if (mapLatencyProfile.enabled) {
|
|
5607
|
+
mapLatencyProfile.recordRowQueue(nowMs() - rowQueueStartedAt);
|
|
5608
|
+
}
|
|
5609
|
+
const rowExecutionStartedAt = mapLatencyProfile.enabled
|
|
5610
|
+
? nowMs()
|
|
5611
|
+
: 0;
|
|
5511
5612
|
let rowMarkedActive = false;
|
|
5512
5613
|
try {
|
|
5513
5614
|
startedExecutedRows += 1;
|
|
@@ -5744,6 +5845,11 @@ function createMinimalWorkerCtx(
|
|
|
5744
5845
|
// single clean run failure.
|
|
5745
5846
|
}
|
|
5746
5847
|
} finally {
|
|
5848
|
+
if (mapLatencyProfile.enabled) {
|
|
5849
|
+
mapLatencyProfile.recordRowExecution(
|
|
5850
|
+
nowMs() - rowExecutionStartedAt,
|
|
5851
|
+
);
|
|
5852
|
+
}
|
|
5747
5853
|
if (rowMarkedActive) {
|
|
5748
5854
|
activeExecutedRows = Math.max(0, activeExecutedRows - 1);
|
|
5749
5855
|
reportExecutionHeartbeat(false);
|
|
@@ -5796,6 +5902,10 @@ function createMinimalWorkerCtx(
|
|
|
5796
5902
|
concurrency,
|
|
5797
5903
|
},
|
|
5798
5904
|
});
|
|
5905
|
+
mapLatencyProfile.recordPhase(
|
|
5906
|
+
'worker_execution',
|
|
5907
|
+
nowMs() - workersStartedAt,
|
|
5908
|
+
);
|
|
5799
5909
|
const buildRowFailureSamples = () =>
|
|
5800
5910
|
failedRowEntries
|
|
5801
5911
|
.map((failure, executedIndex) =>
|
|
@@ -5923,6 +6033,10 @@ function createMinimalWorkerCtx(
|
|
|
5923
6033
|
rowsToExecute: rowsToExecute.length,
|
|
5924
6034
|
},
|
|
5925
6035
|
});
|
|
6036
|
+
mapLatencyProfile.recordPhase(
|
|
6037
|
+
'persist_rows',
|
|
6038
|
+
nowMs() - persistRowsStartedAt,
|
|
6039
|
+
);
|
|
5926
6040
|
} catch (error) {
|
|
5927
6041
|
if (
|
|
5928
6042
|
workflowStep &&
|
|
@@ -6028,6 +6142,7 @@ function createMinimalWorkerCtx(
|
|
|
6028
6142
|
ms: nowMs() - hashStartedAt,
|
|
6029
6143
|
extra: { mapName: name, chunkIndex, rows: out.length },
|
|
6030
6144
|
});
|
|
6145
|
+
mapLatencyProfile.recordPhase('hash', nowMs() - hashStartedAt);
|
|
6031
6146
|
recordRunnerPerfTrace({
|
|
6032
6147
|
req,
|
|
6033
6148
|
phase: 'runner.map_chunk.total',
|
|
@@ -6230,6 +6345,23 @@ function createMinimalWorkerCtx(
|
|
|
6230
6345
|
finalizedRowsWritten,
|
|
6231
6346
|
),
|
|
6232
6347
|
});
|
|
6348
|
+
const dataset = runtimeDatasetIdentity(req.playName, name);
|
|
6349
|
+
const persistedRows = finalizedRowsWritten + totalRowsFailed;
|
|
6350
|
+
await callbacks?.onDatasetLifecycle?.({
|
|
6351
|
+
...dataset,
|
|
6352
|
+
phase: 'available',
|
|
6353
|
+
persistedRows,
|
|
6354
|
+
succeededRows: finalizedRowsWritten,
|
|
6355
|
+
failedRows: totalRowsFailed,
|
|
6356
|
+
complete: true,
|
|
6357
|
+
at: completedAt,
|
|
6358
|
+
});
|
|
6359
|
+
emitEvent({
|
|
6360
|
+
type: 'log',
|
|
6361
|
+
level: totalRowsFailed > 0 ? 'warn' : 'info',
|
|
6362
|
+
message: datasetAvailableLog(req.runId, dataset, persistedRows),
|
|
6363
|
+
ts: completedAt,
|
|
6364
|
+
});
|
|
6233
6365
|
callbacks?.onMapCompleted?.(mapNodeId, completedAt);
|
|
6234
6366
|
emitEvent({
|
|
6235
6367
|
type: 'log',
|
|
@@ -6503,7 +6635,8 @@ function createMinimalWorkerCtx(
|
|
|
6503
6635
|
if (chunkResult.fatalError) {
|
|
6504
6636
|
throw new Error(
|
|
6505
6637
|
`ctx.dataset("${name}") stopped after a runtime persistence failure ` +
|
|
6506
|
-
`outside the retryable chunk step.
|
|
6638
|
+
`outside the retryable chunk step. Circuit breaker prevented queued row ` +
|
|
6639
|
+
`dispatch. Provider calls already executed for ` +
|
|
6507
6640
|
`${chunkResult.rowsExecuted} row(s), so the chunk was not retried. ` +
|
|
6508
6641
|
`Fix the runtime persistence cause and re-run to resume. ` +
|
|
6509
6642
|
`First error: ${chunkResult.fatalError}`,
|
|
@@ -6643,6 +6776,27 @@ function createMinimalWorkerCtx(
|
|
|
6643
6776
|
chunks: dispatchResult.chunksExecuted,
|
|
6644
6777
|
},
|
|
6645
6778
|
});
|
|
6779
|
+
if (mapLatencyProfile.enabled) {
|
|
6780
|
+
const endedAt = nowMs();
|
|
6781
|
+
const summary = mapLatencyProfile.summary(endedAt, {
|
|
6782
|
+
chunks: dispatchResult.chunksExecuted,
|
|
6783
|
+
rows: totalRowsWritten,
|
|
6784
|
+
});
|
|
6785
|
+
recordRunnerPerfTrace({
|
|
6786
|
+
req,
|
|
6787
|
+
phase: 'runner.map.profile',
|
|
6788
|
+
ms: endedAt - mapStartedAt,
|
|
6789
|
+
extra: summary,
|
|
6790
|
+
});
|
|
6791
|
+
// Local Absurd does not always forward dynamic-worker perf traces. Keep
|
|
6792
|
+
// one bounded copy in the durable run log for the diagnostic command.
|
|
6793
|
+
emitEvent({
|
|
6794
|
+
type: 'log',
|
|
6795
|
+
level: 'info',
|
|
6796
|
+
message: `[perf] map_latency_profile ${JSON.stringify(summary)}`,
|
|
6797
|
+
ts: endedAt,
|
|
6798
|
+
});
|
|
6799
|
+
}
|
|
6646
6800
|
return dataset;
|
|
6647
6801
|
};
|
|
6648
6802
|
|
|
@@ -7555,9 +7709,7 @@ async function handleRun(request: Request, env: WorkerEnv): Promise<Response> {
|
|
|
7555
7709
|
captureHarnessBinding(env);
|
|
7556
7710
|
await probeHarnessOnce(env, runPrefix);
|
|
7557
7711
|
const ctx = createMinimalWorkerCtx(req, emit, env);
|
|
7558
|
-
const result = await (
|
|
7559
|
-
playFn as (ctx: unknown, input: PlayRunInputPayload) => Promise<unknown>
|
|
7560
|
-
)(ctx, req.runtimeInput);
|
|
7712
|
+
const result = await runCustomerPlay(ctx, req.runtimeInput);
|
|
7561
7713
|
emit({
|
|
7562
7714
|
type: 'result',
|
|
7563
7715
|
result,
|
|
@@ -7971,6 +8123,17 @@ async function executeRunRequest(
|
|
|
7971
8123
|
},
|
|
7972
8124
|
onMapStarted: (nodeId, at) => stepLifecycle?.onMapStarted(nodeId, at),
|
|
7973
8125
|
onMapCompleted: (nodeId, at) => stepLifecycle?.onMapCompleted(nodeId, at),
|
|
8126
|
+
onDatasetLifecycle: async (event) => {
|
|
8127
|
+
const { at, ...dataset } = event;
|
|
8128
|
+
pendingLedgerEvents.push({
|
|
8129
|
+
...dataset,
|
|
8130
|
+
type: 'dataset.lifecycle',
|
|
8131
|
+
runId: req.runId,
|
|
8132
|
+
source: 'worker',
|
|
8133
|
+
occurredAt: at,
|
|
8134
|
+
});
|
|
8135
|
+
await flushLedgerEvents(event.complete === true);
|
|
8136
|
+
},
|
|
7974
8137
|
onToolCalled: (toolId, at) => stepLifecycle?.onToolCalled(toolId, at),
|
|
7975
8138
|
onToolFailed: (toolId, at) => stepLifecycle?.onToolFailed(toolId, at),
|
|
7976
8139
|
};
|
|
@@ -8037,9 +8200,7 @@ async function executeRunRequest(
|
|
|
8037
8200
|
: null;
|
|
8038
8201
|
try {
|
|
8039
8202
|
const playStartedAt = nowMs();
|
|
8040
|
-
const result = await (
|
|
8041
|
-
playFn as (ctx: unknown, input: PlayRunInputPayload) => Promise<unknown>
|
|
8042
|
-
)(ctx, req.runtimeInput);
|
|
8203
|
+
const result = await runCustomerPlay(ctx, req.runtimeInput);
|
|
8043
8204
|
recordRunnerPerfTrace({
|
|
8044
8205
|
req,
|
|
8045
8206
|
phase: 'runner.play_function',
|
|
@@ -8083,7 +8244,33 @@ async function executeRunRequest(
|
|
|
8083
8244
|
ms: nowMs() - ledgerFlushWaitStartedAt,
|
|
8084
8245
|
});
|
|
8085
8246
|
const resultDatasetStartedAt = nowMs();
|
|
8086
|
-
await persistResultDatasets(
|
|
8247
|
+
const persistedResultCounts = await persistResultDatasets(
|
|
8248
|
+
req,
|
|
8249
|
+
promoted.handles,
|
|
8250
|
+
serializedResult,
|
|
8251
|
+
);
|
|
8252
|
+
for (const dataset of promoted.handles) {
|
|
8253
|
+
if (dataset.datasetKind === 'map') continue;
|
|
8254
|
+
const identity = runtimeDatasetIdentity(
|
|
8255
|
+
req.playName,
|
|
8256
|
+
dataset.tableNamespace,
|
|
8257
|
+
);
|
|
8258
|
+
const count = persistedResultCounts.get(dataset.tableNamespace) ?? 0;
|
|
8259
|
+
pendingLedgerEvents.push({
|
|
8260
|
+
...identity,
|
|
8261
|
+
type: 'dataset.lifecycle',
|
|
8262
|
+
runId: req.runId,
|
|
8263
|
+
source: 'worker',
|
|
8264
|
+
occurredAt: nowMs(),
|
|
8265
|
+
phase: 'available',
|
|
8266
|
+
persistedRows: count,
|
|
8267
|
+
succeededRows: count,
|
|
8268
|
+
failedRows: 0,
|
|
8269
|
+
complete: true,
|
|
8270
|
+
});
|
|
8271
|
+
appendRunLogLine(datasetAvailableLog(req.runId, identity, count));
|
|
8272
|
+
}
|
|
8273
|
+
await flushLedgerEvents(true);
|
|
8087
8274
|
resultDatasetsPersisted = true;
|
|
8088
8275
|
recordRunnerPerfTrace({
|
|
8089
8276
|
req,
|
|
@@ -8547,8 +8734,9 @@ async function persistResultDatasets(
|
|
|
8547
8734
|
req: RunRequest,
|
|
8548
8735
|
resultDatasets: ProjectedResultDatasetHandle[],
|
|
8549
8736
|
serializedResult: unknown,
|
|
8550
|
-
): Promise<
|
|
8737
|
+
): Promise<Map<string, number>> {
|
|
8551
8738
|
const persistedNamespaces = new Set<string>();
|
|
8739
|
+
const persistedCounts = new Map<string, number>();
|
|
8552
8740
|
for (const dataset of resultDatasets) {
|
|
8553
8741
|
if (dataset.datasetKind === 'map') continue;
|
|
8554
8742
|
const handle = dataset.handle as WorkerDatasetHandle<
|
|
@@ -8587,6 +8775,7 @@ async function persistResultDatasets(
|
|
|
8587
8775
|
inputOffset += chunk.length;
|
|
8588
8776
|
}
|
|
8589
8777
|
persistedNamespaces.add(dataset.tableNamespace);
|
|
8778
|
+
persistedCounts.set(dataset.tableNamespace, inputOffset);
|
|
8590
8779
|
}
|
|
8591
8780
|
|
|
8592
8781
|
const datasets = collectDatasetEnvelopes(serializedResult);
|
|
@@ -8620,7 +8809,9 @@ async function persistResultDatasets(
|
|
|
8620
8809
|
attemptLeaseTtlMs: runtimeSheetAttemptLeaseTtlMs(req),
|
|
8621
8810
|
userEmail: req.userEmail,
|
|
8622
8811
|
});
|
|
8812
|
+
persistedCounts.set(dataset.tableNamespace, dataset.rows.length);
|
|
8623
8813
|
}
|
|
8814
|
+
return persistedCounts;
|
|
8624
8815
|
}
|
|
8625
8816
|
|
|
8626
8817
|
const RESULT_DATASET_PERSIST_CHUNK_ROWS = 5_000;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
export function formatCustomerConsoleValue(value: unknown): string {
|
|
2
|
+
if (typeof value === 'string') return value;
|
|
3
|
+
try {
|
|
4
|
+
if (value instanceof Error) return value.stack ?? value.message;
|
|
5
|
+
if (value && typeof value === 'object') {
|
|
6
|
+
const seen = new WeakSet<object>();
|
|
7
|
+
const serialized = JSON.stringify(value, (_key, nested) => {
|
|
8
|
+
if (typeof nested === 'symbol' || typeof nested === 'bigint') {
|
|
9
|
+
return nested.toString();
|
|
10
|
+
}
|
|
11
|
+
if (nested && typeof nested === 'object') {
|
|
12
|
+
if (seen.has(nested)) return '[Circular]';
|
|
13
|
+
seen.add(nested);
|
|
14
|
+
}
|
|
15
|
+
return nested;
|
|
16
|
+
});
|
|
17
|
+
if (serialized !== undefined) return serialized;
|
|
18
|
+
}
|
|
19
|
+
return String(value);
|
|
20
|
+
} catch {
|
|
21
|
+
return '[Unprintable]';
|
|
22
|
+
}
|
|
23
|
+
}
|
|
@@ -337,6 +337,21 @@ function countToolSubstep(substep: PlayStaticSubstep): MapToolStats {
|
|
|
337
337
|
minProviderBranchCoalescedCount(branchStats),
|
|
338
338
|
];
|
|
339
339
|
}
|
|
340
|
+
if (substep.kind === 'loop') {
|
|
341
|
+
// The static extractor records every switch arm in `steps` when a loop
|
|
342
|
+
// walks a fixed provider list. Each arm can execute on a different
|
|
343
|
+
// iteration, so using the conditional's max-one-branch estimate would
|
|
344
|
+
// budget a seven-provider waterfall as one provider call per row.
|
|
345
|
+
return substep.steps.reduce<MapToolStats>((stats, step) => {
|
|
346
|
+
const stepStats =
|
|
347
|
+
step.type === 'control_flow' &&
|
|
348
|
+
step.kind === 'conditional' &&
|
|
349
|
+
step.steps.length > 0
|
|
350
|
+
? countToolSubsteps(step.steps)
|
|
351
|
+
: countToolSubstep(step);
|
|
352
|
+
return addStats(stats, stepStats);
|
|
353
|
+
}, [0, 0, 0, 0, 0]);
|
|
354
|
+
}
|
|
340
355
|
return countToolSubsteps(substep.steps);
|
|
341
356
|
}
|
|
342
357
|
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
type ToolTrace = {
|
|
2
|
+
phase: string;
|
|
3
|
+
ms?: number;
|
|
4
|
+
extra?: Record<string, unknown>;
|
|
5
|
+
};
|
|
6
|
+
|
|
7
|
+
type Totals = Record<string, number>;
|
|
8
|
+
|
|
9
|
+
function add(totals: Totals, key: string, ms: number | undefined): void {
|
|
10
|
+
if (typeof ms !== 'number' || !Number.isFinite(ms) || ms < 0) return;
|
|
11
|
+
totals[key] = (totals[key] ?? 0) + ms;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Opt-in map profiler. It aggregates in memory and produces one terminal
|
|
16
|
+
* trace, so normal runs do not pay per-row/per-tool telemetry cost.
|
|
17
|
+
*/
|
|
18
|
+
export class MapLatencyProfile {
|
|
19
|
+
private readonly phaseWorkMs: Totals = {};
|
|
20
|
+
private readonly toolWorkMs: Totals = {};
|
|
21
|
+
private readonly counts: Totals = {};
|
|
22
|
+
|
|
23
|
+
constructor(
|
|
24
|
+
readonly enabled: boolean,
|
|
25
|
+
private readonly mapName: string,
|
|
26
|
+
private readonly startedAtMs: number,
|
|
27
|
+
) {}
|
|
28
|
+
|
|
29
|
+
recordPhase(phase: string, ms: number, count = 1): void {
|
|
30
|
+
if (!this.enabled) return;
|
|
31
|
+
add(this.phaseWorkMs, phase, ms);
|
|
32
|
+
this.counts[`${phase}_count`] =
|
|
33
|
+
(this.counts[`${phase}_count`] ?? 0) + count;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
recordRowQueue(ms: number): void {
|
|
37
|
+
this.recordPhase('row_slot_queue', ms);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
recordRowExecution(ms: number): void {
|
|
41
|
+
this.recordPhase('row_execution', ms);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
recordToolTrace(trace: ToolTrace): void {
|
|
45
|
+
if (!this.enabled) return;
|
|
46
|
+
switch (trace.phase) {
|
|
47
|
+
case 'runner.tool.request':
|
|
48
|
+
add(this.toolWorkMs, 'request_end_to_end', trace.ms);
|
|
49
|
+
this.counts.tool_requests = (this.counts.tool_requests ?? 0) + 1;
|
|
50
|
+
break;
|
|
51
|
+
case 'runner.tool.drain':
|
|
52
|
+
add(this.toolWorkMs, 'drain', trace.ms);
|
|
53
|
+
this.counts.tool_drains = (this.counts.tool_drains ?? 0) + 1;
|
|
54
|
+
break;
|
|
55
|
+
case 'runner.tool.group':
|
|
56
|
+
add(this.toolWorkMs, 'group', trace.ms);
|
|
57
|
+
this.counts.tool_groups = (this.counts.tool_groups ?? 0) + 1;
|
|
58
|
+
break;
|
|
59
|
+
case 'runner.tool.receipt_prepare':
|
|
60
|
+
add(this.toolWorkMs, 'receipt_prepare', trace.ms);
|
|
61
|
+
break;
|
|
62
|
+
case 'runner.tool.admission':
|
|
63
|
+
add(this.toolWorkMs, 'admission_wait', trace.ms);
|
|
64
|
+
break;
|
|
65
|
+
case 'runner.tool.provider':
|
|
66
|
+
add(this.toolWorkMs, 'provider_execute', trace.ms);
|
|
67
|
+
break;
|
|
68
|
+
case 'runner.tool.receipt_complete':
|
|
69
|
+
add(this.toolWorkMs, 'receipt_complete', trace.ms);
|
|
70
|
+
break;
|
|
71
|
+
default:
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
summary(endedAtMs: number, input: { chunks: number; rows: number }) {
|
|
77
|
+
return {
|
|
78
|
+
mapName: this.mapName,
|
|
79
|
+
mapWallMs: Math.max(0, endedAtMs - this.startedAtMs),
|
|
80
|
+
chunks: input.chunks,
|
|
81
|
+
rows: input.rows,
|
|
82
|
+
// These are summed work durations. They can exceed mapWallMs when rows
|
|
83
|
+
// or provider calls overlap; the terminal trace must not mislabel them
|
|
84
|
+
// as a wall-clock decomposition.
|
|
85
|
+
phaseWorkMs: this.phaseWorkMs,
|
|
86
|
+
toolWorkMs: this.toolWorkMs,
|
|
87
|
+
counts: this.counts,
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
}
|