@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.
- package/dist/__tests__/parser/StringNode.test.js +12 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.js +4 -1
- package/dist/lib/Config.d.ts +4 -0
- package/dist/lib/Config.js +13 -0
- package/dist/lib/FileManager.d.ts +6 -0
- package/dist/lib/FileManager.js +56 -4
- package/dist/lib/HeapAnalyzer.d.ts +5 -1
- package/dist/lib/HeapAnalyzer.js +33 -21
- package/dist/lib/RunInfoUtils.d.ts +39 -0
- package/dist/lib/RunInfoUtils.js +86 -0
- package/dist/lib/Types.d.ts +80 -2
- package/dist/lib/Utils.d.ts +30 -20
- package/dist/lib/Utils.js +119 -151
- package/dist/lib/charts/MemoryBarChart.d.ts +1 -0
- package/dist/lib/charts/MemoryBarChart.js +23 -2
- package/dist/lib/heap-data/HeapEdge.d.ts +3 -1
- package/dist/lib/heap-data/HeapEdge.js +13 -0
- package/dist/lib/heap-data/HeapLocation.d.ts +3 -1
- package/dist/lib/heap-data/HeapLocation.js +14 -0
- package/dist/lib/heap-data/HeapNode.d.ts +6 -1
- package/dist/lib/heap-data/HeapNode.js +46 -0
- package/dist/lib/heap-data/HeapSnapshot.d.ts +3 -1
- package/dist/lib/heap-data/HeapSnapshot.js +7 -0
- package/dist/lib/heap-data/HeapStringNode.d.ts +2 -1
- package/dist/lib/heap-data/HeapStringNode.js +5 -0
- package/dist/lib/trace-filters/BaseTraceFilter.rule.d.ts +29 -0
- package/dist/lib/trace-filters/BaseTraceFilter.rule.js +22 -0
- package/dist/lib/trace-filters/LeakTraceFilter.d.ts +20 -0
- package/dist/lib/trace-filters/LeakTraceFilter.js +37 -0
- package/dist/lib/trace-filters/TraceFilterRuleList.d.ts +13 -0
- package/dist/lib/trace-filters/TraceFilterRuleList.js +33 -0
- package/dist/lib/trace-filters/rules/FilterAttachedDOMToDetachedDOMTrace.rule.d.ts +15 -0
- package/dist/lib/trace-filters/rules/FilterAttachedDOMToDetachedDOMTrace.rule.js +55 -0
- package/dist/lib/trace-filters/rules/FilterDOMNodeChainTrace.rule.d.ts +15 -0
- package/dist/lib/trace-filters/rules/FilterDOMNodeChainTrace.rule.js +41 -0
- package/dist/lib/trace-filters/rules/FilterHermesTrace.rule.d.ts +15 -0
- package/dist/lib/trace-filters/rules/FilterHermesTrace.rule.js +29 -0
- package/dist/lib/trace-filters/rules/FilterInternalNodeTrace.rule.d.ts +15 -0
- package/dist/lib/trace-filters/rules/FilterInternalNodeTrace.rule.js +57 -0
- package/dist/lib/trace-filters/rules/FilterPendingActivitiesTrace.rule.d.ts +15 -0
- package/dist/lib/trace-filters/rules/FilterPendingActivitiesTrace.rule.js +62 -0
- package/dist/lib/trace-filters/rules/FilterShadowRootTrace.rule.d.ts +15 -0
- package/dist/lib/trace-filters/rules/FilterShadowRootTrace.rule.js +44 -0
- package/dist/lib/trace-filters/rules/FilterStyleEngineTrace.rule.d.ts +15 -0
- package/dist/lib/trace-filters/rules/FilterStyleEngineTrace.rule.js +49 -0
- package/dist/logger/LeakClusterLogger.js +1 -0
- package/dist/paths/TraceFinder.js +16 -2
- package/dist/trace-cluster/TraceBucket.d.ts +2 -1
- package/dist/trace-cluster/TraceBucket.js +22 -4
- package/dist/trace-cluster/TraceElement.d.ts +6 -1
- package/dist/trace-cluster/TraceElement.js +33 -0
- package/dist/trace-cluster/strategies/TraceSimilarityStrategy.js +10 -0
- package/package.json +1 -1
- 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.
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
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.
|
|
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;
|