@@ -3,16 +3,27 @@ import { isTruthyEnvValue } from "../infra/env.js";
33import { createSubsystemLogger } from "../logging/subsystem.js" ;
44
55const restartTraceLog = createSubsystemLogger ( "gateway" ) ;
6+ const RESTART_TRACE_HANDOFF_STARTED_AT_ENV = "OPENCLAW_GATEWAY_RESTART_TRACE_STARTED_AT_MS" ;
7+ const RESTART_TRACE_HANDOFF_LAST_AT_ENV = "OPENCLAW_GATEWAY_RESTART_TRACE_LAST_AT_MS" ;
8+ const RESTART_TRACE_HANDOFF_MAX_AGE_MS = 10 * 60_000 ;
69
710type RestartTraceMetricValue = boolean | number | string | null | undefined ;
811type RestartTraceMetrics =
912 | Readonly < Record < string , RestartTraceMetricValue > >
1013 | ReadonlyArray < readonly [ string , RestartTraceMetricValue ] > ;
14+ export type GatewayRestartTraceHandoff = {
15+ startedAt : number ;
16+ lastAt : number ;
17+ } ;
1118
1219let startedAt = 0 ;
1320let lastAt = 0 ;
1421let active = false ;
1522
23+ function nowMs ( ) : number {
24+ return performance . timeOrigin + performance . now ( ) ;
25+ }
26+
1627function isRestartTraceEnabled ( ) : boolean {
1728 return isTruthyEnvValue ( process . env . OPENCLAW_GATEWAY_RESTART_TRACE ) ;
1829}
@@ -91,7 +102,7 @@ export function startGatewayRestartTrace(name: string, metrics?: RestartTraceMet
91102 active = false ;
92103 return ;
93104 }
94- const now = performance . now ( ) ;
105+ const now = nowMs ( ) ;
95106 startedAt = now ;
96107 lastAt = now ;
97108 active = true ;
@@ -106,7 +117,7 @@ export function markGatewayRestartTrace(name: string, metrics?: RestartTraceMetr
106117 if ( ! isGatewayRestartTraceActive ( ) ) {
107118 return ;
108119 }
109- const now = performance . now ( ) ;
120+ const now = nowMs ( ) ;
110121 emitRestartTrace ( name , now - lastAt , now - startedAt , metrics ) ;
111122 lastAt = now ;
112123}
@@ -124,11 +135,11 @@ export async function measureGatewayRestartTrace<T>(
124135 if ( ! isGatewayRestartTraceActive ( ) ) {
125136 return await run ( ) ;
126137 }
127- const before = performance . now ( ) ;
138+ const before = nowMs ( ) ;
128139 try {
129140 return await run ( ) ;
130141 } finally {
131- const now = performance . now ( ) ;
142+ const now = nowMs ( ) ;
132143 emitRestartTrace (
133144 name ,
134145 now - before ,
@@ -147,7 +158,7 @@ export function recordGatewayRestartTrace(
147158 if ( ! isGatewayRestartTraceActive ( ) || ! Number . isFinite ( durationMs ) ) {
148159 return ;
149160 }
150- const now = performance . now ( ) ;
161+ const now = nowMs ( ) ;
151162 emitRestartTrace ( name , Math . max ( 0 , durationMs ) , now - startedAt , metrics ) ;
152163 lastAt = now ;
153164}
@@ -183,6 +194,87 @@ export function collectGatewayProcessMemoryUsageMb(): ReadonlyArray<readonly [st
183194 ] ;
184195}
185196
197+ function normalizeRestartTraceHandoff ( value : unknown ) : GatewayRestartTraceHandoff | null {
198+ if ( typeof value !== "object" || value === null || Array . isArray ( value ) ) {
199+ return null ;
200+ }
201+ const record = value as { startedAt ?: unknown ; lastAt ?: unknown } ;
202+ if (
203+ typeof record . startedAt !== "number" ||
204+ ! Number . isFinite ( record . startedAt ) ||
205+ typeof record . lastAt !== "number" ||
206+ ! Number . isFinite ( record . lastAt ) ||
207+ record . startedAt <= 0 ||
208+ record . lastAt < record . startedAt ||
209+ record . lastAt - record . startedAt > RESTART_TRACE_HANDOFF_MAX_AGE_MS
210+ ) {
211+ return null ;
212+ }
213+ const now = nowMs ( ) ;
214+ if ( record . startedAt > now || now - record . startedAt > RESTART_TRACE_HANDOFF_MAX_AGE_MS ) {
215+ return null ;
216+ }
217+ return {
218+ startedAt : record . startedAt ,
219+ lastAt : record . lastAt ,
220+ } ;
221+ }
222+
223+ export function captureGatewayRestartTraceHandoff ( ) : GatewayRestartTraceHandoff | undefined {
224+ if ( ! isGatewayRestartTraceActive ( ) ) {
225+ return undefined ;
226+ }
227+ return { startedAt, lastAt } ;
228+ }
229+
230+ export function createGatewayRestartTraceHandoffEnv (
231+ handoff : GatewayRestartTraceHandoff | undefined = captureGatewayRestartTraceHandoff ( ) ,
232+ ) : NodeJS . ProcessEnv | undefined {
233+ const normalized = normalizeRestartTraceHandoff ( handoff ) ;
234+ if ( ! normalized ) {
235+ return undefined ;
236+ }
237+ return {
238+ [ RESTART_TRACE_HANDOFF_STARTED_AT_ENV ] : String ( normalized . startedAt ) ,
239+ [ RESTART_TRACE_HANDOFF_LAST_AT_ENV ] : String ( normalized . lastAt ) ,
240+ } ;
241+ }
242+
243+ export function resumeGatewayRestartTraceFromHandoff (
244+ handoff : unknown ,
245+ metrics ?: RestartTraceMetrics ,
246+ ) : boolean {
247+ if ( ! isRestartTraceEnabled ( ) || active ) {
248+ return false ;
249+ }
250+ const normalized = normalizeRestartTraceHandoff ( handoff ) ;
251+ if ( ! normalized ) {
252+ return false ;
253+ }
254+ startedAt = normalized . startedAt ;
255+ lastAt = normalized . lastAt ;
256+ active = true ;
257+ markGatewayRestartTrace ( "restart.process-resume" , metrics ) ;
258+ return true ;
259+ }
260+
261+ export function resumeGatewayRestartTraceFromEnv (
262+ env : NodeJS . ProcessEnv = process . env ,
263+ metrics ?: RestartTraceMetrics ,
264+ ) : boolean {
265+ const startedRaw = env [ RESTART_TRACE_HANDOFF_STARTED_AT_ENV ] ;
266+ const lastRaw = env [ RESTART_TRACE_HANDOFF_LAST_AT_ENV ] ;
267+ delete env [ RESTART_TRACE_HANDOFF_STARTED_AT_ENV ] ;
268+ delete env [ RESTART_TRACE_HANDOFF_LAST_AT_ENV ] ;
269+ return resumeGatewayRestartTraceFromHandoff (
270+ {
271+ startedAt : Number ( startedRaw ) ,
272+ lastAt : Number ( lastRaw ) ,
273+ } ,
274+ metrics ,
275+ ) ;
276+ }
277+
186278export function resetGatewayRestartTraceForTest ( ) : void {
187279 startedAt = 0 ;
188280 lastAt = 0 ;
0 commit comments