DominatorTreeNode.js 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337
  1. /* This Source Code Form is subject to the terms of the Mozilla Public
  2. * License, v. 2.0. If a copy of the MPL was not distributed with this
  3. * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
  4. "use strict";
  5. const { immutableUpdate } = require("resource://devtools/shared/ThreadSafeDevToolsUtils.js");
  6. const { Visitor, walk } = require("resource://devtools/shared/heapsnapshot/CensusUtils.js");
  7. const { deduplicatePaths } = require("resource://devtools/shared/heapsnapshot/shortest-paths");
  8. const DEFAULT_MAX_DEPTH = 4;
  9. const DEFAULT_MAX_SIBLINGS = 15;
  10. const DEFAULT_MAX_NUM_PATHS = 5;
  11. /**
  12. * A single node in a dominator tree.
  13. *
  14. * @param {NodeId} nodeId
  15. * @param {NodeSize} retainedSize
  16. */
  17. function DominatorTreeNode(nodeId, label, shallowSize, retainedSize) {
  18. // The id of this node.
  19. this.nodeId = nodeId;
  20. // The label structure generated by describing the given node.
  21. this.label = label;
  22. // The shallow size of this node.
  23. this.shallowSize = shallowSize;
  24. // The retained size of this node.
  25. this.retainedSize = retainedSize;
  26. // The id of this node's parent or undefined if this node is the root.
  27. this.parentId = undefined;
  28. // An array of immediately dominated child `DominatorTreeNode`s, or undefined.
  29. this.children = undefined;
  30. // An object of the form returned by `deduplicatePaths`, encoding the set of
  31. // the N shortest retaining paths for this node as a graph.
  32. this.shortestPaths = undefined;
  33. // True iff the `children` property does not contain every immediately
  34. // dominated node.
  35. //
  36. // * If children is an array and this property is true: the array does not
  37. // contain the complete set of immediately dominated children.
  38. // * If children is an array and this property is false: the array contains
  39. // the complete set of immediately dominated children.
  40. // * If children is undefined and this property is true: there exist
  41. // immediately dominated children for this node, but they have not been
  42. // loaded yet.
  43. // * If children is undefined and this property is false: this node does not
  44. // dominate any others and therefore has no children.
  45. this.moreChildrenAvailable = true;
  46. }
  47. DominatorTreeNode.prototype = null;
  48. module.exports = DominatorTreeNode;
  49. /**
  50. * Add `child` to the `parent`'s set of children.
  51. *
  52. * @param {DominatorTreeNode} parent
  53. * @param {DominatorTreeNode} child
  54. */
  55. DominatorTreeNode.addChild = function (parent, child) {
  56. if (parent.children === undefined) {
  57. parent.children = [];
  58. }
  59. parent.children.push(child);
  60. child.parentId = parent.nodeId;
  61. };
  62. /**
  63. * A Visitor that is used to generate a label for a node in the heap snapshot
  64. * and get its shallow size as well while we are at it.
  65. */
  66. function LabelAndShallowSizeVisitor() {
  67. // As we walk the description, we accumulate edges in this array.
  68. this._labelPieces = [];
  69. // Once we reach the non-zero count leaf node in the description, we move the
  70. // labelPieces here to signify that we no longer need to accumulate edges.
  71. this._label = undefined;
  72. // Once we reach the non-zero count leaf node in the description, we grab the
  73. // shallow size and place it here.
  74. this._shallowSize = 0;
  75. }
  76. DominatorTreeNode.LabelAndShallowSizeVisitor = LabelAndShallowSizeVisitor;
  77. LabelAndShallowSizeVisitor.prototype = Object.create(Visitor);
  78. /**
  79. * @overrides Visitor.prototype.enter
  80. */
  81. LabelAndShallowSizeVisitor.prototype.enter = function (breakdown, report, edge) {
  82. if (this._labelPieces && edge) {
  83. this._labelPieces.push(edge);
  84. }
  85. };
  86. /**
  87. * @overrides Visitor.prototype.exit
  88. */
  89. LabelAndShallowSizeVisitor.prototype.exit = function (breakdown, report, edge) {
  90. if (this._labelPieces && edge) {
  91. this._labelPieces.pop();
  92. }
  93. };
  94. /**
  95. * @overrides Visitor.prototype.count
  96. */
  97. LabelAndShallowSizeVisitor.prototype.count = function (breakdown, report, edge) {
  98. if (report.count === 0) {
  99. return;
  100. }
  101. this._label = this._labelPieces;
  102. this._labelPieces = undefined;
  103. this._shallowSize = report.bytes;
  104. };
  105. /**
  106. * Get the generated label structure accumulated by this visitor.
  107. *
  108. * @returns {Object}
  109. */
  110. LabelAndShallowSizeVisitor.prototype.label = function () {
  111. return this._label;
  112. };
  113. /**
  114. * Get the shallow size of the node this visitor visited.
  115. *
  116. * @returns {Number}
  117. */
  118. LabelAndShallowSizeVisitor.prototype.shallowSize = function () {
  119. return this._shallowSize;
  120. };
  121. /**
  122. * Generate a label structure for the node with the given id and grab its
  123. * shallow size.
  124. *
  125. * What is a "label" structure? HeapSnapshot.describeNode essentially takes a
  126. * census of a single node rather than the whole heap graph. The resulting
  127. * report has only one count leaf that is non-zero. The label structure is the
  128. * path in this report from the root to the non-zero count leaf.
  129. *
  130. * @param {Number} nodeId
  131. * @param {HeapSnapshot} snapshot
  132. * @param {Object} breakdown
  133. *
  134. * @returns {Object}
  135. * An object with the following properties:
  136. * - {Number} shallowSize
  137. * - {Object} label
  138. */
  139. DominatorTreeNode.getLabelAndShallowSize = function (nodeId,
  140. snapshot,
  141. breakdown) {
  142. const description = snapshot.describeNode(breakdown, nodeId);
  143. const visitor = new LabelAndShallowSizeVisitor();
  144. walk(breakdown, description, visitor);
  145. return {
  146. label: visitor.label(),
  147. shallowSize: visitor.shallowSize(),
  148. };
  149. };
  150. /**
  151. * Do a partial traversal of the given dominator tree and convert it into a tree
  152. * of `DominatorTreeNode`s. Dominator trees have a node for every node in the
  153. * snapshot's heap graph, so we must not allocate a JS object for every node. It
  154. * would be way too many and the node count is effectively unbounded.
  155. *
  156. * Go no deeper down the tree than `maxDepth` and only consider at most
  157. * `maxSiblings` within any single node's children.
  158. *
  159. * @param {DominatorTree} dominatorTree
  160. * @param {HeapSnapshot} snapshot
  161. * @param {Object} breakdown
  162. * @param {Number} maxDepth
  163. * @param {Number} maxSiblings
  164. *
  165. * @returns {DominatorTreeNode}
  166. */
  167. DominatorTreeNode.partialTraversal = function (dominatorTree,
  168. snapshot,
  169. breakdown,
  170. maxDepth = DEFAULT_MAX_DEPTH,
  171. maxSiblings = DEFAULT_MAX_SIBLINGS) {
  172. function dfs(nodeId, depth) {
  173. const { label, shallowSize } =
  174. DominatorTreeNode.getLabelAndShallowSize(nodeId, snapshot, breakdown);
  175. const retainedSize = dominatorTree.getRetainedSize(nodeId);
  176. const node = new DominatorTreeNode(nodeId, label, shallowSize, retainedSize);
  177. const childNodeIds = dominatorTree.getImmediatelyDominated(nodeId);
  178. const newDepth = depth + 1;
  179. if (newDepth < maxDepth) {
  180. const endIdx = Math.min(childNodeIds.length, maxSiblings);
  181. for (let i = 0; i < endIdx; i++) {
  182. DominatorTreeNode.addChild(node, dfs(childNodeIds[i], newDepth));
  183. }
  184. node.moreChildrenAvailable = endIdx < childNodeIds.length;
  185. } else {
  186. node.moreChildrenAvailable = childNodeIds.length > 0;
  187. }
  188. return node;
  189. }
  190. return dfs(dominatorTree.root, 0);
  191. };
  192. /**
  193. * Insert more children into the given (partially complete) dominator tree.
  194. *
  195. * The tree is updated in an immutable and persistent manner: a new tree is
  196. * returned, but all unmodified subtrees (which is most) are shared with the
  197. * original tree. Only the modified nodes are re-allocated.
  198. *
  199. * @param {DominatorTreeNode} tree
  200. * @param {Array<NodeId>} path
  201. * @param {Array<DominatorTreeNode>} newChildren
  202. * @param {Boolean} moreChildrenAvailable
  203. *
  204. * @returns {DominatorTreeNode}
  205. */
  206. DominatorTreeNode.insert = function (tree, path, newChildren, moreChildrenAvailable) {
  207. function insert(tree, i) {
  208. if (tree.nodeId !== path[i]) {
  209. return tree;
  210. }
  211. if (i == path.length - 1) {
  212. return immutableUpdate(tree, {
  213. children: (tree.children || []).concat(newChildren),
  214. moreChildrenAvailable,
  215. });
  216. }
  217. return tree.children
  218. ? immutableUpdate(tree, {
  219. children: tree.children.map(c => insert(c, i + 1))
  220. })
  221. : tree;
  222. }
  223. return insert(tree, 0);
  224. };
  225. /**
  226. * Get the new canonical node with the given `id` in `tree` that exists along
  227. * `path`. If there is no such node along `path`, return null.
  228. *
  229. * This is useful if we have a reference to a now-outdated DominatorTreeNode due
  230. * to a recent call to DominatorTreeNode.insert and want to get the up-to-date
  231. * version. We don't have to walk the whole tree: if there is an updated version
  232. * of the node then it *must* be along the path.
  233. *
  234. * @param {NodeId} id
  235. * @param {DominatorTreeNode} tree
  236. * @param {Array<NodeId>} path
  237. *
  238. * @returns {DominatorTreeNode|null}
  239. */
  240. DominatorTreeNode.getNodeByIdAlongPath = function (id, tree, path) {
  241. function find(node, i) {
  242. if (!node || node.nodeId !== path[i]) {
  243. return null;
  244. }
  245. if (node.nodeId === id) {
  246. return node;
  247. }
  248. if (i === path.length - 1 || !node.children) {
  249. return null;
  250. }
  251. const nextId = path[i + 1];
  252. return find(node.children.find(c => c.nodeId === nextId), i + 1);
  253. }
  254. return find(tree, 0);
  255. };
  256. /**
  257. * Find the shortest retaining paths for the given set of DominatorTreeNodes,
  258. * and populate each node's `shortestPaths` property with them in place.
  259. *
  260. * @param {HeapSnapshot} snapshot
  261. * @param {Object} breakdown
  262. * @param {NodeId} start
  263. * @param {Array<DominatorTreeNode>} treeNodes
  264. * @param {Number} maxNumPaths
  265. */
  266. DominatorTreeNode.attachShortestPaths = function (snapshot,
  267. breakdown,
  268. start,
  269. treeNodes,
  270. maxNumPaths = DEFAULT_MAX_NUM_PATHS) {
  271. const idToTreeNode = new Map();
  272. const targets = [];
  273. for (let node of treeNodes) {
  274. const id = node.nodeId;
  275. idToTreeNode.set(id, node);
  276. targets.push(id);
  277. }
  278. const shortestPaths = snapshot.computeShortestPaths(start,
  279. targets,
  280. maxNumPaths);
  281. for (let [target, paths] of shortestPaths) {
  282. const deduped = deduplicatePaths(target, paths);
  283. deduped.nodes = deduped.nodes.map(id => {
  284. const { label } =
  285. DominatorTreeNode.getLabelAndShallowSize(id, snapshot, breakdown);
  286. return { id, label };
  287. });
  288. idToTreeNode.get(target).shortestPaths = deduped;
  289. }
  290. };