@memlab/core 1.1.21 → 1.1.23
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/__tests__/lib/TestUtils.d.ts +13 -0
- package/dist/__tests__/lib/TestUtils.js +39 -0
- package/dist/lib/Config.d.ts +10 -1
- package/dist/lib/Config.js +21 -2
- package/dist/lib/Constant.js +1 -0
- package/dist/lib/FileManager.js +1 -1
- package/dist/lib/HeapAnalyzer.js +49 -27
- package/dist/lib/RunInfoUtils.d.ts +1 -0
- package/dist/lib/RunInfoUtils.js +8 -4
- package/dist/lib/SerializationHelper.d.ts +18 -0
- package/dist/lib/SerializationHelper.js +36 -0
- package/dist/lib/Serializer.d.ts +2 -6
- package/dist/lib/Serializer.js +12 -3
- package/dist/lib/TraceSampler.d.ts +36 -0
- package/dist/lib/TraceSampler.js +78 -0
- package/dist/lib/Types.d.ts +26 -3
- package/dist/lib/Utils.d.ts +8 -0
- package/dist/lib/Utils.js +74 -6
- package/dist/lib/charts/MemoryBarChart.js +2 -2
- package/dist/lib/heap-data/HeapStringNode.js +25 -16
- package/dist/lib/leak-filters/LeakFilterRuleList.js +2 -0
- package/dist/lib/leak-filters/rules/FilterDetachedDOMElement.rule.d.ts +0 -1
- package/dist/lib/leak-filters/rules/FilterDetachedDOMElement.rule.js +22 -9
- package/dist/lib/leak-filters/rules/FilterOverSizedNodeAsLeak.rule.js +50 -0
- package/dist/lib/leak-filters/rules/FilterUnmountedFiberNode.rule.js +4 -3
- package/dist/lib/leak-filters/rules/FilterXMLHTTPRequest.rule.d.ts +20 -0
- package/dist/lib/leak-filters/rules/FilterXMLHTTPRequest.rule.js +30 -0
- package/dist/lib/trace-filters/TraceFilterRuleList.js +2 -0
- package/dist/lib/trace-filters/rules/FilterCppRootsToDetachedDOMTrace.rule.d.ts +15 -0
- package/dist/lib/trace-filters/rules/FilterCppRootsToDetachedDOMTrace.rule.js +44 -0
- package/dist/paths/TraceFinder.js +1 -1
- package/dist/trace-cluster/TraceBucket.d.ts +1 -1
- package/dist/trace-cluster/TraceBucket.js +54 -22
- package/package.json +1 -1
package/dist/lib/Utils.d.ts
CHANGED
|
@@ -28,6 +28,7 @@ declare function isDOMNodeIncomplete(node: IHeapNode): boolean;
|
|
|
28
28
|
declare function isXMLDocumentNode(node: IHeapNode): boolean;
|
|
29
29
|
declare function isHTMLDocumentNode(node: IHeapNode): boolean;
|
|
30
30
|
declare function isDOMTextNode(node: IHeapNode): boolean;
|
|
31
|
+
declare function isCppRootsNode(node: IHeapNode): boolean;
|
|
31
32
|
declare function isRootNode(node: IHeapNode, opt?: AnyOptions): boolean;
|
|
32
33
|
declare function isDirectPropEdge(edge: IHeapEdge): boolean;
|
|
33
34
|
declare function isReturnEdge(edge: IHeapEdge): boolean;
|
|
@@ -48,6 +49,7 @@ declare function isAlternateNode(node: IHeapNode): boolean;
|
|
|
48
49
|
declare function setIsRegularFiberNode(node: IHeapNode): void;
|
|
49
50
|
declare function isRegularFiberNode(node: IHeapNode): boolean;
|
|
50
51
|
declare function hasHostRoot(node: IHeapNode): boolean;
|
|
52
|
+
declare function markDetachedFiberNode(node: IHeapNode): boolean;
|
|
51
53
|
declare type IterateNodeCallback = (node: IHeapNode, snapshot: IHeapSnapshot) => boolean;
|
|
52
54
|
declare function filterNodesInPlace(idSet: Set<number>, snapshot: IHeapSnapshot, cb: IterateNodeCallback): void;
|
|
53
55
|
declare function applyToNodes(idSet: Set<number>, snapshot: IHeapSnapshot, cb: (node: IHeapNode, snapshot: IHeapSnapshot) => void, options?: AnyOptions): void;
|
|
@@ -75,6 +77,8 @@ declare function isObjectNode(node: IHeapNode): boolean;
|
|
|
75
77
|
declare function isPlainJSObjectNode(node: IHeapNode): boolean;
|
|
76
78
|
declare function pathHasDetachedHTMLNode(path: LeakTracePathItem): boolean;
|
|
77
79
|
declare function pathHasEdgeWithIndex(path: LeakTracePathItem, idx: number): boolean;
|
|
80
|
+
declare function pathHasEdgeWithName(path: LeakTracePathItem, edgeName: string): boolean;
|
|
81
|
+
declare function pathHasNodeOrEdgeWithName(path: LeakTracePathItem, name: Optional<string>): boolean;
|
|
78
82
|
declare function getLastNodeId(path: LeakTracePathItem): number;
|
|
79
83
|
declare function getReadablePercent(num: number): string;
|
|
80
84
|
declare function getReadableBytes(bytes: Optional<number>): string;
|
|
@@ -183,6 +187,7 @@ declare const _default: {
|
|
|
183
187
|
hasReactEdges: typeof hasReactEdges;
|
|
184
188
|
isAlternateNode: typeof isAlternateNode;
|
|
185
189
|
isBlinkRootNode: typeof isBlinkRootNode;
|
|
190
|
+
isCppRootsNode: typeof isCppRootsNode;
|
|
186
191
|
isDOMInternalNode: typeof isDOMInternalNode;
|
|
187
192
|
isDOMNodeIncomplete: typeof isDOMNodeIncomplete;
|
|
188
193
|
isDOMTextNode: typeof isDOMTextNode;
|
|
@@ -225,11 +230,14 @@ declare const _default: {
|
|
|
225
230
|
mapToObject: typeof mapToObject;
|
|
226
231
|
markAllDetachedFiberNode: typeof markAllDetachedFiberNode;
|
|
227
232
|
markAlternateFiberNode: typeof markAlternateFiberNode;
|
|
233
|
+
markDetachedFiberNode: typeof markDetachedFiberNode;
|
|
228
234
|
memCache: Record<string, any>;
|
|
229
235
|
normalizeBaseUrl: typeof normalizeBaseUrl;
|
|
230
236
|
objectToMap: typeof objectToMap;
|
|
231
237
|
pathHasDetachedHTMLNode: typeof pathHasDetachedHTMLNode;
|
|
232
238
|
pathHasEdgeWithIndex: typeof pathHasEdgeWithIndex;
|
|
239
|
+
pathHasEdgeWithName: typeof pathHasEdgeWithName;
|
|
240
|
+
pathHasNodeOrEdgeWithName: typeof pathHasNodeOrEdgeWithName;
|
|
233
241
|
repeat: typeof repeat;
|
|
234
242
|
resolveFilePath: typeof resolveFilePath;
|
|
235
243
|
resolveSnapshotFilePath: typeof resolveSnapshotFilePath;
|
package/dist/lib/Utils.js
CHANGED
|
@@ -126,7 +126,7 @@ function isFiberNode(node) {
|
|
|
126
126
|
return name === 'FiberNode' || name === 'Detached FiberNode';
|
|
127
127
|
}
|
|
128
128
|
// quickly check the detachedness field
|
|
129
|
-
// need to call
|
|
129
|
+
// need to call markDetachedFiberNode(node) before this function
|
|
130
130
|
// does not traverse and check the existance of HostRoot
|
|
131
131
|
// NOTE: Doesn't work for FiberNode without detachedness field
|
|
132
132
|
function isDetachedFiberNode(node) {
|
|
@@ -271,6 +271,10 @@ function isHTMLDocumentNode(node) {
|
|
|
271
271
|
function isDOMTextNode(node) {
|
|
272
272
|
return node.type === 'native' && node.name === 'Text';
|
|
273
273
|
}
|
|
274
|
+
// check if this is a [C++ roots] (synthetic) node
|
|
275
|
+
function isCppRootsNode(node) {
|
|
276
|
+
return node.name === 'C++ roots' && node.type === 'synthetic';
|
|
277
|
+
}
|
|
274
278
|
function isRootNode(node, opt = {}) {
|
|
275
279
|
if (!node) {
|
|
276
280
|
return false;
|
|
@@ -493,13 +497,42 @@ function hasHostRoot(node) {
|
|
|
493
497
|
}
|
|
494
498
|
cur = getReactFiberNode(cur, 'return');
|
|
495
499
|
}
|
|
500
|
+
return false;
|
|
501
|
+
}
|
|
502
|
+
// The Fiber tree starts with a special type of Fiber node (HostRoot).
|
|
503
|
+
// return true if the node is a mounted Fiber node
|
|
504
|
+
function markDetachedFiberNode(node) {
|
|
505
|
+
if (node && node.is_detached) {
|
|
506
|
+
return false;
|
|
507
|
+
}
|
|
508
|
+
let cur = node;
|
|
509
|
+
const visitedIds = new Set();
|
|
510
|
+
const visitedNodes = new Set();
|
|
511
|
+
while (cur && isFiberNode(cur)) {
|
|
512
|
+
if (cur.id == null || visitedIds.has(cur.id)) {
|
|
513
|
+
break;
|
|
514
|
+
}
|
|
515
|
+
visitedNodes.add(cur);
|
|
516
|
+
// if a Fiber node whose dominator is neither root nor
|
|
517
|
+
// another Fiber node, then consider it as detached Fiber node
|
|
518
|
+
if (cur.dominatorNode && cur.dominatorNode.id !== 1) {
|
|
519
|
+
if (!isFiberNode(cur.dominatorNode)) {
|
|
520
|
+
cur.markAsDetached();
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
visitedIds.add(cur.id);
|
|
524
|
+
if (isHostRoot(cur)) {
|
|
525
|
+
return true;
|
|
526
|
+
}
|
|
527
|
+
cur = getReactFiberNode(cur, 'return');
|
|
528
|
+
}
|
|
496
529
|
for (const visitedNode of visitedNodes) {
|
|
497
530
|
visitedNode.markAsDetached();
|
|
498
531
|
}
|
|
499
532
|
return false;
|
|
500
533
|
}
|
|
501
534
|
function filterNodesInPlace(idSet, snapshot, cb) {
|
|
502
|
-
const ids = Array.from(idSet
|
|
535
|
+
const ids = Array.from(idSet);
|
|
503
536
|
for (const id of ids) {
|
|
504
537
|
const node = snapshot.getNodeById(id);
|
|
505
538
|
if (node && !cb(node, snapshot)) {
|
|
@@ -508,7 +541,7 @@ function filterNodesInPlace(idSet, snapshot, cb) {
|
|
|
508
541
|
}
|
|
509
542
|
}
|
|
510
543
|
function applyToNodes(idSet, snapshot, cb, options = {}) {
|
|
511
|
-
let ids = Array.from(idSet
|
|
544
|
+
let ids = Array.from(idSet);
|
|
512
545
|
if (options.shuffle) {
|
|
513
546
|
ids.sort(() => Math.random() - 0.5);
|
|
514
547
|
}
|
|
@@ -951,6 +984,33 @@ function pathHasEdgeWithIndex(path, idx) {
|
|
|
951
984
|
}
|
|
952
985
|
return false;
|
|
953
986
|
}
|
|
987
|
+
function pathHasEdgeWithName(path, edgeName) {
|
|
988
|
+
let p = path;
|
|
989
|
+
while (p) {
|
|
990
|
+
if (p.edge && p.edge.name_or_index === edgeName) {
|
|
991
|
+
return true;
|
|
992
|
+
}
|
|
993
|
+
p = p.next;
|
|
994
|
+
}
|
|
995
|
+
return false;
|
|
996
|
+
}
|
|
997
|
+
function pathHasNodeOrEdgeWithName(path, name) {
|
|
998
|
+
if (name == null) {
|
|
999
|
+
return true;
|
|
1000
|
+
}
|
|
1001
|
+
name = name.toLowerCase();
|
|
1002
|
+
let p = path;
|
|
1003
|
+
while (p) {
|
|
1004
|
+
if (p.edge && `${p.edge.name_or_index}`.toLowerCase().includes(name)) {
|
|
1005
|
+
return true;
|
|
1006
|
+
}
|
|
1007
|
+
if (p.node && `${p.node.name}`.toLowerCase().includes(name)) {
|
|
1008
|
+
return true;
|
|
1009
|
+
}
|
|
1010
|
+
p = p.next;
|
|
1011
|
+
}
|
|
1012
|
+
return false;
|
|
1013
|
+
}
|
|
954
1014
|
function getLastNodeId(path) {
|
|
955
1015
|
if (!path) {
|
|
956
1016
|
return -1;
|
|
@@ -1502,8 +1562,9 @@ function dumpSnapshot(file, snapshot) {
|
|
|
1502
1562
|
function markAllDetachedFiberNode(snapshot) {
|
|
1503
1563
|
Console_1.default.overwrite('marking all detached Fiber nodes...');
|
|
1504
1564
|
snapshot.nodes.forEach(node => {
|
|
1505
|
-
|
|
1506
|
-
|
|
1565
|
+
if (isFiberNode(node)) {
|
|
1566
|
+
markDetachedFiberNode(node);
|
|
1567
|
+
}
|
|
1507
1568
|
});
|
|
1508
1569
|
}
|
|
1509
1570
|
function markAlternateFiberNode(snapshot) {
|
|
@@ -1694,11 +1755,14 @@ function runShell(command, options = {}) {
|
|
|
1694
1755
|
execOptions.shell = '/bin/bash';
|
|
1695
1756
|
}
|
|
1696
1757
|
let ret = null;
|
|
1758
|
+
if (Config_1.default.verbose || Config_1.default.isContinuousTest) {
|
|
1759
|
+
Console_1.default.lowLevel(`running shell command: ${command}`);
|
|
1760
|
+
}
|
|
1697
1761
|
try {
|
|
1698
1762
|
ret = child_process_1.default.execSync(command, execOptions);
|
|
1699
1763
|
}
|
|
1700
1764
|
catch (ex) {
|
|
1701
|
-
if (Config_1.default.verbose) {
|
|
1765
|
+
if (Config_1.default.verbose || Config_1.default.isContinuousTest) {
|
|
1702
1766
|
if (ex instanceof Error) {
|
|
1703
1767
|
Console_1.default.lowLevel(ex.message);
|
|
1704
1768
|
Console_1.default.lowLevel((_c = ex.stack) !== null && _c !== void 0 ? _c : '');
|
|
@@ -1814,6 +1878,7 @@ exports.default = {
|
|
|
1814
1878
|
hasReactEdges,
|
|
1815
1879
|
isAlternateNode,
|
|
1816
1880
|
isBlinkRootNode,
|
|
1881
|
+
isCppRootsNode,
|
|
1817
1882
|
isDOMInternalNode,
|
|
1818
1883
|
isDOMNodeIncomplete,
|
|
1819
1884
|
isDOMTextNode,
|
|
@@ -1856,11 +1921,14 @@ exports.default = {
|
|
|
1856
1921
|
mapToObject,
|
|
1857
1922
|
markAllDetachedFiberNode,
|
|
1858
1923
|
markAlternateFiberNode,
|
|
1924
|
+
markDetachedFiberNode,
|
|
1859
1925
|
memCache,
|
|
1860
1926
|
normalizeBaseUrl,
|
|
1861
1927
|
objectToMap,
|
|
1862
1928
|
pathHasDetachedHTMLNode,
|
|
1863
1929
|
pathHasEdgeWithIndex,
|
|
1930
|
+
pathHasEdgeWithName,
|
|
1931
|
+
pathHasNodeOrEdgeWithName,
|
|
1864
1932
|
repeat,
|
|
1865
1933
|
resolveFilePath,
|
|
1866
1934
|
resolveSnapshotFilePath,
|
|
@@ -97,7 +97,7 @@ class MemoryBarChart {
|
|
|
97
97
|
}
|
|
98
98
|
loadPlotData(options = {}) {
|
|
99
99
|
// plot data for a single run
|
|
100
|
-
if (!options.controlWorkDirs && !options.
|
|
100
|
+
if (!options.controlWorkDirs && !options.treatmentWorkDirs) {
|
|
101
101
|
return this.loadPlotDataFromWorkDir(options);
|
|
102
102
|
}
|
|
103
103
|
// plot data for control and test run
|
|
@@ -105,7 +105,7 @@ class MemoryBarChart {
|
|
|
105
105
|
workDir: options.controlWorkDirs && options.controlWorkDirs[0],
|
|
106
106
|
});
|
|
107
107
|
const testPlotData = this.loadPlotDataFromWorkDir({
|
|
108
|
-
workDir: options.
|
|
108
|
+
workDir: options.treatmentWorkDirs && options.treatmentWorkDirs[0],
|
|
109
109
|
});
|
|
110
110
|
// merge plot data
|
|
111
111
|
return this.mergePlotData([controlPlotData, testPlotData]);
|
|
@@ -21,25 +21,34 @@ class HeapStringNode extends HeapNode_1.default {
|
|
|
21
21
|
}
|
|
22
22
|
get stringValue() {
|
|
23
23
|
var _a, _b, _c;
|
|
24
|
-
const
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
const
|
|
28
|
-
|
|
29
|
-
|
|
24
|
+
const stack = [this];
|
|
25
|
+
let ret = '';
|
|
26
|
+
while (stack.length > 0) {
|
|
27
|
+
const node = stack.pop();
|
|
28
|
+
const type = node.type;
|
|
29
|
+
if (type === 'concatenated string') {
|
|
30
|
+
const firstNode = (_a = node.getReferenceNode('first')) === null || _a === void 0 ? void 0 : _a.toStringNode();
|
|
31
|
+
const secondNode = (_b = node.getReferenceNode('second')) === null || _b === void 0 ? void 0 : _b.toStringNode();
|
|
32
|
+
if (firstNode == null || secondNode == null) {
|
|
33
|
+
throw (0, HeapUtils_1.throwError)(new Error('broken concatenated string'));
|
|
34
|
+
}
|
|
35
|
+
stack.push(secondNode);
|
|
36
|
+
stack.push(firstNode);
|
|
37
|
+
continue;
|
|
30
38
|
}
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
39
|
+
if (type === 'sliced string') {
|
|
40
|
+
const parentNode = (_c = node.getReferenceNode('parent')) === null || _c === void 0 ? void 0 : _c.toStringNode();
|
|
41
|
+
if (parentNode == null) {
|
|
42
|
+
throw (0, HeapUtils_1.throwError)(new Error('broken sliced string'));
|
|
43
|
+
}
|
|
44
|
+
// sliced string in heap snapshot doesn't include
|
|
45
|
+
// the start index and the end index, so this may be inaccurate
|
|
46
|
+
ret += `<sliced string of @${parentNode.id}>`;
|
|
47
|
+
continue;
|
|
37
48
|
}
|
|
38
|
-
|
|
39
|
-
// the start index and the end index, so this may be inaccurate
|
|
40
|
-
return parentNode.stringValue;
|
|
49
|
+
ret += node.name;
|
|
41
50
|
}
|
|
42
|
-
return
|
|
51
|
+
return ret;
|
|
43
52
|
}
|
|
44
53
|
getJSONifyableObject() {
|
|
45
54
|
const rep = super.getJSONifyableObject();
|
|
@@ -21,6 +21,7 @@ const FilterOverSizedNodeAsLeak_rule_1 = require("./rules/FilterOverSizedNodeAsL
|
|
|
21
21
|
const FilterStackTraceFrame_rule_1 = require("./rules/FilterStackTraceFrame.rule");
|
|
22
22
|
const FilterTrivialNode_rule_1 = require("./rules/FilterTrivialNode.rule");
|
|
23
23
|
const FilterUnmountedFiberNode_rule_1 = require("./rules/FilterUnmountedFiberNode.rule");
|
|
24
|
+
const FilterXMLHTTPRequest_rule_1 = require("./rules/FilterXMLHTTPRequest.rule");
|
|
24
25
|
const list = [
|
|
25
26
|
new FilterByExternalFilter_rule_1.FilterByExternalFilterRule(),
|
|
26
27
|
new FilterTrivialNode_rule_1.FilterTrivialNodeRule(),
|
|
@@ -29,5 +30,6 @@ const list = [
|
|
|
29
30
|
new FilterUnmountedFiberNode_rule_1.FilterUnmountedFiberNodeRule(),
|
|
30
31
|
new FilterDetachedDOMElement_rule_1.FilterDetachedDOMElementRule(),
|
|
31
32
|
new FilterStackTraceFrame_rule_1.FilterStackTraceFrameRule(),
|
|
33
|
+
new FilterXMLHTTPRequest_rule_1.FilterXMLHTTPRequestRule(),
|
|
32
34
|
];
|
|
33
35
|
exports.default = (0, InternalValueSetter_1.setInternalValue)(list, __filename, Constant_1.default.internalDir);
|
|
@@ -15,6 +15,5 @@ import { ILeakObjectFilterRule, LeakDecision } from '../BaseLeakFilter.rule';
|
|
|
15
15
|
*/
|
|
16
16
|
export declare class FilterDetachedDOMElementRule implements ILeakObjectFilterRule {
|
|
17
17
|
filter(_config: MemLabConfig, node: IHeapNode): LeakDecision;
|
|
18
|
-
protected checkDetachedFiberNode(config: MemLabConfig, node: IHeapNode): boolean;
|
|
19
18
|
}
|
|
20
19
|
//# sourceMappingURL=FilterDetachedDOMElement.rule.d.ts.map
|
|
@@ -23,18 +23,31 @@ class FilterDetachedDOMElementRule {
|
|
|
23
23
|
const isDetached = Utils_1.default.isDetachedDOMNode(node, {
|
|
24
24
|
ignoreInternalNode: true,
|
|
25
25
|
});
|
|
26
|
-
if (isDetached
|
|
26
|
+
if (isDetached &&
|
|
27
|
+
!isDominatedByEdgeName(node, 'stateNode') &&
|
|
28
|
+
!isDetachedDOMNodeDominatedByDehydratedMemoizedState(node)) {
|
|
27
29
|
return BaseLeakFilter_rule_1.LeakDecision.LEAK;
|
|
28
30
|
}
|
|
29
31
|
return BaseLeakFilter_rule_1.LeakDecision.MAYBE_LEAK;
|
|
30
32
|
}
|
|
31
|
-
checkDetachedFiberNode(config, node) {
|
|
32
|
-
if (!config.detectFiberNodeLeak ||
|
|
33
|
-
!Utils_1.default.isFiberNode(node) ||
|
|
34
|
-
Utils_1.default.hasHostRoot(node)) {
|
|
35
|
-
return false;
|
|
36
|
-
}
|
|
37
|
-
return !Utils_1.default.isNodeDominatedByDeletionsArray(node);
|
|
38
|
-
}
|
|
39
33
|
}
|
|
40
34
|
exports.FilterDetachedDOMElementRule = FilterDetachedDOMElementRule;
|
|
35
|
+
function isDominatedByEdgeName(node, edgeNameOrIndex) {
|
|
36
|
+
var _a;
|
|
37
|
+
const referrerNode = node.getAnyReferrerNode(edgeNameOrIndex);
|
|
38
|
+
if (referrerNode == null) {
|
|
39
|
+
return false;
|
|
40
|
+
}
|
|
41
|
+
return referrerNode.id === ((_a = node.dominatorNode) === null || _a === void 0 ? void 0 : _a.id);
|
|
42
|
+
}
|
|
43
|
+
// check if the input is a detached DOM node dominated by a 'dehydrated'
|
|
44
|
+
// edge from a memoizedState. In this case, the node is not a memory leak
|
|
45
|
+
function isDetachedDOMNodeDominatedByDehydratedMemoizedState(node) {
|
|
46
|
+
var _a;
|
|
47
|
+
const referrerNode = node.getAnyReferrerNode('dehydrated', 'property');
|
|
48
|
+
if (referrerNode == null) {
|
|
49
|
+
return false;
|
|
50
|
+
}
|
|
51
|
+
return (referrerNode.id === ((_a = node.dominatorNode) === null || _a === void 0 ? void 0 : _a.id) &&
|
|
52
|
+
isDominatedByEdgeName(referrerNode, 'memoizedState'));
|
|
53
|
+
}
|
|
@@ -8,8 +8,13 @@
|
|
|
8
8
|
* @format
|
|
9
9
|
* @oncall web_perf_infra
|
|
10
10
|
*/
|
|
11
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
12
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
13
|
+
};
|
|
11
14
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
15
|
exports.FilterOverSizedNodeAsLeakRule = void 0;
|
|
16
|
+
const Utils_1 = __importDefault(require("../../Utils"));
|
|
17
|
+
const Config_1 = require("../../Config");
|
|
13
18
|
const BaseLeakFilter_rule_1 = require("../BaseLeakFilter.rule");
|
|
14
19
|
/**
|
|
15
20
|
* trivial nodes are not reported as memory leaks
|
|
@@ -17,6 +22,10 @@ const BaseLeakFilter_rule_1 = require("../BaseLeakFilter.rule");
|
|
|
17
22
|
class FilterOverSizedNodeAsLeakRule {
|
|
18
23
|
filter(config, node) {
|
|
19
24
|
if (config.oversizeObjectAsLeak) {
|
|
25
|
+
// TODO: add support to skip this check
|
|
26
|
+
if (!isHeapNodeUsefulForLeakTraceDiffing(config, node)) {
|
|
27
|
+
return BaseLeakFilter_rule_1.LeakDecision.NOT_LEAK;
|
|
28
|
+
}
|
|
20
29
|
return node.retainedSize > config.oversizeThreshold
|
|
21
30
|
? BaseLeakFilter_rule_1.LeakDecision.LEAK
|
|
22
31
|
: BaseLeakFilter_rule_1.LeakDecision.NOT_LEAK;
|
|
@@ -25,3 +34,44 @@ class FilterOverSizedNodeAsLeakRule {
|
|
|
25
34
|
}
|
|
26
35
|
}
|
|
27
36
|
exports.FilterOverSizedNodeAsLeakRule = FilterOverSizedNodeAsLeakRule;
|
|
37
|
+
function isHeapNodeUsefulForLeakTraceDiffing(config, node) {
|
|
38
|
+
if (config.traceAllObjectsMode === Config_1.TraceObjectMode.Default) {
|
|
39
|
+
return true;
|
|
40
|
+
}
|
|
41
|
+
const name = node.name;
|
|
42
|
+
if (node.type !== 'object') {
|
|
43
|
+
return false;
|
|
44
|
+
}
|
|
45
|
+
if (name.startsWith('system / ')) {
|
|
46
|
+
return false;
|
|
47
|
+
}
|
|
48
|
+
if (Utils_1.default.isFiberNode(node) && !Utils_1.default.isDetachedFiberNode(node)) {
|
|
49
|
+
return false;
|
|
50
|
+
}
|
|
51
|
+
if (Utils_1.default.isDOMNodeIncomplete(node) && !Utils_1.default.isDetachedDOMNode(node)) {
|
|
52
|
+
return false;
|
|
53
|
+
}
|
|
54
|
+
if (node.getAnyReferrer('__proto__') != null) {
|
|
55
|
+
return false;
|
|
56
|
+
}
|
|
57
|
+
if (node.getAnyReferrer('prototype') != null) {
|
|
58
|
+
return false;
|
|
59
|
+
}
|
|
60
|
+
// react internal objects
|
|
61
|
+
if (node.getAnyReferrer('dependencies') != null) {
|
|
62
|
+
return false;
|
|
63
|
+
}
|
|
64
|
+
if (node.getAnyReferrer('memoizedState') != null) {
|
|
65
|
+
return false;
|
|
66
|
+
}
|
|
67
|
+
if (node.getAnyReferrer('next') != null) {
|
|
68
|
+
return false;
|
|
69
|
+
}
|
|
70
|
+
if (node.getAnyReferrer('deps') != null) {
|
|
71
|
+
return false;
|
|
72
|
+
}
|
|
73
|
+
if (node.getReference('baseQueue') != null) {
|
|
74
|
+
return false;
|
|
75
|
+
}
|
|
76
|
+
return true;
|
|
77
|
+
}
|
|
@@ -26,9 +26,10 @@ class FilterUnmountedFiberNodeRule {
|
|
|
26
26
|
return BaseLeakFilter_rule_1.LeakDecision.MAYBE_LEAK;
|
|
27
27
|
}
|
|
28
28
|
checkDetachedFiberNode(config, node) {
|
|
29
|
-
if (!config.detectFiberNodeLeak ||
|
|
30
|
-
|
|
31
|
-
|
|
29
|
+
if (!config.detectFiberNodeLeak || !Utils_1.default.isFiberNode(node)) {
|
|
30
|
+
return false;
|
|
31
|
+
}
|
|
32
|
+
if (!Utils_1.default.isDetachedFiberNode(node)) {
|
|
32
33
|
return false;
|
|
33
34
|
}
|
|
34
35
|
return !Utils_1.default.isNodeDominatedByDeletionsArray(node);
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
3
|
+
*
|
|
4
|
+
* This source code is licensed under the MIT license found in the
|
|
5
|
+
* LICENSE file in the root directory of this source tree.
|
|
6
|
+
*
|
|
7
|
+
* @format
|
|
8
|
+
* @oncall web_perf_infra
|
|
9
|
+
*/
|
|
10
|
+
import type { MemLabConfig } from '../../Config';
|
|
11
|
+
import type { IHeapNode } from '../../Types';
|
|
12
|
+
import { ILeakObjectFilterRule, LeakDecision } from '../BaseLeakFilter.rule';
|
|
13
|
+
/**
|
|
14
|
+
* mark XMLHTTPRequest with status ok as memory leaks
|
|
15
|
+
*/
|
|
16
|
+
export declare class FilterXMLHTTPRequestRule implements ILeakObjectFilterRule {
|
|
17
|
+
filter(_config: MemLabConfig, node: IHeapNode): LeakDecision;
|
|
18
|
+
protected checkFinishedXMLHTTPRequest(node: IHeapNode): boolean;
|
|
19
|
+
}
|
|
20
|
+
//# sourceMappingURL=FilterXMLHTTPRequest.rule.d.ts.map
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
4
|
+
*
|
|
5
|
+
* This source code is licensed under the MIT license found in the
|
|
6
|
+
* LICENSE file in the root directory of this source tree.
|
|
7
|
+
*
|
|
8
|
+
* @format
|
|
9
|
+
* @oncall web_perf_infra
|
|
10
|
+
*/
|
|
11
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
+
exports.FilterXMLHTTPRequestRule = void 0;
|
|
13
|
+
const BaseLeakFilter_rule_1 = require("../BaseLeakFilter.rule");
|
|
14
|
+
/**
|
|
15
|
+
* mark XMLHTTPRequest with status ok as memory leaks
|
|
16
|
+
*/
|
|
17
|
+
class FilterXMLHTTPRequestRule {
|
|
18
|
+
filter(_config, node) {
|
|
19
|
+
return this.checkFinishedXMLHTTPRequest(node)
|
|
20
|
+
? BaseLeakFilter_rule_1.LeakDecision.LEAK
|
|
21
|
+
: BaseLeakFilter_rule_1.LeakDecision.MAYBE_LEAK;
|
|
22
|
+
}
|
|
23
|
+
checkFinishedXMLHTTPRequest(node) {
|
|
24
|
+
if (node.name !== 'XMLHttpRequest' || node.type !== 'native') {
|
|
25
|
+
return false;
|
|
26
|
+
}
|
|
27
|
+
return (node.findAnyReference((edge) => edge.toNode.name === '{"status":"ok"}') != null);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
exports.FilterXMLHTTPRequestRule = FilterXMLHTTPRequestRule;
|
|
@@ -15,6 +15,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
15
15
|
const Constant_1 = __importDefault(require("../Constant"));
|
|
16
16
|
const InternalValueSetter_1 = require("../InternalValueSetter");
|
|
17
17
|
const FilterAttachedDOMToDetachedDOMTrace_rule_1 = require("./rules/FilterAttachedDOMToDetachedDOMTrace.rule");
|
|
18
|
+
const FilterCppRootsToDetachedDOMTrace_rule_1 = require("./rules/FilterCppRootsToDetachedDOMTrace.rule");
|
|
18
19
|
const FilterDOMNodeChainTrace_rule_1 = require("./rules/FilterDOMNodeChainTrace.rule");
|
|
19
20
|
const FilterHermesTrace_rule_1 = require("./rules/FilterHermesTrace.rule");
|
|
20
21
|
const FilterInternalNodeTrace_rule_1 = require("./rules/FilterInternalNodeTrace.rule");
|
|
@@ -29,5 +30,6 @@ const list = [
|
|
|
29
30
|
new FilterPendingActivitiesTrace_rule_1.FilterPendingActivitiesTraceRule(),
|
|
30
31
|
new FilterDOMNodeChainTrace_rule_1.FilterDOMNodeChainTraceRule(),
|
|
31
32
|
new FilterAttachedDOMToDetachedDOMTrace_rule_1.FilterAttachedDOMToDetachedDOMTraceRule(),
|
|
33
|
+
new FilterCppRootsToDetachedDOMTrace_rule_1.FilterCppRootsToDetachedDOMTraceRule(),
|
|
32
34
|
];
|
|
33
35
|
exports.default = (0, InternalValueSetter_1.setInternalValue)(list, __filename, Constant_1.default.internalDir);
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
3
|
+
*
|
|
4
|
+
* This source code is licensed under the MIT license found in the
|
|
5
|
+
* LICENSE file in the root directory of this source tree.
|
|
6
|
+
*
|
|
7
|
+
* @format
|
|
8
|
+
* @oncall web_perf_infra
|
|
9
|
+
*/
|
|
10
|
+
import type { LeakTracePathItem } from '../../Types';
|
|
11
|
+
import { ILeakTraceFilterRule, LeakTraceFilterOptions, TraceDecision } from '../BaseTraceFilter.rule';
|
|
12
|
+
export declare class FilterCppRootsToDetachedDOMTraceRule implements ILeakTraceFilterRule {
|
|
13
|
+
filter(p: LeakTracePathItem, options?: LeakTraceFilterOptions): TraceDecision;
|
|
14
|
+
}
|
|
15
|
+
//# sourceMappingURL=FilterCppRootsToDetachedDOMTrace.rule.d.ts.map
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
4
|
+
*
|
|
5
|
+
* This source code is licensed under the MIT license found in the
|
|
6
|
+
* LICENSE file in the root directory of this source tree.
|
|
7
|
+
*
|
|
8
|
+
* @format
|
|
9
|
+
* @oncall web_perf_infra
|
|
10
|
+
*/
|
|
11
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
12
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
13
|
+
};
|
|
14
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
15
|
+
exports.FilterCppRootsToDetachedDOMTraceRule = void 0;
|
|
16
|
+
const Config_1 = __importDefault(require("../../Config"));
|
|
17
|
+
const Utils_1 = __importDefault(require("../../Utils"));
|
|
18
|
+
const BaseTraceFilter_rule_1 = require("../BaseTraceFilter.rule");
|
|
19
|
+
class FilterCppRootsToDetachedDOMTraceRule {
|
|
20
|
+
filter(p, options = {}) {
|
|
21
|
+
var _a;
|
|
22
|
+
const curConfig = (_a = options.config) !== null && _a !== void 0 ? _a : Config_1.default;
|
|
23
|
+
// if the path contains edges from [C++ roots] to detached DOM elements
|
|
24
|
+
if (curConfig.hideBrowserLeak && hasCppRootsToDetachedDOMNode(p)) {
|
|
25
|
+
return BaseTraceFilter_rule_1.TraceDecision.NOT_INSIGHTFUL;
|
|
26
|
+
}
|
|
27
|
+
return BaseTraceFilter_rule_1.TraceDecision.MAYBE_INSIGHTFUL;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
exports.FilterCppRootsToDetachedDOMTraceRule = FilterCppRootsToDetachedDOMTraceRule;
|
|
31
|
+
function hasCppRootsToDetachedDOMNode(path) {
|
|
32
|
+
let p = path;
|
|
33
|
+
// all the reference chain consists of DOM elements/nodes
|
|
34
|
+
while (p && p.node) {
|
|
35
|
+
if (Utils_1.default.isCppRootsNode(p.node) &&
|
|
36
|
+
p.next &&
|
|
37
|
+
p.next.node &&
|
|
38
|
+
Utils_1.default.isDOMNodeIncomplete(p.next.node)) {
|
|
39
|
+
return true;
|
|
40
|
+
}
|
|
41
|
+
p = p.next;
|
|
42
|
+
}
|
|
43
|
+
return false;
|
|
44
|
+
}
|
|
@@ -450,7 +450,7 @@ class TraceFinder {
|
|
|
450
450
|
return Config_1.default.edgeNameGreyList.has(String(edge.name_or_index));
|
|
451
451
|
}
|
|
452
452
|
isLessPreferableNode(node) {
|
|
453
|
-
return Config_1.default.nodeNameGreyList.has(node.name);
|
|
453
|
+
return Config_1.default.nodeNameGreyList.has(node.name) || Utils_1.default.isCppRootsNode(node);
|
|
454
454
|
}
|
|
455
455
|
// each edge is indexed by fromNode's ID, toNode's ID, edge name, and edge type
|
|
456
456
|
getEdgeKey(edge) {
|
|
@@ -37,7 +37,7 @@ export default class NormalizedTrace {
|
|
|
37
37
|
private static buildTraceToPathMap;
|
|
38
38
|
private static pushLeakPathToCluster;
|
|
39
39
|
private static initEmptyCluster;
|
|
40
|
-
static clusterControlTreatmentPaths(leakPathsFromControlRuns: LeakTracePathItem[][], controlSnapshots: IHeapSnapshot[],
|
|
40
|
+
static clusterControlTreatmentPaths(leakPathsFromControlRuns: LeakTracePathItem[][], controlSnapshots: IHeapSnapshot[], leakPathsFromTreatmentRuns: LeakTracePathItem[][], treatmentSnapshots: IHeapSnapshot[], aggregateDominatorMetrics: AggregateNodeCb, option?: {
|
|
41
41
|
strategy?: IClusterStrategy;
|
|
42
42
|
}): ControlTreatmentClusterResult;
|
|
43
43
|
static generateUnClassifiedClusters(paths: LeakTracePathItem[], snapshot: IHeapSnapshot, aggregateDominatorMetrics: AggregateNodeCb): TraceCluster[];
|