Bug 1260261 - Associate a `CensusTreeNode` with the leaf in the census report from which it was generated; r=jimb
authorNick Fitzgerald <fitzgen@gmail.com>
Tue, 29 Mar 2016 08:53:00 +0200
changeset 290856 f3ea2fdec3196f6a498c7fe8c1e48e460331f602
parent 290855 5a79fb92dd005dbf7bcc250635c55cbcfab7d470
child 290857 85d231a303cfe702634035ae2470e343842e9e21
push id19656
push usergwagner@mozilla.com
push dateMon, 04 Apr 2016 13:43:23 +0000
treeherderb2g-inbound@e99061fde28a [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjimb
bugs1260261
milestone48.0a1
Bug 1260261 - Associate a `CensusTreeNode` with the leaf in the census report from which it was generated; r=jimb This commit gives every `CensusTreeNode` instance a `reportLeafIndex` member that is an index into a pre-order traversal of the census report from which it was generated. This can be used to get the leaf in the census report corresponding to a given `CensusTreeNode` instance.
devtools/shared/heapsnapshot/census-tree-node.js
devtools/shared/heapsnapshot/tests/unit/head_heapsnapshot.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/test_census-tree-node-06.js
devtools/shared/heapsnapshot/tests/unit/test_census-tree-node-07.js
devtools/shared/heapsnapshot/tests/unit/test_census-tree-node-08.js
devtools/shared/heapsnapshot/tests/unit/test_census-tree-node-10.js
devtools/shared/heapsnapshot/tests/unit/test_census_filtering_01.js
devtools/shared/heapsnapshot/tests/unit/test_census_filtering_02.js
devtools/shared/heapsnapshot/tests/unit/test_census_filtering_03.js
devtools/shared/heapsnapshot/tests/unit/test_census_filtering_04.js
devtools/shared/heapsnapshot/tests/unit/xpcshell.ini
--- a/devtools/shared/heapsnapshot/census-tree-node.js
+++ b/devtools/shared/heapsnapshot/census-tree-node.js
@@ -277,27 +277,32 @@ function CensusTreeNodeVisitor() {
   this._outParams = {
     top: null,
     bottom: null,
   };
 
   // The stack of `CensusTreeNodeCache`s that we use to aggregate many
   // SavedFrame stacks into a single CensusTreeNode tree.
   this._cacheStack = [new CensusTreeNodeCache()];
+
+  // The current index in the DFS of the census report tree.
+  this._index = -1;
 }
 
 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) {
+  this._index++;
+
   const cache = this._cacheStack[this._cacheStack.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);
@@ -359,16 +364,17 @@ CensusTreeNodeVisitor.prototype.exit = f
   dfs(top, cache);
 };
 
 /**
  * @overrides Visitor.prototype.count
  */
 CensusTreeNodeVisitor.prototype.count = function (breakdown, report, edge) {
   const node = this._nodeStack[this._nodeStack.length - 1];
+  node.reportLeafIndex = this._index;
 
   if (breakdown.count) {
     node.count = report.count;
   }
 
   if (breakdown.bytes) {
     node.bytes = report.bytes;
   }
@@ -420,16 +426,37 @@ function CensusTreeNode(name) {
   this.children = undefined;
 
   // The unique ID of this node.
   this.id = ++censusTreeNodeIdCounter;
 
   // If present, the unique ID of this node's parent. If this node does not have
   // a parent, then undefined.
   this.parent = undefined;
+
+  // The `reportLeafIndex` property allows mapping a CensusTreeNode node back to
+  // a leaf in the census report it was generated from. It is always one of the
+  // following variants:
+  //
+  // * A `Number` index pointing a leaf report in a pre-order DFS traversal of
+  //   this CensusTreeNode's census report.
+  //
+  // * A `Set` object containing such indices, when this is part of an inverted
+  //   CensusTreeNode tree and multiple leaves in the report map onto this node.
+  //
+  // * Finally, `undefined` when no leaves in the census report correspond with
+  //   this node.
+  //
+  // The first and third cases are the common cases. The second case is rather
+  // uncommon, and to avoid doubling the number of allocations when creating
+  // CensusTreeNode trees, and objects that get structured cloned when sending
+  // such trees from the HeapAnalysesWorker to the main thread, we only allocate
+  // a Set object once a node actually does have multiple leaves it corresponds
+  // to.
+  this.reportLeafIndex = undefined;
 }
 
 CensusTreeNode.prototype = null;
 
 /**
  * Compare the given nodes by their `totalBytes` properties, and breaking ties
  * with the `totalCount`, `bytes`, and `count` properties (in that order).
  *
@@ -479,22 +506,38 @@ function compareBySelf(node1, node2) {
 function insertOrMergeNode(parentCacheValue, node) {
   if (!parentCacheValue.children) {
     parentCacheValue.children = new CensusTreeNodeCache();
   }
 
   let val = CensusTreeNodeCache.lookupNode(parentCacheValue.children, node);
 
   if (val) {
+    // When inverting, it is possible that multiple leaves in the census report
+    // get merged into a single CensusTreeNode node. When this occurs, switch
+    // from a single index to a set of indices.
+    if (val.node.reportLeafIndex !== undefined &&
+        val.node.reportLeafIndex !== node.reportLeafIndex) {
+      if (typeof val.node.reportLeafIndex === "number") {
+        const oldIndex = val.node.reportLeafIndex;
+        val.node.reportLeafIndex = new Set();
+        val.node.reportLeafIndex.add(oldIndex);
+        val.node.reportLeafIndex.add(node.reportLeafIndex);
+      } else {
+        val.node.reportLeafIndex.add(node.reportLeafIndex);
+      }
+    }
+
     val.node.count += node.count;
     val.node.bytes += node.bytes;
   } else {
     val = new CensusTreeNodeCacheValue();
 
     val.node = new CensusTreeNode(node.name);
+    val.node.reportLeafIndex = node.reportLeafIndex;
     val.node.count = node.count;
     val.node.totalCount = node.totalCount;
     val.node.bytes = node.bytes;
     val.node.totalBytes = node.totalBytes;
 
     addChild(parentCacheValue.node, val.node);
     CensusTreeNodeCache.insertNode(parentCacheValue.children, val);
   }
--- a/devtools/shared/heapsnapshot/tests/unit/head_heapsnapshot.js
+++ b/devtools/shared/heapsnapshot/tests/unit/head_heapsnapshot.js
@@ -248,16 +248,27 @@ function assertStructurallyEquivalent(ac
         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]}`);
+    } else if (actualProtoString === "[object Set]") {
+      const expectedItems = new Set([...expected]);
+
+      for (let item of actual) {
+        ok(expectedItems.has(item),
+           `${path}: every set item in actual should exist in expected: ${item}`);
+        expectedItems.delete(item);
+      }
+
+      equal(expectedItems.size, 0,
+            `${path}: every set item in expected should also exist in actual, did not see ${[...expectedItems]}`);
     } 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);
 
--- a/devtools/shared/heapsnapshot/tests/unit/test_census-tree-node-01.js
+++ b/devtools/shared/heapsnapshot/tests/unit/test_census-tree-node-01.js
@@ -35,38 +35,42 @@ const EXPECTED = {
     {
       name: "js::Shape",
       bytes: 500,
       totalBytes: 500,
       count: 50,
       totalCount: 50,
       children: undefined,
       id: 3,
-      parent: 1
+      parent: 1,
+      reportLeafIndex: 2,
     },
     {
       name: "JSObject",
       bytes: 100,
       totalBytes: 100,
       count: 10,
       totalCount: 10,
       children: undefined,
       id: 2,
-      parent: 1
+      parent: 1,
+      reportLeafIndex: 1,
     },
     {
       name: "JSString",
       bytes: 10,
       totalBytes: 10,
       count: 1,
       totalCount: 1,
       children: undefined,
       id: 4,
-      parent: 1
+      parent: 1,
+      reportLeafIndex: 3,
     },
   ],
   id: 1,
   parent: undefined,
+  reportLeafIndex: undefined,
 };
 
 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
@@ -46,30 +46,33 @@ const EXPECTED = {
           name: "js::Shape2",
           bytes: 40,
           totalBytes: 40,
           count: 4,
           totalCount: 4,
           children: undefined,
           id: 9,
           parent: 7,
+          reportLeafIndex: 8,
         },
         {
           name: "js::Shape",
           bytes: 30,
           totalBytes: 30,
           count: 3,
           totalCount: 3,
           children: undefined,
           id: 8,
           parent: 7,
+          reportLeafIndex: 7,
         },
       ],
       id: 7,
       parent: 1,
+      reportLeafIndex: undefined,
     },
     {
       name: "objects",
       count: 0,
       totalCount: 3,
       bytes: 0,
       totalBytes: 30,
       children: [
@@ -77,51 +80,57 @@ const EXPECTED = {
           name: "Array",
           bytes: 20,
           totalBytes: 20,
           count: 2,
           totalCount: 2,
           children: undefined,
           id: 4,
           parent: 2,
+          reportLeafIndex: 3,
         },
         {
           name: "Function",
           bytes: 10,
           totalBytes: 10,
           count: 1,
           totalCount: 1,
           children: undefined,
           id: 3,
           parent: 2,
+          reportLeafIndex: 2,
         },
       ],
       id: 2,
       parent: 1,
+      reportLeafIndex: undefined,
     },
     {
       name: "strings",
       count: 1,
       totalCount: 1,
       bytes: 10,
       totalBytes: 10,
       children: undefined,
       id: 6,
       parent: 1,
+      reportLeafIndex: 5,
     },
     {
       name: "scripts",
       count: 1,
       totalCount: 1,
       bytes: 1,
       totalBytes: 1,
       children: undefined,
       id: 5,
       parent: 1,
+      reportLeafIndex: 4,
     },
   ],
   id: 1,
   parent: undefined,
+  reportLeafIndex: undefined,
 };
 
 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
@@ -33,16 +33,17 @@ const EXPECTED = {
       name: "Array",
       bytes: 100,
       totalBytes: 100,
       count: 1,
       totalCount: 1,
       children: undefined,
       id: 3,
       parent: 1,
+      reportLeafIndex: 2,
     },
     {
       name: "other",
       count: 0,
       totalCount: 6,
       bytes: 0,
       totalBytes: 60,
       children: [
@@ -50,41 +51,46 @@ const EXPECTED = {
           name: "JIT::CODE::LATER!!!",
           bytes: 40,
           totalBytes: 40,
           count: 4,
           totalCount: 4,
           children: undefined,
           id: 6,
           parent: 4,
+          reportLeafIndex: 5,
         },
         {
           name: "JIT::CODE::NOW!!!",
           bytes: 20,
           totalBytes: 20,
           count: 2,
           totalCount: 2,
           children: undefined,
           id: 5,
           parent: 4,
+          reportLeafIndex: 4,
         },
       ],
       id: 4,
       parent: 1,
+      reportLeafIndex: undefined,
     },
     {
       name: "Function",
       bytes: 10,
       totalBytes: 10,
       count: 10,
       totalCount: 10,
       children: undefined,
       id: 2,
       parent: 1,
+      reportLeafIndex: 1,
     },
   ],
   id: 1,
   parent: undefined,
+  reportLeafIndex: undefined,
 };
 
 function run_test() {
   compareCensusViewData(BREAKDOWN, REPORT, EXPECTED);
 }
--- a/devtools/shared/heapsnapshot/tests/unit/test_census-tree-node-04.js
+++ b/devtools/shared/heapsnapshot/tests/unit/test_census-tree-node-04.js
@@ -65,40 +65,44 @@ function run_test() {
                 name: stack3,
                 bytes: 30,
                 totalBytes: 30,
                 count: 3,
                 totalCount: 3,
                 children: undefined,
                 id: 7,
                 parent: 5,
+                reportLeafIndex: 3,
               },
               {
                 name: stack2,
                 bytes: 20,
                 totalBytes: 20,
                 count: 2,
                 totalCount: 2,
                 children: undefined,
                 id: 6,
                 parent: 5,
+                reportLeafIndex: 2,
               }
             ],
             id: 5,
             parent: 2,
+            reportLeafIndex: undefined,
           },
           {
             name: stack4,
             bytes: 40,
             totalBytes: 40,
             count: 4,
             totalCount: 4,
             children: undefined,
             id: 8,
             parent: 2,
+            reportLeafIndex: 4,
           },
           {
             name: stack1.parent,
             bytes: 0,
             totalBytes: 10,
             count: 0,
             totalCount: 1,
             children: [
@@ -106,44 +110,50 @@ function run_test() {
                 name: stack1,
                 bytes: 10,
                 totalBytes: 10,
                 count: 1,
                 totalCount: 1,
                 children: undefined,
                 id: 4,
                 parent: 3,
+                reportLeafIndex: 1,
               },
             ],
             id: 3,
             parent: 2,
+            reportLeafIndex: undefined,
           },
         ],
         id: 2,
         parent: 1,
+        reportLeafIndex: undefined,
       },
       {
         name: "noStack",
         bytes: 60,
         totalBytes: 60,
         count: 6,
         totalCount: 6,
         children: undefined,
         id: 10,
         parent: 1,
+        reportLeafIndex: 6,
       },
       {
         name: stack5,
         bytes: 50,
         totalBytes: 50,
         count: 5,
         totalCount: 5,
         children: undefined,
         id: 9,
         parent: 1,
+        reportLeafIndex: 5
       },
     ],
     id: 1,
     parent: undefined,
+    reportLeafIndex: undefined,
   };
 
   compareCensusViewData(BREAKDOWN, REPORT, EXPECTED);
 }
--- a/devtools/shared/heapsnapshot/tests/unit/test_census-tree-node-05.js
+++ b/devtools/shared/heapsnapshot/tests/unit/test_census-tree-node-05.js
@@ -69,68 +69,77 @@ function run_test() {
                     name: "other",
                     bytes: 40,
                     totalBytes: 40,
                     count: 4,
                     totalCount: 4,
                     children: undefined,
                     id: 8,
                     parent: 4,
+                    reportLeafIndex: 5,
                   },
                   {
                     name: "Baz",
                     bytes: 30,
                     totalBytes: 30,
                     count: 3,
                     totalCount: 3,
                     children: undefined,
                     id: 7,
                     parent: 4,
+                    reportLeafIndex: 4,
                   },
                   {
                     name: "Bar",
                     bytes: 20,
                     totalBytes: 20,
                     count: 2,
                     totalCount: 2,
                     children: undefined,
                     id: 6,
                     parent: 4,
+                    reportLeafIndex: 3,
                   },
                   {
                     name: "Foo",
                     bytes: 10,
                     totalBytes: 10,
                     count: 1,
                     totalCount: 1,
                     children: undefined,
                     id: 5,
                     parent: 4,
+                    reportLeafIndex: 2,
                   },
                 ],
                 id: 4,
                 parent: 3,
+                reportLeafIndex: undefined,
               }
             ],
             id: 3,
             parent: 2,
+            reportLeafIndex: undefined,
           }
         ],
         id: 2,
         parent: 1,
+        reportLeafIndex: undefined,
       },
       {
         name: "noStack",
         bytes: 50,
         totalBytes: 50,
         count: 5,
         totalCount: 5,
         children: undefined,
         id: 9,
         parent: 1,
+        reportLeafIndex: 6,
       },
     ],
     id: 1,
     parent: undefined,
+    reportLeafIndex: undefined,
   };
 
   compareCensusViewData(BREAKDOWN, REPORT, EXPECTED);
 }
--- a/devtools/shared/heapsnapshot/tests/unit/test_census-tree-node-06.js
+++ b/devtools/shared/heapsnapshot/tests/unit/test_census-tree-node-06.js
@@ -63,20 +63,22 @@ function run_test() {
             name: null,
             bytes: 0,
             totalBytes: 100,
             count: 0,
             totalCount: 10,
             children: undefined,
             id: 16,
             parent: 15,
+            reportLeafIndex: undefined,
           }
         ],
         id: 15,
         parent: 14,
+        reportLeafIndex: 6,
       },
       {
         name: abc_Stack,
         bytes: 50,
         totalBytes: 10,
         count: 5,
         totalCount: 1,
         children: [
@@ -84,16 +86,17 @@ function run_test() {
             name: null,
             bytes: 0,
             totalBytes: 100,
             count: 0,
             totalCount: 10,
             children: undefined,
             id: 18,
             parent: 17,
+            reportLeafIndex: undefined,
           },
           {
             name: abc_Stack.parent,
             bytes: 0,
             totalBytes: 10,
             count: 0,
             totalCount: 1,
             children: [
@@ -101,16 +104,17 @@ function run_test() {
                 name: null,
                 bytes: 0,
                 totalBytes: 100,
                 count: 0,
                 totalCount: 10,
                 children: undefined,
                 id: 22,
                 parent: 19,
+                reportLeafIndex: undefined,
               },
               {
                 name: abc_Stack.parent.parent,
                 bytes: 0,
                 totalBytes: 10,
                 count: 0,
                 totalCount: 1,
                 children: [
@@ -118,20 +122,22 @@ function run_test() {
                     name: null,
                     bytes: 0,
                     totalBytes: 100,
                     count: 0,
                     totalCount: 10,
                     children: undefined,
                     id: 21,
                     parent: 20,
+                    reportLeafIndex: undefined,
                   }
                 ],
                 id: 20,
                 parent: 19,
+                reportLeafIndex: undefined,
               },
               {
                 name: dbc_Stack.parent.parent,
                 bytes: 0,
                 totalBytes: 10,
                 count: 0,
                 totalCount: 1,
                 children: [
@@ -139,24 +145,27 @@ function run_test() {
                     name: null,
                     bytes: 0,
                     totalBytes: 100,
                     count: 0,
                     totalCount: 10,
                     children: undefined,
                     id: 24,
                     parent: 23,
+                    reportLeafIndex: undefined,
                   }
                 ],
                 id: 23,
                 parent: 19,
+                reportLeafIndex: undefined,
               }
             ],
             id: 19,
             parent: 17,
+            reportLeafIndex: undefined,
           },
           {
             name: ec_Stack.parent,
             bytes: 0,
             totalBytes: 10,
             count: 0,
             totalCount: 1,
             children: [
@@ -164,24 +173,28 @@ function run_test() {
                 name: null,
                 bytes: 0,
                 totalBytes: 100,
                 count: 0,
                 totalCount: 10,
                 children: undefined,
                 id: 26,
                 parent: 25,
+                reportLeafIndex: undefined,
               },
             ],
             id: 25,
             parent: 17,
+            reportLeafIndex: undefined,
           },
         ],
         id: 17,
         parent: 14,
+        reportLeafIndex: new Set([1, 2, 3, 4, 5]),
       }
     ],
     id: 14,
     parent: undefined,
+    reportLeafIndex: undefined,
   };
 
   compareCensusViewData(BREAKDOWN, REPORT, EXPECTED, { invert: true });
 }
--- a/devtools/shared/heapsnapshot/tests/unit/test_census-tree-node-07.js
+++ b/devtools/shared/heapsnapshot/tests/unit/test_census-tree-node-07.js
@@ -68,24 +68,27 @@ function run_test() {
                 name: null,
                 bytes: 0,
                 totalBytes: 220,
                 count: 0,
                 totalCount: 22,
                 children: undefined,
                 id: 14,
                 parent: 13,
+                reportLeafIndex: undefined,
               }
             ],
             id: 13,
             parent: 12,
+            reportLeafIndex: undefined,
           }
         ],
         id: 12,
         parent: 11,
+        reportLeafIndex: 9,
       },
       {
         name: "JSAtom",
         bytes: 60,
         totalBytes: 60,
         count: 6,
         totalCount: 6,
         children: [
@@ -100,24 +103,27 @@ function run_test() {
                 name: null,
                 bytes: 0,
                 totalBytes: 220,
                 count: 0,
                 totalCount: 22,
                 children: undefined,
                 id: 17,
                 parent: 16,
+                reportLeafIndex: undefined,
               }
             ],
             id: 16,
             parent: 15,
+            reportLeafIndex: undefined,
           }
         ],
         id: 15,
         parent: 11,
+        reportLeafIndex: 7,
       },
       {
         name: "Array",
         bytes: 50,
         totalBytes: 50,
         count: 5,
         totalCount: 5,
         children: [
@@ -132,24 +138,27 @@ function run_test() {
                 name: null,
                 bytes: 0,
                 totalBytes: 220,
                 count: 0,
                 totalCount: 22,
                 children: undefined,
                 id: 20,
                 parent: 19,
+                reportLeafIndex: undefined,
               }
             ],
             id: 19,
             parent: 18,
+            reportLeafIndex: undefined,
           }
         ],
         id: 18,
         parent: 11,
+        reportLeafIndex: 2,
       },
       {
         name: "js::jit::JitScript",
         bytes: 30,
         totalBytes: 30,
         count: 3,
         totalCount: 3,
         children: [
@@ -164,24 +173,28 @@ function run_test() {
                 name: null,
                 bytes: 0,
                 totalBytes: 220,
                 count: 0,
                 totalCount: 22,
                 children: undefined,
                 id: 23,
                 parent: 22,
+                reportLeafIndex: undefined,
               }
             ],
             id: 22,
             parent: 21,
+            reportLeafIndex: undefined,
           }
         ],
         id: 21,
         parent: 11,
+        reportLeafIndex: 5,
       },
     ],
     id: 11,
     parent: undefined,
+    reportLeafIndex: undefined,
   };
 
   compareCensusViewData(BREAKDOWN, REPORT, EXPECTED, { invert: true });
 }
--- a/devtools/shared/heapsnapshot/tests/unit/test_census-tree-node-08.js
+++ b/devtools/shared/heapsnapshot/tests/unit/test_census-tree-node-08.js
@@ -50,84 +50,93 @@ function run_test() {
           {
             name: "js::jit::JitCode",
             bytes: 400,
             totalBytes: 400,
             count: 40,
             totalCount: 40,
             children: undefined,
             id: 9,
-            parent: 8
+            parent: 8,
+            reportLeafIndex: 8,
           }
         ],
         id: 8,
-        parent: 1
+        parent: 1,
+        reportLeafIndex: undefined,
       },
       {
         name: "http://example.com/trackers.js",
         bytes: 0,
         totalBytes: 300,
         count: 0,
         totalCount: 30,
         children: [
           {
             name: "JSScript",
             bytes: 300,
             totalBytes: 300,
             count: 30,
             totalCount: 30,
             children: undefined,
             id: 7,
-            parent: 6
+            parent: 6,
+            reportLeafIndex: 6,
           }
         ],
         id: 6,
-        parent: 1
+        parent: 1,
+        reportLeafIndex: undefined,
       },
       {
         name: "http://example.com/ads.js",
         bytes: 0,
         totalBytes: 200,
         count: 0,
         totalCount: 20,
         children: [
           {
             name: "js::LazyScript",
             bytes: 200,
             totalBytes: 200,
             count: 20,
             totalCount: 20,
             children: undefined,
             id: 5,
-            parent: 4
+            parent: 4,
+            reportLeafIndex: 4,
           }
         ],
         id: 4,
-        parent: 1
+        parent: 1,
+        reportLeafIndex: undefined,
       },
       {
         name: "http://example.com/app.js",
         bytes: 0,
         totalBytes: 100,
         count: 0,
         totalCount: 10,
         children: [
           {
             name: "JSScript",
             bytes: 100,
             totalBytes: 100,
             count: 10,
             totalCount: 10,
             children: undefined,
             id: 3,
-            parent: 2
+            parent: 2,
+            reportLeafIndex: 2,
           }
         ],
         id: 2,
-        parent: 1
+        parent: 1,
+        reportLeafIndex: undefined,
       }
     ],
     id: 1,
     parent: undefined,
+    reportLeafIndex: undefined,
   };
 
   compareCensusViewData(BREAKDOWN, REPORT, EXPECTED);
 }
new file mode 100644
--- /dev/null
+++ b/devtools/shared/heapsnapshot/tests/unit/test_census-tree-node-10.js
@@ -0,0 +1,43 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+"use strict";
+
+/**
+ * Test when multiple leaves in the census report map to the same node in an
+ * inverted CensusReportTree.
+ */
+
+function run_test() {
+  const BREAKDOWN = {
+    by: "coarseType",
+    objects: {
+      by: "objectClass",
+      then: { by: "count", count: true, bytes: true },
+    },
+    other: {
+      by: "internalType",
+      then: { by: "count", count: true, bytes: true },
+    },
+    strings: { by: "count", count: true, bytes: true },
+    scripts: { by: "count", count: true, bytes: true },
+  };
+
+  const REPORT = {
+    objects: {
+      Array: { count: 1, bytes: 10 },
+    },
+    other: {
+      Array: { count: 1, bytes: 10 },
+    },
+    strings: { count: 0, bytes: 0 },
+    scripts: { count: 0, bytes: 0 },
+  };
+
+  const node = censusReportToCensusTreeNode(BREAKDOWN, REPORT, { invert: true });
+
+  equal(node.children[0].name, "Array");
+  equal(node.children[0].reportLeafIndex.size, 2);
+  dumpn(`node.children[0].reportLeafIndex = ${[...node.children[0].reportLeafIndex]}`);
+  ok(node.children[0].reportLeafIndex.has(2));
+  ok(node.children[0].reportLeafIndex.has(6));
+}
--- a/devtools/shared/heapsnapshot/tests/unit/test_census_filtering_01.js
+++ b/devtools/shared/heapsnapshot/tests/unit/test_census_filtering_01.js
@@ -60,41 +60,46 @@ function run_test() {
           {
             name: "Int32Array",
             bytes: 320,
             totalBytes: 320,
             count: 32,
             totalCount: 32,
             children: undefined,
             id: 15,
-            parent: 14
+            parent: 14,
+            reportLeafIndex: 4,
           },
           {
             name: "UInt8Array",
             bytes: 80,
             totalBytes: 80,
             count: 8,
             totalCount: 8,
             children: undefined,
             id: 16,
-            parent: 14
+            parent: 14,
+            reportLeafIndex: 3,
           },
           {
             name: "Array",
             bytes: 50,
             totalBytes: 50,
             count: 5,
             totalCount: 5,
             children: undefined,
             id: 17,
-            parent: 14
+            parent: 14,
+            reportLeafIndex: 2,
           }
         ],
         id: 14,
-        parent: 13
+        parent: 13,
+        reportLeafIndex: undefined,
       }
     ],
     id: 13,
     parent: undefined,
