diff --git a/scripts/node_suite_run.py b/scripts/node_suite_run.py index a95778c343..9976385c64 100755 --- a/scripts/node_suite_run.py +++ b/scripts/node_suite_run.py @@ -71,7 +71,7 @@ def normalize(text: str) -> str: SLOW_MODULES = { "http", "http2", "https", "net", "dgram", "tls", "cluster", "dns", "stream", "child_process", "worker_threads", "inspector", - "inspector-promises", "repl", "diagnostics_channel", "timers", "fetch", + "inspector-promises", "repl", "diagnostics_channel", "timers", "perf_hooks", "fetch", } tests = [] diff --git a/test-parity/node-suite/perf_hooks/README.md b/test-parity/node-suite/perf_hooks/README.md new file mode 100644 index 0000000000..40f56628bb --- /dev/null +++ b/test-parity/node-suite/perf_hooks/README.md @@ -0,0 +1,74 @@ +# `node:perf_hooks` parity coverage + +This suite uses Node.js **v26.5.0** as its exact behavioral oracle. The audit is +based on that tag's `lib/internal/perf/*`, `lib/internal/histogram.js`, +`test/parallel/test-perf-hooks-*.js`, and `test/sequential/test-perf-hooks.js`. +The comparison sources are Deno **v2.9.2** (`ext/node/polyfills/perf_hooks*`, +`ext/node/ops/perf_hooks.rs`, and `tests/unit_node/perf_hooks_test.ts`) and Bun +**v1.3.14** (`src/js/node/perf_hooks.ts`, the internal event-loop-delay module, +and the primary histogram/perf-hooks tests). Node remains authoritative where +those runtimes deliberately expose a smaller or Web-oriented surface. + +## Deterministic contract map + +The 148 granular fixtures cover: + +- module exports, aliases, global identity, public descriptors, prototypes, + tags, constructors, receivers, and exact supported observer types; +- `now()`, `timeOrigin`, `nodeTiming`, `toJSON()`, and ELU + shape/order/arithmetic invariants without snapshotting host timestamps; +- marks, measures, clears, timeline sorting, repeated-name resolution, detail + cloning, validation, and snapshot/reference identity; +- observer subscription modes, buffering, queue draining, disconnect/reobserve, + replacement, multiple observers, callback/list identity, EntryList filtering, + branding, sorting, and explicit `setImmediate` delivery barriers; +- timerify call/construct behavior, `this`, arguments, result/error identity, + wrapper metadata, nesting, async settlement, observer entries, and optional + histograms; +- recordable histogram empty/populated state, Number/BigInt twins, validation, + percentiles, reset, add/isolation, `recordDelta()`, serialization, and illegal + construction; +- event-loop-delay handle identity, inherited histogram surface, option + validation, enable/disable transitions, reset, and cleanup; +- controlled synthetic resource timing accessors, transfer-size cache modes, + serialization, timeline/observer delivery, validation, and cleanup; +- all stable Node GC constants and Node bootstrap milestone relationships. + +The module is in the sequential node-suite lane because observer dispatch, +immediate barriers, timerify settlement, and event-loop histogram lifecycle must +not be perturbed by six concurrent parity jobs. + +## Isolation and stopping boundary + +Every created mark, measure, resource record, observer, or delay histogram is +cleared, disconnected, or disabled by the fixture that owns it. Time values are +reported only through types, ordering, ranges, or controlled synthetic inputs. +No fixture requires an exact timestamp, duration, percentile, delay, +utilization, or scheduler threshold. + +The following remain intentionally outside this deterministic lane: + +- GC entries/forced GC/finalization; +- exact event-loop delay or utilization measurements and CPU contention; +- real DNS/HTTP/network resource timings; +- workers, inspector/trace integration, bootstrap timing snapshots; +- races, stress, leak, exhaustion, and large-sample percentile tests. + +Node 26.5.0 event-loop-delay histograms do **not** expose `ref()`, `unref()`, or +`hasRef()`; their absence is documented rather than inventing an unsupported +contract. Buffer-full event timing and `recordDelta()` magnitude are not used as +pass conditions because they depend on loop scheduling. Resource timing uses +fully synthetic Fetch Timing Info rather than real I/O. + +## Measured provenance + +The final 148-fixture tree was executed twice with byte-identical output under +Node 26.5.0: **148/148** in both runs. Two sequential Perry classifications with +the release compiler were also identical: **87 pass, 60 output differences, one +compile failure, zero timeouts**. The compile failure is the isolated named +`eventLoopUtilization` export contract; Perry does not currently export it. + +The same fixtures produced **101 pass, 33 differences, 14 runtime errors, zero +timeouts** on Deno 2.9.2, and **97 pass, 24 differences, 27 runtime errors, zero +timeouts** on Bun 1.3.14. Those results describe intentional and incomplete +compatibility surfaces; they do not replace Node as the oracle. diff --git a/test-parity/node-suite/perf_hooks/aaa-time-origin/not-lazy.ts b/test-parity/node-suite/perf_hooks/aaa-time-origin/not-lazy.ts index 18b7c69e62..31792ea93d 100644 --- a/test-parity/node-suite/perf_hooks/aaa-time-origin/not-lazy.ts +++ b/test-parity/node-suite/perf_hooks/aaa-time-origin/not-lazy.ts @@ -1,10 +1,14 @@ -const start = Date.now(); +import { performance } from "node:perf_hooks"; -setTimeout(() => { - const elapsed = Date.now() - start; - const originAge = Date.now() - performance.timeOrigin; - - console.log("elapsed observed:", elapsed >= 15); - console.log("origin includes wait:", originAge + 5 >= elapsed); - console.log("now includes wait:", performance.now() + 5 >= elapsed); -}, 25); +const before = performance.now(); +await new Promise((resolve) => setImmediate(resolve)); +const after = performance.now(); +console.log( + "timeOrigin stable:", + performance.timeOrigin === performance.timeOrigin, +); +console.log("now advances:", after >= before); +console.log( + "origin relation:", + performance.timeOrigin + after >= performance.timeOrigin, +); diff --git a/test-parity/node-suite/perf_hooks/clear/measure-symbol-throws.ts b/test-parity/node-suite/perf_hooks/clear/measure-symbol-throws.ts new file mode 100644 index 0000000000..e66670064b --- /dev/null +++ b/test-parity/node-suite/perf_hooks/clear/measure-symbol-throws.ts @@ -0,0 +1,7 @@ +import { performance } from "node:perf_hooks"; +try { + performance.clearMeasures(Symbol("x") as any); + console.log("no throw"); +} catch (error) { + console.log((error as Error).name); +} diff --git a/test-parity/node-suite/perf_hooks/clear/repeated-name.ts b/test-parity/node-suite/perf_hooks/clear/repeated-name.ts new file mode 100644 index 0000000000..d145761a5c --- /dev/null +++ b/test-parity/node-suite/perf_hooks/clear/repeated-name.ts @@ -0,0 +1,9 @@ +import { performance } from "node:perf_hooks"; +performance.clearMarks(); +performance.mark("same", { startTime: 1 }); +performance.mark("same", { startTime: 2 }); +performance.mark("other", { startTime: 3 }); +performance.clearMarks("same"); +console.log("same:", performance.getEntriesByName("same", "mark").length); +console.log("other:", performance.getEntriesByName("other", "mark").length); +performance.clearMarks(); diff --git a/test-parity/node-suite/perf_hooks/constants/all-gc-values.ts b/test-parity/node-suite/perf_hooks/constants/all-gc-values.ts new file mode 100644 index 0000000000..d9e7a8fb6e --- /dev/null +++ b/test-parity/node-suite/perf_hooks/constants/all-gc-values.ts @@ -0,0 +1,7 @@ +import { constants } from "node:perf_hooks"; +console.log(Object.keys(constants).sort().join(",")); +console.log( + Object.entries(constants).sort(([a], [b]) => a.localeCompare(b)).map(( + [key, value], + ) => `${key}=${value}`).join(","), +); diff --git a/test-parity/node-suite/perf_hooks/entries/argument-validation.ts b/test-parity/node-suite/perf_hooks/entries/argument-validation.ts new file mode 100644 index 0000000000..f22bf88f28 --- /dev/null +++ b/test-parity/node-suite/perf_hooks/entries/argument-validation.ts @@ -0,0 +1,22 @@ +import { performance } from "node:perf_hooks"; +function outcome(label: string, fn: () => unknown) { + try { + fn(); + console.log(label, "no throw"); + } catch (error) { + console.log(label, (error as any).name, (error as any).code); + } +} +outcome("byName missing", () => (performance.getEntriesByName as any)()); +outcome("byType missing", () => (performance.getEntriesByType as any)()); +outcome( + "byName symbol", + () => performance.getEntriesByName(Symbol("x") as any), +); +outcome( + "byType symbol", + () => performance.getEntriesByType(Symbol("x") as any), +); +performance.mark("1", { startTime: 1 }); +console.log("name coerced:", performance.getEntriesByName(1 as any).length); +performance.clearMarks(); diff --git a/test-parity/node-suite/perf_hooks/entries/snapshot-identity.ts b/test-parity/node-suite/perf_hooks/entries/snapshot-identity.ts new file mode 100644 index 0000000000..cfd5ace08d --- /dev/null +++ b/test-parity/node-suite/perf_hooks/entries/snapshot-identity.ts @@ -0,0 +1,16 @@ +import { performance } from "node:perf_hooks"; +performance.clearMarks(); +try { + const entry = performance.mark("identity", { startTime: 1 }); + const first = performance.getEntriesByType("mark"); + const second = performance.getEntriesByType("mark"); + first.length = 0; + console.log("fresh arrays:", first !== second); + console.log("same entries:", second[0] === entry); + console.log( + "mutation isolated:", + performance.getEntriesByType("mark").length, + ); +} finally { + performance.clearMarks(); +} diff --git a/test-parity/node-suite/perf_hooks/entries/timeline-sorting.ts b/test-parity/node-suite/perf_hooks/entries/timeline-sorting.ts new file mode 100644 index 0000000000..fdf13a071b --- /dev/null +++ b/test-parity/node-suite/perf_hooks/entries/timeline-sorting.ts @@ -0,0 +1,16 @@ +import { performance } from "node:perf_hooks"; +performance.clearMarks(); +performance.clearMeasures(); +try { + performance.mark("late", { startTime: 20 }); + performance.mark("early", { startTime: 5 }); + performance.measure("span", { start: 5, duration: 3 }); + console.log( + performance.getEntries().map((e) => + `${e.name}:${e.entryType}:${e.startTime}` + ).join(","), + ); +} finally { + performance.clearMarks(); + performance.clearMeasures(); +} diff --git a/test-parity/node-suite/perf_hooks/eventlooputil/two-arg-synthetic.ts b/test-parity/node-suite/perf_hooks/eventlooputil/two-arg-synthetic.ts index 3d198fe289..dd02e081c6 100644 --- a/test-parity/node-suite/perf_hooks/eventlooputil/two-arg-synthetic.ts +++ b/test-parity/node-suite/perf_hooks/eventlooputil/two-arg-synthetic.ts @@ -1,15 +1,8 @@ import { performance } from "node:perf_hooks"; -const start = performance.eventLoopUtilization(); - -setTimeout(() => { - const end = performance.eventLoopUtilization(); - const twoArg = performance.eventLoopUtilization(end, start); - const oneArg = performance.eventLoopUtilization(end); - const twoSpan = twoArg.idle + twoArg.active; - const oneSpan = oneArg.idle + oneArg.active; - - console.log("two arg type:", typeof twoArg.utilization); - console.log("two arg wider than one arg:", twoSpan > oneSpan + 5); - console.log("two arg in range:", twoArg.utilization >= 0 && twoArg.utilization <= 1); -}, 35); +const newer = { idle: 10, active: 20, utilization: 2 / 3 }; +const older = { idle: 3, active: 5, utilization: 5 / 8 }; +const delta = performance.eventLoopUtilization(newer, older); +console.log("idle:", delta.idle); +console.log("active:", delta.active); +console.log("utilization:", delta.utilization === 15 / 22); diff --git a/test-parity/node-suite/perf_hooks/global/illegal-constructors.ts b/test-parity/node-suite/perf_hooks/global/illegal-constructors.ts new file mode 100644 index 0000000000..83e8e6fed2 --- /dev/null +++ b/test-parity/node-suite/perf_hooks/global/illegal-constructors.ts @@ -0,0 +1,23 @@ +import { + Performance, + PerformanceEntry, + PerformanceMeasure, + PerformanceObserverEntryList, + PerformanceResourceTiming, +} from "node:perf_hooks"; +for ( + const [name, Ctor] of Object.entries({ + Performance, + PerformanceEntry, + PerformanceMeasure, + PerformanceObserverEntryList, + PerformanceResourceTiming, + }) +) { + try { + new (Ctor as any)(); + console.log(`${name}: no throw`); + } catch (error) { + console.log(`${name}:`, (error as any).name, (error as any).code); + } +} diff --git a/test-parity/node-suite/perf_hooks/histogram/add-isolation.ts b/test-parity/node-suite/perf_hooks/histogram/add-isolation.ts new file mode 100644 index 0000000000..a40dc75366 --- /dev/null +++ b/test-parity/node-suite/perf_hooks/histogram/add-isolation.ts @@ -0,0 +1,9 @@ +import { createHistogram } from "node:perf_hooks"; +const source = createHistogram(); +const target = createHistogram(); +source.record(4); +source.record(8); +console.log("add return:", target.add(source) === undefined); +console.log("copied:", target.count, target.min, target.max); +source.record(16); +console.log("isolated:", target.count, target.max); diff --git a/test-parity/node-suite/perf_hooks/histogram/add-validation.ts b/test-parity/node-suite/perf_hooks/histogram/add-validation.ts new file mode 100644 index 0000000000..ed2e6a7822 --- /dev/null +++ b/test-parity/node-suite/perf_hooks/histogram/add-validation.ts @@ -0,0 +1,10 @@ +import { createHistogram } from "node:perf_hooks"; +const h = createHistogram(); +for (const value of [undefined, null, 1, "x", {}, h.toJSON()]) { + try { + h.add(value as any); + console.log(typeof value, "no throw"); + } catch (error) { + console.log(typeof value, (error as Error).name, (error as any).code); + } +} diff --git a/test-parity/node-suite/perf_hooks/histogram/empty-state.ts b/test-parity/node-suite/perf_hooks/histogram/empty-state.ts new file mode 100644 index 0000000000..965491a8dd --- /dev/null +++ b/test-parity/node-suite/perf_hooks/histogram/empty-state.ts @@ -0,0 +1,10 @@ +import { createHistogram } from "node:perf_hooks"; +const h = createHistogram(); +console.log("counts:", h.count, h.countBigInt === 0n); +console.log( + "bounds:", + h.minBigInt === 9223372036854775807n, + h.maxBigInt === 0n, +); +console.log("exceeds:", h.exceeds, h.exceedsBigInt === 0n); +console.log("nan stats:", Number.isNaN(h.mean), Number.isNaN(h.stddev)); diff --git a/test-parity/node-suite/perf_hooks/histogram/illegal-constructor.ts b/test-parity/node-suite/perf_hooks/histogram/illegal-constructor.ts new file mode 100644 index 0000000000..1fd37093b2 --- /dev/null +++ b/test-parity/node-suite/perf_hooks/histogram/illegal-constructor.ts @@ -0,0 +1,9 @@ +import { createHistogram } from "node:perf_hooks"; +const h = createHistogram(); +console.log("constructor:", h.constructor.name); +try { + new (h.constructor as any)(); + console.log("no throw"); +} catch (error) { + console.log((error as Error).name, (error as any).code); +} diff --git a/test-parity/node-suite/perf_hooks/histogram/monitor-disabled-reset.ts b/test-parity/node-suite/perf_hooks/histogram/monitor-disabled-reset.ts new file mode 100644 index 0000000000..992ba650fc --- /dev/null +++ b/test-parity/node-suite/perf_hooks/histogram/monitor-disabled-reset.ts @@ -0,0 +1,15 @@ +import { monitorEventLoopDelay } from "node:perf_hooks"; +const h = monitorEventLoopDelay(); +try { + console.log("surface:", typeof h.reset, typeof h.enable, typeof h.disable); + console.log("initial:", h.count, h.countBigInt === 0n); + console.log("reset:", h.reset() === undefined, h.count); + console.log( + "handle methods:", + typeof (h as any).ref, + typeof (h as any).unref, + typeof (h as any).hasRef, + ); +} finally { + h.disable(); +} diff --git a/test-parity/node-suite/perf_hooks/histogram/monitor-distinct-handles.ts b/test-parity/node-suite/perf_hooks/histogram/monitor-distinct-handles.ts new file mode 100644 index 0000000000..f45f0a1cc4 --- /dev/null +++ b/test-parity/node-suite/perf_hooks/histogram/monitor-distinct-handles.ts @@ -0,0 +1,10 @@ +import { monitorEventLoopDelay } from "node:perf_hooks"; +const first = monitorEventLoopDelay(); +const second = monitorEventLoopDelay(); +try { + console.log("distinct:", first !== second); + console.log("constructors:", first.constructor.name, second.constructor.name); +} finally { + first.disable(); + second.disable(); +} diff --git a/test-parity/node-suite/perf_hooks/histogram/monitor-lifecycle.ts b/test-parity/node-suite/perf_hooks/histogram/monitor-lifecycle.ts new file mode 100644 index 0000000000..4a43751d0f --- /dev/null +++ b/test-parity/node-suite/perf_hooks/histogram/monitor-lifecycle.ts @@ -0,0 +1,9 @@ +import { monitorEventLoopDelay } from "node:perf_hooks"; +const h = monitorEventLoopDelay(); +try { + console.log("enable:", h.enable(), h.enable()); + console.log("disable:", h.disable(), h.disable()); + console.log("reenable:", h.enable()); +} finally { + h.disable(); +} diff --git a/test-parity/node-suite/perf_hooks/histogram/monitor-options-validation.ts b/test-parity/node-suite/perf_hooks/histogram/monitor-options-validation.ts new file mode 100644 index 0000000000..f005dffae7 --- /dev/null +++ b/test-parity/node-suite/perf_hooks/histogram/monitor-options-validation.ts @@ -0,0 +1,19 @@ +import { monitorEventLoopDelay } from "node:perf_hooks"; +function outcome(label: string, options: any) { + let h: any; + try { + h = monitorEventLoopDelay(options); + console.log(label, "ok"); + } catch (error) { + console.log(label, (error as Error).name, (error as any).code); + } finally { + h?.disable(); + } +} +outcome("default", undefined); +outcome("null", null); +outcome("number", 1); +outcome("zero", { resolution: 0 }); +outcome("fraction", { resolution: 1.5 }); +outcome("string", { resolution: "10" }); +outcome("valid", { resolution: 10 }); diff --git a/test-parity/node-suite/perf_hooks/histogram/options-validation.ts b/test-parity/node-suite/perf_hooks/histogram/options-validation.ts new file mode 100644 index 0000000000..30b7a7eda2 --- /dev/null +++ b/test-parity/node-suite/perf_hooks/histogram/options-validation.ts @@ -0,0 +1,16 @@ +import { createHistogram } from "node:perf_hooks"; +function outcome(label: string, value: any) { + try { + createHistogram(value); + console.log(label, "ok"); + } catch (error) { + console.log(label, (error as Error).name, (error as any).code); + } +} +outcome("valid", { lowest: 1, highest: 11, figures: 1 }); +outcome("null", null); +outcome("number", 1); +outcome("lowest string", { lowest: "1" }); +outcome("highest null", { highest: null }); +outcome("figures fraction", { figures: 1.5 }); +outcome("figures high", { figures: 6 }); diff --git a/test-parity/node-suite/perf_hooks/histogram/percentile-validation.ts b/test-parity/node-suite/perf_hooks/histogram/percentile-validation.ts new file mode 100644 index 0000000000..3c5e912263 --- /dev/null +++ b/test-parity/node-suite/perf_hooks/histogram/percentile-validation.ts @@ -0,0 +1,11 @@ +import { createHistogram } from "node:perf_hooks"; +const h = createHistogram(); +h.record(1); +for (const value of [-1, 101, NaN, "50", null] as const) { + try { + h.percentile(value as any); + console.log(String(value), "no throw"); + } catch (error) { + console.log(String(value), (error as Error).name, (error as any).code); + } +} diff --git a/test-parity/node-suite/perf_hooks/histogram/percentiles-maps.ts b/test-parity/node-suite/perf_hooks/histogram/percentiles-maps.ts new file mode 100644 index 0000000000..49ec376f13 --- /dev/null +++ b/test-parity/node-suite/perf_hooks/histogram/percentiles-maps.ts @@ -0,0 +1,17 @@ +import { createHistogram } from "node:perf_hooks"; +const h = createHistogram(); +h.record(2); +h.record(3); +const numbers = h.percentiles; +const bigints = h.percentilesBigInt; +console.log("maps:", numbers instanceof Map, bigints instanceof Map); +console.log( + "keys:", + [...numbers.keys()].join(","), + [...bigints.keys()].join(","), +); +console.log( + "value types:", + [...numbers.values()].every((v) => typeof v === "number"), + [...bigints.values()].every((v) => typeof v === "bigint"), +); diff --git a/test-parity/node-suite/perf_hooks/histogram/percentiles.ts b/test-parity/node-suite/perf_hooks/histogram/percentiles.ts new file mode 100644 index 0000000000..15114b18f6 --- /dev/null +++ b/test-parity/node-suite/perf_hooks/histogram/percentiles.ts @@ -0,0 +1,15 @@ +import { createHistogram } from "node:perf_hooks"; +const h = createHistogram(); +for (const n of [1, 2, 4, 8]) h.record(n); +console.log( + "endpoints:", + h.percentile(1) === h.min, + h.percentile(100) === h.max, +); +console.log( + "bigint endpoints:", + h.percentileBigInt(1) === h.minBigInt, + h.percentileBigInt(100) === h.maxBigInt, +); +console.log("ordered:", h.percentile(25) <= h.percentile(75)); +console.log("types:", typeof h.percentile(50), typeof h.percentileBigInt(50)); diff --git a/test-parity/node-suite/perf_hooks/histogram/record-delta.ts b/test-parity/node-suite/perf_hooks/histogram/record-delta.ts new file mode 100644 index 0000000000..ee5712c72f --- /dev/null +++ b/test-parity/node-suite/perf_hooks/histogram/record-delta.ts @@ -0,0 +1,6 @@ +import { createHistogram } from "node:perf_hooks"; +const h = createHistogram(); +console.log("first return:", h.recordDelta() === undefined, h.count); +await new Promise((resolve) => setImmediate(resolve)); +console.log("second return:", h.recordDelta() === undefined, h.count); +console.log("positive:", h.minBigInt > 0n); diff --git a/test-parity/node-suite/perf_hooks/histogram/record-number-bigint.ts b/test-parity/node-suite/perf_hooks/histogram/record-number-bigint.ts new file mode 100644 index 0000000000..6bb326544c --- /dev/null +++ b/test-parity/node-suite/perf_hooks/histogram/record-number-bigint.ts @@ -0,0 +1,9 @@ +import { createHistogram } from "node:perf_hooks"; +const h = createHistogram(); +h.record(5); +h.record(9n); +console.log("count pair:", h.count, h.countBigInt === 2n); +console.log("min pair:", h.min, h.minBigInt === 5n); +console.log("max pair:", h.max, h.maxBigInt === 9n); +console.log("mean range:", h.mean >= h.min && h.mean <= h.max); +console.log("stddev:", h.stddev >= 0); diff --git a/test-parity/node-suite/perf_hooks/histogram/record-validation.ts b/test-parity/node-suite/perf_hooks/histogram/record-validation.ts new file mode 100644 index 0000000000..a05a616e3d --- /dev/null +++ b/test-parity/node-suite/perf_hooks/histogram/record-validation.ts @@ -0,0 +1,20 @@ +import { createHistogram } from "node:perf_hooks"; +const h = createHistogram(); +for ( + const [label, value] of [ + ["zero", 0], + ["fraction", 1.5], + ["unsafe", Number.MAX_SAFE_INTEGER + 1], + ["nan", NaN], + ["string", "1"], + ["null", null], + ] as const +) { + try { + h.record(value as any); + console.log(label, "no throw"); + } catch (error) { + console.log(label, (error as Error).name, (error as any).code); + } +} +console.log("count:", h.count); diff --git a/test-parity/node-suite/perf_hooks/histogram/reset.ts b/test-parity/node-suite/perf_hooks/histogram/reset.ts new file mode 100644 index 0000000000..1f0a242ed2 --- /dev/null +++ b/test-parity/node-suite/perf_hooks/histogram/reset.ts @@ -0,0 +1,11 @@ +import { createHistogram } from "node:perf_hooks"; +const h = createHistogram(); +h.record(5); +console.log("reset return:", h.reset() === undefined); +console.log("count:", h.count, h.countBigInt === 0n); +console.log( + "empty bounds:", + h.minBigInt === 9223372036854775807n, + h.maxBigInt === 0n, +); +console.log("empty stats:", Number.isNaN(h.mean), Number.isNaN(h.stddev)); diff --git a/test-parity/node-suite/perf_hooks/histogram/to-json.ts b/test-parity/node-suite/perf_hooks/histogram/to-json.ts new file mode 100644 index 0000000000..36a3d56576 --- /dev/null +++ b/test-parity/node-suite/perf_hooks/histogram/to-json.ts @@ -0,0 +1,11 @@ +import { createHistogram } from "node:perf_hooks"; +const h = createHistogram(); +h.record(3); +h.record(7); +const json = h.toJSON(); +console.log("keys:", Object.keys(json).sort().join(",")); +console.log("stable fields:", json.count, json.min, json.max, json.exceeds); +console.log( + "percentiles object:", + typeof json.percentiles === "object" && json.percentiles !== null, +); diff --git a/test-parity/node-suite/perf_hooks/imports/function-aliases.ts b/test-parity/node-suite/perf_hooks/imports/function-aliases.ts new file mode 100644 index 0000000000..2c935abf65 --- /dev/null +++ b/test-parity/node-suite/perf_hooks/imports/function-aliases.ts @@ -0,0 +1,6 @@ +import { eventLoopUtilization, performance, timerify } from "node:perf_hooks"; +console.log("timerify alias:", performance.timerify === timerify); +console.log( + "elu alias:", + performance.eventLoopUtilization === eventLoopUtilization, +); diff --git a/test-parity/node-suite/perf_hooks/imports/namespace-keys.ts b/test-parity/node-suite/perf_hooks/imports/namespace-keys.ts new file mode 100644 index 0000000000..d7878177c3 --- /dev/null +++ b/test-parity/node-suite/perf_hooks/imports/namespace-keys.ts @@ -0,0 +1,6 @@ +import * as hooks from "node:perf_hooks"; +console.log(Object.keys(hooks).sort().join(",")); +console.log( + "default identity:", + hooks.default.performance === hooks.performance, +); diff --git a/test-parity/node-suite/perf_hooks/mark/detail-circular.ts b/test-parity/node-suite/perf_hooks/mark/detail-circular.ts index fc4d9c3212..ee4538dd1d 100644 --- a/test-parity/node-suite/perf_hooks/mark/detail-circular.ts +++ b/test-parity/node-suite/perf_hooks/mark/detail-circular.ts @@ -3,11 +3,10 @@ import { performance } from "node:perf_hooks"; // cycles via reference preservation). const o: any = {}; o.self = o; -let ok = false; try { - performance.mark("c", { detail: o }); - ok = true; -} catch { - ok = false; + const mark = performance.mark("c", { detail: o }); + console.log("distinct clone:", mark.detail !== o); + console.log("cycle preserved:", mark.detail.self === mark.detail); +} finally { + performance.clearMarks(); } -console.log("accepted:", ok); diff --git a/test-parity/node-suite/perf_hooks/mark/direct-constructor.ts b/test-parity/node-suite/perf_hooks/mark/direct-constructor.ts new file mode 100644 index 0000000000..e6ebf72258 --- /dev/null +++ b/test-parity/node-suite/perf_hooks/mark/direct-constructor.ts @@ -0,0 +1,22 @@ +import { + performance, + PerformanceEntry, + PerformanceMark, +} from "node:perf_hooks"; +const detail = { value: 1 }; +const mark = new PerformanceMark("direct", { startTime: 7, detail }); +console.log("shape:", mark.name, mark.entryType, mark.startTime, mark.duration); +console.log( + "instances:", + mark instanceof PerformanceEntry, + mark instanceof PerformanceMark, +); +console.log( + "detail cloned:", + JSON.stringify(mark.detail), + mark.detail !== detail, +); +console.log( + "not timeline entry:", + performance.getEntriesByName("direct").length, +); diff --git a/test-parity/node-suite/perf_hooks/mark/repeated-name-latest.ts b/test-parity/node-suite/perf_hooks/mark/repeated-name-latest.ts new file mode 100644 index 0000000000..0430e33f00 --- /dev/null +++ b/test-parity/node-suite/perf_hooks/mark/repeated-name-latest.ts @@ -0,0 +1,26 @@ +import { performance } from "node:perf_hooks"; +performance.clearMarks(); +performance.clearMeasures(); +try { + performance.mark("same", { startTime: 1 }); + performance.mark("same", { startTime: 5 }); + console.log( + "entries:", + performance.getEntriesByName("same", "mark").map((entry) => entry.startTime) + .join(","), + ); + console.log( + "latest lookup:", + performance.measure("from-latest", "same").startTime, + ); + performance.clearMarks("same"); + try { + performance.measure("missing", "same"); + console.log("missing no throw"); + } catch (error) { + console.log("missing:", (error as Error).name); + } +} finally { + performance.clearMarks(); + performance.clearMeasures(); +} diff --git a/test-parity/node-suite/perf_hooks/mark/reserved-node-milestone.ts b/test-parity/node-suite/perf_hooks/mark/reserved-node-milestone.ts new file mode 100644 index 0000000000..5223b5e1ed --- /dev/null +++ b/test-parity/node-suite/perf_hooks/mark/reserved-node-milestone.ts @@ -0,0 +1,20 @@ +import { performance } from "node:perf_hooks"; +const name = "nodeStart"; +try { + performance.mark(name); + console.log("mark no throw"); +} catch (error) { + console.log("mark:", (error as Error).name, (error as any).code); +} +const measure = performance.measure("milestone", { start: name, duration: 1 }); +console.log( + "measure start:", + measure.startTime === performance.nodeTiming.nodeStart, +); +try { + performance.clearMarks(name); + console.log("clear no throw"); +} catch (error) { + console.log("clear:", (error as Error).name, (error as any).code); +} +performance.clearMeasures(); diff --git a/test-parity/node-suite/perf_hooks/measure/detail-clone-failures.ts b/test-parity/node-suite/perf_hooks/measure/detail-clone-failures.ts new file mode 100644 index 0000000000..400f67fb93 --- /dev/null +++ b/test-parity/node-suite/perf_hooks/measure/detail-clone-failures.ts @@ -0,0 +1,27 @@ +import { performance } from "node:perf_hooks"; +const cycle: any = {}; +cycle.self = cycle; +try { + const measure: any = performance.measure("cycle", { + start: 0, + duration: 1, + detail: cycle, + }); + console.log( + "cycle:", + measure.detail !== cycle, + measure.detail.self === measure.detail, + ); + try { + performance.measure("function", { + start: 0, + duration: 1, + detail: () => {}, + }); + console.log("function no throw"); + } catch (error) { + console.log("function:", (error as Error).name); + } +} finally { + performance.clearMeasures(); +} diff --git a/test-parity/node-suite/perf_hooks/node-timing/shape-invariants.ts b/test-parity/node-suite/perf_hooks/node-timing/shape-invariants.ts new file mode 100644 index 0000000000..58784d8b67 --- /dev/null +++ b/test-parity/node-suite/perf_hooks/node-timing/shape-invariants.ts @@ -0,0 +1,21 @@ +import { performance, PerformanceEntry } from "node:perf_hooks"; +const timing = performance.nodeTiming; +console.log("identity:", timing === performance.nodeTiming); +console.log( + "entry:", + timing instanceof PerformanceEntry, + timing.name, + timing.entryType, + timing.startTime, +); +console.log( + "ordered:", + timing.nodeStart <= timing.v8Start && timing.v8Start <= timing.environment && + timing.environment <= timing.bootstrapComplete, +); +console.log("duration:", timing.duration >= timing.bootstrapComplete); +console.log( + "loop sentinels:", + timing.loopStart === -1 || timing.loopStart >= timing.bootstrapComplete, + timing.loopExit === -1 || timing.loopExit >= timing.loopStart, +); diff --git a/test-parity/node-suite/perf_hooks/node-timing/to-json.ts b/test-parity/node-suite/perf_hooks/node-timing/to-json.ts new file mode 100644 index 0000000000..c8dfa5268e --- /dev/null +++ b/test-parity/node-suite/perf_hooks/node-timing/to-json.ts @@ -0,0 +1,25 @@ +import { performance } from "node:perf_hooks"; +const timing = performance.nodeTiming; +const json = timing.toJSON(); +console.log("keys:", Object.keys(json).sort().join(",")); +console.log( + "stable:", + json.name, + json.entryType, + json.startTime, + json.nodeStart === timing.nodeStart, +); +console.log("fresh:", json !== timing.toJSON()); +console.log( + "numeric:", + [ + "duration", + "nodeStart", + "v8Start", + "environment", + "bootstrapComplete", + "loopStart", + "loopExit", + "idleTime", + ].every((key) => typeof json[key] === "number"), +); diff --git a/test-parity/node-suite/perf_hooks/now/monotonic.ts b/test-parity/node-suite/perf_hooks/now/monotonic.ts deleted file mode 100644 index 403e670fee..0000000000 --- a/test-parity/node-suite/perf_hooks/now/monotonic.ts +++ /dev/null @@ -1,5 +0,0 @@ -import { performance } from "node:perf_hooks"; -// Successive performance.now() readings are monotonically non-decreasing. -const a = performance.now(); -const b = performance.now(); -console.log("monotonic:", b >= a); diff --git a/test-parity/node-suite/perf_hooks/now/sub-ms-precision.ts b/test-parity/node-suite/perf_hooks/now/sub-ms-precision.ts index 7d2d005760..b2624eaca0 100644 --- a/test-parity/node-suite/perf_hooks/now/sub-ms-precision.ts +++ b/test-parity/node-suite/perf_hooks/now/sub-ms-precision.ts @@ -1,4 +1,9 @@ import { performance } from "node:perf_hooks"; -// performance.now() returns sub-millisecond fractional values. -const n = performance.now(); -console.log("fractional:", n !== Math.floor(n)); +// High-resolution readings are finite numbers. Fractional output is common but +// not a portable contract for every clock/platform combination. +const readings = [performance.now(), performance.now(), performance.now()]; +console.log("finite:", readings.every(Number.isFinite)); +console.log( + "ordered:", + readings[0] <= readings[1] && readings[1] <= readings[2], +); diff --git a/test-parity/node-suite/perf_hooks/observer/buffered-false-excludes-prior.ts b/test-parity/node-suite/perf_hooks/observer/buffered-false-excludes-prior.ts new file mode 100644 index 0000000000..10b89d13de --- /dev/null +++ b/test-parity/node-suite/perf_hooks/observer/buffered-false-excludes-prior.ts @@ -0,0 +1,12 @@ +import { performance, PerformanceObserver } from "node:perf_hooks"; +performance.clearMarks(); +performance.mark("prior", { startTime: 1 }); +const observer = new PerformanceObserver(() => {}); +try { + observer.observe({ type: "mark", buffered: false }); + performance.mark("future", { startTime: 2 }); + console.log(observer.takeRecords().map((entry) => entry.name).join(",")); +} finally { + observer.disconnect(); + performance.clearMarks(); +} diff --git a/test-parity/node-suite/perf_hooks/observer/buffered.ts b/test-parity/node-suite/perf_hooks/observer/buffered.ts index 9d8f426939..99c6c2a2ed 100644 --- a/test-parity/node-suite/perf_hooks/observer/buffered.ts +++ b/test-parity/node-suite/perf_hooks/observer/buffered.ts @@ -4,18 +4,15 @@ import { performance, PerformanceObserver } from "node:perf_hooks"; // runtime so the test reports 0 instead of hanging.) performance.mark("pre1"); performance.mark("pre2"); -await new Promise((resolve) => { - let done = false; - const finish = (n: number) => { - if (done) return; - done = true; - console.log("buffered count:", n); - resolve(); - }; - const obs = new PerformanceObserver((list) => { - obs.disconnect(); - finish(list.getEntries().length); - }); - obs.observe({ type: "mark", buffered: true }); - setTimeout(() => finish(0), 300); +let names = "not delivered"; +const obs = new PerformanceObserver((list) => { + names = list.getEntries().map((entry) => entry.name).join(","); }); +try { + obs.observe({ type: "mark", buffered: true }); + await new Promise((resolve) => setImmediate(resolve)); + console.log("buffered names:", names); +} finally { + obs.disconnect(); + performance.clearMarks(); +} diff --git a/test-parity/node-suite/perf_hooks/observer/callback-this.ts b/test-parity/node-suite/perf_hooks/observer/callback-this.ts new file mode 100644 index 0000000000..e79c157ad6 --- /dev/null +++ b/test-parity/node-suite/perf_hooks/observer/callback-this.ts @@ -0,0 +1,14 @@ +import { performance, PerformanceObserver } from "node:perf_hooks"; +let same = false; +const observer = new PerformanceObserver(function () { + same = this === observer; +}); +try { + observer.observe({ entryTypes: ["mark"] }); + performance.mark("callback-this"); + await new Promise((resolve) => setImmediate(resolve)); + console.log("this is observer:", same); +} finally { + observer.disconnect(); + performance.clearMarks(); +} diff --git a/test-parity/node-suite/perf_hooks/observer/disconnect-clears-queue.ts b/test-parity/node-suite/perf_hooks/observer/disconnect-clears-queue.ts new file mode 100644 index 0000000000..72923c7d9d --- /dev/null +++ b/test-parity/node-suite/perf_hooks/observer/disconnect-clears-queue.ts @@ -0,0 +1,16 @@ +import { performance, PerformanceObserver } from "node:perf_hooks"; +let calls = 0; +const observer = new PerformanceObserver(() => { + calls++; +}); +try { + observer.observe({ entryTypes: ["mark"] }); + performance.mark("pending"); + observer.disconnect(); + await new Promise((resolve) => setImmediate(resolve)); + console.log("calls:", calls); + console.log("records:", observer.takeRecords().length); +} finally { + observer.disconnect(); + performance.clearMarks(); +} diff --git a/test-parity/node-suite/perf_hooks/observer/duplicate-entry-types.ts b/test-parity/node-suite/perf_hooks/observer/duplicate-entry-types.ts new file mode 100644 index 0000000000..ef35e3ecd2 --- /dev/null +++ b/test-parity/node-suite/perf_hooks/observer/duplicate-entry-types.ts @@ -0,0 +1,10 @@ +import { performance, PerformanceObserver } from "node:perf_hooks"; +const observer = new PerformanceObserver(() => {}); +try { + observer.observe({ entryTypes: ["mark", "mark"] }); + performance.mark("dedupe"); + console.log("records:", observer.takeRecords().length); +} finally { + observer.disconnect(); + performance.clearMarks(); +} diff --git a/test-parity/node-suite/perf_hooks/observer/entry-list-snapshot.ts b/test-parity/node-suite/perf_hooks/observer/entry-list-snapshot.ts new file mode 100644 index 0000000000..da422680d4 --- /dev/null +++ b/test-parity/node-suite/perf_hooks/observer/entry-list-snapshot.ts @@ -0,0 +1,20 @@ +import { performance, PerformanceObserver } from "node:perf_hooks"; +let checked = false; +const observer = new PerformanceObserver((list) => { + const first = list.getEntries(); + const second = list.getEntries(); + first.length = 0; + console.log("fresh:", first !== second); + console.log("same entry:", second[0] === list.getEntries()[0]); + console.log("isolated:", second.length); + checked = true; +}); +try { + observer.observe({ entryTypes: ["mark"] }); + performance.mark("snapshot"); + await new Promise((resolve) => setImmediate(resolve)); + console.log("callback:", checked); +} finally { + observer.disconnect(); + performance.clearMarks(); +} diff --git a/test-parity/node-suite/perf_hooks/observer/entry-list-validation.ts b/test-parity/node-suite/perf_hooks/observer/entry-list-validation.ts new file mode 100644 index 0000000000..58d02765a6 --- /dev/null +++ b/test-parity/node-suite/perf_hooks/observer/entry-list-validation.ts @@ -0,0 +1,37 @@ +import { + performance, + PerformanceObserver, + PerformanceObserverEntryList, +} from "node:perf_hooks"; +let list: PerformanceObserverEntryList | undefined; +const observer = new PerformanceObserver((value) => { + list = value; +}); +try { + observer.observe({ entryTypes: ["mark"] }); + performance.mark("list-validation"); + await new Promise((resolve) => setImmediate(resolve)); + for ( + const [label, fn] of [ + ["name missing", () => (list!.getEntriesByName as any)()], + ["type missing", () => (list!.getEntriesByType as any)()], + ["name symbol", () => list!.getEntriesByName(Symbol("x") as any)], + ["wrong receiver", () => + Reflect.apply( + PerformanceObserverEntryList.prototype.getEntries, + {}, + [], + )], + ] as const + ) { + try { + fn(); + console.log(label, "no throw"); + } catch (error) { + console.log(label, (error as Error).name, (error as any).code); + } + } +} finally { + observer.disconnect(); + performance.clearMarks(); +} diff --git a/test-parity/node-suite/perf_hooks/observer/mode-switch-validation.ts b/test-parity/node-suite/perf_hooks/observer/mode-switch-validation.ts new file mode 100644 index 0000000000..544ab76877 --- /dev/null +++ b/test-parity/node-suite/perf_hooks/observer/mode-switch-validation.ts @@ -0,0 +1,15 @@ +import { PerformanceObserver } from "node:perf_hooks"; +function outcome(label: string, first: any, second: any) { + const observer = new PerformanceObserver(() => {}); + try { + observer.observe(first); + observer.observe(second); + console.log(label, "no throw"); + } catch (error) { + console.log(label, (error as Error).name); + } finally { + observer.disconnect(); + } +} +outcome("multi to single", { entryTypes: ["mark"] }, { type: "measure" }); +outcome("single to multi", { type: "mark" }, { entryTypes: ["measure"] }); diff --git a/test-parity/node-suite/perf_hooks/observer/multiple-type-replaces.ts b/test-parity/node-suite/perf_hooks/observer/multiple-type-replaces.ts new file mode 100644 index 0000000000..5da3d1bdca --- /dev/null +++ b/test-parity/node-suite/perf_hooks/observer/multiple-type-replaces.ts @@ -0,0 +1,13 @@ +import { performance, PerformanceObserver } from "node:perf_hooks"; +const observer = new PerformanceObserver(() => {}); +try { + observer.observe({ entryTypes: ["mark"] }); + observer.observe({ entryTypes: ["measure"] }); + performance.mark("a", { startTime: 1 }); + performance.measure("m", { start: 1, duration: 1 }); + console.log(observer.takeRecords().map((entry) => entry.entryType).join(",")); +} finally { + observer.disconnect(); + performance.clearMarks(); + performance.clearMeasures(); +} diff --git a/test-parity/node-suite/perf_hooks/observer/reobserve-after-disconnect.ts b/test-parity/node-suite/perf_hooks/observer/reobserve-after-disconnect.ts new file mode 100644 index 0000000000..9251c337f9 --- /dev/null +++ b/test-parity/node-suite/perf_hooks/observer/reobserve-after-disconnect.ts @@ -0,0 +1,14 @@ +import { performance, PerformanceObserver } from "node:perf_hooks"; +const observer = new PerformanceObserver(() => {}); +try { + observer.observe({ entryTypes: ["mark"] }); + observer.disconnect(); + observer.observe({ type: "measure" }); + performance.mark("a", { startTime: 0 }); + performance.measure("m", { start: 0, duration: 1 }); + console.log(observer.takeRecords().map((e) => e.entryType).join(",")); +} finally { + observer.disconnect(); + performance.clearMarks(); + performance.clearMeasures(); +} diff --git a/test-parity/node-suite/perf_hooks/observer/replacement-retains-queued.ts b/test-parity/node-suite/perf_hooks/observer/replacement-retains-queued.ts new file mode 100644 index 0000000000..15c8c61f6f --- /dev/null +++ b/test-parity/node-suite/perf_hooks/observer/replacement-retains-queued.ts @@ -0,0 +1,16 @@ +import { performance, PerformanceObserver } from "node:perf_hooks"; +const observer = new PerformanceObserver(() => {}); +try { + observer.observe({ entryTypes: ["measure"] }); + performance.measure("queued", { start: 1, duration: 1 }); + observer.observe({ entryTypes: ["mark"] }); + performance.mark("later", { startTime: 3 }); + console.log( + observer.takeRecords().map((entry) => `${entry.name}:${entry.entryType}`) + .join(","), + ); +} finally { + observer.disconnect(); + performance.clearMarks(); + performance.clearMeasures(); +} diff --git a/test-parity/node-suite/perf_hooks/observer/single-type-accumulates.ts b/test-parity/node-suite/perf_hooks/observer/single-type-accumulates.ts new file mode 100644 index 0000000000..8bad3a2070 --- /dev/null +++ b/test-parity/node-suite/perf_hooks/observer/single-type-accumulates.ts @@ -0,0 +1,13 @@ +import { performance, PerformanceObserver } from "node:perf_hooks"; +const observer = new PerformanceObserver(() => {}); +try { + observer.observe({ type: "mark" }); + observer.observe({ type: "measure" }); + performance.mark("a", { startTime: 1 }); + performance.measure("m", { start: 1, duration: 1 }); + console.log(observer.takeRecords().map((entry) => entry.entryType).join(",")); +} finally { + observer.disconnect(); + performance.clearMarks(); + performance.clearMeasures(); +} diff --git a/test-parity/node-suite/perf_hooks/observer/supported-entry-types-exact.ts b/test-parity/node-suite/perf_hooks/observer/supported-entry-types-exact.ts new file mode 100644 index 0000000000..53356aae9a --- /dev/null +++ b/test-parity/node-suite/perf_hooks/observer/supported-entry-types-exact.ts @@ -0,0 +1,5 @@ +import { PerformanceObserver } from "node:perf_hooks"; +const types = PerformanceObserver.supportedEntryTypes; +console.log(types.join(",")); +console.log("frozen:", Object.isFrozen(types)); +console.log("fresh getter:", types !== PerformanceObserver.supportedEntryTypes); diff --git a/test-parity/node-suite/perf_hooks/observer/take-records-suppresses-callback.ts b/test-parity/node-suite/perf_hooks/observer/take-records-suppresses-callback.ts new file mode 100644 index 0000000000..5ceab05787 --- /dev/null +++ b/test-parity/node-suite/perf_hooks/observer/take-records-suppresses-callback.ts @@ -0,0 +1,15 @@ +import { performance, PerformanceObserver } from "node:perf_hooks"; +let calls = 0; +const observer = new PerformanceObserver(() => { + calls++; +}); +try { + observer.observe({ entryTypes: ["mark"] }); + performance.mark("drained"); + console.log("taken:", observer.takeRecords().length); + await new Promise((resolve) => setImmediate(resolve)); + console.log("calls:", calls); +} finally { + observer.disconnect(); + performance.clearMarks(); +} diff --git a/test-parity/node-suite/perf_hooks/resource-timing/cache-mode-validation.ts b/test-parity/node-suite/perf_hooks/resource-timing/cache-mode-validation.ts new file mode 100644 index 0000000000..f97eb22aeb --- /dev/null +++ b/test-parity/node-suite/perf_hooks/resource-timing/cache-mode-validation.ts @@ -0,0 +1,24 @@ +import { performance } from "node:perf_hooks"; +const info: any = { + startTime: 0, + endTime: 1, + encodedBodySize: 0, + decodedBodySize: 0, + finalConnectionTimingInfo: null, +}; +for (const value of ["", "local", "invalid", null, 1]) { + try { + performance.markResourceTiming( + info, + "x", + "fetch", + globalThis, + value as any, + ); + console.log(String(value), "ok"); + } catch (error) { + console.log(String(value), (error as Error).name, (error as any).code); + } finally { + performance.clearResourceTimings(); + } +} diff --git a/test-parity/node-suite/perf_hooks/resource-timing/cache-transfer-size.ts b/test-parity/node-suite/perf_hooks/resource-timing/cache-transfer-size.ts new file mode 100644 index 0000000000..dd863570d4 --- /dev/null +++ b/test-parity/node-suite/perf_hooks/resource-timing/cache-transfer-size.ts @@ -0,0 +1,29 @@ +import { performance } from "node:perf_hooks"; +const info: any = { + startTime: 0, + endTime: 1, + encodedBodySize: 150, + decodedBodySize: 250, + finalConnectionTimingInfo: null, +}; +performance.clearResourceTimings(); +try { + const network: any = performance.markResourceTiming( + info, + "network", + "fetch", + globalThis, + "", + ); + const local: any = performance.markResourceTiming( + info, + "local", + "fetch", + globalThis, + "local", + ); + console.log("network:", network.transferSize); + console.log("local:", local.transferSize); +} finally { + performance.clearResourceTimings(); +} diff --git a/test-parity/node-suite/perf_hooks/resource-timing/controlled-fields.ts b/test-parity/node-suite/perf_hooks/resource-timing/controlled-fields.ts new file mode 100644 index 0000000000..f475c7e3c3 --- /dev/null +++ b/test-parity/node-suite/perf_hooks/resource-timing/controlled-fields.ts @@ -0,0 +1,73 @@ +import { + performance, + PerformanceEntry, + PerformanceResourceTiming, +} from "node:perf_hooks"; +performance.clearResourceTimings(); +const timing: any = { + startTime: 10, + redirectStartTime: 11, + redirectEndTime: 12, + postRedirectStartTime: 13, + finalServiceWorkerStartTime: 14, + finalNetworkRequestStartTime: 20, + finalNetworkResponseStartTime: 30, + endTime: 40, + encodedBodySize: 150, + decodedBodySize: 250, + finalConnectionTimingInfo: { + domainLookupStartTime: 15, + domainLookupEndTime: 16, + connectionStartTime: 17, + connectionEndTime: 18, + secureConnectionStartTime: 19, + ALPNNegotiatedProtocol: "h2", + }, +}; +try { + const entry: any = performance.markResourceTiming( + timing, + "https://example.test/resource", + "fetch", + globalThis, + "", + {}, + 201, + "cache", + ); + console.log( + "instance:", + entry instanceof PerformanceEntry, + entry instanceof PerformanceResourceTiming, + ); + console.log( + "base:", + entry.name, + entry.entryType, + entry.initiatorType, + entry.startTime, + entry.duration, + ); + console.log( + "network:", + entry.domainLookupStart, + entry.domainLookupEnd, + entry.connectStart, + entry.connectEnd, + entry.secureConnectionStart, + entry.nextHopProtocol, + ); + console.log( + "response:", + entry.requestStart, + entry.responseStart, + entry.responseEnd, + entry.encodedBodySize, + entry.decodedBodySize, + entry.transferSize, + entry.responseStatus, + entry.deliveryType, + ); +} finally { + performance.clearResourceTimings(); +} diff --git a/test-parity/node-suite/perf_hooks/resource-timing/illegal-constructor-tag.ts b/test-parity/node-suite/perf_hooks/resource-timing/illegal-constructor-tag.ts new file mode 100644 index 0000000000..caa26f8b0a --- /dev/null +++ b/test-parity/node-suite/perf_hooks/resource-timing/illegal-constructor-tag.ts @@ -0,0 +1,14 @@ +import { PerformanceResourceTiming } from "node:perf_hooks"; +console.log( + "tag:", + Object.getOwnPropertyDescriptor( + PerformanceResourceTiming.prototype, + Symbol.toStringTag, + )?.value, +); +try { + new PerformanceResourceTiming(); + console.log("no throw"); +} catch (error) { + console.log((error as Error).name, (error as any).code); +} diff --git a/test-parity/node-suite/perf_hooks/resource-timing/mark-resource-entry.ts b/test-parity/node-suite/perf_hooks/resource-timing/mark-resource-entry.ts index 84c43a75f9..14ea487bae 100644 --- a/test-parity/node-suite/perf_hooks/resource-timing/mark-resource-entry.ts +++ b/test-parity/node-suite/perf_hooks/resource-timing/mark-resource-entry.ts @@ -5,16 +5,16 @@ performance.clearResourceTimings(); const before = performance.getEntriesByType("resource").length; const timingInfo: any = { startTime: 10, - redirectStart: 0, - redirectEnd: 0, - fetchStart: 10, - domainLookupStart: 11, - domainLookupEnd: 12, - connectStart: 13, - connectEnd: 14, - requestStart: 15, - responseStart: 16, - responseEnd: 20, + redirectStartTime: 0, + redirectEndTime: 0, + postRedirectStartTime: 10, + finalServiceWorkerStartTime: 0, + finalNetworkRequestStartTime: 15, + finalNetworkResponseStartTime: 16, + endTime: 20, + encodedBodySize: 0, + decodedBodySize: 0, + finalConnectionTimingInfo: null, }; const returned: any = performance.markResourceTiming( @@ -30,7 +30,13 @@ const last: any = entries[entries.length - 1]; console.log("returned object:", typeof returned); console.log("same returned:", returned === last); console.log("delta:", entries.length - before); -console.log("fields:", last.name, last.entryType, last.initiatorType, last.startTime); -console.log("duration nan:", Number.isNaN(last.duration)); +console.log( + "fields:", + last.name, + last.entryType, + last.initiatorType, + last.startTime, +); +console.log("duration:", last.duration); performance.clearResourceTimings(); console.log("after clear:", performance.getEntriesByType("resource").length); diff --git a/test-parity/node-suite/perf_hooks/resource-timing/mark-resource-timing.ts b/test-parity/node-suite/perf_hooks/resource-timing/mark-resource-timing.ts deleted file mode 100644 index d53cdfbb17..0000000000 --- a/test-parity/node-suite/perf_hooks/resource-timing/mark-resource-timing.ts +++ /dev/null @@ -1,4 +0,0 @@ -import { performance } from "node:perf_hooks"; -// performance.markResourceTiming(info) records a resource-timing entry -// (paired with clearResourceTimings / setResourceTimingBufferSize). -console.log("is function:", typeof performance.markResourceTiming === "function"); diff --git a/test-parity/node-suite/perf_hooks/resource-timing/observer-delivery.ts b/test-parity/node-suite/perf_hooks/resource-timing/observer-delivery.ts new file mode 100644 index 0000000000..563a38610c --- /dev/null +++ b/test-parity/node-suite/perf_hooks/resource-timing/observer-delivery.ts @@ -0,0 +1,29 @@ +import { performance, PerformanceObserver } from "node:perf_hooks"; +const info: any = { + startTime: 2, + endTime: 5, + encodedBodySize: 0, + decodedBodySize: 0, + finalConnectionTimingInfo: null, +}; +let names = "not delivered"; +const observer = new PerformanceObserver((list) => { + names = list.getEntriesByType("resource").map((entry) => entry.name).join( + ",", + ); +}); +try { + observer.observe({ entryTypes: ["resource"] }); + performance.markResourceTiming( + info, + "synthetic-resource", + "fetch", + globalThis, + "", + ); + await new Promise((resolve) => setImmediate(resolve)); + console.log("observed:", names); +} finally { + observer.disconnect(); + performance.clearResourceTimings(); +} diff --git a/test-parity/node-suite/perf_hooks/resource-timing/to-json-controlled.ts b/test-parity/node-suite/perf_hooks/resource-timing/to-json-controlled.ts new file mode 100644 index 0000000000..ea07ca3513 --- /dev/null +++ b/test-parity/node-suite/perf_hooks/resource-timing/to-json-controlled.ts @@ -0,0 +1,36 @@ +import { performance } from "node:perf_hooks"; +performance.clearResourceTimings(); +const timing: any = { + startTime: 1, + endTime: 4, + encodedBodySize: 5, + decodedBodySize: 6, + finalConnectionTimingInfo: null, +}; +try { + const json: any = performance.markResourceTiming( + timing, + "controlled", + "other", + globalThis, + "", + {}, + 204, + "direct", + ).toJSON(); + console.log("keys:", Object.keys(json).sort().join(",")); + console.log( + "stable:", + json.name, + json.entryType, + json.startTime, + json.duration, + json.transferSize, + json.encodedBodySize, + json.decodedBodySize, + json.responseStatus, + json.deliveryType, + ); +} finally { + performance.clearResourceTimings(); +} diff --git a/test-parity/node-suite/perf_hooks/shapes/prototype-tags.ts b/test-parity/node-suite/perf_hooks/shapes/prototype-tags.ts new file mode 100644 index 0000000000..fb0bb85cc0 --- /dev/null +++ b/test-parity/node-suite/perf_hooks/shapes/prototype-tags.ts @@ -0,0 +1,35 @@ +import { + performance, + PerformanceMark, + PerformanceMeasure, + PerformanceObserver, + PerformanceObserverEntryList, + PerformanceResourceTiming, +} from "node:perf_hooks"; +const observer = new PerformanceObserver(() => {}); +const names: [string, any][] = [ + ["performance", performance], + ["mark", performance.mark("tag")], + ["measure", performance.measure("tag-measure")], + ["observer", observer], +]; +for (const [name, value] of names) { + console.log(name, Object.prototype.toString.call(value)); +} +for ( + const Ctor of [ + PerformanceMark, + PerformanceMeasure, + PerformanceObserver, + PerformanceObserverEntryList, + PerformanceResourceTiming, + ] +) { + console.log( + Ctor.name, + Object.getOwnPropertyDescriptor(Ctor.prototype, Symbol.toStringTag)?.value, + ); +} +performance.clearMarks(); +performance.clearMeasures(); +observer.disconnect(); diff --git a/test-parity/node-suite/perf_hooks/shapes/public-descriptors.ts b/test-parity/node-suite/perf_hooks/shapes/public-descriptors.ts new file mode 100644 index 0000000000..368b4c853b --- /dev/null +++ b/test-parity/node-suite/perf_hooks/shapes/public-descriptors.ts @@ -0,0 +1,31 @@ +import { performance, PerformanceObserver } from "node:perf_hooks"; +const prototype = Object.getPrototypeOf(performance); +for (const key of ["now", "mark", "measure", "getEntries", "toJSON"]) { + const descriptor = Object.getOwnPropertyDescriptor(prototype, key)!; + console.log( + key, + typeof descriptor.value, + descriptor.writable, + descriptor.enumerable, + descriptor.configurable, + ); +} +const origin = Object.getOwnPropertyDescriptor(prototype, "timeOrigin")!; +console.log( + "timeOrigin", + typeof origin.get, + origin.set, + origin.enumerable, + origin.configurable, +); +const supported = Object.getOwnPropertyDescriptor( + PerformanceObserver, + "supportedEntryTypes", +)!; +console.log( + "supportedEntryTypes", + typeof supported.get, + supported.set, + supported.enumerable, + supported.configurable, +); diff --git a/test-parity/node-suite/perf_hooks/shapes/receiver-branding.ts b/test-parity/node-suite/perf_hooks/shapes/receiver-branding.ts new file mode 100644 index 0000000000..49a9c984e9 --- /dev/null +++ b/test-parity/node-suite/perf_hooks/shapes/receiver-branding.ts @@ -0,0 +1,40 @@ +import { + performance, + PerformanceEntry, + PerformanceObserver, +} from "node:perf_hooks"; +function outcome(label: string, fn: () => unknown) { + try { + fn(); + console.log(label, "no throw"); + } catch (error) { + console.log(label, (error as Error).name); + } +} +outcome( + "performance.now", + () => Reflect.apply(Object.getPrototypeOf(performance).now, {}, []), +); +outcome( + "performance.mark", + () => Reflect.apply(Object.getPrototypeOf(performance).mark, {}, ["x"]), +); +outcome( + "entry.toJSON", + () => Reflect.apply(PerformanceEntry.prototype.toJSON, {}, []), +); +outcome( + "observer.observe", + () => + Reflect.apply(PerformanceObserver.prototype.observe, {}, [{ + entryTypes: ["mark"], + }]), +); +outcome( + "observer.disconnect", + () => Reflect.apply(PerformanceObserver.prototype.disconnect, {}, []), +); +outcome( + "observer.takeRecords", + () => Reflect.apply(PerformanceObserver.prototype.takeRecords, {}, []), +); diff --git a/test-parity/node-suite/perf_hooks/timerify/async-fulfill.ts b/test-parity/node-suite/perf_hooks/timerify/async-fulfill.ts new file mode 100644 index 0000000000..13ca72a0d0 --- /dev/null +++ b/test-parity/node-suite/perf_hooks/timerify/async-fulfill.ts @@ -0,0 +1,12 @@ +import { PerformanceObserver, timerify } from "node:perf_hooks"; +const observer = new PerformanceObserver(() => {}); +try { + observer.observe({ entryTypes: ["function"] }); + const wrapped = timerify(async (value: number) => value + 1); + const promise = wrapped(4); + console.log("pending records:", observer.takeRecords().length); + console.log("result:", await promise); + console.log("settled records:", observer.takeRecords().length); +} finally { + observer.disconnect(); +} diff --git a/test-parity/node-suite/perf_hooks/timerify/async-reject.ts b/test-parity/node-suite/perf_hooks/timerify/async-reject.ts new file mode 100644 index 0000000000..c75fd9f598 --- /dev/null +++ b/test-parity/node-suite/perf_hooks/timerify/async-reject.ts @@ -0,0 +1,17 @@ +import { PerformanceObserver, timerify } from "node:perf_hooks"; +const observer = new PerformanceObserver(() => {}); +try { + observer.observe({ entryTypes: ["function"] }); + const reason = new Error("reject"); + const wrapped = timerify(async () => { + throw reason; + }); + try { + await wrapped(); + } catch (error) { + console.log("same reason:", error === reason); + } + console.log("records:", observer.takeRecords().length); +} finally { + observer.disconnect(); +} diff --git a/test-parity/node-suite/perf_hooks/timerify/constructor.ts b/test-parity/node-suite/perf_hooks/timerify/constructor.ts new file mode 100644 index 0000000000..e25cc46d96 --- /dev/null +++ b/test-parity/node-suite/perf_hooks/timerify/constructor.ts @@ -0,0 +1,13 @@ +import { timerify } from "node:perf_hooks"; +class Value { + marker = 1; +} +const Wrapped = timerify(Value); +const instance = new Wrapped(); +console.log("instance:", instance instanceof Value, instance.marker); +try { + (Wrapped as any)(); + console.log("call ok"); +} catch (error) { + console.log("call:", (error as Error).name); +} diff --git a/test-parity/node-suite/perf_hooks/timerify/entry-arguments.ts b/test-parity/node-suite/perf_hooks/timerify/entry-arguments.ts new file mode 100644 index 0000000000..9158da4bf6 --- /dev/null +++ b/test-parity/node-suite/perf_hooks/timerify/entry-arguments.ts @@ -0,0 +1,17 @@ +import { PerformanceObserver, timerify } from "node:perf_hooks"; +const argument = { value: 2 }; +let result = "not delivered"; +const observer = new PerformanceObserver((list) => { + const entry: any = list.getEntries()[0]; + result = `${entry.detail.length}:${entry.detail[0]}:${ + entry.detail[1] === argument + }:${entry[0]}:${entry[1] === argument}`; +}); +try { + observer.observe({ entryTypes: ["function"] }); + timerify(function sample(_a: number, _b: object) {})(1, argument); + await new Promise((resolve) => setImmediate(resolve)); + console.log("arguments:", result); +} finally { + observer.disconnect(); +} diff --git a/test-parity/node-suite/perf_hooks/timerify/function-entry.ts b/test-parity/node-suite/perf_hooks/timerify/function-entry.ts index d924068ba1..f5e3ceffc2 100644 --- a/test-parity/node-suite/perf_hooks/timerify/function-entry.ts +++ b/test-parity/node-suite/perf_hooks/timerify/function-entry.ts @@ -3,7 +3,11 @@ import { performance, PerformanceObserver } from "node:perf_hooks"; const seen: string[] = []; const obs = new PerformanceObserver((list: any) => { for (const entry of list.getEntries()) { - seen.push(`${entry.name}:${entry.entryType}:${typeof entry.duration}:${entry.duration >= 0}`); + seen.push( + `${entry.name}:${entry.entryType}:${typeof entry.duration}:${ + entry.duration >= 0 + }`, + ); } }); @@ -19,8 +23,10 @@ console.log("name:", wrapped.name); console.log("length:", wrapped.length); console.log("result:", wrapped(2, 3)); -setTimeout(() => { +try { + await new Promise((resolve) => setImmediate(resolve)); console.log("seen count:", seen.length); console.log("first:", seen[0] || "none"); +} finally { obs.disconnect(); -}, 20); +} diff --git a/test-parity/node-suite/perf_hooks/timerify/histogram-async.ts b/test-parity/node-suite/perf_hooks/timerify/histogram-async.ts new file mode 100644 index 0000000000..b574cd20b4 --- /dev/null +++ b/test-parity/node-suite/perf_hooks/timerify/histogram-async.ts @@ -0,0 +1,8 @@ +import { createHistogram, timerify } from "node:perf_hooks"; +const histogram = createHistogram(); +const wrapped = timerify(async (x: number) => x * 2, { histogram }); +const promise = wrapped(3); +console.log("pending:", histogram.count); +console.log("result:", await promise); +console.log("settled:", histogram.count); +console.log("positive:", histogram.minBigInt > 0n); diff --git a/test-parity/node-suite/perf_hooks/timerify/histogram-sync.ts b/test-parity/node-suite/perf_hooks/timerify/histogram-sync.ts new file mode 100644 index 0000000000..098632a64a --- /dev/null +++ b/test-parity/node-suite/perf_hooks/timerify/histogram-sync.ts @@ -0,0 +1,7 @@ +import { createHistogram, timerify } from "node:perf_hooks"; +const histogram = createHistogram(); +const wrapped = timerify((x: number) => x * 2, { histogram }); +console.log("before:", histogram.count); +console.log("result:", wrapped(3)); +console.log("after:", histogram.count); +console.log("positive:", histogram.minBigInt > 0n); diff --git a/test-parity/node-suite/perf_hooks/timerify/invalid-function.ts b/test-parity/node-suite/perf_hooks/timerify/invalid-function.ts new file mode 100644 index 0000000000..ebcbb1e10e --- /dev/null +++ b/test-parity/node-suite/perf_hooks/timerify/invalid-function.ts @@ -0,0 +1,9 @@ +import { timerify } from "node:perf_hooks"; +for (const value of [undefined, null, 1, Infinity, "x", {}, []]) { + try { + timerify(value as any); + console.log(typeof value, "no throw"); + } catch (error) { + console.log(typeof value, (error as Error).name, (error as any).code); + } +} diff --git a/test-parity/node-suite/perf_hooks/timerify/invalid-histogram.ts b/test-parity/node-suite/perf_hooks/timerify/invalid-histogram.ts new file mode 100644 index 0000000000..270f557020 --- /dev/null +++ b/test-parity/node-suite/perf_hooks/timerify/invalid-histogram.ts @@ -0,0 +1,9 @@ +import { timerify } from "node:perf_hooks"; +for (const value of [null, 1, "", false, {}, []]) { + try { + timerify(() => {}, { histogram: value as any }); + console.log(typeof value, "no throw"); + } catch (error) { + console.log(typeof value, (error as Error).name, (error as any).code); + } +} diff --git a/test-parity/node-suite/perf_hooks/timerify/multiple-wrapping.ts b/test-parity/node-suite/perf_hooks/timerify/multiple-wrapping.ts new file mode 100644 index 0000000000..a2c16683ff --- /dev/null +++ b/test-parity/node-suite/perf_hooks/timerify/multiple-wrapping.ts @@ -0,0 +1,10 @@ +import { timerify } from "node:perf_hooks"; +function work(a: unknown) { + return a; +} +const first = timerify(work); +const second = timerify(work); +const nested = timerify(first); +console.log("distinct:", first !== second, first !== nested); +console.log("names:", first.name, nested.name); +console.log("lengths:", first.length, nested.length); diff --git a/test-parity/node-suite/perf_hooks/timerify/name-length-descriptors.ts b/test-parity/node-suite/perf_hooks/timerify/name-length-descriptors.ts new file mode 100644 index 0000000000..07bdf857e7 --- /dev/null +++ b/test-parity/node-suite/perf_hooks/timerify/name-length-descriptors.ts @@ -0,0 +1,15 @@ +import { timerify } from "node:perf_hooks"; +function sample(a: unknown, b: unknown) { + return [a, b]; +} +const wrapped = timerify(sample); +for (const key of ["name", "length"] as const) { + const d = Object.getOwnPropertyDescriptor(wrapped, key)!; + console.log( + key, + (wrapped as any)[key], + d.writable, + d.enumerable, + d.configurable, + ); +} diff --git a/test-parity/node-suite/perf_hooks/timerify/return-object-identity.ts b/test-parity/node-suite/perf_hooks/timerify/return-object-identity.ts new file mode 100644 index 0000000000..8b763208c5 --- /dev/null +++ b/test-parity/node-suite/perf_hooks/timerify/return-object-identity.ts @@ -0,0 +1,4 @@ +import { timerify } from "node:perf_hooks"; +const value = { ok: true }; +const wrapped = timerify(() => value); +console.log("same object:", wrapped() === value); diff --git a/test-parity/node-suite/perf_hooks/timerify/sync-throw-no-entry.ts b/test-parity/node-suite/perf_hooks/timerify/sync-throw-no-entry.ts new file mode 100644 index 0000000000..087c02d551 --- /dev/null +++ b/test-parity/node-suite/perf_hooks/timerify/sync-throw-no-entry.ts @@ -0,0 +1,19 @@ +import { PerformanceObserver, timerify } from "node:perf_hooks"; +const observer = new PerformanceObserver(() => { + console.log("callback fired"); +}); +try { + observer.observe({ entryTypes: ["function"] }); + const wrapped = timerify(() => { + throw new RangeError("boom"); + }); + try { + wrapped(); + } catch (error) { + console.log("same error:", (error as Error).name, (error as Error).message); + } + await new Promise((resolve) => setImmediate(resolve)); + console.log("records:", observer.takeRecords().length); +} finally { + observer.disconnect(); +} diff --git a/test-parity/node-suite/perf_hooks/timerify/this-arguments-result.ts b/test-parity/node-suite/perf_hooks/timerify/this-arguments-result.ts new file mode 100644 index 0000000000..88ac93c7b5 --- /dev/null +++ b/test-parity/node-suite/perf_hooks/timerify/this-arguments-result.ts @@ -0,0 +1,9 @@ +import { timerify } from "node:perf_hooks"; +const receiver = { base: 10 }; +function sum(this: typeof receiver, a: number, b: number) { + return { total: this.base + a + b, args: arguments.length }; +} +const wrapped = timerify(sum); +const result = wrapped.call(receiver, 2, 3); +console.log("result:", result.total, result.args); +console.log("shape:", wrapped.name, wrapped.length); diff --git a/test-parity/node-suite/perf_hooks/timerify/timerify.ts b/test-parity/node-suite/perf_hooks/timerify/timerify.ts deleted file mode 100644 index 010ed330c5..0000000000 --- a/test-parity/node-suite/perf_hooks/timerify/timerify.ts +++ /dev/null @@ -1,6 +0,0 @@ -import { performance } from "node:perf_hooks"; -// performance.timerify(fn) wraps fn so each call records a 'function' timeline -// entry; the wrapper still returns fn's result. -const wrapped = performance.timerify((a: number, b: number) => a + b); -console.log("is function:", typeof wrapped === "function"); -console.log("result:", wrapped(2, 3)); diff --git a/test-parity/node-suite/perf_hooks/to-json/fresh-snapshots.ts b/test-parity/node-suite/perf_hooks/to-json/fresh-snapshots.ts new file mode 100644 index 0000000000..f52155f0eb --- /dev/null +++ b/test-parity/node-suite/perf_hooks/to-json/fresh-snapshots.ts @@ -0,0 +1,14 @@ +import { performance } from "node:perf_hooks"; +const first = performance.toJSON(); +const second = performance.toJSON(); +console.log("fresh root:", first !== second); +console.log("fresh timing:", first.nodeTiming !== second.nodeTiming); +console.log( + "fresh elu:", + first.eventLoopUtilization !== second.eventLoopUtilization, +); +console.log( + "stable origin:", + first.timeOrigin === second.timeOrigin && + first.timeOrigin === performance.timeOrigin, +); diff --git a/test-parity/node_suite_baseline.json b/test-parity/node_suite_baseline.json index 56a4fa4e88..050348c913 100644 --- a/test-parity/node_suite_baseline.json +++ b/test-parity/node_suite_baseline.json @@ -122,8 +122,8 @@ "total": 92 }, "perf_hooks": { - "pass": 83, - "total": 83 + "pass": 87, + "total": 148 }, "process": { "pass": 95,