Skip to content

Commit

Permalink
perf_hooks: use arrays to store EntryBuffers
Browse files Browse the repository at this point in the history
Also order entries by startTime when calling getEntriesByType.

Fix: #42004
Fix: #42024

PR-URL: #42032
Fixes: #42004
Fixes: #42024
Reviewed-By: Benjamin Gruenbaum <benjamingr@gmail.com>
Reviewed-By: Antoine du Hamel <duhamelantoine1995@gmail.com>
  • Loading branch information
meixg authored and danielleadams committed Apr 24, 2022
1 parent c47b436 commit b7a307f
Show file tree
Hide file tree
Showing 3 changed files with 59 additions and 74 deletions.
95 changes: 24 additions & 71 deletions lib/internal/perf/observe.js
Expand Up @@ -4,12 +4,12 @@ const {
ArrayFrom,
ArrayIsArray,
ArrayPrototypeFilter,
ArrayPrototypeFlatMap,
ArrayPrototypeIncludes,
ArrayPrototypePush,
ArrayPrototypePushApply,
ArrayPrototypeSlice,
ArrayPrototypeSort,
ArrayPrototypeConcat,
Error,
ObjectDefineProperties,
ObjectFreeze,
Expand All @@ -34,7 +34,6 @@ const {
const {
InternalPerformanceEntry,
isPerformanceEntry,
kBufferNext,
} = require('internal/perf/performance_entry');

const {
Expand Down Expand Up @@ -89,8 +88,8 @@ const kSupportedEntryTypes = ObjectFreeze([
]);

// Performance timeline entry Buffers
const markEntryBuffer = createBuffer();
const measureEntryBuffer = createBuffer();
let markEntryBuffer = [];
let measureEntryBuffer = [];
const kMaxPerformanceEntryBuffers = 1e6;
const kClearPerformanceEntryBuffers = ObjectFreeze({
'mark': 'performance.clearMarks',
Expand Down Expand Up @@ -154,9 +153,7 @@ function maybeIncrementObserverCount(type) {
class PerformanceObserverEntryList {
constructor(entries) {
this[kBuffer] = ArrayPrototypeSort(entries, (first, second) => {
if (first.startTime < second.startTime) return -1;
if (first.startTime > second.startTime) return 1;
return 0;
return first.startTime - second.startTime;
});
}

Expand Down Expand Up @@ -344,15 +341,8 @@ function enqueue(entry) {
return;
}

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;
ArrayPrototypePush(buffer, entry);
const count = buffer.length;

if (count > kMaxPerformanceEntryBuffers &&
!kWarnedEntryTypes.has(entryType)) {
Expand All @@ -372,63 +362,40 @@ function enqueue(entry) {
}

function clearEntriesFromBuffer(type, name) {
let buffer;
if (type === 'mark') {
buffer = markEntryBuffer;
} else if (type === 'measure') {
buffer = measureEntryBuffer;
} else {
return;
}
if (name === undefined) {
resetBuffer(buffer);
if (type !== 'mark' && type !== 'measure') {
return;
}

let head = null;
let tail = null;
let count = 0;
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];
count++;
if (type === 'mark') {
markEntryBuffer = name === undefined ?
[] : ArrayPrototypeFilter(markEntryBuffer, (entry) => entry.name !== name);
} else {
measureEntryBuffer = name === undefined ?
[] : ArrayPrototypeFilter(measureEntryBuffer, (entry) => entry.name !== name);
}
buffer.head = head;
buffer.tail = tail;
buffer.count = count;
}

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

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

return ArrayPrototypeSort(bufferList, (first, second) => {
return first.startTime - second.startTime;
});
}

function observerCallback(name, type, startTime, duration, details) {
Expand Down Expand Up @@ -476,20 +443,6 @@ 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,
PerformanceObserverEntryList,
Expand Down
3 changes: 0 additions & 3 deletions lib/internal/perf/performance_entry.js
Expand Up @@ -22,7 +22,6 @@ 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 @@ -72,7 +71,6 @@ class InternalPerformanceEntry {
this[kStart] = start;
this[kDuration] = duration;
this[kDetail] = detail;
this[kBufferNext] = null;
}
}

Expand All @@ -85,5 +83,4 @@ module.exports = {
InternalPerformanceEntry,
PerformanceEntry,
isPerformanceEntry,
kBufferNext,
};
35 changes: 35 additions & 0 deletions test/parallel/test-performance-timeline.mjs
@@ -0,0 +1,35 @@
// This file may needs to be updated to wpt:
// https://github.com/web-platform-tests/wpt

import '../common/index.mjs';
import assert from 'assert';

import { performance } from 'perf_hooks';
import { setTimeout } from 'timers/promises';

// Order by startTime
performance.mark('one');
await setTimeout(50);
performance.mark('two');
await setTimeout(50);
performance.mark('three');
await setTimeout(50);
performance.measure('three', 'three');
await setTimeout(50);
performance.measure('two', 'two');
await setTimeout(50);
performance.measure('one', 'one');
const entries = performance.getEntriesByType('measure');
assert.deepStrictEqual(entries.map((x) => x.name), ['one', 'two', 'three']);
const allEntries = performance.getEntries();
assert.deepStrictEqual(allEntries.map((x) => x.name), ['one', 'one', 'two', 'two', 'three', 'three']);

performance.mark('a');
await setTimeout(50);
performance.measure('a', 'a');
await setTimeout(50);
performance.mark('a');
await setTimeout(50);
performance.measure('a', 'one');
const entriesByName = performance.getEntriesByName('a');
assert.deepStrictEqual(entriesByName.map((x) => x.entryType), ['measure', 'mark', 'measure', 'mark']);

0 comments on commit b7a307f

Please sign in to comment.