@memlab/core 1.1.19 → 1.1.21

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (55) hide show
  1. package/dist/__tests__/parser/StringNode.test.js +12 -1
  2. package/dist/index.d.ts +2 -0
  3. package/dist/index.js +4 -1
  4. package/dist/lib/Config.d.ts +4 -0
  5. package/dist/lib/Config.js +13 -0
  6. package/dist/lib/FileManager.d.ts +6 -0
  7. package/dist/lib/FileManager.js +56 -4
  8. package/dist/lib/HeapAnalyzer.d.ts +5 -1
  9. package/dist/lib/HeapAnalyzer.js +33 -21
  10. package/dist/lib/RunInfoUtils.d.ts +39 -0
  11. package/dist/lib/RunInfoUtils.js +86 -0
  12. package/dist/lib/Types.d.ts +80 -2
  13. package/dist/lib/Utils.d.ts +30 -20
  14. package/dist/lib/Utils.js +119 -151
  15. package/dist/lib/charts/MemoryBarChart.d.ts +1 -0
  16. package/dist/lib/charts/MemoryBarChart.js +23 -2
  17. package/dist/lib/heap-data/HeapEdge.d.ts +3 -1
  18. package/dist/lib/heap-data/HeapEdge.js +13 -0
  19. package/dist/lib/heap-data/HeapLocation.d.ts +3 -1
  20. package/dist/lib/heap-data/HeapLocation.js +14 -0
  21. package/dist/lib/heap-data/HeapNode.d.ts +6 -1
  22. package/dist/lib/heap-data/HeapNode.js +46 -0
  23. package/dist/lib/heap-data/HeapSnapshot.d.ts +3 -1
  24. package/dist/lib/heap-data/HeapSnapshot.js +7 -0
  25. package/dist/lib/heap-data/HeapStringNode.d.ts +2 -1
  26. package/dist/lib/heap-data/HeapStringNode.js +5 -0
  27. package/dist/lib/trace-filters/BaseTraceFilter.rule.d.ts +29 -0
  28. package/dist/lib/trace-filters/BaseTraceFilter.rule.js +22 -0
  29. package/dist/lib/trace-filters/LeakTraceFilter.d.ts +20 -0
  30. package/dist/lib/trace-filters/LeakTraceFilter.js +37 -0
  31. package/dist/lib/trace-filters/TraceFilterRuleList.d.ts +13 -0
  32. package/dist/lib/trace-filters/TraceFilterRuleList.js +33 -0
  33. package/dist/lib/trace-filters/rules/FilterAttachedDOMToDetachedDOMTrace.rule.d.ts +15 -0
  34. package/dist/lib/trace-filters/rules/FilterAttachedDOMToDetachedDOMTrace.rule.js +55 -0
  35. package/dist/lib/trace-filters/rules/FilterDOMNodeChainTrace.rule.d.ts +15 -0
  36. package/dist/lib/trace-filters/rules/FilterDOMNodeChainTrace.rule.js +41 -0
  37. package/dist/lib/trace-filters/rules/FilterHermesTrace.rule.d.ts +15 -0
  38. package/dist/lib/trace-filters/rules/FilterHermesTrace.rule.js +29 -0
  39. package/dist/lib/trace-filters/rules/FilterInternalNodeTrace.rule.d.ts +15 -0
  40. package/dist/lib/trace-filters/rules/FilterInternalNodeTrace.rule.js +57 -0
  41. package/dist/lib/trace-filters/rules/FilterPendingActivitiesTrace.rule.d.ts +15 -0
  42. package/dist/lib/trace-filters/rules/FilterPendingActivitiesTrace.rule.js +62 -0
  43. package/dist/lib/trace-filters/rules/FilterShadowRootTrace.rule.d.ts +15 -0
  44. package/dist/lib/trace-filters/rules/FilterShadowRootTrace.rule.js +44 -0
  45. package/dist/lib/trace-filters/rules/FilterStyleEngineTrace.rule.d.ts +15 -0
  46. package/dist/lib/trace-filters/rules/FilterStyleEngineTrace.rule.js +49 -0
  47. package/dist/logger/LeakClusterLogger.js +1 -0
  48. package/dist/paths/TraceFinder.js +16 -2
  49. package/dist/trace-cluster/TraceBucket.d.ts +2 -1
  50. package/dist/trace-cluster/TraceBucket.js +22 -4
  51. package/dist/trace-cluster/TraceElement.d.ts +6 -1
  52. package/dist/trace-cluster/TraceElement.js +33 -0
  53. package/dist/trace-cluster/strategies/TraceSimilarityStrategy.js +10 -0
  54. package/package.json +1 -1
  55. package/static/visit-order-single-snapshot.json +19 -0
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"));
@@ -53,7 +53,6 @@ const Config_1 = __importStar(require("./Config"));
53
53
  const Console_1 = __importDefault(require("./Console"));
