@@ -43,6 +43,10 @@ import {
4343 SANDBOX_OUTPUT_DIR_MAX_DEPTH ,
4444 SANDBOX_OUTPUT_DIR_SENTINEL ,
4545} from '@/lib/execution/remote-sandbox/paths'
46+ import {
47+ createSandboxPhaseTimer ,
48+ type SandboxPhaseTimer ,
49+ } from '@/lib/execution/remote-sandbox/phase-timing'
4650import { resolvePiSandboxLifetimeMs } from '@/lib/execution/remote-sandbox/pi-lifetime'
4751import { resolveProvider } from '@/lib/execution/remote-sandbox/provider'
4852import {
@@ -116,13 +120,16 @@ async function leaseSandbox(
116120 selected : ResolvedSandbox | null ,
117121 signal : AbortSignal ,
118122 meterUsage : boolean | undefined ,
119- session : SandboxSessionRequest | undefined
123+ session : SandboxSessionRequest | undefined ,
124+ phases : SandboxPhaseTimer
120125) : Promise < SandboxLease > {
121126 const provider = resolveProvider ( )
122127 const sessionCapable = Boolean ( session && ! meterUsage && provider . findSessionSandbox )
123128
124129 if ( session && sessionCapable ) {
130+ const endAccess = phases . start ( 'access' )
125131 await prepareSandboxSessionAccess ( session . key , signal )
132+ endAccess ( )
126133 return withSandboxSessionLock ( session . key , signal , async ( leaseSignal ) => {
127134 const { created, status } = await ensureSessionSandbox ( {
128135 provider,
@@ -132,6 +139,7 @@ async function leaseSandbox(
132139 session,
133140 signal : leaseSignal ,
134141 bootstrapTimeoutMs : remainingSandboxBudgetMs ( signal ) ,
142+ phases,
135143 } )
136144 return {
137145 created,
@@ -214,6 +222,16 @@ function raceSandboxAbort<T>(operation: Promise<T>, signal: AbortSignal): Promis
214222 } )
215223}
216224
225+ /** Attaches the phase breakdown to whichever result the execution returns, after its release. */
226+ async function withPhaseTimings (
227+ execute : ( phases : SandboxPhaseTimer ) => Promise < SandboxExecutionResult >
228+ ) : Promise < SandboxExecutionResult > {
229+ const phases = createSandboxPhaseTimer ( )
230+ const result = await execute ( phases )
231+ result . phaseMs = phases . timings
232+ return result
233+ }
234+
217235async function withSandboxExecutionBudget < T > (
218236 timeoutMs : number ,
219237 parentSignal : AbortSignal | undefined ,
@@ -864,22 +882,26 @@ async function acceptSessionOutputHistory(
864882async function executeInSandboxWithinBudget (
865883 // The budget wrapper always injects the signal; the required-signal type states that
866884 // invariant instead of a cast hiding it.
867- req : SandboxExecutionRequest & { signal : AbortSignal }
885+ req : SandboxExecutionRequest & { signal : AbortSignal } ,
886+ phases : SandboxPhaseTimer
868887) : Promise < SandboxExecutionResult > {
869888 const { code, language, signal } = req
870889 const kind = req . sandboxKind ?? 'code'
871890 throwIfAborted ( signal )
872891
873892 // Resolved before the sandbox is created so a selection that cannot be honored
874893 // fails without spending a provider create.
894+ const endResolve = phases . start ( 'resolve' )
875895 const selected = await resolveWorkspaceSandbox ( {
876896 kind,
877897 language,
878898 workspaceId : req . workspaceId ,
879899 sandboxId : req . sandboxId ,
880900 } )
901+ endResolve ( )
881902 throwIfAborted ( signal )
882903
904+ const endLease = phases . start ( 'lease' )
883905 const lease = await leaseSandbox (
884906 kind ,
885907 {
@@ -890,8 +912,10 @@ async function executeInSandboxWithinBudget(
890912 selected ,
891913 signal ,
892914 req . meterUsage ,
893- req . session
915+ req . session ,
916+ phases
894917 )
918+ endLease ( )
895919 const created = lease . created
896920 const sandbox = created . sandbox
897921 const sandboxId = sandbox . sandboxId
@@ -906,6 +930,7 @@ async function executeInSandboxWithinBudget(
906930 // the finally below. Dependencies land before the inputs so user code and its
907931 // mounts always see a complete environment.
908932 //
933+ const endPrepare = phases . start ( 'prepare' )
909934 if ( lease . session && req . session )
910935 await recordSessionFileInput (
911936 req . session . key ,
@@ -926,6 +951,7 @@ async function executeInSandboxWithinBudget(
926951 signal ,
927952 Boolean ( lease . session )
928953 )
954+ endPrepare ( )
929955 const executionEnvironment = {
930956 ...selected ?. envs ,
931957 ...req . session ?. envs ,
@@ -944,13 +970,15 @@ async function executeInSandboxWithinBudget(
944970
945971 let execution : SandboxCodeResult
946972 try {
973+ const endRun = phases . start ( 'run' )
947974 execution = await sandbox . runCode ( code , {
948975 timeoutMs : codeBudgetMs ( req , signal ) ,
949976 javascriptPreload : buildJavaScriptRuntimeBindingsSource ( req . runtimeBindings ?? [ ] ) ,
950977 maxOutputBytes : MAX_SANDBOX_PROCESS_OUTPUT_BYTES ,
951978 signal,
952979 ...( hasExecutionEnvironment ? { envs : executionEnvironment } : { } ) ,
953980 } )
981+ endRun ( )
954982 } catch ( error ) {
955983 throwIfAborted ( signal )
956984 throw error
@@ -1009,11 +1037,13 @@ async function executeInSandboxWithinBudget(
10091037 ...sessionField ,
10101038 }
10111039 try {
1040+ const endCollect = phases . start ( 'collect' )
10121041 const { exportedFiles, exportedFileContent, collectedFiles } = await collectExportedFiles (
10131042 sandbox ,
10141043 req ,
10151044 { signal }
10161045 )
1046+ endCollect ( )
10171047 throwIfAborted ( signal )
10181048 billableResult . exportedFileContent = exportedFileContent
10191049 billableResult . exportedFiles = exportedFiles
@@ -1044,6 +1074,7 @@ async function executeInSandboxWithinBudget(
10441074 if ( cost && billableOutputError ) {
10451075 attachTrustedSandboxOutputCost ( billableOutputError , cost )
10461076 }
1077+ const endRelease = phases . start ( 'release' )
10471078 try {
10481079 await privateInputFiles ?. cleanup ( )
10491080 await lease . release ( )
@@ -1052,6 +1083,7 @@ async function executeInSandboxWithinBudget(
10521083 providerId : created . providerId ,
10531084 sandboxId,
10541085 } )
1086+ endRelease ( )
10551087 }
10561088 }
10571089}
@@ -1061,34 +1093,41 @@ export function executeInSandbox(req: SandboxExecutionRequest): Promise<SandboxE
10611093 req . timeoutMs ,
10621094 req . signal ,
10631095 Boolean ( req . session && ! req . meterUsage ) ,
1064- ( signal ) => executeInSandboxWithinBudget ( { ...req , signal } )
1096+ ( signal ) =>
1097+ withPhaseTimings ( ( phases ) => executeInSandboxWithinBudget ( { ...req , signal } , phases ) )
10651098 )
10661099}
10671100
10681101async function executeShellInSandboxWithinBudget (
1069- req : SandboxShellExecutionRequest & { signal : AbortSignal }
1102+ req : SandboxShellExecutionRequest & { signal : AbortSignal } ,
1103+ phases : SandboxPhaseTimer
10701104) : Promise < SandboxExecutionResult > {
10711105 const { code, envs, signal } = req
10721106 const kind = req . sandboxKind ?? 'shell'
10731107 throwIfAborted ( signal )
10741108
10751109 // No language is passed: a shell execution runs commands rather than a language
10761110 // runtime, so whichever language the sandbox carries is the one it installs.
1111+ const endResolve = phases . start ( 'resolve' )
10771112 const selected = await resolveWorkspaceSandbox ( {
10781113 kind,
10791114 workspaceId : req . workspaceId ,
10801115 sandboxId : req . sandboxId ,
10811116 } )
1117+ endResolve ( )
10821118 throwIfAborted ( signal )
10831119
1120+ const endLease = phases . start ( 'lease' )
10841121 const lease = await leaseSandbox (
10851122 kind ,
10861123 { imageRef : selected ?. imageRef , lifetimeMs : remainingSandboxBudgetMs ( signal ) } ,
10871124 selected ,
10881125 signal ,
10891126 req . meterUsage ,
1090- req . session
1127+ req . session ,
1128+ phases
10911129 )
1130+ endLease ( )
10921131 const created = lease . created
10931132 const sandbox = created . sandbox
10941133 const sandboxId = sandbox . sandboxId
@@ -1102,6 +1141,7 @@ async function executeShellInSandboxWithinBudget(
11021141 // Inside the try so a failed install or mount still releases the sandbox via
11031142 // the finally below. The install shares the caller's budget rather than adding
11041143 // to it — see the note in `executeInSandbox`.
1144+ const endPrepare = phases . start ( 'prepare' )
11051145 if ( lease . session && req . session )
11061146 await recordSessionFileInput (
11071147 req . session . key ,
@@ -1123,9 +1163,11 @@ async function executeShellInSandboxWithinBudget(
11231163 signal ,
11241164 Boolean ( lease . session )
11251165 )
1166+ endPrepare ( )
11261167
11271168 let result : SandboxCommandResult
11281169 try {
1170+ const endRun = phases . start ( 'run' )
11291171 result = await sandbox . runCommand ( code , {
11301172 envs : {
11311173 ...selected ?. envs ,
@@ -1141,6 +1183,7 @@ async function executeShellInSandboxWithinBudget(
11411183 rootUser : ! lease . session ,
11421184 atMostOnce : true ,
11431185 } )
1186+ endRun ( )
11441187 } catch ( error ) {
11451188 throwIfAborted ( signal )
11461189 throw error
@@ -1185,11 +1228,13 @@ async function executeShellInSandboxWithinBudget(
11851228 ...sessionField ,
11861229 }
11871230 try {
1231+ const endCollect = phases . start ( 'collect' )
11881232 const { exportedFiles, exportedFileContent, collectedFiles } = await collectExportedFiles (
11891233 sandbox ,
11901234 req ,
11911235 { signal }
11921236 )
1237+ endCollect ( )
11931238 throwIfAborted ( signal )
11941239 billableResult . exportedFileContent = exportedFileContent
11951240 billableResult . exportedFiles = exportedFiles
@@ -1220,6 +1265,7 @@ async function executeShellInSandboxWithinBudget(
12201265 if ( cost && billableOutputError ) {
12211266 attachTrustedSandboxOutputCost ( billableOutputError , cost )
12221267 }
1268+ const endRelease = phases . start ( 'release' )
12231269 try {
12241270 await privateInputFiles ?. cleanup ( )
12251271 await lease . release ( )
@@ -1228,6 +1274,7 @@ async function executeShellInSandboxWithinBudget(
12281274 providerId : created . providerId ,
12291275 sandboxId,
12301276 } )
1277+ endRelease ( )
12311278 }
12321279 }
12331280}
@@ -1239,7 +1286,8 @@ export function executeShellInSandbox(
12391286 req . timeoutMs ,
12401287 req . signal ,
12411288 Boolean ( req . session && ! req . meterUsage ) ,
1242- ( signal ) => executeShellInSandboxWithinBudget ( { ...req , signal } )
1289+ ( signal ) =>
1290+ withPhaseTimings ( ( phases ) => executeShellInSandboxWithinBudget ( { ...req , signal } , phases ) )
12431291 )
12441292}
12451293
0 commit comments