@memlab/core 1.1.18 → 1.1.20

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.
@@ -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
@@ -7,11 +7,11 @@
7
7
  * @format
8
8
  * @oncall web_perf_infra
9
9
  */
10
- /** @internal */
11
- export declare function registerPackage(): Promise<void>;
12
10
  export * from './lib/Types';
13
11
  export * from './lib/NodeHeap';
14
12
  /** @internal */
13
+ export declare function registerPackage(): Promise<void>;
14
+ /** @internal */
15
15
  export { default as config } from './lib/Config';
16
16
  /** @internal */
17
17
  export * from './lib/InternalValueSetter';
@@ -36,6 +36,8 @@ export { default as analysis } from './lib/HeapAnalyzer';
36
36
  /** @internal */
37
37
  export { default as constant } from './lib/Constant';
38
38
  /** @internal */
39
+ export { default as memoryBarChart } from './lib/charts/MemoryBarChart';
40
+ /** @internal */
39
41
  export { default as modes } from './modes/RunningModes';
40
42
  /** @internal */
41
43
  export { default as ProcessManager } from './lib/ProcessManager';
package/dist/index.js CHANGED
@@ -35,9 +35,11 @@ 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.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.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
+ __exportStar(require("./lib/Types"), exports);
42
+ __exportStar(require("./lib/NodeHeap"), exports);
41
43
  /** @internal */
