



























@@ -3,16 +3,27 @@ import { isTruthyEnvValue } from "../infra/env.js";
33import { createSubsystemLogger } from "../logging/subsystem.js";
4455const 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;
69710type 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+};
11181219let startedAt = 0;
1320let lastAt = 0;
1421let active = false;
152223+function nowMs(): number {
24+return performance.timeOrigin + performance.now();
25+}
26+1627function isRestartTraceEnabled(): boolean {
1728return isTruthyEnvValue(process.env.OPENCLAW_GATEWAY_RESTART_TRACE);
1829}
@@ -91,7 +102,7 @@ export function startGatewayRestartTrace(name: string, metrics?: RestartTraceMet
91102active = false;
92103return;
93104}
94-const now = performance.now();
105+const now = nowMs();
95106startedAt = now;
96107lastAt = now;
97108active = true;
@@ -106,7 +117,7 @@ export function markGatewayRestartTrace(name: string, metrics?: RestartTraceMetr
106117if (!isGatewayRestartTraceActive()) {
107118return;
108119}
109-const now = performance.now();
120+const now = nowMs();
110121emitRestartTrace(name, now - lastAt, now - startedAt, metrics);
111122lastAt = now;
112123}
@@ -124,11 +135,11 @@ export async function measureGatewayRestartTrace<T>(
124135if (!isGatewayRestartTraceActive()) {
125136return await run();
126137}
127-const before = performance.now();
138+const before = nowMs();
128139try {
129140return await run();
130141} finally {
131-const now = performance.now();
142+const now = nowMs();
132143emitRestartTrace(
133144name,
134145now - before,
@@ -147,7 +158,7 @@ export function recordGatewayRestartTrace(
147158if (!isGatewayRestartTraceActive() || !Number.isFinite(durationMs)) {
148159return;
149160}
150-const now = performance.now();
161+const now = nowMs();
151162emitRestartTrace(name, Math.max(0, durationMs), now - startedAt, metrics);
152163lastAt = now;
153164}
@@ -183,6 +194,87 @@ export function collectGatewayProcessMemoryUsageMb(): ReadonlyArray<readonly [st
183194];
184195}
185196197+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 {
187279startedAt = 0;
188280lastAt = 0;
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。