54
54
  const Constant_1 = __importDefault(require("./Constant"));
55
55
  const HeapParser_1 = __importDefault(require("./HeapParser"));
56
- const BrowserInfo_1 = __importDefault(require("./BrowserInfo"));
57
56
  const memCache = Object.create(null);
58
57
  const FileManager_1 = __importDefault(require("./FileManager"));
59
58
  const __1 = require("..");
@@ -133,6 +132,21 @@ function isFiberNode(node) {
133
132
  function isDetachedFiberNode(node) {
134
133
  return isFiberNode(node) && isDetached(node);
135
134
  }
135
+ // return true if this node is InternalNode (native)
136
+ function isDOMInternalNode(node) {
137
+ if (node == null) {
138
+ return false;
139
+ }
140
+ return (node.type === 'native' &&
141
+ (node.name === 'InternalNode' || node.name === 'Detached InternalNode'));
142
+ }
143
+ // return true the the nodee is a global handles node
144
+ function isGlobalHandlesNode(node) {
145
+ if (node == null) {
146
+ return false;
147
+ }
148
+ return node.name === '(Global handles)' && node.type === 'synthetic';
149
+ }
136
150
  // this function returns a more general sense of DOM nodes. Specifically,
137
151
  // any detached DOM nodes (e.g., HTMLXXElement, IntersectionObserver etc.)
138
152
  // that are not internal nodes.
@@ -211,18 +225,51 @@ function isPendingActivityNode(node) {
211
225
  if (!node || !node.name) {
212
226
  return false;
213
227
  }
214
- return node.type === 'synthetic' && node.name === 'Pending activities';
228
+ if (node.type !== 'synthetic' && node.type !== 'native') {
229
+ return false;
230
+ }
231
+ return node.name === 'Pending activities';
215
232
  }
233
+ const htmlElementRegex = /^HTML.*Element$/;
234
+ const svgElementRegex = /^SVG.*Element$/;
235
+ const htmlCollectionRegex = /^HTML.*Collection$/;
236
+ const cssElementRegex = /^CSS/;
237
+ const styleSheetRegex = /StyleSheet/;
238
+ // special DOM element names that are not
239
+ // included in the previous regex definitions
240
+ const domElementSpecialNames = new Set([
241
+ 'DOMTokenList',
242
+ 'HTMLDocument',
243
+ 'InternalNode',
244
+ 'Text',
245
+ 'XMLDocument',
246
+ ]);
216
247
  // check the node against a curated list of known HTML Elements
217
248
  // the list may be incomplete
218
249
  function isDOMNodeIncomplete(node) {
250
+ if (node.type !== 'native') {
251
+ return false;
252
+ }
219
253
  let name = node.name;
220
- const pattern = /^HTML.*Element$/;
221
254
  const detachedPrefix = 'Detached ';
222
255
  if (name.startsWith(detachedPrefix)) {
223
256
  name = name.substring(detachedPrefix.length);
224
257
  }
225
- return pattern.test(name);
258
+ return (htmlElementRegex.test(name) ||
259
+ svgElementRegex.test(name) ||
260
+ cssElementRegex.test(name) ||
261
+ styleSheetRegex.test(name) ||
262
+ htmlCollectionRegex.test(name) ||
263
+ domElementSpecialNames.has(name));
264
+ }
265
+ function isXMLDocumentNode(node) {
266
+ return node.type === 'native' && node.name === 'XMLDocument';
267
+ }
268
+ function isHTMLDocumentNode(node) {
269
+ return node.type === 'native' && node.name === 'HTMLDocument';
270
+ }
271
+ function isDOMTextNode(node) {
272
+ return node.type === 'native' && node.name === 'Text';
226
273
  }
227
274
  function isRootNode(node, opt = {}) {
228
275
  if (!node) {
@@ -471,7 +518,9 @@ function applyToNodes(idSet, snapshot, cb, options = {}) {
471
518
  for (const id of ids) {
472
519
  const node = snapshot.getNodeById(id);
473
520
  if (!node) {
474
- Console_1.default.warning(`node @${id} is not found`);
521
+ if (Config_1.default.verbose) {
522
+ Console_1.default.warning(`node @${id} is not found`);
523
+ }
475
524
  return;
476
525
  }
477
526
  cb(node, snapshot);
@@ -831,27 +880,6 @@ function hasOnlyWeakReferrers(node) {
831
880
  (edge) => edge.type !== 'weak' && edge.type !== 'shortcut');
832
881
  return referrer == null;
833
882
  }
834
- function getRunMetaFilePath() {
835
- return Config_1.default.useExternalSnapshot
836
- ? Config_1.default.externalRunMetaFile
837
- : Config_1.default.runMetaFile;
838
- }
839
- function loadRunMetaInfo(metaFile = undefined) {
840
- const file = metaFile || getRunMetaFilePath();
841
- try {
842
- const content = fs_1.default.readFileSync(file, 'UTF-8');
843
- return JSON.parse(content);
844
- }
845
- catch (_) {
846
- throw haltOrThrow('Run info missing. Please make sure `memlab run` is complete.');
847
- }
848
- }
849
- function loadTargetInfoFromRunMeta() {
850
- const meta = loadRunMetaInfo();
851
- Config_1.default.targetApp = meta.app;
852
- Config_1.default.targetTab = meta.interaction;
853
- BrowserInfo_1.default.load(meta.browserInfo);
854
- }
855
883
  function getSnapshotSequenceFilePath() {
856
884
  if (!Config_1.default.useExternalSnapshot) {
857
885
  // load the snapshot sequence meta file from the default location
@@ -870,7 +898,9 @@ function getSnapshotSequenceFilePath() {
870
898
  // this should be called only after exploration
871
899
  function loadTabsOrder(metaFile = undefined) {
872
900
  try {
873
- const file = metaFile || getSnapshotSequenceFilePath();
901
+ const file = metaFile != null && fs_1.default.existsSync(metaFile)
902
+ ? metaFile
903
+ : getSnapshotSequenceFilePath();
874
904
  const content = fs_1.default.readFileSync(file, 'UTF-8');
875
905
  return JSON.parse(content);
876
906
  }
@@ -878,31 +908,6 @@ function loadTabsOrder(metaFile = undefined) {
878
908
  throw haltOrThrow('snapshot meta data invalid or missing');
879
909
  }
880
910
  }
881
- // if true the leak trace is will be reported
882
- function isInterestingPath(p) {
883
- // do not filter paths when analyzing Hermes snapshots
884
- if (Config_1.default.jsEngine === 'hermes') {
885
- return true;
886
- }
887
- // if the path has pattern: Window -> [InternalNode]+ -> DetachedElement
888
- if (Config_1.default.hideBrowserLeak && internalNodeRetainsDetachedElement(p)) {
889
- return false;
890
- }
891
- // if the path has pattern: ShadowRoot -> DetachedElement
892
- if (Config_1.default.hideBrowserLeak && shadowRootRetainsDetachedElement(p)) {
893
- return false;
894
- }
895
- // if the path has pattern: StyleEngine -> InternalNode -> DetachedElement
896
- if (Config_1.default.hideBrowserLeak && styleEngineRetainsDetachedElement(p)) {
897
- return false;
898
- }
899
- // if the path has pattern: Pending activitiies -> DetachedElement
900
- if (Config_1.default.hideBrowserLeak &&
901
- pendingActivitiesRetainsDetachedElementChain(p)) {
902
- return false;
903
- }
904
- return true;
905
- }
906
911
  // return true if the heap node represents JS object or closure
907
912
  function isObjectNode(node) {
908
913
  if (isPlainJSObjectNode(node)) {
@@ -920,89 +925,6 @@ function isPlainJSObjectNode(node) {
920
925
  }
921
926
  return node.name === 'Object';
922
927
  }
923
- // check if the path has pattern:
924
- // Window -> [InternalNode | Text]+ -> DetachedElement
925
- function internalNodeRetainsDetachedElement(path) {
926
- var _a, _b;
927
- if (!path) {
928
- return false;
929
- }
930
- let p = path;
931
- // GC root is not Window
932
- if (!p.node || !p.node.name.startsWith('Window')) {
933
- return false;
934
- }
935
- p = p.next;
936
- // Window is not poining to InternalNode
937
- if (!p || !p.node || p.node.name !== 'InternalNode') {
938
- return false;
939
- }
940
- // skip the rest InternalNode
941
- while (((_a = p.node) === null || _a === void 0 ? void 0 : _a.name) === 'InternalNode' || ((_b = p.node) === null || _b === void 0 ? void 0 : _b.name) === 'Text') {
942
- p = p.next;
943
- if (!p) {
944
- return false;
945
- }
946
- }
947
- // check if the node is a detached element
948
- return p && isDetachedDOMNode(p.node);
949
- }
950
- // check if the path has pattern: ShadowRoot -> DetachedElement
951
- function shadowRootRetainsDetachedElement(path) {
952
- let p = path;
953
- // find the ShadowRoot
954
- while (p && p.node && p.node.name !== 'ShadowRoot') {
955
- p = p.next;
956
- if (!p) {
957
- return false;
958
- }
959
- }
960
- p = p.next;
961
- // check if the node is a detached element
962
- return !!p && isDetachedDOMNode(p.node);
963
- }
964
- // check if the path has pattern: StyleEngine -> InternalNode -> DetachedElement
965
- function styleEngineRetainsDetachedElement(path) {
966
- let p = path;
967
- // find the StyleEngine
968
- while (p && p.node && p.node.name !== 'StyleEngine') {
969
- p = p.next;
970
- if (!p) {
971
- return false;
972
- }
973
- }
974
- p = p.next;
975
- // StyleEngine is not poining to InternalNode
976
- if (!p || !p.node || p.node.name !== 'InternalNode') {
977
- return false;
978
- }
979
- p = p.next;
980
- // check if the InternalNode is pointing to a detached element
981
- return !!p && isDetachedDOMNode(p.node);
982
- }
983
- function pendingActivitiesRetainsDetachedElementChain(path) {
984
- let p = path;
985
- // find the Pending activities
986
- while (p && p.node && !isPendingActivityNode(p.node)) {
987
- p = p.next;
988
- if (!p) {
989
- return false;
990
- }
991
- }
992
- p = p.next;
993
- if (!p || !p.node) {
994
- return false;
995
- }
996
- // all the following reference chain is detached DOM elements
997
- // pointing to other detached DOM elements
998
- while (p && p.node) {
999
- if (!isDetachedDOMNode(p.node)) {
1000
- return false;
1001
- }
1002
- p = p.next;
1003
- }
1004
- return true;
1005
- }
1006
928
  function pathHasDetachedHTMLNode(path) {
1007
929
  if (!path) {
1008
930
  return false;
@@ -1272,7 +1194,7 @@ function checkSnapshots(options = {}) {
1272
1194
  snapshotDir = Config_1.default.externalSnapshotDir || '<missing>';
1273
1195
  }
1274
1196
  else {
1275
- snapshotDir = FileManager_1.default.getCurDataDir({});
1197
+ snapshotDir = FileManager_1.default.getCurDataDir({ workDir: Config_1.default.workDir });
1276
1198
  }
1277
1199
  if (options.snapshotDir) {
1278
1200
  const snapshots = getSnapshotFilesInDir(snapshotDir);
@@ -1347,6 +1269,9 @@ function getSingleSnapshotFileForAnalysis() {
1347
1269
  return resolveSnapshotFilePath(path);
1348
1270
  }
1349
1271
  function getSnapshotFilePath(tab, options = {}) {
1272
+ if (tab.snapshotFile) {
1273
+ return path_1.default.join(FileManager_1.default.getCurDataDir(options), tab.snapshotFile);
1274
+ }
1350
1275
  const fileName = `s${tab.idx}.heapsnapshot`;
1351
1276
  if (options.workDir) {
1352
1277
  return path_1.default.join(FileManager_1.default.getCurDataDir(options), fileName);
@@ -1779,6 +1704,9 @@ function runShell(command, options = {}) {
1779
1704
  Console_1.default.lowLevel((_c = ex.stack) !== null && _c !== void 0 ? _c : '');
1780
1705
  }
1781
1706
  }
1707
+ if (options.throwError) {
1708
+ throw ex;
1709
+ }
1782
1710
  if (options.ignoreError === true) {
1783
1711
  return '';
1784
1712
  }
@@ -1790,7 +1718,6 @@ exports.runShell = runShell;
1790
1718
  function getRetainedSize(node) {
1791
1719
  return node.retainedSize;
1792
1720
  }
1793
- exports.getRetainedSize = getRetainedSize;
1794
1721
  function aggregateDominatorMetrics(ids, snapshot, checkNodeCb, nodeMetricsCb) {
1795
1722
  let ret = 0;
1796
1723
  const dominators = __1.utils.getConditionalDominatorIds(ids, snapshot, checkNodeCb);
@@ -1799,15 +1726,51 @@ function aggregateDominatorMetrics(ids, snapshot, checkNodeCb, nodeMetricsCb) {
1799
1726
  });
1800
1727
  return ret;
1801
1728
  }
1802
- exports.aggregateDominatorMetrics = aggregateDominatorMetrics;
1729
+ function getLeakTracePathLength(path) {
1730
+ let len = 0;
1731
+ let p = path;
1732
+ while (p) {
1733
+ p = p.next;
1734
+ ++len;
1735
+ }
1736
+ return len;
1737
+ }
1738
+ function getNumberAtPercentile(arr, percentile) {
1739
+ arr.sort(function (a, b) {
1740
+ return a - b;
1741
+ });
1742
+ const index = (percentile / 100) * arr.length;
1743
+ const indexInt = Math.floor(index);
1744
+ if (indexInt === index) {
1745
+ return arr[Math.floor(index)];
1746
+ }
1747
+ if (indexInt + 1 < arr.length) {
1748
+ return (arr[indexInt] + arr[indexInt + 1]) / 2;
1749
+ }
1750
+ return arr[indexInt];
1751
+ }
1752
+ function mapToObject(map) {
1753
+ const ret = Object.create(null);
1754
+ map.forEach((v, k) => {
1755
+ ret[k] = v;
1756
+ });
1757
+ return ret;
1758
+ }
1759
+ function objectToMap(object) {
1760
+ const ret = new Map();
1761
+ for (const k of Object.keys(object)) {
1762
+ ret.set(k, object[k]);
1763
+ }
1764
+ return ret;
1765
+ }
1803
1766
  exports.default = {
1804
1767
  aggregateDominatorMetrics,
1805
1768
  applyToNodes,
1806
1769
  callAsync,
1807
1770
  camelCaseToReadableString,
1771
+ checkIsChildOfParent,
1808
1772
  checkSnapshots,
1809
1773
  checkUninstalledLibrary,
1810
- checkIsChildOfParent,
1811
1774
  closePuppeteer,
1812
1775
  dumpSnapshot,
1813
1776
  equalOrMatch,
@@ -1816,29 +1779,31 @@ exports.default = {
1816
1779
  extractHTMLElementNodeInfo,
1817
1780
  filterNodesInPlace,
1818
1781
  getAllDominators,
1782
+ getBooleanNodeValue,
1819
1783
  getClosureSourceUrl,
1820
1784
  getConditionalDominatorIds,
1821
- getError,
1822
1785
  getEdgeByNameAndType,
1786
+ getError,
1823
1787
  getLastNodeId,
1788
+ getLeakTracePathLength,
1824
1789
  getLeakedNode,
1825
1790
  getNodesIdSet,
1791
+ getNumberAtPercentile,
1826
1792
  getNumberNodeValue,
1827
1793
  getReadableBytes,
1828
1794
  getReadablePercent,
1829
1795
  getReadableTime,
1830
1796
  getRetainedSize,
1831
- getRunMetaFilePath,
1832
1797
  getScenarioName,
1833
1798
  getSingleSnapshotFileForAnalysis,
1834
1799
  getSnapshotDirForAnalysis,
1835
- getSnapshotFilesInDir,
1800
+ getSnapshotFilePath,
1801
+ getSnapshotFilePathWithTabType,
1836
1802
  getSnapshotFilesFromTabsOrder,
1803
+ getSnapshotFilesInDir,
1837
1804
  getSnapshotFromFile,
1838
1805
  getSnapshotNodeIdsFromFile,
1839
1806
  getSnapshotSequenceFilePath,
1840
- getSnapshotFilePath,
1841
- getSnapshotFilePathWithTabType,
1842
1807
  getStringNodeValue,
1843
1808
  getToNodeByEdge,
1844
1809
  getUniqueID,
@@ -1849,18 +1814,21 @@ exports.default = {
1849
1814
  hasReactEdges,
1850
1815
  isAlternateNode,
1851
1816
  isBlinkRootNode,
1817
+ isDOMInternalNode,
1818
+ isDOMNodeIncomplete,
1819
+ isDOMTextNode,
1852
1820
  isDebuggableNode,
1853
- isDetachedFiberNode,
1854
1821
  isDetachedDOMNode,
1822
+ isDetachedFiberNode,
1855
1823
  isDirectPropEdge,
1856
1824
  isDocumentDOMTreesRoot,
1857
- isDOMNodeIncomplete,
1858
1825
  isEssentialEdge,
1859
1826
  isFiberNode,
1860
1827
  isFiberNodeDeletionsEdge,
1828
+ isGlobalHandlesNode,
1829
+ isHTMLDocumentNode,
1861
1830
  isHermesInternalObject,
1862
1831
  isHostRoot,
1863
- isInterestingPath,
1864
1832
  isMeaningfulEdge,
1865
1833
  isMeaningfulNode,
1866
1834
  isNodeDominatedByDeletionsArray,
@@ -1879,17 +1847,18 @@ exports.default = {
1879
1847
  isWeakMapEdge,
1880
1848
  isWeakMapEdgeToKey,
1881
1849
  isWeakMapEdgeToValue,
1850
+ isXMLDocumentNode,
1882
1851
  iterateChildFiberNodes,
1883
1852
  iterateDescendantFiberNodes,
1884
- loadRunMetaInfo,
1885
1853
  loadLeakFilter,
1886
1854
  loadScenario,
1887
1855
  loadTabsOrder,
1888
- loadTargetInfoFromRunMeta,
1856
+ mapToObject,
1889
1857
  markAllDetachedFiberNode,
1890
1858
  markAlternateFiberNode,
1891
1859
  memCache,
1892
1860
  normalizeBaseUrl,
1861
+ objectToMap,
1893
1862
  pathHasDetachedHTMLNode,
1894
1863
  pathHasEdgeWithIndex,
1895
1864
  repeat,
@@ -1902,5 +1871,4 @@ exports.default = {
1902
1871
  shuffleArray,
1903
1872
  throwError,
1904
1873
  upperCaseFirstCharacter,
1905
- getBooleanNodeValue,
1906
1874
  };
@@ -10,6 +10,7 @@
10
10
  import type { PlotMemoryOptions } from '../Types';
11
11
  declare class MemoryBarChart {
12
12
  plotMemoryBarChart(options?: PlotMemoryOptions): void;
13
+ private isPlotDataValid;
13
14
  private loadPlotDataFromTabsOrder;
14
15
  private loadPlotDataFromWorkDir;
15
16
  private loadPlotData;
@@ -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 (!this.isPlotDataValid(plotData)) {
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;
@@ -51,6 +57,21 @@ class MemoryBarChart {
51
57
  }));
52
58
  Console_1.default.topLevel('');
53
59
  }
60
+ isPlotDataValid(plotData) {
61
+ if (plotData.length === 0) {
62
+ return false;
63
+ }
64
+ let isEntryValueAllZero = true;
65
+ for (const entry of plotData) {
66
+ if (entry.length !== 2) {
67
+ return false;
68
+ }
69
+ if (entry[1] !== 0) {
70
+ isEntryValueAllZero = false;
71
+ }
72
+ }
73
+ return !isEntryValueAllZero;
74
+ }
54
75
  loadPlotDataFromTabsOrder(tabsOrder) {
55
76
  for (const tab of tabsOrder) {
56
77
  if (!(tab.JSHeapUsedSize > 0)) {
@@ -76,12 +97,12 @@ class MemoryBarChart {
76
97
  }
77
98
  loadPlotData(options = {}) {
78
99
  // plot data for a single run
79
- if (!options.controlWorkDir && !options.treatmentWorkDir) {
100
+ if (!options.controlWorkDirs && !options.treatmentWorkDir) {
80
101
  return this.loadPlotDataFromWorkDir(options);
81
102
  }
82
103
  // plot data for control and test run
83
104
  const controlPlotData = this.loadPlotDataFromWorkDir({
84
- workDir: options.controlWorkDir,
105
+ workDir: options.controlWorkDirs && options.controlWorkDirs[0],
85
106
  });
86
107
  const testPlotData = this.loadPlotDataFromWorkDir({
87
108
  workDir: options.treatmentWorkDir,
@@ -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;