@@ -4,7 +4,7 @@ import process from "node:process";
44import { fileURLToPath } from "node:url" ;
55import { resolveStateDir } from "../config/paths.js" ;
66import type { OpenClawConfig } from "../config/types.openclaw.js" ;
7- import { normalizeEnv } from "../infra/env.js" ;
7+ import { isTruthyEnvValue , normalizeEnv } from "../infra/env.js" ;
88import { isMainModule } from "../infra/is-main.js" ;
99import { ensureOpenClawCliOnPath } from "../infra/path-env.js" ;
1010import { assertSupportedRuntime } from "../infra/runtime-guard.js" ;
@@ -37,6 +37,41 @@ export {
3737 shouldUseRootHelpFastPath ,
3838} from "./run-main-policy.js" ;
3939
40+ type Awaitable < T > = T | Promise < T > ;
41+
42+ function createGatewayCliMainStartupTrace ( argv : string [ ] ) {
43+ const enabled =
44+ isTruthyEnvValue ( process . env . OPENCLAW_GATEWAY_STARTUP_TRACE ) &&
45+ argv . slice ( 2 ) . includes ( "gateway" ) ;
46+ const started = performance . now ( ) ;
47+ let last = started ;
48+ const emit = ( name : string , durationMs : number , totalMs : number ) => {
49+ if ( ! enabled ) {
50+ return ;
51+ }
52+ process . stderr . write (
53+ `[gateway] startup trace: cli.main.${ name } ${ durationMs . toFixed ( 1 ) } ms total=${ totalMs . toFixed ( 1 ) } ms\n` ,
54+ ) ;
55+ } ;
56+ return {
57+ mark ( name : string ) {
58+ const now = performance . now ( ) ;
59+ emit ( name , now - last , now - started ) ;
60+ last = now ;
61+ } ,
62+ async measure < T > ( name : string , run : ( ) => Awaitable < T > ) : Promise < T > {
63+ const before = performance . now ( ) ;
64+ try {
65+ return await run ( ) ;
66+ } finally {
67+ const now = performance . now ( ) ;
68+ emit ( name , now - before , now - started ) ;
69+ last = now ;
70+ }
71+ } ,
72+ } ;
73+ }
74+
4075async function closeCliMemoryManagers ( ) : Promise < void > {
4176 const { hasMemoryRuntime } = await import ( "../plugins/memory-state.js" ) ;
4277 if ( ! hasMemoryRuntime ( ) ) {
@@ -98,6 +133,7 @@ async function ensureCliEnvProxyDispatcher(): Promise<void> {
98133
99134export async function runCli ( argv : string [ ] = process . argv ) {
100135 const originalArgv = normalizeWindowsArgv ( argv ) ;
136+ const startupTrace = createGatewayCliMainStartupTrace ( originalArgv ) ;
101137 const parsedContainer = parseCliContainerArgs ( originalArgv ) ;
102138 if ( ! parsedContainer . ok ) {
103139 throw new Error ( parsedContainer . error ) ;
@@ -123,10 +159,13 @@ export async function runCli(argv: string[] = process.argv) {
123159 return ;
124160 }
125161 let normalizedArgv = parsedProfile . argv ;
162+ startupTrace . mark ( "argv" ) ;
126163
127164 if ( shouldLoadCliDotEnv ( ) ) {
128- const { loadCliDotEnv } = await import ( "./dotenv.js" ) ;
129- loadCliDotEnv ( { quiet : true } ) ;
165+ await startupTrace . measure ( "dotenv" , async ( ) => {
166+ const { loadCliDotEnv } = await import ( "./dotenv.js" ) ;
167+ loadCliDotEnv ( { quiet : true } ) ;
168+ } ) ;
130169 }
131170 normalizeEnv ( ) ;
132171 if ( shouldEnsureCliPath ( normalizedArgv ) ) {
@@ -206,19 +245,18 @@ export async function runCli(argv: string[] = process.argv) {
206245 const [
207246 { initializeDebugProxyCapture, finalizeDebugProxyCapture } ,
208247 { maybeWarnAboutDebugProxyCoverage } ,
209- ] = await Promise . all ( [
210- import ( "../proxy-capture/runtime.js" ) ,
211- import ( "../proxy-capture/coverage.js" ) ,
212- ] ) ;
248+ ] = await startupTrace . measure ( "proxy-imports" , ( ) =>
249+ Promise . all ( [ import ( "../proxy-capture/runtime.js" ) , import ( "../proxy-capture/coverage.js" ) ] ) ,
250+ ) ;
213251 initializeDebugProxyCapture ( "cli" ) ;
214252 process . once ( "exit" , ( ) => {
215253 finalizeDebugProxyCapture ( ) ;
216254 } ) ;
217- await ensureCliEnvProxyDispatcher ( ) ;
255+ await startupTrace . measure ( "proxy-dispatcher" , ( ) => ensureCliEnvProxyDispatcher ( ) ) ;
218256 maybeWarnAboutDebugProxyCoverage ( ) ;
219257
220- const { tryRouteCli } = await import ( "./route.js" ) ;
221- if ( await tryRouteCli ( normalizedArgv ) ) {
258+ const { tryRouteCli } = await startupTrace . measure ( "route- import" , ( ) => import ( "./route.js" ) ) ;
259+ if ( await startupTrace . measure ( "route" , ( ) => tryRouteCli ( normalizedArgv ) ) ) {
222260 return ;
223261 }
224262
@@ -253,14 +291,16 @@ export async function runCli(argv: string[] = process.argv) {
253291 isUncaughtExceptionHandled,
254292 } ,
255293 { restoreTerminalState } ,
256- ] = await Promise . all ( [
257- import ( "./program.js" ) ,
258- import ( "../infra/errors.js" ) ,
259- import ( "../infra/fatal-error-hooks.js" ) ,
260- import ( "../infra/unhandled-rejections.js" ) ,
261- import ( "../terminal/restore.js" ) ,
262- ] ) ;
263- const program = buildProgram ( ) ;
294+ ] = await startupTrace . measure ( "core-imports" , ( ) =>
295+ Promise . all ( [
296+ import ( "./program.js" ) ,
297+ import ( "../infra/errors.js" ) ,
298+ import ( "../infra/fatal-error-hooks.js" ) ,
299+ import ( "../infra/unhandled-rejections.js" ) ,
300+ import ( "../terminal/restore.js" ) ,
301+ ] ) ,
302+ ) ;
303+ const program = await startupTrace . measure ( "build-program" , ( ) => buildProgram ( ) ) ;
264304
265305 // Global error handlers to prevent silent crashes from unhandled rejections/exceptions.
266306 // These log the error and exit gracefully instead of crashing without trace.
@@ -291,14 +331,16 @@ export async function runCli(argv: string[] = process.argv) {
291331 // are correct even with lazy command registration.
292332 const { primary } = invocation ;
293333 if ( primary && shouldRegisterPrimaryCommandOnly ( parseArgv ) ) {
294- const { getProgramContext } = await import ( "./program/program-context.js" ) ;
295- const ctx = getProgramContext ( program ) ;
296- if ( ctx ) {
297- const { registerCoreCliByName } = await import ( "./program/command-registry.js" ) ;
298- await registerCoreCliByName ( program , ctx , primary , parseArgv ) ;
299- }
300- const { registerSubCliByName } = await import ( "./program/register.subclis.js" ) ;
301- await registerSubCliByName ( program , primary ) ;
334+ await startupTrace . measure ( "register-primary" , async ( ) => {
335+ const { getProgramContext } = await import ( "./program/program-context.js" ) ;
336+ const ctx = getProgramContext ( program ) ;
337+ if ( ctx ) {
338+ const { registerCoreCliByName } = await import ( "./program/command-registry.js" ) ;
339+ await registerCoreCliByName ( program , ctx , primary , parseArgv ) ;
340+ }
341+ const { registerSubCliByName } = await import ( "./program/register.subclis.js" ) ;
342+ await registerSubCliByName ( program , primary ) ;
343+ } ) ;
302344 }
303345
304346 const hasBuiltinPrimary =
@@ -312,17 +354,14 @@ export async function runCli(argv: string[] = process.argv) {
312354 hasBuiltinPrimary,
313355 } ) ;
314356 if ( ! shouldSkipPluginRegistration ) {
315- // Register plugin CLI commands before parsing
316- const { registerPluginCliCommandsFromValidatedConfig } = await import ( "../plugins/cli.js" ) ;
317- const config = await registerPluginCliCommandsFromValidatedConfig (
318- program ,
319- undefined ,
320- undefined ,
321- {
357+ const config = await startupTrace . measure ( "register-plugin-commands" , async ( ) => {
358+ const { registerPluginCliCommandsFromValidatedConfig } =
359+ await import ( "../plugins/cli.js" ) ;
360+ return await registerPluginCliCommandsFromValidatedConfig ( program , undefined , undefined , {
322361 mode : "lazy" ,
323362 primary,
324- } ,
325- ) ;
363+ } ) ;
364+ } ) ;
326365 if ( config ) {
327366 if (
328367 primary &&
@@ -349,7 +388,7 @@ export async function runCli(argv: string[] = process.argv) {
349388 stopStartupProgress ( ) ;
350389
351390 try {
352- await program . parseAsync ( parseArgv ) ;
391+ await startupTrace . measure ( "parse" , ( ) => program . parseAsync ( parseArgv ) ) ;
353392 } catch ( error ) {
354393 if ( ! isCommanderParseExit ( error ) ) {
355394 throw error ;
0 commit comments