HeapAnalysesWorker.js 9.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304
  1. /* This Source Code Form is subject to the terms of the Mozilla Public
  2. * License, v. 2.0. If a copy of the MPL was not distributed with this
  3. * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
  4. /* global ThreadSafeChromeUtils*/
  5. // This is a worker which reads offline heap snapshots into memory and performs
  6. // heavyweight analyses on them without blocking the main thread. A
  7. // HeapAnalysesWorker is owned and communicated with by a HeapAnalysesClient
  8. // instance. See HeapAnalysesClient.js.
  9. "use strict";
  10. importScripts("resource://gre/modules/workers/require.js");
  11. importScripts("resource://devtools/shared/worker/helper.js");
  12. const { censusReportToCensusTreeNode } = require("resource://devtools/shared/heapsnapshot/census-tree-node.js");
  13. const DominatorTreeNode = require("resource://devtools/shared/heapsnapshot/DominatorTreeNode.js");
  14. const CensusUtils = require("resource://devtools/shared/heapsnapshot/CensusUtils.js");
  15. const DEFAULT_START_INDEX = 0;
  16. const DEFAULT_MAX_COUNT = 50;
  17. /**
  18. * The set of HeapSnapshot instances this worker has read into memory. Keyed by
  19. * snapshot file path.
  20. */
  21. const snapshots = Object.create(null);
  22. /**
  23. * The set of `DominatorTree`s that have been computed, mapped by their id (aka
  24. * the index into this array).
  25. *
  26. * @see /dom/webidl/DominatorTree.webidl
  27. */
  28. const dominatorTrees = [];
  29. /**
  30. * The i^th HeapSnapshot in this array is the snapshot used to generate the i^th
  31. * dominator tree in `dominatorTrees` above. This lets us map from a dominator
  32. * tree id to the snapshot it came from.
  33. */
  34. const dominatorTreeSnapshots = [];
  35. /**
  36. * @see HeapAnalysesClient.prototype.readHeapSnapshot
  37. */
  38. workerHelper.createTask(self, "readHeapSnapshot", ({ snapshotFilePath }) => {
  39. snapshots[snapshotFilePath] =
  40. ThreadSafeChromeUtils.readHeapSnapshot(snapshotFilePath);
  41. return true;
  42. });
  43. /**
  44. * @see HeapAnalysesClient.prototype.deleteHeapSnapshot
  45. */
  46. workerHelper.createTask(self, "deleteHeapSnapshot", ({ snapshotFilePath }) => {
  47. let snapshot = snapshots[snapshotFilePath];
  48. if (!snapshot) {
  49. throw new Error(`No known heap snapshot for '${snapshotFilePath}'`);
  50. }
  51. snapshots[snapshotFilePath] = undefined;
  52. let dominatorTreeId = dominatorTreeSnapshots.indexOf(snapshot);
  53. if (dominatorTreeId != -1) {
  54. dominatorTreeSnapshots[dominatorTreeId] = undefined;
  55. dominatorTrees[dominatorTreeId] = undefined;
  56. }
  57. });
  58. /**
  59. * @see HeapAnalysesClient.prototype.takeCensus
  60. */
  61. workerHelper.createTask(self, "takeCensus", ({ snapshotFilePath, censusOptions, requestOptions }) => {
  62. if (!snapshots[snapshotFilePath]) {
  63. throw new Error(`No known heap snapshot for '${snapshotFilePath}'`);
  64. }
  65. let report = snapshots[snapshotFilePath].takeCensus(censusOptions);
  66. let parentMap;
  67. if (requestOptions.asTreeNode || requestOptions.asInvertedTreeNode) {
  68. const opts = { filter: requestOptions.filter || null };
  69. if (requestOptions.asInvertedTreeNode) {
  70. opts.invert = true;
  71. }
  72. report = censusReportToCensusTreeNode(censusOptions.breakdown, report, opts);
  73. parentMap = CensusUtils.createParentMap(report);
  74. }
  75. return { report, parentMap };
  76. });
  77. /**
  78. * @see HeapAnalysesClient.prototype.getCensusIndividuals
  79. */
  80. workerHelper.createTask(self, "getCensusIndividuals", request => {
  81. const {
  82. dominatorTreeId,
  83. indices,
  84. censusBreakdown,
  85. labelBreakdown,
  86. maxRetainingPaths,
  87. maxIndividuals,
  88. } = request;
  89. const dominatorTree = dominatorTrees[dominatorTreeId];
  90. if (!dominatorTree) {
  91. throw new Error(
  92. `There does not exist a DominatorTree with the id ${dominatorTreeId}`);
  93. }
  94. const snapshot = dominatorTreeSnapshots[dominatorTreeId];
  95. const nodeIds = CensusUtils.getCensusIndividuals(indices, censusBreakdown, snapshot);
  96. const nodes = nodeIds
  97. .sort((a, b) => dominatorTree.getRetainedSize(b) - dominatorTree.getRetainedSize(a))
  98. .slice(0, maxIndividuals)
  99. .map(id => {
  100. const { label, shallowSize } =
  101. DominatorTreeNode.getLabelAndShallowSize(id, snapshot, labelBreakdown);
  102. const retainedSize = dominatorTree.getRetainedSize(id);
  103. const node = new DominatorTreeNode(id, label, shallowSize, retainedSize);
  104. node.moreChildrenAvailable = false;
  105. return node;
  106. });
  107. DominatorTreeNode.attachShortestPaths(snapshot,
  108. labelBreakdown,
  109. dominatorTree.root,
  110. nodes,
  111. maxRetainingPaths);
  112. return { nodes };
  113. });
  114. /**
  115. * @see HeapAnalysesClient.prototype.takeCensusDiff
  116. */
  117. workerHelper.createTask(self, "takeCensusDiff", request => {
  118. const {
  119. firstSnapshotFilePath,
  120. secondSnapshotFilePath,
  121. censusOptions,
  122. requestOptions
  123. } = request;
  124. if (!snapshots[firstSnapshotFilePath]) {
  125. throw new Error(`No known heap snapshot for '${firstSnapshotFilePath}'`);
  126. }
  127. if (!snapshots[secondSnapshotFilePath]) {
  128. throw new Error(`No known heap snapshot for '${secondSnapshotFilePath}'`);
  129. }
  130. const first = snapshots[firstSnapshotFilePath].takeCensus(censusOptions);
  131. const second = snapshots[secondSnapshotFilePath].takeCensus(censusOptions);
  132. let delta = CensusUtils.diff(censusOptions.breakdown, first, second);
  133. let parentMap;
  134. if (requestOptions.asTreeNode || requestOptions.asInvertedTreeNode) {
  135. const opts = { filter: requestOptions.filter || null };
  136. if (requestOptions.asInvertedTreeNode) {
  137. opts.invert = true;
  138. }
  139. delta = censusReportToCensusTreeNode(censusOptions.breakdown, delta, opts);
  140. parentMap = CensusUtils.createParentMap(delta);
  141. }
  142. return { delta, parentMap };
  143. });
  144. /**
  145. * @see HeapAnalysesClient.prototype.getCreationTime
  146. */
  147. workerHelper.createTask(self, "getCreationTime", snapshotFilePath => {
  148. if (!snapshots[snapshotFilePath]) {
  149. throw new Error(`No known heap snapshot for '${snapshotFilePath}'`);
  150. }
  151. return snapshots[snapshotFilePath].creationTime;
  152. });
  153. /**
  154. * @see HeapAnalysesClient.prototype.computeDominatorTree
  155. */
  156. workerHelper.createTask(self, "computeDominatorTree", snapshotFilePath => {
  157. const snapshot = snapshots[snapshotFilePath];
  158. if (!snapshot) {
  159. throw new Error(`No known heap snapshot for '${snapshotFilePath}'`);
  160. }
  161. const id = dominatorTrees.length;
  162. dominatorTrees.push(snapshot.computeDominatorTree());
  163. dominatorTreeSnapshots.push(snapshot);
  164. return id;
  165. });
  166. /**
  167. * @see HeapAnalysesClient.prototype.getDominatorTree
  168. */
  169. workerHelper.createTask(self, "getDominatorTree", request => {
  170. const {
  171. dominatorTreeId,
  172. breakdown,
  173. maxDepth,
  174. maxSiblings,
  175. maxRetainingPaths,
  176. } = request;
  177. if (!(0 <= dominatorTreeId && dominatorTreeId < dominatorTrees.length)) {
  178. throw new Error(
  179. `There does not exist a DominatorTree with the id ${dominatorTreeId}`);
  180. }
  181. const dominatorTree = dominatorTrees[dominatorTreeId];
  182. const snapshot = dominatorTreeSnapshots[dominatorTreeId];
  183. const tree = DominatorTreeNode.partialTraversal(dominatorTree,
  184. snapshot,
  185. breakdown,
  186. maxDepth,
  187. maxSiblings);
  188. const nodes = [];
  189. (function getNodes(node) {
  190. nodes.push(node);
  191. if (node.children) {
  192. for (let i = 0, length = node.children.length; i < length; i++) {
  193. getNodes(node.children[i]);
  194. }
  195. }
  196. }(tree));
  197. DominatorTreeNode.attachShortestPaths(snapshot,
  198. breakdown,
  199. dominatorTree.root,
  200. nodes,
  201. maxRetainingPaths);
  202. return tree;
  203. });
  204. /**
  205. * @see HeapAnalysesClient.prototype.getImmediatelyDominated
  206. */
  207. workerHelper.createTask(self, "getImmediatelyDominated", request => {
  208. const {
  209. dominatorTreeId,
  210. nodeId,
  211. breakdown,
  212. startIndex,
  213. maxCount,
  214. maxRetainingPaths,
  215. } = request;
  216. if (!(0 <= dominatorTreeId && dominatorTreeId < dominatorTrees.length)) {
  217. throw new Error(
  218. `There does not exist a DominatorTree with the id ${dominatorTreeId}`);
  219. }
  220. const dominatorTree = dominatorTrees[dominatorTreeId];
  221. const snapshot = dominatorTreeSnapshots[dominatorTreeId];
  222. const childIds = dominatorTree.getImmediatelyDominated(nodeId);
  223. if (!childIds) {
  224. throw new Error(`${nodeId} is not a node id in the dominator tree`);
  225. }
  226. const start = startIndex || DEFAULT_START_INDEX;
  227. const count = maxCount || DEFAULT_MAX_COUNT;
  228. const end = start + count;
  229. const nodes = childIds
  230. .slice(start, end)
  231. .map(id => {
  232. const { label, shallowSize } =
  233. DominatorTreeNode.getLabelAndShallowSize(id, snapshot, breakdown);
  234. const retainedSize = dominatorTree.getRetainedSize(id);
  235. const node = new DominatorTreeNode(id, label, shallowSize, retainedSize);
  236. node.parentId = nodeId;
  237. // DominatorTree.getImmediatelyDominated will always return non-null here
  238. // because we got the id directly from the dominator tree.
  239. node.moreChildrenAvailable = dominatorTree.getImmediatelyDominated(id).length > 0;
  240. return node;
  241. });
  242. const path = [];
  243. let id = nodeId;
  244. do {
  245. path.push(id);
  246. id = dominatorTree.getImmediateDominator(id);
  247. } while (id !== null);
  248. path.reverse();
  249. const moreChildrenAvailable = childIds.length > end;
  250. DominatorTreeNode.attachShortestPaths(snapshot,
  251. breakdown,
  252. dominatorTree.root,
  253. nodes,
  254. maxRetainingPaths);
  255. return { nodes, moreChildrenAvailable, path };
  256. });