@memlab/core 1.1.19 → 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);
@@ -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
@@ -51,7 +51,9 @@ export declare class FileManager {
51
51
  getPreviewReportDir(options?: FileOption): string;
52
52
  getLeakSummaryFile(options?: FileOption): string;
53
53
  getRunMetaFile(options?: FileOption): string;
54
+ getRunMetaExternalTemplateFile(): string;
54
55
  getSnapshotSequenceMetaFile(options?: FileOption): string;
56
+ getSnapshotSequenceExternalTemplateFile(): string;
55
57
  getInputDataDir(): string;
56
58
  getAllFilesInSubDirs(dir: string): string[];
57
59
  getTmpDir(): string;
@@ -72,6 +74,7 @@ export declare class FileManager {
72
74
  iterateAllFiles(dir: string, callback: (filepath: string) => AnyValue): void;
73
75
  rmWorkDir(options?: FileOption): void;
74
76
  isWithinInternalDirectory(filePath: string): boolean;
77
+ createDefaultVisitOrderMetaFile(options?: FileOption): void;
75
78
  initDirs(config: MemLabConfig, options?: FileOption): void;
76
79
  }
77
80
  declare const _default: FileManager;
@@ -179,9 +179,15 @@ class FileManager {
179
179
  getRunMetaFile(options = FileManager.defaultFileOption) {
180
180
  return path_1.default.join(this.getCurDataDir(options), 'run-meta.json');
181
181
  }
182
+ getRunMetaExternalTemplateFile() {
183
+ return path_1.default.join(this.getCodeDataDir(), 'run-meta.json');
184
+ }
182
185
  getSnapshotSequenceMetaFile(options = FileManager.defaultFileOption) {
183
186
  return path_1.default.join(this.getCurDataDir(options), 'snap-seq.json');
184
187
  }
188
+ getSnapshotSequenceExternalTemplateFile() {
189
+ return path_1.default.join(this.getCodeDataDir(), 'visit-order.json');
190
+ }
185
191
  getInputDataDir() {
186
192
  return path_1.default.join(this.getDefaultWorkDir(), 'input');
187
193
  }
@@ -331,6 +337,37 @@ class FileManager {
331
337
  const internalDir = Constant_1.default.internalDir;
332
338
  return filePath.includes(`${sep}${internalDir}${sep}`);
333
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
+ }
334
371
  initDirs(config, options = FileManager.defaultFileOption) {
335
372
  // cache the last processed memlab config instance
336
373
  // the instance should be a singleton
@@ -339,6 +376,11 @@ class FileManager {
339
376
  // make sure getWorkDir is called first before
340
377
  // any other get file or get dir calls
341
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
+ }
342
384
  // remember the current working directory
343
385
  // especially if this is a transcient working directory
344
386
  config.workDir = joinAndProcessDir(options, workDir);
@@ -376,15 +418,16 @@ class FileManager {
376
418
  config.heapAnalysisLogDir = joinAndProcessDir(options, this.getHeapAnalysisLogDir(options));
377
419
  config.metricsOutDir = joinAndProcessDir(options, loggerOutDir, 'metrics');
378
420
  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');
421
+ config.externalRunMetaFile = this.getRunMetaExternalTemplateFile();
422
+ config.externalSnapshotVisitOrderFile =
423
+ this.getSnapshotSequenceExternalTemplateFile();
382
424
  joinAndProcessDir(options, this.getUniqueTraceClusterDir(options));
383
425
  config.newUniqueClusterDir = joinAndProcessDir(options, this.getNewUniqueTraceClusterDir(options));
384
426
  config.staleUniqueClusterDir = joinAndProcessDir(options, this.getStaleUniqueTraceClusterDir(options));
385
427
  config.currentUniqueClusterDir = joinAndProcessDir(options, this.getExistingUniqueTraceClusterDir(options));
386
428
  config.unclassifiedClusterDir = joinAndProcessDir(options, this.getUnclassifiedTraceClusterDir(options));
387
429
  config.allClusterSummaryFile = this.getAllClusterSummaryFile(options);
430
+ this.createDefaultVisitOrderMetaFile(options);
388
431
  }
389
432
  }
390
433
  exports.FileManager = FileManager;
@@ -47,6 +47,7 @@ export declare type FileOption = {
47
47
  workDir?: Optional<string>;
48
48
  clear?: boolean;
49
49
  transient?: boolean;
50
+ errorWhenAbsent?: boolean;
50
51
  };
51
52
  /** @internal */
52
53
  export declare type CLIOptions = {
@@ -897,6 +898,7 @@ export interface IE2EStepBasic {
897
898
  /** @internal */
898
899
  export declare type E2EStepInfo = IE2EStepBasic & {
899
900
  snapshot: boolean;
901
+ snapshotFile?: string;
900
902
  screenshot: boolean;
901
903
  idx: number;
902
904
  JSHeapUsedSize: number;
@@ -1218,6 +1220,16 @@ export interface IHeapLocation {
1218
1220
  * get the column number
1219
1221
  */
1220
1222
  column: number;
1223
+ /**
1224
+ * convert to a concise readable string output
1225
+ * (like calling `JSON.stringify(node, ...args)`).
1226
+ * Note: calling `JSON.stringify(node, ...args)` will not work
1227
+ * since the string is too large and not readable.
1228
+ *
1229
+ * This API does not completely serialize all the information
1230
+ * captured by the hosting object.
1231
+ */
1232
+ toJSONString(...args: Array<AnyValue>): string;
1221
1233
  }
1222
1234
  /** @internal */
1223
1235
  export interface IHeapEdgeBasic {
@@ -1290,6 +1302,16 @@ export interface IHeapEdge extends IHeapEdgeBasic {
1290
1302
  * JS heap object where this reference starts
1291
1303
  */
1292
1304
  fromNode: IHeapNode;
1305
+ /**
1306
+ * convert to a concise readable string output
1307
+ * (like calling `JSON.stringify(node, ...args)`).
1308
+ * Note: calling `JSON.stringify(node, ...args)` will not work
1309
+ * since the string is too large and not readable.
1310
+ *
1311
+ * This API does not completely serialize all the information
1312
+ * captured by the hosting object.
1313
+ */
1314
+ toJSONString(...args: Array<AnyValue>): string;
1293
1315
  }
1294
1316
  /**
1295
1317
  * A pseudo array containing all heap graph edges (references to heap objects
@@ -1446,6 +1468,11 @@ export interface IHeapNode extends IHeapNodeBasic {
1446
1468
  * object (including engine-internal, native, and JS references).
1447
1469
  */
1448
1470
  referrers: IHeapEdge[];
1471
+ /**
1472
+ * Get the number of all incoming references pointing to this heap object
1473
+ * (including engine-internal, native, and JS references).
1474
+ */
1475
+ numOfReferrers: number;
1449
1476
  /**
1450
1477
  * returns true if the heap node has been set an incoming edge
1451
1478
  * which leads to the parent node on the shortest path to GC root.
@@ -1493,6 +1520,16 @@ export interface IHeapNode extends IHeapNodeBasic {
1493
1520
  * inside the string node.
1494
1521
  */
1495
1522
  toStringNode(): Nullable<IHeapStringNode>;
1523
+ /**
1524
+ * convert to a concise readable string output
1525
+ * (like calling `JSON.stringify(node, ...args)`).
1526
+ * Note: calling `JSON.stringify(node, ...args)` will not work
1527
+ * since the string is too large and not readable.
1528
+ *
1529
+ * This API does not completely serialize all the information
1530
+ * captured by the hosting object.
1531
+ */
1532
+ toJSONString(...args: Array<AnyValue>): string;
1496
1533
  /**
1497
1534
  * executes a provided callback once for each JavaScript reference in the
1498
1535
  * hosting node (or outgoing edges from the node)
@@ -1563,6 +1600,24 @@ export interface IHeapNode extends IHeapNodeBasic {
1563
1600
  * ```
1564
1601
  */
1565
1602
  findAnyReferrer: (predicate: Predicator<IHeapEdge>) => Nullable<IHeapEdge>;
1603
+ /**
1604
+ * executes a provided predicate callback once for each JavaScript heap
1605
+ * object (heap graph node) pointing to the hosting node
1606
+ * (or nodes having edges to the hosting node) until the predicate
1607
+ * returns `true`
1608
+ * @param predicate the callback for each incoming JavaScript heap object
1609
+ * @returns the first referring node for which the predicate returns `true`,
1610
+ * otherwise returns `null` if no such node is found.
1611
+ *
1612
+ * * **Examples**:
1613
+ * ```typescript
1614
+ * const referrer = node.findAnyReferrerNode((node: IHeapNode) => {
1615
+ * // find the referring node with name "Parent"
1616
+ * return node.name === 'Parent';
1617
+ * });
1618
+ * ```
1619
+ */
1620
+ findAnyReferrerNode(predicate: Predicator<IHeapNode>): Nullable<IHeapNode>;
1566
1621
  /**
1567
1622
  * executes a provided predicate callback once for each JavaScript reference
1568
1623
  * pointing to the hosting node (or incoming edges to the node)
@@ -1580,6 +1635,24 @@ export interface IHeapNode extends IHeapNodeBasic {
1580
1635
  * ```
1581
1636
  */
1582
1637
  findReferrers: (predicate: Predicator<IHeapEdge>) => IHeapEdge[];
1638
+ /**
1639
+ * executes a provided predicate callback once for each JavaScript heap
1640
+ * object (heap graph node) pointing to the hosting node
1641
+ * (or nodes having edges to the hosting node)
1642
+ * @param predicate the callback for each referrer nodes
1643
+ * @returns an array containing all the referrer nodes for which the
1644
+ * predicate returns `true`, otherwise returns an empty array if no such
1645
+ * node is found.
1646
+ *
1647
+ * * **Examples**:
1648
+ * ```typescript
1649
+ * const referrerNodes = node.findReferrerNodes((node: IHeapNode) => {
1650
+ * // find all the referring nodes with name "Parent"
1651
+ * return node.name === 'Parent';
1652
+ * });
1653
+ * ```
1654
+ */
1655
+ findReferrerNodes: (predicate: Predicator<IHeapNode>) => IHeapNode[];
1583
1656
  /**
1584
1657
  * Given a JS reference's name and type, this API finds an outgoing JS
1585
1658
  * reference from the hosting node.
@@ -125,8 +125,10 @@ declare function isNodeDominatedByDeletionsArray(node: IHeapNode): boolean;
125
125
  declare function getUniqueID(): string;
126
126
  declare function getClosureSourceUrl(node: IHeapNode): Nullable<string>;
127
127
  export declare function runShell(command: string, options?: ShellOptions): Nullable<string>;
128
- export declare function getRetainedSize(node: IHeapNode): number;
129
- export declare function aggregateDominatorMetrics(ids: HeapNodeIdSet, snapshot: IHeapSnapshot, checkNodeCb: (node: IHeapNode) => boolean, nodeMetricsCb: (node: IHeapNode) => number): number;
128
+ declare function getRetainedSize(node: IHeapNode): number;
129
+ declare function aggregateDominatorMetrics(ids: HeapNodeIdSet, snapshot: IHeapSnapshot, checkNodeCb: (node: IHeapNode) => boolean, nodeMetricsCb: (node: IHeapNode) => number): number;
130
+ declare function getLeakTracePathLength(path: LeakTracePathItem): number;
131
+ declare function getNumberAtPercentile(arr: number[], percentile: number): number;
130
132
  declare const _default: {
131
133
  aggregateDominatorMetrics: typeof aggregateDominatorMetrics;
132
134
  applyToNodes: typeof applyToNodes;
@@ -149,7 +151,9 @@ declare const _default: {
149
151
  getEdgeByNameAndType: typeof getEdgeByNameAndType;
150
152
  getLastNodeId: typeof getLastNodeId;
151
153
  getLeakedNode: typeof getLeakedNode;
154
+ getLeakTracePathLength: typeof getLeakTracePathLength;
152
155
  getNodesIdSet: typeof getNodesIdSet;
156
+ getNumberAtPercentile: typeof getNumberAtPercentile;
153
157
  getNumberNodeValue: typeof getNumberNodeValue;
154
158
  getReadableBytes: typeof getReadableBytes;
155
159
  getReadablePercent: typeof getReadablePercent;
package/dist/lib/Utils.js CHANGED
@@ -44,7 +44,7 @@ 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.aggregateDominatorMetrics = exports.getRetainedSize = exports.runShell = exports.resolveSnapshotFilePath = void 0;
47
+ exports.runShell = exports.resolveSnapshotFilePath = void 0;
48
48
  const fs_1 = __importDefault(require("fs"));
49
49
  const path_1 = __importDefault(require("path"));
50
50
  const child_process_1 = __importDefault(require("child_process"));
@@ -1272,7 +1272,7 @@ function checkSnapshots(options = {}) {
1272
1272
  snapshotDir = Config_1.default.externalSnapshotDir || '<missing>';
1273
1273
  }
1274
1274
  else {
1275
- snapshotDir = FileManager_1.default.getCurDataDir({});
1275
+ snapshotDir = FileManager_1.default.getCurDataDir({ workDir: Config_1.default.workDir });
1276
1276
  }
1277
1277
  if (options.snapshotDir) {
1278
1278
  const snapshots = getSnapshotFilesInDir(snapshotDir);
@@ -1347,6 +1347,9 @@ function getSingleSnapshotFileForAnalysis() {
1347
1347
  return resolveSnapshotFilePath(path);
1348
1348
  }
1349
1349
  function getSnapshotFilePath(tab, options = {}) {
1350
+ if (tab.snapshotFile) {
1351
+ return path_1.default.join(FileManager_1.default.getCurDataDir(options), tab.snapshotFile);
1352
+ }
1350
1353
  const fileName = `s${tab.idx}.heapsnapshot`;
1351
1354
  if (options.workDir) {
1352
1355
  return path_1.default.join(FileManager_1.default.getCurDataDir(options), fileName);
@@ -1790,7 +1793,6 @@ exports.runShell = runShell;
1790
1793
  function getRetainedSize(node) {
1791
1794
  return node.retainedSize;
1792
1795
  }
1793
- exports.getRetainedSize = getRetainedSize;
1794
1796
  function aggregateDominatorMetrics(ids, snapshot, checkNodeCb, nodeMetricsCb) {
1795
1797
  let ret = 0;
1796
1798
  const dominators = __1.utils.getConditionalDominatorIds(ids, snapshot, checkNodeCb);
@@ -1799,7 +1801,29 @@ function aggregateDominatorMetrics(ids, snapshot, checkNodeCb, nodeMetricsCb) {
1799
1801
  });
1800
1802
  return ret;
1801
1803
  }
1802
- exports.aggregateDominatorMetrics = aggregateDominatorMetrics;
1804
+ function getLeakTracePathLength(path) {
1805
+ let len = 0;
1806
+ let p = path;
1807
+ while (p) {
1808
+ p = p.next;
1809
+ ++len;
1810
+ }
1811
+ return len;
1812
+ }
1813
+ function getNumberAtPercentile(arr, percentile) {
1814
+ arr.sort(function (a, b) {
1815
+ return a - b;
1816
+ });
1817
+ const index = (percentile / 100) * arr.length;
1818
+ const indexInt = Math.floor(index);
1819
+ if (indexInt === index) {
1820
+ return arr[Math.floor(index)];
1821
+ }
1822
+ if (indexInt + 1 < arr.length) {
1823
+ return (arr[indexInt] + arr[indexInt + 1]) / 2;
1824
+ }
1825
+ return arr[indexInt];
1826
+ }
1803
1827
  exports.default = {
1804
1828
  aggregateDominatorMetrics,
1805
1829
  applyToNodes,
@@ -1822,7 +1846,9 @@ exports.default = {
1822
1846
  getEdgeByNameAndType,
1823
1847
  getLastNodeId,
1824
1848
  getLeakedNode,
1849
+ getLeakTracePathLength,
1825
1850
  getNodesIdSet,
1851
+ getNumberAtPercentile,
1826
1852
  getNumberNodeValue,
1827
1853
  getReadableBytes,
1828
1854
  getReadablePercent,
@@ -30,6 +30,12 @@ class MemoryBarChart {
30
30
  Console_1.default.warning(`plot data not load correctly: ${Utils_1.default.getError(ex).message}`);
31
31
  return;
32
32
  }
33
+ if (plotData.length === 0) {
34
+ if (Config_1.default.verbose) {
35
+ Console_1.default.warning('no memory usage data to plot');
36
+ }
37
+ return;
38
+ }
33
39
  // normalize plot data
34
40
  const minY = 1;
35
41
  const maxY = plotData.reduce((m, v) => Math.max(m, v[1]), 0) * 1.15;
@@ -8,7 +8,7 @@
8
8
  * @lightSyntaxTransform
9
9
  * @oncall web_perf_infra
10
10
  */
11
- import type { IHeapEdge } from '../Types';
11
+ import type { AnyRecord, AnyValue, IHeapEdge } from '../Types';
12
12
  import type HeapSnapshot from './HeapSnapshot';
13
13
  import HeapNode from './HeapNode';
14
14
  export default class HeapEdge implements IHeapEdge {
@@ -23,5 +23,7 @@ export default class HeapEdge implements IHeapEdge {
23
23
  get to_node(): number;
24
24
  get toNode(): HeapNode;
25
25
  get fromNode(): HeapNode;
26
+ getJSONifyableObject(): AnyRecord;
27
+ toJSONString(...args: Array<AnyValue>): string;
26
28
  }
27
29
  //# sourceMappingURL=HeapEdge.d.ts.map
@@ -71,5 +71,18 @@ class HeapEdge {
71
71
  const srcNodeIdx = edgeIndex2SrcNodeIndex[this.idx];
72
72
  return new HeapNode_1.default(heapSnapshot, srcNodeIdx);
73
73
  }
74
+ getJSONifyableObject() {
75
+ return {
76
+ name_or_index: this.name_or_index,
77
+ type: this.type,
78
+ edgeIndex: this.edgeIndex,
79
+ toNode: this.toNode.getJSONifyableObject(),
80
+ fromNode: this.fromNode.getJSONifyableObject(),
81
+ };
82
+ }
83
+ toJSONString(...args) {
84
+ const rep = this.getJSONifyableObject();
85
+ return JSON.stringify(rep, ...args);
86
+ }
74
87
  }
75
88
  exports.default = HeapEdge;
@@ -8,7 +8,7 @@
8
8
  * @lightSyntaxTransform
9
9
  * @oncall web_perf_infra
10
10
  */
11
- import type { IHeapLocation, IHeapNode, Nullable } from '../Types';
11
+ import type { AnyRecord, AnyValue, IHeapLocation, IHeapNode, Nullable } from '../Types';
12
12
  import type HeapSnapshot from './HeapSnapshot';
13
13
  export default class HeapLocation implements IHeapLocation {
14
14
  private heapSnapshot;
@@ -19,5 +19,7 @@ export default class HeapLocation implements IHeapLocation {
19
19
  get script_id(): number;
20
20
  get line(): number;
21
21
  get column(): number;
22
+ getJSONifyableObject(): AnyRecord;
23
+ toJSONString(...args: Array<AnyValue>): string;
22
24
  }
23
25
  //# sourceMappingURL=HeapLocation.d.ts.map
@@ -43,5 +43,19 @@ class HeapLocation {
43
43
  const locationFieldsCount = heapSnapshot._locationFieldsCount;
44
44
  return locations[this.idx * locationFieldsCount + heapSnapshot._locationColumnOffset];
45
45
  }
46
+ getJSONifyableObject() {
47
+ const node = this.node;
48
+ const jsonNode = node == null ? null : node.getJSONifyableObject();
49
+ return {
50
+ node: jsonNode,
51
+ script_id: this.script_id,
52
+ line: this.line,
53
+ column: this.column,
54
+ };
55
+ }
56
+ toJSONString(...args) {
57
+ const rep = this.getJSONifyableObject();
58
+ return JSON.stringify(rep, ...args);
59
+ }
46
60
  }
47
61
  exports.default = HeapLocation;
@@ -8,7 +8,7 @@
8
8
  * @lightSyntaxTransform
9
9
  * @oncall web_perf_infra
10
10
  */
11
- import type { IHeapNode, IHeapEdge, Nullable, EdgeIterationCallback, Predicator, IHeapStringNode } from '../Types';
11
+ import type { IHeapNode, IHeapEdge, Nullable, EdgeIterationCallback, Predicator, IHeapStringNode, AnyRecord, AnyValue } from '../Types';
12
12
  import type HeapSnapshot from './HeapSnapshot';
13
13
  import HeapEdge from './HeapEdge';
14
14
  import HeapLocation from './HeapLocation';
@@ -33,8 +33,11 @@ export default class HeapNode implements IHeapNode {
33
33
  forEachReference(callback: EdgeIterationCallback): void;
34
34
  findAnyReference(predicate: Predicator<IHeapEdge>): Nullable<IHeapEdge>;
35
35
  findAnyReferrer(predicate: Predicator<IHeapEdge>): Nullable<IHeapEdge>;
36
+ findAnyReferrerNode(predicate: Predicator<IHeapNode>): Nullable<IHeapNode>;
36
37
  findReferrers(predicate: Predicator<IHeapEdge>): IHeapEdge[];
38
+ findReferrerNodes(predicate: Predicator<IHeapNode>): IHeapNode[];
37
39
  get referrers(): HeapEdge[];
40
+ get numOfReferrers(): number;
38
41
  forEachReferrer(callback: EdgeIterationCallback): void;
39
42
  get hasPathEdge(): boolean;
40
43
  get pathEdge(): Nullable<HeapEdge>;
@@ -53,5 +56,7 @@ export default class HeapNode implements IHeapNode {
53
56
  getReferrerNodes(edgeName: string | number, edgeType?: string): IHeapNode[];
54
57
  get isString(): boolean;
55
58
  toStringNode(): Nullable<IHeapStringNode>;
59
+ getJSONifyableObject(): AnyRecord;
60
+ toJSONString(...args: Array<AnyValue>): string;
56
61
  }
57
62
  //# sourceMappingURL=HeapNode.d.ts.map
@@ -151,6 +151,17 @@ class HeapNode {
151
151
  });
152
152
  return found;
153
153
  }
154
+ findAnyReferrerNode(predicate) {
155
+ let found = null;
156
+ this.forEachReferrer((edge) => {
157
+ const node = edge.fromNode;
158
+ if (predicate(node)) {
159
+ found = node;
160
+ return { stop: true };
161
+ }
162
+ });
163
+ return found;
164
+ }
154
165
  findReferrers(predicate) {
155
166
  const ret = [];
156
167
  this.forEachReferrer((edge) => {
@@ -161,6 +172,17 @@ class HeapNode {
161
172
  });
162
173
  return ret;
163
174
  }
175
+ findReferrerNodes(predicate) {
176
+ const ret = [];
177
+ this.forEachReferrer((edge) => {
178
+ const node = edge.fromNode;
179
+ if (predicate(node)) {
180
+ ret.push(node);
181
+ }
182
+ return null;
183
+ });
184
+ return ret;
185
+ }
164
186
  get referrers() {
165
187
  const heapSnapshot = this.heapSnapshot;
166
188
  const retainingEdgeIndex2EdgeIndex = heapSnapshot._retainingEdgeIndex2EdgeIndex;
@@ -174,6 +196,13 @@ class HeapNode {
174
196
  }
175
197
  return ret;
176
198
  }
199
+ get numOfReferrers() {
200
+ const heapSnapshot = this.heapSnapshot;
201
+ const firstRetainerIndex = heapSnapshot._firstRetainerIndex;
202
+ const beginIdx = firstRetainerIndex[this.idx];
203
+ const endIdx = firstRetainerIndex[this.idx + 1];
204
+ return endIdx - beginIdx;
205
+ }
177
206
  forEachReferrer(callback) {
178
207
  const heapSnapshot = this.heapSnapshot;
179
208
  const retainingEdgeIndex2EdgeIndex = heapSnapshot._retainingEdgeIndex2EdgeIndex;
@@ -342,6 +371,23 @@ class HeapNode {
342
371
  ? new HeapStringNode_1.default(this.heapSnapshot, this.idx)
343
372
  : null;
344
373
  }
374
+ getJSONifyableObject() {
375
+ return {
376
+ id: this.id,
377
+ name: this.name,
378
+ type: this.type,
379
+ self_size: this.self_size,
380
+ trace_node_id: this.trace_node_id,
381
+ nodeIndex: this.nodeIndex,
382
+ outGoingEdgeCount: this.edge_count,
383
+ incomingEdgeCount: this.numOfReferrers,
384
+ contructorName: this.constructor.name,
385
+ };
386
+ }
387
+ toJSONString(...args) {
388
+ const rep = this.getJSONifyableObject();
389
+ return JSON.stringify(rep, ...args);
390
+ }
345
391
  }
346
392
  exports.default = HeapNode;
347
393
  // HeapStringNode has to be imported after exporting HeapNode
@@ -8,7 +8,7 @@
8
8
  * @lightSyntaxTransform
9
9
  * @oncall web_perf_infra
10
10
  */
11
- import type { IHeapNode, IHeapNodes, IHeapEdges, IHeapSnapshot, HeapNodeTypes, HeapEdgeTypes, HeapSnapshotMeta, RawHeapSnapshot, NumericDictionary, Nullable } from '../Types';
11
+ import type { AnyRecord, AnyValue, IHeapNode, IHeapNodes, IHeapEdges, IHeapSnapshot, HeapNodeTypes, HeapEdgeTypes, HeapSnapshotMeta, RawHeapSnapshot, NumericDictionary, Nullable } from '../Types';
12
12
  import HeapNode from './HeapNode';
13
13
  export default class HeapSnapshot implements IHeapSnapshot {
14
14
  snapshot: RawHeapSnapshot;
@@ -67,6 +67,8 @@ export default class HeapSnapshot implements IHeapSnapshot {
67
67
  _firstRetainerIndex: Uint32Array;
68
68
  _edgeIndex2SrcNodeIndex: Uint32Array;
69
69
  constructor(snapshot: RawHeapSnapshot, _options?: Record<string, never>);
70
+ getJSONifyableObject(): AnyRecord;
71
+ toJSONString(...args: Array<AnyValue>): string;
70
72
  hasObjectWithClassName(className: string): boolean;
71
73
  getAnyObjectWithClassName(className: string): Nullable<IHeapNode>;
72
74
  hasObjectWithPropertyName(nameOrIndex: string | number): boolean;
@@ -129,6 +129,13 @@ class HeapSnapshot {
129
129
  },
130
130
  };
131
131
  }
132
+ getJSONifyableObject() {
133
+ return Object.assign({}, this.snapshot.snapshot);
134
+ }
135
+ toJSONString(...args) {
136
+ const rep = this.getJSONifyableObject();
137
+ return JSON.stringify(rep, ...args);
138
+ }
132
139
  hasObjectWithClassName(className) {
133
140
  let detected = false;
134
141
  this.nodes.forEach((node) => {
@@ -8,11 +8,12 @@
8
8
  * @lightSyntaxTransform
9
9
  * @oncall web_perf_infra
10
10
  */
11
- import type { IHeapStringNode } from '../Types';
11
+ import type { AnyRecord, IHeapStringNode } from '../Types';
12
12
  import type HeapSnapshot from './HeapSnapshot';
13
13
  import HeapNode from './HeapNode';
14
14
  export default class HeapStringNode extends HeapNode implements IHeapStringNode {
15
15
  constructor(heapSnapshot: HeapSnapshot, idx: number);
16
16
  get stringValue(): string;
17
+ getJSONifyableObject(): AnyRecord;
17
18
  }
18
19
  //# sourceMappingURL=HeapStringNode.d.ts.map
@@ -41,5 +41,10 @@ class HeapStringNode extends HeapNode_1.default {
41
41
  }
42
42
  return this.name;
43
43
  }
44
+ getJSONifyableObject() {
45
+ const rep = super.getJSONifyableObject();
46
+ rep.stringValue = this.stringValue;
47
+ return rep;
48
+ }
44
49
  }
45
50
  exports.default = HeapStringNode;
@@ -24,6 +24,7 @@ export default class NormalizedTrace {
24
24
  getTraceSummary(): string;
25
25
  static addLeakedNodeToCluster(cluster: TraceCluster, path: LeakTracePathItem): void;
26
26
  static calculateClusterRetainedSize(cluster: TraceCluster, snapshot: IHeapSnapshot, aggregateDominatorMetrics: AggregateNodeCb): number;
27
+ static getSamplePathMaxLength(paths: LeakTracePathItem[]): number;
27
28
  static samplePaths(paths: LeakTracePathItem[]): LeakTracePathItem[];
28
29
  private static diffTraces;
29
30
  static diffClusters(newClusters: TraceCluster[], existingClusters: TraceCluster[]): TraceClusterDiff;
@@ -122,16 +122,30 @@ class NormalizedTrace {
122
122
  }
123
123
  return (cluster.retainedSize = aggregateDominatorMetrics(cluster.leakedNodeIds, snapshot, () => true, (node) => node.retainedSize));
124
124
  }
125
+ static getSamplePathMaxLength(paths) {
126
+ const lengthArr = paths.map(p => Utils_1.default.getLeakTracePathLength(p));
127
+ return Math.max(30, Utils_1.default.getNumberAtPercentile(lengthArr, 80));
128
+ }
125
129
  static samplePaths(paths) {
126
130
  const maxCount = 5000;
131
+ if (paths.length <= maxCount) {
132
+ return [...paths];
133
+ }
127
134
  const sampleRatio = Math.min(1, maxCount / paths.length);
128
135
  if (sampleRatio < 1) {
129
136
  Console_1.default.warning('Sampling trace due to a large number of traces:');
130
137
  Console_1.default.lowLevel(` Number of Traces: ${paths.length}`);
131
- Console_1.default.lowLevel(` Sampling Ratio: ${Utils_1.default.getReadablePercent(sampleRatio)}`);
138
+ Console_1.default.lowLevel(` Sampling Ratio: ${Utils_1.default.getReadablePercent(sampleRatio)}`);
132
139
  }
133
140
  const ret = [];
141
+ const samplePathMaxLength = NormalizedTrace.getSamplePathMaxLength(paths);
142
+ if (Config_1.default.verbose) {
143
+ Console_1.default.lowLevel(` Sample Trace's Max Length: ${samplePathMaxLength}`);
144
+ }
134
145
  for (const p of paths) {
146
+ if (Utils_1.default.getLeakTracePathLength(p) > samplePathMaxLength) {
147
+ continue;
148
+ }
135
149
  if (Math.random() < sampleRatio) {
136
150
  ret.push(p);
137
151
  }
@@ -7,7 +7,7 @@
7
7
  * @format
8
8
  * @oncall web_perf_infra
9
9
  */
10
- import type { EdgeIterationCallback, IHeapEdge, IHeapLocation, IHeapNode, IHeapSnapshot, IHeapStringNode, Nullable } from '../lib/Types';
10
+ import type { AnyValue, EdgeIterationCallback, IHeapEdge, IHeapLocation, IHeapNode, IHeapSnapshot, IHeapStringNode, Nullable } from '../lib/Types';
11
11
  export declare class NodeRecord implements IHeapNode {
12
12
  kind: string;
13
13
  name: string;
@@ -32,11 +32,14 @@ export declare class NodeRecord implements IHeapNode {
32
32
  forEachReference(_callback: EdgeIterationCallback): void;
33
33
  set referrers(r: IHeapEdge[]);
34
34
  get referrers(): IHeapEdge[];
35
+ get numOfReferrers(): number;
35
36
  toStringNode(): IHeapStringNode;
36
37
  forEachReferrer(_callback: EdgeIterationCallback): void;
37
38
  findAnyReference(): Nullable<IHeapEdge>;
38
39
  findAnyReferrer(): Nullable<IHeapEdge>;
40
+ findAnyReferrerNode(): Nullable<IHeapNode>;
39
41
  findReferrers(): IHeapEdge[];
42
+ findReferrerNodes(): IHeapNode[];
40
43
  set hasPathEdge(f: boolean);
41
44
  get hasPathEdge(): boolean;
42
45
  set pathEdge(r: IHeapEdge);
@@ -51,6 +54,7 @@ export declare class NodeRecord implements IHeapNode {
51
54
  getAnyReferrerNode(_edgeName: string | number, _edgeType?: string): Nullable<IHeapNode>;
52
55
  getReferrers(_edgeName: string | number, _edgeType?: string): IHeapEdge[];
53
56
  getReferrerNodes(_edgeName: string | number, _edgeType?: string): IHeapNode[];
57
+ toJSONString(...args: Array<AnyValue>): string;
54
58
  constructor(node: IHeapNode);
55
59
  private extraceNodeName;
56
60
  }
@@ -62,6 +66,7 @@ export declare class EdgeRecord implements IHeapEdge {
62
66
  is_index: boolean;
63
67
  to_node: number;
64
68
  constructor(edge: IHeapEdge);
69
+ toJSONString(...args: Array<AnyValue>): string;
65
70
  set snapshot(s: IHeapSnapshot);
66
71
  get snapshot(): IHeapSnapshot;
67
72
  set toNode(s: IHeapNode);
@@ -62,6 +62,9 @@ class NodeRecord {
62
62
  get referrers() {
63
63
  throw new Error('NodeRecord.referrers cannot be read');
64
64
  }
65
+ get numOfReferrers() {
66
+ throw new Error('NodeRecord.numOfReferrers cannot be read');
67
+ }
65
68
  toStringNode() {
66
69
  throw new Error('NodeRecord.toStringNode is not implemented');
67
70
  }
@@ -76,9 +79,15 @@ class NodeRecord {
76
79
  findAnyReferrer() {
77
80
  throw new Error('NodeRecord.findAnyReferrer is not implemented');
78
81
  }
82
+ findAnyReferrerNode() {
83
+ throw new Error('NodeRecord.findAnyReferrerNode is not implemented');
84
+ }
79
85
  findReferrers() {
80
86
  throw new Error('NodeRecord.findReferrers is not implemented');
81
87
  }
88
+ findReferrerNodes() {
89
+ throw new Error('NodeRecord.findReferrerNodes is not implemented');
90
+ }
82
91
  set hasPathEdge(f) {
83
92
  throw new Error('NodeRecord.hasPathEdge cannot be assigned');
84
93
  }
@@ -145,6 +154,20 @@ class NodeRecord {
145
154
  _edgeType) {
146
155
  throw new Error('NodeRecord.getReferrerNodes is not implemented');
147
156
  }
157
+ toJSONString(...args) {
158
+ const rep = {
159
+ id: this.id,
160
+ kind: this.kind,
161
+ name: this.name,
162
+ type: this.type,
163
+ self_size: this.self_size,
164
+ trace_node_id: this.trace_node_id,
165
+ nodeIndex: this.nodeIndex,
166
+ incomingEdgeCount: this.numOfReferrers,
167
+ contructorName: this.constructor.name,
168
+ };
169
+ return JSON.stringify(rep, ...args);
170
+ }
148
171
  extraceNodeName(node) {
149
172
  // deserialized node may not have snapshot info
150
173
  if (!node.snapshot || !Utils_1.default.isFiberNode(node)) {
@@ -163,6 +186,16 @@ class EdgeRecord {
163
186
  this.is_index = edge.is_index;
164
187
  this.to_node = edge.to_node;
165
188
  }
189
+ toJSONString(...args) {
190
+ const rep = {
191
+ kind: this.kind,
192
+ name_or_index: this.name_or_index,
193
+ type: this.type,
194
+ edgeIndex: this.edgeIndex,
195
+ to_node: this.to_node,
196
+ };
197
+ return JSON.stringify(rep, ...args);
198
+ }
166
199
  set snapshot(s) {
167
200
  throw new Error('EdgeRecord.snapshot cannot be assigned.');
168
201
  }
@@ -12,6 +12,8 @@ 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
+ const Config_1 = __importDefault(require("../../lib/Config"));
16
+ const Console_1 = __importDefault(require("../../lib/Console"));
15
17
  const ClusterUtils_1 = __importDefault(require("../ClusterUtils"));
16
18
  // cluster by putting similar traces together
17
19
  class TraceSimilarityStrategy {
@@ -41,6 +43,13 @@ class TraceSimilarityStrategy {
41
43
  // checking new clusters
42
44
  outer: for (let i = 0; i < newTraces.length; ++i) {
43
45
  const traceToCheck = newTraces[i];
46
+ // use an odd number as the divider. If we choose 10 as the divider,
47
+ // when updating the progress indicator, the final digit always ends
48
+ // with a zero, which can appear strange and not representative of
49
+ // the actual progress.
50
+ if (!Config_1.default.isContinuousTest && i % 17 === 0) {
51
+ Console_1.default.overwrite(`clustering trace: ${i} / ${newTraces.length}`);
52
+ }
44
53
  for (let j = 0; j < clusters.length; ++j) {
45
54
  const repTrace = clusters[j][0];
46
55
  if (TraceSimilarityStrategy.isSimilarTrace(repTrace, traceToCheck)) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@memlab/core",
3
- "version": "1.1.19",
3
+ "version": "1.1.20",
4
4
  "license": "MIT",
5
5
  "description": "memlab core libraries",
6
6
  "author": "Liang Gong <lgong@fb.com>",
@@ -0,0 +1,19 @@
1
+ [
2
+ {
3
+ "name": "target",
4
+ "url": "",
5
+ "snapshot": true,
6
+ "screenshot": false,
7
+ "type": "target",
8
+ "idx": 1
9
+ },
10
+ {
11
+ "name": "final",
12
+ "url": "",
13
+ "delay": 10000,
14
+ "snapshot": true,
15
+ "screenshot": false,
16
+ "type": "final",
17
+ "idx": 2
18
+ }
19
+ ]