@memlab/core 1.1.14 → 1.1.17

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 CHANGED
@@ -50,5 +50,9 @@ export * from './lib/PackageInfoLoader';
50
50
  /** @internal */
51
51
  export { default as SequentialClustering } from './trace-cluster/SequentialClustering';
52
52
  /** @internal */
53
+ export { default as MultiIterationSeqClustering } from './trace-cluster/MultiIterationSeqClustering';
54
+ /** @internal */
53
55
  export { default as TraceFinder } from './paths/TraceFinder';
56
+ /** @internal */
57
+ export * from './trace-cluster/ClusterUtils';
54
58
  //# sourceMappingURL=index.d.ts.map
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.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.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
41
  /** @internal */
@@ -101,5 +101,10 @@ __exportStar(require("./lib/PackageInfoLoader"), exports);
101
101
  var SequentialClustering_1 = require("./trace-cluster/SequentialClustering");
102
102
  Object.defineProperty(exports, "SequentialClustering", { enumerable: true, get: function () { return __importDefault(SequentialClustering_1).default; } });
103
103
  /** @internal */
104
+ var MultiIterationSeqClustering_1 = require("./trace-cluster/MultiIterationSeqClustering");
105
+ Object.defineProperty(exports, "MultiIterationSeqClustering", { enumerable: true, get: function () { return __importDefault(MultiIterationSeqClustering_1).default; } });
106
+ /** @internal */
104
107
  var TraceFinder_1 = require("./paths/TraceFinder");
105
108
  Object.defineProperty(exports, "TraceFinder", { enumerable: true, get: function () { return __importDefault(TraceFinder_1).default; } });
