Skip to content

Commit 2ec6995

Browse files
committed
perf_hooks: simplify perf_hooks
Remove the `performance.getEntries()` and `performance.clear*()` variants and eliminate the accumulation of the global timeline entries. The design of this particular bit of the API is a memory leak and performance footgun. The `PerformanceObserver` API is a better approach to consuming the data in a more transient way. PR-URL: #19563 Reviewed-By: Anna Henningsen <anna@addaleax.net> Reviewed-By: Matteo Collina <matteo.collina@gmail.com>
1 parent d54f651 commit 2ec6995

File tree

9 files changed

+38
-320
lines changed

9 files changed

+38
-320
lines changed

doc/api/perf_hooks.md

Lines changed: 8 additions & 98 deletions
Original file line numberDiff line numberDiff line change
@@ -10,14 +10,18 @@ is to support collection of high resolution performance metrics.
1010
This is the same Performance API as implemented in modern Web browsers.
1111

1212
```js
13-
const { performance } = require('perf_hooks');
13+
const { PerformanceObserver, performance } = require('perf_hooks');
14+
15+
const obs = new PerformanceObserver((items) => {
16+
console.log(items.getEntries()[0].duration);
17+
performance.clearMarks();
18+
});
19+
obs.observe({ entryTypes: ['measure'] });
20+
1421
performance.mark('A');
1522
doSomeLongRunningProcess(() => {
1623
performance.mark('B');
1724
performance.measure('A to B', 'A', 'B');
18-
const measure = performance.getEntriesByName('A to B')[0];
19-
console.log(measure.duration);
20-
// Prints the number of milliseconds between Mark 'A' and Mark 'B'
2125
});
2226
```
2327

@@ -26,35 +30,6 @@ doSomeLongRunningProcess(() => {
2630
added: v8.5.0
2731
-->
2832

29-
The `Performance` provides access to performance metric data. A single
30-
instance of this class is provided via the `performance` property.
31-
32-
### performance.clearEntries(name)
33-
<!-- YAML
34-
added: v9.5.0
35-
-->
36-
37-
Remove all performance entry objects with `entryType` equal to `name` from the
38-
Performance Timeline.
39-
40-
### performance.clearFunctions([name])
41-
<!-- YAML
42-
added: v8.5.0
43-
-->
44-
45-
* `name` {string}
46-
47-
If `name` is not provided, removes all `PerformanceFunction` objects from the
48-
Performance Timeline. If `name` is provided, removes entries with `name`.
49-
50-
### performance.clearGC()
51-
<!-- YAML
52-
added: v8.5.0
53-
-->
54-
55-
Remove all performance entry objects with `entryType` equal to `gc` from the
56-
Performance Timeline.
57-
5833
### performance.clearMarks([name])
5934
<!-- YAML
6035
added: v8.5.0
@@ -65,53 +40,6 @@ added: v8.5.0
6540
If `name` is not provided, removes all `PerformanceMark` objects from the
6641
Performance Timeline. If `name` is provided, removes only the named mark.
6742

68-
### performance.clearMeasures([name])
69-
<!-- YAML
70-
added: v8.5.0
71-
-->
72-
73-
* `name` {string}
74-
75-
If `name` is not provided, removes all `PerformanceMeasure` objects from the
76-
Performance Timeline. If `name` is provided, removes only objects whose
77-
`performanceEntry.name` matches `name`.
78-
79-
### performance.getEntries()
80-
<!-- YAML
81-
added: v8.5.0
82-
-->
83-
84-
* Returns: {Array}
85-
86-
Returns a list of all `PerformanceEntry` objects in chronological order
87-
with respect to `performanceEntry.startTime`.
88-
89-
### performance.getEntriesByName(name[, type])
90-
<!-- YAML
91-
added: v8.5.0
92-
-->
93-
94-
* `name` {string}
95-
* `type` {string}
96-
* Returns: {Array}
97-
98-
Returns a list of all `PerformanceEntry` objects in chronological order
99-
with respect to `performanceEntry.startTime` whose `performanceEntry.name` is
100-
equal to `name`, and optionally, whose `performanceEntry.entryType` is equal to
101-
`type`.
102-
103-
### performance.getEntriesByType(type)
104-
<!-- YAML
105-
added: v8.5.0
106-
-->
107-
108-
* `type` {string}
109-
* Returns: {Array}
110-
111-
Returns a list of all `PerformanceEntry` objects in chronological order
112-
with respect to `performanceEntry.startTime` whose `performanceEntry.entryType`
113-
is equal to `type`.
114-
11543
### performance.mark([name])
11644
<!-- YAML
11745
added: v8.5.0
@@ -125,20 +53,6 @@ Creates a new `PerformanceMark` entry in the Performance Timeline. A
12553
`performanceEntry.duration` is always `0`. Performance marks are used
12654
to mark specific significant moments in the Performance Timeline.
12755

128-
### performance.maxEntries
129-
<!-- YAML
130-
added: v9.6.0
131-
-->
132-
133-
Value: {number}
134-
135-
The maximum number of Performance Entry items that should be added to the
136-
Performance Timeline. This limit is not strictly enforced, but a process
137-
warning will be emitted if the number of entries in the timeline exceeds
138-
this limit.
139-
140-
Defaults to 150.
141-
14256
### performance.measure(name, startMark, endMark)
14357
<!-- YAML
14458
added: v8.5.0
@@ -220,7 +134,6 @@ const wrapped = performance.timerify(someFunction);
220134
const obs = new PerformanceObserver((list) => {
221135
console.log(list.getEntries()[0].duration);
222136
obs.disconnect();
223-
performance.clearFunctions();
224137
});
225138
obs.observe({ entryTypes: ['function'] });
226139

@@ -608,7 +521,6 @@ hook.enable();
608521
const obs = new PerformanceObserver((list, observer) => {
609522
console.log(list.getEntries()[0]);
610523
performance.clearMarks();
611-
performance.clearMeasures();
612524
observer.disconnect();
613525
});
614526
obs.observe({ entryTypes: ['measure'], buffered: true });
@@ -642,8 +554,6 @@ const obs = new PerformanceObserver((list) => {
642554
console.log(`require('${entry[0]}')`, entry.duration);
643555
});
644556
obs.disconnect();
645-
// Free memory
646-
performance.clearFunctions();
647557
});
648558
obs.observe({ entryTypes: ['function'], buffered: true });
649559

