Skip to content

Commit

Permalink
fixup! perf_hooks: web performance timeline compliance
Browse files Browse the repository at this point in the history
  • Loading branch information
legendecas committed Jul 18, 2021
1 parent 72b9e2f commit 9b0d156
Show file tree
Hide file tree
Showing 5 changed files with 139 additions and 90 deletions.
19 changes: 11 additions & 8 deletions benchmark/perf_hooks/usertiming.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,24 +8,27 @@ const {
} = require('perf_hooks');

const bench = common.createBenchmark(main, {
n: [1e5]
n: [1e5],
observe: ['all', 'measure'],
});

function test() {
performance.mark('a');
setImmediate(() => {
performance.mark('b');
performance.measure('a to b', 'a', 'b');
});
performance.mark('b');
performance.measure('a to b', 'a', 'b');
}

function main({ n }) {
function main({ n, observe }) {
const entryTypes = observe === 'all' ?
[ 'mark', 'measure' ] :
[ observe ];
const obs = new PerformanceObserver(() => {
bench.end(n);
});
obs.observe({ entryTypes: ['measure'], buffered: true });
obs.observe({ entryTypes, buffered: true });

bench.start();
for (let i = 0; i < n; i++)
performance.mark('start');
for (let i = 0; i < 1e5; i++)
test();
}
124 changes: 96 additions & 28 deletions lib/internal/perf/observe.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ const {
ArrayPrototypePush,
ArrayPrototypeSlice,
ArrayPrototypeSort,
Error,
ObjectDefineProperties,
ObjectFreeze,
ObjectKeys,
Expand All @@ -32,6 +33,7 @@ const {
const {
InternalPerformanceEntry,
isPerformanceEntry,
kBufferNext,
} = require('internal/perf/performance_entry');

const {
Expand Down Expand Up @@ -83,12 +85,17 @@ const kSupportedEntryTypes = ObjectFreeze([
'mark',
'measure',
]);
const kTimelineEntryTypes = ObjectFreeze([
'mark',
'measure',
]);

const kPerformanceEntryBuffer = new SafeMap();
// Performance timeline entry Buffers
const markEntryBuffer = createBuffer();
const measureEntryBuffer = createBuffer();
const kMaxPerformanceEntryBuffers = 1e6;
const kClearPerformanceEntryBuffers = ObjectFreeze({
'mark': 'performance.clearMarks',
'measure': 'performance.clearMeasures',
});
const kWarnedEntryTypes = new SafeMap();

const kObservers = new SafeSet();
const kPending = new SafeSet();
let isPending = false;
Expand Down Expand Up @@ -238,7 +245,7 @@ class PerformanceObserver {
maybeIncrementObserverCount(type);
if (buffered) {
const entries = filterBufferMapByNameAndType(undefined, type);
this[kBuffer].push(...entries);
ArrayPrototypePush(this[kBuffer], ...entries);
kPending.add(this);
if (kPending.size)
queuePending();
Expand Down Expand Up @@ -307,50 +314,97 @@ function enqueue(entry) {
}

const entryType = entry.entryType;
if (!kTimelineEntryTypes.includes(entryType)) {
let buffer;
if (entryType === 'mark') {
buffer = markEntryBuffer;
} else if (entryType === 'measure') {
buffer = measureEntryBuffer;
} else {
return;
}
const buffer = getEntryBuffer(entryType);
buffer.push(entry);

const count = buffer.count + 1;
buffer.count = count;
if (count === 1) {
buffer.head = entry;
buffer.tail = entry;
return;
}
buffer.tail[kBufferNext] = entry;
buffer.tail = entry;

if (count > kMaxPerformanceEntryBuffers &&
!kWarnedEntryTypes.has(entryType)) {
kWarnedEntryTypes.set(entryType, true);
// No error code for this since it is a Warning
// eslint-disable-next-line no-restricted-syntax
const w = new Error('Possible perf_hooks memory leak detected. ' +
`${count} ${entryType} entries added to the global ` +
'performance entry buffer. Use ' +
`${kClearPerformanceEntryBuffers[entryType]} to ` +
'clear the buffer.');
w.name = 'MaxPerformanceEntryBufferExceededWarning';
w.entryType = entryType;
w.count = count;
process.emitWarning(w);
}
}

function clearEntriesFromBuffer(type, name) {
let buffer;
if (type === 'mark') {
buffer = markEntryBuffer;
} else if (type === 'measure') {
buffer = measureEntryBuffer;
} else {
return;
}
if (name === undefined) {
kPerformanceEntryBuffer.delete(type);
resetBuffer(buffer);
return;
}
let buffer = getEntryBuffer(type);
buffer = ArrayPrototypeFilter(
buffer,
(entry) => entry.name !== name);
kPerformanceEntryBuffer.set(type, buffer);
}

function getEntryBuffer(type) {
let buffer = kPerformanceEntryBuffer.get(type);
if (buffer === undefined) {
buffer = [];
kPerformanceEntryBuffer.set(type, buffer);
let head = null;
let tail = null;
for (let entry = buffer.head; entry !== null; entry = entry[kBufferNext]) {
if (entry.name !== name) {
head = head ?? entry;
tail = entry;
continue;
}
if (tail === null) {
continue;
}
tail[kBufferNext] = entry[kBufferNext];
}
return buffer;
buffer.head = head;
buffer.tail = tail;
}

function filterBufferMapByNameAndType(name, type) {
let bufferList;
if (type !== undefined) {
bufferList = kPerformanceEntryBuffer.get(type) ?? [];
if (type === 'mark') {
bufferList = [markEntryBuffer];
} else if (type === 'measure') {
bufferList = [measureEntryBuffer];
} else if (type !== undefined) {
// Unrecognized type;
return [];
} else {
bufferList = ArrayFrom(kPerformanceEntryBuffer.values());
bufferList = [markEntryBuffer, measureEntryBuffer];
}
return ArrayPrototypeFlatMap(bufferList,
(buffer) => filterBufferByName(buffer, name));
}

function filterBufferByName(buffer, name) {
if (name === undefined) {
return buffer;
const arr = [];
for (let entry = buffer.head; entry !== null; entry = entry[kBufferNext]) {
if (name === undefined || entry.name === name) {
ArrayPrototypePush(arr, entry);
}
}
return ArrayPrototypeFilter(buffer, (it) => it.name === name);
return arr;
}

function observerCallback(name, type, startTime, duration, details) {
Expand Down Expand Up @@ -398,6 +452,20 @@ function hasObserver(type) {
return observerCounts[observerType] > 0;
}

function createBuffer() {
return {
head: null,
tail: null,
count: 0,
};
}

function resetBuffer(buffer) {
buffer.head = null;
buffer.tail = null;
buffer.count = 0;
}

module.exports = {
PerformanceObserver,
enqueue,
Expand Down
3 changes: 3 additions & 0 deletions lib/internal/perf/performance_entry.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ const kType = Symbol('kType');
const kStart = Symbol('kStart');
const kDuration = Symbol('kDuration');
const kDetail = Symbol('kDetail');
const kBufferNext = Symbol('kBufferNext');

function isPerformanceEntry(obj) {
return obj?.[kName] !== undefined;
Expand Down Expand Up @@ -67,6 +68,7 @@ class InternalPerformanceEntry {
this[kStart] = start;
this[kDuration] = duration;
this[kDetail] = detail;
this[kBufferNext] = null;
}
}

Expand All @@ -79,4 +81,5 @@ module.exports = {
InternalPerformanceEntry,
PerformanceEntry,
isPerformanceEntry,
kBufferNext,
};
Loading

0 comments on commit 9b0d156

Please sign in to comment.