109
+ /** @internal */
110
+ __exportStar(require("./trace-cluster/ClusterUtils"), exports);
@@ -71,6 +71,7 @@ export declare class MemLabConfig {
71
71
  userDataDir: string;
72
72
  curDataDir: string;
73
73
  webSourceDir: string;
74
+ debugDataDir: string;
74
75
  runMetaFile: string;
75
76
  snapshotSequenceFile: string;
76
77
  exploreResultFile: string;
@@ -90,6 +91,7 @@ export declare class MemLabConfig {
90
91
  traceClusterOutDir: string;
91
92
  traceJsonOutDir: string;
92
93
  metricsOutDir: string;
94
+ heapAnalysisLogDir: string;
93
95
  reportScreenshotFile: string;
94
96
  newUniqueClusterDir: string;
95
97
  staleUniqueClusterDir: string;
@@ -188,9 +190,13 @@ export declare class MemLabConfig {
188
190
  mlClusteringLinkageMaxDistance: number;
189
191
  mlMaxDF: number;
190
192
  isSequentialClustering: boolean;
193
+ isMultiIterationSeqClustering: boolean;
191
194
  seqClusteringSplitCount: number;
195
+ multiIterSeqClusteringIteration: number;
196
+ multiIterSeqClusteringSampleSize: number;
192
197
  seqClusteringIsRandomChunks: boolean;
193
198
  instrumentJS: boolean;
199
+ interceptScript: boolean;
194
200
  constructor(options?: ConfigOption);
195
201
  private initInternalConfigs;
196
202
  private init;
@@ -116,7 +116,9 @@ class MemLabConfig {
116
116
  this.snapshotHasDetachedness = false;
117
117
  // by default running in regular mode
118
118
  this.runningMode = RunningModes_1.default.get('regular', this);
119
- // intercept and rewrite JavaScript Code in browser
119
+ // intercept and log JavaScript Code in browser
120
+ this.interceptScript = false;
121
+ // rewrite JavaScript Code in browser
120
122
  this.instrumentJS = false;
121
123
  // external heap snapshot paths, if enabled
122
124
  this.externalSnapshotFilePaths = [];
@@ -139,8 +141,15 @@ class MemLabConfig {
139
141
  this.mlMaxDF = 1;
140
142
  // if true, evaluating results with sequential clustering
141
143
  this.isSequentialClustering = false;
144
+ // if true, evaluating results with sequential
145
+ // clustering with multiple iterations
146
+ this.isMultiIterationSeqClustering = false;
142
147
  // split the sample leak traces into 4 smaller ones by default.
143
148
  this.seqClusteringSplitCount = 4;
149
+ // the number of iterations for multi-iteration sequential clustering
150
+ this.multiIterSeqClusteringIteration = 1;
151
+ // the number of trace samples to retain from each cluster
152
+ this.multiIterSeqClusteringSampleSize = Infinity;
144
153
  // if true, split dataset into trunks
145
154
  // with random order for sequential clustering
146
155
  this.seqClusteringIsRandomChunks = false;
@@ -23,6 +23,7 @@ declare class MemLabConsole {
23
23
  private config;
24
24
  private sections;
25
25
  private log;
26
+ private logFileSet;
26
27
  private styles;
27
28
  private static singleton;
28
29
  protected constructor();
@@ -40,6 +41,8 @@ declare class MemLabConsole {
40
41
  private shouldBeConcise;
41
42
  private clearPrevOverwriteMsg;
42
43
  private printStr;
44
+ registerLogFile(logFile: string): void;
45
+ unregisterLogFile(logFile: string): void;
43
46
  beginSection(name: string): void;
44
47
  endSection(name: string): void;
45
48
  setConfig(config: MemLabConfig): void;
@@ -14,10 +14,12 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
14
14
  Object.defineProperty(exports, "__esModule", { value: true });
15
15
  const chalk_1 = __importDefault(require("chalk"));
16
16
  const fs_1 = __importDefault(require("fs"));
17
+ const path_1 = __importDefault(require("path"));
17
18
  const readline_1 = __importDefault(require("readline"));
18
19
  const string_width_1 = __importDefault(require("string-width"));
19
20
  const stdout = process.stdout;
20
21
  const TABLE_MAX_WIDTH = 50;
22
+ const LOG_BUFFER_LENGTH = 100;
21
23
  const prevLine = '\x1b[F';
22
24
  const eraseLine = '\x1b[K';
23
25
  const barComplete = chalk_1.default.green('\u2588');
@@ -42,10 +44,21 @@ function formatTableArg(arg) {
42
44
  }
43
45
  });
44
46
  }
47
+ function registerExitCleanup(inst, exitHandler) {
48
+ const p = process;
49
+ // normal exit
50
+ p.on('exit', exitHandler.bind(null, { cleanup: true }));
51
+ // ctrl + c event
52
+ p.on('SIGINT', exitHandler.bind(null, { exit: true }));
53
+ // kill pid
54
+ p.on('SIGUSR1', exitHandler.bind(null, { exit: true }));
55
+ p.on('SIGUSR2', exitHandler.bind(null, { exit: true }));
56
+ }
45
57
  class MemLabConsole {
46
58
  constructor() {
47
59
  this.config = {};
48
60
  this.log = [];
61
+ this.logFileSet = new Set();
49
62
  this.styles = {
50
63
  top: (msg) => msg,
51
64
  high: chalk_1.default.dim.bind(chalk_1.default),
@@ -67,12 +80,15 @@ class MemLabConsole {
67
80
  }
68
81
  const inst = new MemLabConsole();
69
82
  MemLabConsole.singleton = inst;
70
- // clean up output
83
+ const exitHandler = (
84
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
85
+ _options,
71
86
  // eslint-disable-next-line @typescript-eslint/no-unused-vars
72
- process.on('exit', (_code) => {
73
- inst.flushLog();
87
+ _exitCode) => {
88
+ inst.flushLog({ sync: true });
74
89
  inst.clearPrevOverwriteMsg();
75
- });
90
+ };
91
+ registerExitCleanup(inst, exitHandler);
76
92
  return inst;
77
93
  }
78
94
  style(msg, name) {
@@ -98,28 +114,49 @@ class MemLabConsole {
98
114
  }
99
115
  logMsg(msg) {
100
116
  // remove control characters
101
- const rawMsg = msg
117
+ const lines = msg.split('\n').map(line => line
102
118
  // eslint-disable-next-line no-control-regex
103
119
  .replace(/[\u0000-\u001F\u007F-\u009F]/g, '')
104
- .replace(/\[\d{1,3}m/g, '');
105
- this.log.push(rawMsg);
106
- if (this.log.length > 20) {
107
- this.flushLog();
120
+ .replace(/\[\d{1,3}m/g, ''));
121
+ this.log.push(...lines);
122
+ if (this.log.length > LOG_BUFFER_LENGTH) {
123
+ this.flushLog({ sync: true });
108
124
  }
109
125
  }
110
- flushLog() {
126
+ flushLog(options = {}) {
111
127
  const str = this.log.join('\n');
112
- if (str.length > 0) {
113
- const file = this.config.consoleLogFile;
114
- fs_1.default.appendFile(file, str + '\n', 'UTF-8', () => {
115
- // NOOP
116
- });
117
- }
118
128
  this.log = [];
129
+ if (str.length === 0) {
130
+ return;
131
+ }
132
+ // synchronous logging
133
+ if (options.sync) {
134
+ for (const logFile of this.logFileSet) {
135
+ try {
136
+ fs_1.default.appendFileSync(logFile, str + '\n', 'UTF-8');
137
+ }
138
+ catch (_a) {
139
+ // fail silently
140
+ }
141
+ }
142
+ }
143
+ else {
144
+ // async logging
145
+ const emptyCallback = () => {
146
+ // no op
147
+ };
148
+ for (const logFile of this.logFileSet) {
149
+ try {
150
+ fs_1.default.appendFile(logFile, str + '\n', 'UTF-8', emptyCallback);
151
+ }
152
+ catch (_b) {
153
+ // fail silently
154
+ }
155
+ }
156
+ }
119
157
  }
120
158
  pushMsg(msg, options = {}) {
121
- const len = this.sections.arr.length;
122
- if (this.config.isContinuousTest || len === 0) {
159
+ if (this.sections.arr.length === 0) {
123
160
  return;
124
161
  }
125
162
  // calculate each line's visible width
@@ -140,7 +177,9 @@ class MemLabConsole {
140
177
  if (!section || section.msgs.length === 0) {
141
178
  return;
142
179
  }
143
- stdout.write(eraseLine);
180
+ if (!this.config.muteConsole) {
181
+ stdout.write(eraseLine);
182
+ }
144
183
  const msg = section.msgs.pop();
145
184
  if (!msg) {
146
185
  return;
@@ -150,8 +189,10 @@ class MemLabConsole {
150
189
  const line = (_a = lines.pop()) !== null && _a !== void 0 ? _a : 0;
151
190
  const width = stdout.columns;
152
191
  let n = line === 0 ? 1 : Math.ceil(line / width);
153
- while (n-- > 0) {
154
- stdout.write(prevLine + eraseLine);
192
+ if (!this.config.muteConsole && !this.config.isTest) {
193
+ while (n-- > 0) {
194
+ stdout.write(prevLine + eraseLine);
195
+ }
155
196
  }
156
197
  }
157
198
  }
@@ -189,11 +230,21 @@ class MemLabConsole {
189
230
  this.clearPrevMsgInLastSection();
190
231
  }
191
232
  printStr(msg, options = {}) {
192
- if (this.config.isTest || this.config.muteConsole) {
233
+ this.pushMsg(msg, options);
234
+ if (this.config.isTest) {
193
235
  return;
194
236
  }
195
- console.log(msg);
196
- this.pushMsg(msg, options);
237
+ if (this.config.isContinuousTest || !this.config.muteConsole) {
238
+ console.log(msg);
239
+ }
240
+ }
241
+ registerLogFile(logFile) {
242
+ this.flushLog({ sync: true });
243
+ this.logFileSet.add(path_1.default.resolve(logFile));
244
+ }
245
+ unregisterLogFile(logFile) {
246
+ this.flushLog({ sync: true });
247
+ this.logFileSet.delete(path_1.default.resolve(logFile));
197
248
  }
198
249
  beginSection(name) {
199
250
  if (this.config.isContinuousTest) {
@@ -300,14 +351,16 @@ class MemLabConsole {
300
351
  this.printStr('');
301
352
  }
302
353
  overwrite(msg, options = {}) {
303
- if (this.config.isTest || this.config.muteConsole) {
354
+ const str = this.style(msg, options.level || 'low');
355
+ if (this.config.isContinuousTest) {
356
+ this.printStr(msg, { isOverwrite: false });
304
357
  return;
305
358
  }
306
- if (this.config.isContinuousTest) {
307
- return console.log(msg);
359
+ if (this.config.isTest || this.config.muteConsole) {
360
+ this.printStr(str, { isOverwrite: true });
361
+ return;
308
362
  }
309
363
  this.clearPrevOverwriteMsg();
310
- const str = this.style(msg, options.level || 'low');
311
364
  this.printStr(str, { isOverwrite: true });
312
365
  }
313
366
  waitForConsole(query) {
@@ -32,8 +32,11 @@ export declare class FileManager {
32
32
  getCurDataDir(options: FileOption): string;
33
33
  getWebSourceDir(options?: FileOption): string;
34
34
  getWebSourceMetaFile(options?: FileOption): string;
35
+ getDebugDataDir(options?: FileOption): string;
36
+ getDebugSourceFile(options?: FileOption): string;
35
37
  getPersistDataDir(options: FileOption): string;
36
38
  getLoggerOutDir(options?: FileOption): string;
39
+ getHeapAnalysisLogDir(options?: FileOption): string;
37
40
  getTraceClustersDir(options?: FileOption): string;
38
41
  getTraceJSONDir(options?: FileOption): string;
39
42
  getUnclassifiedTraceClusterDir(options?: FileOption): string;
@@ -61,6 +64,7 @@ export declare class FileManager {
61
64
  controlWorkDir: string;
62
65
  testWorkDir: string;
63
66
  };
67
+ initNewHeapAnalysisLogFile(options?: FileOption): string;
64
68
  getAndInitTSCompileIntermediateDir(): string;
65
69
  clearDataDirs(options?: FileOption): void;
66
70
  emptyDirIfExists(dir: string): void;
@@ -98,12 +98,22 @@ class FileManager {
98
98
  getWebSourceMetaFile(options = {}) {
99
99
  return path_1.default.join(this.getWebSourceDir(options), 'files.json');
100
100
  }
101
+ getDebugDataDir(options = {}) {
102
+ return path_1.default.join(this.getDataBaseDir(options), 'debug');
103
+ }
104
+ getDebugSourceFile(options = {}) {
105
+ return path_1.default.join(this.getDebugDataDir(options), 'file.js');
106
+ }
101
107
  getPersistDataDir(options) {
102
108
  return path_1.default.join(this.getDataBaseDir(options), 'persist');
103
109
  }
104
110
  getLoggerOutDir(options = {}) {
105
111
  return path_1.default.join(this.getDataBaseDir(options), 'logger');
106
112
  }
113
+ // all heap analysis results generated
114
+ getHeapAnalysisLogDir(options = {}) {
115
+ return path_1.default.join(this.getLoggerOutDir(options), 'heap-analysis');
116
+ }
107
117
  // all trace clusters generated from the current run
108
118
  getTraceClustersDir(options = {}) {
109
119
  return path_1.default.join(this.getLoggerOutDir(options), 'trace-clusters');
@@ -200,6 +210,15 @@ class FileManager {
200
210
  const testWorkDir = joinAndProcessDir({}, expDir, 'test');
201
211
  return { controlWorkDir, testWorkDir };
202
212
  }
213
+ // create a unique log file created for heap analysis output
214
+ initNewHeapAnalysisLogFile(options = {}) {
215
+ const dir = this.getHeapAnalysisLogDir(options);
216
+ const file = path_1.default.join(dir, `analysis-${Utils_1.default.getUniqueID()}-out.log`);
217
+ if (!fs_extra_1.default.existsSync(file)) {
218
+ fs_extra_1.default.createFileSync(file);
219
+ }
220
+ return file;
221
+ }
203
222
  getAndInitTSCompileIntermediateDir() {
204
223
  const dir = path_1.default.join(this.getTmpDir(), 'memlab-code');
205
224
  this.rmDir(dir);
@@ -212,6 +231,7 @@ class FileManager {
212
231
  return;
213
232
  }
214
233
  this.emptyDirIfExists(this.getWebSourceDir(options));
234
+ this.emptyDirIfExists(this.getDebugDataDir(options));
215
235
  const dataSuffix = ['.heapsnapshot', '.json', '.png'];
216
236
  const files = fs_extra_1.default.readdirSync(curDataDir);
217
237
  for (const file of files) {
@@ -238,6 +258,8 @@ class FileManager {
238
258
  this.emptyDirIfExists(this.getUnclassifiedTraceClusterDir(options));
239
259
  // all unique cluster info
240
260
  this.emptyDirIfExists(this.getUniqueTraceClusterDir(options));
261
+ // all heap analysis results
262
+ this.emptyDirIfExists(this.getHeapAnalysisLogDir(options));
241
263
  }
242
264
  resetBrowserDir() {
243
265
  try {
@@ -294,9 +316,12 @@ class FileManager {
294
316
  const outDir = joinAndProcessDir(options, this.getDataOutDir(options));
295
317
  config.curDataDir = joinAndProcessDir(options, this.getCurDataDir(options));
296
318
  config.webSourceDir = joinAndProcessDir(options, this.getWebSourceDir(options));
319
+ config.debugDataDir = joinAndProcessDir(options, this.getDebugDataDir(options));
297
320
  config.dataBuilderDataDir = joinAndProcessDir(options, config.dataBaseDir, 'dataBuilder');
298
321
  config.persistentDataDir = joinAndProcessDir(options, this.getPersistDataDir(options));
322
+ // register the default log file
299
323
  config.consoleLogFile = path_1.default.join(config.curDataDir, 'console-log.txt');
324
+ Console_1.default.registerLogFile(config.consoleLogFile);
300
325
  config.runMetaFile = this.getRunMetaFile(options);
301
326
  config.snapshotSequenceFile = this.getSnapshotSequenceMetaFile(options);
302
327
  config.browserInfoSummary = path_1.default.join(config.curDataDir, 'browser-info.txt');
@@ -314,6 +339,8 @@ class FileManager {
314
339
  config.traceClusterOutDir = joinAndProcessDir(options, this.getTraceClustersDir(options));
315
340
  // detailed trace json files for visualization
316
341
  config.traceJsonOutDir = joinAndProcessDir(options, this.getTraceJSONDir(options));
342
+ // heap analysis results
343
+ config.heapAnalysisLogDir = joinAndProcessDir(options, this.getHeapAnalysisLogDir(options));
317
344
  config.metricsOutDir = joinAndProcessDir(options, loggerOutDir, 'metrics');
318
345
  config.reportScreenshotFile = path_1.default.join(outDir, 'report.png');
319
346
  const codeDataDir = this.getCodeDataDir();
@@ -7,15 +7,10 @@
7
7
  * @format
8
8
  * @oncall web_perf_infra
9
9
  */
10
- import type { E2EStepInfo, HeapNodeIdSet, IHeapNode, IHeapSnapshot, IMemoryAnalystOptions, IMemoryAnalystSnapshotDiff, LeakTracePathItem, Optional, IOveralLeakInfo, TraceCluster, ISerializedInfo } from './Types';
10
+ import type { E2EStepInfo, HeapNodeIdSet, IHeapSnapshot, IMemoryAnalystOptions, IMemoryAnalystSnapshotDiff, IOveralHeapInfo, LeakTracePathItem, Optional, IOveralLeakInfo, TraceCluster, ISerializedInfo } from './Types';
11
11
  import TraceFinder from '../paths/TraceFinder';
12
12
  declare class MemoryAnalyst {
13
13
  checkLeak(): Promise<ISerializedInfo[]>;
14
- checkUnbound(options?: IMemoryAnalystOptions): Promise<void>;
15
- breakDownMemoryByShapes(options?: {
16
- file?: string;
17
- }): Promise<void>;
18
- detectUnboundGrowth(options?: IMemoryAnalystOptions): Promise<void>;
19
14
  detectMemoryLeaks(): Promise<ISerializedInfo[]>;
20
15
  visualizeMemoryUsage(options?: IMemoryAnalystOptions): void;
21
16
  focus(options?: {
@@ -23,19 +18,15 @@ declare class MemoryAnalyst {
23
18
  }): Promise<void>;
24
19
  shouldLoadCompleteSnapshot(tabsOrder: E2EStepInfo[], tab: E2EStepInfo): boolean;
25
20
  diffSnapshots(loadAll?: boolean): Promise<IMemoryAnalystSnapshotDiff>;
26
- private calculateRetainedSizes;
27
21
  preparePathFinder(snapshot: IHeapSnapshot): TraceFinder;
28
22
  private dumpPageInteractionSummary;
29
23
  private dumpLeakSummaryToConsole;
30
24
  private filterLeakedObjects;
31
- aggregateDominatorMetrics(ids: HeapNodeIdSet, snapshot: IHeapSnapshot, checkNodeCb: (node: IHeapNode) => boolean, nodeMetricsCb: (node: IHeapNode) => number): number;
32
- private getOverallHeapInfo;
33
- getRetainedSize(node: IHeapNode): number;
25
+ getOverallHeapInfo(snapshot: IHeapSnapshot, options?: {
26
+ force?: boolean;
27
+ }): Optional<IOveralHeapInfo>;
34
28
  getOverallLeakInfo(leakedNodeIds: HeapNodeIdSet, snapshot: IHeapSnapshot): Optional<IOveralLeakInfo>;
35
- private printHeapInfo;
36
- private breakDownSnapshotByShapes;
37
- private isTrivialEdgeForBreakDown;
38
- private breakDownByReferrers;
29
+ printHeapInfo(leakInfo: IOveralHeapInfo): void;
39
30
  private printHeapAndLeakInfo;
40
31
  private logLeakTraceSummary;
41
32
  searchLeakedTraces(leakedNodeIds: HeapNodeIdSet, snapshot: IHeapSnapshot): Promise<{