+    reportLeafIndex: undefined,
   };
 
   compareCensusViewData(BREAKDOWN, REPORT, EXPECTED, { filter: "Array" });
 }
--- a/devtools/shared/heapsnapshot/tests/unit/test_census_filtering_02.js
+++ b/devtools/shared/heapsnapshot/tests/unit/test_census_filtering_02.js
@@ -62,56 +62,63 @@ function run_test() {
               {
                 name: stack3,
                 bytes: 30,
                 totalBytes: 30,
                 count: 3,
                 totalCount: 3,
                 children: undefined,
                 id: 15,
-                parent: 14
+                parent: 14,
+                reportLeafIndex: 3,
               },
               {
                 name: stack2,
                 bytes: 20,
                 totalBytes: 20,
                 count: 2,
                 totalCount: 2,
                 children: undefined,
                 id: 16,
-                parent: 14
+                parent: 14,
+                reportLeafIndex: 2,
               }
             ],
             id: 14,
-            parent: 13
+            parent: 13,
+            reportLeafIndex: undefined,
           },
           {
             name: stack1.parent,
             bytes: 0,
             totalBytes: 10,
             count: 0,
             totalCount: 1,
             children: [
               {
                 name: stack1,
                 bytes: 10,
                 totalBytes: 10,
                 count: 1,
                 totalCount: 1,
                 children: undefined,
                 id: 18,
-                parent: 17
+                parent: 17,
+                reportLeafIndex: 1,
               }
             ],
             id: 17,
-            parent: 13
+            parent: 13,
+            reportLeafIndex: undefined,
           }
         ],
         id: 13,
