@memlab/heap-analysis 1.0.12 → 1.0.15
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/BaseAnalysis.d.ts +20 -13
- package/dist/BaseAnalysis.js +26 -10
- package/dist/PluginUtils.d.ts +10 -0
- package/dist/plugins/CollectionUnboundGrowthAnalysis.d.ts +2 -2
- package/dist/plugins/CollectionsHoldingStaleAnalysis.d.ts +2 -2
- package/dist/plugins/DetachedDOMElementAnalysis.d.ts +2 -2
- package/dist/plugins/GlobalVariableAnalysis/GlobalVariableAnalysis.d.ts +2 -2
- package/dist/plugins/ObjectContentAnalysis.d.ts +2 -2
- package/dist/plugins/ObjectFanoutAnalysis.d.ts +2 -2
- package/dist/plugins/ObjectShallowAnalysis.d.ts +2 -2
- package/dist/plugins/ObjectShapeAnalysis.d.ts +13 -3
- package/dist/plugins/ObjectShapeAnalysis.js +126 -1
- package/dist/plugins/ObjectSizeAnalysis.d.ts +2 -2
- package/dist/plugins/ObjectUnboundGrowthAnalysis.d.ts +6 -3
- package/dist/plugins/ObjectUnboundGrowthAnalysis.js +142 -1
- package/dist/plugins/ShapeUnboundGrowthAnalysis.d.ts +2 -2
- package/dist/plugins/StringAnalysis.d.ts +2 -2
- package/dist/plugins/UnmountedReactFiberNodesAnalysis.d.ts +2 -2
- package/package.json +2 -2
package/dist/BaseAnalysis.d.ts
CHANGED
|
@@ -7,9 +7,8 @@
|
|
|
7
7
|
* @format
|
|
8
8
|
* @oncall web_perf_infra
|
|
9
9
|
*/
|
|
10
|
-
import type { AnyValue } from '@memlab/core';
|
|
11
|
-
import type { HeapAnalysisOptions } from './PluginUtils';
|
|
12
|
-
import { BaseOption } from '@memlab/core';
|
|
10
|
+
import type { AnyValue, BaseOption } from '@memlab/core';
|
|
11
|
+
import type { AnalyzeSnapshotResult, HeapAnalysisOptions } from './PluginUtils';
|
|
13
12
|
declare abstract class Analysis {
|
|
14
13
|
process(_options: HeapAnalysisOptions): Promise<void>;
|
|
15
14
|
/**
|
|
@@ -27,33 +26,41 @@ declare abstract class Analysis {
|
|
|
27
26
|
/**
|
|
28
27
|
* Run heap analysis for a single heap snapshot file
|
|
29
28
|
* @param file the absolute path of a `.heapsnapshot` file.
|
|
30
|
-
* @returns this API returns
|
|
31
|
-
*
|
|
32
|
-
*
|
|
29
|
+
* @returns this API returns {@link AnalyzeSnapshotResult}, which contains
|
|
30
|
+
* the logging file of analysis console output. Alternatively, to get more
|
|
31
|
+
* structured analysis results, check out the documentation of the hosting
|
|
32
|
+
* heap analysis class and call the analysis-specific API to get results
|
|
33
|
+
* after calling this method.
|
|
33
34
|
* * **Example**:
|
|
34
35
|
* ```typescript
|
|
35
36
|
* const analysis = new StringAnalysis();
|
|
36
|
-
*
|
|
37
|
+
* // analysis console output is saved in result.analysisOutputFile
|
|
38
|
+
* const result = await anaysis.analyzeSnapshotFromFile(snapshotFile);
|
|
39
|
+
* // query analysis-specific and structured results
|
|
37
40
|
* const stringPatterns = analysis.getTopDuplicatedStringsInCount();
|
|
38
41
|
* ```
|
|
39
42
|
*/
|
|
40
|
-
analyzeSnapshotFromFile(file: string): Promise<
|
|
43
|
+
analyzeSnapshotFromFile(file: string): Promise<AnalyzeSnapshotResult>;
|
|
41
44
|
/**
|
|
42
45
|
* Run heap analysis for a series of heap snapshot files
|
|
43
46
|
* @param directory the absolute path of the directory holding a series of
|
|
44
47
|
* `.heapsnapshot` files, all snapshot files will be loaded and analyzed
|
|
45
48
|
* in the alphanumerically ascending order of those snapshot file names.
|
|
46
|
-
* @returns this API returns
|
|
47
|
-
*
|
|
48
|
-
*
|
|
49
|
+
* @returns this API returns {@link AnalyzeSnapshotResult}, which contains
|
|
50
|
+
* the logging file of analysis console output. Alternatively, to get more
|
|
51
|
+
* structured analysis results, check out the documentation of the hosting
|
|
52
|
+
* heap analysis class and call the analysis-specific API to get results
|
|
53
|
+
* after calling this method.
|
|
49
54
|
* * **Example**:
|
|
50
55
|
* ```typescript
|
|
51
56
|
* const analysis = new ShapeUnboundGrowthAnalysis();
|
|
52
|
-
*
|
|
57
|
+
* // analysis console output is saved in result.analysisOutputFile
|
|
58
|
+
* const result = await anaysis.analyzeSnapshotsInDirectory(snapshotDirectory);
|
|
59
|
+
* // query analysis-specific and structured results
|
|
53
60
|
* const shapes = analysis.getShapesWithUnboundGrowth();
|
|
54
61
|
* ```
|
|
55
62
|
*/
|
|
56
|
-
analyzeSnapshotsInDirectory(directory: string): Promise<
|
|
63
|
+
analyzeSnapshotsInDirectory(directory: string): Promise<AnalyzeSnapshotResult>;
|
|
57
64
|
}
|
|
58
65
|
/**
|
|
59
66
|
*
|
package/dist/BaseAnalysis.js
CHANGED
|
@@ -68,25 +68,33 @@ class Analysis {
|
|
|
68
68
|
/**
|
|
69
69
|
* Run heap analysis for a single heap snapshot file
|
|
70
70
|
* @param file the absolute path of a `.heapsnapshot` file.
|
|
71
|
-
* @returns this API returns
|
|
72
|
-
*
|
|
73
|
-
*
|
|
71
|
+
* @returns this API returns {@link AnalyzeSnapshotResult}, which contains
|
|
72
|
+
* the logging file of analysis console output. Alternatively, to get more
|
|
73
|
+
* structured analysis results, check out the documentation of the hosting
|
|
74
|
+
* heap analysis class and call the analysis-specific API to get results
|
|
75
|
+
* after calling this method.
|
|
74
76
|
* * **Example**:
|
|
75
77
|
* ```typescript
|
|
76
78
|
* const analysis = new StringAnalysis();
|
|
77
|
-
*
|
|
79
|
+
* // analysis console output is saved in result.analysisOutputFile
|
|
80
|
+
* const result = await anaysis.analyzeSnapshotFromFile(snapshotFile);
|
|
81
|
+
* // query analysis-specific and structured results
|
|
78
82
|
* const stringPatterns = analysis.getTopDuplicatedStringsInCount();
|
|
79
83
|
* ```
|
|
80
84
|
*/
|
|
81
85
|
analyzeSnapshotFromFile(file) {
|
|
82
86
|
return __awaiter(this, void 0, void 0, function* () {
|
|
83
|
-
|
|
87
|
+
const analysisOutputFile = core_1.fileManager.initNewHeapAnalysisLogFile();
|
|
88
|
+
core_1.info.registerLogFile(analysisOutputFile);
|
|
89
|
+
yield this.process({
|
|
84
90
|
args: {
|
|
85
91
|
_: [],
|
|
86
92
|
snapshot: file,
|
|
87
93
|
'snapshot-dir': '<MUST_PROVIDE_SNAPSHOT_DIR>',
|
|
88
94
|
},
|
|
89
95
|
});
|
|
96
|
+
core_1.info.unregisterLogFile(analysisOutputFile);
|
|
97
|
+
return { analysisOutputFile };
|
|
90
98
|
});
|
|
91
99
|
}
|
|
92
100
|
/**
|
|
@@ -94,25 +102,33 @@ class Analysis {
|
|
|
94
102
|
* @param directory the absolute path of the directory holding a series of
|
|
95
103
|
* `.heapsnapshot` files, all snapshot files will be loaded and analyzed
|
|
96
104
|
* in the alphanumerically ascending order of those snapshot file names.
|
|
97
|
-
* @returns this API returns
|
|
98
|
-
*
|
|
99
|
-
*
|
|
105
|
+
* @returns this API returns {@link AnalyzeSnapshotResult}, which contains
|
|
106
|
+
* the logging file of analysis console output. Alternatively, to get more
|
|
107
|
+
* structured analysis results, check out the documentation of the hosting
|
|
108
|
+
* heap analysis class and call the analysis-specific API to get results
|
|
109
|
+
* after calling this method.
|
|
100
110
|
* * **Example**:
|
|
101
111
|
* ```typescript
|
|
102
112
|
* const analysis = new ShapeUnboundGrowthAnalysis();
|
|
103
|
-
*
|
|
113
|
+
* // analysis console output is saved in result.analysisOutputFile
|
|
114
|
+
* const result = await anaysis.analyzeSnapshotsInDirectory(snapshotDirectory);
|
|
115
|
+
* // query analysis-specific and structured results
|
|
104
116
|
* const shapes = analysis.getShapesWithUnboundGrowth();
|
|
105
117
|
* ```
|
|
106
118
|
*/
|
|
107
119
|
analyzeSnapshotsInDirectory(directory) {
|
|
108
120
|
return __awaiter(this, void 0, void 0, function* () {
|
|
109
|
-
|
|
121
|
+
const analysisOutputFile = core_1.fileManager.initNewHeapAnalysisLogFile();
|
|
122
|
+
core_1.info.registerLogFile(analysisOutputFile);
|
|
123
|
+
yield this.process({
|
|
110
124
|
args: {
|
|
111
125
|
_: [],
|
|
112
126
|
snapshot: '<MUST_PROVIDE_SNAPSHOT_FILE>',
|
|
113
127
|
'snapshot-dir': directory,
|
|
114
128
|
},
|
|
115
129
|
});
|
|
130
|
+
core_1.info.unregisterLogFile(analysisOutputFile);
|
|
131
|
+
return { analysisOutputFile };
|
|
116
132
|
});
|
|
117
133
|
}
|
|
118
134
|
}
|
package/dist/PluginUtils.d.ts
CHANGED
|
@@ -25,6 +25,16 @@ export declare type HeapAnalysisOptions = {
|
|
|
25
25
|
/** @internal */
|
|
26
26
|
config?: MemLabConfig;
|
|
27
27
|
};
|
|
28
|
+
/**
|
|
29
|
+
* This is the return type from calling {@link analyzeSnapshotFromFile}
|
|
30
|
+
* or {@link analyzeSnapshotsInDirectory}.
|
|
31
|
+
*/
|
|
32
|
+
export declare type AnalyzeSnapshotResult = {
|
|
33
|
+
/**
|
|
34
|
+
* file path of the console output of the heap analysis call
|
|
35
|
+
*/
|
|
36
|
+
analysisOutputFile: string;
|
|
37
|
+
};
|
|
28
38
|
declare type PrintNodeOption = {
|
|
29
39
|
indent?: string;
|
|
30
40
|
printReferences?: boolean;
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* @format
|
|
8
8
|
* @oncall web_perf_infra
|
|
9
9
|
*/
|
|
10
|
-
import type { HeapAnalysisOptions } from '../PluginUtils';
|
|
10
|
+
import type { AnalyzeSnapshotResult, HeapAnalysisOptions } from '../PluginUtils';
|
|
11
11
|
import type { BaseOption } from '@memlab/core';
|
|
12
12
|
import BaseAnalysis from '../BaseAnalysis';
|
|
13
13
|
declare class CollectionUnboundGrowthAnalysis extends BaseAnalysis {
|
|
@@ -17,7 +17,7 @@ declare class CollectionUnboundGrowthAnalysis extends BaseAnalysis {
|
|
|
17
17
|
/** @internal */
|
|
18
18
|
getOptions(): BaseOption[];
|
|
19
19
|
/** @internal */
|
|
20
|
-
analyzeSnapshotFromFile(file: string): Promise<
|
|
20
|
+
analyzeSnapshotFromFile(file: string): Promise<AnalyzeSnapshotResult>;
|
|
21
21
|
/** @internal */
|
|
22
22
|
process(options: HeapAnalysisOptions): Promise<void>;
|
|
23
23
|
private checkUnboundCollection;
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* @format
|
|
8
8
|
* @oncall web_perf_infra
|
|
9
9
|
*/
|
|
10
|
-
import type { HeapAnalysisOptions } from '../PluginUtils';
|
|
10
|
+
import type { AnalyzeSnapshotResult, HeapAnalysisOptions } from '../PluginUtils';
|
|
11
11
|
import { BaseOption } from '@memlab/core';
|
|
12
12
|
import BaseAnalysis from '../BaseAnalysis';
|
|
13
13
|
export default class CollectionsHoldingStaleAnalysis extends BaseAnalysis {
|
|
@@ -17,7 +17,7 @@ export default class CollectionsHoldingStaleAnalysis extends BaseAnalysis {
|
|
|
17
17
|
/** @internal */
|
|
18
18
|
getOptions(): BaseOption[];
|
|
19
19
|
/** @internal */
|
|
20
|
-
analyzeSnapshotsInDirectory(directory: string): Promise<
|
|
20
|
+
analyzeSnapshotsInDirectory(directory: string): Promise<AnalyzeSnapshotResult>;
|
|
21
21
|
private staleCollectionMapper;
|
|
22
22
|
/** @internal */
|
|
23
23
|
process(options: HeapAnalysisOptions): Promise<void>;
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* @format
|
|
8
8
|
* @oncall web_perf_infra
|
|
9
9
|
*/
|
|
10
|
-
import type { HeapAnalysisOptions } from '../PluginUtils';
|
|
10
|
+
import type { AnalyzeSnapshotResult, HeapAnalysisOptions } from '../PluginUtils';
|
|
11
11
|
import type { IHeapNode } from '@memlab/core';
|
|
12
12
|
import { BaseOption } from '@memlab/core';
|
|
13
13
|
import BaseAnalysis from '../BaseAnalysis';
|
|
@@ -18,7 +18,7 @@ export default class DetachedDOMElementAnalysis extends BaseAnalysis {
|
|
|
18
18
|
/** @internal */
|
|
19
19
|
getOptions(): BaseOption[];
|
|
20
20
|
/** @internal */
|
|
21
|
-
analyzeSnapshotsInDirectory(directory: string): Promise<
|
|
21
|
+
analyzeSnapshotsInDirectory(directory: string): Promise<AnalyzeSnapshotResult>;
|
|
22
22
|
private detachedElements;
|
|
23
23
|
getDetachedElements(): IHeapNode[];
|
|
24
24
|
/** @internal */
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* @format
|
|
8
8
|
* @oncall web_perf_infra
|
|
9
9
|
*/
|
|
10
|
-
import type { HeapAnalysisOptions } from '../../PluginUtils';
|
|
10
|
+
import type { AnalyzeSnapshotResult, HeapAnalysisOptions } from '../../PluginUtils';
|
|
11
11
|
import { BaseOption } from '@memlab/core';
|
|
12
12
|
import BaseAnalysis from '../../BaseAnalysis';
|
|
13
13
|
declare class GlobalVariableAnalysis extends BaseAnalysis {
|
|
@@ -19,7 +19,7 @@ declare class GlobalVariableAnalysis extends BaseAnalysis {
|
|
|
19
19
|
/** @internal */
|
|
20
20
|
process(options: HeapAnalysisOptions): Promise<void>;
|
|
21
21
|
/** @internal */
|
|
22
|
-
analyzeSnapshotsInDirectory(directory: string): Promise<
|
|
22
|
+
analyzeSnapshotsInDirectory(directory: string): Promise<AnalyzeSnapshotResult>;
|
|
23
23
|
private shouldFilterOutEdge;
|
|
24
24
|
/** @internal */
|
|
25
25
|
private getGlobalVariables;
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* @format
|
|
8
8
|
* @oncall web_perf_infra
|
|
9
9
|
*/
|
|
10
|
-
import type { HeapAnalysisOptions } from '../PluginUtils';
|
|
10
|
+
import type { AnalyzeSnapshotResult, HeapAnalysisOptions } from '../PluginUtils';
|
|
11
11
|
import { BaseOption } from '@memlab/core';
|
|
12
12
|
import BaseAnalysis from '../BaseAnalysis';
|
|
13
13
|
declare class GlobalVariableAnalysis extends BaseAnalysis {
|
|
@@ -19,7 +19,7 @@ declare class GlobalVariableAnalysis extends BaseAnalysis {
|
|
|
19
19
|
/** @internal */
|
|
20
20
|
process(options: HeapAnalysisOptions): Promise<void>;
|
|
21
21
|
/** @internal */
|
|
22
|
-
analyzeSnapshotsInDirectory(directory: string): Promise<
|
|
22
|
+
analyzeSnapshotsInDirectory(directory: string): Promise<AnalyzeSnapshotResult>;
|
|
23
23
|
/** @internal */
|
|
24
24
|
private getObjectProperties;
|
|
25
25
|
/** @internal */
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* @format
|
|
8
8
|
* @oncall web_perf_infra
|
|
9
9
|
*/
|
|
10
|
-
import type { HeapAnalysisOptions } from '../PluginUtils';
|
|
10
|
+
import type { AnalyzeSnapshotResult, HeapAnalysisOptions } from '../PluginUtils';
|
|
11
11
|
import { BaseOption } from '@memlab/core';
|
|
12
12
|
import BaseAnalysis from '../BaseAnalysis';
|
|
13
13
|
declare class ObjectFanoutAnalysis extends BaseAnalysis {
|
|
@@ -17,7 +17,7 @@ declare class ObjectFanoutAnalysis extends BaseAnalysis {
|
|
|
17
17
|
/** @internal */
|
|
18
18
|
getOptions(): BaseOption[];
|
|
19
19
|
/** @internal */
|
|
20
|
-
analyzeSnapshotsInDirectory(directory: string): Promise<
|
|
20
|
+
analyzeSnapshotsInDirectory(directory: string): Promise<AnalyzeSnapshotResult>;
|
|
21
21
|
/** @internal */
|
|
22
22
|
process(options: HeapAnalysisOptions): Promise<void>;
|
|
23
23
|
private getObjectsWithHighFanout;
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* @format
|
|
8
8
|
* @oncall web_perf_infra
|
|
9
9
|
*/
|
|
10
|
-
import type { HeapAnalysisOptions } from '../PluginUtils';
|
|
10
|
+
import type { AnalyzeSnapshotResult, HeapAnalysisOptions } from '../PluginUtils';
|
|
11
11
|
import { BaseOption } from '@memlab/core';
|
|
12
12
|
import BaseAnalysis from '../BaseAnalysis';
|
|
13
13
|
/**
|
|
@@ -44,7 +44,7 @@ declare class ObjectShallowAnalysis extends BaseAnalysis {
|
|
|
44
44
|
/** @internal */
|
|
45
45
|
getOptions(): BaseOption[];
|
|
46
46
|
/** @internal */
|
|
47
|
-
analyzeSnapshotsInDirectory(directory: string): Promise<
|
|
47
|
+
analyzeSnapshotsInDirectory(directory: string): Promise<AnalyzeSnapshotResult>;
|
|
48
48
|
/** @internal */
|
|
49
49
|
process(options: HeapAnalysisOptions): Promise<void>;
|
|
50
50
|
/**
|
|
@@ -7,8 +7,8 @@
|
|
|
7
7
|
* @format
|
|
8
8
|
* @oncall web_perf_infra
|
|
9
9
|
*/
|
|
10
|
-
import type { HeapAnalysisOptions } from '../PluginUtils';
|
|
11
|
-
import { BaseOption } from '@memlab/core';
|
|
10
|
+
import type { AnalyzeSnapshotResult, HeapAnalysisOptions } from '../PluginUtils';
|
|
11
|
+
import type { BaseOption } from '@memlab/core';
|
|
12
12
|
import BaseAnalysis from '../BaseAnalysis';
|
|
13
13
|
declare class ObjectShapeAnalysis extends BaseAnalysis {
|
|
14
14
|
getCommandName(): string;
|
|
@@ -17,9 +17,19 @@ declare class ObjectShapeAnalysis extends BaseAnalysis {
|
|
|
17
17
|
/** @internal */
|
|
18
18
|
getOptions(): BaseOption[];
|
|
19
19
|
/** @internal */
|
|
20
|
-
analyzeSnapshotsInDirectory(directory: string): Promise<
|
|
20
|
+
analyzeSnapshotsInDirectory(directory: string): Promise<AnalyzeSnapshotResult>;
|
|
21
21
|
/** @internal */
|
|
22
22
|
process(options: HeapAnalysisOptions): Promise<void>;
|
|
23
|
+
/** @internal */
|
|
24
|
+
breakDownMemoryByShapes(options?: {
|
|
25
|
+
file?: string;
|
|
26
|
+
}): Promise<void>;
|
|
27
|
+
/** @internal */
|
|
28
|
+
private breakDownSnapshotByShapes;
|
|
29
|
+
/** @internal */
|
|
30
|
+
private breakDownByReferrers;
|
|
31
|
+
/** @internal */
|
|
32
|
+
private isTrivialEdgeForBreakDown;
|
|
23
33
|
}
|
|
24
34
|
export default ObjectShapeAnalysis;
|
|
25
35
|
//# sourceMappingURL=ObjectShapeAnalysis.d.ts.map
|
|
@@ -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 chalk_1 = __importDefault(require("chalk"));
|
|
24
25
|
const core_1 = require("@memlab/core");
|
|
25
26
|
const BaseAnalysis_1 = __importDefault(require("../BaseAnalysis"));
|
|
26
27
|
const PluginUtils_1 = __importDefault(require("../PluginUtils"));
|
|
@@ -49,8 +50,132 @@ class ObjectShapeAnalysis extends BaseAnalysis_1.default {
|
|
|
49
50
|
process(options) {
|
|
50
51
|
return __awaiter(this, void 0, void 0, function* () {
|
|
51
52
|
const snapshotPath = PluginUtils_1.default.getSnapshotFileForAnalysis(options);
|
|
52
|
-
yield
|
|
53
|
+
yield this.breakDownMemoryByShapes({ file: snapshotPath });
|
|
53
54
|
});
|
|
54
55
|
}
|
|
56
|
+
/** @internal */
|
|
57
|
+
breakDownMemoryByShapes(options = {}) {
|
|
58
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
59
|
+
const opt = { buildNodeIdIndex: true, verbose: true };
|
|
60
|
+
const file = options.file ||
|
|
61
|
+
core_1.utils.getSnapshotFilePathWithTabType(/.*/) ||
|
|
62
|
+
'<EMPTY_FILE_PATH>';
|
|
63
|
+
const snapshot = yield core_1.utils.getSnapshotFromFile(file, opt);
|
|
64
|
+
core_1.analysis.preparePathFinder(snapshot);
|
|
65
|
+
const heapInfo = core_1.analysis.getOverallHeapInfo(snapshot, { force: true });
|
|
66
|
+
if (heapInfo) {
|
|
67
|
+
core_1.analysis.printHeapInfo(heapInfo);
|
|
68
|
+
}
|
|
69
|
+
this.breakDownSnapshotByShapes(snapshot);
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
/** @internal */
|
|
73
|
+
breakDownSnapshotByShapes(snapshot) {
|
|
74
|
+
core_1.info.overwrite('Breaking down memory by shapes...');
|
|
75
|
+
const breakdown = Object.create(null);
|
|
76
|
+
const population = Object.create(null);
|
|
77
|
+
// group objects based on their shapes
|
|
78
|
+
snapshot.nodes.forEach((node) => {
|
|
79
|
+
if ((node.type !== 'object' && !core_1.utils.isStringNode(node)) ||
|
|
80
|
+
core_1.config.nodeIgnoreSetInShape.has(node.name)) {
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
const key = core_1.serializer.summarizeNodeShape(node);
|
|
84
|
+
breakdown[key] = breakdown[key] || new Set();
|
|
85
|
+
breakdown[key].add(node.id);
|
|
86
|
+
if (population[key] === undefined) {
|
|
87
|
+
population[key] = { examples: [], n: 0 };
|
|
88
|
+
}
|
|
89
|
+
++population[key].n;
|
|
90
|
+
// retain the top 5 examples
|
|
91
|
+
const examples = population[key].examples;
|
|
92
|
+
examples.push(node);
|
|
93
|
+
examples.sort((n1, n2) => n2.retainedSize - n1.retainedSize);
|
|
94
|
+
if (examples.length > 5) {
|
|
95
|
+
examples.pop();
|
|
96
|
+
}
|
|
97
|
+
});
|
|
98
|
+
// calculate and sort based on retained sizes
|
|
99
|
+
const ret = [];
|
|
100
|
+
for (const key in breakdown) {
|
|
101
|
+
const size = core_1.utils.aggregateDominatorMetrics(breakdown[key], snapshot, () => true, core_1.utils.getRetainedSize);
|
|
102
|
+
ret.push({ key, retainedSize: size });
|
|
103
|
+
}
|
|
104
|
+
ret.sort((o1, o2) => o2.retainedSize - o1.retainedSize);
|
|
105
|
+
core_1.info.topLevel('Object shapes with top retained sizes:');
|
|
106
|
+
core_1.info.lowLevel(' (Use `memlab trace --node-id=@ID` to get trace)\n');
|
|
107
|
+
const topList = ret.slice(0, 40);
|
|
108
|
+
// print settings
|
|
109
|
+
const opt = { color: true, compact: true };
|
|
110
|
+
const dot = chalk_1.default.grey('· ');
|
|
111
|
+
const colon = chalk_1.default.grey(': ');
|
|
112
|
+
// print the shapes with the biggest retained size
|
|
113
|
+
for (const o of topList) {
|
|
114
|
+
const referrerInfo = this.breakDownByReferrers(breakdown[o.key], snapshot);
|
|
115
|
+
const { examples, n } = population[o.key];
|
|
116
|
+
const shapeStr = core_1.serializer.summarizeNodeShape(examples[0], opt);
|
|
117
|
+
const bytes = core_1.utils.getReadableBytes(o.retainedSize);
|
|
118
|
+
const examplesStr = examples
|
|
119
|
+
.map(e => `@${e.id} [${core_1.utils.getReadableBytes(e.retainedSize)}]`)
|
|
120
|
+
.join(' | ');
|
|
121
|
+
const meta = chalk_1.default.grey(` (N: ${n}, Examples: ${examplesStr})`);
|
|
122
|
+
core_1.info.topLevel(`${dot}${shapeStr}${colon}${bytes}${meta}`);
|
|
123
|
+
core_1.info.lowLevel(referrerInfo + '\n');
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
/** @internal */
|
|
127
|
+
breakDownByReferrers(ids, snapshot) {
|
|
128
|
+
const edgeNames = Object.create(null);
|
|
129
|
+
for (const id of ids) {
|
|
130
|
+
const node = snapshot.getNodeById(id);
|
|
131
|
+
for (const edge of (node === null || node === void 0 ? void 0 : node.referrers) || []) {
|
|
132
|
+
const source = edge.fromNode;
|
|
133
|
+
if (!core_1.utils.isMeaningfulEdge(edge) ||
|
|
134
|
+
this.isTrivialEdgeForBreakDown(edge)) {
|
|
135
|
+
continue;
|
|
136
|
+
}
|
|
137
|
+
const sourceName = core_1.serializer.summarizeNodeName(source, {
|
|
138
|
+
color: false,
|
|
139
|
+
});
|
|
140
|
+
const edgeName = core_1.serializer.summarizeEdgeName(edge, {
|
|
141
|
+
color: false,
|
|
142
|
+
abstract: true,
|
|
143
|
+
});
|
|
144
|
+
const edgeKey = `[${sourceName}] --${edgeName}--> `;
|
|
145
|
+
edgeNames[edgeKey] = edgeNames[edgeKey] || {
|
|
146
|
+
numberOfEdgesToNode: 0,
|
|
147
|
+
source,
|
|
148
|
+
edge,
|
|
149
|
+
};
|
|
150
|
+
++edgeNames[edgeKey].numberOfEdgesToNode;
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
const referrerInfo = Object.entries(edgeNames)
|
|
154
|
+
.sort((i1, i2) => i2[1].numberOfEdgesToNode - i1[1].numberOfEdgesToNode)
|
|
155
|
+
.slice(0, 4)
|
|
156
|
+
.map(i => {
|
|
157
|
+
const meta = i[1];
|
|
158
|
+
const source = core_1.serializer.summarizeNodeName(meta.source, {
|
|
159
|
+
color: true,
|
|
160
|
+
});
|
|
161
|
+
const edgeName = core_1.serializer.summarizeEdgeName(meta.edge, {
|
|
162
|
+
color: true,
|
|
163
|
+
abstract: true,
|
|
164
|
+
});
|
|
165
|
+
const edgeSummary = `${source} --${edgeName}-->`;
|
|
166
|
+
return ` · ${edgeSummary}: ${meta.numberOfEdgesToNode}`;
|
|
167
|
+
})
|
|
168
|
+
.join('\n');
|
|
169
|
+
return referrerInfo;
|
|
170
|
+
}
|
|
171
|
+
/** @internal */
|
|
172
|
+
isTrivialEdgeForBreakDown(edge) {
|
|
173
|
+
const source = edge.fromNode;
|
|
174
|
+
return (source.type === 'array' ||
|
|
175
|
+
source.name === '(object elements)' ||
|
|
176
|
+
source.name === 'system' ||
|
|
177
|
+
edge.name_or_index === '__proto__' ||
|
|
178
|
+
edge.name_or_index === 'prototype');
|
|
179
|
+
}
|
|
55
180
|
}
|
|
56
181
|
exports.default = ObjectShapeAnalysis;
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* @format
|
|
8
8
|
* @oncall web_perf_infra
|
|
9
9
|
*/
|
|
10
|
-
import type { HeapAnalysisOptions } from '../PluginUtils';
|
|
10
|
+
import type { AnalyzeSnapshotResult, HeapAnalysisOptions } from '../PluginUtils';
|
|
11
11
|
import { BaseOption } from '@memlab/core';
|
|
12
12
|
import BaseAnalysis from '../BaseAnalysis';
|
|
13
13
|
declare class ObjectSizeRankAnalysis extends BaseAnalysis {
|
|
@@ -17,7 +17,7 @@ declare class ObjectSizeRankAnalysis extends BaseAnalysis {
|
|
|
17
17
|
/** @internal */
|
|
18
18
|
getOptions(): BaseOption[];
|
|
19
19
|
/** @internal */
|
|
20
|
-
analyzeSnapshotsInDirectory(directory: string): Promise<
|
|
20
|
+
analyzeSnapshotsInDirectory(directory: string): Promise<AnalyzeSnapshotResult>;
|
|
21
21
|
/** @internal */
|
|
22
22
|
process(options: HeapAnalysisOptions): Promise<void>;
|
|
23
23
|
}
|
|
@@ -7,8 +7,8 @@
|
|
|
7
7
|
* @format
|
|
8
8
|
* @oncall web_perf_infra
|
|
9
9
|
*/
|
|
10
|
-
import type {
|
|
11
|
-
import {
|
|
10
|
+
import type { BaseOption } from '@memlab/core';
|
|
11
|
+
import type { AnalyzeSnapshotResult, HeapAnalysisOptions } from '../PluginUtils';
|
|
12
12
|
import BaseAnalysis from '../BaseAnalysis';
|
|
13
13
|
declare class ObjectUnboundGrowthAnalysis extends BaseAnalysis {
|
|
14
14
|
getCommandName(): string;
|
|
@@ -17,9 +17,12 @@ declare class ObjectUnboundGrowthAnalysis extends BaseAnalysis {
|
|
|
17
17
|
/** @internal */
|
|
18
18
|
getOptions(): BaseOption[];
|
|
19
19
|
/** @internal */
|
|
20
|
-
analyzeSnapshotFromFile(file: string): Promise<
|
|
20
|
+
analyzeSnapshotFromFile(file: string): Promise<AnalyzeSnapshotResult>;
|
|
21
21
|
/** @internal */
|
|
22
22
|
process(options: HeapAnalysisOptions): Promise<void>;
|
|
23
|
+
private checkUnbound;
|
|
24
|
+
private detectUnboundGrowth;
|
|
25
|
+
private calculateRetainedSizes;
|
|
23
26
|
}
|
|
24
27
|
export default ObjectUnboundGrowthAnalysis;
|
|
25
28
|
//# sourceMappingURL=ObjectUnboundGrowthAnalysis.d.ts.map
|
|
@@ -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 fs_1 = __importDefault(require("fs"));
|
|
24
25
|
const core_1 = require("@memlab/core");
|
|
25
26
|
const BaseAnalysis_1 = __importDefault(require("../BaseAnalysis"));
|
|
26
27
|
const HeapAnalysisSnapshotDirectoryOption_1 = __importDefault(require("../options/HeapAnalysisSnapshotDirectoryOption"));
|
|
@@ -52,8 +53,148 @@ class ObjectUnboundGrowthAnalysis extends BaseAnalysis_1.default {
|
|
|
52
53
|
const snapshotDir = PluginUtils_1.default.getSnapshotDirForAnalysis(options);
|
|
53
54
|
const opt = snapshotDir ? { minSnapshots: 2, snapshotDir } : {};
|
|
54
55
|
core_1.config.chaseWeakMapEdge = false;
|
|
55
|
-
yield
|
|
56
|
+
yield this.checkUnbound(opt);
|
|
56
57
|
});
|
|
57
58
|
}
|
|
59
|
+
checkUnbound(options = {}) {
|
|
60
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
61
|
+
core_1.analysis.visualizeMemoryUsage(options);
|
|
62
|
+
core_1.utils.checkSnapshots(options);
|
|
63
|
+
yield this.detectUnboundGrowth(options);
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
// find any objects that keeps growing
|
|
67
|
+
detectUnboundGrowth(options = {}) {
|
|
68
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
69
|
+
const nodeInfo = Object.create(null);
|
|
70
|
+
let hasCheckedFirstSnapshot = false;
|
|
71
|
+
let snapshot = null;
|
|
72
|
+
const isValidNode = (node) => node.type === 'object' ||
|
|
73
|
+
node.type === 'closure' ||
|
|
74
|
+
node.type === 'regexp';
|
|
75
|
+
const initNodeInfo = (node) => {
|
|
76
|
+
if (!isValidNode(node)) {
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
const n = node.retainedSize;
|
|
80
|
+
nodeInfo[node.id] = {
|
|
81
|
+
type: node.type,
|
|
82
|
+
name: node.name,
|
|
83
|
+
min: n,
|
|
84
|
+
max: n,
|
|
85
|
+
history: [n],
|
|
86
|
+
node,
|
|
87
|
+
};
|
|
88
|
+
};
|
|
89
|
+
const updateNodeInfo = (node) => {
|
|
90
|
+
const item = nodeInfo[node.id];
|
|
91
|
+
if (!item) {
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
if (node.name !== item.name || node.type !== item.type) {
|
|
95
|
+
nodeInfo[node.id] = null;
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
const n = node.retainedSize;
|
|
99
|
+
// only monotonic increase?
|
|
100
|
+
if (core_1.config.monotonicUnboundGrowthOnly && n < item.max) {
|
|
101
|
+
nodeInfo[node.id] = null;
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
item.history.push(n);
|
|
105
|
+
item.max = Math.max(item.max, n);
|
|
106
|
+
item.min = Math.min(item.min, n);
|
|
107
|
+
};
|
|
108
|
+
// summarize the heap objects info in current heap snapshot
|
|
109
|
+
// this is mainly used for better understanding of the % of
|
|
110
|
+
// objects released and allocated over time
|
|
111
|
+
const maybeSummarizeNodeInfo = () => {
|
|
112
|
+
if (!core_1.config.verbose) {
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
let n = 0;
|
|
116
|
+
for (const k in nodeInfo) {
|
|
117
|
+
if (nodeInfo[k]) {
|
|
118
|
+
++n;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
core_1.info.lowLevel(`Objects tracked: ${n}`);
|
|
122
|
+
};
|
|
123
|
+
core_1.info.overwrite('Checking unbounded objects...');
|
|
124
|
+
const snapshotFiles = options.snapshotDir
|
|
125
|
+
? // load snapshots from a directory
|
|
126
|
+
core_1.utils.getSnapshotFilesInDir(options.snapshotDir)
|
|
127
|
+
: // load snapshots based on the visit sequence meta data
|
|
128
|
+
core_1.utils.getSnapshotFilesFromTabsOrder();
|
|
129
|
+
for (const file of snapshotFiles) {
|
|
130
|
+
// force GC before loading each snapshot
|
|
131
|
+
if (global.gc) {
|
|
132
|
+
global.gc();
|
|
133
|
+
}
|
|
134
|
+
// load and preprocess heap snapshot
|
|
135
|
+
const opt = { buildNodeIdIndex: true, verbose: true };
|
|
136
|
+
snapshot = yield core_1.utils.getSnapshotFromFile(file, opt);
|
|
137
|
+
this.calculateRetainedSizes(snapshot);
|
|
138
|
+
// keep track of heap objects
|
|
139
|
+
if (!hasCheckedFirstSnapshot) {
|
|
140
|
+
// record Ids in the snapshot
|
|
141
|
+
snapshot.nodes.forEach(initNodeInfo);
|
|
142
|
+
hasCheckedFirstSnapshot = true;
|
|
143
|
+
}
|
|
144
|
+
else {
|
|
145
|
+
snapshot.nodes.forEach(updateNodeInfo);
|
|
146
|
+
maybeSummarizeNodeInfo();
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
// exit if no heap snapshot found
|
|
150
|
+
if (!hasCheckedFirstSnapshot) {
|
|
151
|
+
return;
|
|
152
|
+
}
|
|
153
|
+
// post process and print the unbounded objects
|
|
154
|
+
const idsInLastSnapshot = new Set();
|
|
155
|
+
snapshot === null || snapshot === void 0 ? void 0 : snapshot.nodes.forEach(node => {
|
|
156
|
+
idsInLastSnapshot.add(node.id);
|
|
157
|
+
});
|
|
158
|
+
let ids = [];
|
|
159
|
+
for (const key in nodeInfo) {
|
|
160
|
+
const id = parseInt(key, 10);
|
|
161
|
+
const item = nodeInfo[id];
|
|
162
|
+
if (!item) {
|
|
163
|
+
continue;
|
|
164
|
+
}
|
|
165
|
+
if (!idsInLastSnapshot.has(id)) {
|
|
166
|
+
continue;
|
|
167
|
+
}
|
|
168
|
+
if (item.min === item.max) {
|
|
169
|
+
continue;
|
|
170
|
+
}
|
|
171
|
+
// filter out non-significant leaks
|
|
172
|
+
if (item.history[item.history.length - 1] < core_1.config.unboundSizeThreshold) {
|
|
173
|
+
continue;
|
|
174
|
+
}
|
|
175
|
+
ids.push(Object.assign({ id }, item));
|
|
176
|
+
}
|
|
177
|
+
if (ids.length === 0) {
|
|
178
|
+
core_1.info.midLevel('No increasing objects found.');
|
|
179
|
+
return;
|
|
180
|
+
}
|
|
181
|
+
ids = ids
|
|
182
|
+
.sort((o1, o2) => o2.history[o2.history.length - 1] - o1.history[o1.history.length - 1])
|
|
183
|
+
.slice(0, 20);
|
|
184
|
+
// print on terminal
|
|
185
|
+
const str = core_1.serializer.summarizeUnboundedObjects(ids, { color: true });
|
|
186
|
+
core_1.info.topLevel('Top growing objects in sizes:');
|
|
187
|
+
core_1.info.lowLevel(' (Use `memlab trace --node-id=@ID` to get trace)');
|
|
188
|
+
core_1.info.topLevel('\n' + str);
|
|
189
|
+
// save results to file
|
|
190
|
+
const csv = core_1.serializer.summarizeUnboundedObjectsToCSV(ids);
|
|
191
|
+
fs_1.default.writeFileSync(core_1.config.unboundObjectCSV, csv, 'UTF-8');
|
|
192
|
+
});
|
|
193
|
+
}
|
|
194
|
+
calculateRetainedSizes(snapshot) {
|
|
195
|
+
const finder = new core_1.TraceFinder();
|
|
196
|
+
// dominator and retained size
|
|
197
|
+
finder.calculateAllNodesRetainedSizes(snapshot);
|
|
198
|
+
}
|
|
58
199
|
}
|
|
59
200
|
exports.default = ObjectUnboundGrowthAnalysis;
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* @format
|
|
8
8
|
* @oncall web_perf_infra
|
|
9
9
|
*/
|
|
10
|
-
import type { HeapAnalysisOptions } from '../PluginUtils';
|
|
10
|
+
import type { AnalyzeSnapshotResult, HeapAnalysisOptions } from '../PluginUtils';
|
|
11
11
|
import { BaseOption } from '@memlab/core';
|
|
12
12
|
import BaseAnalysis from '../BaseAnalysis';
|
|
13
13
|
declare type ShapeSummary = {
|
|
@@ -25,7 +25,7 @@ export default class ShapeUnboundGrowthAnalysis extends BaseAnalysis {
|
|
|
25
25
|
/** @internal */
|
|
26
26
|
getOptions(): BaseOption[];
|
|
27
27
|
/** @internal */
|
|
28
|
-
analyzeSnapshotFromFile(file: string): Promise<
|
|
28
|
+
analyzeSnapshotFromFile(file: string): Promise<AnalyzeSnapshotResult>;
|
|
29
29
|
getShapesWithUnboundGrowth(): ShapeSummary[];
|
|
30
30
|
/** @internal */
|
|
31
31
|
process(options: HeapAnalysisOptions): Promise<void>;
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* @format
|
|
8
8
|
* @oncall web_perf_infra
|
|
9
9
|
*/
|
|
10
|
-
import type { HeapAnalysisOptions } from '../PluginUtils';
|
|
10
|
+
import type { AnalyzeSnapshotResult, HeapAnalysisOptions } from '../PluginUtils';
|
|
11
11
|
import { BaseOption } from '@memlab/core';
|
|
12
12
|
import BaseAnalysis from '../BaseAnalysis';
|
|
13
13
|
/**
|
|
@@ -58,7 +58,7 @@ export default class StringAnalysis extends BaseAnalysis {
|
|
|
58
58
|
/** @internal */
|
|
59
59
|
getOptions(): BaseOption[];
|
|
60
60
|
/** @internal */
|
|
61
|
-
analyzeSnapshotsInDirectory(directory: string): Promise<
|
|
61
|
+
analyzeSnapshotsInDirectory(directory: string): Promise<AnalyzeSnapshotResult>;
|
|
62
62
|
private static shouldIgnoreNode;
|
|
63
63
|
/** @internal */
|
|
64
64
|
process(options: HeapAnalysisOptions): Promise<void>;
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* @format
|
|
8
8
|
* @oncall web_perf_infra
|
|
9
9
|
*/
|
|
10
|
-
import type { HeapAnalysisOptions } from '../PluginUtils';
|
|
10
|
+
import type { AnalyzeSnapshotResult, HeapAnalysisOptions } from '../PluginUtils';
|
|
11
11
|
import { BaseOption } from '@memlab/core';
|
|
12
12
|
import BaseAnalysis from '../BaseAnalysis';
|
|
13
13
|
export default class UnmountedFiberNodeAnalysis extends BaseAnalysis {
|
|
@@ -17,7 +17,7 @@ export default class UnmountedFiberNodeAnalysis extends BaseAnalysis {
|
|
|
17
17
|
/** @internal */
|
|
18
18
|
getOptions(): BaseOption[];
|
|
19
19
|
/** @internal */
|
|
20
|
-
analyzeSnapshotsInDirectory(directory: string): Promise<
|
|
20
|
+
analyzeSnapshotsInDirectory(directory: string): Promise<AnalyzeSnapshotResult>;
|
|
21
21
|
/** @internal */
|
|
22
22
|
process(options: HeapAnalysisOptions): Promise<void>;
|
|
23
23
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@memlab/heap-analysis",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.15",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"description": "heap analysis plugins for memlab",
|
|
6
6
|
"author": "Liang Gong <lgong@fb.com>",
|
|
@@ -50,7 +50,7 @@
|
|
|
50
50
|
"scripts": {
|
|
51
51
|
"build-pkg": "tsc",
|
|
52
52
|
"test-pkg": "jest .",
|
|
53
|
-
"publish-patch": "npm
|
|
53
|
+
"publish-patch": "npm publish",
|
|
54
54
|
"clean-pkg": "rm -rf ./dist && rm -rf ./node_modules && rm -f ./tsconfig.tsbuildinfo"
|
|
55
55
|
},
|
|
56
56
|
"bugs": {
|