@memlab/core 1.1.16 → 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.
@@ -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;
@@ -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<{
@@ -23,7 +23,6 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
23
23
  Object.defineProperty(exports, "__esModule", { value: true });
24
24
  const fs_1 = __importDefault(require("fs"));
25
25
  const babar_1 = __importDefault(require("babar"));
26
- const chalk_1 = __importDefault(require("chalk"));
27
26
  const LeakClusterLogger_1 = __importDefault(require("../logger/LeakClusterLogger"));
28
27
  const LeakTraceDetailsLogger_1 = __importDefault(require("../logger/LeakTraceDetailsLogger"));
29
28
  const TraceFinder_1 = __importDefault(require("../paths/TraceFinder"));
@@ -42,156 +41,6 @@ class MemoryAnalyst {
42
41
  return yield this.detectMemoryLeaks();
43
42
  });
44
43
  }
45
- checkUnbound(options = {}) {
46
- return __awaiter(this, void 0, void 0, function* () {
47
- this.visualizeMemoryUsage(options);
48
- Utils_1.default.checkSnapshots(options);
49
- yield this.detectUnboundGrowth(options);
50
- });
51
- }
52
- breakDownMemoryByShapes(options = {}) {
53
- return __awaiter(this, void 0, void 0, function* () {
54
- const opt = { buildNodeIdIndex: true, verbose: true };
55
- const file = options.file ||
56
- Utils_1.default.getSnapshotFilePathWithTabType(/.*/) ||
57
- '<EMPTY_FILE_PATH>';
58
- const snapshot = yield Utils_1.default.getSnapshotFromFile(file, opt);
59
- this.preparePathFinder(snapshot);
60
- const heapInfo = this.getOverallHeapInfo(snapshot, { force: true });
61
- if (heapInfo) {
62
- this.printHeapInfo(heapInfo);
63
- }
64
- this.breakDownSnapshotByShapes(snapshot);
65
- });
66
- }
67
- // find any objects that keeps growing
68
- detectUnboundGrowth(options = {}) {
69
- return __awaiter(this, void 0, void 0, function* () {
70
- const nodeInfo = Object.create(null);
71
- let hasCheckedFirstSnapshot = false;
72
- let snapshot = null;
73
- const isValidNode = (node) => node.type === 'object' ||
74
- node.type === 'closure' ||
75
- node.type === 'regexp';
76
- const initNodeInfo = (node) => {
77
- if (!isValidNode(node)) {
78
- return;
79
- }
80
- const n = node.retainedSize;
81
- nodeInfo[node.id] = {
82
- type: node.type,
83
- name: node.name,
84
- min: n,
85
- max: n,
86
- history: [n],
87
- node,
88
- };
89
- };
90
- const updateNodeInfo = (node) => {
91
- const item = nodeInfo[node.id];
92
- if (!item) {
93
- return;
94
- }
95
- if (node.name !== item.name || node.type !== item.type) {
96
- nodeInfo[node.id] = null;
97
- return;
98
- }
99
- const n = node.retainedSize;
100
- // only monotonic increase?
101
- if (Config_1.default.monotonicUnboundGrowthOnly && n < item.max) {
102
- nodeInfo[node.id] = null;
103
- return;
104
- }
105
- item.history.push(n);
106
- item.max = Math.max(item.max, n);
107
- item.min = Math.min(item.min, n);
108
- };
109
- // summarize the heap objects info in current heap snapshot
110
- // this is mainly used for better understanding of the % of
111
- // objects released and allocated over time
112
- const maybeSummarizeNodeInfo = () => {
113
- if (!Config_1.default.verbose) {
114
- return;
115
- }
116
- let n = 0;
117
- for (const k in nodeInfo) {
118
- if (nodeInfo[k]) {
119
- ++n;
120
- }
121
- }
122
- Console_1.default.lowLevel(`Objects tracked: ${n}`);
123
- };
124
- Console_1.default.overwrite('Checking unbounded objects...');
125
- const snapshotFiles = options.snapshotDir
126
- ? // load snapshots from a directory
127
- Utils_1.default.getSnapshotFilesInDir(options.snapshotDir)
128
- : // load snapshots based on the visit sequence meta data
129
- Utils_1.default.getSnapshotFilesFromTabsOrder();
130
- for (const file of snapshotFiles) {
131
- // force GC before loading each snapshot
132
- if (global.gc) {
133
- global.gc();
134
- }
135
- // load and preprocess heap snapshot
136
- const opt = { buildNodeIdIndex: true, verbose: true };
137
- snapshot = yield Utils_1.default.getSnapshotFromFile(file, opt);
138
- this.calculateRetainedSizes(snapshot);
139
- // keep track of heap objects
140
- if (!hasCheckedFirstSnapshot) {
141
- // record Ids in the snapshot
142
- snapshot.nodes.forEach(initNodeInfo);
143
- hasCheckedFirstSnapshot = true;
144
- }
145
- else {
146
- snapshot.nodes.forEach(updateNodeInfo);
147
- maybeSummarizeNodeInfo();
148
- }
149
- }
150
- // exit if no heap snapshot found
151
- if (!hasCheckedFirstSnapshot) {
152
- return;
153
- }
154
- // post process and print the unbounded objects
155
- const idsInLastSnapshot = new Set();
156
- snapshot === null || snapshot === void 0 ? void 0 : snapshot.nodes.forEach(node => {
157
- idsInLastSnapshot.add(node.id);
158
- });
159
- let ids = [];
160
- for (const key in nodeInfo) {
161
- const id = parseInt(key, 10);
162
- const item = nodeInfo[id];
163
- if (!item) {
164
- continue;
165
- }
166
- if (!idsInLastSnapshot.has(id)) {
167
- continue;
168
- }
169
- if (item.min === item.max) {
170
- continue;
171
- }
172
- // filter out non-significant leaks
173
- if (item.history[item.history.length - 1] < Config_1.default.unboundSizeThreshold) {
174
- continue;
175
- }
176
- ids.push(Object.assign({ id }, item));
177
- }
178
- if (ids.length === 0) {
179
- Console_1.default.midLevel('No increasing objects found.');
180
- return;
181
- }
182
- ids = ids
183
- .sort((o1, o2) => o2.history[o2.history.length - 1] - o1.history[o1.history.length - 1])
184
- .slice(0, 20);
185
- // print on terminal
186
- const str = Serializer_1.default.summarizeUnboundedObjects(ids, { color: true });
187
- Console_1.default.topLevel('Top growing objects in sizes:');
188
- Console_1.default.lowLevel(' (Use `memlab trace --node-id=@ID` to get trace)');
189
- Console_1.default.topLevel('\n' + str);
190
- // save results to file
191
- const csv = Serializer_1.default.summarizeUnboundedObjectsToCSV(ids);
192
- fs_1.default.writeFileSync(Config_1.default.unboundObjectCSV, csv, 'UTF-8');
193
- });
194
- }
195
44
  // find all unique pattern of leaks
