// node:perf_hooks over the worker's own User Timing API. Event-loop delay histograms // refuse: nothing samples the emulated loop. import { invalidArgType } from "./errors"; import { createHistogram } from "./histogram"; import type { Loop } from "./loop"; import { refused } from "./refuse"; const HISTOGRAM_WHY = "the emulated event loop is not sampled"; /** Node's eventLoopUtilization(util1, util2) delta form: both idle and active are * the earlier measurement's deltas subtracted from the later one's. */ function utilizationDelta(now: { idle: number; active: number }, prev?: EventLoopUtilization): EventLoopUtilization { const idle = prev ? now.idle - prev.idle : now.idle; const active = prev ? now.active - prev.active : now.active; const total = idle + active; return { idle, active, utilization: total === 0 ? 0 : active / total }; } interface EventLoopUtilization { idle: number; active: number; utilization: number; } export function makePerfHooks(loop: Loop) { /** Pure JS on the loop's own performance.now() bookkeeping (loop.ts tracks time * spent inside a guest callback vs. waiting): idle/active since the loop started, * or since `util1` when given one (Node's delta form). Node puts this both at * perf_hooks.eventLoopUtilization and performance.eventLoopUtilization(). */ function eventLoopUtilization(util1?: EventLoopUtilization, util2?: EventLoopUtilization): EventLoopUtilization { const now = loop.utilization(); if (util1 === undefined) return { ...now, utilization: now.idle + now.active === 0 ? 0 : now.active / (now.idle + now.active) }; return utilizationDelta(util2 ?? now, util1); } // The wrapping and the call both work; what's missing is the side effect: Node // records a PerformanceEntry of entryType "function" on every call, a type the // Web Performance API has no script-facing way to create ("mark" and "measure" // are the only ones), and a histogram option (RecordableHistogram) needs // createHistogram, which refuses below. So timerify still times nothing — it only // forwards the call. Node puts this both at perf_hooks.timerify and // performance.timerify(). function timerify any>(fn: F, _options?: { histogram?: unknown }): F { if (typeof fn !== "function") throw invalidArgType("fn", "of type function", fn); const wrapped = function (this: unknown, ...args: unknown[]) { return fn.apply(this, args); }; Object.defineProperty(wrapped, "name", { value: `timerified ${fn.name}`, configurable: true }); Object.defineProperty(wrapped, "length", { value: fn.length, configurable: true }); return wrapped as F; } // The browser's own Performance instance, plus Node's additions it doesn't have. const performance = Object.assign(globalThis.performance, { eventLoopUtilization, timerify }); return { performance, // Re-exports: these are the Web Performance API's own classes, not a Node // reimplementation (DESIGN's browser-first precedence table). Performance: globalThis.Performance, PerformanceObserver: globalThis.PerformanceObserver, PerformanceObserverEntryList: globalThis.PerformanceObserverEntryList, PerformanceEntry: globalThis.PerformanceEntry, PerformanceMark: globalThis.PerformanceMark, PerformanceMeasure: globalThis.PerformanceMeasure, PerformanceResourceTiming: globalThis.PerformanceResourceTiming, constants: { NODE_PERFORMANCE_GC_MAJOR: 4, NODE_PERFORMANCE_GC_MINOR: 1, NODE_PERFORMANCE_GC_INCREMENTAL: 8, NODE_PERFORMANCE_GC_WEAKCB: 16, NODE_PERFORMANCE_GC_FLAGS_NO: 0, NODE_PERFORMANCE_GC_FLAGS_CONSTRUCT_RETAINED: 2, NODE_PERFORMANCE_GC_FLAGS_FORCED: 4, NODE_PERFORMANCE_GC_FLAGS_SYNCHRONOUS_PHANTOM_PROCESSING: 8, NODE_PERFORMANCE_GC_FLAGS_ALL_AVAILABLE_GARBAGE: 16, NODE_PERFORMANCE_GC_FLAGS_ALL_EXTERNAL_MEMORY: 32, NODE_PERFORMANCE_GC_FLAGS_SCHEDULE_IDLE: 64, }, eventLoopUtilization, // A histogram is pure JS (createHistogram, below) — what's missing is a sampler // for the emulated event loop to feed one. monitorEventLoopDelay: refused("perf_hooks", "monitorEventLoopDelay", HISTOGRAM_WHY), createHistogram, timerify, }; }