Bug 1260939 - Add a method for getting census individuals and their shortest paths to HeapAnalyses{Client,Worker}; r=jimb a=kwierso
authorNick Fitzgerald <fitzgen@gmail.com>
Thu, 31 Mar 2016 16:19:59 -0700
changeset 291302 9c04c66d849bae2c29518f81a3d6a13068ef485f
parent 291301 49adb658e099d5b38c24c5c4cf34e603c0e12afc
child 291303 537e8f7a4950944c6835d67f76e8e899682209b3
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, kwierso
bugs1260939
milestone48.0a1
Bug 1260939 - Add a method for getting census individuals and their shortest paths to HeapAnalyses{Client,Worker}; r=jimb a=kwierso MozReview-Commit-ID: HHJ2masZB4k
devtools/shared/heapsnapshot/HeapAnalysesClient.js
devtools/shared/heapsnapshot/HeapAnalysesWorker.js
devtools/shared/heapsnapshot/tests/unit/test_HeapAnalyses_getCensusIndividuals_01.js
devtools/shared/heapsnapshot/tests/unit/xpcshell.ini
--- a/devtools/shared/heapsnapshot/HeapAnalysesClient.js
+++ b/devtools/shared/heapsnapshot/HeapAnalysesClient.js
@@ -120,16 +120,45 @@ HeapAnalysesClient.prototype.takeCensus 
   return this._worker.performTask("takeCensus", {
     snapshotFilePath,
     censusOptions,
     requestOptions,
   });
 };
 
 /**
+ * Get the individual nodes that correspond to the given census report leaf
+ * indices.
+ *
+ * @param {Object} opts
+ *        An object with the following properties:
+ *        - {DominatorTreeId} dominatorTreeId: The id of the dominator tree.
+ *        - {Set<Number>} indices: The indices of the census report leaves we
+ *          would like to get the individuals for.
+ *        - {Object} censusBreakdown: The breakdown used to generate the census.
+ *        - {Object} labelBreakdown: The breakdown we would like to use when
+ *          labeling the resulting nodes.
+ *        - {Number} maxRetainingPaths: The maximum number of retaining paths to
+ *          compute for each node.
+ *        - {Number} maxIndividuals: The maximum number of individual nodes to
+ *          return.
+ *
+ * @returns {Promise<Object>}
+ *          A promise of an object with the following properties:
+ *          - {Array<DominatorTreeNode>} nodes: An array of `DominatorTreeNode`s
+ *            with their shortest paths attached, and without any dominator tree
+ *            child/parent information attached. The results are sorted by
+ *            retained size.
+ *
+ */
+HeapAnalysesClient.prototype.getCensusIndividuals = function(opts) {
+  return this._worker.performTask("getCensusIndividuals", opts);
+};
+
+/**
  * Request that the worker take a census on the heap snapshots with the given
  * paths and then return the difference between them. Both heap snapshots must
  * have already been read into memory by the worker (see `readHeapSnapshot`).
  *
  * @param {String} firstSnapshotFilePath
  *        The first snapshot file path.
  *
  * @param {String} secondSnapshotFilePath
--- a/devtools/shared/heapsnapshot/HeapAnalysesWorker.js
+++ b/devtools/shared/heapsnapshot/HeapAnalysesWorker.js
@@ -14,21 +14,38 @@ importScripts("resource://gre/modules/wo
 importScripts("resource://devtools/shared/worker/helper.js");
 const { censusReportToCensusTreeNode } = require("resource://devtools/shared/heapsnapshot/census-tree-node.js");
 const DominatorTreeNode = require("resource://devtools/shared/heapsnapshot/DominatorTreeNode.js");
 const CensusUtils = require("resource://devtools/shared/heapsnapshot/CensusUtils.js");
 
 const DEFAULT_START_INDEX = 0;
 const DEFAULT_MAX_COUNT = 50;
 
-// The set of HeapSnapshot instances this worker has read into memory. Keyed by
-// snapshot file path.
+/**
+ * The set of HeapSnapshot instances this worker has read into memory. Keyed by
+ * snapshot file path.
+ */
 const snapshots = Object.create(null);
 
 /**
+ * The set of `DominatorTree`s that have been computed, mapped by their id (aka
+ * the index into this array).
+ *
+ * @see /dom/webidl/DominatorTree.webidl
+ */
+const dominatorTrees = [];
+
+/**
+ * The i^th HeapSnapshot in this array is the snapshot used to generate the i^th
+ * dominator tree in `dominatorTrees` above. This lets us map from a dominator
+ * tree id to the snapshot it came from.
+ */
+const dominatorTreeSnapshots = [];
+
+/**
  * @see HeapAnalysesClient.prototype.readHeapSnapshot
  */
 workerHelper.createTask(self, "readHeapSnapshot", ({ snapshotFilePath }) => {
   snapshots[snapshotFilePath] =
     ThreadSafeChromeUtils.readHeapSnapshot(snapshotFilePath);
   return true;
 });
 