lib/perf_hooks.js

Lines changed: 7 additions & 94 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
const {
44
PerformanceEntry,
55
mark: _mark,
6+
clearMark: _clearMark,
67
measure: _measure,
78
milestones,
89
observerCounts,
@@ -50,17 +51,11 @@ const kBuffering = Symbol('buffering');
5051
const kQueued = Symbol('queued');
5152
const kTimerified = Symbol('timerified');
5253
const kInsertEntry = Symbol('insert-entry');
53-
const kIndexEntry = Symbol('index-entry');
54-
const kClearEntry = Symbol('clear-entry');
5554
const kGetEntries = Symbol('get-entries');
5655
const kIndex = Symbol('index');
5756
const kMarks = Symbol('marks');
5857
const kCount = Symbol('count');
59-
const kMaxCount = Symbol('max-count');
60-
const kDefaultMaxCount = 150;
6158

62-
observerCounts[NODE_PERFORMANCE_ENTRY_TYPE_MARK] = 1;
63-
observerCounts[NODE_PERFORMANCE_ENTRY_TYPE_MEASURE] = 1;
6459
const observers = {};
6560
const observerableTypes = [
6661
'node',
@@ -286,17 +281,12 @@ class PerformanceObserverEntryList {
286281
const item = { entry };
287282
L.append(this[kEntries], item);
288283
this[kCount]++;
289-
this[kIndexEntry](item);
290284
}
291285

292286
get length() {
293287
return this[kCount];
294288
}
295289

296-
[kIndexEntry](entry) {
297-
// Default implementation does nothing
298-
}
299-
300290
[kGetEntries](name, type) {
301291
const ret = [];
302292
const list = this[kEntries];
@@ -407,72 +397,11 @@ class PerformanceObserver extends AsyncResource {
407397
}
408398
}
409399

410-
class Performance extends PerformanceObserverEntryList {
400+
class Performance {
411401
constructor() {
412-
super();
413402
this[kIndex] = {
414403
[kMarks]: new Set()
415404
};
416-
this[kMaxCount] = kDefaultMaxCount;
417-
this[kInsertEntry](nodeTiming);
418-
}
419-
420-
set maxEntries(val) {
421-
if (typeof val !== 'number' || val >>> 0 !== val) {
422-
const errors = lazyErrors();
423-
throw new errors.ERR_INVALID_ARG_TYPE('val', 'number', val);
424-
}
425-
this[kMaxCount] = Math.max(1, val >>> 0);
426-
}
427-
428-
get maxEntries() {
429-
return this[kMaxCount];
430-
}
431-
432-
[kIndexEntry](item) {
433-
const index = this[kIndex];
434-
const type = item.entry.entryType;
435-
let items = index[type];
436-
if (!items) {
437-
items = index[type] = {};
438-
L.init(items);
439-
}
440-
const entry = item.entry;
441-
L.append(items, { entry, item });
442-
const count = this[kCount];
443-
if (count > this[kMaxCount]) {
444-
const text = count === 1 ? 'is 1 entry' : `are ${count} entries`;
445-
process.emitWarning('Possible perf_hooks memory leak detected. ' +
446-
`There ${text} in the ` +
447-
'Performance Timeline. Use the clear methods ' +
448-
'to remove entries that are no longer needed or ' +
449-
'set performance.maxEntries equal to a higher ' +
450-
'value (currently the maxEntries is ' +
451-
`${this[kMaxCount]}).`);
452-
}
453-
}
454-
455-
[kClearEntry](type, name) {
456-
const index = this[kIndex];
457-
const items = index[type];
458-
if (!items) return;
459-
let item = L.peek(items);
460-
while (item && item !== items) {
461-
const entry = item.entry;
462-
const next = item._idlePrev;
463-
if (name !== undefined) {
464-
if (entry.name === `${name}`) {
465-
L.remove(item); // remove from the index
466-
L.remove(item.item); // remove from the master
467-
this[kCount]--;
468-
}
469-
} else {
470-
L.remove(item); // remove from the index
471-
L.remove(item.item); // remove from the master
472-
this[kCount]--;
473-
}
474-
item = next;
475-
}
476405
}
477406

478407
get nodeTiming() {
@@ -507,27 +436,13 @@ class Performance extends PerformanceObserverEntryList {
507436

508437
clearMarks(name) {
509438
name = name !== undefined ? `${name}` : name;
510-
this[kClearEntry]('mark', name);
511-
if (name !== undefined)
439+
if (name !== undefined) {
512440
this[kIndex][kMarks].delete(name);
513-
else
441+
_clearMark(name);
442+
} else {
514443
this[kIndex][kMarks].clear();
515-
}
516-
517-
clearMeasures(name) {
518-
this[kClearEntry]('measure', name);
519-
}
520-
521-
clearGC() {
522-
this[kClearEntry]('gc');
523-
}
524-
525-
clearFunctions(name) {
526-
this[kClearEntry]('function', name);
527-
}
528-
529-
clearEntries(name) {
530-
this[kClearEntry](name);
444+
_clearMark();
445+
}
531446
}
532447

533448
timerify(fn) {
@@ -563,7 +478,6 @@ class Performance extends PerformanceObserverEntryList {
563478

564479
[kInspect]() {
565480
return {
566-
maxEntries: this.maxEntries,
567481
nodeTiming: this.nodeTiming,
568482
timeOrigin: this.timeOrigin
569483
};
@@ -595,7 +509,6 @@ function observersCallback(entry) {
595509
if (type === NODE_PERFORMANCE_ENTRY_TYPE_HTTP2)
596510
collectHttp2Stats(entry);
597511

598-
performance[kInsertEntry](entry);
599512
const list = getObserversList(type);
600513

601514
let current = L.peek(list);

src/node_perf.cc

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -130,7 +130,7 @@ void PerformanceEntry::Notify(Environment* env,
130130
object.As<Object>(),
131131
env->performance_entry_callback(),
132132
1, &object,
133-
node::async_context{0, 0}).ToLocalChecked();
133+
node::async_context{0, 0});
134134
}
135135
}
136136

@@ -153,6 +153,17 @@ void Mark(const FunctionCallbackInfo<Value>& args) {
153153
args.GetReturnValue().Set(obj);
154154
}
155155

156+
void ClearMark(const FunctionCallbackInfo<Value>& args) {
157+
Environment* env = Environment::GetCurrent(args);
158+
auto marks = env->performance_marks();
159+
160+
if (args.Length() == 0) {
161+
marks->clear();
162+
} else {
163+
Utf8Value name(env->isolate(), args[0]);
164+
marks->erase(*name);
165+
}
166+
}
156167

157168
inline uint64_t GetPerformanceMark(Environment* env, std::string name) {
158169
auto marks = env->performance_marks();
@@ -395,6 +406,7 @@ void Initialize(Local<Object> target,
395406
target->Set(context, performanceEntryString, fn).FromJust();
396407
env->set_performance_entry_template(fn);
397408

409+
env->SetMethod(target, "clearMark", ClearMark);
398410
env->SetMethod(target, "mark", Mark);
399411
env->SetMethod(target, "measure", Measure);
400412
env->SetMethod(target, "markMilestone", MarkMilestone);

test/parallel/test-http2-perf_hooks.js

Lines changed: 1 addition & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ if (!common.hasCrypto)
66
const assert = require('assert');
77
const h2 = require('http2');
88

9-
const { PerformanceObserver, performance } = require('perf_hooks');
9+
const { PerformanceObserver } = require('perf_hooks');
1010

1111
const obs = new PerformanceObserver(common.mustCall((items) => {
1212
const entry = items.getEntries()[0];
@@ -46,7 +46,6 @@ const obs = new PerformanceObserver(common.mustCall((items) => {
4646
default:
4747
assert.fail('invalid entry name');
4848
}
49-
performance.clearEntries('http2');
5049
}, 4));
5150
obs.observe({ entryTypes: ['http2'] });
5251

@@ -101,10 +100,3 @@ server.on('listening', common.mustCall(() => {
101100
}));
102101

103102
}));
104-
105-
process.on('exit', () => {
106-
const entries = performance.getEntries();
107-
// There shouldn't be any http2 entries left over.
108-
assert.strictEqual(entries.length, 1);
109-
assert.strictEqual(entries[0], performance.nodeTiming);
110-
});

0 commit comments

Comments
 (0)