@memlab/core 1.1.18 → 1.1.20
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__/parser/StringNode.test.js +12 -1
- package/dist/index.d.ts +4 -2
- package/dist/index.js +6 -3
- package/dist/lib/Config.d.ts +2 -0
- package/dist/lib/Config.js +4 -0
- package/dist/lib/FileManager.d.ts +5 -0
- package/dist/lib/FileManager.js +78 -7
- package/dist/lib/HeapAnalyzer.d.ts +15 -8
- package/dist/lib/HeapAnalyzer.js +106 -98
- package/dist/lib/Types.d.ts +93 -0
- package/dist/lib/Utils.d.ts +9 -3
- package/dist/lib/Utils.js +37 -7
- package/dist/lib/charts/MemoryBarChart.d.ts +20 -0
- package/dist/lib/charts/MemoryBarChart.js +116 -0
- package/dist/lib/heap-data/HeapEdge.d.ts +3 -1
- package/dist/lib/heap-data/HeapEdge.js +13 -0
- package/dist/lib/heap-data/HeapLocation.d.ts +3 -1
- package/dist/lib/heap-data/HeapLocation.js +14 -0
- package/dist/lib/heap-data/HeapNode.d.ts +6 -1
- package/dist/lib/heap-data/HeapNode.js +46 -0
- package/dist/lib/heap-data/HeapSnapshot.d.ts +3 -1
- package/dist/lib/heap-data/HeapSnapshot.js +7 -0
- package/dist/lib/heap-data/HeapStringNode.d.ts +2 -1
- package/dist/lib/heap-data/HeapStringNode.js +5 -0
- package/dist/trace-cluster/TraceBucket.d.ts +11 -1
- package/dist/trace-cluster/TraceBucket.js +146 -1
- package/dist/trace-cluster/TraceElement.d.ts +6 -1
- package/dist/trace-cluster/TraceElement.js +33 -0
- package/dist/trace-cluster/strategies/TraceSimilarityStrategy.js +9 -0
- package/package.json +1 -1
- package/static/visit-order-single-snapshot.json +19 -0
package/dist/lib/HeapAnalyzer.js
CHANGED
|
@@ -22,77 +22,83 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
22
22
|
};
|
|
23
23
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
24
24
|
const fs_1 = __importDefault(require("fs"));
|
|
25
|
-
const babar_1 = __importDefault(require("babar"));
|
|
26
|
-
const LeakClusterLogger_1 = __importDefault(require("../logger/LeakClusterLogger"));
|
|
27
|
-
const LeakTraceDetailsLogger_1 = __importDefault(require("../logger/LeakTraceDetailsLogger"));
|
|
28
|
-
const TraceFinder_1 = __importDefault(require("../paths/TraceFinder"));
|
|
29
|
-
const TraceBucket_1 = __importDefault(require("../trace-cluster/TraceBucket"));
|
|
30
25
|
const Config_1 = __importDefault(require("./Config"));
|
|
31
26
|
const Console_1 = __importDefault(require("./Console"));
|
|
32
27
|
const Serializer_1 = __importDefault(require("./Serializer"));
|
|
33
28
|
const Utils_1 = __importDefault(require("./Utils"));
|
|
29
|
+
const FileManager_1 = __importDefault(require("./FileManager"));
|
|
30
|
+
const MemoryBarChart_1 = __importDefault(require("./charts/MemoryBarChart"));
|
|
31
|
+
const LeakClusterLogger_1 = __importDefault(require("../logger/LeakClusterLogger"));
|
|
32
|
+
const LeakTraceDetailsLogger_1 = __importDefault(require("../logger/LeakTraceDetailsLogger"));
|
|
33
|
+
const TraceFinder_1 = __importDefault(require("../paths/TraceFinder"));
|
|
34
|
+
const TraceBucket_1 = __importDefault(require("../trace-cluster/TraceBucket"));
|
|
34
35
|
const LeakObjectFilter_1 = require("./leak-filters/LeakObjectFilter");
|
|
35
36
|
const MLTraceSimilarityStrategy_1 = __importDefault(require("../trace-cluster/strategies/MLTraceSimilarityStrategy"));
|
|
36
37
|
class MemoryAnalyst {
|
|
37
38
|
checkLeak() {
|
|
38
39
|
return __awaiter(this, void 0, void 0, function* () {
|
|
39
|
-
|
|
40
|
+
MemoryBarChart_1.default.plotMemoryBarChart();
|
|
40
41
|
Utils_1.default.checkSnapshots();
|
|
41
42
|
return yield this.detectMemoryLeaks();
|
|
42
43
|
});
|
|
43
44
|
}
|
|
45
|
+
diffLeakByWorkDir(options) {
|
|
46
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
47
|
+
const controlSnapshotDir = FileManager_1.default.getCurDataDir({
|
|
48
|
+
workDir: options.controlWorkDir,
|
|
49
|
+
});
|
|
50
|
+
const treatmentSnapshotDir = FileManager_1.default.getCurDataDir({
|
|
51
|
+
workDir: options.treatmentWorkDir,
|
|
52
|
+
});
|
|
53
|
+
// check control working dir
|
|
54
|
+
Utils_1.default.checkSnapshots({ snapshotDir: controlSnapshotDir });
|
|
55
|
+
// check treatment working dir
|
|
56
|
+
Utils_1.default.checkSnapshots({ snapshotDir: treatmentSnapshotDir });
|
|
57
|
+
// display control and treatment memory
|
|
58
|
+
MemoryBarChart_1.default.plotMemoryBarChart(options);
|
|
59
|
+
return this.diffMemoryLeakTraces(options);
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
// find all unique pattern of leaks
|
|
63
|
+
diffMemoryLeakTraces(options) {
|
|
64
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
65
|
+
Config_1.default.dumpNodeInfo = false;
|
|
66
|
+
// diff snapshots and get control raw paths
|
|
67
|
+
let snapshotDiff = yield this.diffSnapshots({
|
|
68
|
+
loadAllSnapshots: true,
|
|
69
|
+
workDir: options.controlWorkDir,
|
|
70
|
+
});
|
|
71
|
+
const controlLeakPaths = this.filterLeakPaths(snapshotDiff.leakedHeapNodeIdSet, snapshotDiff.snapshot, { workDir: options.controlWorkDir });
|
|
72
|
+
const controlSnapshot = snapshotDiff.snapshot;
|
|
73
|
+
// diff snapshots and get treatment raw paths
|
|
74
|
+
snapshotDiff = yield this.diffSnapshots({
|
|
75
|
+
loadAllSnapshots: true,
|
|
76
|
+
workDir: options.treatmentWorkDir,
|
|
77
|
+
});
|
|
78
|
+
const treatmentLeakPaths = this.filterLeakPaths(snapshotDiff.leakedHeapNodeIdSet, snapshotDiff.snapshot, { workDir: options.controlWorkDir });
|
|
79
|
+
const treatmentSnapshot = snapshotDiff.snapshot;
|
|
80
|
+
Console_1.default.topLevel(`${controlLeakPaths.length} traces from control group`);
|
|
81
|
+
Console_1.default.topLevel(`${treatmentLeakPaths.length} traces from treatment group`);
|
|
82
|
+
const result = TraceBucket_1.default.clusterControlTreatmentPaths(controlLeakPaths, controlSnapshot, treatmentLeakPaths, treatmentSnapshot, Utils_1.default.aggregateDominatorMetrics, {
|
|
83
|
+
strategy: Config_1.default.isMLClustering
|
|
84
|
+
? new MLTraceSimilarityStrategy_1.default()
|
|
85
|
+
: undefined,
|
|
86
|
+
});
|
|
87
|
+
Console_1.default.midLevel(`MemLab found ${result.treatmentOnlyClusters.length} new leak(s) in the treatment group`);
|
|
88
|
+
yield this.serializeClusterUpdate(result.treatmentOnlyClusters);
|
|
89
|
+
// TODO (lgong): log leak traces
|
|
90
|
+
return [];
|
|
91
|
+
});
|
|
92
|
+
}
|
|
44
93
|
// find all unique pattern of leaks
|
|
45
94
|
detectMemoryLeaks() {
|
|
46
95
|
return __awaiter(this, void 0, void 0, function* () {
|
|
47
|
-
const snapshotDiff = yield this.diffSnapshots(true);
|
|
96
|
+
const snapshotDiff = yield this.diffSnapshots({ loadAllSnapshots: true });
|
|
48
97
|
Config_1.default.dumpNodeInfo = false;
|
|
49
|
-
const
|
|
98
|
+
const paths = yield this.findLeakTraces(snapshotDiff.leakedHeapNodeIdSet, snapshotDiff.snapshot);
|
|
50
99
|
return LeakTraceDetailsLogger_1.default.logTraces(snapshotDiff.leakedHeapNodeIdSet, snapshotDiff.snapshot, snapshotDiff.listOfLeakedHeapNodeIdSet, paths, Config_1.default.traceJsonOutDir);
|
|
51
100
|
});
|
|
52
101
|
}
|
|
53
|
-
visualizeMemoryUsage(options = {}) {
|
|
54
|
-
if (Config_1.default.useExternalSnapshot || options.snapshotDir) {
|
|
55
|
-
return;
|
|
56
|
-
}
|
|
57
|
-
const tabsOrder = Utils_1.default.loadTabsOrder();
|
|
58
|
-
// if memory usage data is incomplete, skip the visualization
|
|
59
|
-
for (const tab of tabsOrder) {
|
|
60
|
-
if (!(tab.JSHeapUsedSize > 0)) {
|
|
61
|
-
if (Config_1.default.verbose) {
|
|
62
|
-
Console_1.default.error('Memory usage data incomplete');
|
|
63
|
-
}
|
|
64
|
-
return;
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
const plotData = tabsOrder.map((tab, idx) => [
|
|
68
|
-
idx + 1,
|
|
69
|
-
((tab.JSHeapUsedSize / 100000) | 0) / 10,
|
|
70
|
-
]);
|
|
71
|
-
// the graph component cannot handle an array with a single element
|
|
72
|
-
while (plotData.length < 2) {
|
|
73
|
-
plotData.push([plotData.length + 1, 0]);
|
|
74
|
-
}
|
|
75
|
-
// plot visual settings
|
|
76
|
-
const minY = 1;
|
|
77
|
-
const maxY = plotData.reduce((m, v) => Math.max(m, v[1]), 0) * 1.15;
|
|
78
|
-
const yFractions = 1;
|
|
79
|
-
const yLabelWidth = 1 +
|
|
80
|
-
Math.max(minY.toFixed(yFractions).length, maxY.toFixed(yFractions).length);
|
|
81
|
-
const maxWidth = process.stdout.columns - 10;
|
|
82
|
-
const idealWidth = Math.max(2 * plotData.length + 2 * yLabelWidth, 10);
|
|
83
|
-
const plotWidth = Math.min(idealWidth, maxWidth);
|
|
84
|
-
Console_1.default.topLevel('Memory usage across all steps:');
|
|
85
|
-
Console_1.default.topLevel((0, babar_1.default)(plotData, {
|
|
86
|
-
color: 'green',
|
|
87
|
-
width: plotWidth,
|
|
88
|
-
height: 10,
|
|
89
|
-
xFractions: 0,
|
|
90
|
-
yFractions,
|
|
91
|
-
minY,
|
|
92
|
-
maxY,
|
|
93
|
-
}));
|
|
94
|
-
Console_1.default.topLevel('');
|
|
95
|
-
}
|
|
96
102
|
focus(options = {}) {
|
|
97
103
|
return __awaiter(this, void 0, void 0, function* () {
|
|
98
104
|
Console_1.default.overwrite(`Generating report for node @${Config_1.default.focusFiberNodeId}`);
|
|
@@ -113,7 +119,7 @@ class MemoryAnalyst {
|
|
|
113
119
|
}
|
|
114
120
|
else {
|
|
115
121
|
Utils_1.default.checkSnapshots();
|
|
116
|
-
const snapshotDiff = yield this.diffSnapshots(true);
|
|
122
|
+
const snapshotDiff = yield this.diffSnapshots({ loadAllSnapshots: true });
|
|
117
123
|
nodeIdsInSnapshots = snapshotDiff.listOfLeakedHeapNodeIdSet;
|
|
118
124
|
snapshotLeakedHeapNodeIdSet = snapshotDiff.leakedHeapNodeIdSet;
|
|
119
125
|
snapshot = snapshotDiff.snapshot;
|
|
@@ -130,16 +136,16 @@ class MemoryAnalyst {
|
|
|
130
136
|
}
|
|
131
137
|
return false;
|
|
132
138
|
}
|
|
133
|
-
diffSnapshots(
|
|
139
|
+
diffSnapshots(options = {}) {
|
|
134
140
|
return __awaiter(this, void 0, void 0, function* () {
|
|
135
141
|
const nodeIdsInSnapshots = [];
|
|
136
|
-
const tabsOrder = Utils_1.default.loadTabsOrder();
|
|
142
|
+
const tabsOrder = Utils_1.default.loadTabsOrder(FileManager_1.default.getSnapshotSequenceMetaFile(options));
|
|
137
143
|
// a set keeping track of node ids generated before the target snapshot
|
|
138
144
|
const baselineIds = new Set();
|
|
139
145
|
let collectBaselineIds = true;
|
|
140
146
|
let targetAllocatedHeapNodeIdSet = null;
|
|
141
147
|
let leakedHeapNodeIdSet = null;
|
|
142
|
-
const
|
|
148
|
+
const parseSnapshotOptions = { verbose: true, workDir: options.workDir };
|
|
143
149
|
let snapshot = null;
|
|
144
150
|
for (let i = 0; i < tabsOrder.length; i++) {
|
|
145
151
|
const tab = tabsOrder[i];
|
|
@@ -157,13 +163,13 @@ class MemoryAnalyst {
|
|
|
157
163
|
continue;
|
|
158
164
|
}
|
|
159
165
|
// in quick mode, there is no need to load all snapshots
|
|
160
|
-
if (!
|
|
166
|
+
if (!options.loadAllSnapshots && !tab.type) {
|
|
161
167
|
continue;
|
|
162
168
|
}
|
|
163
|
-
const file = Utils_1.default.getSnapshotFilePath(tab);
|
|
169
|
+
const file = Utils_1.default.getSnapshotFilePath(tab, options);
|
|
164
170
|
if (this.shouldLoadCompleteSnapshot(tabsOrder, tab)) {
|
|
165
171
|
// final snapshot needs to build node index
|
|
166
|
-
const opt = Object.assign({ buildNodeIdIndex: true }, options);
|
|
172
|
+
const opt = Object.assign(Object.assign({ buildNodeIdIndex: true }, parseSnapshotOptions), { workDir: options.workDir });
|
|
167
173
|
snapshot = yield Utils_1.default.getSnapshotFromFile(file, opt);
|
|
168
174
|
// record Ids in the snapshot
|
|
169
175
|
snapshot.nodes.forEach(node => {
|
|
@@ -171,7 +177,7 @@ class MemoryAnalyst {
|
|
|
171
177
|
});
|
|
172
178
|
}
|
|
173
179
|
else {
|
|
174
|
-
idsInSnapshot = yield Utils_1.default.getSnapshotNodeIdsFromFile(file,
|
|
180
|
+
idsInSnapshot = yield Utils_1.default.getSnapshotNodeIdsFromFile(file, parseSnapshotOptions);
|
|
175
181
|
nodeIdsInSnapshots.pop();
|
|
176
182
|
nodeIdsInSnapshots.push(idsInSnapshot);
|
|
177
183
|
}
|
|
@@ -234,10 +240,10 @@ class MemoryAnalyst {
|
|
|
234
240
|
return finder;
|
|
235
241
|
}
|
|
236
242
|
// summarize the page interaction and dump to the leak text summary file
|
|
237
|
-
dumpPageInteractionSummary() {
|
|
238
|
-
const tabsOrder = Utils_1.default.loadTabsOrder();
|
|
243
|
+
dumpPageInteractionSummary(options = {}) {
|
|
244
|
+
const tabsOrder = Utils_1.default.loadTabsOrder(FileManager_1.default.getSnapshotSequenceMetaFile(options));
|
|
239
245
|
const tabsOrderStr = Serializer_1.default.summarizeTabsOrder(tabsOrder);
|
|
240
|
-
fs_1.default.writeFileSync(
|
|
246
|
+
fs_1.default.writeFileSync(FileManager_1.default.getLeakSummaryFile(options), tabsOrderStr, 'UTF-8');
|
|
241
247
|
}
|
|
242
248
|
// summarize the leak and print the info in console
|
|
243
249
|
dumpLeakSummaryToConsole(leakedNodeIds, snapshot) {
|
|
@@ -318,9 +324,9 @@ class MemoryAnalyst {
|
|
|
318
324
|
Console_1.default.topLevel(`· ${name}: ${value}`);
|
|
319
325
|
});
|
|
320
326
|
}
|
|
321
|
-
printHeapAndLeakInfo(leakedNodeIds, snapshot) {
|
|
327
|
+
printHeapAndLeakInfo(leakedNodeIds, snapshot, options = {}) {
|
|
322
328
|
// write page interaction summary to the leaks text file
|
|
323
|
-
this.dumpPageInteractionSummary();
|
|
329
|
+
this.dumpPageInteractionSummary(options);
|
|
324
330
|
// dump leak summry to console
|
|
325
331
|
this.dumpLeakSummaryToConsole(leakedNodeIds, snapshot);
|
|
326
332
|
// get aggregated leak info
|
|
@@ -329,42 +335,46 @@ class MemoryAnalyst {
|
|
|
329
335
|
this.printHeapInfo(heapInfo);
|
|
330
336
|
}
|
|
331
337
|
}
|
|
332
|
-
logLeakTraceSummary(trace, nodeIdInPaths, snapshot) {
|
|
338
|
+
logLeakTraceSummary(trace, nodeIdInPaths, snapshot, options = {}) {
|
|
333
339
|
if (!Config_1.default.isFullRun) {
|
|
334
340
|
return;
|
|
335
341
|
}
|
|
336
342
|
// convert the path to a string
|
|
337
343
|
const pathStr = Serializer_1.default.summarizePath(trace, nodeIdInPaths, snapshot);
|
|
338
|
-
fs_1.default.appendFileSync(
|
|
344
|
+
fs_1.default.appendFileSync(FileManager_1.default.getLeakSummaryFile(options), `\n\n${pathStr}\n\n`, 'UTF-8');
|
|
345
|
+
}
|
|
346
|
+
filterLeakPaths(leakedNodeIds, snapshot, options = {}) {
|
|
347
|
+
const finder = this.preparePathFinder(snapshot);
|
|
348
|
+
this.printHeapAndLeakInfo(leakedNodeIds, snapshot, options);
|
|
349
|
+
// get all leaked objects
|
|
350
|
+
this.filterLeakedObjects(leakedNodeIds, snapshot);
|
|
351
|
+
const nodeIdInPaths = new Set();
|
|
352
|
+
const paths = [];
|
|
353
|
+
let numOfLeakedObjects = 0;
|
|
354
|
+
let i = 0;
|
|
355
|
+
// analysis for each node
|
|
356
|
+
Utils_1.default.applyToNodes(leakedNodeIds, snapshot, node => {
|
|
357
|
+
if (!Config_1.default.isContinuousTest && ++i % 11 === 0) {
|
|
358
|
+
Console_1.default.overwrite(`progress: ${i} / ${leakedNodeIds.size} @${node.id}`);
|
|
359
|
+
}
|
|
360
|
+
// BFS search for path from the leaked node to GC roots
|
|
361
|
+
const p = finder.getPathToGCRoots(snapshot, node);
|
|
362
|
+
if (!p || !Utils_1.default.isInterestingPath(p)) {
|
|
363
|
+
return;
|
|
364
|
+
}
|
|
365
|
+
++numOfLeakedObjects;
|
|
366
|
+
paths.push(p);
|
|
367
|
+
this.logLeakTraceSummary(p, nodeIdInPaths, snapshot, options);
|
|
368
|
+
}, { reverse: true });
|
|
369
|
+
if (Config_1.default.verbose) {
|
|
370
|
+
Console_1.default.midLevel(`${numOfLeakedObjects} leaked objects`);
|
|
371
|
+
}
|
|
372
|
+
return paths;
|
|
339
373
|
}
|
|
340
374
|
// find unique paths of leaked nodes
|
|
341
|
-
|
|
375
|
+
findLeakTraces(leakedNodeIds, snapshot, options = {}) {
|
|
342
376
|
return __awaiter(this, void 0, void 0, function* () {
|
|
343
|
-
const
|
|
344
|
-
this.printHeapAndLeakInfo(leakedNodeIds, snapshot);
|
|
345
|
-
// get all leaked objects
|
|
346
|
-
this.filterLeakedObjects(leakedNodeIds, snapshot);
|
|
347
|
-
const nodeIdInPaths = new Set();
|
|
348
|
-
const paths = [];
|
|
349
|
-
let numOfLeakedObjects = 0;
|
|
350
|
-
let i = 0;
|
|
351
|
-
// analysis for each node
|
|
352
|
-
Utils_1.default.applyToNodes(leakedNodeIds, snapshot, node => {
|
|
353
|
-
if (!Config_1.default.isContinuousTest && ++i % 11 === 0) {
|
|
354
|
-
Console_1.default.overwrite(`progress: ${i} / ${leakedNodeIds.size} @${node.id}`);
|
|
355
|
-
}
|
|
356
|
-
// BFS search for path from the leaked node to GC roots
|
|
357
|
-
const p = finder.getPathToGCRoots(snapshot, node);
|
|
358
|
-
if (!p || !Utils_1.default.isInterestingPath(p)) {
|
|
359
|
-
return;
|
|
360
|
-
}
|
|
361
|
-
++numOfLeakedObjects;
|
|
362
|
-
paths.push(p);
|
|
363
|
-
this.logLeakTraceSummary(p, nodeIdInPaths, snapshot);
|
|
364
|
-
}, { reverse: true });
|
|
365
|
-
if (Config_1.default.verbose) {
|
|
366
|
-
Console_1.default.midLevel(`${numOfLeakedObjects} leaked objects`);
|
|
367
|
-
}
|
|
377
|
+
const paths = this.filterLeakPaths(leakedNodeIds, snapshot, options);
|
|
368
378
|
// cluster traces from the current run
|
|
369
379
|
const clusters = TraceBucket_1.default.clusterPaths(paths, snapshot, Utils_1.default.aggregateDominatorMetrics, {
|
|
370
380
|
strategy: Config_1.default.isMLClustering
|
|
@@ -378,14 +388,12 @@ class MemoryAnalyst {
|
|
|
378
388
|
const clustersUnclassified = TraceBucket_1.default.generateUnClassifiedClusters(paths, snapshot, Utils_1.default.aggregateDominatorMetrics);
|
|
379
389
|
LeakClusterLogger_1.default.logUnclassifiedClusters(clustersUnclassified);
|
|
380
390
|
}
|
|
381
|
-
return
|
|
382
|
-
paths: clusters.map(c => c.path),
|
|
383
|
-
};
|
|
391
|
+
return clusters.map(c => c.path);
|
|
384
392
|
});
|
|
385
393
|
}
|
|
386
394
|
/**
|
|
387
395
|
* Given a set of heap object ids, cluster them based on the similarity
|
|
388
|
-
* of their retainer traces
|
|
396
|
+
* of their retainer traces
|
|
389
397
|
* @param leakedNodeIds
|
|
390
398
|
* @param snapshot
|
|
391
399
|
* @returns
|
|
@@ -429,7 +437,7 @@ class MemoryAnalyst {
|
|
|
429
437
|
}
|
|
430
438
|
});
|
|
431
439
|
}
|
|
432
|
-
dumpPathByNodeId(leakedIdSet, snapshot, nodeIdsInSnapshots, id, pathLoaderFile, summaryFile) {
|
|
440
|
+
dumpPathByNodeId(leakedIdSet, snapshot, nodeIdsInSnapshots, id, pathLoaderFile, summaryFile, options = {}) {
|
|
433
441
|
Console_1.default.overwrite('start analysis...');
|
|
434
442
|
const finder = this.preparePathFinder(snapshot);
|
|
435
443
|
const nodeIdInPaths = new Set();
|
|
@@ -443,7 +451,7 @@ class MemoryAnalyst {
|
|
|
443
451
|
return;
|
|
444
452
|
}
|
|
445
453
|
LeakTraceDetailsLogger_1.default.logTrace(leakedIdSet, snapshot, nodeIdsInSnapshots, path, pathLoaderFile);
|
|
446
|
-
const tabsOrder = Utils_1.default.loadTabsOrder();
|
|
454
|
+
const tabsOrder = Utils_1.default.loadTabsOrder(FileManager_1.default.getSnapshotSequenceMetaFile(options));
|
|
447
455
|
const interactionSummary = Serializer_1.default.summarizeTabsOrder(tabsOrder);
|
|
448
456
|
let pathSummary = Serializer_1.default.summarizePath(path, nodeIdInPaths, snapshot, { color: true });
|
|
449
457
|
Console_1.default.topLevel(pathSummary);
|
package/dist/lib/Types.d.ts
CHANGED
|
@@ -47,6 +47,7 @@ export declare type FileOption = {
|
|
|
47
47
|
workDir?: Optional<string>;
|
|
48
48
|
clear?: boolean;
|
|
49
49
|
transient?: boolean;
|
|
50
|
+
errorWhenAbsent?: boolean;
|
|
50
51
|
};
|
|
51
52
|
/** @internal */
|
|
52
53
|
export declare type CLIOptions = {
|
|
@@ -801,6 +802,15 @@ export declare type TraceClusterMetaInfo = {
|
|
|
801
802
|
meta_data: string;
|
|
802
803
|
};
|
|
803
804
|
/** @internal */
|
|
805
|
+
export declare type ControlTreatmentClusterResult = {
|
|
806
|
+
controlOnlyClusters: TraceCluster[];
|
|
807
|
+
treatmentOnlyClusters: TraceCluster[];
|
|
808
|
+
hybridClusters: Array<{
|
|
809
|
+
control: TraceCluster;
|
|
810
|
+
treatment: TraceCluster;
|
|
811
|
+
}>;
|
|
812
|
+
};
|
|
813
|
+
/** @internal */
|
|
804
814
|
export interface E2EInteraction {
|
|
805
815
|
kind: string;
|
|
806
816
|
timeout?: number;
|
|
@@ -888,6 +898,7 @@ export interface IE2EStepBasic {
|
|
|
888
898
|
/** @internal */
|
|
889
899
|
export declare type E2EStepInfo = IE2EStepBasic & {
|
|
890
900
|
snapshot: boolean;
|
|
901
|
+
snapshotFile?: string;
|
|
891
902
|
screenshot: boolean;
|
|
892
903
|
idx: number;
|
|
893
904
|
JSHeapUsedSize: number;
|
|
@@ -1209,6 +1220,16 @@ export interface IHeapLocation {
|
|
|
1209
1220
|
* get the column number
|
|
1210
1221
|
*/
|
|
1211
1222
|
column: number;
|
|
1223
|
+
/**
|
|
1224
|
+
* convert to a concise readable string output
|
|
1225
|
+
* (like calling `JSON.stringify(node, ...args)`).
|
|
1226
|
+
* Note: calling `JSON.stringify(node, ...args)` will not work
|
|
1227
|
+
* since the string is too large and not readable.
|
|
1228
|
+
*
|
|
1229
|
+
* This API does not completely serialize all the information
|
|
1230
|
+
* captured by the hosting object.
|
|
1231
|
+
*/
|
|
1232
|
+
toJSONString(...args: Array<AnyValue>): string;
|
|
1212
1233
|
}
|
|
1213
1234
|
/** @internal */
|
|
1214
1235
|
export interface IHeapEdgeBasic {
|
|
@@ -1281,6 +1302,16 @@ export interface IHeapEdge extends IHeapEdgeBasic {
|
|
|
1281
1302
|
* JS heap object where this reference starts
|
|
1282
1303
|
*/
|
|
1283
1304
|
fromNode: IHeapNode;
|
|
1305
|
+
/**
|
|
1306
|
+
* convert to a concise readable string output
|
|
1307
|
+
* (like calling `JSON.stringify(node, ...args)`).
|
|
1308
|
+
* Note: calling `JSON.stringify(node, ...args)` will not work
|
|
1309
|
+
* since the string is too large and not readable.
|
|
1310
|
+
*
|
|
1311
|
+
* This API does not completely serialize all the information
|
|
1312
|
+
* captured by the hosting object.
|
|
1313
|
+
*/
|
|
1314
|
+
toJSONString(...args: Array<AnyValue>): string;
|
|
1284
1315
|
}
|
|
1285
1316
|
/**
|
|
1286
1317
|
* A pseudo array containing all heap graph edges (references to heap objects
|
|
@@ -1437,6 +1468,11 @@ export interface IHeapNode extends IHeapNodeBasic {
|
|
|
1437
1468
|
* object (including engine-internal, native, and JS references).
|
|
1438
1469
|
*/
|
|
1439
1470
|
referrers: IHeapEdge[];
|
|
1471
|
+
/**
|
|
1472
|
+
* Get the number of all incoming references pointing to this heap object
|
|
1473
|
+
* (including engine-internal, native, and JS references).
|
|
1474
|
+
*/
|
|
1475
|
+
numOfReferrers: number;
|
|
1440
1476
|
/**
|
|
1441
1477
|
* returns true if the heap node has been set an incoming edge
|
|
1442
1478
|
* which leads to the parent node on the shortest path to GC root.
|
|
@@ -1484,6 +1520,16 @@ export interface IHeapNode extends IHeapNodeBasic {
|
|
|
1484
1520
|
* inside the string node.
|
|
1485
1521
|
*/
|
|
1486
1522
|
toStringNode(): Nullable<IHeapStringNode>;
|
|
1523
|
+
/**
|
|
1524
|
+
* convert to a concise readable string output
|
|
1525
|
+
* (like calling `JSON.stringify(node, ...args)`).
|
|
1526
|
+
* Note: calling `JSON.stringify(node, ...args)` will not work
|
|
1527
|
+
* since the string is too large and not readable.
|
|
1528
|
+
*
|
|
1529
|
+
* This API does not completely serialize all the information
|
|
1530
|
+
* captured by the hosting object.
|
|
1531
|
+
*/
|
|
1532
|
+
toJSONString(...args: Array<AnyValue>): string;
|
|
1487
1533
|
/**
|
|
1488
1534
|
* executes a provided callback once for each JavaScript reference in the
|
|
1489
1535
|
* hosting node (or outgoing edges from the node)
|
|
@@ -1554,6 +1600,24 @@ export interface IHeapNode extends IHeapNodeBasic {
|
|
|
1554
1600
|
* ```
|
|
1555
1601
|
*/
|
|
1556
1602
|
findAnyReferrer: (predicate: Predicator<IHeapEdge>) => Nullable<IHeapEdge>;
|
|
1603
|
+
/**
|
|
1604
|
+
* executes a provided predicate callback once for each JavaScript heap
|
|
1605
|
+
* object (heap graph node) pointing to the hosting node
|
|
1606
|
+
* (or nodes having edges to the hosting node) until the predicate
|
|
1607
|
+
* returns `true`
|
|
1608
|
+
* @param predicate the callback for each incoming JavaScript heap object
|
|
1609
|
+
* @returns the first referring node for which the predicate returns `true`,
|
|
1610
|
+
* otherwise returns `null` if no such node is found.
|
|
1611
|
+
*
|
|
1612
|
+
* * **Examples**:
|
|
1613
|
+
* ```typescript
|
|
1614
|
+
* const referrer = node.findAnyReferrerNode((node: IHeapNode) => {
|
|
1615
|
+
* // find the referring node with name "Parent"
|
|
1616
|
+
* return node.name === 'Parent';
|
|
1617
|
+
* });
|
|
1618
|
+
* ```
|
|
1619
|
+
*/
|
|
1620
|
+
findAnyReferrerNode(predicate: Predicator<IHeapNode>): Nullable<IHeapNode>;
|
|
1557
1621
|
/**
|
|
1558
1622
|
* executes a provided predicate callback once for each JavaScript reference
|
|
1559
1623
|
* pointing to the hosting node (or incoming edges to the node)
|
|
@@ -1571,6 +1635,24 @@ export interface IHeapNode extends IHeapNodeBasic {
|
|
|
1571
1635
|
* ```
|
|
1572
1636
|
*/
|
|
1573
1637
|
findReferrers: (predicate: Predicator<IHeapEdge>) => IHeapEdge[];
|
|
1638
|
+
/**
|
|
1639
|
+
* executes a provided predicate callback once for each JavaScript heap
|
|
1640
|
+
* object (heap graph node) pointing to the hosting node
|
|
1641
|
+
* (or nodes having edges to the hosting node)
|
|
1642
|
+
* @param predicate the callback for each referrer nodes
|
|
1643
|
+
* @returns an array containing all the referrer nodes for which the
|
|
1644
|
+
* predicate returns `true`, otherwise returns an empty array if no such
|
|
1645
|
+
* node is found.
|
|
1646
|
+
*
|
|
1647
|
+
* * **Examples**:
|
|
1648
|
+
* ```typescript
|
|
1649
|
+
* const referrerNodes = node.findReferrerNodes((node: IHeapNode) => {
|
|
1650
|
+
* // find all the referring nodes with name "Parent"
|
|
1651
|
+
* return node.name === 'Parent';
|
|
1652
|
+
* });
|
|
1653
|
+
* ```
|
|
1654
|
+
*/
|
|
1655
|
+
findReferrerNodes: (predicate: Predicator<IHeapNode>) => IHeapNode[];
|
|
1574
1656
|
/**
|
|
1575
1657
|
* Given a JS reference's name and type, this API finds an outgoing JS
|
|
1576
1658
|
* reference from the hosting node.
|
|
@@ -1841,6 +1923,17 @@ export interface IOveralLeakInfo extends Partial<IOveralHeapInfo> {
|
|
|
1841
1923
|
leakedAlternateFiberNodeSize: number;
|
|
1842
1924
|
}
|
|
1843
1925
|
/** @internal */
|
|
1926
|
+
export declare type DiffLeakOptions = {
|
|
1927
|
+
controlWorkDir: string;
|
|
1928
|
+
treatmentWorkDir: string;
|
|
1929
|
+
};
|
|
1930
|
+
/** @internal */
|
|
1931
|
+
export declare type PlotMemoryOptions = {
|
|
1932
|
+
controlWorkDir?: string;
|
|
1933
|
+
treatmentWorkDir?: string;
|
|
1934
|
+
workDir?: string;
|
|
1935
|
+
} & IMemoryAnalystOptions;
|
|
1936
|
+
/** @internal */
|
|
1844
1937
|
export interface IMemoryAnalystOptions {
|
|
1845
1938
|
snapshotDir?: string;
|
|
1846
1939
|
minSnapshots?: number;
|
package/dist/lib/Utils.d.ts
CHANGED
|
@@ -95,7 +95,9 @@ declare function checkSnapshots(options?: {
|
|
|
95
95
|
export declare function resolveSnapshotFilePath(snapshotFile: Nullable<string>): string;
|
|
96
96
|
declare function getSnapshotDirForAnalysis(): string;
|
|
97
97
|
declare function getSingleSnapshotFileForAnalysis(): string;
|
|
98
|
-
declare function getSnapshotFilePath(tab: E2EStepInfo
|
|
98
|
+
declare function getSnapshotFilePath(tab: E2EStepInfo, options?: {
|
|
99
|
+
workDir?: string;
|
|
100
|
+
}): string;
|
|
99
101
|
declare function equalOrMatch(v1: any, v2: any): boolean;
|
|
100
102
|
declare function getSnapshotFilePathWithTabType(type: string | RegExp): Nullable<string>;
|
|
101
103
|
declare function isMeaningfulNode(node: IHeapNode): boolean;
|
|
@@ -123,8 +125,10 @@ declare function isNodeDominatedByDeletionsArray(node: IHeapNode): boolean;
|
|
|
123
125
|
declare function getUniqueID(): string;
|
|
124
126
|
declare function getClosureSourceUrl(node: IHeapNode): Nullable<string>;
|
|
125
127
|
export declare function runShell(command: string, options?: ShellOptions): Nullable<string>;
|
|
126
|
-
|
|
127
|
-
|
|
128
|
+
declare function getRetainedSize(node: IHeapNode): number;
|
|
129
|
+
declare function aggregateDominatorMetrics(ids: HeapNodeIdSet, snapshot: IHeapSnapshot, checkNodeCb: (node: IHeapNode) => boolean, nodeMetricsCb: (node: IHeapNode) => number): number;
|
|
130
|
+
declare function getLeakTracePathLength(path: LeakTracePathItem): number;
|
|
131
|
+
declare function getNumberAtPercentile(arr: number[], percentile: number): number;
|
|
128
132
|
declare const _default: {
|
|
129
133
|
aggregateDominatorMetrics: typeof aggregateDominatorMetrics;
|
|
130
134
|
applyToNodes: typeof applyToNodes;
|
|
@@ -147,7 +151,9 @@ declare const _default: {
|
|
|
147
151
|
getEdgeByNameAndType: typeof getEdgeByNameAndType;
|
|
148
152
|
getLastNodeId: typeof getLastNodeId;
|
|
149
153
|
getLeakedNode: typeof getLeakedNode;
|
|
154
|
+
getLeakTracePathLength: typeof getLeakTracePathLength;
|
|
150
155
|
getNodesIdSet: typeof getNodesIdSet;
|
|
156
|
+
getNumberAtPercentile: typeof getNumberAtPercentile;
|
|
151
157
|
getNumberNodeValue: typeof getNumberNodeValue;
|
|
152
158
|
getReadableBytes: typeof getReadableBytes;
|
|
153
159
|
getReadablePercent: typeof getReadablePercent;
|
package/dist/lib/Utils.js
CHANGED
|
@@ -44,7 +44,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
44
44
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
45
45
|
};
|
|
46
46
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
47
|
-
exports.
|
|
47
|
+
exports.runShell = exports.resolveSnapshotFilePath = void 0;
|
|
48
48
|
const fs_1 = __importDefault(require("fs"));
|
|
49
49
|
const path_1 = __importDefault(require("path"));
|
|
50
50
|
const child_process_1 = __importDefault(require("child_process"));
|
|
@@ -1272,7 +1272,7 @@ function checkSnapshots(options = {}) {
|
|
|
1272
1272
|
snapshotDir = Config_1.default.externalSnapshotDir || '<missing>';
|
|
1273
1273
|
}
|
|
1274
1274
|
else {
|
|
1275
|
-
snapshotDir = FileManager_1.default.getCurDataDir({});
|
|
1275
|
+
snapshotDir = FileManager_1.default.getCurDataDir({ workDir: Config_1.default.workDir });
|
|
1276
1276
|
}
|
|
1277
1277
|
if (options.snapshotDir) {
|
|
1278
1278
|
const snapshots = getSnapshotFilesInDir(snapshotDir);
|
|
@@ -1346,13 +1346,20 @@ function getSingleSnapshotFileForAnalysis() {
|
|
|
1346
1346
|
}
|
|
1347
1347
|
return resolveSnapshotFilePath(path);
|
|
1348
1348
|
}
|
|
1349
|
-
function getSnapshotFilePath(tab) {
|
|
1349
|
+
function getSnapshotFilePath(tab, options = {}) {
|
|
1350
|
+
if (tab.snapshotFile) {
|
|
1351
|
+
return path_1.default.join(FileManager_1.default.getCurDataDir(options), tab.snapshotFile);
|
|
1352
|
+
}
|
|
1353
|
+
const fileName = `s${tab.idx}.heapsnapshot`;
|
|
1354
|
+
if (options.workDir) {
|
|
1355
|
+
return path_1.default.join(FileManager_1.default.getCurDataDir(options), fileName);
|
|
1356
|
+
}
|
|
1350
1357
|
if (!Config_1.default.useExternalSnapshot) {
|
|
1351
|
-
return path_1.default.join(Config_1.default.curDataDir,
|
|
1358
|
+
return path_1.default.join(Config_1.default.curDataDir, fileName);
|
|
1352
1359
|
}
|
|
1353
1360
|
// if we are loading snapshot from external snapshot dir
|
|
1354
1361
|
if (Config_1.default.externalSnapshotDir) {
|
|
1355
|
-
return path_1.default.join(Config_1.default.externalSnapshotDir,
|
|
1362
|
+
return path_1.default.join(Config_1.default.externalSnapshotDir, fileName);
|
|
1356
1363
|
}
|
|
1357
1364
|
return Config_1.default.externalSnapshotFilePaths[tab.idx - 1];
|
|
1358
1365
|
}
|
|
@@ -1786,7 +1793,6 @@ exports.runShell = runShell;
|
|
|
1786
1793
|
function getRetainedSize(node) {
|
|
1787
1794
|
return node.retainedSize;
|
|
1788
1795
|
}
|
|
1789
|
-
exports.getRetainedSize = getRetainedSize;
|
|
1790
1796
|
function aggregateDominatorMetrics(ids, snapshot, checkNodeCb, nodeMetricsCb) {
|
|
1791
1797
|
let ret = 0;
|
|
1792
1798
|
const dominators = __1.utils.getConditionalDominatorIds(ids, snapshot, checkNodeCb);
|
|
@@ -1795,7 +1801,29 @@ function aggregateDominatorMetrics(ids, snapshot, checkNodeCb, nodeMetricsCb) {
|
|
|
1795
1801
|
});
|
|
1796
1802
|
return ret;
|
|
1797
1803
|
}
|
|
1798
|
-
|
|
1804
|
+
function getLeakTracePathLength(path) {
|
|
1805
|
+
let len = 0;
|
|
1806
|
+
let p = path;
|
|
1807
|
+
while (p) {
|
|
1808
|
+
p = p.next;
|
|
1809
|
+
++len;
|
|
1810
|
+
}
|
|
1811
|
+
return len;
|
|
1812
|
+
}
|
|
1813
|
+
function getNumberAtPercentile(arr, percentile) {
|
|
1814
|
+
arr.sort(function (a, b) {
|
|
1815
|
+
return a - b;
|
|
1816
|
+
});
|
|
1817
|
+
const index = (percentile / 100) * arr.length;
|
|
1818
|
+
const indexInt = Math.floor(index);
|
|
1819
|
+
if (indexInt === index) {
|
|
1820
|
+
return arr[Math.floor(index)];
|
|
1821
|
+
}
|
|
1822
|
+
if (indexInt + 1 < arr.length) {
|
|
1823
|
+
return (arr[indexInt] + arr[indexInt + 1]) / 2;
|
|
1824
|
+
}
|
|
1825
|
+
return arr[indexInt];
|
|
1826
|
+
}
|
|
1799
1827
|
exports.default = {
|
|
1800
1828
|
aggregateDominatorMetrics,
|
|
1801
1829
|
applyToNodes,
|
|
@@ -1818,7 +1846,9 @@ exports.default = {
|
|
|
1818
1846
|
getEdgeByNameAndType,
|
|
1819
1847
|
getLastNodeId,
|
|
1820
1848
|
getLeakedNode,
|
|
1849
|
+
getLeakTracePathLength,
|
|
1821
1850
|
getNodesIdSet,
|
|
1851
|
+
getNumberAtPercentile,
|
|
1822
1852
|
getNumberNodeValue,
|
|
1823
1853
|
getReadableBytes,
|
|
1824
1854
|
getReadablePercent,
|
|
@@ -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 { PlotMemoryOptions } from '../Types';
|
|
11
|
+
declare class MemoryBarChart {
|
|
12
|
+
plotMemoryBarChart(options?: PlotMemoryOptions): void;
|
|
13
|
+
private loadPlotDataFromTabsOrder;
|
|
14
|
+
private loadPlotDataFromWorkDir;
|
|
15
|
+
private loadPlotData;
|
|
16
|
+
private mergePlotData;
|
|
17
|
+
}
|
|
18
|
+
declare const _default: MemoryBarChart;
|
|
19
|
+
export default _default;
|
|
20
|
+
//# sourceMappingURL=MemoryBarChart.d.ts.map
|