/
githubmirror
/
deno
Обзор
Документация
Войти
/
githubmirror
/
deno
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
main
ext/node/polyfills/perf_hooks.js
684 строки
18 KB
Kenta Moriuchi
chore: make prefer-primordials lint an internal plugin (#35978)
23 июл 2026, 11:53
Не верифицирован
23 июл 2026, 11:53
5b55d08
Код
Авторство
О чём код?
// Copyright 2018-2026 the Deno authors. MIT license. (function () { const { core, primordials } = __bootstrap; const { ArrayFrom, ArrayIsArray, ArrayPrototypeFilter, ArrayPrototypeIncludes, ArrayPrototypeIndexOf, ArrayPrototypePush, ArrayPrototypeSlice, ArrayPrototypeSplice, BigInt, BigIntPrototypeToString, Error, FunctionPrototypeApply, MapPrototypeGet, MapPrototypeSet, MathMax, MathRound, Number, NumberIsInteger, NumberMAX_SAFE_INTEGER, ObjectDefineProperty, ObjectFromEntries, ObjectPrototypeIsPrototypeOf, queueMicrotask, ReflectConstruct, SafeArrayIterator, SafeMap, Symbol, SymbolDispose, } = primordials; const { performance, PerformanceEntry, PerformanceObserver: WebPerformanceObserver, PerformanceObserverEntryList, } = core.loadExtScript("ext:deno_web/15_performance.js"); const { EldHistogram, BaseHistogram } = core.ops; const { ERR_ILLEGAL_CONSTRUCTOR, ERR_INVALID_ARG_TYPE, ERR_OUT_OF_RANGE } = core .loadExtScript( "ext:deno_node/internal/errors.ts", ); const { customInspectSymbol } = core.loadExtScript( "ext:deno_node/internal/util.mjs", ); const { inspect } = core.loadExtScript( "ext:deno_node/internal/util/inspect.mjs", ); const constants = { NODE_PERFORMANCE_ENTRY_TYPE_NODE: 0, NODE_PERFORMANCE_ENTRY_TYPE_MARK: 1, NODE_PERFORMANCE_ENTRY_TYPE_MEASURE: 2, NODE_PERFORMANCE_ENTRY_TYPE_GC: 3, NODE_PERFORMANCE_ENTRY_TYPE_FUNCTION: 4, NODE_PERFORMANCE_ENTRY_TYPE_HTTP2: 5, NODE_PERFORMANCE_ENTRY_TYPE_HTTP: 6, NODE_PERFORMANCE_ENTRY_TYPE_DNS: 7, NODE_PERFORMANCE_ENTRY_TYPE_NET: 8, }; // Entry types Node.js's PerformanceObserver supports beyond the web spec's // "mark"/"measure". The web layer's PerformanceObserver filters these out via // supportedEntryTypes, so this subclass tracks them in a parallel registry. const NODE_ENTRY_TYPES = ["http2", "function", "gc", "http", "dns", "net"]; const nodeObservers = []; const _nodeTypes = Symbol("[[nodeTypes]]"); const _nodeBuffer = Symbol("[[nodeBuffer]]"); const _nodeScheduled = Symbol("[[nodeScheduled]]"); const _nodeCallback = Symbol("[[nodeCallback]]"); function createNodeEntryList(entries) { return { getEntries() { return ArrayPrototypeSlice(entries); }, getEntriesByType(type) { return ArrayPrototypeFilter(entries, (e) => e.entryType === type); }, getEntriesByName(name, type) { return ArrayPrototypeFilter( entries, (e) => e.name === name && (type === undefined || e.entryType === type), ); }, }; } // Node-compatible PerformanceObserver that throws proper Node.js errors class PerformanceObserver extends WebPerformanceObserver { [_nodeTypes] = []; [_nodeBuffer] = []; [_nodeScheduled] = false; [_nodeCallback] = null; constructor(callback) { if (typeof callback !== "function") { throw new ERR_INVALID_ARG_TYPE("callback", "Function", callback); } super(callback); this[_nodeCallback] = callback; } static get supportedEntryTypes() { return [ ...new SafeArrayIterator(WebPerformanceObserver.supportedEntryTypes), ...new SafeArrayIterator(NODE_ENTRY_TYPES), ]; } observe(options) { if (typeof options !== "object" || options === null) { throw new ERR_INVALID_ARG_TYPE("options", "Object", options); } if ( options.entryTypes !== undefined && !ArrayIsArray(options.entryTypes) ) { throw new ERR_INVALID_ARG_TYPE( "options.entryTypes", "string[]", options.entryTypes, ); } const requestedTypes = options.entryTypes !== undefined ? options.entryTypes : (options.type !== undefined ? [options.type] : []); const webTypes = ArrayPrototypeFilter( requestedTypes, (t) => !ArrayPrototypeIncludes(NODE_ENTRY_TYPES, t), ); const nodeTypes = ArrayPrototypeFilter( requestedTypes, (t) => ArrayPrototypeIncludes(NODE_ENTRY_TYPES, t), ); if (webTypes.length > 0) { if (options.entryTypes !== undefined) { super.observe({ entryTypes: webTypes, buffered: options.buffered }); } else if (webTypes.length === 1) { super.observe({ type: webTypes[0], buffered: options.buffered }); } } if (nodeTypes.length > 0) { this[_nodeTypes] = nodeTypes; this[_nodeBuffer] = []; if (!ArrayPrototypeIncludes(nodeObservers, this)) { ArrayPrototypePush(nodeObservers, this); } } } disconnect() { super.disconnect(); const idx = ArrayPrototypeIndexOf(nodeObservers, this); if (idx !== -1) ArrayPrototypeSplice(nodeObservers, idx, 1); this[_nodeTypes] = []; this[_nodeBuffer] = []; } } // Internal helper used by node:http2 and other modules to dispatch // Node-only PerformanceObserver entries (e.g. `Http2Session`) that the web // PerformanceObserver does not understand. function enqueueNodePerformanceEntry(entry) { for (let i = 0; i < nodeObservers.length; i++) { const obs = nodeObservers[i]; if (!ArrayPrototypeIncludes(obs[_nodeTypes], entry.entryType)) continue; ArrayPrototypePush(obs[_nodeBuffer], entry); if (obs[_nodeScheduled]) continue; obs[_nodeScheduled] = true; queueMicrotask(() => { obs[_nodeScheduled] = false; const entries = obs[_nodeBuffer]; obs[_nodeBuffer] = []; if (entries.length === 0) return; const list = createNodeEntryList(entries); try { obs[_nodeCallback](list, obs); } catch (_e) { // Match web observer: callback errors should not crash dispatch. } }); } } function hasNodeObserverForType(entryType) { for (let i = 0; i < nodeObservers.length; i++) { if (ArrayPrototypeIncludes(nodeObservers[i][_nodeTypes], entryType)) { return true; } } return false; } const eventLoopUtilization = () => { // TODO(@marvinhagemeister): Return actual non-stubbed values return { idle: 0, active: 0, utilization: 0 }; }; performance.eventLoopUtilization = eventLoopUtilization; performance.nodeTiming = {}; function recordTimerifyHistogram(histogram, start) { const durationNs = MathMax(1, MathRound((performance.now() - start) * 1e6)); histogram.record(durationNs); } const timerify = (fn, options = { __proto__: null }) => { if (typeof fn !== "function") { throw new ERR_INVALID_ARG_TYPE("fn", "function", fn); } if ( options !== undefined && (typeof options !== "object" || options === null) ) { throw new ERR_INVALID_ARG_TYPE("options", "Object", options); } if (options?.histogram !== undefined) { if ( !ObjectPrototypeIsPrototypeOf( RecordableHistogram.prototype, options.histogram, ) ) { throw new ERR_INVALID_ARG_TYPE( "options.histogram", "RecordableHistogram", options.histogram, ); } } const histogram = options?.histogram; function timerified(...args) { // TODO(bartlomieju): emit PerformanceEntry with entryType 'function' const start = histogram === undefined ? 0 : performance.now(); let result; try { result = new.target ? ReflectConstruct(fn, args) : FunctionPrototypeApply(fn, this, args); } catch (err) { if (histogram !== undefined) { recordTimerifyHistogram(histogram, start); } throw err; } if ( histogram !== undefined && result !== null && (typeof result === "object" || typeof result === "function") && typeof result.then === "function" ) { // `result` is an arbitrary user thenable, not necessarily a native // Promise, so PromisePrototypeFinally cannot be used here. // deno-lint-ignore deno-internal/prefer-primordials return result.finally(() => recordTimerifyHistogram(histogram, start)); } if (histogram !== undefined) { recordTimerifyHistogram(histogram, start); } return result; } ObjectDefineProperty(timerified, "name", { __proto__: null, value: `timerified ${fn.name}`, configurable: true, }); ObjectDefineProperty(timerified, "length", { __proto__: null, value: fn.length, configurable: true, }); return timerified; }; performance.timerify = timerify; // TODO(bartlomieju): performance.markResourceTiming = () => {}; function monitorEventLoopDelay(options = { __proto__: null }) { const { resolution = 10 } = options; return new EventLoopDelayHistogram(new EldHistogram(resolution)); } const NUMBER_MAX_SAFE_INTEGER = NumberMAX_SAFE_INTEGER; const EMPTY_HISTOGRAM_MIN = 9223372036854776000; const EMPTY_HISTOGRAM_MIN_BIGINT = 9223372036854775807n; const BIGINT_MAX = 0x7FFFFFFFFFFFFFFFn; const _handle = Symbol("[[handle]]"); const _cloneId = Symbol("[[cloneId]]"); let nextHistogramCloneId = 1; const histogramCloneRegistry = new SafeMap(); function getHistogramCloneId(histogram) { if (histogram[_cloneId] === undefined) { histogram[_cloneId] = nextHistogramCloneId++; } MapPrototypeSet( histogramCloneRegistry, histogram[_cloneId], histogram[_handle], ); return histogram[_cloneId]; } // Public, JS-side Histogram class - `instanceof` works against both // `monitorEventLoopDelay()` results and `createHistogram()` results. class Histogram { constructor(handle) { // Intentionally not user-constructable. if (handle === undefined) { throw new ERR_ILLEGAL_CONSTRUCTOR(); } this[_handle] = handle; } get count() { return this[_handle].count; } get countBigInt() { return this[_handle].countBigInt; } get min() { if (this.max === 0) return EMPTY_HISTOGRAM_MIN; return this[_handle].min; } get minBigInt() { if (this.max === 0) return EMPTY_HISTOGRAM_MIN_BIGINT; return BigInt(this[_handle].minBigInt); } get max() { return this[_handle].max; } get maxBigInt() { return BigInt(this[_handle].maxBigInt); } get mean() { if (this.max === 0) return NaN; return this[_handle].mean; } get stddev() { if (this.max === 0) return NaN; return this[_handle].stddev; } get exceeds() { return this[_handle].exceeds ?? 0; } get exceedsBigInt() { return BigInt(this[_handle].exceedsBigInt ?? 0); } get percentiles() { // Real Map (not SafeMap): returned to userland and must pass // `instanceof Map` (SafeMap's prototype chain excludes Map). // deno-lint-ignore deno-internal/prefer-primordials const out = new Map(); if (typeof this[_handle].percentiles === "function") { const flat = this[_handle].percentiles(); for (let i = 0; i < flat.length; i += 2) { MapPrototypeSet(out, flat[i], flat[i + 1]); } } else if (this.count === 0) { MapPrototypeSet(out, 100, 0); } else { MapPrototypeSet(out, 0, this.min); for ( let percentile = 50; percentile < 100; percentile += (100 - percentile) / 2 ) { MapPrototypeSet(out, percentile, this.percentile(percentile)); if (percentile > 99.999) break; } MapPrototypeSet(out, 100, this.max); } return out; } get percentilesBigInt() { // Real Map (not SafeMap): returned to userland and must pass // `instanceof Map` (SafeMap's prototype chain excludes Map). // deno-lint-ignore deno-internal/prefer-primordials const out = new Map(); if (typeof this[_handle].percentilesBigInt === "function") { const flat = this[_handle].percentilesBigInt(); for (let i = 0; i < flat.length; i += 2) { MapPrototypeSet(out, flat[i], BigInt(flat[i + 1])); } } else if (this.count === 0) { MapPrototypeSet(out, 100, 0n); } else { MapPrototypeSet(out, 0, this.minBigInt); for ( let percentile = 50; percentile < 100; percentile += (100 - percentile) / 2 ) { MapPrototypeSet(out, percentile, this.percentileBigInt(percentile)); if (percentile > 99.999) break; } MapPrototypeSet(out, 100, this.maxBigInt); } return out; } percentile(p) { if (typeof p !== "number") { throw new ERR_INVALID_ARG_TYPE("percentile", "number", p); } if (!(p > 0 && p <= 100)) { throw new ERR_OUT_OF_RANGE("percentile", "> 0 && <= 100", p); } return this[_handle].percentile(p); } percentileBigInt(p) { if (typeof p !== "number") { throw new ERR_INVALID_ARG_TYPE("percentile", "number", p); } if (!(p > 0 && p <= 100)) { throw new ERR_OUT_OF_RANGE("percentile", "> 0 && <= 100", p); } return BigInt(this[_handle].percentileBigInt(p)); } reset() { this[_handle].reset(); } toJSON() { return { count: this.count, min: this.min, max: this.max, mean: this.mean, exceeds: this.exceeds, stddev: this.stddev, percentiles: ObjectFromEntries(this.percentiles), }; } [customInspectSymbol](depth, options) { if (depth < 0) { return this; } return `Histogram ${ inspect({ min: this.min, max: this.max, mean: this.mean, exceeds: this.exceeds, stddev: this.stddev, count: this.count, percentiles: this.percentiles, }, options) }`; } } class RecordableHistogram extends Histogram { constructor(handle, cloneId) { super(handle); this[_cloneId] = cloneId; } record(val) { if (typeof val === "bigint") { if (val < 1n || val > BIGINT_MAX) { throw new ERR_OUT_OF_RANGE("val", "a positive integer", val); } this[_handle].record(val); return; } if (typeof val !== "number" || !NumberIsInteger(val)) { throw new ERR_INVALID_ARG_TYPE("val", ["integer", "bigint"], val); } if (val < 1 || val > NUMBER_MAX_SAFE_INTEGER) { throw new ERR_OUT_OF_RANGE( "val", `>= 1 && <= ${NUMBER_MAX_SAFE_INTEGER}`, val, ); } this[_handle].record(BigInt(val)); } recordDelta() { this[_handle].recordDelta(); } add(other) { if (!ObjectPrototypeIsPrototypeOf(RecordableHistogram.prototype, other)) { throw new ERR_INVALID_ARG_TYPE( "other", "RecordableHistogram", other, ); } this[_handle].add(other[_handle]); } [core.hostObjectBrand]() { return { type: "RecordableHistogram", id: getHistogramCloneId(this), }; } } class EventLoopDelayHistogram extends Histogram { constructor(handle) { super(handle); } enable() { return this[_handle].enable(); } disable() { return this[_handle].disable(); } [SymbolDispose]() { this.disable(); } [core.hostObjectBrand]() { return { type: "EventLoopDelayHistogram", count: this.count, countBigInt: this.countBigInt, min: this.min, minBigInt: this.minBigInt, max: this.max, maxBigInt: this.maxBigInt, mean: this.mean, exceeds: this.exceeds, exceedsBigInt: this.exceedsBigInt, stddev: this.stddev, percentiles: ArrayFrom(this.percentiles), percentilesBigInt: ArrayFrom( this.percentilesBigInt, (entry) => [entry[0], BigIntPrototypeToString(entry[1])], ), }; } } function snapshotHistogram(data) { return new Histogram({ count: data.count, countBigInt: data.countBigInt, min: data.min, minBigInt: data.minBigInt, max: data.max, maxBigInt: data.maxBigInt, mean: data.mean, exceeds: data.exceeds ?? 0, exceedsBigInt: data.exceedsBigInt ?? 0n, stddev: data.stddev, percentiles: () => { const out = []; const entries = data.percentiles ?? [[100, 0]]; for (let i = 0; i < entries.length; i++) { ArrayPrototypePush(out, entries[i][0], entries[i][1]); } return out; }, percentilesBigInt: () => { const out = []; const entries = data.percentilesBigInt ?? [[100, "0"]]; for (let i = 0; i < entries.length; i++) { ArrayPrototypePush(out, entries[i][0], entries[i][1]); } return out; }, percentile: (p) => { const entries = data.percentiles ?? [[100, 0]]; for (let i = 0; i < entries.length; i++) { if (entries[i][0] >= p) return entries[i][1]; } return data.max; }, percentileBigInt: (p) => { const entries = data.percentilesBigInt ?? [[100, "0"]]; for (let i = 0; i < entries.length; i++) { if (entries[i][0] >= p) return BigInt(entries[i][1]); } return BigInt(data.maxBigInt); }, reset() {}, }); } // Registered eagerly from `02_register_cloneable.js`; impl stays lazy here. function deserializeRecordableHistogram(data) { const handle = MapPrototypeGet(histogramCloneRegistry, data.id); if (handle === undefined) { throw new Error("Unable to deserialize RecordableHistogram"); } return new RecordableHistogram(handle, data.id); } function validateInteger(value, name, min, max) { if (typeof value === "bigint") { if (value < BigInt(min) || value > BigInt(max)) { throw new ERR_OUT_OF_RANGE(name, `>= ${min} && <= ${max}`, value); } return Number(value); } if (typeof value !== "number" || !NumberIsInteger(value)) { throw new ERR_INVALID_ARG_TYPE(name, ["integer", "bigint"], value); } if (value < min || value > max) { throw new ERR_OUT_OF_RANGE(name, `>= ${min} && <= ${max}`, value); } return value; } function createHistogram(options = { __proto__: null }) { if (options === null || typeof options !== "object") { throw new ERR_INVALID_ARG_TYPE("options", "Object", options); } const { lowest = 1, highest = NUMBER_MAX_SAFE_INTEGER, figures = 3, } = options; const lo = validateInteger( lowest, "options.lowest", 1, NUMBER_MAX_SAFE_INTEGER, ); const hi = validateInteger( highest, "options.highest", 2 * lo, NUMBER_MAX_SAFE_INTEGER, ); if (typeof figures !== "number" || !NumberIsInteger(figures)) { throw new ERR_INVALID_ARG_TYPE("options.figures", "integer", figures); } if (figures < 1 || figures > 5) { throw new ERR_OUT_OF_RANGE("options.figures", ">= 1 && <= 5", figures); } const handle = new BaseHistogram(BigInt(lo), BigInt(hi), figures); return new RecordableHistogram(handle); } return { default: { performance, PerformanceObserver, PerformanceObserverEntryList, PerformanceEntry, monitorEventLoopDelay, createHistogram, eventLoopUtilization, timerify, constants, }, constants, createHistogram, deserializeRecordableHistogram, enqueueNodePerformanceEntry, eventLoopUtilization, hasNodeObserverForType, monitorEventLoopDelay, performance, PerformanceEntry, PerformanceObserver, PerformanceObserverEntryList, snapshotHistogram, timerify, }; })();