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 291275 9c04c66d849bae2c29518f81a3d6a13068ef485f
parent 291274 49adb658e099d5b38c24c5c4cf34e603c0e12afc
child 291276 537e8f7a4950944c6835d67f76e8e899682209b3
push id74545
push userkwierso@gmail.com
push dateFri, 01 Apr 2016 23:05:42 +0000
treeherdermozilla-inbound@c410d4e20586 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjimb, kwierso
bugs1260939
milestone48.0a1
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
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]