@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
@@ -0,0 +1,47 @@
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 BaseAnalysis_1 = __importDefault(require("../BaseAnalysis"));
26
+ const HeapAnalysisSnapshotDirectoryOption_1 = __importDefault(require("../options/HeapAnalysisSnapshotDirectoryOption"));
27
+ const PluginUtils_1 = __importDefault(require("../PluginUtils"));
28
+ class ObjectUnboundGrowthAnalysis extends BaseAnalysis_1.default {
29
+ getCommandName() {
30
+ return 'unbound-object';
31
+ }
32
+ getDescription() {
33
+ return 'Check unbound object growth';
34
+ }
35
+ getOptions() {
36
+ return [new HeapAnalysisSnapshotDirectoryOption_1.default()];
37
+ }
38
+ process(options) {
39
+ return __awaiter(this, void 0, void 0, function* () {
40
+ const snapshotDir = PluginUtils_1.default.getSnapshotDirForAnalysis(options);
41
+ const opt = snapshotDir ? { minSnapshots: 2, snapshotDir } : {};
42
+ core_1.config.chaseWeakMapEdge = false;
43
+ yield core_1.analysis.checkUnbound(opt);
44
+ });
45
+ }
46
+ }
47
+ exports.default = ObjectUnboundGrowthAnalysis;
@@ -0,0 +1,42 @@
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 { IHeapSnapshot } from '@memlab/core';
11
+ import type { HeapAnalysisOptions } from '../PluginUtils';
12
+ import { BaseOption } from '@memlab/core';
13
+ import BaseAnalysis from '../BaseAnalysis';
14
+ declare type ShapesInfo = {
15
+ [shape: string]: ShapeInfo;
16
+ };
17
+ declare type ShapeInfo = {
18
+ shape: string;
19
+ n: number;
20
+ examples: number[];
21
+ size: number;
22
+ ids: Set<number>;
23
+ };
24
+ declare type ShapeSummary = {
25
+ shape: string;
26
+ counts: number[];
27
+ sizes: number[];
28
+ examples: number[];
29
+ };
30
+ export default class ShapeUnboundGrowthAnalysis extends BaseAnalysis {
31
+ getCommandName(): string;
32
+ getDescription(): string;
33
+ getOptions(): BaseOption[];
34
+ private shapesOfInterest;
35
+ process(options: HeapAnalysisOptions): Promise<ShapeSummary[]>;
36
+ private retrieveShapesOfInterest;
37
+ getShapesInfo(snapshot: IHeapSnapshot): ShapesInfo;
38
+ getSummary(ShapesInfoList: ShapesInfo[]): ShapeSummary[];
39
+ private print;
40
+ }
41
+ export {};
42
+ //# sourceMappingURL=ShapeUnboundGrowthAnalysis.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ShapeUnboundGrowthAnalysis.d.ts","sourceRoot":"","sources":["../../src/plugins/ShapeUnboundGrowthAnalysis.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,KAAK,EAAC,aAAa,EAAW,MAAM,cAAc,CAAC;AAC1D,OAAO,KAAK,EAAC,mBAAmB,EAAC,MAAM,gBAAgB,CAAC;AAGxD,OAAO,EAAC,UAAU,EAAkC,MAAM,cAAc,CAAC;AAEzE,OAAO,YAAY,MAAM,iBAAiB,CAAC;AAG3C,aAAK,UAAU,GAAG;IAChB,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,CAAC;CAC5B,CAAC;AAEF,aAAK,SAAS,GAAG;IACf,KAAK,EAAE,MAAM,CAAC;IACd,CAAC,EAAE,MAAM,CAAC;IACV,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,IAAI,EAAE,MAAM,CAAC;IACb,GAAG,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;CAClB,CAAC;AAEF,aAAK,YAAY,GAAG;IAClB,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,KAAK,EAAE,MAAM,EAAE,CAAC;IAChB,QAAQ,EAAE,MAAM,EAAE,CAAC;CACpB,CAAC;AAEF,MAAM,CAAC,OAAO,OAAO,0BAA2B,SAAQ,YAAY;IAC3D,cAAc,IAAI,MAAM;IAIxB,cAAc,IAAI,MAAM;IAI/B,UAAU,IAAI,UAAU,EAAE;IAI1B,OAAO,CAAC,gBAAgB,CAA+B;IAE1C,OAAO,CAAC,OAAO,EAAE,mBAAmB,GAAG,OAAO,CAAC,YAAY,EAAE,CAAC;IAoB3E,OAAO,CAAC,wBAAwB;IAOhC,aAAa,CAAC,QAAQ,EAAE,aAAa,GAAG,UAAU;IAoDlD,UAAU,CAAC,cAAc,EAAE,UAAU,EAAE,GAAG,YAAY,EAAE;IAyDxD,OAAO,CAAC,KAAK;CAmBd"}
@@ -0,0 +1,164 @@
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 chalk_1 = __importDefault(require("chalk"));
25
+ const core_1 = require("@memlab/core");
26
+ const HeapAnalysisSnapshotDirectoryOption_1 = __importDefault(require("../options/HeapAnalysisSnapshotDirectoryOption"));
27
+ const BaseAnalysis_1 = __importDefault(require("../BaseAnalysis"));
28
+ const PluginUtils_1 = __importDefault(require("../PluginUtils"));
29
+ class ShapeUnboundGrowthAnalysis extends BaseAnalysis_1.default {
30
+ constructor() {
31
+ super(...arguments);
32
+ this.shapesOfInterest = null;
33
+ }
34
+ getCommandName() {
35
+ return 'unbound-shape';
36
+ }
37
+ getDescription() {
38
+ return 'Get shapes with unbound growth';
39
+ }
40
+ getOptions() {
41
+ return [new HeapAnalysisSnapshotDirectoryOption_1.default()];
42
+ }
43
+ process(options) {
44
+ return __awaiter(this, void 0, void 0, function* () {
45
+ let list = yield PluginUtils_1.default.snapshotMapReduce(this.getShapesInfo.bind(this), this.getSummary, options);
46
+ this.retrieveShapesOfInterest(list);
47
+ list = yield PluginUtils_1.default.snapshotMapReduce(this.getShapesInfo.bind(this), this.getSummary, options);
48
+ this.shapesOfInterest = null;
49
+ this.print(list);
50
+ return list;
51
+ });
52
+ }
53
+ retrieveShapesOfInterest(list) {
54
+ this.shapesOfInterest = new Set();
55
+ for (const summary of list) {
56
+ this.shapesOfInterest.add(summary.shape);
57
+ }
58
+ }
59
+ getShapesInfo(snapshot) {
60
+ const population = Object.create(null);
61
+ const shapes = this.shapesOfInterest;
62
+ // group objects based on their shapes
63
+ snapshot.nodes.forEach(node => {
64
+ if ((node.type !== 'object' && !core_1.utils.isStringNode(node)) ||
65
+ core_1.config.nodeIgnoreSetInShape.has(node.name)) {
66
+ return;
67
+ }
68
+ const key = core_1.serializer.summarizeNodeShape(node, { color: true });
69
+ if (shapes && !shapes.has(key)) {
70
+ return;
71
+ }
72
+ population[key] =
73
+ population[key] ||
74
+ {
75
+ shape: key,
76
+ n: 0,
77
+ examples: [],
78
+ ids: new Set(),
79
+ };
80
+ const record = population[key];
81
+ ++record.n;
82
+ if (record.examples.length < 3) {
83
+ record.examples.push(node.id);
84
+ }
85
+ if (shapes && shapes.has(key)) {
86
+ record.ids.add(node.id);
87
+ }
88
+ });
89
+ if (shapes) {
90
+ for (const record of Object.values(population)) {
91
+ record.size = PluginUtils_1.default.aggregateDominatorMetrics(record.ids, snapshot, () => true, node => node.retainedSize);
92
+ record.ids = new Set();
93
+ }
94
+ }
95
+ return population;
96
+ }
97
+ getSummary(ShapesInfoList) {
98
+ const shapes = Object.create(null);
99
+ for (const population of ShapesInfoList) {
100
+ for (const key in population) {
101
+ shapes[key] = shapes[key] || {
102
+ decrease: false,
103
+ increase: false,
104
+ counts: [],
105
+ sizes: [],
106
+ examples: [],
107
+ };
108
+ if (shapes[key].decrease) {
109
+ continue;
110
+ }
111
+ const counts = shapes[key].counts;
112
+ const n = population[key].n;
113
+ if (counts.length > 0 && counts[counts.length - 1] > n) {
114
+ shapes[key].decrease = true;
115
+ continue;
116
+ }
117
+ if (counts.length > 0 && counts[counts.length - 1] < n) {
118
+ shapes[key].increase = true;
119
+ }
120
+ counts.push(n);
121
+ shapes[key].sizes.push(population[key].size);
122
+ const examples = shapes[key].examples;
123
+ examples.push(population[key].examples[0]);
124
+ }
125
+ }
126
+ const result = [];
127
+ for (const key in shapes) {
128
+ if (!shapes[key].increase) {
129
+ continue;
130
+ }
131
+ if (shapes[key].decrease || shapes[key].counts.length <= 1) {
132
+ continue;
133
+ }
134
+ const counts = shapes[key].counts;
135
+ if (counts[counts.length - 1] < 1000) {
136
+ continue;
137
+ }
138
+ const examples = shapes[key].examples;
139
+ const sizes = shapes[key].sizes;
140
+ result.push({ shape: key, counts, examples, sizes });
141
+ }
142
+ result.sort((v1, v2) => v2.counts[v2.counts.length - 1] - v1.counts[v1.counts.length - 1]);
143
+ return result;
144
+ }
145
+ print(list) {
146
+ const colon = chalk_1.default.grey(': ');
147
+ const sep = chalk_1.default.grey(' > ');
148
+ const sep2 = chalk_1.default.grey(', ');
149
+ const dot = chalk_1.default.grey('· ');
150
+ for (const item of list) {
151
+ const shapeStat = [];
152
+ for (let i = 0; i < item.counts.length; ++i) {
153
+ let size = core_1.utils.getReadableBytes(item.sizes[i]);
154
+ size = chalk_1.default.grey(size);
155
+ shapeStat.push(`${item.counts[i]} (${size})`);
156
+ }
157
+ const countHistory = shapeStat.join(sep);
158
+ const examples = item.examples.map(v => `@${v}`).join(sep2);
159
+ const msg = `${dot}${item.shape}${colon}${countHistory}${colon}${examples}`;
160
+ core_1.info.topLevel(msg);
161
+ }
162
+ }
163
+ }
164
+ exports.default = ShapeUnboundGrowthAnalysis;
@@ -0,0 +1,40 @@
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 { HeapAnalysisOptions } from '../PluginUtils';
11
+ import { BaseOption } from '@memlab/core';
12
+ import BaseAnalysis from '../BaseAnalysis';
13
+ declare type StringRecord = {
14
+ n: number;
15
+ size: number;
16
+ ids: string[];
17
+ str?: string;
18
+ };
19
+ export default class StringAnalysis extends BaseAnalysis {
20
+ private topDupStrInCnt;
21
+ private topDupStrInCntListSize;
22
+ private topDupStrInSize;
23
+ private topDupStrInSizeListSize;
24
+ private stringPatternsStat;
25
+ getTopDuplicatedStringsInCount(): StringRecord[];
26
+ private static stringPatternsToObserve;
27
+ getCommandName(): string;
28
+ getDescription(): string;
29
+ getOptions(): BaseOption[];
30
+ private static shouldIgnoreNode;
31
+ process(options: HeapAnalysisOptions): Promise<void>;
32
+ private getPreprocessedStringMap;
33
+ private rankRecords;
34
+ private calculateTopDuplicatedStringsInCount;
35
+ private calculateTopDuplicatedStringsInSize;
36
+ private calculateStringPatternsStatistics;
37
+ private print;
38
+ }
39
+ export {};
40
+ //# sourceMappingURL=StringAnalysis.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"StringAnalysis.d.ts","sourceRoot":"","sources":["../../src/plugins/StringAnalysis.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAGH,OAAO,KAAK,EAAC,mBAAmB,EAAC,MAAM,gBAAgB,CAAC;AAGxD,OAAO,EAAc,UAAU,EAAC,MAAM,cAAc,CAAC;AACrD,OAAO,YAAY,MAAM,iBAAiB,CAAC;AAQ3C,aAAK,YAAY,GAAG;IAClB,CAAC,EAAE,MAAM,CAAC;IACV,IAAI,EAAE,MAAM,CAAC;IACb,GAAG,EAAE,MAAM,EAAE,CAAC;IACd,GAAG,CAAC,EAAE,MAAM,CAAC;CACd,CAAC;AAiBF,MAAM,CAAC,OAAO,OAAO,cAAe,SAAQ,YAAY;IACtD,OAAO,CAAC,cAAc,CAAsB;IAC5C,OAAO,CAAC,sBAAsB,CAAM;IACpC,OAAO,CAAC,eAAe,CAAsB;IAC7C,OAAO,CAAC,uBAAuB,CAAM;IACrC,OAAO,CAAC,kBAAkB,CAAyB;IAE5C,8BAA8B,IAAI,YAAY,EAAE;IAMvD,OAAO,CAAC,MAAM,CAAC,uBAAuB,CAmCpC;IAEK,cAAc,IAAI,MAAM;IAIxB,cAAc,IAAI,MAAM;IAI/B,UAAU,IAAI,UAAU,EAAE;IAI1B,OAAO,CAAC,MAAM,CAAC,gBAAgB;IAczB,OAAO,CAAC,OAAO,EAAE,mBAAmB,GAAG,OAAO,CAAC,IAAI,CAAC;IAU1D,OAAO,CAAC,wBAAwB;IAwBhC,OAAO,CAAC,WAAW;IA0BnB,OAAO,CAAC,oCAAoC;IAS5C,OAAO,CAAC,mCAAmC;IAS3C,OAAO,CAAC,iCAAiC;IA6BzC,OAAO,CAAC,KAAK;CA4Cd"}
@@ -0,0 +1,217 @@
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 chalk_1 = __importDefault(require("chalk"));
25
+ const core_1 = require("@memlab/core");
26
+ const BaseAnalysis_1 = __importDefault(require("../BaseAnalysis"));
27
+ const PluginUtils_1 = __importDefault(require("../PluginUtils"));
28
+ const HeapAnalysisSnapshotFileOption_1 = __importDefault(require("../options/HeapAnalysisSnapshotFileOption"));
29
+ class StringAnalysis extends BaseAnalysis_1.default {
30
+ constructor() {
31
+ super(...arguments);
32
+ this.topDupStrInCnt = [];
33
+ this.topDupStrInCntListSize = 10;
34
+ this.topDupStrInSize = [];
35
+ this.topDupStrInSizeListSize = 10;
36
+ this.stringPatternsStat = {};
37
+ }
38
+ getTopDuplicatedStringsInCount() {
39
+ return this.topDupStrInCnt;
40
+ }
41
+ getCommandName() {
42
+ return 'string';
43
+ }
44
+ getDescription() {
45
+ return 'Analyze string in heap';
46
+ }
47
+ getOptions() {
48
+ return [new HeapAnalysisSnapshotFileOption_1.default()];
49
+ }
50
+ static shouldIgnoreNode(node) {
51
+ if (!core_1.utils.isStringNode(node) || core_1.utils.isSlicedStringNode(node)) {
52
+ return true;
53
+ }
54
+ // ignore strings retained by code
55
+ const dominators = core_1.utils.getAllDominators(node);
56
+ const hasCodeDominator = dominators.reduce((prev, cur) => prev && cur.type !== 'code', true);
57
+ return !hasCodeDominator;
58
+ }
59
+ process(options) {
60
+ return __awaiter(this, void 0, void 0, function* () {
61
+ const snapshot = yield PluginUtils_1.default.loadHeapSnapshot(options);
62
+ const stringMap = this.getPreprocessedStringMap(snapshot);
63
+ this.calculateStringPatternsStatistics(stringMap);
64
+ this.calculateTopDuplicatedStringsInCount(stringMap);
65
+ this.calculateTopDuplicatedStringsInSize(stringMap);
66
+ this.print();
67
+ });
68
+ }
69
+ // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
70
+ getPreprocessedStringMap(snapshot) {
71
+ core_1.info.overwrite('building string map...');
72
+ const stringMap = Object.create(null);
73
+ // going through string nodes
74
+ snapshot.nodes.forEach((node) => {
75
+ if (StringAnalysis.shouldIgnoreNode(node)) {
76
+ return;
77
+ }
78
+ const strValue = core_1.utils.getStringNodeValue(node);
79
+ stringMap[strValue] = stringMap[strValue] || {
80
+ n: 0,
81
+ size: 0,
82
+ ids: [],
83
+ };
84
+ const record = stringMap[strValue];
85
+ ++record.n;
86
+ record.size += node.retainedSize;
87
+ record.ids.push(node.id);
88
+ });
89
+ return stringMap;
90
+ }
91
+ rankRecords(stringMap, compare, listSize) {
92
+ let rank = [];
93
+ for (const [str, record] of Object.entries(stringMap)) {
94
+ if (record.n <= 1) {
95
+ continue; // only care about duplicated strings
96
+ }
97
+ let i = 0;
98
+ for (i = rank.length - 1; i >= 0; --i) {
99
+ const item = rank[i];
100
+ if (compare(item, record) >= 0) {
101
+ rank.splice(i + 1, 0, Object.assign({ str }, record));
102
+ break;
103
+ }
104
+ }
105
+ if (i < 0) {
106
+ rank.unshift(Object.assign({ str }, record));
107
+ }
108
+ rank = rank.slice(0, listSize);
109
+ }
110
+ return rank;
111
+ }
112
+ calculateTopDuplicatedStringsInCount(stringMap) {
113
+ core_1.info.overwrite('calculating top duplicated strings in count...');
114
+ this.topDupStrInCnt = this.rankRecords(stringMap, (item1, item2) => item1.n - item2.n, this.topDupStrInCntListSize);
115
+ }
116
+ calculateTopDuplicatedStringsInSize(stringMap) {
117
+ core_1.info.overwrite('calculating top duplicated strings in size...');
118
+ this.topDupStrInSize = this.rankRecords(stringMap, (item1, item2) => item1.size - item2.size, this.topDupStrInSizeListSize);
119
+ }
120
+ calculateStringPatternsStatistics(stringMap) {
121
+ core_1.info.overwrite('calculating statistics for specified string patterns...');
122
+ const strPatternStat = Object.create(null);
123
+ for (const [str, record] of Object.entries(stringMap)) {
124
+ patternLoop: for (const [patternName, patternCheck] of Object.entries(StringAnalysis.stringPatternsToObserve)) {
125
+ if (!patternCheck(str)) {
126
+ continue patternLoop;
127
+ }
128
+ strPatternStat[patternName] = strPatternStat[patternName] || {
129
+ n: 0,
130
+ dupN: 0,
131
+ size: 0,
132
+ dupSize: 0,
133
+ };
134
+ const item = strPatternStat[patternName];
135
+ item.n += record.n;
136
+ item.size += record.size;
137
+ if (record.n > 1) {
138
+ item.dupN += record.n - 1;
139
+ item.dupSize += (record.size * (record.n - 1)) / record.n;
140
+ }
141
+ }
142
+ }
143
+ this.stringPatternsStat = strPatternStat;
144
+ }
145
+ print() {
146
+ const sep = chalk_1.default.grey(', ');
147
+ const colon = chalk_1.default.grey(': ');
148
+ const beg = chalk_1.default.grey('[');
149
+ const end = chalk_1.default.grey(']');
150
+ const dot = chalk_1.default.grey('·');
151
+ const head = chalk_1.default.yellow.bind(chalk_1.default);
152
+ // print statistics of specified string patterns
153
+ for (const [str, item] of Object.entries(this.stringPatternsStat)) {
154
+ core_1.info.topLevel(head(`\n${str}`));
155
+ let p = core_1.utils.getReadablePercent(item.dupN / item.n);
156
+ core_1.info.topLevel(`${dot}Count: ${item.n} (${p} are duplicated)`);
157
+ const size = core_1.utils.getReadableBytes(item.size);
158
+ p = core_1.utils.getReadablePercent(item.dupSize / item.size);
159
+ core_1.info.topLevel(`${dot}Size: ${size} (${p} are duplicated)`);
160
+ }
161
+ core_1.info.topLevel(head('\nTop duplicated strings in count'));
162
+ for (let i = 0; i < this.topDupStrInCnt.length; ++i) {
163
+ const item = this.topDupStrInCnt[i];
164
+ const size = core_1.utils.getReadableBytes(item.size);
165
+ const str = `"${chalk_1.default.blue(item.str)}"`;
166
+ core_1.info.topLevel(` ${dot}${beg}${item.n}${sep}${size}${end}${colon}${str}`);
167
+ }
168
+ core_1.info.topLevel(head('\nTop duplicated strings in size:'));
169
+ for (let i = 0; i < this.topDupStrInSize.length; ++i) {
170
+ const item = this.topDupStrInSize[i];
171
+ const size = core_1.utils.getReadableBytes(item.size);
172
+ const str = `"${chalk_1.default.blue(item.str)}"`;
173
+ const exampleIds = item.ids.slice(0, 10).map((id) => `@${id}`);
174
+ let examples = exampleIds.join(', ');
175
+ if (exampleIds.length < item.ids.length) {
176
+ examples += ' ...';
177
+ }
178
+ examples = chalk_1.default.grey(examples);
179
+ core_1.info.topLevel(`\n ${dot}${beg}${size}${sep}${item.n}${end}${colon}${str}`);
180
+ core_1.info.topLevel(` node examples: ${examples}`);
181
+ }
182
+ }
183
+ }
184
+ exports.default = StringAnalysis;
185
+ // collect statistics for specified string patterns
186
+ // pattern name -> string pattern checker
187
+ StringAnalysis.stringPatternsToObserve = {
188
+ // all strings (excluding sliced strings)
189
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
190
+ 'All strings': (_) => true,
191
+ 'Relay DataID': (str) => {
192
+ return str.startsWith('Uzpf');
193
+ },
194
+ 'Relay DataID composite': (str) => {
195
+ return /^\d+\{"/.test(str);
196
+ },
197
+ // example: "{"query_id":-7401558440803739294,"serialized":"z5_eJztfQ...
198
+ 'Relay query': (str) => {
199
+ return str.startsWith('{"query_id":');
200
+ },
201
+ 'Relay client string': (str) => {
202
+ return str.startsWith('client:');
203
+ },
204
+ // example: n00je7tq arfg74bv qs9ysxi8 k77z8yql i09qtzwb n7fi1qx3
205
+ 'Element class name': (str) => {
206
+ const arr = str.split(' ');
207
+ if (arr.length < 6) {
208
+ return false;
209
+ }
210
+ for (const s of arr) {
211
+ if (s.length !== 8) {
212
+ return false;
213
+ }
214
+ }
215
+ return true;
216
+ },
217
+ };
@@ -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 { HeapAnalysisOptions } from '../PluginUtils';
11
+ import { BaseOption } from '@memlab/core';
12
+ import BaseAnalysis from '../BaseAnalysis';
13
+ export default class UnmountedFiberNodeAnalysis extends BaseAnalysis {
14
+ getCommandName(): string;
15
+ getDescription(): string;
16
+ getOptions(): BaseOption[];
17
+ process(options: HeapAnalysisOptions): Promise<void>;
18
+ }
19
+ //# sourceMappingURL=UnmountedReactFiberNodesAnalysis.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"UnmountedReactFiberNodesAnalysis.d.ts","sourceRoot":"","sources":["../../src/plugins/UnmountedReactFiberNodesAnalysis.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,KAAK,EAAC,mBAAmB,EAAC,MAAM,gBAAgB,CAAC;AAExD,OAAO,EAAQ,UAAU,EAAC,MAAM,cAAc,CAAC;AAE/C,OAAO,YAAY,MAAM,iBAAiB,CAAC;AAG3C,MAAM,CAAC,OAAO,OAAO,0BAA2B,SAAQ,YAAY;IAClE,cAAc,IAAI,MAAM;IAIxB,cAAc,IAAI,MAAM;IAIxB,UAAU,IAAI,UAAU,EAAE;IAIpB,OAAO,CAAC,OAAO,EAAE,mBAAmB,GAAG,OAAO,CAAC,IAAI,CAAC;CAQ3D"}
@@ -0,0 +1,46 @@
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 HeapAnalysisSnapshotFileOption_1 = __importDefault(require("../options/HeapAnalysisSnapshotFileOption"));
26
+ const BaseAnalysis_1 = __importDefault(require("../BaseAnalysis"));
27
+ const PluginUtils_1 = __importDefault(require("../PluginUtils"));
28
+ class UnmountedFiberNodeAnalysis extends BaseAnalysis_1.default {
29
+ getCommandName() {
30
+ return 'unmounted-fiber-node';
31
+ }
32
+ getDescription() {
33
+ return 'Get unmounted React Fiber nodes';
34
+ }
35
+ getOptions() {
36
+ return [new HeapAnalysisSnapshotFileOption_1.default()];
37
+ }
38
+ process(options) {
39
+ return __awaiter(this, void 0, void 0, function* () {
40
+ const snapshot = yield PluginUtils_1.default.loadHeapSnapshot(options);
41
+ const largeObjects = PluginUtils_1.default.filterOutLargestObjects(snapshot, core_1.utils.isDetachedFiberNode);
42
+ PluginUtils_1.default.printNodeListInTerminal(largeObjects);
43
+ });
44
+ }
45
+ }
46
+ exports.default = UnmountedFiberNodeAnalysis;
package/package.json ADDED
@@ -0,0 +1,55 @@
1
+ {
2
+ "name": "@memlab/heap-analysis",
3
+ "version": "1.0.0",
4
+ "description": "heap analysis plugins for memlab",
5
+ "keywords": [
6
+ "heap",
7
+ "analysis",
8
+ "plugin"
9
+ ],
10
+ "author": "Liang Gong <lgong@fb.com>",
11
+ "contributors": [],
12
+ "license": "MIT",
13
+ "main": "dist/index.js",
14
+ "types": "dist/index.d.ts",
15
+ "files": [
16
+ "dist"
17
+ ],
18
+ "dependencies": {
19
+ "@memlab/core": "^1.0.0",
20
+ "@memlab/e2e": "^1.0.0",
21
+ "ansi": "^0.3.1",
22
+ "babar": "^0.2.0",
23
+ "chalk": "^4.0.0",
24
+ "fs-extra": "^4.0.2",
25
+ "minimist": "^1.2.0",
26
+ "puppeteer": "^13.5.1",
27
+ "string-width": "^4.2.0",
28
+ "util.promisify": "^1.1.1",
29
+ "xvfb": "^0.4.0"
30
+ },
31
+ "devDependencies": {
32
+ "@types/fs-extra": "^9.0.3",
33
+ "@types/jest": "^27.4.1",
34
+ "@types/minimist": "^1.2.2",
35
+ "@types/node": "^12.16.3",
36
+ "@types/puppeteer": "^5.4.4",
37
+ "jest": "^27.5.1",
38
+ "ts-jest": "^27.1.4",
39
+ "typescript": "^4.6.3"
40
+ },
41
+ "repository": {
42
+ "type": "git",
43
+ "url": "git+https://github.com/facebookincubator/memlab.git",
44
+ "directory": "packages/heap-analysis"
45
+ },
46
+ "scripts": {
47
+ "build-pkg": "tsc",
48
+ "test-pkg": "jest .",
49
+ "clean-pkg": "rm -rf ./dist && rm -rf ./node_modules && rm -f ./tsconfig.tsbuildinfo"
50
+ },
51
+ "bugs": {
52
+ "url": "https://github.com/facebookincubator/memlab/issues"
53
+ },
54
+ "homepage": "https://github.com/facebookincubator/memlab#readme"
55
+ }