@memlab/heap-analysis 1.0.0

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.
Files changed (59) hide show
  1. package/README.md +11 -0
  2. package/dist/BaseAnalysis.d.ts +26 -0
  3. package/dist/BaseAnalysis.d.ts.map +1 -0
  4. package/dist/BaseAnalysis.js +112 -0
  5. package/dist/HeapAnalysisLoader.d.ts +19 -0
  6. package/dist/HeapAnalysisLoader.d.ts.map +1 -0
  7. package/dist/HeapAnalysisLoader.js +59 -0
  8. package/dist/PluginUtils.d.ts +48 -0
  9. package/dist/PluginUtils.d.ts.map +1 -0
  10. package/dist/PluginUtils.js +283 -0
  11. package/dist/__tests__/HeapAnalysis.test.d.ts +11 -0
  12. package/dist/__tests__/HeapAnalysis.test.d.ts.map +1 -0
  13. package/dist/__tests__/HeapAnalysis.test.js +32 -0
  14. package/dist/index.d.ts +23 -0
  15. package/dist/index.d.ts.map +1 -0
  16. package/dist/index.js +41 -0
  17. package/dist/options/HeapAnalysisSnapshotDirectoryOption.d.ts +18 -0
  18. package/dist/options/HeapAnalysisSnapshotDirectoryOption.d.ts.map +1 -0
  19. package/dist/options/HeapAnalysisSnapshotDirectoryOption.js +50 -0
  20. package/dist/options/HeapAnalysisSnapshotFileOption.d.ts +18 -0
  21. package/dist/options/HeapAnalysisSnapshotFileOption.d.ts.map +1 -0
  22. package/dist/options/HeapAnalysisSnapshotFileOption.js +50 -0
  23. package/dist/plugins/CollectionsHoldingStaleAnalysis.d.ts +22 -0
  24. package/dist/plugins/CollectionsHoldingStaleAnalysis.d.ts.map +1 -0
  25. package/dist/plugins/CollectionsHoldingStaleAnalysis.js +132 -0
  26. package/dist/plugins/DetachedDOMElementAnalysis.d.ts +22 -0
  27. package/dist/plugins/DetachedDOMElementAnalysis.d.ts.map +1 -0
  28. package/dist/plugins/DetachedDOMElementAnalysis.js +53 -0
  29. package/dist/plugins/GlobalVariableAnalysis/BuiltInGlobalVariables.d.ts +13 -0
  30. package/dist/plugins/GlobalVariableAnalysis/BuiltInGlobalVariables.d.ts.map +1 -0
  31. package/dist/plugins/GlobalVariableAnalysis/BuiltInGlobalVariables.js +1073 -0
  32. package/dist/plugins/GlobalVariableAnalysis/GlobalVariableAnalysis.d.ts +22 -0
  33. package/dist/plugins/GlobalVariableAnalysis/GlobalVariableAnalysis.d.ts.map +1 -0
  34. package/dist/plugins/GlobalVariableAnalysis/GlobalVariableAnalysis.js +78 -0
  35. package/dist/plugins/ObjectFanoutAnalysis.d.ts +21 -0
  36. package/dist/plugins/ObjectFanoutAnalysis.d.ts.map +1 -0
  37. package/dist/plugins/ObjectFanoutAnalysis.js +69 -0
  38. package/dist/plugins/ObjectShallowAnalysis.d.ts +42 -0
  39. package/dist/plugins/ObjectShallowAnalysis.d.ts.map +1 -0
  40. package/dist/plugins/ObjectShallowAnalysis.js +233 -0
  41. package/dist/plugins/ObjectShapeAnalysis.d.ts +20 -0
  42. package/dist/plugins/ObjectShapeAnalysis.d.ts.map +1 -0
  43. package/dist/plugins/ObjectShapeAnalysis.js +45 -0
  44. package/dist/plugins/ObjectSizeAnalysis.d.ts +20 -0
  45. package/dist/plugins/ObjectSizeAnalysis.d.ts.map +1 -0
  46. package/dist/plugins/ObjectSizeAnalysis.js +45 -0
  47. package/dist/plugins/ObjectUnboundGrowthAnalysis.d.ts +20 -0
  48. package/dist/plugins/ObjectUnboundGrowthAnalysis.d.ts.map +1 -0
  49. package/dist/plugins/ObjectUnboundGrowthAnalysis.js +47 -0
  50. package/dist/plugins/ShapeUnboundGrowthAnalysis.d.ts +42 -0
  51. package/dist/plugins/ShapeUnboundGrowthAnalysis.d.ts.map +1 -0
  52. package/dist/plugins/ShapeUnboundGrowthAnalysis.js +164 -0
  53. package/dist/plugins/StringAnalysis.d.ts +40 -0
  54. package/dist/plugins/StringAnalysis.d.ts.map +1 -0
  55. package/dist/plugins/StringAnalysis.js +217 -0
  56. package/dist/plugins/UnmountedReactFiberNodesAnalysis.d.ts +19 -0
  57. package/dist/plugins/UnmountedReactFiberNodesAnalysis.d.ts.map +1 -0
  58. package/dist/plugins/UnmountedReactFiberNodesAnalysis.js +46 -0
  59. package/package.json +55 -0
