diff --git a/lib/internal/bootstrap/pre_execution.js b/lib/internal/bootstrap/pre_execution.js index 83ccfe90c11065..3b69844dc4ea0c 100644 --- a/lib/internal/bootstrap/pre_execution.js +++ b/lib/internal/bootstrap/pre_execution.js @@ -27,6 +27,7 @@ function prepareMainThreadExecution(expandArgv1 = false) { // Patch the process object with legacy properties and normalizations patchProcessObject(expandArgv1); setupTraceCategoryState(); + setupPerfHooks(); setupInspectorHooks(); setupWarningHandler(); @@ -222,6 +223,11 @@ function setupTraceCategoryState() { toggleTraceCategoryState(isTraceCategoryEnabled('node.async_hooks')); } +function setupPerfHooks() { + require('internal/perf/performance').refreshTimeOrigin(); + require('internal/perf/utils').refreshTimeOrigin(); +} + function setupInspectorHooks() { // If Debugger.setAsyncCallStackDepth is sent during bootstrap, // we cannot immediately call into JS to enable the hooks, which could @@ -474,6 +480,7 @@ module.exports = { setupCoverageHooks, setupWarningHandler, setupDebugEnv, + setupPerfHooks, prepareMainThreadExecution, initializeDeprecations, initializeESMLoader, diff --git a/lib/internal/http.js b/lib/internal/http.js index badfaa5c4a88d8..56187a2b1cc315 100644 --- a/lib/internal/http.js +++ b/lib/internal/http.js @@ -9,7 +9,7 @@ const { const { setUnrefTimeout } = require('internal/timers'); -const { InternalPerformanceEntry } = require('internal/perf/perf'); +const { InternalPerformanceEntry } = require('internal/perf/performance_entry'); const { enqueue, diff --git a/lib/internal/main/worker_thread.js b/lib/internal/main/worker_thread.js index e22fd17fa2d214..d6434ff96e1185 100644 --- a/lib/internal/main/worker_thread.js +++ b/lib/internal/main/worker_thread.js @@ -18,6 +18,7 @@ const { setupInspectorHooks, setupWarningHandler, setupDebugEnv, + setupPerfHooks, initializeDeprecations, initializeWASI, initializeCJSLoader, @@ -114,6 +115,7 @@ port.on('message', (message) => { } = message; setupTraceCategoryState(); + setupPerfHooks(); initializeReport(); if (manifestSrc) { require('internal/process/policy').setup(manifestSrc, manifestURL); diff --git a/lib/internal/perf/event_loop_utilization.js b/lib/internal/perf/event_loop_utilization.js index 398c4ad4e42f58..d73b2f5a831ab9 100644 --- a/lib/internal/perf/event_loop_utilization.js +++ b/lib/internal/perf/event_loop_utilization.js @@ -2,7 +2,7 @@ const nodeTiming = require('internal/perf/nodetiming'); -const { now } = require('internal/perf/perf'); +const { now } = require('internal/perf/utils'); function eventLoopUtilization(util1, util2) { const ls = nodeTiming.loopStart; diff --git a/lib/internal/perf/nodetiming.js b/lib/internal/perf/nodetiming.js index 5ff6dd38cd86d3..fcbd7efff49099 100644 --- a/lib/internal/perf/nodetiming.js +++ b/lib/internal/perf/nodetiming.js @@ -3,15 +3,14 @@ const { ObjectDefineProperties, ObjectSetPrototypeOf, - SafeArrayIterator, - SafeSet, } = primordials; +const { PerformanceEntry } = require('internal/perf/performance_entry'); + const { - PerformanceEntry, - kReadOnlyAttributes, now, -} = require('internal/perf/perf'); + getMilestoneTimestamp, +} = require('internal/perf/utils'); const { customInspectSymbol: kInspect, @@ -29,26 +28,8 @@ const { NODE_PERFORMANCE_MILESTONE_ENVIRONMENT }, loopIdleTime, - milestones, - timeOrigin, } = internalBinding('performance'); -function getMilestoneTimestamp(milestoneIdx) { - const ns = milestones[milestoneIdx]; - if (ns === -1) - return ns; - return ns / 1e6 - timeOrigin; -} - -const readOnlyAttributes = new SafeSet(new SafeArrayIterator([ - 'nodeStart', - 'v8Start', - 'environment', - 'loopStart', - 'loopExit', - 'bootstrapComplete', -])); - class PerformanceNodeTiming { constructor() { ObjectDefineProperties(this, { @@ -159,10 +140,6 @@ class PerformanceNodeTiming { idleTime: this.idleTime, }; } - - static get [kReadOnlyAttributes]() { - return readOnlyAttributes; - } } ObjectSetPrototypeOf( diff --git a/lib/internal/perf/observe.js b/lib/internal/perf/observe.js index c96925c723f64e..8ec8512434510b 100644 --- a/lib/internal/perf/observe.js +++ b/lib/internal/perf/observe.js @@ -31,7 +31,7 @@ const { const { InternalPerformanceEntry, isPerformanceEntry, -} = require('internal/perf/perf'); +} = require('internal/perf/performance_entry'); const { codes: { @@ -174,11 +174,13 @@ class PerformanceObserverEntryList { } class PerformanceObserver { - [kBuffer] = []; - [kEntryTypes] = new SafeSet(); - [kType] = undefined; - constructor(callback) { + // TODO(joyeecheung): V8 snapshot does not support instance member + // initializers for now: + // https://bugs.chromium.org/p/v8/issues/detail?id=10704 + this[kBuffer] = []; + this[kEntryTypes] = new SafeSet(); + this[kType] = undefined; validateCallback(callback); this[kCallback] = callback; } diff --git a/lib/internal/perf/performance.js b/lib/internal/perf/performance.js new file mode 100644 index 00000000000000..ca4aed90e4e270 --- /dev/null +++ b/lib/internal/perf/performance.js @@ -0,0 +1,129 @@ +'use strict'; + +const { + ObjectDefineProperty, + ObjectDefineProperties, + ObjectSetPrototypeOf, + TypeError, +} = primordials; + +const { + EventTarget, +} = require('internal/event_target'); + +const { now } = require('internal/perf/utils'); + +const { + mark, + measure, + clearMarks, +} = require('internal/perf/usertiming'); + +const eventLoopUtilization = require('internal/perf/event_loop_utilization'); +const nodeTiming = require('internal/perf/nodetiming'); +const timerify = require('internal/perf/timerify'); +const { customInspectSymbol: kInspect } = require('internal/util'); +const { inspect } = require('util'); + +const { + getTimeOriginTimestamp +} = internalBinding('performance'); + +class Performance extends EventTarget { + constructor() { + // eslint-disable-next-line no-restricted-syntax + throw new TypeError('Illegal constructor'); + } + + [kInspect](depth, options) { + if (depth < 0) return this; + + const opts = { + ...options, + depth: options.depth == null ? null : options.depth - 1 + }; + + return `Performance ${inspect({ + nodeTiming: this.nodeTiming, + timeOrigin: this.timeOrigin, + }, opts)}`; + } + +} + +function toJSON() { + return { + nodeTiming: this.nodeTiming, + timeOrigin: this.timeOrigin, + eventLoopUtilization: this.eventLoopUtilization() + }; +} + +class InternalPerformance extends EventTarget {} +InternalPerformance.prototype.constructor = Performance.prototype.constructor; +ObjectSetPrototypeOf(InternalPerformance.prototype, Performance.prototype); + +ObjectDefineProperties(Performance.prototype, { + clearMarks: { + configurable: true, + enumerable: false, + value: clearMarks, + }, + eventLoopUtilization: { + configurable: true, + enumerable: false, + value: eventLoopUtilization, + }, + mark: { + configurable: true, + enumerable: false, + value: mark, + }, + measure: { + configurable: true, + enumerable: false, + value: measure, + }, + nodeTiming: { + configurable: true, + enumerable: false, + value: nodeTiming, + }, + now: { + configurable: true, + enumerable: false, + value: now, + }, + timerify: { + configurable: true, + enumerable: false, + value: timerify, + }, + // This would be updated during pre-execution in case + // the process is launched from a snapshot. + // TODO(joyeecheung): we may want to warn about access to + // this during snapshot building. + timeOrigin: { + configurable: true, + enumerable: true, + value: getTimeOriginTimestamp(), + }, + toJSON: { + configurable: true, + enumerable: true, + value: toJSON, + } +}); + +function refreshTimeOrigin() { + ObjectDefineProperty(Performance.prototype, 'timeOrigin', { + configurable: true, + enumerable: true, + value: getTimeOriginTimestamp(), + }); +} + +module.exports = { + InternalPerformance, + refreshTimeOrigin +}; diff --git a/lib/internal/perf/perf.js b/lib/internal/perf/performance_entry.js similarity index 87% rename from lib/internal/perf/perf.js rename to lib/internal/perf/performance_entry.js index d049d3c68fff04..f9f1c9e8966e2d 100644 --- a/lib/internal/perf/perf.js +++ b/lib/internal/perf/performance_entry.js @@ -6,10 +6,6 @@ const { TypeError, } = primordials; -const { - timeOrigin, -} = internalBinding('performance'); - const { customInspectSymbol: kInspect, } = require('internal/util'); @@ -21,12 +17,6 @@ const kType = Symbol('kType'); const kStart = Symbol('kStart'); const kDuration = Symbol('kDuration'); const kDetail = Symbol('kDetail'); -const kReadOnlyAttributes = Symbol('kReadOnlyAttributes'); - -function now() { - const hr = process.hrtime(); - return (hr[0] * 1000 + hr[1] / 1e6) - timeOrigin; -} function isPerformanceEntry(obj) { return obj?.[kName] !== undefined; @@ -88,7 +78,5 @@ ObjectSetPrototypeOf( module.exports = { InternalPerformanceEntry, PerformanceEntry, - kReadOnlyAttributes, isPerformanceEntry, - now, }; diff --git a/lib/internal/perf/timerify.js b/lib/internal/perf/timerify.js index d730f62aae7eb1..dae0b06bf80c8a 100644 --- a/lib/internal/perf/timerify.js +++ b/lib/internal/perf/timerify.js @@ -9,10 +9,8 @@ const { Symbol, } = primordials; -const { - InternalPerformanceEntry, - now, -} = require('internal/perf/perf'); +const { InternalPerformanceEntry } = require('internal/perf/performance_entry'); +const { now } = require('internal/perf/utils'); const { validateFunction, diff --git a/lib/internal/perf/usertiming.js b/lib/internal/perf/usertiming.js index 6672a3f4dfdeb0..f83091de1919a8 100644 --- a/lib/internal/perf/usertiming.js +++ b/lib/internal/perf/usertiming.js @@ -3,16 +3,13 @@ const { ObjectKeys, SafeMap, + SafeSet, + SafeArrayIterator, } = primordials; -const { - InternalPerformanceEntry, - kReadOnlyAttributes, - now, -} = require('internal/perf/perf'); - +const { InternalPerformanceEntry } = require('internal/perf/performance_entry'); +const { now } = require('internal/perf/utils'); const { enqueue } = require('internal/perf/observe'); - const nodeTiming = require('internal/perf/nodetiming'); const { @@ -31,8 +28,15 @@ const { } = require('internal/errors'); const marks = new SafeMap(); -const nodeTimingReadOnlyAttributes = - nodeTiming.constructor[kReadOnlyAttributes]; + +const nodeTimingReadOnlyAttributes = new SafeSet(new SafeArrayIterator([ + 'nodeStart', + 'v8Start', + 'environment', + 'loopStart', + 'loopExit', + 'bootstrapComplete', +])); function getMark(name) { if (name === undefined) return; diff --git a/lib/internal/perf/utils.js b/lib/internal/perf/utils.js new file mode 100644 index 00000000000000..bcc7e223b8c882 --- /dev/null +++ b/lib/internal/perf/utils.js @@ -0,0 +1,33 @@ +'use strict'; + +const binding = internalBinding('performance'); +const { + milestones, + getTimeOrigin, +} = binding; + +// TODO(joyeecheung): we may want to warn about access to +// this during snapshot building. +let timeOrigin = getTimeOrigin(); + +function now() { + const hr = process.hrtime(); + return (hr[0] * 1000 + hr[1] / 1e6) - timeOrigin; +} + +function getMilestoneTimestamp(milestoneIdx) { + const ns = milestones[milestoneIdx]; + if (ns === -1) + return ns; + return ns / 1e6 - timeOrigin; +} + +function refreshTimeOrigin() { + timeOrigin = getTimeOrigin(); +} + +module.exports = { + now, + getMilestoneTimestamp, + refreshTimeOrigin +}; diff --git a/lib/internal/worker.js b/lib/internal/worker.js index f2414ebeec4aae..931bce0c518fc3 100644 --- a/lib/internal/worker.js +++ b/lib/internal/worker.js @@ -28,7 +28,7 @@ const { const EventEmitter = require('events'); const assert = require('internal/assert'); const path = require('path'); -const { timeOrigin } = internalBinding('performance'); +const { now } = require('internal/perf/utils'); const errorCodes = require('internal/errors').codes; const { @@ -504,12 +504,6 @@ function eventLoopUtilization(util1, util2) { return { idle: idle_delta, active: active_delta, utilization }; } -// Duplicate code from performance.now() so don't need to require perf_hooks. -function now() { - const hr = process.hrtime(); - return (hr[0] * 1000 + hr[1] / 1e6) - timeOrigin; -} - module.exports = { ownsProcessState, isMainThread, diff --git a/lib/perf_hooks.js b/lib/perf_hooks.js index a92a040f2de839..339d3ca4ff0ab4 100644 --- a/lib/perf_hooks.js +++ b/lib/perf_hooks.js @@ -1,126 +1,23 @@ 'use strict'; const { - ObjectDefineProperties, ObjectDefineProperty, - ObjectSetPrototypeOf, - TypeError, } = primordials; const { - timeOriginTimestamp, constants, } = internalBinding('performance'); -const { - EventTarget, -} = require('internal/event_target'); - -const { - PerformanceEntry, - now, -} = require('internal/perf/perf'); +const { PerformanceEntry } = require('internal/perf/performance_entry'); const { PerformanceObserver } = require('internal/perf/observe'); - -const { - PerformanceMark, - mark, - measure, - clearMarks, -} = require('internal/perf/usertiming'); +const { PerformanceMark } = require('internal/perf/usertiming'); +const { InternalPerformance } = require('internal/perf/performance'); const { createHistogram } = require('internal/histogram'); -const eventLoopUtilization = require('internal/perf/event_loop_utilization'); const monitorEventLoopDelay = require('internal/perf/event_loop_delay'); -const nodeTiming = require('internal/perf/nodetiming'); -const timerify = require('internal/perf/timerify'); -const { customInspectSymbol: kInspect } = require('internal/util'); -const { inspect } = require('util'); - -class Performance extends EventTarget { - constructor() { - // eslint-disable-next-line no-restricted-syntax - throw new TypeError('Illegal constructor'); - } - - [kInspect](depth, options) { - if (depth < 0) return this; - - const opts = { - ...options, - depth: options.depth == null ? null : options.depth - 1 - }; - - return `Performance ${inspect({ - nodeTiming: this.nodeTiming, - timeOrigin: this.timeOrigin, - }, opts)}`; - } - -} - -function toJSON() { - return { - nodeTiming: this.nodeTiming, - timeOrigin: this.timeOrigin, - eventLoopUtilization: this.eventLoopUtilization() - }; -} - -class InternalPerformance extends EventTarget {} -InternalPerformance.prototype.constructor = Performance.prototype.constructor; -ObjectSetPrototypeOf(InternalPerformance.prototype, Performance.prototype); - -ObjectDefineProperties(Performance.prototype, { - clearMarks: { - configurable: true, - enumerable: false, - value: clearMarks, - }, - eventLoopUtilization: { - configurable: true, - enumerable: false, - value: eventLoopUtilization, - }, - mark: { - configurable: true, - enumerable: false, - value: mark, - }, - measure: { - configurable: true, - enumerable: false, - value: measure, - }, - nodeTiming: { - configurable: true, - enumerable: false, - value: nodeTiming, - }, - now: { - configurable: true, - enumerable: false, - value: now, - }, - timerify: { - configurable: true, - enumerable: false, - value: timerify, - }, - timeOrigin: { - configurable: true, - enumerable: true, - value: timeOriginTimestamp, - }, - toJSON: { - configurable: true, - enumerable: true, - value: toJSON, - } -}); module.exports = { PerformanceEntry, diff --git a/src/node_perf.cc b/src/node_perf.cc index 8c5778ecb10bb0..41c93ea1640e5e 100644 --- a/src/node_perf.cc +++ b/src/node_perf.cc @@ -274,6 +274,15 @@ void ELDHistogram::OnInterval() { "stddev", histogram()->Stddev()); } +void GetTimeOrigin(const FunctionCallbackInfo& args) { + args.GetReturnValue().Set(Number::New(args.GetIsolate(), timeOrigin / 1e6)); +} + +void GetTimeOriginTimeStamp(const FunctionCallbackInfo& args) { + args.GetReturnValue().Set( + Number::New(args.GetIsolate(), timeOriginTimestamp / MICROS_PER_MILLIS)); +} + void Initialize(Local target, Local unused, Local context, @@ -308,6 +317,8 @@ void Initialize(Local target, RemoveGarbageCollectionTracking); env->SetMethod(target, "notify", Notify); env->SetMethod(target, "loopIdleTime", LoopIdleTime); + env->SetMethod(target, "getTimeOrigin", GetTimeOrigin); + env->SetMethod(target, "getTimeOriginTimestamp", GetTimeOriginTimeStamp); Local constants = Object::New(isolate); @@ -344,17 +355,6 @@ void Initialize(Local target, PropertyAttribute attr = static_cast(ReadOnly | DontDelete); - target->DefineOwnProperty(context, - FIXED_ONE_BYTE_STRING(isolate, "timeOrigin"), - Number::New(isolate, timeOrigin / 1e6), - attr).ToChecked(); - - target->DefineOwnProperty( - context, - FIXED_ONE_BYTE_STRING(isolate, "timeOriginTimestamp"), - Number::New(isolate, timeOriginTimestamp / MICROS_PER_MILLIS), - attr).ToChecked(); - target->DefineOwnProperty(context, env->constants_string(), constants, diff --git a/test/parallel/test-bootstrap-modules.js b/test/parallel/test-bootstrap-modules.js index f18f85728d36e3..5cf9b8df2a38e6 100644 --- a/test/parallel/test-bootstrap-modules.js +++ b/test/parallel/test-bootstrap-modules.js @@ -23,6 +23,7 @@ const expectedModules = new Set([ 'Internal Binding module_wrap', 'Internal Binding native_module', 'Internal Binding options', + 'Internal Binding performance', 'Internal Binding process_methods', 'Internal Binding report', 'Internal Binding serdes', @@ -60,6 +61,7 @@ const expectedModules = new Set([ 'NativeModule internal/fs/rimraf', 'NativeModule internal/fs/watchers', 'NativeModule internal/heap_utils', + 'NativeModule internal/histogram', 'NativeModule internal/idna', 'NativeModule internal/linkedlist', 'NativeModule internal/modules/run_main', @@ -77,6 +79,14 @@ const expectedModules = new Set([ 'NativeModule internal/modules/esm/translators', 'NativeModule internal/process/esm_loader', 'NativeModule internal/options', + 'NativeModule internal/perf/event_loop_utilization', + 'NativeModule internal/perf/nodetiming', + 'NativeModule internal/perf/observe', + 'NativeModule internal/perf/performance', + 'NativeModule internal/perf/performance_entry', + 'NativeModule internal/perf/timerify', + 'NativeModule internal/perf/usertiming', + 'NativeModule internal/perf/utils', 'NativeModule internal/priority_queue', 'NativeModule internal/process/execution', 'NativeModule internal/process/per_thread',