@memlab/core 1.1.19 → 1.1.21
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/__tests__/parser/StringNode.test.js +12 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.js +4 -1
- package/dist/lib/Config.d.ts +4 -0
- package/dist/lib/Config.js +13 -0
- package/dist/lib/FileManager.d.ts +6 -0
- package/dist/lib/FileManager.js +56 -4
- package/dist/lib/HeapAnalyzer.d.ts +5 -1
- package/dist/lib/HeapAnalyzer.js +33 -21
- package/dist/lib/RunInfoUtils.d.ts +39 -0
- package/dist/lib/RunInfoUtils.js +86 -0
- package/dist/lib/Types.d.ts +80 -2
- package/dist/lib/Utils.d.ts +30 -20
- package/dist/lib/Utils.js +119 -151
- package/dist/lib/charts/MemoryBarChart.d.ts +1 -0
- package/dist/lib/charts/MemoryBarChart.js +23 -2
- package/dist/lib/heap-data/HeapEdge.d.ts +3 -1
- package/dist/lib/heap-data/HeapEdge.js +13 -0
- package/dist/lib/heap-data/HeapLocation.d.ts +3 -1
- package/dist/lib/heap-data/HeapLocation.js +14 -0
- package/dist/lib/heap-data/HeapNode.d.ts +6 -1
- package/dist/lib/heap-data/HeapNode.js +46 -0
- package/dist/lib/heap-data/HeapSnapshot.d.ts +3 -1
- package/dist/lib/heap-data/HeapSnapshot.js +7 -0
- package/dist/lib/heap-data/HeapStringNode.d.ts +2 -1
- package/dist/lib/heap-data/HeapStringNode.js +5 -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 +33 -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/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 +16 -2
- package/dist/trace-cluster/TraceBucket.d.ts +2 -1
- package/dist/trace-cluster/TraceBucket.js +22 -4
- package/dist/trace-cluster/TraceElement.d.ts +6 -1
- package/dist/trace-cluster/TraceElement.js +33 -0
- package/dist/trace-cluster/strategies/TraceSimilarityStrategy.js +10 -0
- package/package.json +1 -1
- package/static/visit-order-single-snapshot.json +19 -0
|
@@ -28,7 +28,7 @@ beforeEach(() => {
|
|
|
28
28
|
});
|
|
29
29
|
const timeout = 5 * 60 * 1000;
|
|
30
30
|
test('String heap object APIs work', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
31
|
-
var _a, _b, _c;
|
|
31
|
+
var _a, _b, _c, _d, _e, _f;
|
|
32
32
|
class TestObject {
|
|
33
33
|
constructor() {
|
|
34
34
|
this.originalString = 'test';
|
|
@@ -44,6 +44,8 @@ test('String heap object APIs work', () => __awaiter(void 0, void 0, void 0, fun
|
|
|
44
44
|
const heap = yield (0, NodeHeap_1.takeNodeMinimalHeap)();
|
|
45
45
|
const testObject = heap.getAnyObjectWithClassName('TestObject');
|
|
46
46
|
expect(testObject).not.toBe(null);
|
|
47
|
+
// test the number of referrers getter
|
|
48
|
+
expect(testObject === null || testObject === void 0 ? void 0 : testObject.numOfReferrers).toBeGreaterThan(0);
|
|
47
49
|
// testObject.originalString === 'test'
|
|
48
50
|
const originalString = testObject === null || testObject === void 0 ? void 0 : testObject.getReferenceNode('originalString');
|
|
49
51
|
expect(originalString === null || originalString === void 0 ? void 0 : originalString.isString).toBe(true);
|
|
@@ -58,4 +60,13 @@ test('String heap object APIs work', () => __awaiter(void 0, void 0, void 0, fun
|
|
|
58
60
|
expect(complexConcatString === null || complexConcatString === void 0 ? void 0 : complexConcatString.type).toBe('concatenated string');
|
|
59
61
|
expect(complexConcatString === null || complexConcatString === void 0 ? void 0 : complexConcatString.isString).toBe(true);
|
|
60
62
|
expect((_c = complexConcatString === null || complexConcatString === void 0 ? void 0 : complexConcatString.toStringNode()) === null || _c === void 0 ? void 0 : _c.stringValue).toBe('prefix_value_123_suffix');
|
|
63
|
+
// test the toJSONString API
|
|
64
|
+
let strRepresentation = (_d = concatString === null || concatString === void 0 ? void 0 : concatString.toJSONString()) !== null && _d !== void 0 ? _d : '{}';
|
|
65
|
+
let rep = JSON.parse(strRepresentation);
|
|
66
|
+
expect(rep.type).toBe('concatenated string');
|
|
67
|
+
expect(rep.stringValue).toBe(undefined);
|
|
68
|
+
strRepresentation = (_f = (_e = concatString === null || concatString === void 0 ? void 0 : concatString.toStringNode()) === null || _e === void 0 ? void 0 : _e.toJSONString()) !== null && _f !== void 0 ? _f : '{}';
|
|
69
|
+
rep = JSON.parse(strRepresentation);
|
|
70
|
+
expect(rep.type).toBe('concatenated string');
|
|
71
|
+
expect(rep.stringValue).toBe('prefix_suffix');
|
|
61
72
|
}), timeout);
|
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
|
@@ -99,6 +99,7 @@ export declare class MemLabConfig {
|
|
|
99
99
|
dataBuilderDataDir: string;
|
|
100
100
|
unclassifiedClusterDir: string;
|
|
101
101
|
externalCookiesFile: Optional<string>;
|
|
102
|
+
extraRunInfoMap: Map<string, string>;
|
|
102
103
|
heapConfig: Optional<IHeapConfig>;
|
|
103
104
|
puppeteerConfig: LaunchOptions & BrowserLaunchArgumentOptions & BrowserConnectOptions;
|
|
104
105
|
openDevtoolsConsole: boolean;
|
|
@@ -196,6 +197,8 @@ export declare class MemLabConfig {
|
|
|
196
197
|
seqClusteringIsRandomChunks: boolean;
|
|
197
198
|
instrumentJS: boolean;
|
|
198
199
|
interceptScript: boolean;
|
|
200
|
+
isAnalyzingMainThread: boolean;
|
|
201
|
+
targetWorkerTitle: Nullable<string>;
|
|
199
202
|
constructor(options?: ConfigOption);
|
|
200
203
|
private initInternalConfigs;
|
|
201
204
|
private init;
|
|
@@ -223,6 +226,7 @@ export declare class MemLabConfig {
|
|
|
223
226
|
setDevice(deviceName: string, options?: {
|
|
224
227
|
manualOverride?: boolean;
|
|
225
228
|
}): void;
|
|
229
|
+
setRunInfo(key: string, value: string): void;
|
|
226
230
|
private removeFromSet;
|
|
227
231
|
private addToSet;
|
|
228
232
|
enableXvfb(display: string): void;
|
package/dist/lib/Config.js
CHANGED
|
@@ -176,6 +176,12 @@ class MemLabConfig {
|
|
|
176
176
|
// if true, split dataset into trunks
|
|
177
177
|
// with random order for sequential clustering
|
|
178
178
|
this.seqClusteringIsRandomChunks = false;
|
|
179
|
+
// extra E2E run info (other than the fields defined in
|
|
180
|
+
// RunMetaInfo like app, interaction, browserInfo).
|
|
181
|
+
// Information saved in this map will be
|
|
182
|
+
// auto-serialized to run-meta.json when the file is saved
|
|
183
|
+
// and auto-deserialized from run-meta.json when the file is loaded
|
|
184
|
+
this.extraRunInfoMap = new Map();
|
|
179
185
|
}
|
|
180
186
|
// initialize configurable parameters
|
|
181
187
|
init(options = {}) {
|
|
@@ -211,6 +217,10 @@ class MemLabConfig {
|
|
|
211
217
|
this.isManualDebug = false;
|
|
212
218
|
// number of warmup repeat in each browser tab instance
|
|
213
219
|
this.warmupRepeat = 2;
|
|
220
|
+
// by default, analyzing heap in main thread
|
|
221
|
+
this.isAnalyzingMainThread = true;
|
|
222
|
+
// target worker's title
|
|
223
|
+
this.targetWorkerTitle = null;
|
|
214
224
|
// default waiting time when there is no page load checker callback
|
|
215
225
|
this.delayWhenNoPageLoadCheck = 2000;
|
|
216
226
|
// repeat the intermediate sequence
|
|
@@ -503,6 +513,9 @@ class MemLabConfig {
|
|
|
503
513
|
this.puppeteerConfig.defaultViewport = null;
|
|
504
514
|
this.defaultUserAgent = null;
|
|
505
515
|
}
|
|
516
|
+
setRunInfo(key, value) {
|
|
517
|
+
this.extraRunInfoMap.set(key, value);
|
|
518
|
+
}
|
|
506
519
|
removeFromSet(set, list) {
|
|
507
520
|
for (const v of list) {
|
|
508
521
|
set.delete(v);
|
|
@@ -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;
|
|
@@ -51,7 +53,9 @@ export declare class FileManager {
|
|
|
51
53
|
getPreviewReportDir(options?: FileOption): string;
|
|
52
54
|
getLeakSummaryFile(options?: FileOption): string;
|
|
53
55
|
getRunMetaFile(options?: FileOption): string;
|
|
56
|
+
getRunMetaExternalTemplateFile(): string;
|
|
54
57
|
getSnapshotSequenceMetaFile(options?: FileOption): string;
|
|
58
|
+
getSnapshotSequenceExternalTemplateFile(): string;
|
|
55
59
|
getInputDataDir(): string;
|
|
56
60
|
getAllFilesInSubDirs(dir: string): string[];
|
|
57
61
|
getTmpDir(): string;
|
|
@@ -72,6 +76,8 @@ export declare class FileManager {
|
|
|
72
76
|
iterateAllFiles(dir: string, callback: (filepath: string) => AnyValue): void;
|
|
73
77
|
rmWorkDir(options?: FileOption): void;
|
|
74
78
|
isWithinInternalDirectory(filePath: string): boolean;
|
|
79
|
+
createDefaultVisitOrderMetaFile(options?: FileOption): void;
|
|
80
|
+
createDefaultVisitOrderMetaFileWithSingleSnapshot(options: FileOption | undefined, snapshotFile: string): void;
|
|
75
81
|
initDirs(config: MemLabConfig, options?: FileOption): void;
|
|
76
82
|
}
|
|
77
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() {
|
|
@@ -179,9 +181,15 @@ class FileManager {
|
|
|
179
181
|
getRunMetaFile(options = FileManager.defaultFileOption) {
|
|
180
182
|
return path_1.default.join(this.getCurDataDir(options), 'run-meta.json');
|
|
181
183
|
}
|
|
184
|
+
getRunMetaExternalTemplateFile() {
|
|
185
|
+
return path_1.default.join(this.getCodeDataDir(), 'run-meta.json');
|
|
186
|
+
}
|
|
182
187
|
getSnapshotSequenceMetaFile(options = FileManager.defaultFileOption) {
|
|
183
188
|
return path_1.default.join(this.getCurDataDir(options), 'snap-seq.json');
|
|
184
189
|
}
|
|
190
|
+
getSnapshotSequenceExternalTemplateFile() {
|
|
191
|
+
return path_1.default.join(this.getCodeDataDir(), 'visit-order.json');
|
|
192
|
+
}
|
|
185
193
|
getInputDataDir() {
|
|
186
194
|
return path_1.default.join(this.getDefaultWorkDir(), 'input');
|
|
187
195
|
}
|
|
@@ -304,6 +312,9 @@ class FileManager {
|
|
|
304
312
|
}
|
|
305
313
|
}
|
|
306
314
|
isDirectory(file) {
|
|
315
|
+
if (!fs_extra_1.default.existsSync(file)) {
|
|
316
|
+
return false;
|
|
317
|
+
}
|
|
307
318
|
const stats = fs_extra_1.default.statSync(file);
|
|
308
319
|
return stats.isDirectory();
|
|
309
320
|
}
|
|
@@ -331,6 +342,41 @@ class FileManager {
|
|
|
331
342
|
const internalDir = Constant_1.default.internalDir;
|
|
332
343
|
return filePath.includes(`${sep}${internalDir}${sep}`);
|
|
333
344
|
}
|
|
345
|
+
createDefaultVisitOrderMetaFile(options = FileManager.defaultFileOption) {
|
|
346
|
+
// if memlab/data/cur doesn't exist, return
|
|
347
|
+
const curDataDir = this.getCurDataDir(options);
|
|
348
|
+
if (!fs_extra_1.default.existsSync(curDataDir)) {
|
|
349
|
+
return;
|
|
350
|
+
}
|
|
351
|
+
// if the snap-seq.json file exists, return
|
|
352
|
+
const snapshotSeqMetaFile = this.getSnapshotSequenceMetaFile(options);
|
|
353
|
+
if (fs_extra_1.default.existsSync(snapshotSeqMetaFile)) {
|
|
354
|
+
return;
|
|
355
|
+
}
|
|
356
|
+
// if there is no .heapsnapshot file, return
|
|
357
|
+
const files = fs_extra_1.default.readdirSync(curDataDir);
|
|
358
|
+
const snapshotFile = files.find(file => file.endsWith('.heapsnapshot'));
|
|
359
|
+
if (snapshotFile == null) {
|
|
360
|
+
return;
|
|
361
|
+
}
|
|
362
|
+
// If there is at least one snapshot, create a snap-seq.json file.
|
|
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);
|
|
368
|
+
const codeDataDir = this.getCodeDataDir();
|
|
369
|
+
const singleSnapshotMetaFile = path_1.default.join(codeDataDir, 'visit-order-single-snapshot.json');
|
|
370
|
+
const visitOrder = JSON.parse(fs_extra_1.default.readFileSync(singleSnapshotMetaFile, 'UTF-8'));
|
|
371
|
+
// fill in snapshot file name for each entry with snapshot: true
|
|
372
|
+
visitOrder.forEach(step => {
|
|
373
|
+
if (step.snapshot === true) {
|
|
374
|
+
step.snapshotFile = snapshotFile;
|
|
375
|
+
}
|
|
376
|
+
});
|
|
377
|
+
// save the snapshot meta file
|
|
378
|
+
fs_extra_1.default.writeFileSync(snapshotSeqMetaFile, JSON.stringify(visitOrder, null, 2), 'UTF-8');
|
|
379
|
+
}
|
|
334
380
|
initDirs(config, options = FileManager.defaultFileOption) {
|
|
335
381
|
// cache the last processed memlab config instance
|
|
336
382
|
// the instance should be a singleton
|
|
@@ -339,6 +385,11 @@ class FileManager {
|
|
|
339
385
|
// make sure getWorkDir is called first before
|
|
340
386
|
// any other get file or get dir calls
|
|
341
387
|
const workDir = this.getWorkDir(options);
|
|
388
|
+
// if errorWhenAbsent is set to true, make it
|
|
389
|
+
// an error when the working directory does not exist
|
|
390
|
+
if (options.errorWhenAbsent && !fs_extra_1.default.existsSync(workDir)) {
|
|
391
|
+
throw Utils_1.default.haltOrThrow(`work dir does not exist: ${workDir}`);
|
|
392
|
+
}
|
|
342
393
|
// remember the current working directory
|
|
343
394
|
// especially if this is a transcient working directory
|
|
344
395
|
config.workDir = joinAndProcessDir(options, workDir);
|
|
@@ -376,15 +427,16 @@ class FileManager {
|
|
|
376
427
|
config.heapAnalysisLogDir = joinAndProcessDir(options, this.getHeapAnalysisLogDir(options));
|
|
377
428
|
config.metricsOutDir = joinAndProcessDir(options, loggerOutDir, 'metrics');
|
|
378
429
|
config.reportScreenshotFile = path_1.default.join(outDir, 'report.png');
|
|
379
|
-
|
|
380
|
-
config.
|
|
381
|
-
|
|
430
|
+
config.externalRunMetaFile = this.getRunMetaExternalTemplateFile();
|
|
431
|
+
config.externalSnapshotVisitOrderFile =
|
|
432
|
+
this.getSnapshotSequenceExternalTemplateFile();
|
|
382
433
|
joinAndProcessDir(options, this.getUniqueTraceClusterDir(options));
|
|
383
434
|
config.newUniqueClusterDir = joinAndProcessDir(options, this.getNewUniqueTraceClusterDir(options));
|
|
384
435
|
config.staleUniqueClusterDir = joinAndProcessDir(options, this.getStaleUniqueTraceClusterDir(options));
|
|
385
436
|
config.currentUniqueClusterDir = joinAndProcessDir(options, this.getExistingUniqueTraceClusterDir(options));
|
|
386
437
|
config.unclassifiedClusterDir = joinAndProcessDir(options, this.getUnclassifiedTraceClusterDir(options));
|
|
387
438
|
config.allClusterSummaryFile = this.getAllClusterSummaryFile(options);
|
|
439
|
+
this.createDefaultVisitOrderMetaFile(options);
|
|
388
440
|
}
|
|
389
441
|
}
|
|
390
442
|
exports.FileManager = FileManager;
|
|
@@ -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,7 @@ 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");
|
|
37
38
|
class MemoryAnalyst {
|
|
38
39
|
checkLeak() {
|
|
39
40
|
return __awaiter(this, void 0, void 0, function* () {
|
|
@@ -44,14 +45,14 @@ class MemoryAnalyst {
|
|
|
44
45
|
}
|
|
45
46
|
diffLeakByWorkDir(options) {
|
|
46
47
|
return __awaiter(this, void 0, void 0, function* () {
|
|
47
|
-
const
|
|
48
|
-
workDir:
|
|
49
|
-
});
|
|
48
|
+
const controlSnapshotDirs = options.controlWorkDirs.map(controlWorkDir => FileManager_1.default.getCurDataDir({
|
|
49
|
+
workDir: controlWorkDir,
|
|
50
|
+
}));
|
|
50
51
|
const treatmentSnapshotDir = FileManager_1.default.getCurDataDir({
|
|
51
52
|
workDir: options.treatmentWorkDir,
|
|
52
53
|
});
|
|
53
54
|
// check control working dir
|
|
54
|
-
Utils_1.default.checkSnapshots({ snapshotDir: controlSnapshotDir });
|
|
55
|
+
controlSnapshotDirs.forEach(controlSnapshotDir => Utils_1.default.checkSnapshots({ snapshotDir: controlSnapshotDir }));
|
|
55
56
|
// check treatment working dir
|
|
56
57
|
Utils_1.default.checkSnapshots({ snapshotDir: treatmentSnapshotDir });
|
|
57
58
|
// display control and treatment memory
|
|
@@ -63,31 +64,37 @@ class MemoryAnalyst {
|
|
|
63
64
|
diffMemoryLeakTraces(options) {
|
|
64
65
|
return __awaiter(this, void 0, void 0, function* () {
|
|
65
66
|
Config_1.default.dumpNodeInfo = false;
|
|
66
|
-
// diff snapshots and get control raw paths
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
67
|
+
// diff snapshots from control dirs and get control raw paths array
|
|
68
|
+
const controlSnapshots = [];
|
|
69
|
+
const leakPathsFromControlRuns = [];
|
|
70
|
+
for (const controlWorkDir of options.controlWorkDirs) {
|
|
71
|
+
const snapshotDiff = yield this.diffSnapshots({
|
|
72
|
+
loadAllSnapshots: true,
|
|
73
|
+
workDir: controlWorkDir,
|
|
74
|
+
});
|
|
75
|
+
leakPathsFromControlRuns.push(this.filterLeakPaths(snapshotDiff.leakedHeapNodeIdSet, snapshotDiff.snapshot, { workDir: controlWorkDir }));
|
|
76
|
+
controlSnapshots.push(snapshotDiff.snapshot);
|
|
77
|
+
}
|
|
73
78
|
// diff snapshots and get treatment raw paths
|
|
74
|
-
snapshotDiff = yield this.diffSnapshots({
|
|
79
|
+
const snapshotDiff = yield this.diffSnapshots({
|
|
75
80
|
loadAllSnapshots: true,
|
|
76
81
|
workDir: options.treatmentWorkDir,
|
|
77
82
|
});
|
|
78
|
-
const treatmentLeakPaths = this.filterLeakPaths(snapshotDiff.leakedHeapNodeIdSet, snapshotDiff.snapshot, { workDir: options.
|
|
83
|
+
const treatmentLeakPaths = this.filterLeakPaths(snapshotDiff.leakedHeapNodeIdSet, snapshotDiff.snapshot, { workDir: options.treatmentWorkDir });
|
|
79
84
|
const treatmentSnapshot = snapshotDiff.snapshot;
|
|
80
|
-
|
|
85
|
+
const controlPathCounts = JSON.stringify(leakPathsFromControlRuns.map(leakPaths => leakPaths.length));
|
|
86
|
+
Console_1.default.topLevel(`${controlPathCounts} traces from control group`);
|
|
81
87
|
Console_1.default.topLevel(`${treatmentLeakPaths.length} traces from treatment group`);
|
|
82
|
-
const result = TraceBucket_1.default.clusterControlTreatmentPaths(
|
|
88
|
+
const result = TraceBucket_1.default.clusterControlTreatmentPaths(leakPathsFromControlRuns, controlSnapshots, treatmentLeakPaths, treatmentSnapshot, Utils_1.default.aggregateDominatorMetrics, {
|
|
83
89
|
strategy: Config_1.default.isMLClustering
|
|
84
90
|
? new MLTraceSimilarityStrategy_1.default()
|
|
85
91
|
: undefined,
|
|
86
92
|
});
|
|
87
93
|
Console_1.default.midLevel(`MemLab found ${result.treatmentOnlyClusters.length} new leak(s) in the treatment group`);
|
|
88
94
|
yield this.serializeClusterUpdate(result.treatmentOnlyClusters);
|
|
89
|
-
//
|
|
90
|
-
|
|
95
|
+
// serialize JSON file with detailed leak trace information
|
|
96
|
+
const treatmentOnlyPaths = result.treatmentOnlyClusters.map(c => c.path);
|
|
97
|
+
return LeakTraceDetailsLogger_1.default.logTraces(snapshotDiff.leakedHeapNodeIdSet, snapshotDiff.snapshot, snapshotDiff.listOfLeakedHeapNodeIdSet, treatmentOnlyPaths, Config_1.default.traceJsonOutDir);
|
|
91
98
|
});
|
|
92
99
|
}
|
|
93
100
|
// find all unique pattern of leaks
|
|
@@ -124,7 +131,7 @@ class MemoryAnalyst {
|
|
|
124
131
|
snapshotLeakedHeapNodeIdSet = snapshotDiff.leakedHeapNodeIdSet;
|
|
125
132
|
snapshot = snapshotDiff.snapshot;
|
|
126
133
|
}
|
|
127
|
-
this.dumpPathByNodeId(snapshotLeakedHeapNodeIdSet, snapshot, nodeIdsInSnapshots, Config_1.default.focusFiberNodeId, Config_1.default.viewJsonFile, Config_1.default.singleReportSummary);
|
|
134
|
+
this.dumpPathByNodeId(snapshotLeakedHeapNodeIdSet, snapshot, nodeIdsInSnapshots, Config_1.default.focusFiberNodeId, Config_1.default.viewJsonFile, Config_1.default.singleReportSummary, { printConsoleOnly: true });
|
|
128
135
|
});
|
|
129
136
|
}
|
|
130
137
|
shouldLoadCompleteSnapshot(tabsOrder, tab) {
|
|
@@ -348,6 +355,7 @@ class MemoryAnalyst {
|
|
|
348
355
|
this.printHeapAndLeakInfo(leakedNodeIds, snapshot, options);
|
|
349
356
|
// get all leaked objects
|
|
350
357
|
this.filterLeakedObjects(leakedNodeIds, snapshot);
|
|
358
|
+
const leakTraceFilter = new LeakTraceFilter_1.LeakTraceFilter();
|
|
351
359
|
const nodeIdInPaths = new Set();
|
|
352
360
|
const paths = [];
|
|
353
361
|
let numOfLeakedObjects = 0;
|
|
@@ -359,7 +367,8 @@ class MemoryAnalyst {
|
|
|
359
367
|
}
|
|
360
368
|
// BFS search for path from the leaked node to GC roots
|
|
361
369
|
const p = finder.getPathToGCRoots(snapshot, node);
|
|
362
|
-
if (
|
|
370
|
+
if (p == null ||
|
|
371
|
+
!leakTraceFilter.filter(p, { config: Config_1.default, leakedNodeIds, snapshot })) {
|
|
363
372
|
return;
|
|
364
373
|
}
|
|
365
374
|
++numOfLeakedObjects;
|
|
@@ -451,10 +460,13 @@ class MemoryAnalyst {
|
|
|
451
460
|
return;
|
|
452
461
|
}
|
|
453
462
|
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
463
|
let pathSummary = Serializer_1.default.summarizePath(path, nodeIdInPaths, snapshot, { color: true });
|
|
457
464
|
Console_1.default.topLevel(pathSummary);
|
|
465
|
+
if (options.printConsoleOnly) {
|
|
466
|
+
return;
|
|
467
|
+
}
|
|
468
|
+
const tabsOrder = Utils_1.default.loadTabsOrder(FileManager_1.default.getSnapshotSequenceMetaFile(options));
|
|
469
|
+
const interactionSummary = Serializer_1.default.summarizeTabsOrder(tabsOrder);
|
|
458
470
|
pathSummary = Serializer_1.default.summarizePath(path, nodeIdInPaths, snapshot);
|
|
459
471
|
const summary = `Page Interaction: \n${interactionSummary}\n\n` +
|
|
460
472
|
`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;
|