@memlab/heap-analysis 1.0.1 → 1.0.4
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/README.md +4 -3
- package/dist/BaseAnalysis.d.ts +82 -5
- package/dist/BaseAnalysis.js +78 -12
- package/dist/PluginUtils.d.ts +267 -1
- package/dist/PluginUtils.js +269 -3
- package/dist/__tests__/package.test.d.ts +2 -0
- package/dist/__tests__/package.test.js +85 -0
- package/dist/index.d.ts +7 -1
- package/dist/index.js +27 -5
- package/dist/plugins/CollectionsHoldingStaleAnalysis.d.ts +5 -0
- package/dist/plugins/CollectionsHoldingStaleAnalysis.js +11 -0
- package/dist/plugins/DetachedDOMElementAnalysis.d.ts +5 -0
- package/dist/plugins/DetachedDOMElementAnalysis.js +11 -0
- package/dist/plugins/GlobalVariableAnalysis/GlobalVariableAnalysis.d.ts +6 -0
- package/dist/plugins/GlobalVariableAnalysis/GlobalVariableAnalysis.js +13 -0
- package/dist/plugins/ObjectFanoutAnalysis.d.ts +5 -0
- package/dist/plugins/ObjectFanoutAnalysis.js +12 -0
- package/dist/plugins/ObjectShallowAnalysis.d.ts +27 -1
- package/dist/plugins/ObjectShallowAnalysis.js +25 -0
- package/dist/plugins/ObjectShapeAnalysis.d.ts +5 -0
- package/dist/plugins/ObjectShapeAnalysis.js +11 -0
- package/dist/plugins/ObjectSizeAnalysis.d.ts +5 -0
- package/dist/plugins/ObjectSizeAnalysis.js +12 -0
- package/dist/plugins/ObjectUnboundGrowthAnalysis.d.ts +5 -0
- package/dist/plugins/ObjectUnboundGrowthAnalysis.js +11 -0
- package/dist/plugins/ShapeUnboundGrowthAnalysis.d.ts +11 -15
- package/dist/plugins/ShapeUnboundGrowthAnalysis.js +16 -1
- package/dist/plugins/StringAnalysis.d.ts +34 -2
- package/dist/plugins/StringAnalysis.js +32 -3
- package/dist/plugins/UnmountedReactFiberNodesAnalysis.d.ts +5 -0
- package/dist/plugins/UnmountedReactFiberNodesAnalysis.js +11 -0
- package/package.json +3 -2
- package/dist/BaseAnalysis.d.ts.map +0 -1
- package/dist/HeapAnalysisLoader.d.ts.map +0 -1
- package/dist/PluginUtils.d.ts.map +0 -1
- package/dist/__tests__/HeapAnalysis.test.d.ts.map +0 -1
- package/dist/index.d.ts.map +0 -1
- package/dist/options/HeapAnalysisSnapshotDirectoryOption.d.ts.map +0 -1
- package/dist/options/HeapAnalysisSnapshotFileOption.d.ts.map +0 -1
- package/dist/plugins/CollectionsHoldingStaleAnalysis.d.ts.map +0 -1
- package/dist/plugins/DetachedDOMElementAnalysis.d.ts.map +0 -1
- package/dist/plugins/GlobalVariableAnalysis/BuiltInGlobalVariables.d.ts.map +0 -1
- package/dist/plugins/GlobalVariableAnalysis/GlobalVariableAnalysis.d.ts.map +0 -1
- package/dist/plugins/ObjectFanoutAnalysis.d.ts.map +0 -1
- package/dist/plugins/ObjectShallowAnalysis.d.ts.map +0 -1
- package/dist/plugins/ObjectShapeAnalysis.d.ts.map +0 -1
- package/dist/plugins/ObjectSizeAnalysis.d.ts.map +0 -1
- package/dist/plugins/ObjectUnboundGrowthAnalysis.d.ts.map +0 -1
- package/dist/plugins/ShapeUnboundGrowthAnalysis.d.ts.map +0 -1
- package/dist/plugins/StringAnalysis.d.ts.map +0 -1
- package/dist/plugins/UnmountedReactFiberNodesAnalysis.d.ts.map +0 -1
package/dist/PluginUtils.js
CHANGED
|
@@ -51,9 +51,13 @@ function isNodeWorthInspecting(node) {
|
|
|
51
51
|
}
|
|
52
52
|
return true;
|
|
53
53
|
}
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
54
|
+
/**
|
|
55
|
+
* filter out dominators that have a similar size, for example if
|
|
56
|
+
* input is [A, B] and A is the dominator of B, then this function
|
|
57
|
+
* throw away A if the size of A is close to the size of B
|
|
58
|
+
* @param nodeList an array of heap nodes
|
|
59
|
+
* @returns an array of heap nodes with dominators that have similar size removed
|
|
60
|
+
*/
|
|
57
61
|
function filterOutDominators(nodeList) {
|
|
58
62
|
const candidateIdSet = new Set(nodeList.map(node => node.id));
|
|
59
63
|
const childrenSizeInList = new Map();
|
|
@@ -182,6 +186,46 @@ function getObjectOutgoingEdgeCount(node) {
|
|
|
182
186
|
}
|
|
183
187
|
return node.edge_count;
|
|
184
188
|
}
|
|
189
|
+
/**
|
|
190
|
+
* Get the heap snapshot file's absolute path passed to the hosting heap
|
|
191
|
+
* analysis via `HeapAnalysisOptions`.
|
|
192
|
+
*
|
|
193
|
+
* This API is supposed to be used within the overridden `process` method
|
|
194
|
+
* of an `BaseAnalysis` instance.
|
|
195
|
+
*
|
|
196
|
+
* @param options this is the auto-generated input passed to all the `BaseAnalysis` instances
|
|
197
|
+
* @returns the absolute path of the heap snapshot file
|
|
198
|
+
* * **Examples:**
|
|
199
|
+
* ```typescript
|
|
200
|
+
* import type {IHeapSnapshot} from '@memlab/core';
|
|
201
|
+
* import type {HeapAnalysisOptions} from '@memlab/heap-analysis';
|
|
202
|
+
* import {getSnapshotFileForAnalysis, BaseAnalysis} from '@memlab/heap-analysis';
|
|
203
|
+
*
|
|
204
|
+
* class ExampleAnalysis extends BaseAnalysis {
|
|
205
|
+
* public getCommandName(): string {
|
|
206
|
+
* return 'example-analysis';
|
|
207
|
+
* }
|
|
208
|
+
*
|
|
209
|
+
* public getDescription(): string {
|
|
210
|
+
* return 'an example analysis for demo';
|
|
211
|
+
* }
|
|
212
|
+
*
|
|
213
|
+
* async process(options: HeapAnalysisOptions): Promise<void> {
|
|
214
|
+
* const file = getSnapshotFileForAnalysis(options);
|
|
215
|
+
* }
|
|
216
|
+
* }
|
|
217
|
+
* ```
|
|
218
|
+
*
|
|
219
|
+
* Use the following code to invoke the heap analysis:
|
|
220
|
+
* ```typescript
|
|
221
|
+
* const analysis = new ExampleAnalysis();
|
|
222
|
+
* // any .heapsnapshot file recorded by memlab or saved manually from Chrome
|
|
223
|
+
* await analysis.analyzeSnapshotFromFile(snapshotFile);
|
|
224
|
+
* ```
|
|
225
|
+
* The new heap analysis can also be used with {@link analyze}, in that case
|
|
226
|
+
* `getSnapshotFileForAnalysis` will use the last heap snapshot in alphanumerically
|
|
227
|
+
* ascending order from {@link BrowserInteractionResultReader}.
|
|
228
|
+
*/
|
|
185
229
|
function getSnapshotFileForAnalysis(options) {
|
|
186
230
|
const args = options.args;
|
|
187
231
|
if (args.snapshot) {
|
|
@@ -192,6 +236,47 @@ function getSnapshotFileForAnalysis(options) {
|
|
|
192
236
|
}
|
|
193
237
|
return core_2.utils.getSingleSnapshotFileForAnalysis();
|
|
194
238
|
}
|
|
239
|
+
/**
|
|
240
|
+
* Get the absolute path of the directory holding all the heap snapshot files
|
|
241
|
+
* passed to the hosting heap analysis via `HeapAnalysisOptions`.
|
|
242
|
+
*
|
|
243
|
+
* This API is supposed to be used within the overridden `process` method
|
|
244
|
+
* of an `BaseAnalysis` instance.
|
|
245
|
+
*
|
|
246
|
+
* @param options this is the auto-generated input passed
|
|
247
|
+
* to all the `BaseAnalysis` instances
|
|
248
|
+
* @returns the absolute path of the directory
|
|
249
|
+
* * **Examples:**
|
|
250
|
+
* ```typescript
|
|
251
|
+
* import type {IHeapSnapshot} from '@memlab/core';
|
|
252
|
+
* import type {HeapAnalysisOptions} from '@memlab/heap-analysis';
|
|
253
|
+
* import {getSnapshotFileForAnalysis, BaseAnalysis} from '@memlab/heap-analysis';
|
|
254
|
+
*
|
|
255
|
+
* class ExampleAnalysis extends BaseAnalysis {
|
|
256
|
+
* public getCommandName(): string {
|
|
257
|
+
* return 'example-analysis';
|
|
258
|
+
* }
|
|
259
|
+
*
|
|
260
|
+
* public getDescription(): string {
|
|
261
|
+
* return 'an example analysis for demo';
|
|
262
|
+
* }
|
|
263
|
+
*
|
|
264
|
+
* async process(options: HeapAnalysisOptions): Promise<void> {
|
|
265
|
+
* const directory = getSnapshotDirForAnalysis(options);
|
|
266
|
+
* }
|
|
267
|
+
* }
|
|
268
|
+
* ```
|
|
269
|
+
*
|
|
270
|
+
* Use the following code to invoke the heap analysis:
|
|
271
|
+
* ```typescript
|
|
272
|
+
* const analysis = new ExampleAnalysis();
|
|
273
|
+
* // any .heapsnapshot file recorded by memlab or saved manually from Chrome
|
|
274
|
+
* await analysis.analyzeSnapshotFromFile(snapshotFile);
|
|
275
|
+
* ```
|
|
276
|
+
* The new heap analysis can also be used with {@link analyze}, in that case
|
|
277
|
+
* `getSnapshotDirForAnalysis` use the snapshot directory from
|
|
278
|
+
* {@link BrowserInteractionResultReader}.
|
|
279
|
+
*/
|
|
195
280
|
function getSnapshotDirForAnalysis(options) {
|
|
196
281
|
const args = options.args;
|
|
197
282
|
if (args['snapshot-dir']) {
|
|
@@ -202,21 +287,147 @@ function getSnapshotDirForAnalysis(options) {
|
|
|
202
287
|
}
|
|
203
288
|
return null;
|
|
204
289
|
}
|
|
290
|
+
/**
|
|
291
|
+
* Load the heap graph based on the single JavaScript heap snapshot
|
|
292
|
+
* passed to the hosting heap analysis via `HeapAnalysisOptions`.
|
|
293
|
+
*
|
|
294
|
+
* This API is supposed to be used within the `process` implementation
|
|
295
|
+
* of an `BaseAnalysis` instance.
|
|
296
|
+
*
|
|
297
|
+
* @param options this is the auto-generated input passed to all the `BaseAnalysis` instances
|
|
298
|
+
* @returns the graph representation of the heap
|
|
299
|
+
* * **Examples:**
|
|
300
|
+
* ```typescript
|
|
301
|
+
* import type {IHeapSnapshot} from '@memlab/core';
|
|
302
|
+
* import type {HeapAnalysisOptions} from '@memlab/heap-analysis';
|
|
303
|
+
* import {loadHeapSnapshot, BaseAnalysis} from '@memlab/heap-analysis';
|
|
304
|
+
*
|
|
305
|
+
* class ExampleAnalysis extends BaseAnalysis {
|
|
306
|
+
* public getCommandName(): string {
|
|
307
|
+
* return 'example-analysis';
|
|
308
|
+
* }
|
|
309
|
+
*
|
|
310
|
+
* public getDescription(): string {
|
|
311
|
+
* return 'an example analysis for demo';
|
|
312
|
+
* }
|
|
313
|
+
*
|
|
314
|
+
* async process(options: HeapAnalysisOptions): Promise<void> {
|
|
315
|
+
* const heap = await loadHeapSnapshot(options);
|
|
316
|
+
* // doing heap analysis
|
|
317
|
+
* }
|
|
318
|
+
* }
|
|
319
|
+
* ```
|
|
320
|
+
*
|
|
321
|
+
* Use the following code to invoke the heap analysis:
|
|
322
|
+
* ```typescript
|
|
323
|
+
* const analysis = new ExampleAnalysis();
|
|
324
|
+
* // any .heapsnapshot file recorded by memlab or saved manually from Chrome
|
|
325
|
+
* await analysis.analyzeSnapshotFromFile(snapshotFile);
|
|
326
|
+
* ```
|
|
327
|
+
* The new heap analysis can also be used with {@link analyze}, in that case
|
|
328
|
+
* `loadHeapSnapshot` will use the last heap snapshot in alphanumerically
|
|
329
|
+
* ascending order from {@link BrowserInteractionResultReader}.
|
|
330
|
+
*/
|
|
205
331
|
function loadHeapSnapshot(options) {
|
|
206
332
|
return __awaiter(this, void 0, void 0, function* () {
|
|
207
333
|
const file = getSnapshotFileForAnalysis(options);
|
|
208
334
|
return loadProcessedSnapshot({ file });
|
|
209
335
|
});
|
|
210
336
|
}
|
|
337
|
+
/**
|
|
338
|
+
* Load and parse a `.heapsnapshot` file and calculate meta data like
|
|
339
|
+
* dominator nodes and retained sizes.
|
|
340
|
+
* @param file the absolute path of the `.heapsnapshot` file
|
|
341
|
+
* @returns the heap graph representation instance that supports querying
|
|
342
|
+
* the heap
|
|
343
|
+
* * **Examples**:
|
|
344
|
+
* ```typescript
|
|
345
|
+
* import {dumpNodeHeapSnapshot} from '@memlab/core';
|
|
346
|
+
* import {getHeapFromFile} from '@memlab/heap-analysis';
|
|
347
|
+
*
|
|
348
|
+
* (async function (){
|
|
349
|
+
* const heapFile = dumpNodeHeapSnapshot();
|
|
350
|
+
* const heap = await getHeapFromFile(heapFile);
|
|
351
|
+
* })();
|
|
352
|
+
* ```
|
|
353
|
+
*/
|
|
354
|
+
function getHeapFromFile(file) {
|
|
355
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
356
|
+
return yield loadProcessedSnapshot({ file });
|
|
357
|
+
});
|
|
358
|
+
}
|
|
211
359
|
function loadProcessedSnapshot(options = {}) {
|
|
212
360
|
return __awaiter(this, void 0, void 0, function* () {
|
|
213
361
|
const opt = { buildNodeIdIndex: true, verbose: true };
|
|
214
362
|
const file = options.file || core_2.utils.getSnapshotFilePathWithTabType(/.*/);
|
|
215
363
|
const snapshot = yield core_2.utils.getSnapshotFromFile(file, opt);
|
|
216
364
|
core_2.analysis.preparePathFinder(snapshot);
|
|
365
|
+
core_2.info.flush();
|
|
217
366
|
return snapshot;
|
|
218
367
|
});
|
|
219
368
|
}
|
|
369
|
+
/**
|
|
370
|
+
* When a heap analysis is taking multiple heap snapshots as input for memory
|
|
371
|
+
* analysis (e.g., finding which object keeps growing in size in a series of
|
|
372
|
+
* heap snapshots), this API could be used to do
|
|
373
|
+
* [MapRedue](https://en.wikipedia.org/wiki/MapReduce) on all heap snapshots.
|
|
374
|
+
*
|
|
375
|
+
* This API is supposed to be used within the `process` implementation
|
|
376
|
+
* of an `BaseAnalysis` instance that is designed to analyze multiple heap
|
|
377
|
+
* snapshots (as an example, finding which object keeps growing overtime)
|
|
378
|
+
*
|
|
379
|
+
* @param mapCallback the map function in MapReduce, the function will be applied
|
|
380
|
+
* to each heap snapshot
|
|
381
|
+
* @param reduceCallback the reduce function in MapReduce, the function will take
|
|
382
|
+
* as input all intermediate results from all map function calls
|
|
383
|
+
* @typeParam T1 - the type of the intermediate result from each map function call
|
|
384
|
+
* @typeParam T2 - the type of the final result of the reduce function call
|
|
385
|
+
* @param options this is the auto-generated input passed to all the `BaseAnalysis` instances
|
|
386
|
+
* @returns the return value of your reduce function
|
|
387
|
+
* * **Examples:**
|
|
388
|
+
* ```typescript
|
|
389
|
+
* import type {IHeapSnapshot} from '@memlab/core';
|
|
390
|
+
* import type {HeapAnalysisOptions} from '@memlab/heap-analysis';
|
|
391
|
+
* import {snapshotMapReduce, BaseAnalysis} from '@memlab/heap-analysis';
|
|
392
|
+
*
|
|
393
|
+
* class ExampleAnalysis extends BaseAnalysis {
|
|
394
|
+
* public getCommandName(): string {
|
|
395
|
+
* return 'example-analysis';
|
|
396
|
+
* }
|
|
397
|
+
*
|
|
398
|
+
* public getDescription(): string {
|
|
399
|
+
* return 'an example analysis for demo';
|
|
400
|
+
* }
|
|
401
|
+
*
|
|
402
|
+
* async process(options: HeapAnalysisOptions): Promise<void> {
|
|
403
|
+
* // check if the number of heap objects keeps growing overtime
|
|
404
|
+
* const isMonotonicIncreasing = await snapshotMapReduce(
|
|
405
|
+
* (heap) => heap.nodes.length,
|
|
406
|
+
* (nodeCounts) =>
|
|
407
|
+
* nodeCounts[0] < nodeCounts[nodeCounts.length - 1] &&
|
|
408
|
+
* nodeCounts.every((count, i) => i === 0 || count >= nodeCounts[i - 1]),
|
|
409
|
+
* options,
|
|
410
|
+
* );
|
|
411
|
+
* }
|
|
412
|
+
* }
|
|
413
|
+
* ```
|
|
414
|
+
*
|
|
415
|
+
* Use the following code to invoke the heap analysis:
|
|
416
|
+
* ```typescript
|
|
417
|
+
* const analysis = new ExampleAnalysis();
|
|
418
|
+
* // snapshotDir includes a series of .heapsnapshot files recorded by
|
|
419
|
+
* // memlab or saved manually from Chrome, those files will be loaded
|
|
420
|
+
* // in alphanumerically asceneding order
|
|
421
|
+
* await analysis.analyzeSnapshotsInDirectory(snapshotDir);
|
|
422
|
+
* ```
|
|
423
|
+
* The new heap analysis can also be used with {@link analyze}, in that case
|
|
424
|
+
* `snapshotMapReduce` will use all the heap snapshot in alphanumerically
|
|
425
|
+
* ascending order from {@link BrowserInteractionResultReader}.
|
|
426
|
+
*
|
|
427
|
+
* **Why not passing in all heap snapshots as an array of {@link IHeapSnapshot}s?**
|
|
428
|
+
* Each heap snapshot could be non-trivial in size, loading them all at once
|
|
429
|
+
* may not be possible.
|
|
430
|
+
*/
|
|
220
431
|
function snapshotMapReduce(mapCallback, reduceCallback, options) {
|
|
221
432
|
return __awaiter(this, void 0, void 0, function* () {
|
|
222
433
|
const snapshotDir = getSnapshotDirForAnalysis(options);
|
|
@@ -239,6 +450,18 @@ function snapshotMapReduce(mapCallback, reduceCallback, options) {
|
|
|
239
450
|
return reduceCallback(intermediateResults);
|
|
240
451
|
});
|
|
241
452
|
}
|
|
453
|
+
/**
|
|
454
|
+
* This API aggregates metrics from the
|
|
455
|
+
* [dominator nodes](https://firefox-source-docs.mozilla.org/devtools-user/memory/dominators/index.html)
|
|
456
|
+
* of the set of input heap objects.
|
|
457
|
+
*
|
|
458
|
+
* @param ids Set of ids of heap objects (or nodes)
|
|
459
|
+
* @param snapshot heap graph loaded from a heap snapshot
|
|
460
|
+
* @param checkNodeCb filter callback to exclude some heap object/nodes
|
|
461
|
+
* before calculating the dominator nodes
|
|
462
|
+
* @param nodeMetricsCb callback to calculate metrics from each dominator node
|
|
463
|
+
* @returns the aggregated metrics
|
|
464
|
+
*/
|
|
242
465
|
function aggregateDominatorMetrics(ids, snapshot, checkNodeCb, nodeMetricsCb) {
|
|
243
466
|
let ret = 0;
|
|
244
467
|
const dominators = core_2.utils.getConditionalDominatorIds(ids, snapshot, checkNodeCb);
|
|
@@ -247,6 +470,47 @@ function aggregateDominatorMetrics(ids, snapshot, checkNodeCb, nodeMetricsCb) {
|
|
|
247
470
|
});
|
|
248
471
|
return ret;
|
|
249
472
|
}
|
|
473
|
+
/**
|
|
474
|
+
* This API calculate the set of
|
|
475
|
+
* [dominator nodes](https://firefox-source-docs.mozilla.org/devtools-user/memory/dominators/index.html)
|
|
476
|
+
* of the set of input heap objects.
|
|
477
|
+
* @param ids Set of ids of heap objects (or nodes)
|
|
478
|
+
* @param snapshot heap loaded from a heap snapshot
|
|
479
|
+
* @returns the set of dominator nodes/objects
|
|
480
|
+
* * * **Examples**:
|
|
481
|
+
* ```typescript
|
|
482
|
+
* import {dumpNodeHeapSnapshot} from '@memlab/core';
|
|
483
|
+
* import {getHeapFromFile, getDominatorNodes} from '@memlab/heap-analysis';
|
|
484
|
+
*
|
|
485
|
+
* class TestObject {}
|
|
486
|
+
*
|
|
487
|
+
* (async function () {
|
|
488
|
+
* const t1 = new TestObject();
|
|
489
|
+
* const t2 = new TestObject();
|
|
490
|
+
*
|
|
491
|
+
* // dump the heap of this running JavaScript program
|
|
492
|
+
* const heapFile = dumpNodeHeapSnapshot();
|
|
493
|
+
* const heap = await getHeapFromFile(heapFile);
|
|
494
|
+
*
|
|
495
|
+
* // find the heap node for TestObject
|
|
496
|
+
* let nodes = [];
|
|
497
|
+
* heap.nodes.forEach(node => {
|
|
498
|
+
* if (node.name === 'TestObject' && node.type === 'object') {
|
|
499
|
+
* nodes.push(node);
|
|
500
|
+
* }
|
|
501
|
+
* });
|
|
502
|
+
*
|
|
503
|
+
* // get the dominator nodes
|
|
504
|
+
* const dominatorIds = getDominatorNodes(
|
|
505
|
+
* new Set(nodes.map(node => node.id)),
|
|
506
|
+
* heap,
|
|
507
|
+
* );
|
|
508
|
+
* })();
|
|
509
|
+
* ```
|
|
510
|
+
*/
|
|
511
|
+
function getDominatorNodes(ids, snapshot) {
|
|
512
|
+
return core_2.utils.getConditionalDominatorIds(ids, snapshot, () => true);
|
|
513
|
+
}
|
|
250
514
|
function filterOutLargestObjects(snapshot, objectFilter, listSize = 50) {
|
|
251
515
|
let largeObjects = [];
|
|
252
516
|
snapshot.nodes.forEach(node => {
|
|
@@ -272,11 +536,13 @@ exports.default = {
|
|
|
272
536
|
aggregateDominatorMetrics,
|
|
273
537
|
defaultAnalysisArgs,
|
|
274
538
|
filterOutLargestObjects,
|
|
539
|
+
getDominatorNodes,
|
|
275
540
|
getObjectOutgoingEdgeCount,
|
|
276
541
|
getSnapshotDirForAnalysis,
|
|
277
542
|
getSnapshotFileForAnalysis,
|
|
278
543
|
isNodeWorthInspecting,
|
|
279
544
|
loadHeapSnapshot,
|
|
545
|
+
getHeapFromFile,
|
|
280
546
|
printNodeListInTerminal,
|
|
281
547
|
printReferencesInTerminal,
|
|
282
548
|
snapshotMapReduce,
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
3
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
4
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
5
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
6
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
7
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
8
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
9
|
+
});
|
|
10
|
+
};
|
|
11
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
+
const core_1 = require("@memlab/core");
|
|
13
|
+
const index_1 = require("../index");
|
|
14
|
+
beforeEach(() => {
|
|
15
|
+
core_1.config.isTest = true;
|
|
16
|
+
});
|
|
17
|
+
test('loadHeapSnapshot works as expected', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
18
|
+
let called = false;
|
|
19
|
+
class ExampleAnalysis extends index_1.BaseAnalysis {
|
|
20
|
+
getCommandName() {
|
|
21
|
+
return 'example-analysis';
|
|
22
|
+
}
|
|
23
|
+
getDescription() {
|
|
24
|
+
return 'an example analysis for demo';
|
|
25
|
+
}
|
|
26
|
+
process(options) {
|
|
27
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
28
|
+
const heap = yield (0, index_1.loadHeapSnapshot)(options);
|
|
29
|
+
called = true;
|
|
30
|
+
expect(heap.nodes.length > 0).toBe(true);
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
const analysis = new ExampleAnalysis();
|
|
35
|
+
yield analysis.analyzeSnapshotFromFile((0, core_1.dumpNodeHeapSnapshot)());
|
|
36
|
+
expect(called).toBe(true);
|
|
37
|
+
}));
|
|
38
|
+
test('analyzeSnapshotFromFile works as expected', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
39
|
+
let called = false;
|
|
40
|
+
const heapFile = (0, core_1.dumpNodeHeapSnapshot)();
|
|
41
|
+
class ExampleAnalysis extends index_1.BaseAnalysis {
|
|
42
|
+
getCommandName() {
|
|
43
|
+
return 'example-analysis';
|
|
44
|
+
}
|
|
45
|
+
getDescription() {
|
|
46
|
+
return 'an example analysis for demo';
|
|
47
|
+
}
|
|
48
|
+
process(options) {
|
|
49
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
50
|
+
const file = (0, index_1.getSnapshotFileForAnalysis)(options);
|
|
51
|
+
called = true;
|
|
52
|
+
expect(file).toBe(heapFile);
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
const analysis = new ExampleAnalysis();
|
|
57
|
+
yield analysis.analyzeSnapshotFromFile(heapFile);
|
|
58
|
+
expect(called).toBe(true);
|
|
59
|
+
}));
|
|
60
|
+
test('getHeapFromFile works as expected', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
61
|
+
const heapFile = (0, core_1.dumpNodeHeapSnapshot)();
|
|
62
|
+
const heap = yield (0, index_1.getHeapFromFile)(heapFile);
|
|
63
|
+
expect(heap.nodes.length > 0).toBe(true);
|
|
64
|
+
}));
|
|
65
|
+
test('getDominatorNodes works as expected', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
66
|
+
class TestObject {
|
|
67
|
+
}
|
|
68
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
69
|
+
const t1 = new TestObject();
|
|
70
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
71
|
+
const t2 = new TestObject();
|
|
72
|
+
// dump the heap of this running JavaScript program
|
|
73
|
+
const heapFile = (0, core_1.dumpNodeHeapSnapshot)();
|
|
74
|
+
const heap = yield (0, index_1.getHeapFromFile)(heapFile);
|
|
75
|
+
// find the heap node for TestObject
|
|
76
|
+
const nodes = [];
|
|
77
|
+
heap.nodes.forEach(node => {
|
|
78
|
+
if (node.name === 'TestObject' && node.type === 'object') {
|
|
79
|
+
nodes.push(node);
|
|
80
|
+
}
|
|
81
|
+
});
|
|
82
|
+
// get the dominator nodes
|
|
83
|
+
const dominatorIds = (0, index_1.getDominatorNodes)(new Set(nodes.map(node => node.id)), heap);
|
|
84
|
+
expect(dominatorIds.size).toBeGreaterThan(0);
|
|
85
|
+
}));
|
package/dist/index.d.ts
CHANGED
|
@@ -7,7 +7,10 @@
|
|
|
7
7
|
* @emails oncall+ws_labs
|
|
8
8
|
* @format
|
|
9
9
|
*/
|
|
10
|
-
|
|
10
|
+
/** @internal */
|
|
11
|
+
export declare function registerPackage(): Promise<void>;
|
|
12
|
+
export declare const getDominatorNodes: (ids: Set<number>, snapshot: import("@memlab/core").IHeapSnapshot) => Set<number>, getHeapFromFile: (file: string) => Promise<import("@memlab/core").IHeapSnapshot>, getSnapshotDirForAnalysis: (options: import("./PluginUtils").HeapAnalysisOptions) => import("@memlab/core").Nullable<string>, getSnapshotFileForAnalysis: (options: import("./PluginUtils").HeapAnalysisOptions) => string, loadHeapSnapshot: (options: import("./PluginUtils").HeapAnalysisOptions) => Promise<import("@memlab/core").IHeapSnapshot>, snapshotMapReduce: <T1, T2>(mapCallback: (snapshot: import("@memlab/core").IHeapSnapshot, i: number, file: string) => T1, reduceCallback: (results: T1[]) => T2, options: import("./PluginUtils").HeapAnalysisOptions) => Promise<T2>;
|
|
13
|
+
export type { HeapAnalysisOptions } from './PluginUtils';
|
|
11
14
|
export { default as BaseAnalysis } from './BaseAnalysis';
|
|
12
15
|
export { default as DetachedDOMElementAnalysis } from './plugins/DetachedDOMElementAnalysis';
|
|
13
16
|
export { default as GlobalVariableAnalysis } from './plugins/GlobalVariableAnalysis/GlobalVariableAnalysis';
|
|
@@ -19,5 +22,8 @@ export { default as ObjectFanoutAnalysis } from './plugins/ObjectFanoutAnalysis'
|
|
|
19
22
|
export { default as ObjectShapeAnalysis } from './plugins/ObjectShapeAnalysis';
|
|
20
23
|
export { default as ObjectUnboundGrowthAnalysis } from './plugins/ObjectUnboundGrowthAnalysis';
|
|
21
24
|
export { default as StringAnalysis } from './plugins/StringAnalysis';
|
|
25
|
+
/** @internal */
|
|
22
26
|
export { default as PluginUtils } from './PluginUtils';
|
|
27
|
+
/** @internal */
|
|
28
|
+
export { default as heapAnalysisLoader } from './HeapAnalysisLoader';
|
|
23
29
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.js
CHANGED
|
@@ -8,13 +8,31 @@
|
|
|
8
8
|
* @emails oncall+ws_labs
|
|
9
9
|
* @format
|
|
10
10
|
*/
|
|
11
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
12
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
13
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
14
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
15
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
16
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
17
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
18
|
+
});
|
|
19
|
+
};
|
|
11
20
|
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
12
21
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
13
22
|
};
|
|
14
23
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
15
|
-
exports.PluginUtils = exports.StringAnalysis = exports.ObjectUnboundGrowthAnalysis = exports.ObjectShapeAnalysis = exports.ObjectFanoutAnalysis = exports.ShapeUnboundGrowthAnalysis = exports.ObjectSizeAnalysis = exports.ObjectShallowAnalysis = exports.CollectionsHoldingStaleAnalysis = exports.GlobalVariableAnalysis = exports.DetachedDOMElementAnalysis = exports.BaseAnalysis = exports.
|
|
16
|
-
|
|
17
|
-
|
|
24
|
+
exports.heapAnalysisLoader = exports.PluginUtils = exports.StringAnalysis = exports.ObjectUnboundGrowthAnalysis = exports.ObjectShapeAnalysis = exports.ObjectFanoutAnalysis = exports.ShapeUnboundGrowthAnalysis = exports.ObjectSizeAnalysis = exports.ObjectShallowAnalysis = exports.CollectionsHoldingStaleAnalysis = exports.GlobalVariableAnalysis = exports.DetachedDOMElementAnalysis = exports.BaseAnalysis = exports.snapshotMapReduce = exports.loadHeapSnapshot = exports.getSnapshotFileForAnalysis = exports.getSnapshotDirForAnalysis = exports.getHeapFromFile = exports.getDominatorNodes = exports.registerPackage = void 0;
|
|
25
|
+
const path_1 = __importDefault(require("path"));
|
|
26
|
+
const core_1 = require("@memlab/core");
|
|
27
|
+
/** @internal */
|
|
28
|
+
function registerPackage() {
|
|
29
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
30
|
+
return core_1.PackageInfoLoader.registerPackage(path_1.default.join(__dirname, '..'));
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
exports.registerPackage = registerPackage;
|
|
34
|
+
const PluginUtils_1 = __importDefault(require("./PluginUtils"));
|
|
35
|
+
exports.getDominatorNodes = PluginUtils_1.default.getDominatorNodes, exports.getHeapFromFile = PluginUtils_1.default.getHeapFromFile, exports.getSnapshotDirForAnalysis = PluginUtils_1.default.getSnapshotDirForAnalysis, exports.getSnapshotFileForAnalysis = PluginUtils_1.default.getSnapshotFileForAnalysis, exports.loadHeapSnapshot = PluginUtils_1.default.loadHeapSnapshot, exports.snapshotMapReduce = PluginUtils_1.default.snapshotMapReduce;
|
|
18
36
|
var BaseAnalysis_1 = require("./BaseAnalysis");
|
|
19
37
|
Object.defineProperty(exports, "BaseAnalysis", { enumerable: true, get: function () { return __importDefault(BaseAnalysis_1).default; } });
|
|
20
38
|
var DetachedDOMElementAnalysis_1 = require("./plugins/DetachedDOMElementAnalysis");
|
|
@@ -37,5 +55,9 @@ var ObjectUnboundGrowthAnalysis_1 = require("./plugins/ObjectUnboundGrowthAnalys
|
|
|
37
55
|
Object.defineProperty(exports, "ObjectUnboundGrowthAnalysis", { enumerable: true, get: function () { return __importDefault(ObjectUnboundGrowthAnalysis_1).default; } });
|
|
38
56
|
var StringAnalysis_1 = require("./plugins/StringAnalysis");
|
|
39
57
|
Object.defineProperty(exports, "StringAnalysis", { enumerable: true, get: function () { return __importDefault(StringAnalysis_1).default; } });
|
|
40
|
-
|
|
41
|
-
|
|
58
|
+
/** @internal */
|
|
59
|
+
var PluginUtils_2 = require("./PluginUtils");
|
|
60
|
+
Object.defineProperty(exports, "PluginUtils", { enumerable: true, get: function () { return __importDefault(PluginUtils_2).default; } });
|
|
61
|
+
/** @internal */
|
|
62
|
+
var HeapAnalysisLoader_1 = require("./HeapAnalysisLoader");
|
|
63
|
+
Object.defineProperty(exports, "heapAnalysisLoader", { enumerable: true, get: function () { return __importDefault(HeapAnalysisLoader_1).default; } });
|
|
@@ -12,9 +12,14 @@ import { BaseOption } from '@memlab/core';
|
|
|
12
12
|
import BaseAnalysis from '../BaseAnalysis';
|
|
13
13
|
export default class CollectionsHoldingStaleAnalysis extends BaseAnalysis {
|
|
14
14
|
getCommandName(): string;
|
|
15
|
+
/** @internal */
|
|
15
16
|
getDescription(): string;
|
|
17
|
+
/** @internal */
|
|
16
18
|
getOptions(): BaseOption[];
|
|
19
|
+
/** @internal */
|
|
20
|
+
analyzeSnapshotsInDirectory(directory: string): Promise<void>;
|
|
17
21
|
private staleCollectionMapper;
|
|
22
|
+
/** @internal */
|
|
18
23
|
process(options: HeapAnalysisOptions): Promise<void>;
|
|
19
24
|
private getCollectionsWithStaleValues;
|
|
20
25
|
private print;
|
|
@@ -69,12 +69,23 @@ class CollectionsHoldingStaleAnalysis extends BaseAnalysis_1.default {
|
|
|
69
69
|
getCommandName() {
|
|
70
70
|
return 'collections-with-stale';
|
|
71
71
|
}
|
|
72
|
+
/** @internal */
|
|
72
73
|
getDescription() {
|
|
73
74
|
return 'Analyze collections holding stale objects';
|
|
74
75
|
}
|
|
76
|
+
/** @internal */
|
|
75
77
|
getOptions() {
|
|
76
78
|
return [new HeapAnalysisSnapshotFileOption_1.default()];
|
|
77
79
|
}
|
|
80
|
+
/** @internal */
|
|
81
|
+
analyzeSnapshotsInDirectory(directory) {
|
|
82
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
83
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
84
|
+
const d = directory;
|
|
85
|
+
throw core_1.utils.haltOrThrow(`${this.constructor.name} does not support analyzeSnapshotsInDirectory`);
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
/** @internal */
|
|
78
89
|
process(options) {
|
|
79
90
|
return __awaiter(this, void 0, void 0, function* () {
|
|
80
91
|
const snapshot = yield PluginUtils_1.default.loadHeapSnapshot(options);
|
|
@@ -13,10 +13,15 @@ import { BaseOption } from '@memlab/core';
|
|
|
13
13
|
import BaseAnalysis from '../BaseAnalysis';
|
|
14
14
|
export default class DetachedDOMElementAnalysis extends BaseAnalysis {
|
|
15
15
|
getCommandName(): string;
|
|
16
|
+
/** @internal */
|
|
16
17
|
getDescription(): string;
|
|
18
|
+
/** @internal */
|
|
17
19
|
getOptions(): BaseOption[];
|
|
20
|
+
/** @internal */
|
|
21
|
+
analyzeSnapshotsInDirectory(directory: string): Promise<void>;
|
|
18
22
|
private detachedElements;
|
|
19
23
|
getDetachedElements(): IHeapNode[];
|
|
24
|
+
/** @internal */
|
|
20
25
|
process(options: HeapAnalysisOptions): Promise<void>;
|
|
21
26
|
}
|
|
22
27
|
//# sourceMappingURL=DetachedDOMElementAnalysis.d.ts.map
|
|
@@ -33,15 +33,26 @@ class DetachedDOMElementAnalysis extends BaseAnalysis_1.default {
|
|
|
33
33
|
getCommandName() {
|
|
34
34
|
return 'detached-DOM';
|
|
35
35
|
}
|
|
36
|
+
/** @internal */
|
|
36
37
|
getDescription() {
|
|
37
38
|
return 'Get detached DOM elements';
|
|
38
39
|
}
|
|
40
|
+
/** @internal */
|
|
39
41
|
getOptions() {
|
|
40
42
|
return [new HeapAnalysisSnapshotFileOption_1.default()];
|
|
41
43
|
}
|
|
44
|
+
/** @internal */
|
|
45
|
+
analyzeSnapshotsInDirectory(directory) {
|
|
46
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
47
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
48
|
+
const d = directory;
|
|
49
|
+
throw core_1.utils.haltOrThrow(`${this.constructor.name} does not support analyzeSnapshotsInDirectory`);
|
|
50
|
+
});
|
|
51
|
+
}
|
|
42
52
|
getDetachedElements() {
|
|
43
53
|
return this.detachedElements;
|
|
44
54
|
}
|
|
55
|
+
/** @internal */
|
|
45
56
|
process(options) {
|
|
46
57
|
return __awaiter(this, void 0, void 0, function* () {
|
|
47
58
|
const snapshot = yield PluginUtils_1.default.loadHeapSnapshot(options);
|
|
@@ -12,10 +12,16 @@ import { BaseOption } from '@memlab/core';
|
|
|
12
12
|
import BaseAnalysis from '../../BaseAnalysis';
|
|
13
13
|
declare class GlobalVariableAnalysis extends BaseAnalysis {
|
|
14
14
|
getCommandName(): string;
|
|
15
|
+
/** @internal */
|
|
15
16
|
getDescription(): string;
|
|
17
|
+
/** @internal */
|
|
16
18
|
getOptions(): BaseOption[];
|
|
19
|
+
/** @internal */
|
|
17
20
|
process(options: HeapAnalysisOptions): Promise<void>;
|
|
21
|
+
/** @internal */
|
|
22
|
+
analyzeSnapshotsInDirectory(directory: string): Promise<void>;
|
|
18
23
|
private shouldFilterOutEdge;
|
|
24
|
+
/** @internal */
|
|
19
25
|
private getGlobalVariables;
|
|
20
26
|
}
|
|
21
27
|
export default GlobalVariableAnalysis;
|
|
@@ -21,6 +21,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
21
21
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
22
22
|
};
|
|
23
23
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
24
|
+
const core_1 = require("@memlab/core");
|
|
24
25
|
const HeapAnalysisSnapshotFileOption_1 = __importDefault(require("../../options/HeapAnalysisSnapshotFileOption"));
|
|
25
26
|
const BaseAnalysis_1 = __importDefault(require("../../BaseAnalysis"));
|
|
26
27
|
const PluginUtils_1 = __importDefault(require("../../PluginUtils"));
|
|
@@ -29,12 +30,15 @@ class GlobalVariableAnalysis extends BaseAnalysis_1.default {
|
|
|
29
30
|
getCommandName() {
|
|
30
31
|
return 'global-variable';
|
|
31
32
|
}
|
|
33
|
+
/** @internal */
|
|
32
34
|
getDescription() {
|
|
33
35
|
return 'Get global variables in heap';
|
|
34
36
|
}
|
|
37
|
+
/** @internal */
|
|
35
38
|
getOptions() {
|
|
36
39
|
return [new HeapAnalysisSnapshotFileOption_1.default()];
|
|
37
40
|
}
|
|
41
|
+
/** @internal */
|
|
38
42
|
process(options) {
|
|
39
43
|
return __awaiter(this, void 0, void 0, function* () {
|
|
40
44
|
const snapshot = yield PluginUtils_1.default.loadHeapSnapshot(options);
|
|
@@ -42,6 +46,14 @@ class GlobalVariableAnalysis extends BaseAnalysis_1.default {
|
|
|
42
46
|
PluginUtils_1.default.printReferencesInTerminal(list);
|
|
43
47
|
});
|
|
44
48
|
}
|
|
49
|
+
/** @internal */
|
|
50
|
+
analyzeSnapshotsInDirectory(directory) {
|
|
51
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
52
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
53
|
+
const d = directory;
|
|
54
|
+
throw core_1.utils.haltOrThrow(`${this.constructor.name} does not support analyzeSnapshotsInDirectory`);
|
|
55
|
+
});
|
|
56
|
+
}
|
|
45
57
|
shouldFilterOutEdge(edge) {
|
|
46
58
|
if (BuiltInGlobalVariables_1.default.has(`${edge.name_or_index}`)) {
|
|
47
59
|
return true;
|
|
@@ -57,6 +69,7 @@ class GlobalVariableAnalysis extends BaseAnalysis_1.default {
|
|
|
57
69
|
}
|
|
58
70
|
return false;
|
|
59
71
|
}
|
|
72
|
+
/** @internal */
|
|
60
73
|
getGlobalVariables(snapshot) {
|
|
61
74
|
// rank heap objects based on fanout
|
|
62
75
|
const ret = [];
|
|
@@ -12,8 +12,13 @@ import { BaseOption } from '@memlab/core';
|
|
|
12
12
|
import BaseAnalysis from '../BaseAnalysis';
|
|
13
13
|
declare class ObjectFanoutAnalysis extends BaseAnalysis {
|
|
14
14
|
getCommandName(): string;
|
|
15
|
+
/** @internal */
|
|
15
16
|
getDescription(): string;
|
|
17
|
+
/** @internal */
|
|
16
18
|
getOptions(): BaseOption[];
|
|
19
|
+
/** @internal */
|
|
20
|
+
analyzeSnapshotsInDirectory(directory: string): Promise<void>;
|
|
21
|
+
/** @internal */
|
|
17
22
|
process(options: HeapAnalysisOptions): Promise<void>;
|
|
18
23
|
private getObjectsWithHighFanout;
|
|
19
24
|
}
|