@@ -69,16 +86,59 @@ workerHelper.createTask(self, "takeCensu
     report = censusReportToCensusTreeNode(censusOptions.breakdown, report, opts);
     parentMap = CensusUtils.createParentMap(report);
   }
 
   return { report, parentMap };
 });
 
 /**
+ * @see HeapAnalysesClient.prototype.getCensusIndividuals
+ */
+workerHelper.createTask(self, "getCensusIndividuals", request => {
+  const {
+    dominatorTreeId,
+    indices,
+    censusBreakdown,
+    labelBreakdown,
+    maxRetainingPaths,
+    maxIndividuals,
+  } = request;
+
+  const dominatorTree = dominatorTrees[dominatorTreeId];
+  if (!dominatorTree) {
+    throw new Error(
+      `There does not exist a DominatorTree with the id ${dominatorTreeId}`);
+  }
+
+  const snapshot = dominatorTreeSnapshots[dominatorTreeId];
+  const nodeIds = CensusUtils.getCensusIndividuals(indices, censusBreakdown, snapshot);
+
+  const nodes = nodeIds
+    .sort((a, b) => dominatorTree.getRetainedSize(b) - dominatorTree.getRetainedSize(a))
+    .slice(0, maxIndividuals)
+    .map(id => {
+      const { label, shallowSize } =
+        DominatorTreeNode.getLabelAndShallowSize(id, snapshot, labelBreakdown);
+      const retainedSize = dominatorTree.getRetainedSize(id);
+      const node = new DominatorTreeNode(id, label, shallowSize, retainedSize);
+      node.moreChildrenAvailable = false;
+      return node;
+    });
+
+  DominatorTreeNode.attachShortestPaths(snapshot,
+                                        labelBreakdown,
+                                        dominatorTree.root,
+                                        nodes,
+                                        maxRetainingPaths);
+
+  return { nodes };
+});
+
+/**
  * @see HeapAnalysesClient.prototype.takeCensusDiff
  */
 workerHelper.createTask(self, "takeCensusDiff", request => {
   const {
     firstSnapshotFilePath,
     secondSnapshotFilePath,
     censusOptions,
     requestOptions
@@ -115,31 +175,16 @@ workerHelper.createTask(self, "takeCensu
 workerHelper.createTask(self, "getCreationTime", snapshotFilePath => {
   if (!snapshots[snapshotFilePath]) {
     throw new Error(`No known heap snapshot for '${snapshotFilePath}'`);
   }
   return snapshots[snapshotFilePath].creationTime;
 });
 
 /**
- * The set of `DominatorTree`s that have been computed, mapped by their id (aka
- * the index into this array).
- *
- * @see /dom/webidl/DominatorTree.webidl
- */
-const dominatorTrees = [];
-
-/**
- * The i^th HeapSnapshot in this array is the snapshot used to generate the i^th
- * dominator tree in `dominatorTrees` above. This lets us map from a dominator
- * tree id to the snapshot it came from.
- */
-const dominatorTreeSnapshots = [];
-
-/**
  * @see HeapAnalysesClient.prototype.computeDominatorTree
  */
 workerHelper.createTask(self, "computeDominatorTree", snapshotFilePath => {
   const snapshot = snapshots[snapshotFilePath];
   if (!snapshot) {
     throw new Error(`No known heap snapshot for '${snapshotFilePath}'`);
   }
 
new file mode 100644
--- /dev/null
+++ b/devtools/shared/heapsnapshot/tests/unit/test_HeapAnalyses_getCensusIndividuals_01.js
@@ -0,0 +1,89 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// Test that the HeapAnalyses{Client,Worker} can get census individuals.
+
+function run_test() {
+  run_next_test();
+}
+
+const COUNT = { by: "count", count: true, bytes: true };
+
+const CENSUS_BREAKDOWN = {
+  by: "coarseType",
+  objects: COUNT,
+  strings: COUNT,
+  scripts: COUNT,
+  other: COUNT,
+};
+
+const LABEL_BREAKDOWN = {
+  by: "internalType",
+  then: COUNT,
+};
+
+const MAX_INDIVIDUALS = 10;
+
+add_task(function* () {
+  const client = new HeapAnalysesClient();
+
+  const snapshotFilePath = saveNewHeapSnapshot();
+  yield client.readHeapSnapshot(snapshotFilePath);
+  ok(true, "Should have read the heap snapshot");
+
+  const dominatorTreeId = yield client.computeDominatorTree(snapshotFilePath);
+  ok(true, "Should have computed dominator tree");
+
+  const { report } = yield client.takeCensus(snapshotFilePath,
+                                             { breakdown: CENSUS_BREAKDOWN },
+                                             { asTreeNode: true });
+  ok(report, "Should get a report");
+
+  let nodesWithLeafIndicesFound = 0;
+
+  yield* (function* assertCanGetIndividuals(censusNode) {
+    if (censusNode.reportLeafIndex !== undefined) {
+      nodesWithLeafIndicesFound++;
+
+      const response = yield client.getCensusIndividuals({
+        dominatorTreeId,
+        indices: DevToolsUtils.isSet(censusNode.reportLeafIndex)
+          ? censusNode.reportLeafIndex
+          : new Set([censusNode.reportLeafIndex]),
+        censusBreakdown: CENSUS_BREAKDOWN,
+        labelBreakdown: LABEL_BREAKDOWN,
+        maxRetainingPaths: 1,
+        maxIndividuals: MAX_INDIVIDUALS,
+      });
+
+      dumpn(`response = ${JSON.stringify(response, null, 4)}`);
+
+      equal(response.nodes.length, Math.min(MAX_INDIVIDUALS, censusNode.count),
+         "response.nodes.length === Math.min(MAX_INDIVIDUALS, censusNode.count)");
+
+      let lastRetainedSize = Infinity;
+      for (let individual of response.nodes) {
+        equal(typeof individual.nodeId, "number",
+              "individual.nodeId should be a number");
+        ok(individual.retainedSize <= lastRetainedSize,
+           "individual.retainedSize <= lastRetainedSize");
+        lastRetainedSize = individual.retainedSize;
+        ok(individual.shallowSize, "individual.shallowSize should exist and be non-zero");
+        ok(individual.shortestPaths, "individual.shortestPaths should exist");
+        ok(individual.shortestPaths.nodes, "individual.shortestPaths.nodes should exist");
+        ok(individual.shortestPaths.edges, "individual.shortestPaths.edges should exist");
+        ok(individual.label, "individual.label should exist");
+      }
+    }
+
+    if (censusNode.children) {
+      for (let child of censusNode.children) {
+        yield* assertCanGetIndividuals(child);
+      }
+    }
+  }(report));
+
+  equal(nodesWithLeafIndicesFound, 4, "Should have found a leaf for each coarse type");
+
+  client.destroy();
+});
--- a/devtools/shared/heapsnapshot/tests/unit/xpcshell.ini
+++ b/devtools/shared/heapsnapshot/tests/unit/xpcshell.ini
@@ -52,16 +52,17 @@ support-files =
 [test_DominatorTreeNode_partialTraversal_01.js]
 [test_getCensusIndividuals_01.js]
 [test_getReportLeaves_01.js]
 [test_HeapAnalyses_computeDominatorTree_01.js]
 [test_HeapAnalyses_computeDominatorTree_02.js]
 [test_HeapAnalyses_deleteHeapSnapshot_01.js]
 [test_HeapAnalyses_deleteHeapSnapshot_02.js]
 [test_HeapAnalyses_deleteHeapSnapshot_03.js]
+[test_HeapAnalyses_getCensusIndividuals_01.js]
 [test_HeapAnalyses_getCreationTime_01.js]
 [test_HeapAnalyses_getDominatorTree_01.js]
 [test_HeapAnalyses_getDominatorTree_02.js]
 [test_HeapAnalyses_getImmediatelyDominated_01.js]
 [test_HeapAnalyses_readHeapSnapshot_01.js]
 [test_HeapAnalyses_takeCensusDiff_01.js]
 [test_HeapAnalyses_takeCensusDiff_02.js]
 [test_HeapAnalyses_takeCensus_01.js]