@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.
- package/README.md +11 -0
- package/dist/BaseAnalysis.d.ts +26 -0
- package/dist/BaseAnalysis.d.ts.map +1 -0
- package/dist/BaseAnalysis.js +112 -0
- package/dist/HeapAnalysisLoader.d.ts +19 -0
- package/dist/HeapAnalysisLoader.d.ts.map +1 -0
- package/dist/HeapAnalysisLoader.js +59 -0
- package/dist/PluginUtils.d.ts +48 -0
- package/dist/PluginUtils.d.ts.map +1 -0
- package/dist/PluginUtils.js +283 -0
- package/dist/__tests__/HeapAnalysis.test.d.ts +11 -0
- package/dist/__tests__/HeapAnalysis.test.d.ts.map +1 -0
- package/dist/__tests__/HeapAnalysis.test.js +32 -0
- package/dist/index.d.ts +23 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +41 -0
- package/dist/options/HeapAnalysisSnapshotDirectoryOption.d.ts +18 -0
- package/dist/options/HeapAnalysisSnapshotDirectoryOption.d.ts.map +1 -0
- package/dist/options/HeapAnalysisSnapshotDirectoryOption.js +50 -0
- package/dist/options/HeapAnalysisSnapshotFileOption.d.ts +18 -0
- package/dist/options/HeapAnalysisSnapshotFileOption.d.ts.map +1 -0
- package/dist/options/HeapAnalysisSnapshotFileOption.js +50 -0
- package/dist/plugins/CollectionsHoldingStaleAnalysis.d.ts +22 -0
- package/dist/plugins/CollectionsHoldingStaleAnalysis.d.ts.map +1 -0
- package/dist/plugins/CollectionsHoldingStaleAnalysis.js +132 -0
- package/dist/plugins/DetachedDOMElementAnalysis.d.ts +22 -0
- package/dist/plugins/DetachedDOMElementAnalysis.d.ts.map +1 -0
- package/dist/plugins/DetachedDOMElementAnalysis.js +53 -0
- package/dist/plugins/GlobalVariableAnalysis/BuiltInGlobalVariables.d.ts +13 -0
- package/dist/plugins/GlobalVariableAnalysis/BuiltInGlobalVariables.d.ts.map +1 -0
- package/dist/plugins/GlobalVariableAnalysis/BuiltInGlobalVariables.js +1073 -0
- package/dist/plugins/GlobalVariableAnalysis/GlobalVariableAnalysis.d.ts +22 -0
- package/dist/plugins/GlobalVariableAnalysis/GlobalVariableAnalysis.d.ts.map +1 -0
- package/dist/plugins/GlobalVariableAnalysis/GlobalVariableAnalysis.js +78 -0
- package/dist/plugins/ObjectFanoutAnalysis.d.ts +21 -0
- package/dist/plugins/ObjectFanoutAnalysis.d.ts.map +1 -0
- package/dist/plugins/ObjectFanoutAnalysis.js +69 -0
- package/dist/plugins/ObjectShallowAnalysis.d.ts +42 -0
- package/dist/plugins/ObjectShallowAnalysis.d.ts.map +1 -0
- package/dist/plugins/ObjectShallowAnalysis.js +233 -0
- package/dist/plugins/ObjectShapeAnalysis.d.ts +20 -0
- package/dist/plugins/ObjectShapeAnalysis.d.ts.map +1 -0
- package/dist/plugins/ObjectShapeAnalysis.js +45 -0
- package/dist/plugins/ObjectSizeAnalysis.d.ts +20 -0
- package/dist/plugins/ObjectSizeAnalysis.d.ts.map +1 -0
- package/dist/plugins/ObjectSizeAnalysis.js +45 -0
- package/dist/plugins/ObjectUnboundGrowthAnalysis.d.ts +20 -0
- package/dist/plugins/ObjectUnboundGrowthAnalysis.d.ts.map +1 -0
- package/dist/plugins/ObjectUnboundGrowthAnalysis.js +47 -0
- package/dist/plugins/ShapeUnboundGrowthAnalysis.d.ts +42 -0
- package/dist/plugins/ShapeUnboundGrowthAnalysis.d.ts.map +1 -0
- package/dist/plugins/ShapeUnboundGrowthAnalysis.js +164 -0
- package/dist/plugins/StringAnalysis.d.ts +40 -0
- package/dist/plugins/StringAnalysis.d.ts.map +1 -0
- package/dist/plugins/StringAnalysis.js +217 -0
- package/dist/plugins/UnmountedReactFiberNodesAnalysis.d.ts +19 -0
- package/dist/plugins/UnmountedReactFiberNodesAnalysis.d.ts.map +1 -0
- package/dist/plugins/UnmountedReactFiberNodesAnalysis.js +46 -0
- package/package.json +55 -0
package/README.md
ADDED
|
@@ -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
|
+
}));
|
package/dist/index.d.ts
ADDED
|
@@ -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"}
|