From b7a307f0ed007299d4e39ab94ee4b098054938c7 Mon Sep 17 00:00:00 2001 From: Xuguang Mei Date: Mon, 21 Feb 2022 06:57:31 +0800 Subject: [PATCH] perf_hooks: use arrays to store EntryBuffers Also order entries by startTime when calling getEntriesByType. Fix: https://github.com/nodejs/node/issues/42004 Fix: https://github.com/nodejs/node/issues/42024 PR-URL: https://github.com/nodejs/node/pull/42032 Fixes: https://github.com/nodejs/node/issues/42004 Fixes: https://github.com/nodejs/node/issues/42024 Reviewed-By: Benjamin Gruenbaum Reviewed-By: Antoine du Hamel --- lib/internal/perf/observe.js | 95 ++++++--------------- lib/internal/perf/performance_entry.js | 3 - test/parallel/test-performance-timeline.mjs | 35 ++++++++ 3 files changed, 59 insertions(+), 74 deletions(-) create mode 100644 test/parallel/test-performance-timeline.mjs diff --git a/lib/internal/perf/observe.js b/lib/internal/perf/observe.js index eadc617452e952..37c389e1497fe4 100644 --- a/lib/internal/perf/observe.js +++ b/lib/internal/perf/observe.js @@ -4,12 +4,12 @@ const { ArrayFrom, ArrayIsArray, ArrayPrototypeFilter, - ArrayPrototypeFlatMap, ArrayPrototypeIncludes, ArrayPrototypePush, ArrayPrototypePushApply, ArrayPrototypeSlice, ArrayPrototypeSort, + ArrayPrototypeConcat, Error, ObjectDefineProperties, ObjectFreeze, @@ -34,7 +34,6 @@ const { const { InternalPerformanceEntry, isPerformanceEntry, - kBufferNext, } = require('internal/perf/performance_entry'); const { @@ -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', @@ -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; }); } @@ -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)) { @@ -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) { @@ -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, diff --git a/lib/internal/perf/performance_entry.js b/lib/internal/perf/performance_entry.js index d8eedb9fb8f85b..759f1315d5b6c2 100644 --- a/lib/internal/perf/performance_entry.js +++ b/lib/internal/perf/performance_entry.js @@ -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; @@ -72,7 +71,6 @@ class InternalPerformanceEntry { this[kStart] = start; this[kDuration] = duration; this[kDetail] = detail; - this[kBufferNext] = null; } } @@ -85,5 +83,4 @@ module.exports = { InternalPerformanceEntry, PerformanceEntry, isPerformanceEntry, - kBufferNext, }; diff --git a/test/parallel/test-performance-timeline.mjs b/test/parallel/test-performance-timeline.mjs new file mode 100644 index 00000000000000..18ef762ffd8d3b --- /dev/null +++ b/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']);