tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

DominatorTreeNode.js (10260B)


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