@memlab/core 1.1.20 → 1.1.22
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +2 -0
- package/dist/index.js +4 -1
- package/dist/lib/Config.d.ts +11 -0
- package/dist/lib/Config.js +26 -1
- package/dist/lib/Constant.js +1 -0
- package/dist/lib/FileManager.d.ts +3 -0
- package/dist/lib/FileManager.js +10 -1
- package/dist/lib/HeapAnalyzer.d.ts +5 -1
- package/dist/lib/HeapAnalyzer.js +56 -34
- package/dist/lib/RunInfoUtils.d.ts +39 -0
- package/dist/lib/RunInfoUtils.js +86 -0
- package/dist/lib/TraceSampler.d.ts +36 -0
- package/dist/lib/TraceSampler.js +78 -0
- package/dist/lib/Types.d.ts +12 -2
- package/dist/lib/Utils.d.ts +31 -19
- package/dist/lib/Utils.js +124 -145
- package/dist/lib/charts/MemoryBarChart.d.ts +1 -0
- package/dist/lib/charts/MemoryBarChart.js +18 -3
- package/dist/lib/heap-data/HeapStringNode.js +25 -16
- package/dist/lib/leak-filters/rules/FilterOverSizedNodeAsLeak.rule.js +50 -0
- package/dist/lib/trace-filters/BaseTraceFilter.rule.d.ts +29 -0
- package/dist/lib/trace-filters/BaseTraceFilter.rule.js +22 -0
- package/dist/lib/trace-filters/LeakTraceFilter.d.ts +20 -0
- package/dist/lib/trace-filters/LeakTraceFilter.js +37 -0
- package/dist/lib/trace-filters/TraceFilterRuleList.d.ts +13 -0
- package/dist/lib/trace-filters/TraceFilterRuleList.js +35 -0
- package/dist/lib/trace-filters/rules/FilterAttachedDOMToDetachedDOMTrace.rule.d.ts +15 -0
- package/dist/lib/trace-filters/rules/FilterAttachedDOMToDetachedDOMTrace.rule.js +55 -0
- package/dist/lib/trace-filters/rules/FilterCppRootsToDetachedDOMTrace.rule.d.ts +15 -0
- package/dist/lib/trace-filters/rules/FilterCppRootsToDetachedDOMTrace.rule.js +44 -0
- package/dist/lib/trace-filters/rules/FilterDOMNodeChainTrace.rule.d.ts +15 -0
- package/dist/lib/trace-filters/rules/FilterDOMNodeChainTrace.rule.js +41 -0
- package/dist/lib/trace-filters/rules/FilterHermesTrace.rule.d.ts +15 -0
- package/dist/lib/trace-filters/rules/FilterHermesTrace.rule.js +29 -0
- package/dist/lib/trace-filters/rules/FilterInternalNodeTrace.rule.d.ts +15 -0
- package/dist/lib/trace-filters/rules/FilterInternalNodeTrace.rule.js +57 -0
- package/dist/lib/trace-filters/rules/FilterPendingActivitiesTrace.rule.d.ts +15 -0
- package/dist/lib/trace-filters/rules/FilterPendingActivitiesTrace.rule.js +62 -0
- package/dist/lib/trace-filters/rules/FilterShadowRootTrace.rule.d.ts +15 -0
- package/dist/lib/trace-filters/rules/FilterShadowRootTrace.rule.js +44 -0
- package/dist/lib/trace-filters/rules/FilterStyleEngineTrace.rule.d.ts +15 -0
- package/dist/lib/trace-filters/rules/FilterStyleEngineTrace.rule.js +49 -0
- package/dist/logger/LeakClusterLogger.js +1 -0
- package/dist/paths/TraceFinder.js +17 -3
- package/dist/trace-cluster/TraceBucket.d.ts +1 -1
- package/dist/trace-cluster/TraceBucket.js +20 -14
- package/dist/trace-cluster/strategies/TraceSimilarityStrategy.js +1 -0
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -26,6 +26,8 @@ export { default as utils } from './lib/Utils';
|
|
|
26
26
|
/** @internal */
|
|
27
27
|
export { default as fileManager } from './lib/FileManager';
|
|
28
28
|
/** @internal */
|
|
29
|
+
export { default as runInfoUtils } from './lib/RunInfoUtils';
|
|
30
|
+
/** @internal */
|
|
29
31
|
export * from './lib/FileManager';
|
|
30
32
|
/** @internal */
|
|
31
33
|
export { default as serializer } from './lib/Serializer';
|
package/dist/index.js
CHANGED
|
@@ -35,7 +35,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
35
35
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
36
36
|
};
|
|
37
37
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
38
|
-
exports.TraceFinder = exports.MultiIterationSeqClustering = exports.SequentialClustering = exports.EvaluationMetric = exports.NormalizedTrace = exports.leakClusterLogger = exports.ProcessManager = exports.modes = exports.memoryBarChart = exports.constant = exports.analysis = exports.browserInfo = exports.serializer = exports.fileManager = exports.utils = exports.BaseOption = exports.info = exports.config = exports.registerPackage = void 0;
|
|
38
|
+
exports.TraceFinder = exports.MultiIterationSeqClustering = exports.SequentialClustering = exports.EvaluationMetric = exports.NormalizedTrace = exports.leakClusterLogger = exports.ProcessManager = exports.modes = exports.memoryBarChart = exports.constant = exports.analysis = exports.browserInfo = exports.serializer = exports.runInfoUtils = exports.fileManager = exports.utils = exports.BaseOption = exports.info = exports.config = exports.registerPackage = void 0;
|
|
39
39
|
const path_1 = __importDefault(require("path"));
|
|
40
40
|
const PackageInfoLoader_1 = require("./lib/PackageInfoLoader");
|
|
41
41
|
__exportStar(require("./lib/Types"), exports);
|
|
@@ -67,6 +67,9 @@ Object.defineProperty(exports, "utils", { enumerable: true, get: function () { r
|
|
|
67
67
|
var FileManager_1 = require("./lib/FileManager");
|
|
68
68
|
Object.defineProperty(exports, "fileManager", { enumerable: true, get: function () { return __importDefault(FileManager_1).default; } });
|
|
69
69
|
/** @internal */
|
|
70
|
+
var RunInfoUtils_1 = require("./lib/RunInfoUtils");
|
|
71
|
+
Object.defineProperty(exports, "runInfoUtils", { enumerable: true, get: function () { return __importDefault(RunInfoUtils_1).default; } });
|
|
72
|
+
/** @internal */
|
|
70
73
|
__exportStar(require("./lib/FileManager"), exports);
|
|
71
74
|
/** @internal */
|
|
72
75
|
var Serializer_1 = require("./lib/Serializer");
|
package/dist/lib/Config.d.ts
CHANGED
|
@@ -38,6 +38,11 @@ declare type ConfigOption = {
|
|
|
38
38
|
workDir?: string;
|
|
39
39
|
};
|
|
40
40
|
/** @internal */
|
|
41
|
+
export declare enum TraceObjectMode {
|
|
42
|
+
Default = 1,
|
|
43
|
+
SelectedJSObjects = 2
|
|
44
|
+
}
|
|
45
|
+
/** @internal */
|
|
41
46
|
export declare enum ErrorHandling {
|
|
42
47
|
Halt = 1,
|
|
43
48
|
Throw = 2
|
|
@@ -99,6 +104,7 @@ export declare class MemLabConfig {
|
|
|
99
104
|
dataBuilderDataDir: string;
|
|
100
105
|
unclassifiedClusterDir: string;
|
|
101
106
|
externalCookiesFile: Optional<string>;
|
|
107
|
+
extraRunInfoMap: Map<string, string>;
|
|
102
108
|
heapConfig: Optional<IHeapConfig>;
|
|
103
109
|
puppeteerConfig: LaunchOptions & BrowserLaunchArgumentOptions & BrowserConnectOptions;
|
|
104
110
|
openDevtoolsConsole: boolean;
|
|
@@ -176,6 +182,7 @@ export declare class MemLabConfig {
|
|
|
176
182
|
nodeIgnoreSetInShape: Set<string>;
|
|
177
183
|
oversizeObjectAsLeak: boolean;
|
|
178
184
|
oversizeThreshold: number;
|
|
185
|
+
traceAllObjectsMode: TraceObjectMode;
|
|
179
186
|
clusterRetainedSizeThreshold: number;
|
|
180
187
|
externalLeakFilter?: Optional<ILeakFilter>;
|
|
181
188
|
monoRepoDir: string;
|
|
@@ -198,6 +205,9 @@ export declare class MemLabConfig {
|
|
|
198
205
|
interceptScript: boolean;
|
|
199
206
|
isAnalyzingMainThread: boolean;
|
|
200
207
|
targetWorkerTitle: Nullable<string>;
|
|
208
|
+
noReCluster: boolean;
|
|
209
|
+
maxSamplesForClustering: number;
|
|
210
|
+
filterTraceByName: Nullable<string>;
|
|
201
211
|
constructor(options?: ConfigOption);
|
|
202
212
|
private initInternalConfigs;
|
|
203
213
|
private init;
|
|
@@ -225,6 +235,7 @@ export declare class MemLabConfig {
|
|
|
225
235
|
setDevice(deviceName: string, options?: {
|
|
226
236
|
manualOverride?: boolean;
|
|
227
237
|
}): void;
|
|
238
|
+
setRunInfo(key: string, value: string): void;
|
|
228
239
|
private removeFromSet;
|
|
229
240
|
private addToSet;
|
|
230
241
|
enableXvfb(display: string): void;
|
package/dist/lib/Config.js
CHANGED
|
@@ -35,7 +35,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
35
35
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
36
36
|
};
|
|
37
37
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
38
|
-
exports.MemLabConfig = exports.ErrorHandling = void 0;
|
|
38
|
+
exports.MemLabConfig = exports.ErrorHandling = exports.TraceObjectMode = void 0;
|
|
39
39
|
const path_1 = __importDefault(require("path"));
|
|
40
40
|
const RunningModes_1 = __importDefault(require("../modes/RunningModes"));
|
|
41
41
|
const Console_1 = __importDefault(require("./Console"));
|
|
@@ -57,6 +57,12 @@ const defaultViewport = {
|
|
|
57
57
|
deviceScaleFactor: 1,
|
|
58
58
|
};
|
|
59
59
|
/** @internal */
|
|
60
|
+
var TraceObjectMode;
|
|
61
|
+
(function (TraceObjectMode) {
|
|
62
|
+
TraceObjectMode[TraceObjectMode["Default"] = 1] = "Default";
|
|
63
|
+
TraceObjectMode[TraceObjectMode["SelectedJSObjects"] = 2] = "SelectedJSObjects";
|
|
64
|
+
})(TraceObjectMode = exports.TraceObjectMode || (exports.TraceObjectMode = {}));
|
|
65
|
+
/** @internal */
|
|
60
66
|
var ErrorHandling;
|
|
61
67
|
(function (ErrorHandling) {
|
|
62
68
|
ErrorHandling[ErrorHandling["Halt"] = 1] = "Halt";
|
|
@@ -176,6 +182,17 @@ class MemLabConfig {
|
|
|
176
182
|
// if true, split dataset into trunks
|
|
177
183
|
// with random order for sequential clustering
|
|
178
184
|
this.seqClusteringIsRandomChunks = false;
|
|
185
|
+
// maximum number of samples as input for leak trace clustering
|
|
186
|
+
this.maxSamplesForClustering = 5000;
|
|
187
|
+
// extra E2E run info (other than the fields defined in
|
|
188
|
+
// RunMetaInfo like app, interaction, browserInfo).
|
|
189
|
+
// Information saved in this map will be
|
|
190
|
+
// auto-serialized to run-meta.json when the file is saved
|
|
191
|
+
// and auto-deserialized from run-meta.json when the file is loaded
|
|
192
|
+
this.extraRunInfoMap = new Map();
|
|
193
|
+
// if specified via CLI options, this will filter leak traces by
|
|
194
|
+
// node and edge names in the leak trace
|
|
195
|
+
this.filterTraceByName = null;
|
|
179
196
|
}
|
|
180
197
|
// initialize configurable parameters
|
|
181
198
|
init(options = {}) {
|
|
@@ -203,6 +220,8 @@ class MemLabConfig {
|
|
|
203
220
|
this.skipGC = false;
|
|
204
221
|
// true if running in ContinuousTest
|
|
205
222
|
this.isContinuousTest = false;
|
|
223
|
+
// true if reclustering is turned off
|
|
224
|
+
this.noReCluster = false;
|
|
206
225
|
// true if running a local test
|
|
207
226
|
this.isTest = false;
|
|
208
227
|
// true if running in local puppeteer mode
|
|
@@ -335,6 +354,9 @@ class MemLabConfig {
|
|
|
335
354
|
this.oversizeObjectAsLeak = false;
|
|
336
355
|
// if larger than this threshold, consider as memory leak
|
|
337
356
|
this.oversizeThreshold = 0;
|
|
357
|
+
// when specified default, this mode will trace/diff all objects
|
|
358
|
+
// you can specified other modes (e.g., selected JS objects only)
|
|
359
|
+
this.traceAllObjectsMode = TraceObjectMode.Default;
|
|
338
360
|
// only report leak clusters with aggregated retained size
|
|
339
361
|
// bigger than this threshold
|
|
340
362
|
this.clusterRetainedSizeThreshold = 0;
|
|
@@ -507,6 +529,9 @@ class MemLabConfig {
|
|
|
507
529
|
this.puppeteerConfig.defaultViewport = null;
|
|
508
530
|
this.defaultUserAgent = null;
|
|
509
531
|
}
|
|
532
|
+
setRunInfo(key, value) {
|
|
533
|
+
this.extraRunInfoMap.set(key, value);
|
|
534
|
+
}
|
|
510
535
|
removeFromSet(set, list) {
|
|
511
536
|
for (const v of list) {
|
|
512
537
|
set.delete(v);
|
package/dist/lib/Constant.js
CHANGED
|
@@ -10,6 +10,8 @@
|
|
|
10
10
|
import type { MemLabConfig } from './Config';
|
|
11
11
|
import type { AnyValue, FileOption } from './Types';
|
|
12
12
|
/** @internal */
|
|
13
|
+
export declare function joinAndProcessDir(options: FileOption, ...args: AnyValue[]): string;
|
|
14
|
+
/** @internal */
|
|
13
15
|
export declare class FileManager {
|
|
14
16
|
private memlabConfigCache;
|
|
15
17
|
getDefaultWorkDir(): string;
|
|
@@ -75,6 +77,7 @@ export declare class FileManager {
|
|
|
75
77
|
rmWorkDir(options?: FileOption): void;
|
|
76
78
|
isWithinInternalDirectory(filePath: string): boolean;
|
|
77
79
|
createDefaultVisitOrderMetaFile(options?: FileOption): void;
|
|
80
|
+
createDefaultVisitOrderMetaFileWithSingleSnapshot(options: FileOption | undefined, snapshotFile: string): void;
|
|
78
81
|
initDirs(config: MemLabConfig, options?: FileOption): void;
|
|
79
82
|
}
|
|
80
83
|
declare const _default: FileManager;
|
package/dist/lib/FileManager.js
CHANGED
|
@@ -12,13 +12,14 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
12
12
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
13
13
|
};
|
|
14
14
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
15
|
-
exports.FileManager = void 0;
|
|
15
|
+
exports.FileManager = exports.joinAndProcessDir = void 0;
|
|
16
16
|
const fs_extra_1 = __importDefault(require("fs-extra"));
|
|
17
17
|
const os_1 = __importDefault(require("os"));
|
|
18
18
|
const path_1 = __importDefault(require("path"));
|
|
19
19
|
const Console_1 = __importDefault(require("./Console"));
|
|
20
20
|
const Constant_1 = __importDefault(require("./Constant"));
|
|
21
21
|
const Utils_1 = __importDefault(require("./Utils"));
|
|
22
|
+
/** @internal */
|
|
22
23
|
function joinAndProcessDir(options, ...args) {
|
|
23
24
|
const filepath = path_1.default.join(...args);
|
|
24
25
|
if (!fs_extra_1.default.existsSync(filepath)) {
|
|
@@ -34,6 +35,7 @@ function joinAndProcessDir(options, ...args) {
|
|
|
34
35
|
}
|
|
35
36
|
return filepath;
|
|
36
37
|
}
|
|
38
|
+
exports.joinAndProcessDir = joinAndProcessDir;
|
|
37
39
|
/** @internal */
|
|
38
40
|
class FileManager {
|
|
39
41
|
constructor() {
|
|
@@ -310,6 +312,9 @@ class FileManager {
|
|
|
310
312
|
}
|
|
311
313
|
}
|
|
312
314
|
isDirectory(file) {
|
|
315
|
+
if (!fs_extra_1.default.existsSync(file)) {
|
|
316
|
+
return false;
|
|
317
|
+
}
|
|
313
318
|
const stats = fs_extra_1.default.statSync(file);
|
|
314
319
|
return stats.isDirectory();
|
|
315
320
|
}
|
|
@@ -356,6 +361,10 @@ class FileManager {
|
|
|
356
361
|
}
|
|
357
362
|
// If there is at least one snapshot, create a snap-seq.json file.
|
|
358
363
|
// First, get the meta file for leak detection in a single heap snapshot
|
|
364
|
+
this.createDefaultVisitOrderMetaFileWithSingleSnapshot(options, snapshotFile);
|
|
365
|
+
}
|
|
366
|
+
createDefaultVisitOrderMetaFileWithSingleSnapshot(options = FileManager.defaultFileOption, snapshotFile) {
|
|
367
|
+
const snapshotSeqMetaFile = this.getSnapshotSequenceMetaFile(options);
|
|
359
368
|
const codeDataDir = this.getCodeDataDir();
|
|
360
369
|
const singleSnapshotMetaFile = path_1.default.join(codeDataDir, 'visit-order-single-snapshot.json');
|
|
361
370
|
const visitOrder = JSON.parse(fs_extra_1.default.readFileSync(singleSnapshotMetaFile, 'UTF-8'));
|
|
@@ -16,6 +16,10 @@ declare type DiffSnapshotsOptions = {
|
|
|
16
16
|
declare type WorkDirOptions = {
|
|
17
17
|
workDir?: string;
|
|
18
18
|
};
|
|
19
|
+
declare type GetTraceOptions = {
|
|
20
|
+
workDir?: string;
|
|
21
|
+
printConsoleOnly?: boolean;
|
|
22
|
+
};
|
|
19
23
|
declare class MemoryAnalyst {
|
|
20
24
|
checkLeak(): Promise<ISerializedInfo[]>;
|
|
21
25
|
diffLeakByWorkDir(options: DiffLeakOptions): Promise<ISerializedInfo[]>;
|
|
@@ -50,7 +54,7 @@ declare class MemoryAnalyst {
|
|
|
50
54
|
serializeClusterUpdate(clusters: TraceCluster[], options?: {
|
|
51
55
|
reclusterOnly?: boolean;
|
|
52
56
|
}): Promise<void>;
|
|
53
|
-
dumpPathByNodeId(leakedIdSet: HeapNodeIdSet, snapshot: IHeapSnapshot, nodeIdsInSnapshots: Array<HeapNodeIdSet>, id: number, pathLoaderFile: string, summaryFile: string, options?:
|
|
57
|
+
dumpPathByNodeId(leakedIdSet: HeapNodeIdSet, snapshot: IHeapSnapshot, nodeIdsInSnapshots: Array<HeapNodeIdSet>, id: number, pathLoaderFile: string, summaryFile: string, options?: GetTraceOptions): void;
|
|
54
58
|
}
|
|
55
59
|
declare const _default: MemoryAnalyst;
|
|
56
60
|
export default _default;
|
package/dist/lib/HeapAnalyzer.js
CHANGED
|
@@ -34,6 +34,8 @@ const TraceFinder_1 = __importDefault(require("../paths/TraceFinder"));
|
|
|
34
34
|
const TraceBucket_1 = __importDefault(require("../trace-cluster/TraceBucket"));
|
|
35
35
|
const LeakObjectFilter_1 = require("./leak-filters/LeakObjectFilter");
|
|
36
36
|
const MLTraceSimilarityStrategy_1 = __importDefault(require("../trace-cluster/strategies/MLTraceSimilarityStrategy"));
|
|
37
|
+
const LeakTraceFilter_1 = require("./trace-filters/LeakTraceFilter");
|
|
38
|
+
const TraceSampler_1 = __importDefault(require("./TraceSampler"));
|
|
37
39
|
class MemoryAnalyst {
|
|
38
40
|
checkLeak() {
|
|
39
41
|
return __awaiter(this, void 0, void 0, function* () {
|
|
@@ -44,14 +46,14 @@ class MemoryAnalyst {
|
|
|
44
46
|
}
|
|
45
47
|
diffLeakByWorkDir(options) {
|
|
46
48
|
return __awaiter(this, void 0, void 0, function* () {
|
|
47
|
-
const
|
|
48
|
-
workDir:
|
|
49
|
-
});
|
|
49
|
+
const controlSnapshotDirs = options.controlWorkDirs.map(controlWorkDir => FileManager_1.default.getCurDataDir({
|
|
50
|
+
workDir: controlWorkDir,
|
|
51
|
+
}));
|
|
50
52
|
const treatmentSnapshotDir = FileManager_1.default.getCurDataDir({
|
|
51
53
|
workDir: options.treatmentWorkDir,
|
|
52
54
|
});
|
|
53
55
|
// check control working dir
|
|
54
|
-
Utils_1.default.checkSnapshots({ snapshotDir: controlSnapshotDir });
|
|
56
|
+
controlSnapshotDirs.forEach(controlSnapshotDir => Utils_1.default.checkSnapshots({ snapshotDir: controlSnapshotDir }));
|
|
55
57
|
// check treatment working dir
|
|
56
58
|
Utils_1.default.checkSnapshots({ snapshotDir: treatmentSnapshotDir });
|
|
57
59
|
// display control and treatment memory
|
|
@@ -63,31 +65,37 @@ class MemoryAnalyst {
|
|
|
63
65
|
diffMemoryLeakTraces(options) {
|
|
64
66
|
return __awaiter(this, void 0, void 0, function* () {
|
|
65
67
|
Config_1.default.dumpNodeInfo = false;
|
|
66
|
-
// diff snapshots and get control raw paths
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
68
|
+
// diff snapshots from control dirs and get control raw paths array
|
|
69
|
+
const controlSnapshots = [];
|
|
70
|
+
const leakPathsFromControlRuns = [];
|
|
71
|
+
for (const controlWorkDir of options.controlWorkDirs) {
|
|
72
|
+
const snapshotDiff = yield this.diffSnapshots({
|
|
73
|
+
loadAllSnapshots: true,
|
|
74
|
+
workDir: controlWorkDir,
|
|
75
|
+
});
|
|
76
|
+
leakPathsFromControlRuns.push(this.filterLeakPaths(snapshotDiff.leakedHeapNodeIdSet, snapshotDiff.snapshot, { workDir: controlWorkDir }));
|
|
77
|
+
controlSnapshots.push(snapshotDiff.snapshot);
|
|
78
|
+
}
|
|
73
79
|
// diff snapshots and get treatment raw paths
|
|
74
|
-
snapshotDiff = yield this.diffSnapshots({
|
|
80
|
+
const snapshotDiff = yield this.diffSnapshots({
|
|
75
81
|
loadAllSnapshots: true,
|
|
76
82
|
workDir: options.treatmentWorkDir,
|
|
77
83
|
});
|
|
78
|
-
const treatmentLeakPaths = this.filterLeakPaths(snapshotDiff.leakedHeapNodeIdSet, snapshotDiff.snapshot, { workDir: options.
|
|
84
|
+
const treatmentLeakPaths = this.filterLeakPaths(snapshotDiff.leakedHeapNodeIdSet, snapshotDiff.snapshot, { workDir: options.treatmentWorkDir });
|
|
79
85
|
const treatmentSnapshot = snapshotDiff.snapshot;
|
|
80
|
-
|
|
86
|
+
const controlPathCounts = JSON.stringify(leakPathsFromControlRuns.map(leakPaths => leakPaths.length));
|
|
87
|
+
Console_1.default.topLevel(`${controlPathCounts} traces from control group`);
|
|
81
88
|
Console_1.default.topLevel(`${treatmentLeakPaths.length} traces from treatment group`);
|
|
82
|
-
const result = TraceBucket_1.default.clusterControlTreatmentPaths(
|
|
89
|
+
const result = TraceBucket_1.default.clusterControlTreatmentPaths(leakPathsFromControlRuns, controlSnapshots, treatmentLeakPaths, treatmentSnapshot, Utils_1.default.aggregateDominatorMetrics, {
|
|
83
90
|
strategy: Config_1.default.isMLClustering
|
|
84
91
|
? new MLTraceSimilarityStrategy_1.default()
|
|
85
92
|
: undefined,
|
|
86
93
|
});
|
|
87
94
|
Console_1.default.midLevel(`MemLab found ${result.treatmentOnlyClusters.length} new leak(s) in the treatment group`);
|
|
88
95
|
yield this.serializeClusterUpdate(result.treatmentOnlyClusters);
|
|
89
|
-
//
|
|
90
|
-
|
|
96
|
+
// serialize JSON file with detailed leak trace information
|
|
97
|
+
const treatmentOnlyPaths = result.treatmentOnlyClusters.map(c => c.path);
|
|
98
|
+
return LeakTraceDetailsLogger_1.default.logTraces(snapshotDiff.leakedHeapNodeIdSet, snapshotDiff.snapshot, snapshotDiff.listOfLeakedHeapNodeIdSet, treatmentOnlyPaths, Config_1.default.traceJsonOutDir);
|
|
91
99
|
});
|
|
92
100
|
}
|
|
93
101
|
// find all unique pattern of leaks
|
|
@@ -124,7 +132,7 @@ class MemoryAnalyst {
|
|
|
124
132
|
snapshotLeakedHeapNodeIdSet = snapshotDiff.leakedHeapNodeIdSet;
|
|
125
133
|
snapshot = snapshotDiff.snapshot;
|
|
126
134
|
}
|
|
127
|
-
this.dumpPathByNodeId(snapshotLeakedHeapNodeIdSet, snapshot, nodeIdsInSnapshots, Config_1.default.focusFiberNodeId, Config_1.default.viewJsonFile, Config_1.default.singleReportSummary);
|
|
135
|
+
this.dumpPathByNodeId(snapshotLeakedHeapNodeIdSet, snapshot, nodeIdsInSnapshots, Config_1.default.focusFiberNodeId, Config_1.default.viewJsonFile, Config_1.default.singleReportSummary, { printConsoleOnly: true });
|
|
128
136
|
});
|
|
129
137
|
}
|
|
130
138
|
shouldLoadCompleteSnapshot(tabsOrder, tab) {
|
|
@@ -348,26 +356,37 @@ class MemoryAnalyst {
|
|
|
348
356
|
this.printHeapAndLeakInfo(leakedNodeIds, snapshot, options);
|
|
349
357
|
// get all leaked objects
|
|
350
358
|
this.filterLeakedObjects(leakedNodeIds, snapshot);
|
|
359
|
+
const leakTraceFilter = new LeakTraceFilter_1.LeakTraceFilter();
|
|
351
360
|
const nodeIdInPaths = new Set();
|
|
352
|
-
const
|
|
353
|
-
let numOfLeakedObjects = 0;
|
|
354
|
-
let i = 0;
|
|
361
|
+
const samplePool = [];
|
|
355
362
|
// analysis for each node
|
|
356
363
|
Utils_1.default.applyToNodes(leakedNodeIds, snapshot, node => {
|
|
357
|
-
if (!Config_1.default.isContinuousTest && ++i % 11 === 0) {
|
|
358
|
-
Console_1.default.overwrite(`progress: ${i} / ${leakedNodeIds.size} @${node.id}`);
|
|
359
|
-
}
|
|
360
364
|
// BFS search for path from the leaked node to GC roots
|
|
361
365
|
const p = finder.getPathToGCRoots(snapshot, node);
|
|
362
|
-
if (
|
|
366
|
+
if (p == null ||
|
|
367
|
+
!leakTraceFilter.filter(p, { config: Config_1.default, leakedNodeIds, snapshot })) {
|
|
368
|
+
return;
|
|
369
|
+
}
|
|
370
|
+
// filter leak trace based on CLI-specified node or edge names
|
|
371
|
+
if (!Utils_1.default.pathHasNodeOrEdgeWithName(p, Config_1.default.filterTraceByName)) {
|
|
372
|
+
return;
|
|
373
|
+
}
|
|
374
|
+
// ignore if the leak trace is too long
|
|
375
|
+
if (Utils_1.default.getLeakTracePathLength(p) > 100) {
|
|
363
376
|
return;
|
|
364
377
|
}
|
|
365
|
-
|
|
366
|
-
paths.push(p);
|
|
367
|
-
this.logLeakTraceSummary(p, nodeIdInPaths, snapshot, options);
|
|
378
|
+
samplePool.push(p);
|
|
368
379
|
}, { reverse: true });
|
|
380
|
+
const sampler = new TraceSampler_1.default(samplePool.length);
|
|
381
|
+
const paths = samplePool.filter(p => {
|
|
382
|
+
if (sampler.sample()) {
|
|
383
|
+
this.logLeakTraceSummary(p, nodeIdInPaths, snapshot, options);
|
|
384
|
+
return true;
|
|
385
|
+
}
|
|
386
|
+
return false;
|
|
387
|
+
});
|
|
369
388
|
if (Config_1.default.verbose) {
|
|
370
|
-
Console_1.default.midLevel(
|
|
389
|
+
Console_1.default.midLevel(`Filter and select ${paths.length} leaked trace`);
|
|
371
390
|
}
|
|
372
391
|
return paths;
|
|
373
392
|
}
|
|
@@ -401,11 +420,11 @@ class MemoryAnalyst {
|
|
|
401
420
|
clusterHeapObjects(objectIds, snapshot) {
|
|
402
421
|
const finder = this.preparePathFinder(snapshot);
|
|
403
422
|
const paths = [];
|
|
404
|
-
|
|
423
|
+
const sampler = new TraceSampler_1.default(objectIds.size);
|
|
405
424
|
// analysis for each node
|
|
406
425
|
Utils_1.default.applyToNodes(objectIds, snapshot, node => {
|
|
407
|
-
if (
|
|
408
|
-
|
|
426
|
+
if (!sampler.sample()) {
|
|
427
|
+
return;
|
|
409
428
|
}
|
|
410
429
|
// BFS search for path from the leaked node to GC roots
|
|
411
430
|
const p = finder.getPathToGCRoots(snapshot, node);
|
|
@@ -451,10 +470,13 @@ class MemoryAnalyst {
|
|
|
451
470
|
return;
|
|
452
471
|
}
|
|
453
472
|
LeakTraceDetailsLogger_1.default.logTrace(leakedIdSet, snapshot, nodeIdsInSnapshots, path, pathLoaderFile);
|
|
454
|
-
const tabsOrder = Utils_1.default.loadTabsOrder(FileManager_1.default.getSnapshotSequenceMetaFile(options));
|
|
455
|
-
const interactionSummary = Serializer_1.default.summarizeTabsOrder(tabsOrder);
|
|
456
473
|
let pathSummary = Serializer_1.default.summarizePath(path, nodeIdInPaths, snapshot, { color: true });
|
|
457
474
|
Console_1.default.topLevel(pathSummary);
|
|
475
|
+
if (options.printConsoleOnly) {
|
|
476
|
+
return;
|
|
477
|
+
}
|
|
478
|
+
const tabsOrder = Utils_1.default.loadTabsOrder(FileManager_1.default.getSnapshotSequenceMetaFile(options));
|
|
479
|
+
const interactionSummary = Serializer_1.default.summarizeTabsOrder(tabsOrder);
|
|
458
480
|
pathSummary = Serializer_1.default.summarizePath(path, nodeIdInPaths, snapshot);
|
|
459
481
|
const summary = `Page Interaction: \n${interactionSummary}\n\n` +
|
|
460
482
|
`Path from GC Root to Leaked Object:\n${pathSummary}`;
|
|
@@ -0,0 +1,39 @@
|
|
|
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
|
+
* @format
|
|
8
|
+
* @oncall web_perf_infra
|
|
9
|
+
*/
|
|
10
|
+
import type { Nullable, Optional, RunMetaInfo } from './Types';
|
|
11
|
+
export declare class RunMetaInfoManager {
|
|
12
|
+
getRunMetaFilePath(options?: {
|
|
13
|
+
workDir?: Optional<string>;
|
|
14
|
+
}): string;
|
|
15
|
+
saveRunMetaInfo(runMetaInfo: RunMetaInfo, options?: {
|
|
16
|
+
workDir?: Optional<string>;
|
|
17
|
+
extraRunInfo?: Map<string, string>;
|
|
18
|
+
}): string;
|
|
19
|
+
private loadRunMetaInfoFromFile;
|
|
20
|
+
loadRunMetaInfo(options?: {
|
|
21
|
+
metaFile?: Optional<string>;
|
|
22
|
+
workDir?: Optional<string>;
|
|
23
|
+
}): RunMetaInfo;
|
|
24
|
+
loadRunMetaInfoSilentFail(options?: {
|
|
25
|
+
metaFile?: Optional<string>;
|
|
26
|
+
workDir?: Optional<string>;
|
|
27
|
+
}): Nullable<RunMetaInfo>;
|
|
28
|
+
loadRunMetaExternalTemplate(): RunMetaInfo;
|
|
29
|
+
setConfigFromRunMeta(options?: {
|
|
30
|
+
workDir?: Optional<string>;
|
|
31
|
+
silentFail?: boolean;
|
|
32
|
+
}): void;
|
|
33
|
+
}
|
|
34
|
+
declare const runInfoUtils: {
|
|
35
|
+
runMetaInfoManager: RunMetaInfoManager;
|
|
36
|
+
};
|
|
37
|
+
/** @internal */
|
|
38
|
+
export default runInfoUtils;
|
|
39
|
+
//# sourceMappingURL=RunInfoUtils.d.ts.map
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.RunMetaInfoManager = void 0;
|
|
7
|
+
const fs_1 = __importDefault(require("fs"));
|
|
8
|
+
const BrowserInfo_1 = __importDefault(require("./BrowserInfo"));
|
|
9
|
+
const Config_1 = __importDefault(require("./Config"));
|
|
10
|
+
const Constant_1 = __importDefault(require("./Constant"));
|
|
11
|
+
const FileManager_1 = __importDefault(require("./FileManager"));
|
|
12
|
+
const Utils_1 = __importDefault(require("./Utils"));
|
|
13
|
+
const InternalValueSetter_1 = require("./InternalValueSetter");
|
|
14
|
+
class RunMetaInfoManager {
|
|
15
|
+
getRunMetaFilePath(options) {
|
|
16
|
+
if ((options === null || options === void 0 ? void 0 : options.workDir) != null) {
|
|
17
|
+
return FileManager_1.default.getRunMetaFile({ workDir: options.workDir });
|
|
18
|
+
}
|
|
19
|
+
if (Config_1.default.useExternalSnapshot) {
|
|
20
|
+
return Config_1.default.externalRunMetaFile;
|
|
21
|
+
}
|
|
22
|
+
if (Config_1.default.runMetaFile != null) {
|
|
23
|
+
return Config_1.default.runMetaFile;
|
|
24
|
+
}
|
|
25
|
+
return FileManager_1.default.getRunMetaFile();
|
|
26
|
+
}
|
|
27
|
+
saveRunMetaInfo(runMetaInfo, options) {
|
|
28
|
+
var _a;
|
|
29
|
+
const runMetaFile = this.getRunMetaFilePath(options);
|
|
30
|
+
const serializable = Object.assign(Object.assign({}, runMetaInfo), { extraInfo: Object.assign(Object.assign({}, Utils_1.default.mapToObject(Config_1.default.extraRunInfoMap)), Utils_1.default.mapToObject((_a = options === null || options === void 0 ? void 0 : options.extraRunInfo) !== null && _a !== void 0 ? _a : new Map())) });
|
|
31
|
+
fs_1.default.writeFileSync(runMetaFile, JSON.stringify(serializable, null, 2), 'UTF-8');
|
|
32
|
+
return runMetaFile;
|
|
33
|
+
}
|
|
34
|
+
loadRunMetaInfoFromFile(file) {
|
|
35
|
+
const content = fs_1.default.readFileSync(file, 'UTF-8');
|
|
36
|
+
const runMetaInfo = JSON.parse(content);
|
|
37
|
+
if (runMetaInfo && runMetaInfo.extraInfo) {
|
|
38
|
+
Config_1.default.extraRunInfoMap = Utils_1.default.objectToMap(runMetaInfo.extraInfo);
|
|
39
|
+
}
|
|
40
|
+
return runMetaInfo;
|
|
41
|
+
}
|
|
42
|
+
loadRunMetaInfo(options) {
|
|
43
|
+
const file = (options === null || options === void 0 ? void 0 : options.metaFile) || this.getRunMetaFilePath(options);
|
|
44
|
+
try {
|
|
45
|
+
return this.loadRunMetaInfoFromFile(file);
|
|
46
|
+
}
|
|
47
|
+
catch (_) {
|
|
48
|
+
throw Utils_1.default.haltOrThrow('Run info missing. Please make sure `memlab run` is complete.');
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
loadRunMetaInfoSilentFail(options) {
|
|
52
|
+
const file = (options === null || options === void 0 ? void 0 : options.metaFile) || this.getRunMetaFilePath(options);
|
|
53
|
+
try {
|
|
54
|
+
return this.loadRunMetaInfoFromFile(file);
|
|
55
|
+
}
|
|
56
|
+
catch (_) {
|
|
57
|
+
return null;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
loadRunMetaExternalTemplate() {
|
|
61
|
+
const runMetaTemplateFile = FileManager_1.default.getRunMetaExternalTemplateFile();
|
|
62
|
+
return JSON.parse(fs_1.default.readFileSync(runMetaTemplateFile, 'UTF-8'));
|
|
63
|
+
}
|
|
64
|
+
setConfigFromRunMeta(options = {}) {
|
|
65
|
+
const meta = (options === null || options === void 0 ? void 0 : options.silentFail)
|
|
66
|
+
? this.loadRunMetaInfoSilentFail(options)
|
|
67
|
+
: this.loadRunMetaInfo(options);
|
|
68
|
+
if (meta == null) {
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
if ((meta === null || meta === void 0 ? void 0 : meta.app) == null || (meta === null || meta === void 0 ? void 0 : meta.interaction) == null) {
|
|
72
|
+
if (options === null || options === void 0 ? void 0 : options.silentFail) {
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
throw Utils_1.default.haltOrThrow('No app or interaction infomation');
|
|
76
|
+
}
|
|
77
|
+
Config_1.default.targetApp = meta.app;
|
|
78
|
+
Config_1.default.targetTab = meta.interaction;
|
|
79
|
+
BrowserInfo_1.default.load(meta.browserInfo);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
exports.RunMetaInfoManager = RunMetaInfoManager;
|
|
83
|
+
const runInfoUtils = { runMetaInfoManager: new RunMetaInfoManager() };
|
|
84
|
+
(0, InternalValueSetter_1.setInternalValue)(runInfoUtils, __filename, Constant_1.default.internalDir);
|
|
85
|
+
/** @internal */
|
|
86
|
+
exports.default = runInfoUtils;
|
|
@@ -0,0 +1,36 @@
|
|
|
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
|
+
* @format
|
|
8
|
+
* @oncall web_perf_infra
|
|
9
|
+
*/
|
|
10
|
+
export declare type TraceSamplerOption = {
|
|
11
|
+
maxSample?: number;
|
|
12
|
+
};
|
|
13
|
+
export default class TraceSampler {
|
|
14
|
+
private maxCount;
|
|
15
|
+
private processed;
|
|
16
|
+
private selected;
|
|
17
|
+
private population;
|
|
18
|
+
constructor(n: number, options?: TraceSamplerOption);
|
|
19
|
+
init(n: number): void;
|
|
20
|
+
private calculateSampleRatio;
|
|
21
|
+
/**
|
|
22
|
+
* The caller decide to give up sampling this time.
|
|
23
|
+
* This `giveup` and the `sample` method in aggregation should be
|
|
24
|
+
* called `this.population` times.
|
|
25
|
+
*
|
|
26
|
+
* For example, if `giveup` is called n1 times,
|
|
27
|
+
* and `sample` is called n2 times, then n1 + n2 === this.population.
|
|
28
|
+
*/
|
|
29
|
+
giveup(): void;
|
|
30
|
+
/**
|
|
31
|
+
* This sample method should be called precisely this.population times.
|
|
32
|
+
* @returns true if this sample should be taken
|
|
33
|
+
*/
|
|
34
|
+
sample(): boolean;
|
|
35
|
+
}
|
|
36
|
+
//# sourceMappingURL=TraceSampler.d.ts.map
|
|
@@ -0,0 +1,78 @@
|
|
|
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
|
+
* @format
|
|
9
|
+
* @oncall web_perf_infra
|
|
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 Config_1 = __importDefault(require("./Config"));
|
|
16
|
+
const Console_1 = __importDefault(require("./Console"));
|
|
17
|
+
const Utils_1 = __importDefault(require("./Utils"));
|
|
18
|
+
class TraceSampler {
|
|
19
|
+
constructor(n, options = {}) {
|
|
20
|
+
var _a;
|
|
21
|
+
// the max number of traces after sampling
|
|
22
|
+
this.maxCount = Config_1.default.maxSamplesForClustering;
|
|
23
|
+
this.processed = 0;
|
|
24
|
+
this.selected = 0;
|
|
25
|
+
this.population = -1;
|
|
26
|
+
this.maxCount = (_a = options.maxSample) !== null && _a !== void 0 ? _a : Config_1.default.maxSamplesForClustering;
|
|
27
|
+
this.init(n);
|
|
28
|
+
}
|
|
29
|
+
init(n) {
|
|
30
|
+
this.processed = 0;
|
|
31
|
+
this.selected = 0;
|
|
32
|
+
this.population = n;
|
|
33
|
+
this.calculateSampleRatio(n);
|
|
34
|
+
}
|
|
35
|
+
calculateSampleRatio(n) {
|
|
36
|
+
const sampleRatio = Math.min(1, this.maxCount / n);
|
|
37
|
+
if (sampleRatio < 1) {
|
|
38
|
+
Console_1.default.warning('Sampling trace due to a large number of traces:');
|
|
39
|
+
Console_1.default.lowLevel(` Number of Traces: ${n}`);
|
|
40
|
+
Console_1.default.lowLevel(` Sampling Ratio: ${Utils_1.default.getReadablePercent(sampleRatio)}`);
|
|
41
|
+
}
|
|
42
|
+
return sampleRatio;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* The caller decide to give up sampling this time.
|
|
46
|
+
* This `giveup` and the `sample` method in aggregation should be
|
|
47
|
+
* called `this.population` times.
|
|
48
|
+
*
|
|
49
|
+
* For example, if `giveup` is called n1 times,
|
|
50
|
+
* and `sample` is called n2 times, then n1 + n2 === this.population.
|
|
51
|
+
*/
|
|
52
|
+
giveup() {
|
|
53
|
+
++this.processed;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* This sample method should be called precisely this.population times.
|
|
57
|
+
* @returns true if this sample should be taken
|
|
58
|
+
*/
|
|
59
|
+
sample() {
|
|
60
|
+
if (this.processed >= this.population) {
|
|
61
|
+
throw Utils_1.default.haltOrThrow(`processing ${this.processed + 1} samples but total population is ${this.population}`);
|
|
62
|
+
}
|
|
63
|
+
// use large number to mod here to avoid too much console I/O
|
|
64
|
+
if (!Config_1.default.isContinuousTest && this.processed % 771 === 0) {
|
|
65
|
+
const percent = Utils_1.default.getReadablePercent(this.processed / this.population);
|
|
66
|
+
Console_1.default.overwrite(`progress: ${this.processed} / ${this.population} (${percent})`);
|
|
67
|
+
}
|
|
68
|
+
const dynamicRatio = (this.maxCount - this.selected) / (this.population - this.processed);
|
|
69
|
+
// increase the counter indicating how many samples has been processed
|
|
70
|
+
++this.processed;
|
|
71
|
+
if (Math.random() <= dynamicRatio) {
|
|
72
|
+
++this.selected;
|
|
73
|
+
return true;
|
|
74
|
+
}
|
|
75
|
+
return false;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
exports.default = TraceSampler;
|