Summary
Node performance.timeOrigin is fixed near process start, before user code first reads it. Perry initializes TIME_ORIGIN_MS lazily on the first time_origin_ms() call, so code that waits before touching performance.timeOrigin would anchor the origin too late.
Node 25.9.0 behavior
const start = Date.now();
setTimeout(() => {
const { performance } = require("node:perf_hooks");
console.log(Date.now() - start); // about 80ms in this probe
console.log(Date.now() - performance.timeOrigin); // already includes startup + wait time
console.log(performance.now()); // similar process-relative elapsed time
}, 80);
Local Node v25.9.0 reported about 83ms elapsed before the first read, while Date.now() - performance.timeOrigin and performance.now() were already about 117ms. The origin was not captured lazily at the first performance access.
Perry source evidence
crates/perry-runtime/src/perf_hooks.rs defines static TIME_ORIGIN_MS: OnceLock<f64>.
time_origin_ms() calls TIME_ORIGIN_MS.get_or_init(|| SystemTime::now().duration_since(UNIX_EPOCH)...), so the origin is the wall-clock time of the first time_origin_ms() call.
performance.timeOrigin, performance.toJSON(), performance.nodeTiming, and event loop utilization all read this lazy value.
Expected
performance.timeOrigin should be initialized from a process-start timestamp, not from the first property/method access. This should remain true even if node:perf_hooks is imported or globalThis.performance is accessed later in the program.
Duplicate checks
performance.timeOrigin process start lazy
perf_hooks timeOrigin first read
I did not find an existing issue for lazy timeOrigin initialization. Related #3012 covers performance.now() being epoch-based, not the origin capture point itself.
Summary
Node
performance.timeOriginis fixed near process start, before user code first reads it. Perry initializesTIME_ORIGIN_MSlazily on the firsttime_origin_ms()call, so code that waits before touchingperformance.timeOriginwould anchor the origin too late.Node 25.9.0 behavior
Local Node v25.9.0 reported about 83ms elapsed before the first read, while
Date.now() - performance.timeOriginandperformance.now()were already about 117ms. The origin was not captured lazily at the firstperformanceaccess.Perry source evidence
crates/perry-runtime/src/perf_hooks.rsdefinesstatic TIME_ORIGIN_MS: OnceLock<f64>.time_origin_ms()callsTIME_ORIGIN_MS.get_or_init(|| SystemTime::now().duration_since(UNIX_EPOCH)...), so the origin is the wall-clock time of the firsttime_origin_ms()call.performance.timeOrigin,performance.toJSON(),performance.nodeTiming, and event loop utilization all read this lazy value.Expected
performance.timeOriginshould be initialized from a process-start timestamp, not from the first property/method access. This should remain true even ifnode:perf_hooksis imported orglobalThis.performanceis accessed later in the program.Duplicate checks
performance.timeOrigin process start lazyperf_hooks timeOrigin first readI did not find an existing issue for lazy
timeOrigininitialization. Related #3012 coversperformance.now()being epoch-based, not the origin capture point itself.