Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

bootstrap: include perf_hooks in the builtin snapshot #38971

Closed
wants to merge 3 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 7 additions & 16 deletions lib/internal/bootstrap/node.js
Original file line number Diff line number Diff line change
Expand Up @@ -241,10 +241,9 @@ if (!config.noBrowserGlobals) {

defineOperation(globalThis, 'queueMicrotask', queueMicrotask);

defineLazyGlobal(globalThis, 'performance', () => {
const { performance } = require('perf_hooks');
return performance;
});
// https://www.w3.org/TR/hr-time-2/#the-performance-attribute
defineReplacableAttribute(globalThis, 'performance',
require('perf_hooks').performance);

// Non-standard extensions:
defineOperation(globalThis, 'clearImmediate', timers.clearImmediate);
Expand Down Expand Up @@ -494,20 +493,12 @@ function defineOperation(target, name, method) {
});
}

function defineLazyGlobal(target, name, loader) {
let value;
let overridden = false;
// https://heycam.github.io/webidl/#Replaceable
function defineReplacableAttribute(target, name, value) {
ObjectDefineProperty(target, name, {
writable: true,
enumerable: true,
configurable: true,
get() {
if (value === undefined && !overridden)
value = loader();
return value;
},
set(val) {
value = val;
overridden = true;
}
value,
});
}
7 changes: 7 additions & 0 deletions lib/internal/bootstrap/pre_execution.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ function prepareMainThreadExecution(expandArgv1 = false) {
// Patch the process object with legacy properties and normalizations
patchProcessObject(expandArgv1);
setupTraceCategoryState();
setupPerfHooks();
setupInspectorHooks();
setupWarningHandler();

Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -474,6 +480,7 @@ module.exports = {
setupCoverageHooks,
setupWarningHandler,
setupDebugEnv,
setupPerfHooks,
prepareMainThreadExecution,
initializeDeprecations,
initializeESMLoader,
Expand Down
10 changes: 2 additions & 8 deletions lib/internal/event_target.js
Original file line number Diff line number Diff line change
Expand Up @@ -59,13 +59,7 @@ const kRemoveListener = Symbol('kRemoveListener');
const kIsNodeStyleListener = Symbol('kIsNodeStyleListener');
const kTrustEvent = Symbol('kTrustEvent');

// Lazy load perf_hooks to avoid the additional overhead on startup
let perf_hooks;
function lazyNow() {
if (perf_hooks === undefined)
perf_hooks = require('perf_hooks');
return perf_hooks.performance.now();
}
const { now } = require('internal/perf/utils');

// TODO(joyeecheung): V8 snapshot does not support instance member
// initializers for now:
Expand Down Expand Up @@ -98,7 +92,7 @@ class Event {
this[kComposed] = !!composed;
this[kType] = `${type}`;
this[kDefaultPrevented] = false;
this[kTimestamp] = lazyNow();
this[kTimestamp] = now();
this[kPropagationStopped] = false;
if (options?.[kTrustEvent]) {
isTrustedSet.add(this);
Expand Down
2 changes: 1 addition & 1 deletion lib/internal/http.js
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
2 changes: 2 additions & 0 deletions lib/internal/main/worker_thread.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ const {
setupInspectorHooks,
setupWarningHandler,
setupDebugEnv,
setupPerfHooks,
initializeDeprecations,
initializeWASI,
initializeCJSLoader,
Expand Down Expand Up @@ -114,6 +115,7 @@ port.on('message', (message) => {
} = message;

setupTraceCategoryState();
setupPerfHooks();
initializeReport();
if (manifestSrc) {
require('internal/process/policy').setup(manifestSrc, manifestURL);
Expand Down
2 changes: 1 addition & 1 deletion lib/internal/perf/event_loop_utilization.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
31 changes: 4 additions & 27 deletions lib/internal/perf/nodetiming.js
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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, {
Expand Down Expand Up @@ -159,10 +140,6 @@ class PerformanceNodeTiming {
idleTime: this.idleTime,
};
}

static get [kReadOnlyAttributes]() {
return readOnlyAttributes;
}
}

ObjectSetPrototypeOf(
Expand Down
12 changes: 7 additions & 5 deletions lib/internal/perf/observe.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ const {
const {
InternalPerformanceEntry,
isPerformanceEntry,
} = require('internal/perf/perf');
} = require('internal/perf/performance_entry');

const {
codes: {
Expand Down Expand Up @@ -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;
}
Expand Down
129 changes: 129 additions & 0 deletions lib/internal/perf/performance.js
Original file line number Diff line number Diff line change
@@ -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
};
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,6 @@ const {
TypeError,
} = primordials;

const {
timeOrigin,
} = internalBinding('performance');

const {
customInspectSymbol: kInspect,
} = require('internal/util');
Expand All @@ -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;
Expand Down Expand Up @@ -88,7 +78,5 @@ ObjectSetPrototypeOf(
module.exports = {
InternalPerformanceEntry,
PerformanceEntry,
kReadOnlyAttributes,
isPerformanceEntry,
now,
};
6 changes: 2 additions & 4 deletions lib/internal/perf/timerify.js
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
Loading