196
45
  detectMemoryLeaks() {
197
46
  return __awaiter(this, void 0, void 0, function* () {
@@ -368,11 +217,6 @@ class MemoryAnalyst {
368
217
  };
369
218
  });
370
219
  }
371
- calculateRetainedSizes(snapshot) {
372
- const finder = new TraceFinder_1.default();
373
- // dominator and retained size
374
- finder.calculateAllNodesRetainedSizes(snapshot);
375
- }
376
220
  // initialize the path finder
377
221
  preparePathFinder(snapshot) {
378
222
  const finder = new TraceFinder_1.default();
@@ -442,14 +286,6 @@ class MemoryAnalyst {
442
286
  Console_1.default.midLevel(`${leakedNodeIds.size} Fiber nodes and Detached elements`);
443
287
  }
444
288
  }
445
- aggregateDominatorMetrics(ids, snapshot, checkNodeCb, nodeMetricsCb) {
446
- let ret = 0;
447
- const dominators = Utils_1.default.getConditionalDominatorIds(ids, snapshot, checkNodeCb);
448
- Utils_1.default.applyToNodes(dominators, snapshot, node => {
449
- ret += nodeMetricsCb(node);
450
- });
451
- return ret;
452
- }
453
289
  getOverallHeapInfo(snapshot, options = {}) {
454
290
  if (!Config_1.default.verbose && !options.force) {
455
291
  return;
@@ -457,22 +293,19 @@ class MemoryAnalyst {
457
293
  Console_1.default.overwrite('summarizing heap info...');
458
294
  const allIds = Utils_1.default.getNodesIdSet(snapshot);
459
295
  const heapInfo = {
460
- fiberNodeSize: this.aggregateDominatorMetrics(allIds, snapshot, Utils_1.default.isFiberNode, this.getRetainedSize),
461
- regularFiberNodeSize: this.aggregateDominatorMetrics(allIds, snapshot, Utils_1.default.isRegularFiberNode, this.getRetainedSize),
462
- detachedFiberNodeSize: this.aggregateDominatorMetrics(allIds, snapshot, Utils_1.default.isDetachedFiberNode, this.getRetainedSize),
463
- alternateFiberNodeSize: this.aggregateDominatorMetrics(allIds, snapshot, Utils_1.default.isAlternateNode, this.getRetainedSize),
464
- error: this.aggregateDominatorMetrics(allIds, snapshot, node => node.name === 'Error', this.getRetainedSize),
296
+ fiberNodeSize: Utils_1.default.aggregateDominatorMetrics(allIds, snapshot, Utils_1.default.isFiberNode, Utils_1.default.getRetainedSize),
297
+ regularFiberNodeSize: Utils_1.default.aggregateDominatorMetrics(allIds, snapshot, Utils_1.default.isRegularFiberNode, Utils_1.default.getRetainedSize),
298
+ detachedFiberNodeSize: Utils_1.default.aggregateDominatorMetrics(allIds, snapshot, Utils_1.default.isDetachedFiberNode, Utils_1.default.getRetainedSize),
299
+ alternateFiberNodeSize: Utils_1.default.aggregateDominatorMetrics(allIds, snapshot, Utils_1.default.isAlternateNode, Utils_1.default.getRetainedSize),
300
+ error: Utils_1.default.aggregateDominatorMetrics(allIds, snapshot, node => node.name === 'Error', Utils_1.default.getRetainedSize),
465
301
  };
466
302
  return heapInfo;
467
303
  }
468
- getRetainedSize(node) {
469
- return node.retainedSize;
470
- }
471
304
  getOverallLeakInfo(leakedNodeIds, snapshot) {
472
305
  if (!Config_1.default.verbose) {
473
306
  return;
474
307
  }
475
- const leakInfo = Object.assign(Object.assign({}, this.getOverallHeapInfo(snapshot)), { leakedSize: this.aggregateDominatorMetrics(leakedNodeIds, snapshot, () => true, this.getRetainedSize), leakedFiberNodeSize: this.aggregateDominatorMetrics(leakedNodeIds, snapshot, Utils_1.default.isFiberNode, this.getRetainedSize), leakedAlternateFiberNodeSize: this.aggregateDominatorMetrics(leakedNodeIds, snapshot, Utils_1.default.isAlternateNode, this.getRetainedSize) });
308
+ const leakInfo = Object.assign(Object.assign({}, this.getOverallHeapInfo(snapshot)), { leakedSize: Utils_1.default.aggregateDominatorMetrics(leakedNodeIds, snapshot, () => true, Utils_1.default.getRetainedSize), leakedFiberNodeSize: Utils_1.default.aggregateDominatorMetrics(leakedNodeIds, snapshot, Utils_1.default.isFiberNode, Utils_1.default.getRetainedSize), leakedAlternateFiberNodeSize: Utils_1.default.aggregateDominatorMetrics(leakedNodeIds, snapshot, Utils_1.default.isAlternateNode, Utils_1.default.getRetainedSize) });
476
309
  return leakInfo;
477
310
  }
478
311
  printHeapInfo(leakInfo) {
@@ -485,111 +318,6 @@ class MemoryAnalyst {
485
318
  Console_1.default.topLevel(`· ${name}: ${value}`);
486
319
  });
487
320
  }
488
- breakDownSnapshotByShapes(snapshot) {
489
- Console_1.default.overwrite('Breaking down memory by shapes...');
490
- const breakdown = Object.create(null);
491
- const population = Object.create(null);
492
- // group objects based on their shapes
493
- snapshot.nodes.forEach(node => {
494
- if ((node.type !== 'object' && !Utils_1.default.isStringNode(node)) ||
495
- Config_1.default.nodeIgnoreSetInShape.has(node.name)) {
496
- return;
497
- }
498
- const key = Serializer_1.default.summarizeNodeShape(node);
499
- breakdown[key] = breakdown[key] || new Set();
500
- breakdown[key].add(node.id);
501
- if (population[key] === undefined) {
502
- population[key] = { examples: [], n: 0 };
503
- }
504
- ++population[key].n;
505
- // retain the top 5 examples
506
- const examples = population[key].examples;
507
- examples.push(node);
508
- examples.sort((n1, n2) => n2.retainedSize - n1.retainedSize);
509
- if (examples.length > 5) {
510
- examples.pop();
511
- }
512
- });
513
- // calculate and sort based on retained sizes
514
- const ret = [];
515
- for (const key in breakdown) {
516
- const size = this.aggregateDominatorMetrics(breakdown[key], snapshot, () => true, this.getRetainedSize);
517
- ret.push({ key, retainedSize: size });
518
- }
519
- ret.sort((o1, o2) => o2.retainedSize - o1.retainedSize);
520
- Console_1.default.topLevel('Object shapes with top retained sizes:');
521
- Console_1.default.lowLevel(' (Use `memlab trace --node-id=@ID` to get trace)\n');
522
- const topList = ret.slice(0, 40);
523
- // print settings
524
- const opt = { color: true, compact: true };
525
- const dot = chalk_1.default.grey('· ');
526
- const colon = chalk_1.default.grey(': ');
527
- // print the shapes with the biggest retained size
528
- for (const o of topList) {
529
- const referrerInfo = this.breakDownByReferrers(breakdown[o.key], snapshot);
530
- const { examples, n } = population[o.key];
531
- const shapeStr = Serializer_1.default.summarizeNodeShape(examples[0], opt);
532
- const bytes = Utils_1.default.getReadableBytes(o.retainedSize);
533
- const examplesStr = examples
534
- .map(e => `@${e.id} [${Utils_1.default.getReadableBytes(e.retainedSize)}]`)
535
- .join(' | ');
536
- const meta = chalk_1.default.grey(` (N: ${n}, Examples: ${examplesStr})`);
537
- Console_1.default.topLevel(`${dot}${shapeStr}${colon}${bytes}${meta}`);
538
- Console_1.default.lowLevel(referrerInfo + '\n');
539
- }
540
- }
541
- isTrivialEdgeForBreakDown(edge) {
542
- const source = edge.fromNode;
543
- return (source.type === 'array' ||
544
- source.name === '(object elements)' ||
545
- source.name === 'system' ||
546
- edge.name_or_index === '__proto__' ||
547
- edge.name_or_index === 'prototype');
548
- }
549
- breakDownByReferrers(ids, snapshot) {
550
- const edgeNames = Object.create(null);
551
- for (const id of ids) {
552
- const node = snapshot.getNodeById(id);
553
- for (const edge of (node === null || node === void 0 ? void 0 : node.referrers) || []) {
554
- const source = edge.fromNode;
555
- if (!Utils_1.default.isMeaningfulEdge(edge) ||
556
- this.isTrivialEdgeForBreakDown(edge)) {
557
- continue;
558
- }
559
- const sourceName = Serializer_1.default.summarizeNodeName(source, {
560
- color: false,
561
- });
562
- const edgeName = Serializer_1.default.summarizeEdgeName(edge, {
563
- color: false,
564
- abstract: true,
565
- });
566
- const edgeKey = `[${sourceName}] --${edgeName}--> `;
567
- edgeNames[edgeKey] = edgeNames[edgeKey] || {
568
- numberOfEdgesToNode: 0,
569
- source,
570
- edge,
571
- };
572
- ++edgeNames[edgeKey].numberOfEdgesToNode;
573
- }
574
- }
575
- const referrerInfo = Object.entries(edgeNames)
576
- .sort((i1, i2) => i2[1].numberOfEdgesToNode - i1[1].numberOfEdgesToNode)
577
- .slice(0, 4)
578
- .map(i => {
579
- const meta = i[1];
580
- const source = Serializer_1.default.summarizeNodeName(meta.source, {
581
- color: true,
582
- });
583
- const edgeName = Serializer_1.default.summarizeEdgeName(meta.edge, {
584
- color: true,
585
- abstract: true,
586
- });
587
- const edgeSummary = `${source} --${edgeName}-->`;
588
- return ` · ${edgeSummary}: ${meta.numberOfEdgesToNode}`;
589
- })
590
- .join('\n');
591
- return referrerInfo;
592
- }
593
321
  printHeapAndLeakInfo(leakedNodeIds, snapshot) {
594
322
  // write page interaction summary to the leaks text file
595
323
  this.dumpPageInteractionSummary();
@@ -616,10 +344,6 @@ class MemoryAnalyst {
616
344
  this.printHeapAndLeakInfo(leakedNodeIds, snapshot);
617
345
  // get all leaked objects
618
346
  this.filterLeakedObjects(leakedNodeIds, snapshot);
619
- if (Config_1.default.verbose) {
620
- // show a breakdown of different object structures
621
- this.breakDownSnapshotByShapes(snapshot);
622
- }
623
347
  const nodeIdInPaths = new Set();
624
348
  const paths = [];
625
349
  let numOfLeakedObjects = 0;
@@ -642,7 +366,7 @@ class MemoryAnalyst {
642
366
  Console_1.default.midLevel(`${numOfLeakedObjects} leaked objects`);
643
367
  }
644
368
  // cluster traces from the current run
645
- const clusters = TraceBucket_1.default.clusterPaths(paths, snapshot, this.aggregateDominatorMetrics, {
369
+ const clusters = TraceBucket_1.default.clusterPaths(paths, snapshot, Utils_1.default.aggregateDominatorMetrics, {
646
370
  strategy: Config_1.default.isMLClustering
647
371
  ? new MLTraceSimilarityStrategy_1.default()
648
372
  : undefined,
@@ -651,7 +375,7 @@ class MemoryAnalyst {
651
375
  yield this.serializeClusterUpdate(clusters);
652
376
  if (Config_1.default.logUnclassifiedClusters) {
653
377
  // cluster traces from the current run
654
- const clustersUnclassified = TraceBucket_1.default.generateUnClassifiedClusters(paths, snapshot, this.aggregateDominatorMetrics);
378
+ const clustersUnclassified = TraceBucket_1.default.generateUnClassifiedClusters(paths, snapshot, Utils_1.default.aggregateDominatorMetrics);
655
379
  LeakClusterLogger_1.default.logUnclassifiedClusters(clustersUnclassified);
656
380
  }
657
381
  return {
@@ -682,7 +406,7 @@ class MemoryAnalyst {
682
406
  }
683
407
  }, { reverse: true });
684
408
  // cluster traces from the current run
685
- const clusters = TraceBucket_1.default.clusterPaths(paths, snapshot, this.aggregateDominatorMetrics, {
409
+ const clusters = TraceBucket_1.default.clusterPaths(paths, snapshot, Utils_1.default.aggregateDominatorMetrics, {
686
410
  strategy: Config_1.default.isMLClustering
687
411
  ? new MLTraceSimilarityStrategy_1.default()
688
412
  : undefined,
@@ -8,7 +8,7 @@
8
8
  * @oncall web_perf_infra
9
9
  */
10
10
  import { ParsedArgs } from 'minimist';
11
- import type { LaunchOptions, Page } from 'puppeteer';
11
+ import type { LaunchOptions, Page as PuppeteerPage } from 'puppeteer';
12
12
  import type { ErrorHandling, MemLabConfig } from './Config';
13
13
  /** @internal */
14
14
  export declare type AnyValue = any;
@@ -56,6 +56,12 @@ export declare type XvfbType = {
56
56
  display: () => string;
57
57
  };
58
58
  /** @internal */
59
+ export declare type ShellOptions = {
60
+ dir?: Optional<string>;
61
+ ignoreError?: Optional<boolean>;
62
+ disconnectStdio?: Optional<boolean>;
63
+ };
64
+ /** @internal */
59
65
  export declare type CLIArgs = {
60
66
  verbose: boolean;
61
67
  app: string;
@@ -85,6 +91,57 @@ export declare type CLIArgs = {
85
91
  'local-puppeteer': boolean;
86
92
  'snapshot-dir': string;
87
93
  };
94
+ /**
95
+ * This is the puppeteer [`Page`](https://pptr.dev/api/puppeteer.page)
96
+ * class used by MemLab. The puppeteer `Page` class instance provides
97
+ * APIs to interact with the web browser.
98
+ *
99
+ * The puppeteer `Page` type can be incompatible across different versions.
100
+ * Your local npm-installed puppeteer version may be different from
101
+ * the puppeteer used by MemLab. This may cause some type errors, for example:
102
+ *
103
+ * ```typescript
104
+ * import type {Page} from 'puppeteer';
105
+ * import type {RunOptions} from '@memlab/api';
106
+ *
107
+ * const runOptions: RunOptions = {
108
+ * scenario: {
109
+ * // initial page load url: Google Maps
110
+ * url: () => {
111
+ * return "https://www.google.com/maps/@37.386427,-122.0428214,11z";
112
+ * },
113
+ * // type error here if your local puppeeter version is different
114
+ * // from the puppeteer used by MemLab
115
+ * action: async function (page: Page) {
116
+ * await page.click('button[aria-label="Hotels"]');
117
+ * },
118
+ * },
119
+ * };
120
+ * ```
121
+ *
122
+ * To avoid the type error in the code example above, MemLab exports the
123
+ * puppeteer `Page` type used by MemLab so that your code can import it
124
+ * when necessary:
125
+ *
126
+ * ```typescript
127
+ * import type {Page} from '@memlab/core' // import Page type from memlab
128
+ * import type {RunOptions} from 'memlab';
129
+ *
130
+ * const runOptions: RunOptions = {
131
+ * scenario: {
132
+ * // initial page load url: Google Maps
133
+ * url: () => {
134
+ * return "https://www.google.com/maps/@37.386427,-122.0428214,11z";
135
+ * },
136
+ * // no type error here
137
+ * action: async function (page: Page) {
138
+ * await page.click('button[aria-label="Hotels"]');
139
+ * },
140
+ * },
141
+ * };
142
+ * ```
143
+ */
144
+ export declare type Page = PuppeteerPage;
88
145
  /**
89
146
  * the predicate callback is used to decide if a
90
147
  * entity of type `T`.
@@ -338,7 +395,8 @@ export declare type LeakFilterCallback = (node: IHeapNode, snapshot: IHeapSnapsh
338
395
  * For concrete examples, check out {@link action} or {@link back}.
339
396
  *
340
397
  * @param page the puppeteer [`Page`](https://pptr.dev/api/puppeteer.page)
341
- * object, which provides APIs to interact with the web browser
398
+ * object, which provides APIs to interact with the web browser.
399
+ * To import this type, check out {@link Page}.
342
400
  * @returns no return value
343
401
  */
344
402
  export declare type InteractionsCallback = (page: Page, args?: OperationArgs) => Promise<void>;
@@ -420,7 +478,8 @@ export interface IScenario {
420
478
  *
421
479
  * * **Parameters**:
422
480
  * * page: `Page` | the puppeteer [`Page`](https://pptr.dev/api/puppeteer.page)
423
- * object, which provides APIs to interact with the web browser
481
+ * object, which provides APIs to interact with the web browser. To import
482
+ * this type, check out {@link Page}.
424
483
  *
425
484
  * * **Examples**:
426
485
  * ```typescript
@@ -467,7 +526,8 @@ export interface IScenario {
467
526
  *
468
527
  * * **Parameters**:
469
528
  * * page: `Page` | the puppeteer [`Page`](https://pptr.dev/api/puppeteer.page)
470
- * object, which provides APIs to interact with the web browser
529
+ * object, which provides APIs to interact with the web browser. To import
530
+ * this type, check out {@link Page}.
471
531
  *
472
532
  * * **Examples**:
473
533
  * ```typescript
@@ -496,7 +556,8 @@ export interface IScenario {
496
556
  *
497
557
  * * **Parameters**:
498
558
  * * page: `Page` | the puppeteer [`Page`](https://pptr.dev/api/puppeteer.page)
499
- * object, which provides APIs to interact with the web browser
559
+ * object, which provides APIs to interact with the web browser. To import
560
+ * this type, check out {@link Page}.
500
561
  *
501
562
  * * **Examples**:
502
563
  * ```typescript
@@ -539,7 +600,8 @@ export interface IScenario {
539
600
  *
540
601
  * * **Parameters**:
541
602
  * * page: `Page` | the puppeteer [`Page`](https://pptr.dev/api/puppeteer.page)
542
- * object, which provides APIs to interact with the web browser
603
+ * object, which provides APIs to interact with the web browser. To import
604
+ * this type, check out {@link Page}.
543
605
  *
544
606
  * * **Examples**:
545
607
  * ```typescript
@@ -582,7 +644,8 @@ export interface IScenario {
582
644
  *
583
645
  * * **Parameters**:
584
646
  * * page: `Page` | the puppeteer [`Page`](https://pptr.dev/api/puppeteer.page)
585
- * object, which provides APIs to interact with the web browser
647
+ * object, which provides APIs to interact with the web browser. To import
648
+ * this type, check out {@link Page}.
586
649
  * * **Returns**: a boolean value, if it returns `true`, memlab will consider
587
650
  * the navigation completes, if it returns `false`, memlab will keep calling
588
651
  * this callback until it returns `true`. This is an async callback, you can
@@ -766,6 +829,7 @@ export interface IDataBuilder {
766
829
  * Callback function to provide if the page is loaded.
767
830
  * For concrete example, check out {@link isPageLoaded}.
768
831
  * @param page - puppeteer's [Page](https://pptr.dev/api/puppeteer.page/) object.
832
+ * To import this type, check out {@link Page}.
769
833
  * @returns a boolean value, if it returns `true`, memlab will consider
770
834
  * the navigation completes, if it returns `false`, memlab will keep calling
771
835
  * this callback until it returns `true`. This is an async callback, you can
@@ -773,6 +837,8 @@ export interface IDataBuilder {
773
837
  */
774
838
  export declare type CheckPageLoadCallback = (page: Page) => Promise<boolean>;
775
839
  /** @internal */
840
+ export declare type PageSetupCallback = (page: Page) => Promise<void>;
841
+ /** @internal */
776
842
  export interface IE2EScenarioVisitPlan {
777
843
  name: string;
778
844
  appName: string;
@@ -786,6 +852,7 @@ export interface IE2EScenarioVisitPlan {
786
852
  dataBuilder: Optional<IDataBuilder>;
787
853
  isPageLoaded?: CheckPageLoadCallback;
788
854
  scenario?: IScenario;
855
+ pageSetup?: PageSetupCallback;
789
856
  }
790
857
  /** @internal */
791
858
  export declare type OperationArgs = {
@@ -7,7 +7,7 @@
7
7
  * @format
8
8
  * @oncall web_perf_infra
9
9
  */
10
- import type { HaltOrThrowOptions } from './Types';
10
+ import type { HaltOrThrowOptions, HeapNodeIdSet, ShellOptions } from './Types';
11
11
  import type { Browser, Page } from 'puppeteer';
12
12
  import type { AnyAyncFunction, AnyOptions, E2EStepInfo, IHeapSnapshot, IHeapNode, IHeapEdge, IScenario, ILeakFilter, LeakTracePathItem, RunMetaInfo, RawHeapSnapshot, Nullable, Optional } from './Types';
13
13
  declare function isHermesInternalObject(node: IHeapNode): boolean;
@@ -122,7 +122,11 @@ declare function getError(maybeError: unknown): Error;
122
122
  declare function isNodeDominatedByDeletionsArray(node: IHeapNode): boolean;
123
123
  declare function getUniqueID(): string;
124
124
  declare function getClosureSourceUrl(node: IHeapNode): Nullable<string>;
125
+ export declare function runShell(command: string, options?: ShellOptions): string;
126
+ export declare function getRetainedSize(node: IHeapNode): number;
127
+ export declare function aggregateDominatorMetrics(ids: HeapNodeIdSet, snapshot: IHeapSnapshot, checkNodeCb: (node: IHeapNode) => boolean, nodeMetricsCb: (node: IHeapNode) => number): number;
125
128
  declare const _default: {
129
+ aggregateDominatorMetrics: typeof aggregateDominatorMetrics;
126
130
  applyToNodes: typeof applyToNodes;
127
131
  callAsync: typeof callAsync;
128
132
  camelCaseToReadableString: typeof camelCaseToReadableString;
@@ -148,6 +152,7 @@ declare const _default: {
148
152
  getReadableBytes: typeof getReadableBytes;
149
153
  getReadablePercent: typeof getReadablePercent;
150
154
  getReadableTime: typeof getReadableTime;
155
+ getRetainedSize: typeof getRetainedSize;
151
156
  getRunMetaFilePath: typeof getRunMetaFilePath;
152
157
  getScenarioName: typeof getScenarioName;
153
158
  getSingleSnapshotFileForAnalysis: typeof getSingleSnapshotFileForAnalysis;
@@ -215,6 +220,7 @@ declare const _default: {
215
220
  repeat: typeof repeat;
216
221
  resolveFilePath: typeof resolveFilePath;
217
222
  resolveSnapshotFilePath: typeof resolveSnapshotFilePath;
223
+ runShell: typeof runShell;
218
224
  setIsAlternateNode: typeof setIsAlternateNode;
219
225
  setIsRegularFiberNode: typeof setIsRegularFiberNode;
220
226
  shouldShowMoreInfo: typeof shouldShowMoreInfo;
package/dist/lib/Utils.js CHANGED
@@ -44,9 +44,10 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
44
44
  return (mod && mod.__esModule) ? mod : { "default": mod };
45
45
  };
46
46
  Object.defineProperty(exports, "__esModule", { value: true });
47
- exports.resolveSnapshotFilePath = void 0;
47
+ exports.aggregateDominatorMetrics = exports.getRetainedSize = exports.runShell = exports.resolveSnapshotFilePath = void 0;
48
48
  const fs_1 = __importDefault(require("fs"));
49
49
  const path_1 = __importDefault(require("path"));
50
+ const child_process_1 = __importDefault(require("child_process"));
50
51
  const process_1 = __importDefault(require("process"));
51
52
  const Config_1 = __importStar(require("./Config"));
52
53
  const Console_1 = __importDefault(require("./Console"));
@@ -1751,7 +1752,52 @@ function getClosureSourceUrl(node) {
1751
1752
  const url = (_b = (_a = urlNode === null || urlNode === void 0 ? void 0 : urlNode.toStringNode()) === null || _a === void 0 ? void 0 : _a.stringValue) !== null && _b !== void 0 ? _b : null;
1752
1753
  return url;
1753
1754
  }
1755
+ function runShell(command, options = {}) {
1756
+ var _a, _b, _c;
1757
+ const runningDir = (_b = (_a = options.dir) !== null && _a !== void 0 ? _a : Config_1.default.workDir) !== null && _b !== void 0 ? _b : FileManager_1.default.getTmpDir();
1758
+ const execOptions = {
1759
+ cwd: runningDir,
1760
+ stdio: options.disconnectStdio
1761
+ ? []
1762
+ : [process_1.default.stdin, process_1.default.stdout, process_1.default.stderr],
1763
+ };
1764
+ if (process_1.default.platform !== 'win32') {
1765
+ execOptions.shell = '/bin/bash';
1766
+ }
1767
+ let ret = '';
1768
+ try {
1769
+ ret = child_process_1.default.execSync(command, execOptions);
1770
+ }
1771
+ catch (ex) {
1772
+ if (Config_1.default.verbose) {
1773
+ if (ex instanceof Error) {
1774
+ Console_1.default.lowLevel(ex.message);
1775
+ Console_1.default.lowLevel((_c = ex.stack) !== null && _c !== void 0 ? _c : '');
1776
+ }
1777
+ }
1778
+ if (options.ignoreError === true) {
1779
+ return '';
1780
+ }
1781
+ __1.utils.haltOrThrow(`Error when executing command: ${command}`);
1782
+ }
1783
+ return ret && ret.toString('UTF-8');
1784
+ }
1785
+ exports.runShell = runShell;
1786
+ function getRetainedSize(node) {
1787
+ return node.retainedSize;
1788
+ }
1789
+ exports.getRetainedSize = getRetainedSize;
1790
+ function aggregateDominatorMetrics(ids, snapshot, checkNodeCb, nodeMetricsCb) {
1791
+ let ret = 0;
1792
+ const dominators = __1.utils.getConditionalDominatorIds(ids, snapshot, checkNodeCb);
1793
+ __1.utils.applyToNodes(dominators, snapshot, node => {
1794
+ ret += nodeMetricsCb(node);
1795
+ });
1796
+ return ret;
1797
+ }
1798
+ exports.aggregateDominatorMetrics = aggregateDominatorMetrics;
1754
1799
  exports.default = {
1800
+ aggregateDominatorMetrics,
1755
1801
  applyToNodes,
1756
1802
  callAsync,
1757
1803
  camelCaseToReadableString,
@@ -1777,6 +1823,7 @@ exports.default = {
1777
1823
  getReadableBytes,
1778
1824
  getReadablePercent,
1779
1825
  getReadableTime,
1826
+ getRetainedSize,
1780
1827
  getRunMetaFilePath,
1781
1828
  getScenarioName,
1782
1829
  getSingleSnapshotFileForAnalysis,
@@ -1844,6 +1891,7 @@ exports.default = {
1844
1891
  repeat,
1845
1892
  resolveFilePath,
1846
1893
  resolveSnapshotFilePath,
1894
+ runShell,
1847
1895
  setIsAlternateNode,
1848
1896
  setIsRegularFiberNode,
1849
1897
  shouldShowMoreInfo,
@@ -257,10 +257,9 @@ class HeapNode {
257
257
  get location() {
258
258
  const heapSnapshot = this.heapSnapshot;
259
259
  const locationIdx = heapSnapshot._nodeIdx2LocationIdx[this.idx];
260
- if (locationIdx == null) {
261
- return null;
262
- }
263
- return new HeapLocation_1.default(heapSnapshot, locationIdx);
260
+ return locationIdx == null
261
+ ? null
262
+ : new HeapLocation_1.default(heapSnapshot, locationIdx);
264
263
  }
265
264
  // search reference by edge name and edge type
266
265
  getReference(edgeName, edgeType) {
@@ -206,8 +206,8 @@ class HeapSnapshot {
206
206
  const locationFieldsCount = this._locationFieldsCount;
207
207
  let locationIdx = 0;
208
208
  while (locationIdx < this._locationCount) {
209
- const id = locations[locationIdx * locationFieldsCount + this._locationObjectIndexOffset];
210
- this._nodeIdx2LocationIdx[id] = locationIdx;
209
+ const nodeIndex = locations[locationIdx * locationFieldsCount + this._locationObjectIndexOffset];
210
+ this._nodeIdx2LocationIdx[nodeIndex] = locationIdx;
211
211
  ++locationIdx;
212
212
  }
213
213
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@memlab/core",
3
- "version": "1.1.16",
3
+ "version": "1.1.17",
4
4
  "license": "MIT",
5
5
  "description": "memlab core libraries",
6
6
  "author": "Liang Gong <lgong@fb.com>",
@@ -59,7 +59,7 @@
59
59
  "scripts": {
60
60
  "build-pkg": "tsc",
61
61
  "test-pkg": "jest .",
62
- "publish-patch": "npm version patch --force && npm publish",
62
+ "publish-patch": "npm publish",
63
63
  "clean-pkg": "rm -rf ./dist && rm -rf ./node_modules && rm -f ./tsconfig.tsbuildinfo"
64
64
  },
65
65
  "bugs": {