JSHeapSnapshot.js 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622
  1. /*
  2. * Copyright (C) 2012 Google Inc. All rights reserved.
  3. *
  4. * Redistribution and use in source and binary forms, with or without
  5. * modification, are permitted provided that the following conditions are
  6. * met:
  7. *
  8. * * Redistributions of source code must retain the above copyright
  9. * notice, this list of conditions and the following disclaimer.
  10. * * Redistributions in binary form must reproduce the above
  11. * copyright notice, this list of conditions and the following disclaimer
  12. * in the documentation and/or other materials provided with the
  13. * distribution.
  14. * * Neither the name of Google Inc. nor the names of its
  15. * contributors may be used to endorse or promote products derived from
  16. * this software without specific prior written permission.
  17. *
  18. * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
  19. * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
  20. * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
  21. * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
  22. * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
  23. * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
  24. * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
  25. * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
  26. * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
  27. * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
  28. * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  29. */
  30. /**
  31. * @constructor
  32. * @param{WebInspector.HeapSnapshotProgress} progress
  33. * @extends {WebInspector.HeapSnapshot}
  34. */
  35. WebInspector.JSHeapSnapshot = function(profile, progress)
  36. {
  37. this._nodeFlags = { // bit flags
  38. canBeQueried: 1,
  39. detachedDOMTreeNode: 2,
  40. pageObject: 4, // The idea is to track separately the objects owned by the page and the objects owned by debugger.
  41. visitedMarkerMask: 0x0ffff, // bits: 0,1111,1111,1111,1111
  42. visitedMarker: 0x10000 // bits: 1,0000,0000,0000,0000
  43. };
  44. WebInspector.HeapSnapshot.call(this, profile, progress);
  45. }
  46. WebInspector.JSHeapSnapshot.prototype = {
  47. createNode: function(nodeIndex)
  48. {
  49. return new WebInspector.JSHeapSnapshotNode(this, nodeIndex);
  50. },
  51. createEdge: function(edges, edgeIndex)
  52. {
  53. return new WebInspector.JSHeapSnapshotEdge(this, edges, edgeIndex);
  54. },
  55. createRetainingEdge: function(retainedNodeIndex, retainerIndex)
  56. {
  57. return new WebInspector.JSHeapSnapshotRetainerEdge(this, retainedNodeIndex, retainerIndex);
  58. },
  59. classNodesFilter: function()
  60. {
  61. function filter(node)
  62. {
  63. return node.isUserObject();
  64. }
  65. return filter;
  66. },
  67. containmentEdgesFilter: function(showHiddenData)
  68. {
  69. function filter(edge) {
  70. if (edge.isInvisible())
  71. return false;
  72. if (showHiddenData)
  73. return true;
  74. return !edge.isHidden() && !edge.node().isHidden();
  75. }
  76. return filter;
  77. },
  78. retainingEdgesFilter: function(showHiddenData)
  79. {
  80. var containmentEdgesFilter = this.containmentEdgesFilter(showHiddenData);
  81. function filter(edge)
  82. {
  83. return containmentEdgesFilter(edge) && !edge.node().isRoot() && !edge.isWeak();
  84. }
  85. return filter;
  86. },
  87. dispose: function()
  88. {
  89. WebInspector.HeapSnapshot.prototype.dispose.call(this);
  90. delete this._flags;
  91. },
  92. _markInvisibleEdges: function()
  93. {
  94. // Mark hidden edges of global objects as invisible.
  95. // FIXME: This is a temporary measure. Normally, we should
  96. // really hide all hidden nodes.
  97. for (var iter = this.rootNode().edges(); iter.hasNext(); iter.next()) {
  98. var edge = iter.edge;
  99. if (!edge.isShortcut())
  100. continue;
  101. var node = edge.node();
  102. var propNames = {};
  103. for (var innerIter = node.edges(); innerIter.hasNext(); innerIter.next()) {
  104. var globalObjEdge = innerIter.edge;
  105. if (globalObjEdge.isShortcut())
  106. propNames[globalObjEdge._nameOrIndex()] = true;
  107. }
  108. for (innerIter.rewind(); innerIter.hasNext(); innerIter.next()) {
  109. var globalObjEdge = innerIter.edge;
  110. if (!globalObjEdge.isShortcut()
  111. && globalObjEdge.node().isHidden()
  112. && globalObjEdge._hasStringName()
  113. && (globalObjEdge._nameOrIndex() in propNames))
  114. this._containmentEdges[globalObjEdge._edges._start + globalObjEdge.edgeIndex + this._edgeTypeOffset] = this._edgeInvisibleType;
  115. }
  116. }
  117. },
  118. _calculateFlags: function()
  119. {
  120. this._flags = new Uint32Array(this.nodeCount);
  121. this._markDetachedDOMTreeNodes();
  122. this._markQueriableHeapObjects();
  123. this._markPageOwnedNodes();
  124. },
  125. /**
  126. * @param {!WebInspector.HeapSnapshotNode} node
  127. * @return {!boolean}
  128. */
  129. _isUserRoot: function(node)
  130. {
  131. return node.isUserRoot() || node.isDocumentDOMTreesRoot();
  132. },
  133. /**
  134. * @param {function(!WebInspector.HeapSnapshotNode)} action
  135. * @param {boolean=} userRootsOnly
  136. */
  137. forEachRoot: function(action, userRootsOnly)
  138. {
  139. /**
  140. * @param {!WebInspector.HeapSnapshotNode} node
  141. * @param {!string} name
  142. * @return {!WebInspector.HeapSnapshotNode|null}
  143. */
  144. function getChildNodeByName(node, name)
  145. {
  146. for (var iter = node.edges(); iter.hasNext(); iter.next()) {
  147. var child = iter.edge.node();
  148. if (child.name() === name)
  149. return child;
  150. }
  151. return null;
  152. }
  153. /**
  154. * @param {!WebInspector.HeapSnapshotNode} node
  155. * @param {!string} name
  156. * @return {!WebInspector.HeapSnapshotNode|null}
  157. */
  158. function getChildNodeByLinkName(node, name)
  159. {
  160. for (var iter = node.edges(); iter.hasNext(); iter.next()) {
  161. var edge = iter.edge;
  162. if (edge.name() === name)
  163. return edge.node();
  164. }
  165. return null;
  166. }
  167. var visitedNodes = {};
  168. /**
  169. * @param {!WebInspector.HeapSnapshotNode} node
  170. */
  171. function doAction(node)
  172. {
  173. var ordinal = node._ordinal();
  174. if (!visitedNodes[ordinal]) {
  175. action(node);
  176. visitedNodes[ordinal] = true;
  177. }
  178. }
  179. var gcRoots = getChildNodeByName(this.rootNode(), "(GC roots)");
  180. if (!gcRoots)
  181. return;
  182. if (userRootsOnly) {
  183. for (var iter = this.rootNode().edges(); iter.hasNext(); iter.next()) {
  184. var node = iter.edge.node();
  185. if (node.isDocumentDOMTreesRoot())
  186. doAction(node);
  187. else if (node.isUserRoot()) {
  188. var nativeContextNode = getChildNodeByLinkName(node, "native_context");
  189. if (nativeContextNode)
  190. doAction(nativeContextNode);
  191. else
  192. doAction(node);
  193. }
  194. }
  195. } else {
  196. for (var iter = gcRoots.edges(); iter.hasNext(); iter.next()) {
  197. var subRoot = iter.edge.node();
  198. for (var iter2 = subRoot.edges(); iter2.hasNext(); iter2.next())
  199. doAction(iter2.edge.node());
  200. doAction(subRoot);
  201. }
  202. for (var iter = this.rootNode().edges(); iter.hasNext(); iter.next())
  203. doAction(iter.edge.node())
  204. }
  205. },
  206. userObjectsMapAndFlag: function()
  207. {
  208. return {
  209. map: this._flags,
  210. flag: this._nodeFlags.pageObject
  211. };
  212. },
  213. _flagsOfNode: function(node)
  214. {
  215. return this._flags[node.nodeIndex / this._nodeFieldCount];
  216. },
  217. _markDetachedDOMTreeNodes: function()
  218. {
  219. var flag = this._nodeFlags.detachedDOMTreeNode;
  220. var detachedDOMTreesRoot;
  221. for (var iter = this.rootNode().edges(); iter.hasNext(); iter.next()) {
  222. var node = iter.edge.node();
  223. if (node.name() === "(Detached DOM trees)") {
  224. detachedDOMTreesRoot = node;
  225. break;
  226. }
  227. }
  228. if (!detachedDOMTreesRoot)
  229. return;
  230. var detachedDOMTreeRE = /^Detached DOM tree/;
  231. for (var iter = detachedDOMTreesRoot.edges(); iter.hasNext(); iter.next()) {
  232. var node = iter.edge.node();
  233. if (detachedDOMTreeRE.test(node.className())) {
  234. for (var edgesIter = node.edges(); edgesIter.hasNext(); edgesIter.next())
  235. this._flags[edgesIter.edge.node().nodeIndex / this._nodeFieldCount] |= flag;
  236. }
  237. }
  238. },
  239. _markQueriableHeapObjects: function()
  240. {
  241. // Allow runtime properties query for objects accessible from Window objects
  242. // via regular properties, and for DOM wrappers. Trying to access random objects
  243. // can cause a crash due to insonsistent state of internal properties of wrappers.
  244. var flag = this._nodeFlags.canBeQueried;
  245. var hiddenEdgeType = this._edgeHiddenType;
  246. var internalEdgeType = this._edgeInternalType;
  247. var invisibleEdgeType = this._edgeInvisibleType;
  248. var weakEdgeType = this._edgeWeakType;
  249. var edgeToNodeOffset = this._edgeToNodeOffset;
  250. var edgeTypeOffset = this._edgeTypeOffset;
  251. var edgeFieldsCount = this._edgeFieldsCount;
  252. var containmentEdges = this._containmentEdges;
  253. var nodes = this._nodes;
  254. var nodeCount = this.nodeCount;
  255. var nodeFieldCount = this._nodeFieldCount;
  256. var firstEdgeIndexes = this._firstEdgeIndexes;
  257. var flags = this._flags;
  258. var list = [];
  259. for (var iter = this.rootNode().edges(); iter.hasNext(); iter.next()) {
  260. if (iter.edge.node().isUserRoot())
  261. list.push(iter.edge.node().nodeIndex / nodeFieldCount);
  262. }
  263. while (list.length) {
  264. var nodeOrdinal = list.pop();
  265. if (flags[nodeOrdinal] & flag)
  266. continue;
  267. flags[nodeOrdinal] |= flag;
  268. var beginEdgeIndex = firstEdgeIndexes[nodeOrdinal];
  269. var endEdgeIndex = firstEdgeIndexes[nodeOrdinal + 1];
  270. for (var edgeIndex = beginEdgeIndex; edgeIndex < endEdgeIndex; edgeIndex += edgeFieldsCount) {
  271. var childNodeIndex = containmentEdges[edgeIndex + edgeToNodeOffset];
  272. var childNodeOrdinal = childNodeIndex / nodeFieldCount;
  273. if (flags[childNodeOrdinal] & flag)
  274. continue;
  275. var type = containmentEdges[edgeIndex + edgeTypeOffset];
  276. if (type === hiddenEdgeType || type === invisibleEdgeType || type === internalEdgeType || type === weakEdgeType)
  277. continue;
  278. list.push(childNodeOrdinal);
  279. }
  280. }
  281. },
  282. _markPageOwnedNodes: function()
  283. {
  284. var edgeShortcutType = this._edgeShortcutType;
  285. var edgeElementType = this._edgeElementType;
  286. var edgeToNodeOffset = this._edgeToNodeOffset;
  287. var edgeTypeOffset = this._edgeTypeOffset;
  288. var edgeFieldsCount = this._edgeFieldsCount;
  289. var edgeWeakType = this._edgeWeakType;
  290. var firstEdgeIndexes = this._firstEdgeIndexes;
  291. var containmentEdges = this._containmentEdges;
  292. var containmentEdgesLength = containmentEdges.length;
  293. var nodes = this._nodes;
  294. var nodeFieldCount = this._nodeFieldCount;
  295. var nodesCount = this.nodeCount;
  296. var flags = this._flags;
  297. var flag = this._nodeFlags.pageObject;
  298. var visitedMarker = this._nodeFlags.visitedMarker;
  299. var visitedMarkerMask = this._nodeFlags.visitedMarkerMask;
  300. var markerAndFlag = visitedMarker | flag;
  301. var nodesToVisit = new Uint32Array(nodesCount);
  302. var nodesToVisitLength = 0;
  303. var rootNodeOrdinal = this._rootNodeIndex / nodeFieldCount;
  304. var node = this.rootNode();
  305. for (var edgeIndex = firstEdgeIndexes[rootNodeOrdinal], endEdgeIndex = firstEdgeIndexes[rootNodeOrdinal + 1];
  306. edgeIndex < endEdgeIndex;
  307. edgeIndex += edgeFieldsCount) {
  308. var edgeType = containmentEdges[edgeIndex + edgeTypeOffset];
  309. var nodeIndex = containmentEdges[edgeIndex + edgeToNodeOffset];
  310. if (edgeType === edgeElementType) {
  311. node.nodeIndex = nodeIndex;
  312. if (!node.isDocumentDOMTreesRoot())
  313. continue;
  314. } else if (edgeType !== edgeShortcutType)
  315. continue;
  316. var nodeOrdinal = nodeIndex / nodeFieldCount;
  317. nodesToVisit[nodesToVisitLength++] = nodeOrdinal;
  318. flags[nodeOrdinal] |= visitedMarker;
  319. }
  320. while (nodesToVisitLength) {
  321. var nodeOrdinal = nodesToVisit[--nodesToVisitLength];
  322. flags[nodeOrdinal] |= flag;
  323. flags[nodeOrdinal] &= visitedMarkerMask;
  324. var beginEdgeIndex = firstEdgeIndexes[nodeOrdinal];
  325. var endEdgeIndex = firstEdgeIndexes[nodeOrdinal + 1];
  326. for (var edgeIndex = beginEdgeIndex; edgeIndex < endEdgeIndex; edgeIndex += edgeFieldsCount) {
  327. var childNodeIndex = containmentEdges[edgeIndex + edgeToNodeOffset];
  328. var childNodeOrdinal = childNodeIndex / nodeFieldCount;
  329. if (flags[childNodeOrdinal] & markerAndFlag)
  330. continue;
  331. var type = containmentEdges[edgeIndex + edgeTypeOffset];
  332. if (type === edgeWeakType)
  333. continue;
  334. nodesToVisit[nodesToVisitLength++] = childNodeOrdinal;
  335. flags[childNodeOrdinal] |= visitedMarker;
  336. }
  337. }
  338. },
  339. __proto__: WebInspector.HeapSnapshot.prototype
  340. };
  341. /**
  342. * @constructor
  343. * @extends {WebInspector.HeapSnapshotNode}
  344. * @param {WebInspector.JSHeapSnapshot} snapshot
  345. * @param {number=} nodeIndex
  346. */
  347. WebInspector.JSHeapSnapshotNode = function(snapshot, nodeIndex)
  348. {
  349. WebInspector.HeapSnapshotNode.call(this, snapshot, nodeIndex)
  350. }
  351. WebInspector.JSHeapSnapshotNode.prototype = {
  352. canBeQueried: function()
  353. {
  354. var flags = this._snapshot._flagsOfNode(this);
  355. return !!(flags & this._snapshot._nodeFlags.canBeQueried);
  356. },
  357. isUserObject: function()
  358. {
  359. var flags = this._snapshot._flagsOfNode(this);
  360. return !!(flags & this._snapshot._nodeFlags.pageObject);
  361. },
  362. className: function()
  363. {
  364. var type = this.type();
  365. switch (type) {
  366. case "hidden":
  367. return "(system)";
  368. case "object":
  369. case "native":
  370. return this.name();
  371. case "code":
  372. return "(compiled code)";
  373. default:
  374. return "(" + type + ")";
  375. }
  376. },
  377. classIndex: function()
  378. {
  379. var snapshot = this._snapshot;
  380. var nodes = snapshot._nodes;
  381. var type = nodes[this.nodeIndex + snapshot._nodeTypeOffset];;
  382. if (type === snapshot._nodeObjectType || type === snapshot._nodeNativeType)
  383. return nodes[this.nodeIndex + snapshot._nodeNameOffset];
  384. return -1 - type;
  385. },
  386. id: function()
  387. {
  388. var snapshot = this._snapshot;
  389. return snapshot._nodes[this.nodeIndex + snapshot._nodeIdOffset];
  390. },
  391. isHidden: function()
  392. {
  393. return this._type() === this._snapshot._nodeHiddenType;
  394. },
  395. isSynthetic: function()
  396. {
  397. return this._type() === this._snapshot._nodeSyntheticType;
  398. },
  399. /**
  400. * @return {!boolean}
  401. */
  402. isUserRoot: function()
  403. {
  404. return !this.isSynthetic();
  405. },
  406. /**
  407. * @return {!boolean}
  408. */
  409. isDocumentDOMTreesRoot: function()
  410. {
  411. return this.isSynthetic() && this.name() === "(Document DOM trees)";
  412. },
  413. serialize: function()
  414. {
  415. var result = WebInspector.HeapSnapshotNode.prototype.serialize.call(this);
  416. var flags = this._snapshot._flagsOfNode(this);
  417. if (flags & this._snapshot._nodeFlags.canBeQueried)
  418. result.canBeQueried = true;
  419. if (flags & this._snapshot._nodeFlags.detachedDOMTreeNode)
  420. result.detachedDOMTreeNode = true;
  421. return result;
  422. },
  423. __proto__: WebInspector.HeapSnapshotNode.prototype
  424. };
  425. /**
  426. * @constructor
  427. * @extends {WebInspector.HeapSnapshotEdge}
  428. * @param {WebInspector.JSHeapSnapshot} snapshot
  429. * @param {Array.<number>} edges
  430. * @param {number=} edgeIndex
  431. */
  432. WebInspector.JSHeapSnapshotEdge = function(snapshot, edges, edgeIndex)
  433. {
  434. WebInspector.HeapSnapshotEdge.call(this, snapshot, edges, edgeIndex);
  435. }
  436. WebInspector.JSHeapSnapshotEdge.prototype = {
  437. clone: function()
  438. {
  439. return new WebInspector.JSHeapSnapshotEdge(this._snapshot, this._edges, this.edgeIndex);
  440. },
  441. hasStringName: function()
  442. {
  443. if (!this.isShortcut())
  444. return this._hasStringName();
  445. return isNaN(parseInt(this._name(), 10));
  446. },
  447. isElement: function()
  448. {
  449. return this._type() === this._snapshot._edgeElementType;
  450. },
  451. isHidden: function()
  452. {
  453. return this._type() === this._snapshot._edgeHiddenType;
  454. },
  455. isWeak: function()
  456. {
  457. return this._type() === this._snapshot._edgeWeakType;
  458. },
  459. isInternal: function()
  460. {
  461. return this._type() === this._snapshot._edgeInternalType;
  462. },
  463. isInvisible: function()
  464. {
  465. return this._type() === this._snapshot._edgeInvisibleType;
  466. },
  467. isShortcut: function()
  468. {
  469. return this._type() === this._snapshot._edgeShortcutType;
  470. },
  471. name: function()
  472. {
  473. if (!this.isShortcut())
  474. return this._name();
  475. var numName = parseInt(this._name(), 10);
  476. return isNaN(numName) ? this._name() : numName;
  477. },
  478. toString: function()
  479. {
  480. var name = this.name();
  481. switch (this.type()) {
  482. case "context": return "->" + name;
  483. case "element": return "[" + name + "]";
  484. case "weak": return "[[" + name + "]]";
  485. case "property":
  486. return name.indexOf(" ") === -1 ? "." + name : "[\"" + name + "\"]";
  487. case "shortcut":
  488. if (typeof name === "string")
  489. return name.indexOf(" ") === -1 ? "." + name : "[\"" + name + "\"]";
  490. else
  491. return "[" + name + "]";
  492. case "internal":
  493. case "hidden":
  494. case "invisible":
  495. return "{" + name + "}";
  496. };
  497. return "?" + name + "?";
  498. },
  499. _hasStringName: function()
  500. {
  501. return !this.isElement() && !this.isHidden() && !this.isWeak();
  502. },
  503. _name: function()
  504. {
  505. return this._hasStringName() ? this._snapshot._strings[this._nameOrIndex()] : this._nameOrIndex();
  506. },
  507. _nameOrIndex: function()
  508. {
  509. return this._edges.item(this.edgeIndex + this._snapshot._edgeNameOffset);
  510. },
  511. _type: function()
  512. {
  513. return this._edges.item(this.edgeIndex + this._snapshot._edgeTypeOffset);
  514. },
  515. __proto__: WebInspector.HeapSnapshotEdge.prototype
  516. };
  517. /**
  518. * @constructor
  519. * @extends {WebInspector.HeapSnapshotRetainerEdge}
  520. * @param {WebInspector.JSHeapSnapshot} snapshot
  521. */
  522. WebInspector.JSHeapSnapshotRetainerEdge = function(snapshot, retainedNodeIndex, retainerIndex)
  523. {
  524. WebInspector.HeapSnapshotRetainerEdge.call(this, snapshot, retainedNodeIndex, retainerIndex);
  525. }
  526. WebInspector.JSHeapSnapshotRetainerEdge.prototype = {
  527. clone: function()
  528. {
  529. return new WebInspector.JSHeapSnapshotRetainerEdge(this._snapshot, this._retainedNodeIndex, this.retainerIndex());
  530. },
  531. isHidden: function()
  532. {
  533. return this._edge().isHidden();
  534. },
  535. isInternal: function()
  536. {
  537. return this._edge().isInternal();
  538. },
  539. isInvisible: function()
  540. {
  541. return this._edge().isInvisible();
  542. },
  543. isShortcut: function()
  544. {
  545. return this._edge().isShortcut();
  546. },
  547. isWeak: function()
  548. {
  549. return this._edge().isWeak();
  550. },
  551. __proto__: WebInspector.HeapSnapshotRetainerEdge.prototype
  552. }