summaryrefslogtreecommitdiffstats
path: root/devtools/client/memory/components/heap.js
diff options
context:
space:
mode:
Diffstat (limited to 'devtools/client/memory/components/heap.js')
-rw-r--r--devtools/client/memory/components/heap.js455
1 files changed, 455 insertions, 0 deletions
diff --git a/devtools/client/memory/components/heap.js b/devtools/client/memory/components/heap.js
new file mode 100644
index 000000000..786f37ae1
--- /dev/null
+++ b/devtools/client/memory/components/heap.js
@@ -0,0 +1,455 @@
+/* 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/. */
+
+const { DOM: dom, createClass, PropTypes, createFactory } = require("devtools/client/shared/vendor/react");
+const { assert, safeErrorString } = require("devtools/shared/DevToolsUtils");
+const Census = createFactory(require("./census"));
+const CensusHeader = createFactory(require("./census-header"));
+const DominatorTree = createFactory(require("./dominator-tree"));
+const DominatorTreeHeader = createFactory(require("./dominator-tree-header"));
+const TreeMap = createFactory(require("./tree-map"));
+const HSplitBox = createFactory(require("devtools/client/shared/components/h-split-box"));
+const Individuals = createFactory(require("./individuals"));
+const IndividualsHeader = createFactory(require("./individuals-header"));
+const ShortestPaths = createFactory(require("./shortest-paths"));
+const { getStatusTextFull, L10N } = require("../utils");
+const {
+ snapshotState: states,
+ diffingState,
+ viewState,
+ censusState,
+ treeMapState,
+ dominatorTreeState,
+ individualsState,
+} = require("../constants");
+const models = require("../models");
+const { snapshot: snapshotModel, diffingModel } = models;
+
+/**
+ * Get the app state's current state atom.
+ *
+ * @see the relevant state string constants in `../constants.js`.
+ *
+ * @param {models.view} view
+ * @param {snapshotModel} snapshot
+ * @param {diffingModel} diffing
+ * @param {individualsModel} individuals
+ *
+ * @return {snapshotState|diffingState|dominatorTreeState}
+ */
+function getState(view, snapshot, diffing, individuals) {
+ switch (view.state) {
+ case viewState.CENSUS:
+ return snapshot.census
+ ? snapshot.census.state
+ : snapshot.state;
+
+ case viewState.DIFFING:
+ return diffing.state;
+
+ case viewState.TREE_MAP:
+ return snapshot.treeMap
+ ? snapshot.treeMap.state
+ : snapshot.state;
+
+ case viewState.DOMINATOR_TREE:
+ return snapshot.dominatorTree
+ ? snapshot.dominatorTree.state
+ : snapshot.state;
+
+ case viewState.INDIVIDUALS:
+ return individuals.state;
+ }
+
+ assert(false, `Unexpected view state: ${view.state}`);
+ return null;
+}
+
+/**
+ * Return true if we should display a status message when we are in the given
+ * state. Return false otherwise.
+ *
+ * @param {snapshotState|diffingState|dominatorTreeState} state
+ * @param {models.view} view
+ * @param {snapshotModel} snapshot
+ *
+ * @returns {Boolean}
+ */
+function shouldDisplayStatus(state, view, snapshot) {
+ switch (state) {
+ case states.IMPORTING:
+ case states.SAVING:
+ case states.SAVED:
+ case states.READING:
+ case censusState.SAVING:
+ case treeMapState.SAVING:
+ case diffingState.SELECTING:
+ case diffingState.TAKING_DIFF:
+ case dominatorTreeState.COMPUTING:
+ case dominatorTreeState.COMPUTED:
+ case dominatorTreeState.FETCHING:
+ case individualsState.COMPUTING_DOMINATOR_TREE:
+ case individualsState.FETCHING:
+ return true;
+ }
+ return view.state === viewState.DOMINATOR_TREE && !snapshot.dominatorTree;
+}
+
+/**
+ * Get the status text to display for the given state.
+ *
+ * @param {snapshotState|diffingState|dominatorTreeState} state
+ * @param {diffingModel} diffing
+ *
+ * @returns {String}
+ */
+function getStateStatusText(state, diffing) {
+ if (state === diffingState.SELECTING) {
+ return L10N.getStr(diffing.firstSnapshotId === null
+ ? "diffing.prompt.selectBaseline"
+ : "diffing.prompt.selectComparison");
+ }
+
+ return getStatusTextFull(state);
+}
+
+/**
+ * Given that we should display a status message, return true if we should also
+ * display a throbber along with the status message. Return false otherwise.
+ *
+ * @param {diffingModel} diffing
+ *
+ * @returns {Boolean}
+ */
+function shouldDisplayThrobber(diffing) {
+ return !diffing || diffing.state !== diffingState.SELECTING;
+}
+
+/**
+ * Get the current state's error, or return null if there is none.
+ *
+ * @param {snapshotModel} snapshot
+ * @param {diffingModel} diffing
+ * @param {individualsModel} individuals
+ *
+ * @returns {Error|null}
+ */
+function getError(snapshot, diffing, individuals) {
+ if (diffing) {
+ if (diffing.state === diffingState.ERROR) {
+ return diffing.error;
+ }
+ if (diffing.census === censusState.ERROR) {
+ return diffing.census.error;
+ }
+ }
+
+ if (snapshot) {
+ if (snapshot.state === states.ERROR) {
+ return snapshot.error;
+ }
+
+ if (snapshot.census === censusState.ERROR) {
+ return snapshot.census.error;
+ }
+
+ if (snapshot.treeMap === treeMapState.ERROR) {
+ return snapshot.treeMap.error;
+ }
+
+ if (snapshot.dominatorTree &&
+ snapshot.dominatorTree.state === dominatorTreeState.ERROR) {
+ return snapshot.dominatorTree.error;
+ }
+ }
+
+ if (individuals && individuals.state === individualsState.ERROR) {
+ return individuals.error;
+ }
+
+ return null;
+}
+
+/**
+ * Main view for the memory tool.
+ *
+ * The Heap component contains several panels for different states; an initial
+ * state of only a button to take a snapshot, loading states, the census view
+ * tree, the dominator tree, etc.
+ */
+const Heap = module.exports = createClass({
+ displayName: "Heap",
+
+ propTypes: {
+ onSnapshotClick: PropTypes.func.isRequired,
+ onLoadMoreSiblings: PropTypes.func.isRequired,
+ onCensusExpand: PropTypes.func.isRequired,
+ onCensusCollapse: PropTypes.func.isRequired,
+ onDominatorTreeExpand: PropTypes.func.isRequired,
+ onDominatorTreeCollapse: PropTypes.func.isRequired,
+ onCensusFocus: PropTypes.func.isRequired,
+ onDominatorTreeFocus: PropTypes.func.isRequired,
+ onShortestPathsResize: PropTypes.func.isRequired,
+ snapshot: snapshotModel,
+ onViewSourceInDebugger: PropTypes.func.isRequired,
+ onPopView: PropTypes.func.isRequired,
+ individuals: models.individuals,
+ onViewIndividuals: PropTypes.func.isRequired,
+ onFocusIndividual: PropTypes.func.isRequired,
+ diffing: diffingModel,
+ view: models.view.isRequired,
+ sizes: PropTypes.object.isRequired,
+ },
+
+ render() {
+ let {
+ snapshot,
+ diffing,
+ onSnapshotClick,
+ onLoadMoreSiblings,
+ onViewSourceInDebugger,
+ onViewIndividuals,
+ individuals,
+ view,
+ } = this.props;
+
+
+ if (!diffing && !snapshot && !individuals) {
+ return this._renderInitial(onSnapshotClick);
+ }
+
+ const state = getState(view, snapshot, diffing, individuals);
+ const statusText = getStateStatusText(state, diffing);
+
+ if (shouldDisplayStatus(state, view, snapshot)) {
+ return this._renderStatus(state, statusText, diffing);
+ }
+
+ const error = getError(snapshot, diffing, individuals);
+ if (error) {
+ return this._renderError(state, statusText, error);
+ }
+
+ if (view.state === viewState.CENSUS || view.state === viewState.DIFFING) {
+ const census = view.state === viewState.CENSUS
+ ? snapshot.census
+ : diffing.census;
+ if (!census) {
+ return this._renderStatus(state, statusText, diffing);
+ }
+ return this._renderCensus(state, census, diffing, onViewSourceInDebugger,
+ onViewIndividuals);
+ }
+
+ if (view.state === viewState.TREE_MAP) {
+ return this._renderTreeMap(state, snapshot.treeMap);
+ }
+
+ if (view.state === viewState.INDIVIDUALS) {
+ assert(individuals.state === individualsState.FETCHED,
+ "Should have fetched the individuals -- other states are rendered as statuses");
+ return this._renderIndividuals(state, individuals,
+ individuals.dominatorTree,
+ onViewSourceInDebugger);
+ }
+
+ assert(view.state === viewState.DOMINATOR_TREE,
+ "If we aren't in progress, looking at a census, or diffing, then we " +
+ "must be looking at a dominator tree");
+ assert(!diffing, "Should not have diffing");
+ assert(snapshot.dominatorTree, "Should have a dominator tree");
+
+ return this._renderDominatorTree(state, onViewSourceInDebugger, snapshot.dominatorTree,
+ onLoadMoreSiblings);
+ },
+
+ /**
+ * Render the heap view's container panel with the given contents inside of
+ * it.
+ *
+ * @param {snapshotState|diffingState|dominatorTreeState} state
+ * @param {...Any} contents
+ */
+ _renderHeapView(state, ...contents) {
+ return dom.div(
+ {
+ id: "heap-view",
+ "data-state": state
+ },
+ dom.div(
+ {
+ className: "heap-view-panel",
+ "data-state": state,
+ },
+ ...contents
+ )
+ );
+ },
+
+ _renderInitial(onSnapshotClick) {
+ return this._renderHeapView("initial", dom.button(
+ {
+ className: "devtools-toolbarbutton take-snapshot",
+ onClick: onSnapshotClick,
+ "data-standalone": true,
+ "data-text-only": true,
+ },
+ L10N.getStr("take-snapshot")
+ ));
+ },
+
+ _renderStatus(state, statusText, diffing) {
+ let throbber = "";
+ if (shouldDisplayThrobber(diffing)) {
+ throbber = "devtools-throbber";
+ }
+
+ return this._renderHeapView(state, dom.span(
+ {
+ className: `snapshot-status ${throbber}`
+ },
+ statusText
+ ));
+ },
+
+ _renderError(state, statusText, error) {
+ return this._renderHeapView(
+ state,
+ dom.span({ className: "snapshot-status error" }, statusText),
+ dom.pre({}, safeErrorString(error))
+ );
+ },
+
+ _renderCensus(state, census, diffing, onViewSourceInDebugger, onViewIndividuals) {
+ assert(census.report, "Should not render census that does not have a report");
+
+ if (!census.report.children) {
+ const msg = diffing ? L10N.getStr("heapview.no-difference")
+ : census.filter ? L10N.getStr("heapview.none-match")
+ : L10N.getStr("heapview.empty");
+ return this._renderHeapView(state, dom.div({ className: "empty" }, msg));
+ }
+
+ const contents = [];
+
+ if (census.display.breakdown.by === "allocationStack"
+ && census.report.children
+ && census.report.children.length === 1
+ && census.report.children[0].name === "noStack") {
+ contents.push(dom.div({ className: "error no-allocation-stacks" },
+ L10N.getStr("heapview.noAllocationStacks")));
+ }
+
+ contents.push(CensusHeader({ diffing }));
+ contents.push(Census({
+ onViewSourceInDebugger,
+ onViewIndividuals,
+ diffing,
+ census,
+ onExpand: node => this.props.onCensusExpand(census, node),
+ onCollapse: node => this.props.onCensusCollapse(census, node),
+ onFocus: node => this.props.onCensusFocus(census, node),
+ }));
+
+ return this._renderHeapView(state, ...contents);
+ },
+
+ _renderTreeMap(state, treeMap) {
+ return this._renderHeapView(
+ state,
+ TreeMap({ treeMap })
+ );
+ },
+
+ _renderIndividuals(state, individuals, dominatorTree, onViewSourceInDebugger) {
+ assert(individuals.state === individualsState.FETCHED,
+ "Should have fetched individuals");
+ assert(dominatorTree && dominatorTree.root,
+ "Should have a dominator tree and its root");
+
+ const tree = dom.div(
+ {
+ className: "vbox",
+ style: {
+ overflowY: "auto"
+ }
+ },
+ IndividualsHeader(),
+ Individuals({
+ individuals,
+ dominatorTree,
+ onViewSourceInDebugger,
+ onFocus: this.props.onFocusIndividual
+ })
+ );
+
+ const shortestPaths = ShortestPaths({
+ graph: individuals.focused
+ ? individuals.focused.shortestPaths
+ : null
+ });
+
+ return this._renderHeapView(
+ state,
+ dom.div(
+ { className: "hbox devtools-toolbar" },
+ dom.label(
+ { id: "pop-view-button-label" },
+ dom.button(
+ {
+ id: "pop-view-button",
+ className: "devtools-button",
+ onClick: this.props.onPopView,
+ },
+ L10N.getStr("toolbar.pop-view")
+ ),
+ L10N.getStr("toolbar.pop-view.label")
+ ),
+ L10N.getStr("toolbar.viewing-individuals")
+ ),
+ HSplitBox({
+ start: tree,
+ end: shortestPaths,
+ startWidth: this.props.sizes.shortestPathsSize,
+ onResize: this.props.onShortestPathsResize,
+ })
+ );
+ },
+
+ _renderDominatorTree(state, onViewSourceInDebugger, dominatorTree, onLoadMoreSiblings) {
+ const tree = dom.div(
+ {
+ className: "vbox",
+ style: {
+ overflowY: "auto"
+ }
+ },
+ DominatorTreeHeader(),
+ DominatorTree({
+ onViewSourceInDebugger,
+ dominatorTree,
+ onLoadMoreSiblings,
+ onExpand: this.props.onDominatorTreeExpand,
+ onCollapse: this.props.onDominatorTreeCollapse,
+ onFocus: this.props.onDominatorTreeFocus,
+ })
+ );
+
+ const shortestPaths = ShortestPaths({
+ graph: dominatorTree.focused
+ ? dominatorTree.focused.shortestPaths
+ : null
+ });
+
+ return this._renderHeapView(
+ state,
+ HSplitBox({
+ start: tree,
+ end: shortestPaths,
+ startWidth: this.props.sizes.shortestPathsSize,
+ onResize: this.props.onShortestPathsResize,
+ })
+ );
+ },
+});