Skip to content

Commit

Permalink
feat(node, cloudflare): fill in api surface for node:perf_hooks (#257)
Browse files Browse the repository at this point in the history
  • Loading branch information
IgorMinar authored Oct 7, 2024
1 parent 889e7a1 commit 43e5b0e
Show file tree
Hide file tree
Showing 4 changed files with 205 additions and 5 deletions.
12 changes: 12 additions & 0 deletions src/presets/cloudflare.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ const hybridNodeCompatModules = [
"console",
"buffer",
"crypto",
"perf_hooks",
"module",
"process",
"timers",
Expand Down Expand Up @@ -68,6 +69,17 @@ const cloudflarePreset: Preset = {
process: "unenv/runtime/node/process/$cloudflare",
setImmediate: ["unenv/runtime/node/timers/$cloudflare", "setImmediate"],
clearImmediate: ["unenv/runtime/node/timers/$cloudflare", "clearImmediate"],
performance: ["perf_hooks", "performance"],
Performance: ["perf_hooks", "Performance"],
PerformanceEntry: ["perf_hooks", "PerformanceEntry"],
PerformanceMark: ["perf_hooks", "PerformanceMark"],
PerformanceMeasure: ["perf_hooks", "PerformanceMeasure"],
PerformanceObserver: ["perf_hooks", "PerformanceObserver"],
PerformanceObserverEntryList: [
"perf_hooks",
"PerformanceObserverEntryList",
],
PerformanceResourceTiming: ["perf_hooks", "PerformanceResourceTiming"],
},
polyfill: [],
external: cloudflareNodeCompatModules.flatMap((p) => [p, `node:${p}`]),
Expand Down
99 changes: 99 additions & 0 deletions src/runtime/node/perf_hooks/$cloudflare.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
import type nodePerfHooks from "node:perf_hooks";

export {
Performance,
PerformanceEntry,
PerformanceMark,
PerformanceMeasure,
PerformanceObserverEntryList,
PerformanceObserver,
PerformanceResourceTiming,
constants,
createHistogram,
monitorEventLoopDelay,
} from "./index";

import {
Performance,
PerformanceEntry,
PerformanceMark,
PerformanceMeasure,
PerformanceObserverEntryList,
PerformanceObserver,
PerformanceResourceTiming,
constants,
createHistogram,
monitorEventLoopDelay,
performance as unenvPerformance,
} from "./index";

// The following is an unusual way to access the original/unpatched globalThis.performance.
// This is needed to get hold of the real performance object before any of the unenv polyfills are
// applied via `inject` or `polyfill` config in presets.
//
// This code relies on the that rollup/esbuild/webpack don't evaluate string concatenation
// so they don't recognize the below as `globalThis.performance` which they would try to rewrite
// into unenv/runtime/node/perf_hooks, thus creating a circular dependency, and breaking this polyfill.
const workerdGlobalPerformance = (globalThis as any)[
"perf" + "ormance"
] as typeof nodePerfHooks.performance;

// reuse unenv's polyfill, but since preserve globalThis.performance identity
// we use `.bind(unenvPerformance)` here to preserve the `this` for all delegated method calls
export const performance = Object.assign(workerdGlobalPerformance, {
// @ts-expect-error undocumented public API
addEventListener: unenvPerformance.addEventListener.bind(unenvPerformance),
clearMarks: unenvPerformance.clearMarks.bind(unenvPerformance),
clearMeasures: unenvPerformance.clearMeasures.bind(unenvPerformance),
clearResourceTimings:
unenvPerformance.clearResourceTimings.bind(unenvPerformance),
// @ts-expect-error undocumented public API
dispatchEvent: unenvPerformance.dispatchEvent.bind(unenvPerformance),
eventLoopUtilization:
unenvPerformance.eventLoopUtilization.bind(unenvPerformance),
getEntries: unenvPerformance.getEntries.bind(unenvPerformance),
getEntriesByName: unenvPerformance.getEntriesByName.bind(unenvPerformance),
getEntriesByType: unenvPerformance.getEntriesByType.bind(unenvPerformance),
mark: unenvPerformance.mark.bind(unenvPerformance),
markResourceTiming:
unenvPerformance.markResourceTiming.bind(unenvPerformance),
measure: unenvPerformance.measure.bind(unenvPerformance),
nodeTiming: { ...unenvPerformance.nodeTiming },
onresourcetimingbufferfull:
// @ts-expect-error undocumented public API
typeof unenvPerformance.onresourcetimingbufferfull === "function"
? // @ts-expect-error undocumented public API
unenvPerformance.onresourcetimingbufferfull.bind(unenvPerformance)
: // @ts-expect-error undocumented public API
unenvPerformance.onresourcetimingbufferfull,
removeEventListener:
// @ts-expect-error undocumented public API
unenvPerformance.removeEventListener.bind(unenvPerformance),
setResourceTimingBufferSize:
unenvPerformance.setResourceTimingBufferSize.bind(unenvPerformance),
timerify: unenvPerformance.timerify.bind(unenvPerformance),
toJSON: unenvPerformance.toJSON.bind(unenvPerformance),
});

export default {
/**
* manually unroll unenv-polyfilled-symbols to make it tree-shakeable
*/
Performance,
PerformanceEntry,
PerformanceMark,
PerformanceMeasure,
// @ts-expect-error TODO: resolve type-mismatch between web and node PerformanceObserverEntryList
PerformanceObserverEntryList,
PerformanceObserver,
// @ts-expect-error TODO: resolve type-mismatch between web and node PerformanceObserverEntryList
PerformanceResourceTiming,
constants,
createHistogram,
monitorEventLoopDelay,

/**
* manually unroll workerd-polyfilled-symbols to make it tree-shakeable
*/
performance,
} satisfies typeof nodePerfHooks;
66 changes: 64 additions & 2 deletions src/runtime/node/perf_hooks/internal/constants.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,64 @@
import type perf_hooks from "node:perf_hooks";

export const constants: typeof perf_hooks.constants = {
// captured from Node.js v22.3.0 using
// Object.getOwnPropertyDescriptors(require('perf_hooks').constants)
const constants: typeof perf_hooks.constants = Object.create(null, {
NODE_PERFORMANCE_ENTRY_TYPE_GC: {
value: 0,
enumerable: false,
},
NODE_PERFORMANCE_ENTRY_TYPE_HTTP: {
value: 1,
enumerable: false,
},
NODE_PERFORMANCE_ENTRY_TYPE_HTTP2: {
value: 2,
enumerable: false,
},
NODE_PERFORMANCE_ENTRY_TYPE_NET: {
value: 3,
enumerable: false,
},
NODE_PERFORMANCE_ENTRY_TYPE_DNS: {
value: 4,
enumerable: false,
},
NODE_PERFORMANCE_MILESTONE_TIME_ORIGIN_TIMESTAMP: {
value: 0,
enumerable: false,
},
NODE_PERFORMANCE_MILESTONE_TIME_ORIGIN: {
value: 1,
enumerable: false,
},
NODE_PERFORMANCE_MILESTONE_ENVIRONMENT: {
value: 2,
enumerable: false,
},
NODE_PERFORMANCE_MILESTONE_NODE_START: {
value: 3,
enumerable: false,
},
NODE_PERFORMANCE_MILESTONE_V8_START: {
value: 4,
enumerable: false,
},
NODE_PERFORMANCE_MILESTONE_LOOP_START: {
value: 5,
enumerable: false,
},
NODE_PERFORMANCE_MILESTONE_LOOP_EXIT: {
value: 6,
enumerable: false,
},
NODE_PERFORMANCE_MILESTONE_BOOTSTRAP_COMPLETE: {
value: 7,
enumerable: false,
},
});

// add enumerable properties
Object.assign(constants, {
NODE_PERFORMANCE_GC_MAJOR: 4,
NODE_PERFORMANCE_GC_MINOR: 1,
NODE_PERFORMANCE_GC_INCREMENTAL: 8,
Expand All @@ -12,4 +70,8 @@ export const constants: typeof perf_hooks.constants = {
NODE_PERFORMANCE_GC_FLAGS_ALL_AVAILABLE_GARBAGE: 16,
NODE_PERFORMANCE_GC_FLAGS_ALL_EXTERNAL_MEMORY: 32,
NODE_PERFORMANCE_GC_FLAGS_SCHEDULE_IDLE: 64,
};
});

Object.freeze(constants);

export { constants };
33 changes: 30 additions & 3 deletions src/runtime/node/perf_hooks/internal/performance.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,24 @@ export {
PerformanceObserverEntryList,
} from "../../../web/performance/index";

// grabbed from Node.js v22.3.0 using:
// performance.nodeTiming
const nodeTiming = {
name: "node",
entryType: "node",
startTime: 0,
duration: 305_963.045_666,
nodeStart: 1.662_124_991_416_931_2,
v8Start: 44.762_125_015_258_79,
bootstrapComplete: 49.992_666_006_088_26,
environment: 46.754_665_970_802_31,
loopStart: 63.262_040_972_709_656,
loopExit: -1,
idleTime: 305_360.555_328,
// only present in Node.js 18.x
detail: undefined,
} satisfies Omit<perf_hooks.PerformanceNodeTiming, "toJSON">;

// Performance
export const Performance = class Performance
extends _Performance<perf_hooks.PerformanceEntry>
Expand All @@ -31,8 +49,11 @@ export const Performance = class Performance
throw createNotImplementedError("Performance.timerify");
}

get nodeTiming() {
return <perf_hooks.PerformanceNodeTiming>{};
get nodeTiming(): perf_hooks.PerformanceNodeTiming {
return {
...nodeTiming,
toJSON: () => nodeTiming,
};
}

eventLoopUtilization() {
Expand All @@ -59,8 +80,14 @@ export const Performance = class Performance
initiatorType: string,
global: object,
cacheMode: "" | "local",
bodyInfo: object,
responseStatus: number,
deliveryType?: string,
): perf_hooks.PerformanceResourceTiming {
throw createNotImplementedError("Performance.markResourceTiming");
// TODO: create a new PerformanceResourceTiming entry
// so that performance.getEntries, getEntriesByName, and getEntriesByType return it
// see: https://nodejs.org/api/perf_hooks.html#performancemarkresourcetimingtiminginfo-requestedurl-initiatortype-global-cachemode-bodyinfo-responsestatus-deliverytype
return new _PerformanceResourceTiming("");
}
};

Expand Down

0 comments on commit 43e5b0e

Please sign in to comment.