@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.
Files changed (55) hide show
  1. package/dist/__tests__/parser/StringNode.test.js +12 -1
  2. package/dist/index.d.ts +2 -0
  3. package/dist/index.js +4 -1
  4. package/dist/lib/Config.d.ts +4 -0
  5. package/dist/lib/Config.js +13 -0
  6. package/dist/lib/FileManager.d.ts +6 -0
  7. package/dist/lib/FileManager.js +56 -4
  8. package/dist/lib/HeapAnalyzer.d.ts +5 -1
  9. package/dist/lib/HeapAnalyzer.js +33 -21
  10. package/dist/lib/RunInfoUtils.d.ts +39 -0
  11. package/dist/lib/RunInfoUtils.js +86 -0
  12. package/dist/lib/Types.d.ts +80 -2
  13. package/dist/lib/Utils.d.ts +30 -20
  14. package/dist/lib/Utils.js +119 -151
  15. package/dist/lib/charts/MemoryBarChart.d.ts +1 -0
  16. package/dist/lib/charts/MemoryBarChart.js +23 -2
  17. package/dist/lib/heap-data/HeapEdge.d.ts +3 -1
  18. package/dist/lib/heap-data/HeapEdge.js +13 -0
  19. package/dist/lib/heap-data/HeapLocation.d.ts +3 -1
  20. package/dist/lib/heap-data/HeapLocation.js +14 -0
  21. package/dist/lib/heap-data/HeapNode.d.ts +6 -1
  22. package/dist/lib/heap-data/HeapNode.js +46 -0
  23. package/dist/lib/heap-data/HeapSnapshot.d.ts +3 -1
  24. package/dist/lib/heap-data/HeapSnapshot.js +7 -0
  25. package/dist/lib/heap-data/HeapStringNode.d.ts +2 -1
  26. package/dist/lib/heap-data/HeapStringNode.js +5 -0
  27. package/dist/lib/trace-filters/BaseTraceFilter.rule.d.ts +29 -0
  28. package/dist/lib/trace-filters/BaseTraceFilter.rule.js +22 -0
  29. package/dist/lib/trace-filters/LeakTraceFilter.d.ts +20 -0
  30. package/dist/lib/trace-filters/LeakTraceFilter.js +37 -0
  31. package/dist/lib/trace-filters/TraceFilterRuleList.d.ts +13 -0
  32. package/dist/lib/trace-filters/TraceFilterRuleList.js +33 -0
  33. package/dist/lib/trace-filters/rules/FilterAttachedDOMToDetachedDOMTrace.rule.d.ts +15 -0
  34. package/dist/lib/trace-filters/rules/FilterAttachedDOMToDetachedDOMTrace.rule.js +55 -0
  35. package/dist/lib/trace-filters/rules/FilterDOMNodeChainTrace.rule.d.ts +15 -0
  36. package/dist/lib/trace-filters/rules/FilterDOMNodeChainTrace.rule.js +41 -0
  37. package/dist/lib/trace-filters/rules/FilterHermesTrace.rule.d.ts +15 -0
  38. package/dist/lib/trace-filters/rules/FilterHermesTrace.rule.js +29 -0
  39. package/dist/lib/trace-filters/rules/FilterInternalNodeTrace.rule.d.ts +15 -0
  40. package/dist/lib/trace-filters/rules/FilterInternalNodeTrace.rule.js +57 -0
  41. package/dist/lib/trace-filters/rules/FilterPendingActivitiesTrace.rule.d.ts +15 -0
  42. package/dist/lib/trace-filters/rules/FilterPendingActivitiesTrace.rule.js +62 -0
  43. package/dist/lib/trace-filters/rules/FilterShadowRootTrace.rule.d.ts +15 -0
  44. package/dist/lib/trace-filters/rules/FilterShadowRootTrace.rule.js +44 -0
  45. package/dist/lib/trace-filters/rules/FilterStyleEngineTrace.rule.d.ts +15 -0
  46. package/dist/lib/trace-filters/rules/FilterStyleEngineTrace.rule.js +49 -0
  47. package/dist/logger/LeakClusterLogger.js +1 -0
  48. package/dist/paths/TraceFinder.js +16 -2
  49. package/dist/trace-cluster/TraceBucket.d.ts +2 -1
  50. package/dist/trace-cluster/TraceBucket.js +22 -4
  51. package/dist/trace-cluster/TraceElement.d.ts +6 -1
  52. package/dist/trace-cluster/TraceElement.js +33 -0
  53. package/dist/trace-cluster/strategies/TraceSimilarityStrategy.js +10 -0
  54. package/package.json +1 -1
  55. 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");
@@ -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;
@@ -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;
@@ -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
- const codeDataDir = this.getCodeDataDir();
380
- config.externalRunMetaFile = path_1.default.join(codeDataDir, 'run-meta.json');
381
- config.externalSnapshotVisitOrderFile = path_1.default.join(codeDataDir, 'visit-order.json');
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?: WorkDirOptions): void;
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;
@@ -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 controlSnapshotDir = FileManager_1.default.getCurDataDir({
48
- workDir: options.controlWorkDir,
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
- let snapshotDiff = yield this.diffSnapshots({
68
- loadAllSnapshots: true,
69
- workDir: options.controlWorkDir,
70
- });
71
- const controlLeakPaths = this.filterLeakPaths(snapshotDiff.leakedHeapNodeIdSet, snapshotDiff.snapshot, { workDir: options.controlWorkDir });
72
- const controlSnapshot = snapshotDiff.snapshot;
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.controlWorkDir });
83
+ const treatmentLeakPaths = this.filterLeakPaths(snapshotDiff.leakedHeapNodeIdSet, snapshotDiff.snapshot, { workDir: options.treatmentWorkDir });
79
84
  const treatmentSnapshot = snapshotDiff.snapshot;
80
- Console_1.default.topLevel(`${controlLeakPaths.length} traces from control group`);
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(controlLeakPaths, controlSnapshot, treatmentLeakPaths, treatmentSnapshot, Utils_1.default.aggregateDominatorMetrics, {
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
- // TODO (lgong): log leak traces
90
- return [];
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 (!p || !Utils_1.default.isInterestingPath(p)) {
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;