Backed out changeset 3a73b2d41ed5 (bug 1201542) for test_census-view-01.html failures
authorWes Kocher <wkocher@mozilla.com>
Tue, 20 Oct 2015 12:40:13 -0700
changeset 303781 101384a5c8c91fdf71e30d29d8a52008333156b0
parent 303780 8aff0cae7bfc01ed6674d9536fba68326fbf5caa
child 303782 69f447f55be7075795ce2ef7d3add7439a8ae894
push id1001
push userraliiev@mozilla.com
push dateMon, 18 Jan 2016 19:06:03 +0000
treeherdermozilla-release@8b89261f3ac4 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
bugs1201542
milestone44.0a1
backs out3a73b2d41ed52616ad6b5d65d9663ff307fc6a73
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Backed out changeset 3a73b2d41ed5 (bug 1201542) for test_census-view-01.html failures
devtools/client/memory/test/mochitest/test_census-view-01.html
devtools/shared/heapsnapshot/CensusUtils.js
devtools/shared/heapsnapshot/HeapAnalysesWorker.js
devtools/shared/heapsnapshot/census-tree-node.js
devtools/shared/heapsnapshot/tests/unit/head_heapsnapshot.js
devtools/shared/heapsnapshot/tests/unit/test_HeapAnalyses_takeCensus_06.js
devtools/shared/heapsnapshot/tests/unit/test_census-tree-node-01.js
devtools/shared/heapsnapshot/tests/unit/test_census-tree-node-02.js
devtools/shared/heapsnapshot/tests/unit/test_census-tree-node-03.js
devtools/shared/heapsnapshot/tests/unit/test_census-tree-node-04.js
devtools/shared/heapsnapshot/tests/unit/test_census-tree-node-05.js
devtools/shared/heapsnapshot/tests/unit/xpcshell.ini
--- a/devtools/client/memory/test/mochitest/test_census-view-01.html
+++ b/devtools/client/memory/test/mochitest/test_census-view-01.html
@@ -14,17 +14,17 @@ Bug 1067491 - Test taking a census over 
   <link href="chrome://devtools/skin/themes/memory.css" type="text/css" />
 </head>
 <body>
 <ul id="container" style="width:100%;height:300px;"></ul>
 <pre id="test">
 <script src="head.js" type="application/javascript;version=1.8"></script>
 <script>
 window.onload = function() {
-  var { censusReportToCensusTreeNode } = require("devtools/shared/heapsnapshot/census-tree-node");
+  var { CensusTreeNode } = require("devtools/shared/heapsnapshot/census-tree-node");
   var { INDENTATION, CensusView } = require("devtools/client/memory/modules/census-view");
   SimpleTest.waitForExplicitFinish();
   const countBreakdown = { by: "count", count: true, bytes: true };
 
   const BREAKDOWN = {
     by: "coarseType",
     objects: { by: "objectClass", then: countBreakdown },
     strings: countBreakdown,
@@ -47,17 +47,17 @@ window.onload = function() {
     { level: 0, name: "strings", bytes: 10, count: 1, },
     { level: 0, name: "objects" },
         { level: 1, name: "Array", bytes: 20, count: 2, },
         { level: 1, name: "Function", bytes: 10, count: 1, },
     { level: 0, name: "other" },
         { level: 1, name: "js::Shape2", bytes: 40, count: 4, },
         { level: 1, name: "js::Shape", bytes: 30, count: 3, },
   ];
-  var censusTreeNode = censusReportToCensusTreeNode(BREAKDOWN, REPORT);
+  var censusTreeNode = new CensusTreeNode(BREAKDOWN, REPORT);
 
   var view = new CensusView({
     censusTreeNode: censusTreeNode,
     hidden: true
   });
 
   view.attachTo(document.querySelector("#container"));
 
--- a/devtools/shared/heapsnapshot/CensusUtils.js
+++ b/devtools/shared/heapsnapshot/CensusUtils.js
@@ -12,39 +12,29 @@ function Visitor() { };
 exports.Visitor = Visitor;
 
 /**
  * The `enter` method is called when a new sub-report is entered in traversal.
  *
  * @param {Object} breakdown
  *        The breakdown for the sub-report that is being entered by traversal.
  *
- * @param {Object} report
- *        The report generated by the given breakdown.
- *
  * @param {any} edge
  *        The edge leading to this sub-report. The edge is null if (but not iff!
  *        eg, null allocation stack edges) we are entering the root report.
  */
-Visitor.prototype.enter = function (breakdown, report, edge) { };
+Visitor.prototype.enter = function (breakdown, edge) { };
 
 /**
  * The `exit` method is called when traversal of a sub-report has finished.
  *
  * @param {Object} breakdown
  *        The breakdown for the sub-report whose traversal has finished.
- *
- * @param {Object} report
- *        The report generated by the given breakdown.
- *
- * @param {any} edge
- *        The edge leading to this sub-report. The edge is null if (but not iff!
- *        eg, null allocation stack edges) we are entering the root report.
  */
-Visitor.prototype.exit = function (breakdown, report, edge) { };
+Visitor.prototype.exit = function (breakdown) { };
 
 /**
  * The `count` method is called when leaf nodes (reports whose breakdown is
  * by: "count") in the report tree are encountered.
  *
  * @param {Object} breakdown
  *        The count breakdown for this report.
  *
@@ -116,25 +106,25 @@ function getReportEdges(breakdown, repor
   return EDGES[breakdown.by](breakdown, report);
 }
 exports.getReportEdges = getReportEdges;
 
 /*** walk *******************************************************************/
 
 function recursiveWalk(breakdown, edge, report, visitor) {
   if (breakdown.by === "count") {
-    visitor.enter(breakdown, report, edge);
+    visitor.enter(breakdown, edge);
     visitor.count(breakdown, report, edge);
-    visitor.exit(breakdown, report, edge);
+    visitor.exit(breakdown, edge);
   } else {
-    visitor.enter(breakdown, report, edge);
+    visitor.enter(breakdown, edge);
     for (let { edge, referent, breakdown } of getReportEdges(breakdown, report)) {
       recursiveWalk(breakdown, edge, referent, visitor);
     }
-    visitor.exit(breakdown, report, edge);
+    visitor.exit(breakdown, edge);
   }
 };
 
 /**
  * Walk the given `report` that was generated by taking a census with the
  * specified `breakdown`.
  *
  * @param {Object} breakdown
@@ -211,17 +201,17 @@ DiffVisitor.prototype._set = function (r
   } else {
     report[edge] = val;
   }
 };
 
 /**
  * @overrides Visitor.prototype.enter
  */
-DiffVisitor.prototype.enter = function (breakdown, report, edge) {
+DiffVisitor.prototype.enter = function (breakdown, edge) {
   const isFirstTimeEntering = this._results === null;
 
   const newResults = breakdown.by === "allocationStack" ? new Map() : {};
   let newOther;
 
   if (!this._results) {
     // This is the first time we have entered a sub-report.
     this._results = newResults;
@@ -240,17 +230,17 @@ DiffVisitor.prototype.enter = function (
   const visited = this._edgesVisited[this._edgesVisited.length - 1];
   visited.add(edge);
   this._edgesVisited.push(new Set());
 };
 
 /**
  * @overrides Visitor.prototype.exit
  */
-DiffVisitor.prototype.exit = function (breakdown, report, edge) {
+DiffVisitor.prototype.exit = function (breakdown) {
   // Find all the edges in the other census report that were not traversed and
   // add them to the results directly.
   const other = this._otherCensusStack[this._otherCensusStack.length - 1];
   if (other) {
     const visited = this._edgesVisited[this._edgesVisited.length - 1];
     const unvisited = getReportEdges(breakdown, other)
       .map(e => e.edge)
       .filter(e => !visited.has(e));
--- a/devtools/shared/heapsnapshot/HeapAnalysesWorker.js
+++ b/devtools/shared/heapsnapshot/HeapAnalysesWorker.js
@@ -7,17 +7,17 @@
 // heavyweight analyses on them without blocking the main thread. A
 // HeapAnalysesWorker is owned and communicated with by a HeapAnalysesClient
 // instance. See HeapAnalysesClient.js.
 
 "use strict";
 
 importScripts("resource://gre/modules/workers/require.js");
 importScripts("resource://gre/modules/devtools/shared/worker/helper.js");
-const { censusReportToCensusTreeNode } = require("resource://gre/modules/devtools/shared/heapsnapshot/census-tree-node.js");
+const { CensusTreeNode } = require("resource://gre/modules/devtools/shared/heapsnapshot/census-tree-node.js");
 const CensusUtils = require("resource://gre/modules/devtools/shared/heapsnapshot/CensusUtils.js");
 
 // The set of HeapSnapshot instances this worker has read into memory. Keyed by
 // snapshot file path.
 const snapshots = Object.create(null);
 
 /**
  * @see HeapAnalysesClient.prototype.readHeapSnapshot
@@ -33,17 +33,17 @@ workerHelper.createTask(self, "readHeapS
  */
 workerHelper.createTask(self, "takeCensus", ({ snapshotFilePath, censusOptions, requestOptions }) => {
   if (!snapshots[snapshotFilePath]) {
     throw new Error(`No known heap snapshot for '${snapshotFilePath}'`);
   }
 
   let report = snapshots[snapshotFilePath].takeCensus(censusOptions);
   return requestOptions.asTreeNode
-    ? censusReportToCensusTreeNode(censusOptions.breakdown, report)
+    ? new CensusTreeNode(censusOptions.breakdown, report)
     : report;
 });
 
 /**
  * @see HeapAnalysesClient.prototype.takeCensusDiff
  */
 workerHelper.createTask(self, "takeCensusDiff", request => {
   const {
@@ -61,11 +61,11 @@ workerHelper.createTask(self, "takeCensu
     throw new Error(`No known heap snapshot for '${secondSnapshotFilePath}'`);
   }
 
   const first = snapshots[firstSnapshotFilePath].takeCensus(censusOptions);
   const second = snapshots[secondSnapshotFilePath].takeCensus(censusOptions);
   const delta = CensusUtils.diff(censusOptions.breakdown, first, second);
 
   return requestOptions.asTreeNode
-    ? censusReportToCensusTreeNode(censusOptions.breakdown, delta)
+    ? new CensusTreeNode(censusOptions.breakdown, delta)
     : delta;
 });
--- a/devtools/shared/heapsnapshot/census-tree-node.js
+++ b/devtools/shared/heapsnapshot/census-tree-node.js
@@ -1,354 +1,18 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 "use strict";
 
-// CensusTreeNode is an intermediate representation of a census report that
-// exists between after a report is generated by taking a census and before the
-// report is rendered in the DOM. It must be dead simple to render, with no
-// further data processing or massaging needed before rendering DOM nodes. Our
-// goal is to do the census report to CensusTreeNode transformation in the
-// HeapAnalysesWorker, and ensure that the **only** work that the main thread
-// has to do is strictly DOM rendering work.
-
-const { Visitor, walk } = require("resource://gre/modules/devtools/shared/heapsnapshot/CensusUtils.js");
-
 /**
- * Return true if the given object is a SavedFrame stack object, false otherwise.
- *
- * @param {any} obj
- * @returns {Boolean}
- */
-function isSavedFrame(obj) {
-  return Object.prototype.toString.call(obj) === "[object SavedFrame]";
-}
-
-/**
- * A FrameCache maps from SavedFrames to CensusTreeNodes. It is used when
- * aggregating multiple SavedFrame allocation stack keys into a tree of many
- * CensusTreeNodes. Each stack may share older frames, and we want to preserve
- * this sharing when converting to CensusTreeNode, so before creating a new
- * CensusTreeNode, we look for an existing one in one of our FrameCaches.
- */
-function FrameCache() {}
-FrameCache.prototype = null;
-
-/**
- * The value of a single entry stored in a FrameCache. It is a pair of the
- * CensusTreeNode for this frame, and the subsequent FrameCache for this node's
- * children.
- *
- * @param {SavedFrame} frame
- *        The frame being cached.
- */
-function FrameCacheValue(frame) {
-  // The CensusTreeNode for this frame.
-  this.node = new CensusTreeNode(frame);
-  // The FrameCache for this frame's children.
-  this.children = undefined;
-}
-
-FrameCacheValue.prototype = null;
-
-/**
- * Create a unique string for the given SavedFrame (ignoring the frame's parent
- * chain) that can be used as a hash to key this frame within a FrameCache.
- *
- * @param {SavedFrame} frame
- *        The SavedFrame object we would like to lookup in or insert into a
- *        FrameCache.
- *
- * @returns {String}
- *          The unique string that can be used as a key in a FrameCache.
- */
-FrameCache.hash = function (frame) {
-  return `${frame.functionDisplayName},${frame.source},${frame.line},${frame.column},${frame.asyncCause}`;
-};
-
-/**
- * Associate `frame` with `value` in the given `cache`.
- *
- * @param {FrameCache} cache
- * @param {SavedFrame} frame
- * @param {FrameCacheValue} value
- */
-FrameCache.insert = function (cache, frame, value) {
-  cache[FrameCache.hash(frame)] = value;
-};
-
-/**
- * Lookup `frame` in `cache` and return its value if it exists.
- *
- * @param {FrameCache} cache
- * @param {SavedFrame} frame
- *
- * @returns {undefined|FrameCacheValue}
+ * Utilities for interfacing with census reports from dbg.memory.takeCensus().
  */
-FrameCache.lookup = function (cache, frame) {
-  return cache[FrameCache.hash(frame)];
-};
-
-/**
- * Add `child` to `parent`'s set of children.
- *
- * @param {CensusTreeNode} parent
- * @param {CensusTreeNode} child
- */
-function addChild(parent, child) {
-  if (!parent.children) {
-    parent.children = [];
-  }
-  parent.children.push(child);
-}
-
-/**
- * Get an array of each frame in the provided stack.
- *
- * @param {SavedFrame} stack
- * @returns {Array<SavedFrame>}
- */
-function getArrayOfFrames(stack) {
-  const frames = [];
-  let frame = stack;
-  while (frame) {
-    frames.push(frame);
-    frame = frame.parent;
-  }
-  frames.reverse();
-  return frames;
-}
-
-/**
- * Given an `edge` to a sub-`report` whose structure is described by
- * `breakdown`, create a CensusTreeNode tree.
- *
- * @param {Object} breakdown
- *        The breakdown specifying the structure of the given report.
- *
- * @param {Object} report
- *        The census report.
- *
- * @param {null|String|SavedFrame} edge
- *        The edge leading to this report from the parent report.
- *
- * @param {FrameCache} frameCache
- *        The cache of CensusTreeNodes we have already made for the siblings of
- *        the node being created. The existing nodes are reused when possible.
- *
- * @param {Object} outParams
- *        The return values are attached to this object after this function
- *        returns. Because we create a CensusTreeNode for each frame in a
- *        SavedFrame stack edge, there may multiple nodes per sub-report.
- *
- *          - top: The deepest node in the CensusTreeNode subtree created.
- *
- *          - bottom: The shallowest node in the CensusTreeNode subtree created.
- *                    This is null if the shallowest node in the subtree was
- *                    found in the `frameCache` and reused.
- *
- *        Note that top and bottom are not necessarily different. In the case
- *        where there is a 1:1 correspondence between an edge in the report and
- *        a CensusTreeNode, top and bottom refer to the same node.
- */
-function makeCensusTreeNodeSubTree(breakdown, report, edge, frameCache, outParams) {
-  if (!isSavedFrame(edge)) {
-    const node = new CensusTreeNode(edge);
-    outParams.top = outParams.bottom = node;
-    return;
-  }
-
-  // Loop through each frame in the stack and get or create a CensusTreeNode for
-  // the frame.
-
-  const frames = getArrayOfFrames(edge);
-  let cache = frameCache;
-  let prevNode;
-  for (let i = 0, length = frames.length; i < length; i++) {
-    const frame = frames[i];
 
-    // Get or create the FrameCacheValue for this frame. If we already have a
-    // FrameCacheValue (and hence a CensusTreeNode) for this frame, we don't
-    // need to add the node to the previous node's children as we have already
-    // done that. If we don't have a FrameCacheValue and CensusTreeNode for
-    // this frame, then create one and make sure to hook it up as a child of
-    // the previous node.
-    let isNewNode = false;
-    let val = FrameCache.lookup(cache, frame);
-    if (!val) {
-      isNewNode = true;
-      val = new FrameCacheValue(frame);
-
-      FrameCache.insert(cache, frame, val);
-      if (prevNode) {
-        addChild(prevNode, val.node);
-      }
-    }
-
-    if (i === 0) {
-      outParams.bottom = isNewNode ? val.node : null;
-    }
-    if (i === length - 1) {
-      outParams.top = val.node;
-    }
-
-    prevNode = val.node;
-
-    if (i !== length - 1 && !val.children) {
-      // This is not the last frame and therefore this node will have
-      // children, which we must cache.
-      val.children = new FrameCache();
-    }
-
-    cache = val.children;
-  }
-}
-
-/**
- * A Visitor that walks a census report and creates the corresponding
- * CensusTreeNode tree.
- */
-function CensusTreeNodeVisitor() {
-  // The root of the resulting CensusTreeNode tree.
-  this._root = null;
-
-  // The stack of CensusTreeNodes that we are in the process of building while
-  // walking the census report.
-  this._nodeStack = [];
-
-  // To avoid unnecessary allocations, we reuse the same out parameter object
-  // passed to `makeCensusTreeNodeSubTree` every time we call it.
-  this._outParams = {
-    top: null,
-    bottom: null,
-  };
-
-  // The stack of `FrameCache`s that we use to aggregate many SavedFrame stacks
-  // into a single CensusTreeNode tree.
-  this._frameCacheStack = [new FrameCache()];
-}
-
-CensusTreeNodeVisitor.prototype = Object.create(Visitor);
-
-/**
- * Create the CensusTreeNode subtree for this sub-report and link it to the
- * parent CensusTreeNode.
- *
- * @overrides Visitor.prototype.enter
- */
-CensusTreeNodeVisitor.prototype.enter = function (breakdown, report, edge) {
-  const cache = this._frameCacheStack[this._frameCacheStack.length - 1];
-  makeCensusTreeNodeSubTree(breakdown, report, edge, cache, this._outParams);
-  const { top, bottom } = this._outParams;
-
-  if (!this._root) {
-    this._root = bottom;
-  } else {
-    if (bottom) {
-      addChild(this._nodeStack[this._nodeStack.length - 1], bottom);
-    }
-  }
-
-  this._frameCacheStack.push(new FrameCache);
-  this._nodeStack.push(top);
-};
-
-function values(cache) {
-  return Object.keys(cache).map(k => cache[k]);
-}
-
-/**
- * We have finished adding children to the CensusTreeNode subtree for the
- * current sub-report. Make sure that the children are sorted for every node in
- * the subtree.
- *
- * @overrides Visitor.prototype.exit
- */
-CensusTreeNodeVisitor.prototype.exit = function (breakdown, report, edge) {
-  const top = this._nodeStack.pop();
-  if (top.children) {
-    top.children.sort(compareByBytes);
-  }
-
-  const cache = this._frameCacheStack.pop();
-  const toSort = values(cache);
-  while (toSort.length) {
-    const { node, children } = toSort.pop();
-    if (!node.children) {
-      continue;
-    }
-
-    if (node !== top) {
-      node.children.sort(compareByBytes);
-    }
-
-    if (!children) {
-      continue;
-    }
-
-    const newlyNeedSorting = values(children);
-    for (let i = 0, length = newlyNeedSorting.length; i < length; i++) {
-      toSort.push(newlyNeedSorting[i]);
-    }
-  }
-};
-
-/**
- * @overrides Visitor.prototype.count
- */
-CensusTreeNodeVisitor.prototype.count = function (breakdown, report, edge) {
-  const node = this._nodeStack[this._nodeStack.length - 1];
-  node.count = report.count;
-  node.bytes = report.bytes;
-};
-
-/**
- * Get the root of the resulting CensusTreeNode tree.
- *
- * @returns {CensusTreeNode}
- */
-CensusTreeNodeVisitor.prototype.root = function () {
-  if (!this._root) {
-    throw new Error("Attempt to get the root before walking the census report!");
-  }
-
-  if (this._nodeStack.length) {
-    throw new Error("Attempt to get the root while walking the census report!");
-  }
-
-  return this._root;
-};
-
-/**
- * Create a single, uninitialized CensusTreeNode.
- *
- * @param {null|String|SavedFrame} name
- */
-function CensusTreeNode (name) {
-  this.name = name;
-  this.bytes = undefined;
-  this.count = undefined;
-  this.children = undefined;
-}
-
-CensusTreeNode.prototype = null;
-
-/**
- * Compare the given nodes by their `bytes` properties.
- *
- * @param {CensusTreeNode} node1
- * @param {CensusTreeNode} node2
- *
- * @returns {Number}
- *          A number suitable for using with Array.prototype.sort.
- */
-function compareByBytes (node1, node2) {
-  return (node2.bytes || 0) - (node1.bytes || 0);
-}
+const COARSE_TYPES = new Set(["objects", "scripts", "strings", "other"]);
 
 /**
  * Takes a report from a census (`dbg.memory.takeCensus()`) and the breakdown
  * used to generate the census and returns a structure used to render
  * a tree to display the data.
  *
  * Returns a recursive "CensusTreeNode" object, looking like:
  *
@@ -356,20 +20,72 @@ function compareByBytes (node1, node2) {
  *   // `children` if it exists, is sorted by `bytes`, if they are leaf nodes.
  *   children: ?[<CensusTreeNode...>],
  *   name: <?String>
  *   count: <?Number>
  *   bytes: <?Number>
  * }
  *
  * @param {Object} breakdown
- *        The breakdown used to generate the census report.
- *
  * @param {Object} report
- *        The census report generated with the specified breakdown.
- *
- * @returns {CensusTreeNode}
+ * @param {?String} name
+ * @return {Object}
+ */
+function CensusTreeNode (breakdown, report, name) {
+  this.name = name;
+  this.bytes = void 0;
+  this.count = void 0;
+  this.children = void 0;
+
+  CensusTreeNodeBreakdowns[breakdown.by](this, breakdown, report);
+
+  if (this.children) {
+    this.children.sort(sortByBytes);
+  }
+}
+
+CensusTreeNode.prototype = null;
+
+/**
+ * A series of functions to handle different breakdowns used by CensusTreeNode
  */
-exports.censusReportToCensusTreeNode = function (breakdown, report) {
-  const visitor = new CensusTreeNodeVisitor();
-  walk(breakdown, report, visitor);
-  return visitor.root();
+const CensusTreeNodeBreakdowns = Object.create(null);
+
+CensusTreeNodeBreakdowns.count = function (node, breakdown, report) {
+  if (breakdown.bytes === true) {
+    node.bytes = report.bytes;
+  }
+  if (breakdown.count === true) {
+    node.count = report.count;
+  }
+};
+
+CensusTreeNodeBreakdowns.internalType = function (node, breakdown, report) {
+  node.children = [];
+  for (let key of Object.keys(report)) {
+    node.children.push(new CensusTreeNode(breakdown.then, report[key], key));
+  }
 };
+
+CensusTreeNodeBreakdowns.objectClass = function (node, breakdown, report) {
+  node.children = [];
+  for (let key of Object.keys(report)) {
+    let bd = key === "other" ? breakdown.other : breakdown.then;
+    node.children.push(new CensusTreeNode(bd, report[key], key));
+  }
+};
+
+CensusTreeNodeBreakdowns.coarseType = function (node, breakdown, report) {
+  node.children = [];
+  for (let type of Object.keys(breakdown).filter(type => COARSE_TYPES.has(type))) {
+    node.children.push(new CensusTreeNode(breakdown[type], report[type], type));
+  }
+};
+
+CensusTreeNodeBreakdowns.allocationStack = function (node, breakdown, report) {
+  node.children = [];
+};
+
+function sortByBytes (a, b) {
+  return (b.bytes || 0) - (a.bytes || 0);
+}
+
+exports.CensusTreeNode = CensusTreeNode;
--- a/devtools/shared/heapsnapshot/tests/unit/head_heapsnapshot.js
+++ b/devtools/shared/heapsnapshot/tests/unit/head_heapsnapshot.js
@@ -15,17 +15,17 @@ const { Census } = Cu.import("resource:/
 const { addDebuggerToGlobal } =
   Cu.import("resource://gre/modules/jsdebugger.jsm", {});
 const { Task } = Cu.import("resource://gre/modules/Task.jsm", {});
 
 const DevToolsUtils = require("devtools/shared/DevToolsUtils");
 const HeapAnalysesClient =
   require("devtools/shared/heapsnapshot/HeapAnalysesClient");
 const Services = require("Services");
-const { censusReportToCensusTreeNode } = require("devtools/shared/heapsnapshot/census-tree-node");
+const { CensusTreeNode } = require("devtools/shared/heapsnapshot/census-tree-node");
 const CensusUtils = require("devtools/shared/heapsnapshot/CensusUtils");
 
 // Always log packets when running tests. runxpcshelltests.py will throw
 // the output away anyway, unless you give it the --verbose flag.
 if (Services.appInfo &&
     Services.appInfo.processType == Services.appInfo.PROCESS_TYPE_DEFAULT) {
   Services.prefs.setBoolPref("devtools.debugger.log", true);
 }
@@ -145,61 +145,40 @@ function saveHeapSnapshotAndTakeCensus(d
   const snapshot = ChromeUtils.readHeapSnapshot(filePath);
   ok(snapshot, "Should have read a heap snapshot back from " + filePath);
   ok(snapshot instanceof HeapSnapshot, "snapshot should be an instance of HeapSnapshot");
 
   equal(typeof snapshot.takeCensus, "function", "snapshot should have a takeCensus method");
   return snapshot.takeCensus(censusOptions);
 }
 
-function isSavedFrame(obj) {
-  return Object.prototype.toString.call(obj) === "[object SavedFrame]";
-}
-
-function savedFrameReplacer(key, val) {
-  if (isSavedFrame(val)) {
-    return `<SavedFrame '${val.toString().split(/\n/g).shift()}'>`;
-  } else {
-    return val;
-  }
-}
-
 /**
  * Assert that creating a CensusTreeNode from the given `report` with the
  * specified `breakdown` creates the given `expected` CensusTreeNode.
  *
  * @param {Object} breakdown
  *        The census breakdown.
  *
  * @param {Object} report
  *        The census report.
  *
  * @param {Object} expected
  *        The expected CensusTreeNode result.
+ *
+ * @param {String} assertion
+ *        The assertion message.
  */
-function compareCensusViewData (breakdown, report, expected) {
-  dumpn("Generating CensusTreeNode from report:");
-  dumpn("breakdown: " + JSON.stringify(breakdown, null, 4));
-  dumpn("report: " + JSON.stringify(report, null, 4));
-  dumpn("expected: " + JSON.stringify(expected, savedFrameReplacer, 4));
-
-  const actual = censusReportToCensusTreeNode(breakdown, report);
-  dumpn("actual: " + JSON.stringify(actual, savedFrameReplacer, 4));
-
-  assertStructurallyEquivalent(actual, expected);
+function compareCensusViewData (breakdown, report, expected, assertion) {
+  let data = new CensusTreeNode(breakdown, report);
+  equal(JSON.stringify(data), JSON.stringify(expected), assertion);
 }
 
 // Deep structural equivalence that can handle Map objects in addition to plain
 // objects.
 function assertStructurallyEquivalent(actual, expected, path="root") {
-  if (actual === expected) {
-    equal(actual, expected, "actual and expected are the same");
-    return;
-  }
-
   equal(typeof actual, typeof expected, `${path}: typeof should be the same`);
 
   if (actual && typeof actual === "object") {
     const actualProtoString = Object.prototype.toString.call(actual);
     const expectedProtoString = Object.prototype.toString.call(expected);
     equal(actualProtoString, expectedProtoString,
           `${path}: Object.prototype.toString.call() should be the same`);
 
@@ -211,30 +190,30 @@ function assertStructurallyEquivalent(ac
            `${path}: every key in actual should exist in expected: ${String(key).slice(0, 10)}`);
         expectedKeys.delete(key);
 
         assertStructurallyEquivalent(actual.get(key), expected.get(key),
                                      path + ".get(" + String(key).slice(0, 20) + ")");
       }
 
       equal(expectedKeys.size, 0,
-            `${path}: every key in expected should also exist in actual, did not see ${[...expectedKeys]}`);
+            `${path}: every key in expected should also exist in actual`);
     } else {
       const expectedKeys = new Set(Object.keys(expected));
 
       for (let key of Object.keys(actual)) {
         ok(expectedKeys.has(key),
            `${path}: every key in actual should exist in expected: ${key}`);
         expectedKeys.delete(key);
 
         assertStructurallyEquivalent(actual[key], expected[key], path + "." + key);
       }
 
       equal(expectedKeys.size, 0,
-            `${path}: every key in expected should also exist in actual, did not see ${[...expectedKeys]}`);
+            `${path}: every key in expected should also exist in actual`);
     }
   } else {
     equal(actual, expected, `${path}: primitives should be equal`);
   }
 }
 
 /**
  * Assert that creating a diff of the `first` and `second` census reports
deleted file mode 100644
--- a/devtools/shared/heapsnapshot/tests/unit/test_HeapAnalyses_takeCensus_06.js
+++ /dev/null
@@ -1,109 +0,0 @@
-/* Any copyright is dedicated to the Public Domain.
-   http://creativecommons.org/publicdomain/zero/1.0/ */
-
-// Test that the HeapAnalyses{Client,Worker} can take censuses by
-// "allocationStack" and return a CensusTreeNode.
-
-function run_test() {
-  run_next_test();
-}
-
-const BREAKDOWN = {
-  by: "objectClass",
-  then: {
-    by: "allocationStack",
-    then: { by: "count", count: true, bytes: true },
-    noStack: { by: "count", count: true, bytes: true }
-  },
-  other: { by: "count", count: true, bytes: true }
-};
-
-add_task(function* () {
-  const g = newGlobal();
-  const dbg = new Debugger(g);
-
-  // 5 allocation markers with no stack.
-  g.eval(`
-         this.markers = [];
-         for (var i = 0; i < 5; i++) {
-           markers.push(allocationMarker());
-         }
-         `);
-
-  dbg.memory.allocationSamplingProbability = 1;
-  dbg.memory.trackingAllocationSites = true;
-
-  // 5 allocation markers at 5 stacks.
-  g.eval(`
-         (function shouldHaveCountOfOne() {
-           markers.push(allocationMarker());
-           markers.push(allocationMarker());
-           markers.push(allocationMarker());
-           markers.push(allocationMarker());
-           markers.push(allocationMarker());
-         }());
-         `);
-
-  // 5 allocation markers at 1 stack.
-  g.eval(`
-         (function shouldHaveCountOfFive() {
-           for (var i = 0; i < 5; i++) {
-             markers.push(allocationMarker());
-           }
-         }());
-         `);
-
-  const snapshotFilePath = saveNewHeapSnapshot({ debugger: dbg });
-
-  const client = new HeapAnalysesClient();
-  yield client.readHeapSnapshot(snapshotFilePath);
-  ok(true, "Should have read the heap snapshot");
-
-  const report = yield client.takeCensus(snapshotFilePath, {
-    breakdown: BREAKDOWN
-  });
-
-  const treeNode = yield client.takeCensus(snapshotFilePath, {
-    breakdown: BREAKDOWN
-  }, {
-    asTreeNode: true
-  });
-
-  const markers = treeNode.children.find(c => c.name === "AllocationMarker");
-  ok(markers);
-
-  const noStack = markers.children.find(c => c.name === "noStack");
-  equal(noStack.count, 5);
-
-  let numShouldHaveFiveFound = 0;
-  let numShouldHaveOneFound = 0;
-
-  function walk(node) {
-    if (node.children) {
-      node.children.forEach(walk);
-    }
-
-    if (!isSavedFrame(node.name)) {
-      return;
-    }
-
-    if (node.name.functionDisplayName === "shouldHaveCountOfFive") {
-      equal(node.count, 5, "shouldHaveCountOfFive should have count of five");
-      numShouldHaveFiveFound++;
-    }
-
-    if (node.name.functionDisplayName === "shouldHaveCountOfOne") {
-      equal(node.count, 1, "shouldHaveCountOfOne should have count of one");
-      numShouldHaveOneFound++;
-    }
-  }
-  markers.children.forEach(walk);
-
-  equal(numShouldHaveFiveFound, 1);
-  equal(numShouldHaveOneFound, 5);
-
-  compareCensusViewData(BREAKDOWN, report, treeNode,
-    "Returning census as a tree node represents same data as the report");
-
-  client.destroy();
-});
--- a/devtools/shared/heapsnapshot/tests/unit/test_census-tree-node-01.js
+++ b/devtools/shared/heapsnapshot/tests/unit/test_census-tree-node-01.js
@@ -1,14 +1,17 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 /**
  * Tests CensusTreeNode with `internalType` breakdown.
  */
+function run_test() {
+  compareCensusViewData(BREAKDOWN, REPORT, EXPECTED, `${JSON.stringify(BREAKDOWN)} has correct results.`);
+}
 
 const BREAKDOWN = {
   by: "internalType",
   then: { by: "count", count: true, bytes: true }
 };
 
 const REPORT = {
   "JSObject": {
@@ -21,21 +24,14 @@ const REPORT = {
   },
   "JSString": {
     "bytes": 0,
     "count": 0,
   },
 };
 
 const EXPECTED = {
-  name: null,
-  bytes: undefined,
-  count: undefined,
   children: [
-    { name: "js::Shape", bytes: 500, count: 50, children: undefined },
-    { name: "JSObject", bytes: 100, count: 10, children: undefined },
-    { name: "JSString", bytes: 0, count: 0, children: undefined },
+    { name: "js::Shape", bytes: 500, count: 50, },
+    { name: "JSObject", bytes: 100, count: 10, },
+    { name: "JSString", bytes: 0, count: 0, },
   ],
 };
-
-function run_test() {
-  compareCensusViewData(BREAKDOWN, REPORT, EXPECTED);
-}
--- a/devtools/shared/heapsnapshot/tests/unit/test_census-tree-node-02.js
+++ b/devtools/shared/heapsnapshot/tests/unit/test_census-tree-node-02.js
@@ -1,71 +1,45 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 /**
  * Tests CensusTreeNode with `coarseType` breakdown.
  */
 
+function run_test() {
+  compareCensusViewData(BREAKDOWN, REPORT, EXPECTED, `${JSON.stringify(BREAKDOWN)} has correct results.`);
+}
+
 const countBreakdown = { by: "count", count: true, bytes: true };
 
 const BREAKDOWN = {
   by: "coarseType",
   objects: { by: "objectClass", then: countBreakdown },
   strings: countBreakdown,
-  scripts: countBreakdown,
   other: { by: "internalType", then: countBreakdown },
 };
 
 const REPORT = {
   "objects": {
     "Function": { bytes: 10, count: 1 },
     "Array": { bytes: 20, count: 2 },
   },
   "strings": { bytes: 10, count: 1 },
-  "scripts": { bytes: 1, count: 1 },
   "other": {
     "js::Shape": { bytes: 30, count: 3 },
     "js::Shape2": { bytes: 40, count: 4 }
   },
 };
 
 const EXPECTED = {
-  name: null,
-  bytes: undefined,
-  count: undefined,
   children: [
-    {
-      name: "strings",
-      count: 1,
-      bytes: 10,
-      children: undefined
-    },
-    {
-      name: "scripts",
-      count: 1,
-      bytes: 1,
-      children: undefined
-    },
-    {
-      name: "objects",
-      count: undefined,
-      bytes: undefined,
-      children: [
-        { name: "Array", bytes: 20, count: 2, children: undefined },
-        { name: "Function", bytes: 10, count: 1, children: undefined },
-      ]
-    },
-    {
-      name: "other",
-      count: undefined,
-      bytes: undefined,
-      children: [
-        { name: "js::Shape2", bytes: 40, count: 4, children: undefined },
-        { name: "js::Shape", bytes: 30, count: 3, children: undefined },
-      ]
-    },
+    { name: "strings", bytes: 10, count: 1, },
+    { name: "objects", children: [
+      { name: "Array", bytes: 20, count: 2, },
+      { name: "Function", bytes: 10, count: 1, },
+    ]},
+    { name: "other", children: [
+      { name: "js::Shape2", bytes: 40, count: 4, },
+      { name: "js::Shape", bytes: 30, count: 3, },
+    ]},
   ]
 };
-
-function run_test() {
-  compareCensusViewData(BREAKDOWN, REPORT, EXPECTED);
-}
--- a/devtools/shared/heapsnapshot/tests/unit/test_census-tree-node-03.js
+++ b/devtools/shared/heapsnapshot/tests/unit/test_census-tree-node-03.js
@@ -1,15 +1,19 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 /**
  * Tests CensusTreeNode with `objectClass` breakdown.
  */
 
+function run_test() {
+  compareCensusViewData(BREAKDOWN, REPORT, EXPECTED, `${JSON.stringify(BREAKDOWN)} has correct results.`);
+}
+
 const countBreakdown = { by: "count", count: true, bytes: true };
 
 const BREAKDOWN = {
   by: "objectClass",
   then: countBreakdown,
   other: { by: "internalType", then: countBreakdown }
 };
 
@@ -18,29 +22,17 @@ const REPORT = {
   "Array": { bytes: 100, count: 1 },
   "other": {
     "JIT::CODE::NOW!!!": { bytes: 20, count: 2 },
     "JIT::CODE::LATER!!!": { bytes: 40, count: 4 }
   }
 };
 
 const EXPECTED = {
-  name: null,
-  count: undefined,
-  bytes: undefined,
   children: [
-    { name: "Array", bytes: 100, count: 1, children: undefined },
-    { name: "Function", bytes: 10, count: 10, children: undefined },
-    {
-      name: "other",
-      count: undefined,
-      bytes: undefined,
-      children: [
-        { name: "JIT::CODE::LATER!!!", bytes: 40, count: 4, children: undefined },
-        { name: "JIT::CODE::NOW!!!", bytes: 20, count: 2, children: undefined },
-      ]
-    }
+    { name: "Array", bytes: 100, count: 1 },
+    { name: "Function", bytes: 10, count: 10 },
+    { name: "other", children: [
+      { name: "JIT::CODE::LATER!!!", bytes: 40, count: 4 },
+      { name: "JIT::CODE::NOW!!!", bytes: 20, count: 2 },
+    ]}
   ]
 };
-
-function run_test() {
-  compareCensusViewData(BREAKDOWN, REPORT, EXPECTED);
-}
deleted file mode 100644
--- a/devtools/shared/heapsnapshot/tests/unit/test_census-tree-node-04.js
+++ /dev/null
@@ -1,109 +0,0 @@
-/* Any copyright is dedicated to the Public Domain.
-   http://creativecommons.org/publicdomain/zero/1.0/ */
-
-/**
- * Tests CensusTreeNode with `allocationStack` breakdown.
- */
-
-function run_test() {
-  const countBreakdown = { by: "count", count: true, bytes: true };
-
-  const BREAKDOWN = {
-    by: "allocationStack",
-    then: countBreakdown,
-    noStack: countBreakdown,
-  };
-
-  let stack1, stack2, stack3, stack4, stack5;
-
-  (function a() {
-    (function b() {
-      (function c() {
-        stack1 = saveStack(3);
-      }());
-      (function d() {
-        stack2 = saveStack(3);
-        stack3 = saveStack(3);
-      }());
-      stack4 = saveStack(2);
-    }());
-  }());
-
-  stack5 = saveStack(1);
-
-  const REPORT = new Map([
-    [stack1,    { bytes: 10, count: 1 }],
-    [stack2,    { bytes: 20, count: 2 }],
-    [stack3,    { bytes: 30, count: 3 }],
-    [stack4,    { bytes: 40, count: 4 }],
-    [stack5,    { bytes: 50, count: 5 }],
-    ["noStack", { bytes: 60, count: 6 }],
-  ]);
-
-  const EXPECTED = {
-    name: null,
-    bytes: undefined,
-    count: undefined,
-    children: [
-      {
-        name: "noStack",
-        bytes: 60,
-        count: 6,
-        children: undefined
-      },
-      {
-        name: stack5,
-        bytes: 50,
-        count: 5,
-        children: undefined
-      },
-      {
-        name: stack4.parent,
-        bytes: undefined,
-        count: undefined,
-        children: [
-          {
-            name: stack4,
-            bytes: 40,
-            count: 4,
-            children: undefined
-          },
-          {
-            name: stack1.parent,
-            bytes: undefined,
-            count: undefined,
-            children: [
-              {
-                name: stack1,
-                bytes: 10,
-                count: 1,
-                children: undefined
-              },
-            ]
-          },
-          {
-            name: stack3.parent,
-            bytes: undefined,
-            count: undefined,
-            children: [
-              {
-                name: stack3,
-                bytes: 30,
-                count: 3,
-                children: undefined
-              },
-              {
-                name: stack2,
-                bytes: 20,
-                count: 2,
-                children: undefined
-              }
-            ]
-          }
-        ]
-      }
-    ]
-  };
-
-  compareCensusViewData(BREAKDOWN, REPORT, EXPECTED);
-}
deleted file mode 100644
--- a/devtools/shared/heapsnapshot/tests/unit/test_census-tree-node-05.js
+++ /dev/null
@@ -1,100 +0,0 @@
-/* Any copyright is dedicated to the Public Domain.
-   http://creativecommons.org/publicdomain/zero/1.0/ */
-
-/**
- * Tests CensusTreeNode with `allocationStack` => `objectClass` breakdown.
- */
-
-function run_test() {
-  const countBreakdown = { by: "count", count: true, bytes: true };
-
-  const BREAKDOWN = {
-    by: "allocationStack",
-    then: {
-      by: "objectClass",
-      then: countBreakdown,
-      other: countBreakdown
-    },
-    noStack: countBreakdown,
-  };
-
-  let stack;
-
-  (function a() {
-    (function b() {
-      (function c() {
-        stack = saveStack(3);
-      }());
-    }());
-  }());
-
-  const REPORT = new Map([
-    [stack,     { Foo:   { bytes: 10, count: 1 },
-                  Bar:   { bytes: 20, count: 2 },
-                  Baz:   { bytes: 30, count: 3 },
-                  other: { bytes: 40, count: 4 }
-                }],
-    ["noStack", { bytes: 50, count: 5 }],
-  ]);
-
-  const EXPECTED = {
-    name: null,
-    bytes: undefined,
-    count: undefined,
-    children: [
-      {
-        name: "noStack",
-        bytes: 50,
-        count: 5,
-        children: undefined
-      },
-      {
-        name: stack.parent.parent,
-        bytes: undefined,
-        count: undefined,
-        children: [
-          {
-            name: stack.parent,
-            bytes: undefined,
-            count: undefined,
-            children: [
-              {
-                name: stack,
-                bytes: undefined,
-                count: undefined,
-                children: [
-                  {
-                    name: "other",
-                    bytes: 40,
-                    count: 4,
-                    children: undefined
-                  },
-                  {
-                    name: "Baz",
-                    bytes: 30,
-                    count: 3,
-                    children: undefined
-                  },
-                  {
-                    name: "Bar",
-                    bytes: 20,
-                    count: 2,
-                    children: undefined
-                  },
-                  {
-                    name: "Foo",
-                    bytes: 10,
-                    count: 1,
-                    children: undefined
-                  },
-                ]
-              }
-            ]
-          }
-        ]
-      }
-    ]
-  };
-
-  compareCensusViewData(BREAKDOWN, REPORT, EXPECTED);
-}
--- a/devtools/shared/heapsnapshot/tests/unit/xpcshell.ini
+++ b/devtools/shared/heapsnapshot/tests/unit/xpcshell.ini
@@ -13,26 +13,23 @@ support-files =
 [test_census_diff_02.js]
 [test_census_diff_03.js]
 [test_census_diff_04.js]
 [test_census_diff_05.js]
 [test_census_diff_06.js]
 [test_census-tree-node-01.js]
 [test_census-tree-node-02.js]
 [test_census-tree-node-03.js]
-[test_census-tree-node-04.js]
-[test_census-tree-node-05.js]
 [test_HeapAnalyses_readHeapSnapshot_01.js]
 [test_HeapAnalyses_takeCensusDiff_01.js]
 [test_HeapAnalyses_takeCensus_01.js]
 [test_HeapAnalyses_takeCensus_02.js]
 [test_HeapAnalyses_takeCensus_03.js]
 [test_HeapAnalyses_takeCensus_04.js]
 [test_HeapAnalyses_takeCensus_05.js]
-[test_HeapAnalyses_takeCensus_06.js]
 [test_HeapSnapshot_creationTime_01.js]
 [test_HeapSnapshot_takeCensus_01.js]
 [test_HeapSnapshot_takeCensus_02.js]
 [test_HeapSnapshot_takeCensus_03.js]
 [test_HeapSnapshot_takeCensus_04.js]
 [test_HeapSnapshot_takeCensus_05.js]
 [test_HeapSnapshot_takeCensus_06.js]
 [test_HeapSnapshot_takeCensus_07.js]