-        parent: 12
+        parent: 12,
+        reportLeafIndex: undefined,
       }
     ],
     id: 12,
-    parent: undefined
+    parent: undefined,
+    reportLeafIndex: undefined,
   };
 
   compareCensusViewData(BREAKDOWN, REPORT, EXPECTED, { filter: "bar" });
 }
--- a/devtools/shared/heapsnapshot/tests/unit/test_census_filtering_03.js
+++ b/devtools/shared/heapsnapshot/tests/unit/test_census_filtering_03.js
@@ -47,12 +47,13 @@ function run_test() {
     name: null,
     bytes: 0,
     totalBytes: 620,
     count: 0,
     totalCount: 62,
     children: undefined,
     id: 13,
     parent: undefined,
+    reportLeafIndex: undefined,
   };
 
   compareCensusViewData(BREAKDOWN, REPORT, EXPECTED, { filter: "zzzzzzzzzzzzzzzzzzzz" });
 }
--- a/devtools/shared/heapsnapshot/tests/unit/test_census_filtering_04.js
+++ b/devtools/shared/heapsnapshot/tests/unit/test_census_filtering_04.js
@@ -69,30 +69,34 @@ function run_test() {
           {
             name: "Function",
             bytes: 70,
             totalBytes: 70,
             count: 7,
             totalCount: 7,
             id: 13,
             parent: 12,
-            children: undefined
+            children: undefined,
+            reportLeafIndex: 2,
           },
           {
             name: "Array",
             bytes: 60,
             totalBytes: 60,
             count: 6,
             totalCount: 6,
             id: 14,
             parent: 12,
-            children: undefined
+            children: undefined,
+            reportLeafIndex: 3,
           },
         ],
         id: 12,
-        parent: 11
+        parent: 11,
+        reportLeafIndex: undefined,
       }
     ],
-    id: 11
+    id: 11,
+    reportLeafIndex: undefined,
   };
 
   compareCensusViewData(BREAKDOWN, REPORT, EXPECTED, { filter: "objects" });
 }
--- a/devtools/shared/heapsnapshot/tests/unit/xpcshell.ini
+++ b/devtools/shared/heapsnapshot/tests/unit/xpcshell.ini
@@ -26,16 +26,17 @@ support-files =
 [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_census-tree-node-06.js]
 [test_census-tree-node-07.js]
 [test_census-tree-node-08.js]
 [test_census-tree-node-09.js]
+[test_census-tree-node-10.js]
 [test_countToBucketBreakdown_01.js]
 [test_deduplicatePaths_01.js]
 [test_DominatorTree_01.js]
 [test_DominatorTree_02.js]
 [test_DominatorTree_03.js]
 [test_DominatorTree_04.js]
 [test_DominatorTree_05.js]
 [test_DominatorTree_06.js]