package/README.md ADDED
@@ -0,0 +1,11 @@
1
+ # `@memlab/heap-analysis`
2
+
3
+ > TODO: description
4
+
5
+ ## Usage
6
+
7
+ ```
8
+ const heapAnalysis = require('heap-analysis');
9
+
10
+ // TODO: DEMONSTRATE API
11
+ ```
@@ -0,0 +1,26 @@
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
+ * @emails oncall+ws_labs
8
+ * @format
9
+ */
10
+ import type { AnyValue } from '@memlab/core';
11
+ import type { HeapAnalysisOptions } from './PluginUtils';
12
+ import { BaseOption } from '@memlab/core';
13
+ declare abstract class Analysis {
14
+ protected process(_options: HeapAnalysisOptions): Promise<AnyValue>;
15
+ run(options?: HeapAnalysisOptions): Promise<AnyValue>;
16
+ analyzeSnapshotFromFile(file: string): Promise<AnyValue>;
17
+ analyzeSnapshotsInDirectory(directory: string): Promise<AnyValue>;
18
+ }
19
+ declare class BaseAnalysis extends Analysis {
20
+ getCommandName(): string;
21
+ getDescription(): string;
22
+ protected process(_options: HeapAnalysisOptions): Promise<AnyValue>;
23
+ getOptions(): BaseOption[];
24
+ }
25
+ export default BaseAnalysis;
26
+ //# sourceMappingURL=BaseAnalysis.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"BaseAnalysis.d.ts","sourceRoot":"","sources":["../src/BaseAnalysis.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,KAAK,EAAC,QAAQ,EAAC,MAAM,cAAc,CAAC;AAC3C,OAAO,KAAK,EAAC,mBAAmB,EAAC,MAAM,eAAe,CAAC;AAEvD,OAAO,EAAC,UAAU,EAA0B,MAAM,cAAc,CAAC;AAuBjE,uBAAe,QAAQ;cAEL,OAAO,CAAC,QAAQ,EAAE,mBAAmB,GAAG,OAAO,CAAC,QAAQ,CAAC;IAM5D,GAAG,CACd,OAAO,GAAE,mBAAqD,GAC7D,OAAO,CAAC,QAAQ,CAAC;IAKP,uBAAuB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,QAAQ,CAAC;IAUxD,2BAA2B,CACtC,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC,QAAQ,CAAC;CASrB;AAED,cAAM,YAAa,SAAQ,QAAQ;IAGjC,cAAc,IAAI,MAAM;IAOxB,cAAc,IAAI,MAAM;cAUR,OAAO,CAErB,QAAQ,EAAE,mBAAmB,GAC5B,OAAO,CAAC,QAAQ,CAAC;IAMpB,UAAU,IAAI,UAAU,EAAE;CAG3B;AAED,eAAe,YAAY,CAAC"}
@@ -0,0 +1,112 @@
1
+ "use strict";
2
+ /**
3
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
4
+ *
5
+ * This source code is licensed under the MIT license found in the
6
+ * LICENSE file in the root directory of this source tree.
7
+ *
8
+ * @emails oncall+ws_labs
9
+ * @format
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
+ };
20
+ var __importDefault = (this && this.__importDefault) || function (mod) {
21
+ return (mod && mod.__esModule) ? mod : { "default": mod };
22
+ };
23
+ Object.defineProperty(exports, "__esModule", { value: true });
24
+ const core_1 = require("@memlab/core");
25
+ const e2e_1 = require("@memlab/e2e");
26
+ const PluginUtils_1 = __importDefault(require("./PluginUtils"));
27
+ // Identify the target scenario and
28
+ // add its setting to Config
29
+ function loadScenarioConfig() {
30
+ core_1.utils.loadTargetInfoFromRunMeta();
31
+ if (core_1.config.targetApp === 'external' ||
32
+ core_1.config.targetTab.startsWith(core_1.constant.namePrefixForScenarioFromFile)) {
33
+ return;
34
+ }
35
+ const synthesizer = e2e_1.defaultTestPlanner.getSynthesizer({ needCookies: false });
36
+ synthesizer
37
+ .getNodeNameBlocklist()
38
+ .forEach(name => core_1.config.nodeNameBlockList.add(name));
39
+ synthesizer
40
+ .getEdgeNameBlocklist()
41
+ .forEach(name => core_1.config.edgeNameBlockList.add(name));
42
+ }
43
+ class Analysis {
44
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
45
+ process(_options) {
46
+ return __awaiter(this, void 0, void 0, function* () {
47
+ const className = this.constructor.name;
48
+ throw new Error(`${className}.process is not implemented`);
49
+ });
50
+ }
51
+ // DO NOT override this method
52
+ run(options = PluginUtils_1.default.defaultAnalysisArgs) {
53
+ return __awaiter(this, void 0, void 0, function* () {
54
+ loadScenarioConfig();
55
+ return yield this.process(options);
56
+ });
57
+ }
58
+ analyzeSnapshotFromFile(file) {
59
+ return __awaiter(this, void 0, void 0, function* () {
60
+ return this.process({
61
+ args: {
62
+ _: [],
63
+ snapshot: file,
64
+ 'snapshot-dir': '<MUST_PROVIDE_SNAPSHOT_DIR>',
65
+ },
66
+ });
67
+ });
68
+ }
69
+ analyzeSnapshotsInDirectory(directory) {
70
+ return __awaiter(this, void 0, void 0, function* () {
71
+ return this.process({
72
+ args: {
73
+ _: [],
74
+ snapshot: '<MUST_PROVIDE_SNAPSHOT_FILE>',
75
+ 'snapshot-dir': directory,
76
+ },
77
+ });
78
+ });
79
+ }
80
+ }
81
+ class BaseAnalysis extends Analysis {
82
+ // The following terminal command will initiate with this analysis
83
+ // `memlab analyze <command-name>`
84
+ getCommandName() {
85
+ const className = this.constructor.name;
86
+ throw new Error(`${className}.getCommandName is not implemented`);
87
+ }
88
+ // The description of this analysis will be printed by
89
+ // `memlab analyze list`
90
+ getDescription() {
91
+ const className = this.constructor.name;
92
+ throw new Error(`${className}.getDescription is not implemented`);
93
+ }
94
+ // Callback for `memlab analyze <command-name>`
95
+ // Do the memory analysis and print results in this callback
96
+ // The analysis should support:
97
+ // 1) printing results on screen
98
+ // 2) returning results via the return value
99
+ process(
100
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
101
+ _options) {
102
+ return __awaiter(this, void 0, void 0, function* () {
103
+ const className = this.constructor.name;
104
+ throw new Error(`${className}.process is not implemented`);
105
+ });
106
+ }
107
+ // override this method if you would like CLI to print the option info
108
+ getOptions() {
109
+ return [];
110
+ }
111
+ }
112
+ exports.default = BaseAnalysis;
@@ -0,0 +1,19 @@
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
+ * @emails oncall+ws_labs
8
+ * @format
9
+ */
10
+ import type BaseAnalysis from './BaseAnalysis';
11
+ declare class HeapAnalysisLoader {
12
+ private modules;
13
+ loadAllAnalysis(): Map<string, BaseAnalysis>;
14
+ private registerAnalyses;
15
+ private registerAnalysesFromDir;
16
+ }
17
+ declare const _default: HeapAnalysisLoader;
18
+ export default _default;
19
+ //# sourceMappingURL=HeapAnalysisLoader.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"HeapAnalysisLoader.d.ts","sourceRoot":"","sources":["../src/HeapAnalysisLoader.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAKH,OAAO,KAAK,YAAY,MAAM,gBAAgB,CAAC;AAE/C,cAAM,kBAAkB;IACtB,OAAO,CAAC,OAAO,CAAwC;IAEhD,eAAe,IAAI,GAAG,CAAC,MAAM,EAAE,YAAY,CAAC;IASnD,OAAO,CAAC,gBAAgB;IAKxB,OAAO,CAAC,uBAAuB;CA4BhC;;AAED,wBAAwC"}
@@ -0,0 +1,59 @@
1
+ "use strict";
2
+ /**
3
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
4
+ *
5
+ * This source code is licensed under the MIT license found in the
6
+ * LICENSE file in the root directory of this source tree.
7
+ *
8
+ * @emails oncall+ws_labs
9
+ * @format
10
+ */
11
+ var __importDefault = (this && this.__importDefault) || function (mod) {
12
+ return (mod && mod.__esModule) ? mod : { "default": mod };
13
+ };
14
+ Object.defineProperty(exports, "__esModule", { value: true });
15
+ const fs_1 = __importDefault(require("fs"));
16
+ const path_1 = __importDefault(require("path"));
17
+ const core_1 = require("@memlab/core");
18
+ class HeapAnalysisLoader {
19
+ constructor() {
20
+ this.modules = new Map();
21
+ }
22
+ loadAllAnalysis() {
23
+ if (this.modules.size === 0) {
24
+ // auto load all analysis modules
25
+ this.modules = new Map();
26
+ this.registerAnalyses();
27
+ }
28
+ return this.modules;
29
+ }
30
+ registerAnalyses() {
31
+ const modulesDir = path_1.default.resolve(__dirname, 'plugins');
32
+ this.registerAnalysesFromDir(modulesDir);
33
+ }
34
+ registerAnalysesFromDir(modulesDir) {
35
+ const moduleFiles = fs_1.default.readdirSync(modulesDir);
36
+ for (const moduleFile of moduleFiles) {
37
+ const modulePath = path_1.default.join(modulesDir, moduleFile);
38
+ // recursively import modules from subdirectories
39
+ if (fs_1.default.lstatSync(modulePath).isDirectory()) {
40
+ this.registerAnalysesFromDir(modulePath);
41
+ continue;
42
+ }
43
+ // only import modules files ends with with Analysis.js
44
+ if (!moduleFile.endsWith('Analysis.js')) {
45
+ continue;
46
+ }
47
+ // eslint-disable-next-line @typescript-eslint/no-var-requires
48
+ const module = require(modulePath);
49
+ const moduleConstructor = typeof module.default === 'function' ? module.default : module;
50
+ const moduleInstance = new moduleConstructor();
51
+ const commandName = moduleInstance.getCommandName();
52
+ if (this.modules.has(commandName)) {
53
+ core_1.utils.haltOrThrow(`heap command ${commandName} is already registered`);
54
+ }
55
+ this.modules.set(commandName, moduleInstance);
56
+ }
57
+ }
58
+ }
59
+ exports.default = new HeapAnalysisLoader();
@@ -0,0 +1,48 @@
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
+ * @emails oncall+ws_labs
8
+ * @format
9
+ */
10
+ import type { ParsedArgs } from 'minimist';
11
+ import { IHeapSnapshot, IHeapNode, AnyOptions, IHeapEdge, Nullable, MemLabConfig } from '@memlab/core';
12
+ export declare type HeapAnalysisOptions = {
13
+ args: ParsedArgs;
14
+ config?: MemLabConfig;
15
+ };
16
+ declare function isNodeWorthInspecting(node: IHeapNode): boolean;
17
+ declare type PrintNodeOption = {
18
+ indent?: string;
19
+ printReferences?: boolean;
20
+ };
21
+ declare function printNodeListInTerminal(nodeList: IHeapNode[], options?: AnyOptions & PrintNodeOption): void;
22
+ declare function printReferencesInTerminal(edgeList: IHeapEdge[], options?: AnyOptions & PrintNodeOption): void;
23
+ declare function getObjectOutgoingEdgeCount(node: IHeapNode): number;
24
+ declare function getSnapshotFileForAnalysis(options: HeapAnalysisOptions): string;
25
+ declare function getSnapshotDirForAnalysis(options: HeapAnalysisOptions): Nullable<string>;
26
+ declare function loadHeapSnapshot(options: HeapAnalysisOptions): Promise<IHeapSnapshot>;
27
+ declare function snapshotMapReduce<T1, T2>(mapCallback: (snapshot: IHeapSnapshot, i: number, file: string) => T1, reduceCallback: (results: T1[]) => T2, options: HeapAnalysisOptions): Promise<T2>;
28
+ declare function aggregateDominatorMetrics(ids: Set<number>, snapshot: IHeapSnapshot, checkNodeCb: (node: IHeapNode) => boolean, nodeMetricsCb: (node: IHeapNode) => number): number;
29
+ declare function filterOutLargestObjects(snapshot: IHeapSnapshot, objectFilter: (node: IHeapNode) => boolean, listSize?: number): IHeapNode[];
30
+ declare const _default: {
31
+ aggregateDominatorMetrics: typeof aggregateDominatorMetrics;
32
+ defaultAnalysisArgs: {
33
+ args: {
34
+ _: never[];
35
+ };
36
+ };
37
+ filterOutLargestObjects: typeof filterOutLargestObjects;
38
+ getObjectOutgoingEdgeCount: typeof getObjectOutgoingEdgeCount;
39
+ getSnapshotDirForAnalysis: typeof getSnapshotDirForAnalysis;
40
+ getSnapshotFileForAnalysis: typeof getSnapshotFileForAnalysis;
41
+ isNodeWorthInspecting: typeof isNodeWorthInspecting;
42
+ loadHeapSnapshot: typeof loadHeapSnapshot;
43
+ printNodeListInTerminal: typeof printNodeListInTerminal;
44
+ printReferencesInTerminal: typeof printReferencesInTerminal;
45
+ snapshotMapReduce: typeof snapshotMapReduce;
46
+ };
47
+ export default _default;
48
+ //# sourceMappingURL=PluginUtils.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"PluginUtils.d.ts","sourceRoot":"","sources":["../src/PluginUtils.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,KAAK,EAAC,UAAU,EAAC,MAAM,UAAU,CAAC;AACzC,OAAO,EACL,aAAa,EACb,SAAS,EACT,UAAU,EACV,SAAS,EACT,QAAQ,EAER,YAAY,EAEb,MAAM,cAAc,CAAC;AAKtB,oBAAY,mBAAmB,GAAG;IAChC,IAAI,EAAE,UAAU,CAAC;IACjB,MAAM,CAAC,EAAE,YAAY,CAAC;CACvB,CAAC;AAmBF,iBAAS,qBAAqB,CAAC,IAAI,EAAE,SAAS,GAAG,OAAO,CAYvD;AAiDD,aAAK,eAAe,GAAG;IACrB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,eAAe,CAAC,EAAE,OAAO,CAAC;CAC3B,CAAC;AACF,iBAAS,uBAAuB,CAC9B,QAAQ,EAAE,SAAS,EAAE,EACrB,OAAO,GAAE,UAAU,GAAG,eAAoB,GACzC,IAAI,CAgBN;AA4ED,iBAAS,yBAAyB,CAChC,QAAQ,EAAE,SAAS,EAAE,EACrB,OAAO,GAAE,UAAU,GAAG,eAAoB,GACzC,IAAI,CAON;AAED,iBAAS,0BAA0B,CAAC,IAAI,EAAE,SAAS,GAAG,MAAM,CAS3D;AAED,iBAAS,0BAA0B,CAAC,OAAO,EAAE,mBAAmB,GAAG,MAAM,CAWxE;AAED,iBAAS,yBAAyB,CAChC,OAAO,EAAE,mBAAmB,GAC3B,QAAQ,CAAC,MAAM,CAAC,CASlB;AAED,iBAAe,gBAAgB,CAC7B,OAAO,EAAE,mBAAmB,GAC3B,OAAO,CAAC,aAAa,CAAC,CAGxB;AAYD,iBAAe,iBAAiB,CAAC,EAAE,EAAE,EAAE,EACrC,WAAW,EAAE,CAAC,QAAQ,EAAE,aAAa,EAAE,CAAC,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,KAAK,EAAE,EACrE,cAAc,EAAE,CAAC,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,EACrC,OAAO,EAAE,mBAAmB,GAC3B,OAAO,CAAC,EAAE,CAAC,CAqBb;AAED,iBAAS,yBAAyB,CAChC,GAAG,EAAE,GAAG,CAAC,MAAM,CAAC,EAChB,QAAQ,EAAE,aAAa,EACvB,WAAW,EAAE,CAAC,IAAI,EAAE,SAAS,KAAK,OAAO,EACzC,aAAa,EAAE,CAAC,IAAI,EAAE,SAAS,KAAK,MAAM,GACzC,MAAM,CAWR;AAED,iBAAS,uBAAuB,CAC9B,QAAQ,EAAE,aAAa,EACvB,YAAY,EAAE,CAAC,IAAI,EAAE,SAAS,KAAK,OAAO,EAC1C,QAAQ,SAAK,GACZ,SAAS,EAAE,CAoBb;;;;;;;;;;;;;;;;;;AAED,wBAYE"}
@@ -0,0 +1,283 @@
1
+ "use strict";
2
+ /**
3
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
4
+ *
5
+ * This source code is licensed under the MIT license found in the
6
+ * LICENSE file in the root directory of this source tree.
7
+ *
8
+ * @emails oncall+ws_labs
9
+ * @format
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
+ };
20
+ var __importDefault = (this && this.__importDefault) || function (mod) {
21
+ return (mod && mod.__esModule) ? mod : { "default": mod };
22
+ };
23
+ Object.defineProperty(exports, "__esModule", { value: true });
24
+ const core_1 = require("@memlab/core");
25
+ const chalk_1 = __importDefault(require("chalk"));
26
+ const core_2 = require("@memlab/core");
27
+ const nodeNameBlockList = new Set([
28
+ '(Startup object cache)',
29
+ '(Global handles)',
30
+ '(External strings)',
31
+ '(Builtins)',
32
+ ]);
33
+ const nodeTypeBlockList = new Set([
34
+ 'array',
35
+ 'native',
36
+ 'code',
37
+ 'synthetic',
38
+ 'hidden',
39
+ ]);
40
+ const defaultAnalysisArgs = { args: { _: [] } };
41
+ function isNodeWorthInspecting(node) {
42
+ // exclude meta objects like GC roots etc.
43
+ if (node.id <= 3) {
44
+ return false;
45
+ }
46
+ if (nodeTypeBlockList.has(node.type)) {
47
+ return false;
48
+ }
49
+ if (nodeNameBlockList.has(node.name)) {
50
+ return false;
51
+ }
52
+ return true;
53
+ }
54
+ // filter out dominators that have a similar size
55
+ // for example if input is [A, B] and A is the dominator of B
56
+ // then this function throw away A if the size of A is close to the size of B
57
+ function filterOutDominators(nodeList) {
58
+ const candidateIdSet = new Set(nodeList.map(node => node.id));
59
+ const childrenSizeInList = new Map();
60
+ for (const node of nodeList) {
61
+ const visitedIds = new Set();
62
+ let curNode = node;
63
+ inner: while (!visitedIds.has(curNode.id)) {
64
+ curNode = node.dominatorNode;
65
+ if (!curNode || curNode.id === node.id) {
66
+ break inner;
67
+ }
68
+ // record the size of the children node in the candidate list
69
+ // and associate the children size with its dominator in the candidate list
70
+ if (candidateIdSet.has(curNode.id)) {
71
+ let childrenSize = node.retainedSize;
72
+ if (childrenSizeInList.has(curNode.id)) {
73
+ childrenSize += childrenSizeInList.get(curNode.id)[1];
74
+ }
75
+ childrenSizeInList.set(curNode.id, [
76
+ curNode.retainedSize,
77
+ childrenSize,
78
+ ]);
79
+ break inner;
80
+ }
81
+ visitedIds.add(curNode.id);
82
+ }
83
+ }
84
+ // remove the dominator node from the candidate set
85
+ // if the dominator node's size is similar to the child node
86
+ for (const [dominatorId, [dominatorSize, childrenSize],] of childrenSizeInList) {
87
+ if (dominatorSize - childrenSize < 500000) {
88
+ candidateIdSet.delete(dominatorId);
89
+ }
90
+ }
91
+ return nodeList.filter(node => candidateIdSet.has(node.id));
92
+ }
93
+ function printNodeListInTerminal(nodeList, options = {}) {
94
+ const dot = chalk_1.default.grey('· ');
95
+ const indent = options.indent || '';
96
+ const printRef = !!options.printReferences;
97
+ if (!options.printAll) {
98
+ nodeList = filterOutDominators(nodeList);
99
+ }
100
+ for (const node of nodeList) {
101
+ const nodeInfo = getHeapObjectString(node);
102
+ core_2.info.topLevel(`${indent}${dot}${nodeInfo}`);
103
+ if (printRef) {
104
+ printReferencesInTerminal(node.references, { indent: indent + ' ' });
105
+ }
106
+ }
107
+ }
108
+ function isNumeric(v) {
109
+ if (typeof v === 'number') {
110
+ return true;
111
+ }
112
+ if (parseInt(v, 10) + '' === v + '') {
113
+ return true;
114
+ }
115
+ if (parseFloat(v) + '' === v + '') {
116
+ return true;
117
+ }
118
+ return false;
119
+ }
120
+ function getObjectReferenceNames(node) {
121
+ const referrers = node.referrers;
122
+ const names = new Set();
123
+ const visited = new Set();
124
+ for (const edge of referrers) {
125
+ const name = edge.name_or_index;
126
+ // in case infinite loop
127
+ if (visited.has(edge.edgeIndex)) {
128
+ continue;
129
+ }
130
+ visited.add(edge.edgeIndex);
131
+ // numeric index is not informative
132
+ if (isNumeric(name) || name === '') {
133
+ continue;
134
+ }
135
+ // context and previous references are not informative
136
+ if ((name === 'previous' || name === 'context') &&
137
+ edge.type === 'internal') {
138
+ for (const ref of edge.fromNode.referrers) {
139
+ referrers.push(ref);
140
+ }
141
+ continue;
142
+ }
143
+ names.add(name);
144
+ }
145
+ const refs = Array.from(names).slice(0, 10).join(chalk_1.default.grey(', '));
146
+ return 'refs: ' + chalk_1.default.grey('[') + refs + chalk_1.default.grey(']');
147
+ }
148
+ function getHeapObjectString(node) {
149
+ const colon = chalk_1.default.grey(':');
150
+ const comma = chalk_1.default.grey(',');
151
+ const serializeOpt = { color: true, compact: true };
152
+ const shapeStr = core_2.serializer.summarizeNodeShape(node, serializeOpt);
153
+ const edgeCount = getObjectOutgoingEdgeCount(node);
154
+ const fanout = `${edgeCount} edges`;
155
+ const bytes = core_2.utils.getReadableBytes(node.retainedSize);
156
+ const nodeId = chalk_1.default.grey(`@${node.id}`);
157
+ const type = node.type === 'object' ? '' : ` ${node.type}`;
158
+ const refs = getObjectReferenceNames(node);
159
+ return (`${nodeId}${type} ${shapeStr}${colon} ` +
160
+ `${fanout}${comma} ${bytes}${comma} ${refs}`);
161
+ }
162
+ function getReferenceString(edge) {
163
+ const edgeName = chalk_1.default.green(edge.name_or_index);
164
+ const objectInfo = getHeapObjectString(edge.toNode);
165
+ return ` --${edgeName}--> ${objectInfo}`;
166
+ }
167
+ function printReferencesInTerminal(edgeList, options = {}) {
168
+ const dot = chalk_1.default.grey('· ');
169
+ const indent = options.indent || '';
170
+ for (const edge of edgeList) {
171
+ const refStr = getReferenceString(edge);
172
+ core_2.info.topLevel(`${indent}${dot}${refStr}`);
173
+ }
174
+ }
175
+ function getObjectOutgoingEdgeCount(node) {
176
+ if (node.name === 'Set' || node.name === 'Map') {
177
+ const edge = core_2.utils.getEdgeByNameAndType(node, 'table');
178
+ if (!edge) {
179
+ return node.edge_count;
180
+ }
181
+ return edge.toNode.edge_count;
182
+ }
183
+ return node.edge_count;
184
+ }
185
+ function getSnapshotFileForAnalysis(options) {
186
+ const args = options.args;
187
+ if (args.snapshot) {
188
+ return args.snapshot;
189
+ }
190
+ if (core_1.config.externalSnapshotFilePaths.length > 0) {
191
+ return core_1.config.externalSnapshotFilePaths[core_1.config.externalSnapshotFilePaths.length - 1];
192
+ }
193
+ return core_2.utils.getSingleSnapshotFileForAnalysis();
194
+ }
195
+ function getSnapshotDirForAnalysis(options) {
196
+ const args = options.args;
197
+ if (args['snapshot-dir']) {
198
+ return args['snapshot-dir'];
199
+ }
200
+ if (core_1.config.externalSnapshotDir) {
201
+ return core_1.config.externalSnapshotDir;
202
+ }
203
+ return null;
204
+ }
205
+ function loadHeapSnapshot(options) {
206
+ return __awaiter(this, void 0, void 0, function* () {
207
+ const file = getSnapshotFileForAnalysis(options);
208
+ return loadProcessedSnapshot({ file });
209
+ });
210
+ }
211
+ function loadProcessedSnapshot(options = {}) {
212
+ return __awaiter(this, void 0, void 0, function* () {
213
+ const opt = { buildNodeIdIndex: true, verbose: true };
214
+ const file = options.file || core_2.utils.getSnapshotFilePathWithTabType(/.*/);
215
+ const snapshot = yield core_2.utils.getSnapshotFromFile(file, opt);
216
+ core_2.analysis.preparePathFinder(snapshot);
217
+ return snapshot;
218
+ });
219
+ }
220
+ function snapshotMapReduce(mapCallback, reduceCallback, options) {
221
+ return __awaiter(this, void 0, void 0, function* () {
222
+ const snapshotDir = getSnapshotDirForAnalysis(options);
223
+ core_2.utils.checkSnapshots({ snapshotDir });
224
+ const snapshotFiles = snapshotDir
225
+ ? // load snapshots from a directory
226
+ core_2.utils.getSnapshotFilesInDir(snapshotDir)
227
+ : // load snapshots based on the visit sequence meta data
228
+ core_2.utils.getSnapshotFilesFromTabsOrder();
229
+ const intermediateResults = [];
230
+ for (let i = 0; i < snapshotFiles.length; ++i) {
231
+ const file = snapshotFiles[i];
232
+ // force GC before loading each snapshot
233
+ if (global.gc) {
234
+ global.gc();
235
+ }
236
+ const snapshot = yield loadProcessedSnapshot({ file });
237
+ intermediateResults.push(mapCallback(snapshot, i, file));
238
+ }
239
+ return reduceCallback(intermediateResults);
240
+ });
241
+ }
242
+ function aggregateDominatorMetrics(ids, snapshot, checkNodeCb, nodeMetricsCb) {
243
+ let ret = 0;
244
+ const dominators = core_2.utils.getConditionalDominatorIds(ids, snapshot, checkNodeCb);
245
+ core_2.utils.applyToNodes(dominators, snapshot, node => {
246
+ ret += nodeMetricsCb(node);
247
+ });
248
+ return ret;
249
+ }
250
+ function filterOutLargestObjects(snapshot, objectFilter, listSize = 50) {
251
+ let largeObjects = [];
252
+ snapshot.nodes.forEach(node => {
253
+ if (!objectFilter(node)) {
254
+ return;
255
+ }
256
+ const size = node.retainedSize;
257
+ let i;
258
+ for (i = largeObjects.length - 1; i >= 0; --i) {
259
+ if (largeObjects[i].retainedSize >= size) {
260
+ largeObjects.splice(i + 1, 0, node);
261
+ break;
262
+ }
263
+ }
264
+ if (i < 0) {
265
+ largeObjects.unshift(node);
266
+ }
267
+ largeObjects = largeObjects.slice(0, listSize);
268
+ });
269
+ return largeObjects;
270
+ }
271
+ exports.default = {
272
+ aggregateDominatorMetrics,
273
+ defaultAnalysisArgs,
274
+ filterOutLargestObjects,
275
+ getObjectOutgoingEdgeCount,
276
+ getSnapshotDirForAnalysis,
277
+ getSnapshotFileForAnalysis,
278
+ isNodeWorthInspecting,
279
+ loadHeapSnapshot,
280
+ printNodeListInTerminal,
281
+ printReferencesInTerminal,
282
+ snapshotMapReduce,
283
+ };
@@ -0,0 +1,11 @@
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
+ * @emails oncall+ws_labs
8
+ * @format
9
+ */
10
+ export {};
11
+ //# sourceMappingURL=HeapAnalysis.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"HeapAnalysis.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/HeapAnalysis.test.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG"}
@@ -0,0 +1,32 @@
1
+ "use strict";
2
+ /**
3
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
4
+ *
5
+ * This source code is licensed under the MIT license found in the
6
+ * LICENSE file in the root directory of this source tree.
7
+ *
8
+ * @emails oncall+ws_labs
9
+ * @format
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
+ };
20
+ var __importDefault = (this && this.__importDefault) || function (mod) {
21
+ return (mod && mod.__esModule) ? mod : { "default": mod };
22
+ };
23
+ Object.defineProperty(exports, "__esModule", { value: true });
24
+ const core_1 = require("@memlab/core");
25
+ const HeapAnalysisLoader_1 = __importDefault(require("../HeapAnalysisLoader"));
26
+ beforeEach(() => {
27
+ core_1.config.isTest = true;
28
+ });
29
+ test('Heap analysis modules can be loaded', () => __awaiter(void 0, void 0, void 0, function* () {
30
+ const heapAnalysisMap = HeapAnalysisLoader_1.default.loadAllAnalysis();
31
+ expect(heapAnalysisMap.size).toBeGreaterThan(0);
32
+ }));
@@ -0,0 +1,23 @@
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
+ * @emails oncall+ws_labs
8
+ * @format
9
+ */
10
+ export { default as heapAnalysisLoader } from './HeapAnalysisLoader';
11
+ export { default as BaseAnalysis } from './BaseAnalysis';
12
+ export { default as DetachedDOMElementAnalysis } from './plugins/DetachedDOMElementAnalysis';
13
+ export { default as GlobalVariableAnalysis } from './plugins/GlobalVariableAnalysis/GlobalVariableAnalysis';
14
+ export { default as CollectionsHoldingStaleAnalysis } from './plugins/CollectionsHoldingStaleAnalysis';
15
+ export { default as ObjectShallowAnalysis } from './plugins/ObjectShallowAnalysis';
16
+ export { default as ObjectSizeAnalysis } from './plugins/ObjectSizeAnalysis';
17
+ export { default as ShapeUnboundGrowthAnalysis } from './plugins/ShapeUnboundGrowthAnalysis';
18
+ export { default as ObjectFanoutAnalysis } from './plugins/ObjectFanoutAnalysis';
19
+ export { default as ObjectShapeAnalysis } from './plugins/ObjectShapeAnalysis';
20
+ export { default as ObjectUnboundGrowthAnalysis } from './plugins/ObjectUnboundGrowthAnalysis';
21
+ export { default as StringAnalysis } from './plugins/StringAnalysis';
22
+ export { default as PluginUtils } from './PluginUtils';
23
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,EAAC,OAAO,IAAI,kBAAkB,EAAC,MAAM,sBAAsB,CAAC;AACnE,OAAO,EAAC,OAAO,IAAI,YAAY,EAAC,MAAM,gBAAgB,CAAC;AACvD,OAAO,EAAC,OAAO,IAAI,0BAA0B,EAAC,MAAM,sCAAsC,CAAC;AAC3F,OAAO,EAAC,OAAO,IAAI,sBAAsB,EAAC,MAAM,yDAAyD,CAAC;AAC1G,OAAO,EAAC,OAAO,IAAI,+BAA+B,EAAC,MAAM,2CAA2C,CAAC;AACrG,OAAO,EAAC,OAAO,IAAI,qBAAqB,EAAC,MAAM,iCAAiC,CAAC;AACjF,OAAO,EAAC,OAAO,IAAI,kBAAkB,EAAC,MAAM,8BAA8B,CAAC;AAC3E,OAAO,EAAC,OAAO,IAAI,0BAA0B,EAAC,MAAM,sCAAsC,CAAC;AAC3F,OAAO,EAAC,OAAO,IAAI,oBAAoB,EAAC,MAAM,gCAAgC,CAAC;AAC/E,OAAO,EAAC,OAAO,IAAI,mBAAmB,EAAC,MAAM,+BAA+B,CAAC;AAC7E,OAAO,EAAC,OAAO,IAAI,2BAA2B,EAAC,MAAM,uCAAuC,CAAC;AAC7F,OAAO,EAAC,OAAO,IAAI,cAAc,EAAC,MAAM,0BAA0B,CAAC;AACnE,OAAO,EAAC,OAAO,IAAI,WAAW,EAAC,MAAM,eAAe,CAAC"}