@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.
@@ -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
- this.visualizeMemoryUsage();
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 { paths } = yield this.searchLeakedTraces(snapshotDiff.leakedHeapNodeIdSet, snapshotDiff.snapshot);
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(loadAll = false) {
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 options = { verbose: true };
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 (!loadAll && !tab.type) {
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, options);
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(Config_1.default.exploreResultFile, tabsOrderStr, 'UTF-8');
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(Config_1.default.exploreResultFile, `\n\n${pathStr}\n\n`, 'UTF-8');
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
- searchLeakedTraces(leakedNodeIds, snapshot) {
375
+ findLeakTraces(leakedNodeIds, snapshot, options = {}) {
342
376
  return __awaiter(this, void 0, void 0, function* () {
343
- const finder = this.preparePathFinder(snapshot);
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 and return a
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);
@@ -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;
@@ -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): string;
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
- export declare function getRetainedSize(node: IHeapNode): number;
127
- export declare function aggregateDominatorMetrics(ids: HeapNodeIdSet, snapshot: IHeapSnapshot, checkNodeCb: (node: IHeapNode) => boolean, nodeMetricsCb: (node: IHeapNode) => number): number;
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.aggregateDominatorMetrics = exports.getRetainedSize = exports.runShell = exports.resolveSnapshotFilePath = void 0;
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, `s${tab.idx}.heapsnapshot`);
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, `s${tab.idx}.heapsnapshot`);
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
- exports.aggregateDominatorMetrics = aggregateDominatorMetrics;
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