42
44
  function registerPackage() {
43
45
  return __awaiter(this, void 0, void 0, function* () {
@@ -45,8 +47,6 @@ function registerPackage() {
45
47
  });
46
48
  }
47
49
  exports.registerPackage = registerPackage;
48
- __exportStar(require("./lib/Types"), exports);
49
- __exportStar(require("./lib/NodeHeap"), exports);
50
50
  /** @internal */
51
51
  var Config_1 = require("./lib/Config");
52
52
  Object.defineProperty(exports, "config", { enumerable: true, get: function () { return __importDefault(Config_1).default; } });
@@ -81,6 +81,9 @@ Object.defineProperty(exports, "analysis", { enumerable: true, get: function ()
81
81
  var Constant_1 = require("./lib/Constant");
82
82
  Object.defineProperty(exports, "constant", { enumerable: true, get: function () { return __importDefault(Constant_1).default; } });
83
83
  /** @internal */
84
+ var MemoryBarChart_1 = require("./lib/charts/MemoryBarChart");
85
+ Object.defineProperty(exports, "memoryBarChart", { enumerable: true, get: function () { return __importDefault(MemoryBarChart_1).default; } });
86
+ /** @internal */
84
87
  var RunningModes_1 = require("./modes/RunningModes");
85
88
  Object.defineProperty(exports, "modes", { enumerable: true, get: function () { return __importDefault(RunningModes_1).default; } });
86
89
  /** @internal */
@@ -196,6 +196,8 @@ export declare class MemLabConfig {
196
196
  seqClusteringIsRandomChunks: boolean;
197
197
  instrumentJS: boolean;
198
198
  interceptScript: boolean;
199
+ isAnalyzingMainThread: boolean;
200
+ targetWorkerTitle: Nullable<string>;
199
201
  constructor(options?: ConfigOption);
200
202
  private initInternalConfigs;
201
203
  private init;
@@ -211,6 +211,10 @@ class MemLabConfig {
211
211
  this.isManualDebug = false;
212
212
  // number of warmup repeat in each browser tab instance
213
213
  this.warmupRepeat = 2;
214
+ // by default, analyzing heap in main thread
215
+ this.isAnalyzingMainThread = true;
216
+ // target worker's title
217
+ this.targetWorkerTitle = null;
214
218
  // default waiting time when there is no page load checker callback
215
219
  this.delayWhenNoPageLoadCheck = 2000;
216
220
  // repeat the intermediate sequence
@@ -11,6 +11,7 @@ import type { MemLabConfig } from './Config';
11
11
  import type { AnyValue, FileOption } from './Types';
12
12
  /** @internal */
13
13
  export declare class FileManager {
14
+ private memlabConfigCache;
14
15
  getDefaultWorkDir(): string;
15
16
  generateTmpHeapDir(): string;
16
17
  private static transientInstanceIdx;
@@ -50,7 +51,9 @@ export declare class FileManager {
50
51
  getPreviewReportDir(options?: FileOption): string;
51
52
  getLeakSummaryFile(options?: FileOption): string;
52
53
  getRunMetaFile(options?: FileOption): string;
54
+ getRunMetaExternalTemplateFile(): string;
53
55
  getSnapshotSequenceMetaFile(options?: FileOption): string;
56
+ getSnapshotSequenceExternalTemplateFile(): string;
54
57
  getInputDataDir(): string;
55
58
  getAllFilesInSubDirs(dir: string): string[];
56
59
  getTmpDir(): string;
@@ -63,6 +66,7 @@ export declare class FileManager {
63
66
  initNewHeapAnalysisLogFile(options?: FileOption): string;
64
67
  getAndInitTSCompileIntermediateDir(): string;
65
68
  clearDataDirs(options?: FileOption): void;
69
+ removeSnapshotFiles(options?: FileOption): void;
66
70
  emptyDirIfExists(dir: string): void;
67
71
  emptyTraceLogDataDir(options?: FileOption): void;
68
72
  resetBrowserDir(): void;
@@ -70,6 +74,7 @@ export declare class FileManager {
70
74
  iterateAllFiles(dir: string, callback: (filepath: string) => AnyValue): void;
71
75
  rmWorkDir(options?: FileOption): void;
72
76
  isWithinInternalDirectory(filePath: string): boolean;
77
+ createDefaultVisitOrderMetaFile(options?: FileOption): void;
73
78
  initDirs(config: MemLabConfig, options?: FileOption): void;
74
79
  }
75
80
  declare const _default: FileManager;
@@ -13,7 +13,6 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
13
13
  };
14
14
  Object.defineProperty(exports, "__esModule", { value: true });
15
15
  exports.FileManager = void 0;
16
- const minimist_1 = __importDefault(require("minimist"));
17
16
  const fs_extra_1 = __importDefault(require("fs-extra"));
18
17
  const os_1 = __importDefault(require("os"));
19
18
  const path_1 = __importDefault(require("path"));
@@ -37,6 +36,9 @@ function joinAndProcessDir(options, ...args) {
37
36
  }
38
37
  /** @internal */
39
38
  class FileManager {
39
+ constructor() {
40
+ this.memlabConfigCache = null;
41
+ }
40
42
  getDefaultWorkDir() {
41
43
  return path_1.default.join(this.getTmpDir(), 'memlab');
42
44
  }
@@ -48,11 +50,12 @@ class FileManager {
48
50
  return dirPath;
49
51
  }
50
52
  getWorkDir(options = FileManager.defaultFileOption) {
53
+ var _a;
51
54
  // workDir options supercedes all the other options
52
55
  if (options.workDir) {
53
56
  return path_1.default.resolve(options.workDir);
54
57
  }
55
- // transient options supercedes other the CLI options
58
+ // transient options supercedes the other CLI options
56
59
  if (options.transient) {
57
60
  const idx = ++FileManager.transientInstanceIdx;
58
61
  const instanceId = `${process.pid}-${Date.now()}-${idx}`;
@@ -60,8 +63,11 @@ class FileManager {
60
63
  return path_1.default.resolve(workDir);
61
64
  }
62
65
  // workDir from the CLI options
63
- const argv = (0, minimist_1.default)(process.argv.slice(2));
64
- const workDir = argv['work-dir'] || this.getDefaultWorkDir();
66
+ const workDir = FileManager.defaultFileOption.workDir ||
67
+ (
68
+ // in case there is a transcient working directory generated
69
+ (_a = this.memlabConfigCache) === null || _a === void 0 ? void 0 : _a.workDir) ||
70
+ this.getDefaultWorkDir();
65
71
  return path_1.default.resolve(workDir);
66
72
  }
67
73
  getChromeBinaryZipFile() {
@@ -173,9 +179,15 @@ class FileManager {
173
179
  getRunMetaFile(options = FileManager.defaultFileOption) {
174
180
  return path_1.default.join(this.getCurDataDir(options), 'run-meta.json');
175
181
  }
182
+ getRunMetaExternalTemplateFile() {
183
+ return path_1.default.join(this.getCodeDataDir(), 'run-meta.json');
184
+ }
176
185
  getSnapshotSequenceMetaFile(options = FileManager.defaultFileOption) {
177
186
  return path_1.default.join(this.getCurDataDir(options), 'snap-seq.json');
178
187
  }
188
+ getSnapshotSequenceExternalTemplateFile() {
189
+ return path_1.default.join(this.getCodeDataDir(), 'visit-order.json');
190
+ }
179
191
  getInputDataDir() {
180
192
  return path_1.default.join(this.getDefaultWorkDir(), 'input');
181
193
  }
@@ -249,6 +261,23 @@ class FileManager {
249
261
  }
250
262
  }
251
263
  }
264
+ removeSnapshotFiles(options = FileManager.defaultFileOption) {
265
+ const curDataDir = this.getCurDataDir(options);
266
+ if (!fs_extra_1.default.existsSync(curDataDir)) {
267
+ return;
268
+ }
269
+ const dataSuffix = ['.heapsnapshot'];
270
+ const files = fs_extra_1.default.readdirSync(curDataDir);
271
+ for (const file of files) {
272
+ inner: for (const suffix of dataSuffix) {
273
+ if (file.endsWith(suffix)) {
274
+ const filepath = path_1.default.join(curDataDir, file);
275
+ fs_extra_1.default.unlinkSync(filepath);
276
+ break inner;
277
+ }
278
+ }
279
+ }
280
+ }
252
281
  emptyDirIfExists(dir) {
253
282
  if (this.isDirectory(dir)) {
254
283
  fs_extra_1.default.emptyDirSync(dir);
@@ -308,11 +337,52 @@ class FileManager {
308
337
  const internalDir = Constant_1.default.internalDir;
309
338
  return filePath.includes(`${sep}${internalDir}${sep}`);
310
339
  }
340
+ createDefaultVisitOrderMetaFile(options = FileManager.defaultFileOption) {
341
+ // if memlab/data/cur doesn't exist, return
342
+ const curDataDir = this.getCurDataDir(options);
343
+ if (!fs_extra_1.default.existsSync(curDataDir)) {
344
+ return;
345
+ }
346
+ // if the snap-seq.json file exists, return
347
+ const snapshotSeqMetaFile = this.getSnapshotSequenceMetaFile(options);
348
+ if (fs_extra_1.default.existsSync(snapshotSeqMetaFile)) {
349
+ return;
350
+ }
351
+ // if there is no .heapsnapshot file, return
352
+ const files = fs_extra_1.default.readdirSync(curDataDir);
353
+ const snapshotFile = files.find(file => file.endsWith('.heapsnapshot'));
354
+ if (snapshotFile == null) {
355
+ return;
356
+ }
357
+ // If there is at least one snapshot, create a snap-seq.json file.
358
+ // First, get the meta file for leak detection in a single heap snapshot
359
+ const codeDataDir = this.getCodeDataDir();
360
+ const singleSnapshotMetaFile = path_1.default.join(codeDataDir, 'visit-order-single-snapshot.json');
361
+ const visitOrder = JSON.parse(fs_extra_1.default.readFileSync(singleSnapshotMetaFile, 'UTF-8'));
362
+ // fill in snapshot file name for each entry with snapshot: true
363
+ visitOrder.forEach(step => {
364
+ if (step.snapshot === true) {
365
+ step.snapshotFile = snapshotFile;
366
+ }
367
+ });
368
+ // save the snapshot meta file
369
+ fs_extra_1.default.writeFileSync(snapshotSeqMetaFile, JSON.stringify(visitOrder, null, 2), 'UTF-8');
370
+ }
311
371
  initDirs(config, options = FileManager.defaultFileOption) {
372
+ // cache the last processed memlab config instance
373
+ // the instance should be a singleton
374
+ this.memlabConfigCache = config;
312
375
  config.monoRepoDir = Constant_1.default.monoRepoDir;
313
376
  // make sure getWorkDir is called first before
314
377
  // any other get file or get dir calls
315
378
  const workDir = this.getWorkDir(options);
379
+ // if errorWhenAbsent is set to true, make it
380
+ // an error when the working directory does not exist
381
+ if (options.errorWhenAbsent && !fs_extra_1.default.existsSync(workDir)) {
382
+ throw Utils_1.default.haltOrThrow(`work dir does not exist: ${workDir}`);
383
+ }
384
+ // remember the current working directory
385
+ // especially if this is a transcient working directory
316
386
  config.workDir = joinAndProcessDir(options, workDir);
317
387
  options = Object.assign(Object.assign({}, options), { workDir });
318
388
  config.dataBaseDir = joinAndProcessDir(options, this.getDataBaseDir(options));
@@ -348,15 +418,16 @@ class FileManager {
348
418
  config.heapAnalysisLogDir = joinAndProcessDir(options, this.getHeapAnalysisLogDir(options));
349
419
  config.metricsOutDir = joinAndProcessDir(options, loggerOutDir, 'metrics');
350
420
  config.reportScreenshotFile = path_1.default.join(outDir, 'report.png');
351
- const codeDataDir = this.getCodeDataDir();
352
- config.externalRunMetaFile = path_1.default.join(codeDataDir, 'run-meta.json');
353
- config.externalSnapshotVisitOrderFile = path_1.default.join(codeDataDir, 'visit-order.json');
421
+ config.externalRunMetaFile = this.getRunMetaExternalTemplateFile();
422
+ config.externalSnapshotVisitOrderFile =
423
+ this.getSnapshotSequenceExternalTemplateFile();
354
424
  joinAndProcessDir(options, this.getUniqueTraceClusterDir(options));
355
425
  config.newUniqueClusterDir = joinAndProcessDir(options, this.getNewUniqueTraceClusterDir(options));
356
426
  config.staleUniqueClusterDir = joinAndProcessDir(options, this.getStaleUniqueTraceClusterDir(options));
357
427
  config.currentUniqueClusterDir = joinAndProcessDir(options, this.getExistingUniqueTraceClusterDir(options));
358
428
  config.unclassifiedClusterDir = joinAndProcessDir(options, this.getUnclassifiedTraceClusterDir(options));
359
429
  config.allClusterSummaryFile = this.getAllClusterSummaryFile(options);
430
+ this.createDefaultVisitOrderMetaFile(options);
360
431
  }
361
432
  }
362
433
  exports.FileManager = FileManager;
@@ -7,17 +7,25 @@
7
7
  * @format
8
8
  * @oncall web_perf_infra
9
9
  */
10
- import type { E2EStepInfo, HeapNodeIdSet, IHeapSnapshot, IMemoryAnalystOptions, IMemoryAnalystSnapshotDiff, IOveralHeapInfo, LeakTracePathItem, Optional, IOveralLeakInfo, TraceCluster, ISerializedInfo } from './Types';
10
+ import type { E2EStepInfo, HeapNodeIdSet, IHeapSnapshot, IMemoryAnalystSnapshotDiff, IOveralHeapInfo, LeakTracePathItem, Optional, IOveralLeakInfo, TraceCluster, ISerializedInfo, DiffLeakOptions } from './Types';
11
11
  import TraceFinder from '../paths/TraceFinder';
12
+ declare type DiffSnapshotsOptions = {
13
+ loadAllSnapshots?: boolean;
14
+ workDir?: string;
15
+ };
16
+ declare type WorkDirOptions = {
17
+ workDir?: string;
18
+ };
12
19
  declare class MemoryAnalyst {
13
20
  checkLeak(): Promise<ISerializedInfo[]>;
21
+ diffLeakByWorkDir(options: DiffLeakOptions): Promise<ISerializedInfo[]>;
22
+ diffMemoryLeakTraces(options: DiffLeakOptions): Promise<ISerializedInfo[]>;
14
23
  detectMemoryLeaks(): Promise<ISerializedInfo[]>;
15
- visualizeMemoryUsage(options?: IMemoryAnalystOptions): void;
16
24
  focus(options?: {
17
25
  file?: string;
18
26
  }): Promise<void>;
19
27
  shouldLoadCompleteSnapshot(tabsOrder: E2EStepInfo[], tab: E2EStepInfo): boolean;
20
- diffSnapshots(loadAll?: boolean): Promise<IMemoryAnalystSnapshotDiff>;
28
+ diffSnapshots(options?: DiffSnapshotsOptions): Promise<IMemoryAnalystSnapshotDiff>;
21
29
  preparePathFinder(snapshot: IHeapSnapshot): TraceFinder;
22
30
  private dumpPageInteractionSummary;
23
31
  private dumpLeakSummaryToConsole;
@@ -29,12 +37,11 @@ declare class MemoryAnalyst {
29
37
  printHeapInfo(leakInfo: IOveralHeapInfo): void;
30
38
  private printHeapAndLeakInfo;
31
39
  private logLeakTraceSummary;
32
- searchLeakedTraces(leakedNodeIds: HeapNodeIdSet, snapshot: IHeapSnapshot): Promise<{
33
- paths: LeakTracePathItem[];
34
- }>;
40
+ filterLeakPaths(leakedNodeIds: HeapNodeIdSet, snapshot: IHeapSnapshot, options?: WorkDirOptions): LeakTracePathItem[];
41
+ findLeakTraces(leakedNodeIds: HeapNodeIdSet, snapshot: IHeapSnapshot, options?: WorkDirOptions): Promise<LeakTracePathItem[]>;
35
42
  /**
36
43
  * Given a set of heap object ids, cluster them based on the similarity
37
- * of their retainer traces and return a
44
+ * of their retainer traces
38
45
  * @param leakedNodeIds
39
46
  * @param snapshot
40
47
  * @returns
@@ -43,7 +50,7 @@ declare class MemoryAnalyst {
43
50
  serializeClusterUpdate(clusters: TraceCluster[], options?: {
44
51
  reclusterOnly?: boolean;
45
52
  }): Promise<void>;
46
- dumpPathByNodeId(leakedIdSet: HeapNodeIdSet, snapshot: IHeapSnapshot, nodeIdsInSnapshots: Array<HeapNodeIdSet>, id: number, pathLoaderFile: string, summaryFile: string): void;
53
+ dumpPathByNodeId(leakedIdSet: HeapNodeIdSet, snapshot: IHeapSnapshot, nodeIdsInSnapshots: Array<HeapNodeIdSet>, id: number, pathLoaderFile: string, summaryFile: string, options?: WorkDirOptions): void;
47
54
  }
48
55
  declare const _default: MemoryAnalyst;
49
56
  export default _default;