123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520 |
- /* 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 { assert } = require("devtools/shared/DevToolsUtils");
- const { MemoryFront } = require("devtools/shared/fronts/memory");
- const HeapAnalysesClient = require("devtools/shared/heapsnapshot/HeapAnalysesClient");
- const { PropTypes } = require("devtools/client/shared/vendor/react");
- const {
- snapshotState: states,
- diffingState,
- dominatorTreeState,
- viewState,
- individualsState,
- } = require("./constants");
- /**
- * ONLY USE THIS FOR MODEL VALIDATORS IN CONJUCTION WITH assert()!
- *
- * React checks that the returned values from validator functions are instances
- * of Error, but because React is loaded in its own global, that check is always
- * false and always results in a warning.
- *
- * To work around this and still get model validation, just call assert() inside
- * a function passed to catchAndIgnore. The assert() function will still report
- * assertion failures, but this funciton will swallow the errors so that React
- * doesn't go crazy and drown out the real error in irrelevant and incorrect
- * warnings.
- *
- * Example usage:
- *
- * const MyModel = PropTypes.shape({
- * someProperty: catchAndIgnore(function (model) {
- * assert(someInvariant(model.someProperty), "Should blah blah");
- * })
- * });
- */
- function catchAndIgnore(fn) {
- return function (...args) {
- try {
- fn(...args);
- } catch (err) { }
- return null;
- };
- }
- /**
- * The data describing the census report's shape, and its associated metadata.
- *
- * @see `js/src/doc/Debugger/Debugger.Memory.md`
- */
- const censusDisplayModel = exports.censusDisplay = PropTypes.shape({
- displayName: PropTypes.string.isRequired,
- tooltip: PropTypes.string.isRequired,
- inverted: PropTypes.bool.isRequired,
- breakdown: PropTypes.shape({
- by: PropTypes.string.isRequired,
- })
- });
- /**
- * How we want to label nodes in the dominator tree, and associated
- * metadata. The notable difference from `censusDisplayModel` is the lack of
- * an `inverted` property.
- *
- * @see `js/src/doc/Debugger/Debugger.Memory.md`
- */
- const labelDisplayModel = exports.labelDisplay = PropTypes.shape({
- displayName: PropTypes.string.isRequired,
- tooltip: PropTypes.string.isRequired,
- breakdown: PropTypes.shape({
- by: PropTypes.string.isRequired,
- })
- });
- /**
- * The data describing the tree map's shape, and its associated metadata.
- *
- * @see `js/src/doc/Debugger/Debugger.Memory.md`
- */
- const treeMapDisplayModel = exports.treeMapDisplay = PropTypes.shape({
- displayName: PropTypes.string.isRequired,
- tooltip: PropTypes.string.isRequired,
- inverted: PropTypes.bool.isRequired,
- breakdown: PropTypes.shape({
- by: PropTypes.string.isRequired,
- })
- });
- /**
- * Tree map model.
- */
- const treeMapModel = exports.treeMapModel = PropTypes.shape({
- // The current census report data.
- report: PropTypes.object,
- // The display data used to generate the current census.
- display: treeMapDisplayModel,
- // The current treeMapState this is in
- state: catchAndIgnore(function (treeMap) {
- switch (treeMap.state) {
- case treeMapState.SAVING:
- assert(!treeMap.report, "Should not have a report");
- assert(!treeMap.error, "Should not have an error");
- break;
- case treeMapState.SAVED:
- assert(treeMap.report, "Should have a report");
- assert(!treeMap.error, "Should not have an error");
- break;
- case treeMapState.ERROR:
- assert(treeMap.error, "Should have an error");
- break;
- default:
- assert(false, `Unexpected treeMap state: ${treeMap.state}`);
- }
- })
- });
- let censusModel = exports.censusModel = PropTypes.shape({
- // The current census report data.
- report: PropTypes.object,
- // The parent map for the report.
- parentMap: PropTypes.object,
- // The display data used to generate the current census.
- display: censusDisplayModel,
- // If present, the currently cached report's filter string used for pruning
- // the tree items.
- filter: PropTypes.string,
- // The Immutable.Set<CensusTreeNode.id> of expanded node ids in the report
- // tree.
- expanded: catchAndIgnore(function (census) {
- if (census.report) {
- assert(census.expanded,
- "If we have a report, we should also have the set of expanded nodes");
- }
- }),
- // If a node is currently focused in the report tree, then this is it.
- focused: PropTypes.object,
- // The censusModelState that this census is currently in.
- state: catchAndIgnore(function (census) {
- switch (census.state) {
- case censusState.SAVING:
- assert(!census.report, "Should not have a report");
- assert(!census.parentMap, "Should not have a parent map");
- assert(census.expanded, "Should not have an expanded set");
- assert(!census.error, "Should not have an error");
- break;
- case censusState.SAVED:
- assert(census.report, "Should have a report");
- assert(census.parentMap, "Should have a parent map");
- assert(census.expanded, "Should have an expanded set");
- assert(!census.error, "Should not have an error");
- break;
- case censusState.ERROR:
- assert(!census.report, "Should not have a report");
- assert(census.error, "Should have an error");
- break;
- default:
- assert(false, `Unexpected census state: ${census.state}`);
- }
- })
- });
- /**
- * Dominator tree model.
- */
- let dominatorTreeModel = exports.dominatorTreeModel = PropTypes.shape({
- // The id of this dominator tree.
- dominatorTreeId: PropTypes.number,
- // The root DominatorTreeNode of this dominator tree.
- root: PropTypes.object,
- // The Set<NodeId> of expanded nodes in this dominator tree.
- expanded: PropTypes.object,
- // If a node is currently focused in the dominator tree, then this is it.
- focused: PropTypes.object,
- // If an error was thrown while getting this dominator tree, the `Error`
- // instance (or an error string message) is attached here.
- error: PropTypes.oneOfType([
- PropTypes.string,
- PropTypes.object,
- ]),
- // The display used to generate descriptive labels of nodes in this dominator
- // tree.
- display: labelDisplayModel,
- // The number of active requests to incrementally fetch subtrees. This should
- // only be non-zero when the state is INCREMENTAL_FETCHING.
- activeFetchRequestCount: PropTypes.number,
- // The dominatorTreeState that this domintor tree is currently in.
- state: catchAndIgnore(function (dominatorTree) {
- switch (dominatorTree.state) {
- case dominatorTreeState.COMPUTING:
- assert(dominatorTree.dominatorTreeId == null,
- "Should not have a dominator tree id yet");
- assert(!dominatorTree.root,
- "Should not have the root of the tree yet");
- assert(!dominatorTree.error,
- "Should not have an error");
- break;
- case dominatorTreeState.COMPUTED:
- case dominatorTreeState.FETCHING:
- assert(dominatorTree.dominatorTreeId != null,
- "Should have a dominator tree id");
- assert(!dominatorTree.root,
- "Should not have the root of the tree yet");
- assert(!dominatorTree.error,
- "Should not have an error");
- break;
- case dominatorTreeState.INCREMENTAL_FETCHING:
- assert(typeof dominatorTree.activeFetchRequestCount === "number",
- "The active fetch request count is a number when we are in the " +
- "INCREMENTAL_FETCHING state");
- assert(dominatorTree.activeFetchRequestCount > 0,
- "We are keeping track of how many active requests are in flight.");
- // Fall through...
- case dominatorTreeState.LOADED:
- assert(dominatorTree.dominatorTreeId != null,
- "Should have a dominator tree id");
- assert(dominatorTree.root,
- "Should have the root of the tree");
- assert(dominatorTree.expanded,
- "Should have an expanded set");
- assert(!dominatorTree.error,
- "Should not have an error");
- break;
- case dominatorTreeState.ERROR:
- assert(dominatorTree.error, "Should have an error");
- break;
- default:
- assert(false,
- `Unexpected dominator tree state: ${dominatorTree.state}`);
- }
- }),
- });
- /**
- * Snapshot model.
- */
- let stateKeys = Object.keys(states).map(state => states[state]);
- const snapshotId = PropTypes.number;
- let snapshotModel = exports.snapshot = PropTypes.shape({
- // Unique ID for a snapshot
- id: snapshotId.isRequired,
- // Whether or not this snapshot is currently selected.
- selected: PropTypes.bool.isRequired,
- // Filesystem path to where the snapshot is stored; used to identify the
- // snapshot for HeapAnalysesClient.
- path: PropTypes.string,
- // Current census data for this snapshot.
- census: censusModel,
- // Current dominator tree data for this snapshot.
- dominatorTree: dominatorTreeModel,
- // Current tree map data for this snapshot.
- treeMap: treeMapModel,
- // If an error was thrown while processing this snapshot, the `Error` instance
- // is attached here.
- error: PropTypes.object,
- // Boolean indicating whether or not this snapshot was imported.
- imported: PropTypes.bool.isRequired,
- // The creation time of the snapshot; required after the snapshot has been
- // read.
- creationTime: PropTypes.number,
- // The current state the snapshot is in.
- // @see ./constants.js
- state: catchAndIgnore(function (snapshot, propName) {
- let current = snapshot.state;
- let shouldHavePath = [states.IMPORTING, states.SAVED, states.READ];
- let shouldHaveCreationTime = [states.READ];
- if (!stateKeys.includes(current)) {
- throw new Error(`Snapshot state must be one of ${stateKeys}.`);
- }
- if (shouldHavePath.includes(current) && !snapshot.path) {
- throw new Error(`Snapshots in state ${current} must have a snapshot path.`);
- }
- if (shouldHaveCreationTime.includes(current) && !snapshot.creationTime) {
- throw new Error(`Snapshots in state ${current} must have a creation time.`);
- }
- }),
- });
- let allocationsModel = exports.allocations = PropTypes.shape({
- // True iff we are recording allocation stacks right now.
- recording: PropTypes.bool.isRequired,
- // True iff we are in the process of toggling the recording of allocation
- // stacks on or off right now.
- togglingInProgress: PropTypes.bool.isRequired,
- });
- let diffingModel = exports.diffingModel = PropTypes.shape({
- // The id of the first snapshot to diff.
- firstSnapshotId: snapshotId,
- // The id of the second snapshot to diff.
- secondSnapshotId: catchAndIgnore(function (diffing, propName) {
- if (diffing.secondSnapshotId && !diffing.firstSnapshotId) {
- throw new Error("Cannot have second snapshot without already having " +
- "first snapshot");
- }
- return snapshotId(diffing, propName);
- }),
- // The current census data for the diffing.
- census: censusModel,
- // If an error was thrown while diffing, the `Error` instance is attached
- // here.
- error: PropTypes.object,
- // The current state the diffing is in.
- // @see ./constants.js
- state: catchAndIgnore(function (diffing) {
- switch (diffing.state) {
- case diffingState.TOOK_DIFF:
- assert(diffing.census, "If we took a diff, we should have a census");
- // Fall through...
- case diffingState.TAKING_DIFF:
- assert(diffing.firstSnapshotId, "Should have first snapshot");
- assert(diffing.secondSnapshotId, "Should have second snapshot");
- break;
- case diffingState.SELECTING:
- break;
- case diffingState.ERROR:
- assert(diffing.error, "Should have error");
- break;
- default:
- assert(false, `Bad diffing state: ${diffing.state}`);
- }
- }),
- });
- let previousViewModel = exports.previousView = PropTypes.shape({
- state: catchAndIgnore(function (previous) {
- switch (previous.state) {
- case viewState.DIFFING:
- assert(previous.diffing, "Should have previous diffing state.");
- assert(!previous.selected, "Should not have a previously selected snapshot.");
- break;
- case viewState.CENSUS:
- case viewState.DOMINATOR_TREE:
- case viewState.TREE_MAP:
- assert(previous.selected, "Should have a previously selected snapshot.");
- break;
- case viewState.INDIVIDUALS:
- default:
- assert(false, `Unexpected previous view state: ${previous.state}.`);
- }
- }),
- // The previous diffing state, if any.
- diffing: diffingModel,
- // The previously selected snapshot, if any.
- selected: snapshotId,
- });
- let viewModel = exports.view = PropTypes.shape({
- // The current view state.
- state: catchAndIgnore(function (view) {
- switch (view.state) {
- case viewState.DIFFING:
- case viewState.CENSUS:
- case viewState.DOMINATOR_TREE:
- case viewState.INDIVIDUALS:
- case viewState.TREE_MAP:
- break;
- default:
- assert(false, `Unexpected type of view: ${view.state}`);
- }
- }),
- // The previous view state.
- previous: previousViewModel,
- });
- const individualsModel = exports.individuals = PropTypes.shape({
- error: PropTypes.object,
- nodes: PropTypes.arrayOf(PropTypes.object),
- dominatorTree: dominatorTreeModel,
- id: snapshotId,
- censusBreakdown: PropTypes.object,
- indices: PropTypes.object,
- labelDisplay: labelDisplayModel,
- focused: PropTypes.object,
- state: catchAndIgnore(function (individuals) {
- switch (individuals.state) {
- case individualsState.COMPUTING_DOMINATOR_TREE:
- case individualsState.FETCHING:
- assert(!individuals.nodes, "Should not have individual nodes");
- assert(!individuals.dominatorTree, "Should not have dominator tree");
- assert(!individuals.id, "Should not have an id");
- assert(!individuals.censusBreakdown, "Should not have a censusBreakdown");
- assert(!individuals.indices, "Should not have indices");
- assert(!individuals.labelDisplay, "Should not have a labelDisplay");
- break;
- case individualsState.FETCHED:
- assert(individuals.nodes, "Should have individual nodes");
- assert(individuals.dominatorTree, "Should have dominator tree");
- assert(individuals.id, "Should have an id");
- assert(individuals.censusBreakdown, "Should have a censusBreakdown");
- assert(individuals.indices, "Should have indices");
- assert(individuals.labelDisplay, "Should have a labelDisplay");
- break;
- case individualsState.ERROR:
- assert(individuals.error, "Should have an error object");
- break;
- default:
- assert(false, `Unexpected individuals state: ${individuals.state}`);
- break;
- }
- }),
- });
- let appModel = exports.app = {
- // {MemoryFront} Used to communicate with platform
- front: PropTypes.instanceOf(MemoryFront),
- // Allocations recording related data.
- allocations: allocationsModel.isRequired,
- // {HeapAnalysesClient} Used to interface with snapshots
- heapWorker: PropTypes.instanceOf(HeapAnalysesClient),
- // The display data describing how we want the census data to be.
- censusDisplay: censusDisplayModel.isRequired,
- // The display data describing how we want the dominator tree labels to be
- // computed.
- labelDisplay: labelDisplayModel.isRequired,
- // The display data describing how we want the dominator tree labels to be
- // computed.
- treeMapDisplay: treeMapDisplayModel.isRequired,
- // List of reference to all snapshots taken
- snapshots: PropTypes.arrayOf(snapshotModel).isRequired,
- // If present, a filter string for pruning the tree items.
- filter: PropTypes.string,
- // If present, the current diffing state.
- diffing: diffingModel,
- // If present, the current individuals state.
- individuals: individualsModel,
- // The current type of view.
- view: function (app) {
- viewModel.isRequired(app, "view");
- catchAndIgnore(function (app) {
- switch (app.view.state) {
- case viewState.DIFFING:
- assert(app.diffing, "Should be diffing");
- break;
- case viewState.INDIVIDUALS:
- case viewState.CENSUS:
- case viewState.DOMINATOR_TREE:
- case viewState.TREE_MAP:
- assert(!app.diffing, "Should not be diffing");
- break;
- default:
- assert(false, `Unexpected type of view: ${view.state}`);
- }
- })(app);
- catchAndIgnore(function (app) {
- switch (app.view.state) {
- case viewState.INDIVIDUALS:
- assert(app.individuals, "Should have individuals state");
- break;
- case viewState.DIFFING:
- case viewState.CENSUS:
- case viewState.DOMINATOR_TREE:
- case viewState.TREE_MAP:
- assert(!app.individuals, "Should not have individuals state");
- break;
- default:
- assert(false, `Unexpected type of view: ${view.state}`);
- }
- })(app);
- },
- };
|