Bug 1249788 - Implement the census individuals view; r=jsantell
This adds the INDIVIDUALS view state to the memory panel, renames "dominator tree display" to "label display", and adds a view for listing the individual nodes in a census group and inspecting each nodes' retaining paths.
This commit is contained in:
@@ -69,6 +69,8 @@ registerCleanupFunction(() => {
|
||||
Services.obs.removeObserver(ConsoleObserver, "console-api-log-event");
|
||||
});
|
||||
|
||||
var waitForTime = DevToolsUtils.waitForTime;
|
||||
|
||||
function getFrameScript() {
|
||||
let mm = gBrowser.selectedBrowser.messageManager;
|
||||
let frameURL = "chrome://devtools/content/shared/frame-script-utils.js";
|
||||
|
||||
@@ -65,6 +65,15 @@ toolbar.displayBy=Group by:
|
||||
# describing the select menu options of the display options.
|
||||
toolbar.displayBy.tooltip=Change how objects are grouped
|
||||
|
||||
# TODO FITZGEN
|
||||
toolbar.pop-view=←
|
||||
|
||||
# TODO FITZGEN
|
||||
toolbar.pop-view.label=Go back to aggregates
|
||||
|
||||
# TODO FITZGEN
|
||||
toolbar.viewing-individuals=⁂ Viewing individuals in group
|
||||
|
||||
# LOCALIZATION NOTE (censusDisplays.coarseType.tooltip): The tooltip for the
|
||||
# "coarse type" display option.
|
||||
censusDisplays.coarseType.tooltip=Group items by their type
|
||||
@@ -169,6 +178,9 @@ filter.placeholder=Filter
|
||||
# tool's filter search box.
|
||||
filter.tooltip=Filter the contents of the heap snapshot
|
||||
|
||||
# TODO FITZGEN
|
||||
tree-item.view-individuals.tooltip=View individual nodes in this group and their retaining paths
|
||||
|
||||
# LOCALIZATION NOTE (tree-item.load-more): The label for the links to fetch the
|
||||
# lazily loaded sub trees in the dominator tree view.
|
||||
tree-item.load-more=Load more…
|
||||
@@ -295,6 +307,24 @@ snapshot.state.saving-tree-map.full=Saving tree map…
|
||||
# snapshot state ERROR, used in the main heap view.
|
||||
snapshot.state.error.full=There was an error processing this snapshot.
|
||||
|
||||
# TODO FITZGEN
|
||||
individuals.state.error=Error
|
||||
|
||||
# TODO FITZGEN
|
||||
individuals.state.error.full=There was an error while fetching individuals in the group
|
||||
|
||||
# TODO FITZGEN
|
||||
individuals.state.fetching=Fetching…
|
||||
|
||||
# TODO FITZGEN
|
||||
individuals.state.fetching.full=Fetching individuals in group…
|
||||
|
||||
# TODO FITZGEN
|
||||
individuals.field.node=Node
|
||||
|
||||
# TODO FITZGEN
|
||||
individuals.field.node.tooltip=The individual node in the snapshot
|
||||
|
||||
# LOCALIZATION NOTE (snapshot.state.saving): The label describing the snapshot
|
||||
# state SAVING, used in the snapshot list view
|
||||
snapshot.state.saving=Saving snapshot…
|
||||
|
||||
@@ -9,6 +9,7 @@ const { refresh } = require("./refresh");
|
||||
|
||||
exports.setCensusDisplayAndRefresh = function(heapWorker, display) {
|
||||
return function*(dispatch, getState) {
|
||||
console.log("FITZGEN: setCensusDisplayAndRefresh", display);
|
||||
dispatch(setCensusDisplay(display));
|
||||
yield dispatch(refresh(heapWorker));
|
||||
};
|
||||
|
||||
@@ -9,7 +9,8 @@ const telemetry = require("../telemetry");
|
||||
const {
|
||||
getSnapshot,
|
||||
censusIsUpToDate,
|
||||
snapshotIsDiffable
|
||||
snapshotIsDiffable,
|
||||
findSelectedSnapshot,
|
||||
} = require("../utils");
|
||||
// This is a circular dependency, so do not destructure the needed properties.
|
||||
const snapshotActions = require("./snapshot");
|
||||
@@ -21,7 +22,9 @@ const toggleDiffing = exports.toggleDiffing = function () {
|
||||
return function(dispatch, getState) {
|
||||
dispatch({
|
||||
type: actions.CHANGE_VIEW,
|
||||
view: getState().diffing ? viewState.CENSUS : viewState.DIFFING,
|
||||
newViewState: getState().diffing ? viewState.CENSUS : viewState.DIFFING,
|
||||
oldDiffing: getState().diffing,
|
||||
oldSelected: findSelectedSnapshot(getState()),
|
||||
});
|
||||
};
|
||||
};
|
||||
|
||||
@@ -7,21 +7,24 @@ const { assert } = require("devtools/shared/DevToolsUtils");
|
||||
const { actions } = require("../constants");
|
||||
const { refresh } = require("./refresh");
|
||||
|
||||
exports.setDominatorTreeDisplayAndRefresh = function(heapWorker, display) {
|
||||
/**
|
||||
* Change the display we use for labeling individual nodes and refresh the
|
||||
* current data.
|
||||
*/
|
||||
exports.setLabelDisplayAndRefresh = function(heapWorker, display) {
|
||||
return function*(dispatch, getState) {
|
||||
// Clears out all stored census data and sets the display.
|
||||
dispatch(setDominatorTreeDisplay(display));
|
||||
dispatch(setLabelDisplay(display));
|
||||
yield dispatch(refresh(heapWorker));
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Clears out all census data in the snapshots and sets
|
||||
* a new display.
|
||||
* Change the display we use for labeling individual nodes.
|
||||
*
|
||||
* @param {dominatorTreeDisplayModel} display
|
||||
* @param {labelDisplayModel} display
|
||||
*/
|
||||
const setDominatorTreeDisplay = exports.setDominatorTreeDisplay = function (display) {
|
||||
const setLabelDisplay = exports.setLabelDisplay = function (display) {
|
||||
assert(typeof display === "object"
|
||||
&& display
|
||||
&& display.breakdown
|
||||
@@ -29,7 +32,7 @@ const setDominatorTreeDisplay = exports.setDominatorTreeDisplay = function (disp
|
||||
`Breakdowns must be an object with a \`by\` property, attempted to set: ${uneval(display)}`);
|
||||
|
||||
return {
|
||||
type: actions.SET_DOMINATOR_TREE_DISPLAY,
|
||||
type: actions.SET_LABEL_DISPLAY,
|
||||
display,
|
||||
};
|
||||
};
|
||||
@@ -7,12 +7,13 @@ DevToolsModules(
|
||||
'allocations.js',
|
||||
'census-display.js',
|
||||
'diffing.js',
|
||||
'dominator-tree-display.js',
|
||||
'filter.js',
|
||||
'io.js',
|
||||
'label-display.js',
|
||||
'refresh.js',
|
||||
'sizes.js',
|
||||
'snapshot.js',
|
||||
'task-cache.js',
|
||||
'tree-map-display.js',
|
||||
'view.js',
|
||||
)
|
||||
|
||||
@@ -14,8 +14,9 @@ const snapshot = require("./snapshot");
|
||||
* @param {HeapAnalysesWorker} heapWorker
|
||||
*/
|
||||
exports.refresh = function (heapWorker) {
|
||||
console.log("FITZGEN: refresh");
|
||||
return function* (dispatch, getState) {
|
||||
switch (getState().view) {
|
||||
switch (getState().view.state) {
|
||||
case viewState.DIFFING:
|
||||
assert(getState().diffing, "Should have diffing state if in diffing view");
|
||||
yield dispatch(refreshDiffing(heapWorker));
|
||||
@@ -33,8 +34,12 @@ exports.refresh = function (heapWorker) {
|
||||
yield dispatch(snapshot.refreshSelectedTreeMap(heapWorker));
|
||||
return;
|
||||
|
||||
case viewState.INDIVIDUALS:
|
||||
yield dispatch(snapshot.refreshIndividuals(heapWorker));
|
||||
return;
|
||||
|
||||
default:
|
||||
assert(false, `Unexpected view state: ${getState().view}`);
|
||||
assert(false, `Unexpected view state: ${getState().view.state}`);
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
@@ -3,7 +3,8 @@
|
||||
* You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
"use strict";
|
||||
|
||||
const { assert, reportException } = require("devtools/shared/DevToolsUtils");
|
||||
const { Preferences } = require("resource://gre/modules/Preferences.jsm");
|
||||
const { assert, reportException, isSet } = require("devtools/shared/DevToolsUtils");
|
||||
const {
|
||||
censusIsUpToDate,
|
||||
getSnapshot,
|
||||
@@ -16,12 +17,14 @@ const {
|
||||
viewState,
|
||||
censusState,
|
||||
treeMapState,
|
||||
dominatorTreeState
|
||||
dominatorTreeState,
|
||||
individualsState,
|
||||
} = require("../constants");
|
||||
const telemetry = require("../telemetry");
|
||||
const view = require("./view");
|
||||
const refresh = require("./refresh");
|
||||
const diffing = require("./diffing");
|
||||
const TaskCache = require("./task-cache");
|
||||
|
||||
/**
|
||||
* A series of actions are fired from this task to save, read and generate the
|
||||
@@ -56,16 +59,16 @@ const takeSnapshotAndCensus = exports.takeSnapshotAndCensus = function (front, h
|
||||
* @param {snapshotId} id
|
||||
*/
|
||||
const computeSnapshotData = exports.computeSnapshotData = function(heapWorker, id) {
|
||||
return function* (dispatch, getState) {
|
||||
return function*(dispatch, getState) {
|
||||
if (getSnapshot(getState(), id).state !== states.READ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Decide which type of census to take.
|
||||
const censusTaker = getCurrentCensusTaker(getState().view);
|
||||
const censusTaker = getCurrentCensusTaker(getState().view.state);
|
||||
yield dispatch(censusTaker(heapWorker, id));
|
||||
|
||||
if (getState().view === viewState.DOMINATOR_TREE &&
|
||||
if (getState().view.state === viewState.DOMINATOR_TREE &&
|
||||
!getSnapshot(getState(), id).dominatorTree) {
|
||||
yield dispatch(computeAndFetchDominatorTree(heapWorker, id));
|
||||
}
|
||||
@@ -81,7 +84,7 @@ const computeSnapshotData = exports.computeSnapshotData = function(heapWorker, i
|
||||
*/
|
||||
const selectSnapshotAndRefresh = exports.selectSnapshotAndRefresh = function (heapWorker, id) {
|
||||
return function *(dispatch, getState) {
|
||||
if (getState().diffing) {
|
||||
if (getState().diffing || getState().individuals) {
|
||||
dispatch(view.changeView(viewState.CENSUS));
|
||||
}
|
||||
|
||||
@@ -100,7 +103,7 @@ const takeSnapshot = exports.takeSnapshot = function (front) {
|
||||
return function *(dispatch, getState) {
|
||||
telemetry.countTakeSnapshot();
|
||||
|
||||
if (getState().diffing) {
|
||||
if (getState().diffing || getState().individuals) {
|
||||
dispatch(view.changeView(viewState.CENSUS));
|
||||
}
|
||||
|
||||
@@ -130,11 +133,18 @@ const takeSnapshot = exports.takeSnapshot = function (front) {
|
||||
* @param {HeapAnalysesClient} heapWorker
|
||||
* @param {snapshotId} id
|
||||
*/
|
||||
const readSnapshot = exports.readSnapshot = function readSnapshot (heapWorker, id) {
|
||||
return function *(dispatch, getState) {
|
||||
const readSnapshot = exports.readSnapshot =
|
||||
TaskCache.declareCacheableTask({
|
||||
getCacheKey(_, id) {
|
||||
return id;
|
||||
},
|
||||
|
||||
task: function*(heapWorker, id, removeFromCache, dispatch, getState) {
|
||||
console.log("FITZGEN: readSnapshot");
|
||||
|
||||
const snapshot = getSnapshot(getState(), id);
|
||||
assert([states.SAVED, states.IMPORTING].includes(snapshot.state),
|
||||
`Should only read a snapshot once. Found snapshot in state ${snapshot.state}`);
|
||||
`Should only read a snapshot once. Found snapshot in state ${snapshot.state}`);
|
||||
|
||||
let creationTime;
|
||||
|
||||
@@ -143,14 +153,20 @@ const readSnapshot = exports.readSnapshot = function readSnapshot (heapWorker, i
|
||||
yield heapWorker.readHeapSnapshot(snapshot.path);
|
||||
creationTime = yield heapWorker.getCreationTime(snapshot.path);
|
||||
} catch (error) {
|
||||
console.log("FITZGEN: readSnapshot: error", error);
|
||||
removeFromCache();
|
||||
reportException("readSnapshot", error);
|
||||
dispatch({ type: actions.SNAPSHOT_ERROR, id, error });
|
||||
return;
|
||||
}
|
||||
|
||||
console.log("FITZGEN: readSnapshot: done reading");
|
||||
removeFromCache();
|
||||
dispatch({ type: actions.READ_SNAPSHOT_END, id, creationTime });
|
||||
};
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
let takeCensusTaskCounter = 0;
|
||||
|
||||
/**
|
||||
* Census and tree maps both require snapshots. This function shares the logic
|
||||
@@ -173,18 +189,24 @@ function makeTakeCensusTask({ getDisplay, getFilter, getCensus, beginAction,
|
||||
* @see `devtools/shared/heapsnapshot/HeapAnalysesClient.js`
|
||||
* @see `js/src/doc/Debugger/Debugger.Memory.md` for breakdown details
|
||||
*/
|
||||
return function (heapWorker, id) {
|
||||
return function *(dispatch, getState) {
|
||||
let thisTakeCensusTaskId = ++takeCensusTaskCounter;
|
||||
return TaskCache.declareCacheableTask({
|
||||
getCacheKey(_, id) {
|
||||
return `take-census-task-${thisTakeCensusTaskId}-${id}`;
|
||||
},
|
||||
|
||||
task: function*(heapWorker, id, removeFromCache, dispatch, getState) {
|
||||
console.log("FITZGEN: takeCensus");
|
||||
const snapshot = getSnapshot(getState(), id);
|
||||
if (!snapshot) {
|
||||
console.log("FITZGEN: no snapshot");
|
||||
removeFromCache();
|
||||
return;
|
||||
}
|
||||
|
||||
// Assert that snapshot is in a valid state
|
||||
assert(canTakeCensus(snapshot),
|
||||
`Attempting to take a census when the snapshot is not in a ready
|
||||
state. snapshot.state = ${snapshot.state},
|
||||
census.state = ${(getCensus(snapshot) || {}).state}`);
|
||||
`Attempting to take a census when the snapshot is not in a ready state. snapshot.state = ${snapshot.state}, census.state = ${(getCensus(snapshot) || { state: null }).state}`);
|
||||
|
||||
let report, parentMap;
|
||||
let display = getDisplay(getState());
|
||||
@@ -192,6 +214,8 @@ function makeTakeCensusTask({ getDisplay, getFilter, getCensus, beginAction,
|
||||
|
||||
// If display, filter and inversion haven't changed, don't do anything.
|
||||
if (censusIsUpToDate(filter, display, getCensus(snapshot))) {
|
||||
console.log("FITZGEN: census is up to date");
|
||||
removeFromCache();
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -202,6 +226,7 @@ function makeTakeCensusTask({ getDisplay, getFilter, getCensus, beginAction,
|
||||
display = getDisplay(getState());
|
||||
filter = getState().filter;
|
||||
|
||||
console.log("FITZGEN: taking census with display =", display.displayName);
|
||||
dispatch({
|
||||
type: beginAction,
|
||||
id,
|
||||
@@ -221,6 +246,8 @@ function makeTakeCensusTask({ getDisplay, getFilter, getCensus, beginAction,
|
||||
{ breakdown: display.breakdown },
|
||||
opts));
|
||||
} catch (error) {
|
||||
console.log("FITZGEN: error taking census: " + error + "\n" + error.stack);
|
||||
removeFromCache();
|
||||
reportException("takeCensus", error);
|
||||
dispatch({ type: errorAction, id, error });
|
||||
return;
|
||||
@@ -229,6 +256,9 @@ function makeTakeCensusTask({ getDisplay, getFilter, getCensus, beginAction,
|
||||
while (filter !== getState().filter ||
|
||||
display !== getDisplay(getState()));
|
||||
|
||||
console.log("FITZGEN: done taking census");
|
||||
|
||||
removeFromCache();
|
||||
dispatch({
|
||||
type: endAction,
|
||||
id,
|
||||
@@ -239,8 +269,8 @@ function makeTakeCensusTask({ getDisplay, getFilter, getCensus, beginAction,
|
||||
});
|
||||
|
||||
telemetry.countCensus({ filter, display });
|
||||
};
|
||||
};
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -287,7 +317,7 @@ const defaultCensusTaker = takeTreeMap;
|
||||
*
|
||||
* @param {string} value from viewState
|
||||
*/
|
||||
const getCurrentCensusTaker = exports.getCurrentCensusTaker = function (currentView) {
|
||||
const getCurrentCensusTaker = exports.getCurrentCensusTaker = function(currentView) {
|
||||
switch (currentView) {
|
||||
case viewState.TREE_MAP:
|
||||
return takeTreeMap;
|
||||
@@ -298,6 +328,136 @@ const getCurrentCensusTaker = exports.getCurrentCensusTaker = function (currentV
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Focus the given node in the individuals view.
|
||||
*
|
||||
* @param {DominatorTreeNode} node.
|
||||
*/
|
||||
const focusIndividual = exports.focusIndividual = function(node) {
|
||||
return {
|
||||
type: actions.FOCUS_INDIVIDUAL,
|
||||
node,
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Fetch the individual `DominatorTreeNodes` for the census group specified by
|
||||
* `censusBreakdown` and `reportLeafIndex`.
|
||||
*
|
||||
* @param {HeapAnalysesClient} heapWorker
|
||||
* @param {SnapshotId} id
|
||||
* @param {Object} censusBreakdown
|
||||
* @param {Set<Number> | Number} reportLeafIndex
|
||||
*/
|
||||
const fetchIndividuals = exports.fetchIndividuals =
|
||||
function(heapWorker, id, censusBreakdown, reportLeafIndex) {
|
||||
return function*(dispatch, getState) {
|
||||
if (getState().view.state !== viewState.INDIVIDUALS) {
|
||||
dispatch(view.changeView(viewState.INDIVIDUALS));
|
||||
}
|
||||
|
||||
const snapshot = getSnapshot(getState(), id);
|
||||
assert(snapshot && snapshot.state === states.READ,
|
||||
"The snapshot should already be read into memory");
|
||||
|
||||
if (!dominatorTreeIsComputed(snapshot)) {
|
||||
yield dispatch(computeAndFetchDominatorTree(heapWorker, id));
|
||||
}
|
||||
|
||||
const snapshot_ = getSnapshot(getState(), id);
|
||||
assert(snapshot_.dominatorTree && snapshot_.dominatorTree.root,
|
||||
"Should have a dominator tree with a root.");
|
||||
|
||||
const dominatorTreeId = snapshot_.dominatorTree.dominatorTreeId;
|
||||
|
||||
const indices = isSet(reportLeafIndex)
|
||||
? reportLeafIndex
|
||||
: new Set([reportLeafIndex]);
|
||||
|
||||
let labelDisplay;
|
||||
let nodes;
|
||||
do {
|
||||
labelDisplay = getState().labelDisplay;
|
||||
assert(labelDisplay && labelDisplay.breakdown && labelDisplay.breakdown.by,
|
||||
`Should have a breakdown to label nodes with, got: ${uneval(labelDisplay)}`);
|
||||
|
||||
if (getState().view.state !== viewState.INDIVIDUALS) {
|
||||
// We switched views while in the process of fetching individuals -- any
|
||||
// further work is useless.
|
||||
return;
|
||||
}
|
||||
|
||||
dispatch({ type: actions.FETCH_INDIVIDUALS_START });
|
||||
|
||||
try {
|
||||
({ nodes } = yield heapWorker.getCensusIndividuals({
|
||||
dominatorTreeId,
|
||||
indices,
|
||||
censusBreakdown,
|
||||
labelBreakdown: labelDisplay.breakdown,
|
||||
maxRetainingPaths: Preferences.get("devtools.memory.max-retaining-paths"),
|
||||
maxIndividuals: Preferences.get("devtools.memory.max-individuals"),
|
||||
}));
|
||||
} catch (error) {
|
||||
reportException("actions/snapshot/fetchIndividuals", error);
|
||||
dispatch({ type: actions.INDIVIDUALS_ERROR, error });
|
||||
return;
|
||||
}
|
||||
}
|
||||
while (labelDisplay !== getState().labelDisplay);
|
||||
|
||||
dispatch({
|
||||
type: actions.FETCH_INDIVIDUALS_END,
|
||||
id,
|
||||
censusBreakdown,
|
||||
indices,
|
||||
labelDisplay,
|
||||
nodes,
|
||||
dominatorTree: snapshot_.dominatorTree,
|
||||
});
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Refresh the current individuals view.
|
||||
*
|
||||
* @param {HeapAnalysesClient} heapWorker
|
||||
*/
|
||||
const refreshIndividuals = exports.refreshIndividuals = function(heapWorker) {
|
||||
return function*(dispatch, getState) {
|
||||
assert(getState().view.state === viewState.INDIVIDUALS,
|
||||
"Should be in INDIVIDUALS view.");
|
||||
|
||||
const { individuals } = getState();
|
||||
|
||||
switch (individuals.state) {
|
||||
case individualsState.COMPUTING_DOMINATOR_TREE:
|
||||
case individualsState.FETCHING:
|
||||
// Nothing to do here.
|
||||
return;
|
||||
|
||||
case individualsState.FETCHED:
|
||||
if (getState().individuals.labelDisplay === getState().labelDisplay) {
|
||||
return;
|
||||
}
|
||||
break;
|
||||
|
||||
case individualsState.ERROR:
|
||||
// Doesn't hurt to retry: maybe we won't get an error this time around?
|
||||
break;
|
||||
|
||||
default:
|
||||
assert(false, `Unexpected individuals state: ${individuals.state}`);
|
||||
return;
|
||||
}
|
||||
|
||||
yield dispatch(fetchIndividuals(heapWorker,
|
||||
individuals.id,
|
||||
individuals.censusBreakdown,
|
||||
individuals.indices));
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Refresh the selected snapshot's census data, if need be (for example,
|
||||
* display configuration changed).
|
||||
@@ -305,9 +465,11 @@ const getCurrentCensusTaker = exports.getCurrentCensusTaker = function (currentV
|
||||
* @param {HeapAnalysesClient} heapWorker
|
||||
*/
|
||||
const refreshSelectedCensus = exports.refreshSelectedCensus = function (heapWorker) {
|
||||
console.log("FITZGEN: refreshSelectedCensus");
|
||||
return function*(dispatch, getState) {
|
||||
let snapshot = getState().snapshots.find(s => s.selected);
|
||||
if (!snapshot || snapshot.state !== states.READ) {
|
||||
console.log("FITZGEN: nothing to do");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -319,6 +481,7 @@ const refreshSelectedCensus = exports.refreshSelectedCensus = function (heapWork
|
||||
// task action will follow through and ensure that a census is taken.
|
||||
if ((snapshot.census && snapshot.census.state === censusState.SAVED) ||
|
||||
!snapshot.census) {
|
||||
console.log("FITZGEN: taking census");
|
||||
yield dispatch(takeCensus(heapWorker, snapshot.id));
|
||||
}
|
||||
};
|
||||
@@ -357,8 +520,13 @@ const refreshSelectedTreeMap = exports.refreshSelectedTreeMap = function (heapWo
|
||||
*
|
||||
* @returns {Promise<DominatorTreeId>}
|
||||
*/
|
||||
const computeDominatorTree = exports.computeDominatorTree = function (heapWorker, id) {
|
||||
return function*(dispatch, getState) {
|
||||
const computeDominatorTree = exports.computeDominatorTree =
|
||||
TaskCache.declareCacheableTask({
|
||||
getCacheKey(_, id) {
|
||||
return id;
|
||||
},
|
||||
|
||||
task: function*(heapWorker, id, removeFromCache, dispatch, getState) {
|
||||
const snapshot = getSnapshot(getState(), id);
|
||||
assert(!(snapshot.dominatorTree && snapshot.dominatorTree.dominatorTreeId),
|
||||
"Should not re-compute dominator trees");
|
||||
@@ -369,15 +537,17 @@ const computeDominatorTree = exports.computeDominatorTree = function (heapWorker
|
||||
try {
|
||||
dominatorTreeId = yield heapWorker.computeDominatorTree(snapshot.path);
|
||||
} catch (error) {
|
||||
removeFromCache();
|
||||
reportException("actions/snapshot/computeDominatorTree", error);
|
||||
dispatch({ type: actions.DOMINATOR_TREE_ERROR, id, error });
|
||||
return null;
|
||||
}
|
||||
|
||||
removeFromCache();
|
||||
dispatch({ type: actions.COMPUTE_DOMINATOR_TREE_END, id, dominatorTreeId });
|
||||
return dominatorTreeId;
|
||||
};
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Get the partial subtree, starting from the root, of the
|
||||
@@ -388,8 +558,13 @@ const computeDominatorTree = exports.computeDominatorTree = function (heapWorker
|
||||
*
|
||||
* @returns {Promise<DominatorTreeNode>}
|
||||
*/
|
||||
const fetchDominatorTree = exports.fetchDominatorTree = function (heapWorker, id) {
|
||||
return function*(dispatch, getState) {
|
||||
const fetchDominatorTree = exports.fetchDominatorTree =
|
||||
TaskCache.declareCacheableTask({
|
||||
getCacheKey(_, id) {
|
||||
return id;
|
||||
},
|
||||
|
||||
task: function*(heapWorker, id, removeFromCache, dispatch, getState) {
|
||||
const snapshot = getSnapshot(getState(), id);
|
||||
assert(dominatorTreeIsComputed(snapshot),
|
||||
"Should have dominator tree model and it should be computed");
|
||||
@@ -397,7 +572,7 @@ const fetchDominatorTree = exports.fetchDominatorTree = function (heapWorker, id
|
||||
let display;
|
||||
let root;
|
||||
do {
|
||||
display = getState().dominatorTreeDisplay;
|
||||
display = getState().labelDisplay;
|
||||
assert(display && display.breakdown,
|
||||
`Should have a breakdown to describe nodes with, got: ${uneval(display)}`);
|
||||
|
||||
@@ -407,20 +582,23 @@ const fetchDominatorTree = exports.fetchDominatorTree = function (heapWorker, id
|
||||
root = yield heapWorker.getDominatorTree({
|
||||
dominatorTreeId: snapshot.dominatorTree.dominatorTreeId,
|
||||
breakdown: display.breakdown,
|
||||
maxRetainingPaths: Preferences.get("devtools.memory.max-retaining-paths"),
|
||||
});
|
||||
} catch (error) {
|
||||
removeFromCache();
|
||||
reportException("actions/snapshot/fetchDominatorTree", error);
|
||||
dispatch({ type: actions.DOMINATOR_TREE_ERROR, id, error });
|
||||
return null;
|
||||
}
|
||||
}
|
||||
while (display !== getState().dominatorTreeDisplay);
|
||||
while (display !== getState().labelDisplay);
|
||||
|
||||
removeFromCache();
|
||||
dispatch({ type: actions.FETCH_DOMINATOR_TREE_END, id, root });
|
||||
telemetry.countDominatorTree({ display });
|
||||
return root;
|
||||
};
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Fetch the immediately dominated children represented by the placeholder
|
||||
@@ -430,8 +608,13 @@ const fetchDominatorTree = exports.fetchDominatorTree = function (heapWorker, id
|
||||
* @param {SnapshotId} id
|
||||
* @param {DominatorTreeLazyChildren} lazyChildren
|
||||
*/
|
||||
const fetchImmediatelyDominated = exports.fetchImmediatelyDominated = function (heapWorker, id, lazyChildren) {
|
||||
return function*(dispatch, getState) {
|
||||
const fetchImmediatelyDominated = exports.fetchImmediatelyDominated =
|
||||
TaskCache.declareCacheableTask({
|
||||
getCacheKey(_, id, lazyChildren) {
|
||||
return `${id}-${lazyChildren.key()}`;
|
||||
},
|
||||
|
||||
task: function*(heapWorker, id, lazyChildren, removeFromCache, dispatch, getState) {
|
||||
const snapshot = getSnapshot(getState(), id);
|
||||
assert(snapshot.dominatorTree, "Should have dominator tree model");
|
||||
assert(snapshot.dominatorTree.state === dominatorTreeState.LOADED ||
|
||||
@@ -442,7 +625,7 @@ const fetchImmediatelyDominated = exports.fetchImmediatelyDominated = function (
|
||||
let display;
|
||||
let response;
|
||||
do {
|
||||
display = getState().dominatorTreeDisplay;
|
||||
display = getState().labelDisplay;
|
||||
assert(display, "Should have a display to describe nodes with.");
|
||||
|
||||
dispatch({ type: actions.FETCH_IMMEDIATELY_DOMINATED_START, id });
|
||||
@@ -453,15 +636,18 @@ const fetchImmediatelyDominated = exports.fetchImmediatelyDominated = function (
|
||||
breakdown: display.breakdown,
|
||||
nodeId: lazyChildren.parentNodeId(),
|
||||
startIndex: lazyChildren.siblingIndex(),
|
||||
maxRetainingPaths: Preferences.get("devtools.memory.max-retaining-paths"),
|
||||
});
|
||||
} catch (error) {
|
||||
removeFromCache();
|
||||
reportException("actions/snapshot/fetchImmediatelyDominated", error);
|
||||
dispatch({ type: actions.DOMINATOR_TREE_ERROR, id, error });
|
||||
return null;
|
||||
}
|
||||
}
|
||||
while (display !== getState().dominatorTreeDisplay);
|
||||
while (display !== getState().labelDisplay);
|
||||
|
||||
removeFromCache();
|
||||
dispatch({
|
||||
type: actions.FETCH_IMMEDIATELY_DOMINATED_END,
|
||||
id,
|
||||
@@ -469,8 +655,8 @@ const fetchImmediatelyDominated = exports.fetchImmediatelyDominated = function (
|
||||
nodes: response.nodes,
|
||||
moreChildrenAvailable: response.moreChildrenAvailable,
|
||||
});
|
||||
};
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Compute and then fetch the dominator tree of the snapshot with the given
|
||||
@@ -481,21 +667,29 @@ const fetchImmediatelyDominated = exports.fetchImmediatelyDominated = function (
|
||||
*
|
||||
* @returns {Promise<DominatorTreeNode>}
|
||||
*/
|
||||
const computeAndFetchDominatorTree = exports.computeAndFetchDominatorTree = function (heapWorker, id) {
|
||||
return function*(dispatch, getState) {
|
||||
const computeAndFetchDominatorTree = exports.computeAndFetchDominatorTree =
|
||||
TaskCache.declareCacheableTask({
|
||||
getCacheKey(_, id) {
|
||||
return id;
|
||||
},
|
||||
|
||||
task: function*(heapWorker, id, removeFromCache, dispatch, getState) {
|
||||
const dominatorTreeId = yield dispatch(computeDominatorTree(heapWorker, id));
|
||||
if (dominatorTreeId === null) {
|
||||
removeFromCache();
|
||||
return null;
|
||||
}
|
||||
|
||||
const root = yield dispatch(fetchDominatorTree(heapWorker, id));
|
||||
removeFromCache();
|
||||
|
||||
if (!root) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return root;
|
||||
};
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Update the currently selected snapshot's dominator tree.
|
||||
@@ -556,7 +750,7 @@ const clearSnapshots = exports.clearSnapshots = function (heapWorker) {
|
||||
let censusReady = (s.treeMap && s.treeMap.state === treeMapState.SAVED) ||
|
||||
(s.census && s.census.state === censusState.SAVED);
|
||||
|
||||
return snapshotReady && censusReady
|
||||
return snapshotReady && censusReady;
|
||||
});
|
||||
|
||||
let ids = snapshots.map(s => s.id);
|
||||
@@ -566,6 +760,9 @@ const clearSnapshots = exports.clearSnapshots = function (heapWorker) {
|
||||
if (getState().diffing) {
|
||||
dispatch(diffing.toggleDiffing());
|
||||
}
|
||||
if (getState().individuals) {
|
||||
dispatch(view.popView());
|
||||
}
|
||||
|
||||
yield Promise.all(snapshots.map(snapshot => {
|
||||
return heapWorker.deleteHeapSnapshot(snapshot.path).catch(error => {
|
||||
|
||||
103
devtools/client/memory/actions/task-cache.js
Normal file
103
devtools/client/memory/actions/task-cache.js
Normal file
@@ -0,0 +1,103 @@
|
||||
/* 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/. */
|
||||
"use strict";
|
||||
|
||||
const { assert } = require("devtools/shared/DevToolsUtils");
|
||||
|
||||
/**
|
||||
* The `TaskCache` allows for re-using active tasks when spawning a second task
|
||||
* would simply duplicate work and is unnecessary. It maps from a task's unique
|
||||
* key to the promise of its result.
|
||||
*/
|
||||
const TaskCache = module.exports = class TaskCache {
|
||||
constructor() {
|
||||
this._cache = new Map();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the promise keyed by the given unique `key`, if one exists.
|
||||
*
|
||||
* @param {Any} key
|
||||
* @returns {Promise<Any> | undefined}
|
||||
*/
|
||||
get(key) {
|
||||
return this._cache.get(key);
|
||||
}
|
||||
|
||||
/**
|
||||
* Put the task result promise in the cache and associate it with the given
|
||||
* `key` which must not already have an entry in the cache.
|
||||
*
|
||||
* @param {Any} key
|
||||
* @param {Promise<Any>} promise
|
||||
*/
|
||||
put(key, promise) {
|
||||
assert(!this._cache.has(key),
|
||||
"We should not override extant entries");
|
||||
|
||||
this._cache.set(key, promise);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the cache entry with the given key.
|
||||
*
|
||||
* @param {Any} key
|
||||
*/
|
||||
remove(key) {
|
||||
console.log("FITZGEN: removing task from cache with keky =", key);
|
||||
assert(this._cache.has(key),
|
||||
`Should have an extant entry for key = ${key}`);
|
||||
|
||||
this._cache.delete(key);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Create a new action-orchestrating task that is automatically cached. The
|
||||
* tasks themselves are responsible from removing themselves from the cache.
|
||||
*
|
||||
* @param {Function(...args) -> Any} getCacheKey
|
||||
* @param {Generator(...args) -> Any} task
|
||||
*
|
||||
* @returns Cacheable, Action-Creating Task
|
||||
*/
|
||||
TaskCache.declareCacheableTask = function({ getCacheKey, task }) {
|
||||
const cache = new TaskCache();
|
||||
|
||||
return function(...args) {
|
||||
return function*(dispatch, getState) {
|
||||
const key = getCacheKey(...args);
|
||||
|
||||
const extantResult = cache.get(key);
|
||||
if (extantResult) {
|
||||
console.log("FITZGEN: re-using task with cache key =", key);
|
||||
return extantResult;
|
||||
}
|
||||
|
||||
console.log("FITZGEN: creating new task with cache key =", key);
|
||||
|
||||
// Ensure that we have our new entry in the cache *before* dispatching the
|
||||
// task!
|
||||
let resolve;
|
||||
cache.put(key, new Promise(r => {
|
||||
resolve = r;
|
||||
}));
|
||||
|
||||
resolve(dispatch(function*() {
|
||||
try {
|
||||
args.push(() => cache.remove(key), dispatch, getState);
|
||||
return yield* task(...args);
|
||||
} catch (error) {
|
||||
// Don't perma-cache errors.
|
||||
if (cache.get(key)) {
|
||||
cache.remove(key);
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
}));
|
||||
|
||||
return yield cache.get(key);
|
||||
};
|
||||
};
|
||||
};
|
||||
@@ -3,7 +3,9 @@
|
||||
* You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
"use strict";
|
||||
|
||||
const { assert } = require("devtools/shared/DevToolsUtils");
|
||||
const { actions } = require("../constants");
|
||||
const { findSelectedSnapshot } = require("../utils");
|
||||
const refresh = require("./refresh");
|
||||
|
||||
/**
|
||||
@@ -12,9 +14,28 @@ const refresh = require("./refresh");
|
||||
* @param {viewState} view
|
||||
*/
|
||||
const changeView = exports.changeView = function (view) {
|
||||
return {
|
||||
type: actions.CHANGE_VIEW,
|
||||
view
|
||||
return function(dispatch, getState) {
|
||||
dispatch({
|
||||
type: actions.CHANGE_VIEW,
|
||||
newViewState: view,
|
||||
oldDiffing: getState().diffing,
|
||||
oldSelected: findSelectedSnapshot(getState()),
|
||||
});
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Given that we are in the INDIVIDUALS view state, go back to the state we were
|
||||
* in before.
|
||||
*/
|
||||
const popView = exports.popView = function () {
|
||||
return function(dispatch, getState) {
|
||||
const { previous } = getState().view;
|
||||
assert(previous);
|
||||
dispatch({
|
||||
type: actions.POP_VIEW,
|
||||
previousView: previous,
|
||||
});
|
||||
};
|
||||
};
|
||||
|
||||
@@ -23,6 +44,7 @@ const changeView = exports.changeView = function (view) {
|
||||
* the heap worker.
|
||||
*
|
||||
* @param {viewState} view
|
||||
* @param {HeapAnalysesClient} heapWorker
|
||||
*/
|
||||
exports.changeViewAndRefresh = function (view, heapWorker) {
|
||||
return function* (dispatch, getState) {
|
||||
@@ -30,3 +52,16 @@ exports.changeViewAndRefresh = function (view, heapWorker) {
|
||||
yield dispatch(refresh.refresh(heapWorker));
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Given that we are in the INDIVIDUALS view state, go back to the state we were
|
||||
* previously in and refresh our data.
|
||||
*
|
||||
* @param {HeapAnalysesClient} heapWorker
|
||||
*/
|
||||
exports.popViewAndRefresh = function(heapWorker) {
|
||||
return function* (dispatch, getState) {
|
||||
dispatch(popView());
|
||||
yield dispatch(refresh.refresh(heapWorker));
|
||||
};
|
||||
};
|
||||
|
||||
@@ -6,15 +6,15 @@ const { assert } = require("devtools/shared/DevToolsUtils");
|
||||
const { appinfo } = require("Services");
|
||||
const { DOM: dom, createClass, createFactory, PropTypes } = require("devtools/client/shared/vendor/react");
|
||||
const { connect } = require("devtools/client/shared/vendor/react-redux");
|
||||
const { censusDisplays, dominatorTreeDisplays, treeMapDisplays, diffingState, viewState } = require("./constants");
|
||||
const { censusDisplays, labelDisplays, treeMapDisplays, diffingState, viewState } = require("./constants");
|
||||
const { toggleRecordingAllocationStacks } = require("./actions/allocations");
|
||||
const { setCensusDisplayAndRefresh } = require("./actions/census-display");
|
||||
const { setDominatorTreeDisplayAndRefresh } = require("./actions/dominator-tree-display");
|
||||
const { setLabelDisplayAndRefresh } = require("./actions/label-display");
|
||||
const { setTreeMapDisplayAndRefresh } = require("./actions/tree-map-display");
|
||||
|
||||
const {
|
||||
getCustomCensusDisplays,
|
||||
getCustomDominatorTreeDisplays,
|
||||
getCustomLabelDisplays,
|
||||
getCustomTreeMapDisplays,
|
||||
} = require("devtools/client/memory/utils");
|
||||
const {
|
||||
@@ -37,8 +37,10 @@ const {
|
||||
expandDominatorTreeNode,
|
||||
collapseDominatorTreeNode,
|
||||
focusDominatorTreeNode,
|
||||
fetchIndividuals,
|
||||
focusIndividual,
|
||||
} = require("./actions/snapshot");
|
||||
const { changeViewAndRefresh } = require("./actions/view");
|
||||
const { changeViewAndRefresh, popViewAndRefresh } = require("./actions/view");
|
||||
const { resizeShortestPaths } = require("./actions/sizes");
|
||||
const Toolbar = createFactory(require("./components/toolbar"));
|
||||
const List = createFactory(require("./components/list"));
|
||||
@@ -117,16 +119,16 @@ const MemoryApp = createClass({
|
||||
].concat(custom);
|
||||
},
|
||||
|
||||
_getDominatorTreeDisplays() {
|
||||
const customDisplays = getCustomDominatorTreeDisplays();
|
||||
_getLabelDisplays() {
|
||||
const customDisplays = getCustomLabelDisplays();
|
||||
const custom = Object.keys(customDisplays).reduce((arr, key) => {
|
||||
arr.push(customDisplays[key]);
|
||||
return arr;
|
||||
}, []);
|
||||
|
||||
return [
|
||||
dominatorTreeDisplays.coarseType,
|
||||
dominatorTreeDisplays.allocationStack,
|
||||
labelDisplays.coarseType,
|
||||
labelDisplays.allocationStack,
|
||||
].concat(custom);
|
||||
},
|
||||
|
||||
@@ -154,6 +156,9 @@ const MemoryApp = createClass({
|
||||
diffing,
|
||||
view,
|
||||
sizes,
|
||||
censusDisplay,
|
||||
labelDisplay,
|
||||
individuals,
|
||||
} = this.props;
|
||||
|
||||
const selectedSnapshot = snapshots.find(s => s.selected);
|
||||
@@ -171,6 +176,7 @@ const MemoryApp = createClass({
|
||||
Toolbar({
|
||||
snapshots,
|
||||
censusDisplays: this._getCensusDisplays(),
|
||||
censusDisplay,
|
||||
onCensusDisplayChange: newDisplay =>
|
||||
dispatch(setCensusDisplayAndRefresh(heapWorker, newDisplay)),
|
||||
onImportClick: () => dispatch(pickFileAndImportSnapshotAndCensus(heapWorker)),
|
||||
@@ -185,9 +191,10 @@ const MemoryApp = createClass({
|
||||
diffing,
|
||||
onToggleDiffing: () => dispatch(toggleDiffing()),
|
||||
view,
|
||||
dominatorTreeDisplays: this._getDominatorTreeDisplays(),
|
||||
onDominatorTreeDisplayChange: newDisplay =>
|
||||
dispatch(setDominatorTreeDisplayAndRefresh(heapWorker, newDisplay)),
|
||||
labelDisplays: this._getLabelDisplays(),
|
||||
labelDisplay,
|
||||
onLabelDisplayChange: newDisplay =>
|
||||
dispatch(setLabelDisplayAndRefresh(heapWorker, newDisplay)),
|
||||
treeMapDisplays: this._getTreeMapDisplays(),
|
||||
onTreeMapDisplayChange: newDisplay =>
|
||||
dispatch(setTreeMapDisplayAndRefresh(heapWorker, newDisplay)),
|
||||
@@ -217,6 +224,22 @@ const MemoryApp = createClass({
|
||||
dispatch(fetchImmediatelyDominated(heapWorker,
|
||||
selectedSnapshot.id,
|
||||
lazyChildren)),
|
||||
onPopView: () => dispatch(popViewAndRefresh(heapWorker)),
|
||||
individuals,
|
||||
onViewIndividuals: node => {
|
||||
const snapshotId = diffing
|
||||
? diffing.secondSnapshotId
|
||||
: selectedSnapshot.id;
|
||||
dispatch(fetchIndividuals(heapWorker,
|
||||
snapshotId,
|
||||
censusDisplay.breakdown,
|
||||
node.reportLeafIndex));
|
||||
},
|
||||
onFocusIndividual: node => {
|
||||
assert(view.state === viewState.INDIVIDUALS,
|
||||
"Should be in the individuals view");
|
||||
dispatch(focusIndividual(node));
|
||||
},
|
||||
onCensusExpand: (census, node) => {
|
||||
if (diffing) {
|
||||
assert(diffing.census === census,
|
||||
@@ -251,7 +274,7 @@ const MemoryApp = createClass({
|
||||
}
|
||||
},
|
||||
onDominatorTreeExpand: node => {
|
||||
assert(view === viewState.DOMINATOR_TREE,
|
||||
assert(view.state === viewState.DOMINATOR_TREE,
|
||||
"If expanding dominator tree nodes, should be in dominator tree view");
|
||||
assert(selectedSnapshot, "...and we should have a selected snapshot");
|
||||
assert(selectedSnapshot.dominatorTree,
|
||||
@@ -259,7 +282,7 @@ const MemoryApp = createClass({
|
||||
dispatch(expandDominatorTreeNode(selectedSnapshot.id, node));
|
||||
},
|
||||
onDominatorTreeCollapse: node => {
|
||||
assert(view === viewState.DOMINATOR_TREE,
|
||||
assert(view.state === viewState.DOMINATOR_TREE,
|
||||
"If collapsing dominator tree nodes, should be in dominator tree view");
|
||||
assert(selectedSnapshot, "...and we should have a selected snapshot");
|
||||
assert(selectedSnapshot.dominatorTree,
|
||||
@@ -267,7 +290,7 @@ const MemoryApp = createClass({
|
||||
dispatch(collapseDominatorTreeNode(selectedSnapshot.id, node));
|
||||
},
|
||||
onDominatorTreeFocus: node => {
|
||||
assert(view === viewState.DOMINATOR_TREE,
|
||||
assert(view.state === viewState.DOMINATOR_TREE,
|
||||
"If focusing dominator tree nodes, should be in dominator tree view");
|
||||
assert(selectedSnapshot, "...and we should have a selected snapshot");
|
||||
assert(selectedSnapshot.dominatorTree,
|
||||
|
||||
@@ -4,13 +4,23 @@
|
||||
|
||||
const { DOM: dom, createClass } = require("devtools/client/shared/vendor/react");
|
||||
const { L10N } = require("../utils");
|
||||
const models = require("../models");
|
||||
|
||||
const CensusHeader = module.exports = createClass({
|
||||
displayName: "CensusHeader",
|
||||
|
||||
propTypes: { },
|
||||
propTypes: {
|
||||
diffing: models.diffingModel,
|
||||
},
|
||||
|
||||
render() {
|
||||
let individualsCell;
|
||||
if (!this.props.diffing) {
|
||||
individualsCell = dom.span({
|
||||
className: "heap-tree-item-field heap-tree-item-individuals"
|
||||
});
|
||||
}
|
||||
|
||||
return dom.div(
|
||||
{
|
||||
className: "header"
|
||||
@@ -48,6 +58,8 @@ const CensusHeader = module.exports = createClass({
|
||||
L10N.getStr("heapview.field.totalcount")
|
||||
),
|
||||
|
||||
individualsCell,
|
||||
|
||||
dom.span(
|
||||
{
|
||||
className: "heap-tree-item-name",
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
/* 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/. */
|
||||
"use strict";
|
||||
|
||||
const { isSavedFrame } = require("devtools/shared/DevToolsUtils");
|
||||
const { DOM: dom, createClass, createFactory } = require("devtools/client/shared/vendor/react");
|
||||
@@ -16,7 +17,7 @@ const CensusTreeItem = module.exports = createClass({
|
||||
|| this.props.depth != nextProps.depth
|
||||
|| this.props.expanded != nextProps.expanded
|
||||
|| this.props.focused != nextProps.focused
|
||||
|| this.props.showSign != nextProps.showSign;
|
||||
|| this.props.diffing != nextProps.diffing;
|
||||
},
|
||||
|
||||
render() {
|
||||
@@ -27,22 +28,23 @@ const CensusTreeItem = module.exports = createClass({
|
||||
focused,
|
||||
getPercentBytes,
|
||||
getPercentCount,
|
||||
showSign,
|
||||
diffing,
|
||||
onViewSourceInDebugger,
|
||||
onViewIndividuals,
|
||||
inverted,
|
||||
} = this.props;
|
||||
|
||||
const bytes = formatNumber(item.bytes, showSign);
|
||||
const percentBytes = formatPercent(getPercentBytes(item.bytes), showSign);
|
||||
const bytes = formatNumber(item.bytes, !!diffing);
|
||||
const percentBytes = formatPercent(getPercentBytes(item.bytes), !!diffing);
|
||||
|
||||
const count = formatNumber(item.count, showSign);
|
||||
const percentCount = formatPercent(getPercentCount(item.count), showSign);
|
||||
const count = formatNumber(item.count, !!diffing);
|
||||
const percentCount = formatPercent(getPercentCount(item.count), !!diffing);
|
||||
|
||||
const totalBytes = formatNumber(item.totalBytes, showSign);
|
||||
const percentTotalBytes = formatPercent(getPercentBytes(item.totalBytes), showSign);
|
||||
const totalBytes = formatNumber(item.totalBytes, !!diffing);
|
||||
const percentTotalBytes = formatPercent(getPercentBytes(item.totalBytes), !!diffing);
|
||||
|
||||
const totalCount = formatNumber(item.totalCount, showSign);
|
||||
const percentTotalCount = formatPercent(getPercentCount(item.totalCount), showSign);
|
||||
const totalCount = formatNumber(item.totalCount, !!diffing);
|
||||
const percentTotalCount = formatPercent(getPercentCount(item.totalCount), !!diffing);
|
||||
|
||||
let pointer;
|
||||
if (inverted && depth > 0) {
|
||||
@@ -51,7 +53,35 @@ const CensusTreeItem = module.exports = createClass({
|
||||
pointer = dom.span({ className: "children-pointer" }, "↘");
|
||||
}
|
||||
|
||||
return dom.div({ className: `heap-tree-item ${focused ? "focused" : ""}` },
|
||||
let individualsCell;
|
||||
if (!diffing) {
|
||||
let individualsButton;
|
||||
if (item.reportLeafIndex !== undefined) {
|
||||
individualsButton = dom.button(
|
||||
{
|
||||
key: `individuals-button-${item.id}`,
|
||||
title: L10N.getStr("tree-item.view-individuals.tooltip"),
|
||||
className: "devtools-button individuals-button",
|
||||
onClick: e => {
|
||||
// Don't let the event bubble up to cause this item to focus after
|
||||
// we have switched views, which would lead to assertion failures.
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
onViewIndividuals(item);
|
||||
},
|
||||
},
|
||||
"⁂"
|
||||
);
|
||||
}
|
||||
individualsCell = dom.span(
|
||||
{ className: "heap-tree-item-field heap-tree-item-individuals" },
|
||||
individualsButton
|
||||
);
|
||||
}
|
||||
|
||||
return dom.div(
|
||||
{ className: `heap-tree-item ${focused ? "focused" : ""}` },
|
||||
dom.span({ className: "heap-tree-item-field heap-tree-item-bytes" },
|
||||
dom.span({ className: "heap-tree-number" }, bytes),
|
||||
dom.span({ className: "heap-tree-percent" }, percentBytes)),
|
||||
@@ -64,8 +94,12 @@ const CensusTreeItem = module.exports = createClass({
|
||||
dom.span({ className: "heap-tree-item-field heap-tree-item-total-count" },
|
||||
dom.span({ className: "heap-tree-number" }, totalCount),
|
||||
dom.span({ className: "heap-tree-percent" }, percentTotalCount)),
|
||||
dom.span({ className: "heap-tree-item-field heap-tree-item-name",
|
||||
style: { marginLeft: depth * TREE_ROW_HEIGHT }},
|
||||
individualsCell,
|
||||
dom.span(
|
||||
{
|
||||
className: "heap-tree-item-field heap-tree-item-name",
|
||||
style: { marginLeft: depth * TREE_ROW_HEIGHT }
|
||||
},
|
||||
arrow,
|
||||
pointer,
|
||||
this.toLabel(item.name, onViewSourceInDebugger)
|
||||
|
||||
@@ -18,6 +18,7 @@ const Census = module.exports = createClass({
|
||||
onCollapse: PropTypes.func.isRequired,
|
||||
onFocus: PropTypes.func.isRequired,
|
||||
onViewSourceInDebugger: PropTypes.func.isRequired,
|
||||
onViewIndividuals: PropTypes.func.isRequired,
|
||||
diffing: diffingModel,
|
||||
},
|
||||
|
||||
@@ -29,6 +30,7 @@ const Census = module.exports = createClass({
|
||||
onFocus,
|
||||
diffing,
|
||||
onViewSourceInDebugger,
|
||||
onViewIndividuals,
|
||||
} = this.props;
|
||||
|
||||
const report = census.report;
|
||||
@@ -65,8 +67,9 @@ const Census = module.exports = createClass({
|
||||
expanded,
|
||||
getPercentBytes,
|
||||
getPercentCount,
|
||||
showSign: !!diffing,
|
||||
diffing,
|
||||
inverted: census.display.inverted,
|
||||
onViewIndividuals,
|
||||
}),
|
||||
getRoots: () => report.children || [],
|
||||
getKey: node => node.id,
|
||||
|
||||
@@ -22,7 +22,7 @@ const DominatorTreeItem = module.exports = createClass({
|
||||
propTypes: {
|
||||
item: PropTypes.object.isRequired,
|
||||
depth: PropTypes.number.isRequired,
|
||||
arrow: PropTypes.object.isRequired,
|
||||
arrow: PropTypes.object,
|
||||
focused: PropTypes.bool.isRequired,
|
||||
getPercentSize: PropTypes.func.isRequired,
|
||||
onViewSourceInDebugger: PropTypes.func.isRequired,
|
||||
|
||||
@@ -10,6 +10,8 @@ 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 {
|
||||
@@ -18,23 +20,26 @@ const {
|
||||
viewState,
|
||||
censusState,
|
||||
treeMapState,
|
||||
dominatorTreeState
|
||||
dominatorTreeState,
|
||||
individualsState,
|
||||
} = require("../constants");
|
||||
const { snapshot: snapshotModel, diffingModel } = require("../models");
|
||||
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 {viewState} view
|
||||
* @param {models.view} view
|
||||
* @param {snapshotModel} snapshot
|
||||
* @param {diffingModel} diffing
|
||||
* @param {individualsModel} individuals
|
||||
*
|
||||
* @return {snapshotState|diffingState|dominatorTreeState}
|
||||
*/
|
||||
function getState(view, snapshot, diffing) {
|
||||
switch (view) {
|
||||
function getState(view, snapshot, diffing, individuals) {
|
||||
switch (view.state) {
|
||||
case viewState.CENSUS:
|
||||
return snapshot.census
|
||||
? snapshot.census.state
|
||||
@@ -52,9 +57,12 @@ function getState(view, snapshot, diffing) {
|
||||
return snapshot.dominatorTree
|
||||
? snapshot.dominatorTree.state
|
||||
: snapshot.state;
|
||||
|
||||
case viewState.INDIVIDUALS:
|
||||
return individuals.state;
|
||||
}
|
||||
|
||||
assert(false, `Unexpected view state: ${view}`);
|
||||
assert(false, `Unexpected view state: ${view.state}`);
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -63,7 +71,7 @@ function getState(view, snapshot, diffing) {
|
||||
* state. Return false otherwise.
|
||||
*
|
||||
* @param {snapshotState|diffingState|dominatorTreeState} state
|
||||
* @param {viewState} view
|
||||
* @param {models.view} view
|
||||
* @param {snapshotModel} snapshot
|
||||
*
|
||||
* @returns {Boolean}
|
||||
@@ -81,9 +89,11 @@ function shouldDisplayStatus(state, view, snapshot) {
|
||||
case dominatorTreeState.COMPUTING:
|
||||
case dominatorTreeState.COMPUTED:
|
||||
case dominatorTreeState.FETCHING:
|
||||
case individualsState.COMPUTING_DOMINATOR_TREE:
|
||||
case individualsState.FETCHING:
|
||||
return true;
|
||||
}
|
||||
return view === viewState.DOMINATOR_TREE && !snapshot.dominatorTree;
|
||||
return view.state === viewState.DOMINATOR_TREE && !snapshot.dominatorTree;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -121,10 +131,11 @@ function shouldDisplayThrobber(diffing) {
|
||||
*
|
||||
* @param {snapshotModel} snapshot
|
||||
* @param {diffingModel} diffing
|
||||
* @param {individualsModel} individuals
|
||||
*
|
||||
* @returns {Error|null}
|
||||
*/
|
||||
function getError(snapshot, diffing) {
|
||||
function getError(snapshot, diffing, individuals) {
|
||||
if (diffing) {
|
||||
if (diffing.state === diffingState.ERROR) {
|
||||
return diffing.error;
|
||||
@@ -153,6 +164,10 @@ function getError(snapshot, diffing) {
|
||||
}
|
||||
}
|
||||
|
||||
if (individuals && individuals.state === individualsState.ERROR) {
|
||||
return individuals.error;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -178,8 +193,12 @@ const Heap = module.exports = createClass({
|
||||
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: PropTypes.string.isRequired,
|
||||
view: models.view.isRequired,
|
||||
sizes: PropTypes.object.isRequired,
|
||||
},
|
||||
|
||||
@@ -190,40 +209,52 @@ const Heap = module.exports = createClass({
|
||||
onSnapshotClick,
|
||||
onLoadMoreSiblings,
|
||||
onViewSourceInDebugger,
|
||||
onViewIndividuals,
|
||||
individuals,
|
||||
view,
|
||||
} = this.props;
|
||||
|
||||
if (!diffing && !snapshot) {
|
||||
|
||||
if (!diffing && !snapshot && !individuals) {
|
||||
return this._renderInitial(onSnapshotClick);
|
||||
}
|
||||
|
||||
const state = getState(view, snapshot, diffing);
|
||||
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);
|
||||
const error = getError(snapshot, diffing, individuals);
|
||||
if (error) {
|
||||
return this._renderError(state, statusText, error);
|
||||
}
|
||||
|
||||
if (view === viewState.CENSUS || view === viewState.DIFFING) {
|
||||
const census = view === viewState.CENSUS
|
||||
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);
|
||||
return this._renderCensus(state, census, diffing, onViewSourceInDebugger,
|
||||
onViewIndividuals);
|
||||
}
|
||||
|
||||
if (view === viewState.TREE_MAP) {
|
||||
if (view.state === viewState.TREE_MAP) {
|
||||
return this._renderTreeMap(state, snapshot.treeMap);
|
||||
}
|
||||
|
||||
assert(view === viewState.DOMINATOR_TREE,
|
||||
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");
|
||||
@@ -290,7 +321,7 @@ const Heap = module.exports = createClass({
|
||||
);
|
||||
},
|
||||
|
||||
_renderCensus(state, census, diffing, onViewSourceInDebugger) {
|
||||
_renderCensus(state, census, diffing, onViewSourceInDebugger, onViewIndividuals) {
|
||||
assert(census.report, "Should not render census that does not have a report");
|
||||
|
||||
if (!census.report.children) {
|
||||
@@ -310,9 +341,10 @@ const Heap = module.exports = createClass({
|
||||
L10N.getStr("heapview.noAllocationStacks")));
|
||||
}
|
||||
|
||||
contents.push(CensusHeader());
|
||||
contents.push(CensusHeader({ diffing }));
|
||||
contents.push(Census({
|
||||
onViewSourceInDebugger,
|
||||
onViewIndividuals,
|
||||
diffing,
|
||||
census,
|
||||
onExpand: node => this.props.onCensusExpand(census, node),
|
||||
@@ -330,6 +362,61 @@ const Heap = module.exports = createClass({
|
||||
);
|
||||
},
|
||||
|
||||
_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(
|
||||
{
|
||||
|
||||
44
devtools/client/memory/components/individuals-header.js
Normal file
44
devtools/client/memory/components/individuals-header.js
Normal file
@@ -0,0 +1,44 @@
|
||||
/* 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 } = require("devtools/client/shared/vendor/react");
|
||||
const { L10N } = require("../utils");
|
||||
|
||||
const IndividualsHeader = module.exports = createClass({
|
||||
displayName: "IndividualsHeader",
|
||||
|
||||
propTypes: { },
|
||||
|
||||
render() {
|
||||
return dom.div(
|
||||
{
|
||||
className: "header"
|
||||
},
|
||||
|
||||
dom.span(
|
||||
{
|
||||
className: "heap-tree-item-bytes",
|
||||
title: L10N.getStr("heapview.field.retainedSize.tooltip"),
|
||||
},
|
||||
L10N.getStr("heapview.field.retainedSize")
|
||||
),
|
||||
|
||||
dom.span(
|
||||
{
|
||||
className: "heap-tree-item-bytes",
|
||||
title: L10N.getStr("heapview.field.shallowSize.tooltip"),
|
||||
},
|
||||
L10N.getStr("heapview.field.shallowSize")
|
||||
),
|
||||
|
||||
dom.span(
|
||||
{
|
||||
className: "heap-tree-item-name",
|
||||
title: L10N.getStr("individuals.field.node.tooltip"),
|
||||
},
|
||||
L10N.getStr("individuals.field.node")
|
||||
)
|
||||
);
|
||||
}
|
||||
});
|
||||
61
devtools/client/memory/components/individuals.js
Normal file
61
devtools/client/memory/components/individuals.js
Normal file
@@ -0,0 +1,61 @@
|
||||
/* 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 } = require("devtools/shared/DevToolsUtils");
|
||||
const { createParentMap } = require("devtools/shared/heapsnapshot/CensusUtils");
|
||||
const Tree = createFactory(require("devtools/client/shared/components/tree"));
|
||||
const DominatorTreeItem = createFactory(require("./dominator-tree-item"));
|
||||
const { L10N } = require("../utils");
|
||||
const { TREE_ROW_HEIGHT } = require("../constants");
|
||||
const models = require("../models");
|
||||
|
||||
/**
|
||||
* The list of individuals in a census group.
|
||||
*/
|
||||
const Individuals = module.exports = createClass({
|
||||
displayName: "Individuals",
|
||||
|
||||
propTypes: {
|
||||
onViewSourceInDebugger: PropTypes.func.isRequired,
|
||||
onFocus: PropTypes.func.isRequired,
|
||||
individuals: models.individuals,
|
||||
dominatorTree: models.dominatorTreeModel,
|
||||
},
|
||||
|
||||
render() {
|
||||
const {
|
||||
individuals,
|
||||
dominatorTree,
|
||||
onViewSourceInDebugger,
|
||||
onFocus,
|
||||
} = this.props;
|
||||
|
||||
return Tree({
|
||||
key: "individuals-tree",
|
||||
autoExpandDepth: 0,
|
||||
focused: individuals.focused,
|
||||
getParent: node => null,
|
||||
getChildren: node => [],
|
||||
isExpanded: node => false,
|
||||
onExpand: () => {},
|
||||
onCollapse: () => {},
|
||||
onFocus,
|
||||
renderItem: (item, depth, focused, _, expanded) => {
|
||||
return DominatorTreeItem({
|
||||
item,
|
||||
depth,
|
||||
focused,
|
||||
arrow: undefined,
|
||||
expanded,
|
||||
getPercentSize: size => (size / dominatorTree.root.retainedSize) * 100,
|
||||
onViewSourceInDebugger,
|
||||
});
|
||||
},
|
||||
getRoots: () => individuals.nodes,
|
||||
getKey: node => node.nodeId,
|
||||
itemHeight: TREE_ROW_HEIGHT,
|
||||
});
|
||||
}
|
||||
});
|
||||
@@ -15,6 +15,8 @@ DevToolsModules(
|
||||
'dominator-tree-item.js',
|
||||
'dominator-tree.js',
|
||||
'heap.js',
|
||||
'individuals-header.js',
|
||||
'individuals.js',
|
||||
'list.js',
|
||||
'shortest-paths.js',
|
||||
'snapshot-list-item.js',
|
||||
|
||||
@@ -11,10 +11,14 @@ const { viewState } = require("../constants");
|
||||
|
||||
module.exports = createClass({
|
||||
displayName: "Toolbar",
|
||||
|
||||
propTypes: {
|
||||
censusDisplays: PropTypes.arrayOf(PropTypes.shape({
|
||||
displayName: PropTypes.string.isRequired,
|
||||
})).isRequired,
|
||||
censusDisplay: PropTypes.shape({
|
||||
displayName: PropTypes.string.isRequired,
|
||||
}).isRequired,
|
||||
onTakeSnapshotClick: PropTypes.func.isRequired,
|
||||
onImportClick: PropTypes.func.isRequired,
|
||||
onClearSnapshotsClick: PropTypes.func.isRequired,
|
||||
@@ -25,12 +29,15 @@ module.exports = createClass({
|
||||
setFilterString: PropTypes.func.isRequired,
|
||||
diffing: models.diffingModel,
|
||||
onToggleDiffing: PropTypes.func.isRequired,
|
||||
view: PropTypes.string.isRequired,
|
||||
view: models.view.isRequired,
|
||||
onViewChange: PropTypes.func.isRequired,
|
||||
dominatorTreeDisplays: PropTypes.arrayOf(PropTypes.shape({
|
||||
labelDisplays: PropTypes.arrayOf(PropTypes.shape({
|
||||
displayName: PropTypes.string.isRequired,
|
||||
})).isRequired,
|
||||
onDominatorTreeDisplayChange: PropTypes.func.isRequired,
|
||||
labelDisplay: PropTypes.shape({
|
||||
displayName: PropTypes.string.isRequired,
|
||||
}).isRequired,
|
||||
onLabelDisplayChange: PropTypes.func.isRequired,
|
||||
treeMapDisplays: PropTypes.arrayOf(PropTypes.shape({
|
||||
displayName: PropTypes.string.isRequired,
|
||||
})).isRequired,
|
||||
@@ -45,8 +52,10 @@ module.exports = createClass({
|
||||
onClearSnapshotsClick,
|
||||
onCensusDisplayChange,
|
||||
censusDisplays,
|
||||
dominatorTreeDisplays,
|
||||
onDominatorTreeDisplayChange,
|
||||
censusDisplay,
|
||||
labelDisplays,
|
||||
labelDisplay,
|
||||
onLabelDisplayChange,
|
||||
treeMapDisplays,
|
||||
onTreeMapDisplayChange,
|
||||
onToggleRecordAllocationStacks,
|
||||
@@ -61,7 +70,7 @@ module.exports = createClass({
|
||||
} = this.props;
|
||||
|
||||
let viewToolbarOptions;
|
||||
if (view == viewState.CENSUS || view === viewState.DIFFING) {
|
||||
if (view.state == viewState.CENSUS || view.state === viewState.DIFFING) {
|
||||
viewToolbarOptions = dom.div(
|
||||
{
|
||||
className: "toolbar-group"
|
||||
@@ -82,6 +91,7 @@ module.exports = createClass({
|
||||
censusDisplays.find(b => b.displayName === e.target.value);
|
||||
onCensusDisplayChange(newDisplay);
|
||||
},
|
||||
value: censusDisplay.displayName,
|
||||
},
|
||||
censusDisplays.map(({ tooltip, displayName }) => dom.option(
|
||||
{
|
||||
@@ -106,7 +116,7 @@ module.exports = createClass({
|
||||
value: filterString || undefined,
|
||||
})
|
||||
);
|
||||
} else if (view == viewState.TREE_MAP) {
|
||||
} else if (view.state == viewState.TREE_MAP) {
|
||||
assert(treeMapDisplays.length >= 1,
|
||||
"Should always have at least one tree map display");
|
||||
|
||||
@@ -145,7 +155,8 @@ module.exports = createClass({
|
||||
)
|
||||
: null;
|
||||
} else {
|
||||
assert(view === viewState.DOMINATOR_TREE);
|
||||
assert(view.state === viewState.DOMINATOR_TREE ||
|
||||
view.state === viewState.INDIVIDUALS);
|
||||
|
||||
viewToolbarOptions = dom.div(
|
||||
{
|
||||
@@ -160,16 +171,17 @@ module.exports = createClass({
|
||||
L10N.getStr("toolbar.labelBy"),
|
||||
dom.select(
|
||||
{
|
||||
id: "select-dominator-tree-display",
|
||||
id: "select-label-display",
|
||||
onChange: e => {
|
||||
const newDisplay =
|
||||
dominatorTreeDisplays.find(b => b.displayName === e.target.value);
|
||||
onDominatorTreeDisplayChange(newDisplay);
|
||||
labelDisplays.find(b => b.displayName === e.target.value);
|
||||
onLabelDisplayChange(newDisplay);
|
||||
},
|
||||
value: labelDisplay.displayName,
|
||||
},
|
||||
dominatorTreeDisplays.map(({ tooltip, displayName }) => dom.option(
|
||||
labelDisplays.map(({ tooltip, displayName }) => dom.option(
|
||||
{
|
||||
key: `dominator-tree-display-${displayName}`,
|
||||
key: `label-display-${displayName}`,
|
||||
value: displayName,
|
||||
title: tooltip,
|
||||
},
|
||||
@@ -181,7 +193,7 @@ module.exports = createClass({
|
||||
}
|
||||
|
||||
let viewSelect;
|
||||
if (view !== viewState.DIFFING) {
|
||||
if (view.state !== viewState.DIFFING && view.state !== viewState.INDIVIDUALS) {
|
||||
viewSelect = dom.label(
|
||||
{
|
||||
title: L10N.getStr("toolbar.view.tooltip"),
|
||||
@@ -192,12 +204,12 @@ module.exports = createClass({
|
||||
id: "select-view",
|
||||
onChange: e => onViewChange(e.target.value),
|
||||
defaultValue: view,
|
||||
value: view.state,
|
||||
},
|
||||
dom.option(
|
||||
{
|
||||
value: viewState.TREE_MAP,
|
||||
title: L10N.getStr("toolbar.view.treemap.tooltip"),
|
||||
selected: view
|
||||
},
|
||||
L10N.getStr("toolbar.view.treemap")
|
||||
),
|
||||
@@ -268,6 +280,7 @@ module.exports = createClass({
|
||||
|
||||
dom.label(
|
||||
{
|
||||
id: "record-allocation-stacks-label",
|
||||
title: L10N.getStr("checkbox.recordAllocationStacks.tooltip"),
|
||||
},
|
||||
dom.input({
|
||||
|
||||
@@ -12,7 +12,7 @@ exports.ALLOCATION_RECORDING_OPTIONS = {
|
||||
|
||||
// If TREE_ROW_HEIGHT changes, be sure to change `var(--heap-tree-row-height)`
|
||||
// in `devtools/client/themes/memory.css`
|
||||
exports.TREE_ROW_HEIGHT = 14;
|
||||
exports.TREE_ROW_HEIGHT = 18;
|
||||
|
||||
/*** Actions ******************************************************************/
|
||||
|
||||
@@ -75,13 +75,14 @@ actions.DIFFING_ERROR = "diffing-error";
|
||||
actions.SET_CENSUS_DISPLAY = "set-census-display";
|
||||
|
||||
// Fired to change the display that controls the dominator tree labels.
|
||||
actions.SET_DOMINATOR_TREE_DISPLAY = "set-dominator-tree-display";
|
||||
actions.SET_LABEL_DISPLAY = "set-label-display";
|
||||
|
||||
// Fired to set a tree map display
|
||||
actions.SET_TREE_MAP_DISPLAY = "set-tree-map-display";
|
||||
|
||||
// Fired when changing between census or dominators view.
|
||||
actions.CHANGE_VIEW = "change-view";
|
||||
actions.POP_VIEW = "pop-view";
|
||||
|
||||
// Fired when there is an error processing a snapshot or taking a census.
|
||||
actions.SNAPSHOT_ERROR = "snapshot-error";
|
||||
@@ -100,6 +101,11 @@ actions.FOCUS_CENSUS_NODE = "focus-census-node";
|
||||
actions.FOCUS_DIFFING_CENSUS_NODE = "focus-diffing-census-node";
|
||||
actions.FOCUS_DOMINATOR_TREE_NODE = "focus-dominator-tree-node";
|
||||
|
||||
actions.FOCUS_INDIVIDUAL = "focus-individual";
|
||||
actions.FETCH_INDIVIDUALS_START = "fetch-individuals-start";
|
||||
actions.FETCH_INDIVIDUALS_END = "fetch-individuals-end";
|
||||
actions.INDIVIDUALS_ERROR = "individuals-error";
|
||||
|
||||
actions.COMPUTE_DOMINATOR_TREE_START = "compute-dominator-tree-start";
|
||||
actions.COMPUTE_DOMINATOR_TREE_END = "compute-dominator-tree-end";
|
||||
actions.FETCH_DOMINATOR_TREE_START = "fetch-dominator-tree-start";
|
||||
@@ -179,7 +185,7 @@ const DOMINATOR_TREE_LABEL_COARSE_TYPE = Object.freeze({
|
||||
other: INTERNAL_TYPE,
|
||||
});
|
||||
|
||||
exports.dominatorTreeDisplays = Object.freeze({
|
||||
exports.labelDisplays = Object.freeze({
|
||||
coarseType: Object.freeze({
|
||||
displayName: "Type",
|
||||
get tooltip() {
|
||||
@@ -225,6 +231,7 @@ viewState.CENSUS = "view-state-census";
|
||||
viewState.DIFFING = "view-state-diffing";
|
||||
viewState.DOMINATOR_TREE = "view-state-dominator-tree";
|
||||
viewState.TREE_MAP = "view-state-tree-map";
|
||||
viewState.INDIVIDUALS = "view-state-individuals";
|
||||
|
||||
/*** Snapshot States **********************************************************/
|
||||
|
||||
@@ -318,3 +325,18 @@ dominatorTreeState.FETCHING = "dominator-tree-state-fetching";
|
||||
dominatorTreeState.LOADED = "dominator-tree-state-loaded";
|
||||
dominatorTreeState.INCREMENTAL_FETCHING = "dominator-tree-state-incremental-fetching";
|
||||
dominatorTreeState.ERROR = "dominator-tree-state-error";
|
||||
|
||||
/*** States for Individuals Model *********************************************/
|
||||
|
||||
/*
|
||||
* Various states the individuals model can be in.
|
||||
*
|
||||
* COMPUTING_DOMINATOR_TREE -> FETCHING -> FETCHED
|
||||
*
|
||||
* Any state may lead to the ERROR state, from which it can never leave.
|
||||
*/
|
||||
const individualsState = exports.individualsState = Object.create(null);
|
||||
individualsState.COMPUTING_DOMINATOR_TREE = "individuals-state-computing-dominator-tree";
|
||||
individualsState.FETCHING = "individuals-state-fetching";
|
||||
individualsState.FETCHED = "individuals-state-fetched";
|
||||
individualsState.ERROR = "individuals-state-error";
|
||||
|
||||
@@ -10,7 +10,8 @@ const {
|
||||
snapshotState: states,
|
||||
diffingState,
|
||||
dominatorTreeState,
|
||||
viewState
|
||||
viewState,
|
||||
individualsState,
|
||||
} = require("./constants");
|
||||
|
||||
/**
|
||||
@@ -65,7 +66,7 @@ const censusDisplayModel = exports.censusDisplay = PropTypes.shape({
|
||||
*
|
||||
* @see `js/src/doc/Debugger/Debugger.Memory.md`
|
||||
*/
|
||||
const dominatorTreeDisplayModel = exports.dominatorTreeDisplay = PropTypes.shape({
|
||||
const labelDisplayModel = exports.labelDisplay = PropTypes.shape({
|
||||
displayName: PropTypes.string.isRequired,
|
||||
tooltip: PropTypes.string.isRequired,
|
||||
breakdown: PropTypes.shape({
|
||||
@@ -190,7 +191,7 @@ let dominatorTreeModel = exports.dominatorTreeModel = PropTypes.shape({
|
||||
|
||||
// The display used to generate descriptive labels of nodes in this dominator
|
||||
// tree.
|
||||
display: dominatorTreeDisplayModel,
|
||||
display: labelDisplayModel,
|
||||
|
||||
// The number of active requests to incrementally fetch subtrees. This should
|
||||
// only be non-zero when the state is INCREMENTAL_FETCHING.
|
||||
@@ -346,6 +347,102 @@ let diffingModel = exports.diffingModel = PropTypes.shape({
|
||||
}),
|
||||
});
|
||||
|
||||
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),
|
||||
@@ -361,7 +458,7 @@ let appModel = exports.app = {
|
||||
|
||||
// The display data describing how we want the dominator tree labels to be
|
||||
// computed.
|
||||
dominatorTreeDisplay: dominatorTreeDisplayModel.isRequired,
|
||||
labelDisplay: labelDisplayModel.isRequired,
|
||||
|
||||
// The display data describing how we want the dominator tree labels to be
|
||||
// computed.
|
||||
@@ -376,27 +473,47 @@ let appModel = exports.app = {
|
||||
// If present, the current diffing state.
|
||||
diffing: diffingModel,
|
||||
|
||||
// If present, the current individuals state.
|
||||
individuals: individualsModel,
|
||||
|
||||
// The current type of view.
|
||||
view: catchAndIgnore(function (app) {
|
||||
switch (app.view) {
|
||||
case viewState.CENSUS:
|
||||
assert(!app.diffing, "Should not be diffing");
|
||||
break;
|
||||
view: function(app) {
|
||||
viewModel.isRequired(app, "view");
|
||||
|
||||
case viewState.DIFFING:
|
||||
assert(app.diffing, "Should be diffing");
|
||||
break;
|
||||
catchAndIgnore(function(app) {
|
||||
switch (app.view.state) {
|
||||
case viewState.DIFFING:
|
||||
assert(app.diffing, "Should be diffing");
|
||||
break;
|
||||
|
||||
case viewState.DOMINATOR_TREE:
|
||||
assert(!app.diffing, "Should not 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;
|
||||
|
||||
case viewState.TREE_MAP:
|
||||
assert(!app.diffing, "Should not be diffing");
|
||||
break;
|
||||
default:
|
||||
assert(false, `Unexpected type of view: ${view.state}`);
|
||||
}
|
||||
})(app);
|
||||
|
||||
default:
|
||||
assert(false, `Unexpected type of view: ${app.view}`);
|
||||
}
|
||||
}),
|
||||
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);
|
||||
},
|
||||
};
|
||||
|
||||
@@ -6,7 +6,8 @@
|
||||
exports.allocations = require("./reducers/allocations");
|
||||
exports.censusDisplay = require("./reducers/census-display");
|
||||
exports.diffing = require("./reducers/diffing");
|
||||
exports.dominatorTreeDisplay = require("./reducers/dominator-tree-display");
|
||||
exports.individuals = require("./reducers/individuals");
|
||||
exports.labelDisplay = require("./reducers/label-display");
|
||||
exports.treeMapDisplay = require("./reducers/tree-map-display");
|
||||
exports.errors = require("./reducers/errors");
|
||||
exports.filter = require("./reducers/filter");
|
||||
|
||||
@@ -10,8 +10,17 @@ const { snapshotIsDiffable } = require("../utils");
|
||||
|
||||
const handlers = Object.create(null);
|
||||
|
||||
handlers[actions.CHANGE_VIEW] = function (diffing, { view }) {
|
||||
if (view === viewState.DIFFING) {
|
||||
handlers[actions.POP_VIEW] = function (diffing, { previousView }) {
|
||||
if (previousView.state === viewState.DIFFING) {
|
||||
assert(previousView.diffing, "Should have previousView.diffing");
|
||||
return previousView.diffing;
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
handlers[actions.CHANGE_VIEW] = function (diffing, { newViewState }) {
|
||||
if (newViewState === viewState.DIFFING) {
|
||||
assert(!diffing, "Should not switch to diffing view when already diffing");
|
||||
return Object.freeze({
|
||||
firstSnapshotId: null,
|
||||
|
||||
73
devtools/client/memory/reducers/individuals.js
Normal file
73
devtools/client/memory/reducers/individuals.js
Normal file
@@ -0,0 +1,73 @@
|
||||
/* 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/. */
|
||||
"use strict";
|
||||
|
||||
const { assert, immutableUpdate } = require("devtools/shared/DevToolsUtils");
|
||||
const { actions, individualsState, viewState } = require("../constants");
|
||||
|
||||
const handlers = Object.create(null);
|
||||
|
||||
handlers[actions.POP_VIEW] = function(_state, _action) {
|
||||
return null;
|
||||
};
|
||||
|
||||
handlers[actions.CHANGE_VIEW] = function(individuals, { newViewState }) {
|
||||
if (newViewState === viewState.INDIVIDUALS) {
|
||||
assert(!individuals,
|
||||
"Should not switch to individuals view when already in individuals view");
|
||||
return Object.freeze({
|
||||
state: individualsState.COMPUTING_DOMINATOR_TREE,
|
||||
});
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
handlers[actions.FOCUS_INDIVIDUAL] = function(individuals, { node }) {
|
||||
assert(individuals, "Should have individuals");
|
||||
return immutableUpdate(individuals, { focused: node });
|
||||
};
|
||||
|
||||
handlers[actions.FETCH_INDIVIDUALS_START] = function(individuals, action) {
|
||||
assert(individuals, "Should have individuals");
|
||||
return Object.freeze({
|
||||
state: individualsState.FETCHING,
|
||||
focused: individuals.focused,
|
||||
});
|
||||
};
|
||||
|
||||
handlers[actions.FETCH_INDIVIDUALS_END] = function(individuals, action) {
|
||||
assert(individuals, "Should have individuals");
|
||||
assert(!individuals.nodes, "Should not have nodes");
|
||||
assert(individuals.state === individualsState.FETCHING,
|
||||
"Should only end fetching individuals after starting.");
|
||||
|
||||
const focused = individuals.focused
|
||||
? action.nodes.find(n => n.nodeId === individuals.focused.nodeId)
|
||||
: null;
|
||||
|
||||
return Object.freeze({
|
||||
state: individualsState.FETCHED,
|
||||
nodes: action.nodes,
|
||||
id: action.id,
|
||||
censusBreakdown: action.censusBreakdown,
|
||||
indices: action.indices,
|
||||
labelDisplay: action.labelDisplay,
|
||||
focused,
|
||||
dominatorTree: action.dominatorTree,
|
||||
});
|
||||
};
|
||||
|
||||
handlers[actions.INDIVIDUALS_ERROR] = function(_, { error }) {
|
||||
return Object.freeze({
|
||||
error,
|
||||
nodes: null,
|
||||
state: individualsState.ERROR,
|
||||
});
|
||||
};
|
||||
|
||||
module.exports = function (individuals = null, action) {
|
||||
const handler = handlers[action.type];
|
||||
return handler ? handler(individuals, action) : individuals;
|
||||
};
|
||||
@@ -4,16 +4,16 @@
|
||||
|
||||
"use strict";
|
||||
|
||||
const { actions, dominatorTreeDisplays } = require("../constants");
|
||||
const DEFAULT_DOMINATOR_TREE_DISPLAY = dominatorTreeDisplays.coarseType;
|
||||
const { actions, labelDisplays } = require("../constants");
|
||||
const DEFAULT_LABEL_DISPLAY = labelDisplays.coarseType;
|
||||
|
||||
const handlers = Object.create(null);
|
||||
|
||||
handlers[actions.SET_DOMINATOR_TREE_DISPLAY] = function (_, { display }) {
|
||||
handlers[actions.SET_LABEL_DISPLAY] = function (_, { display }) {
|
||||
return display;
|
||||
};
|
||||
|
||||
module.exports = function (state = DEFAULT_DOMINATOR_TREE_DISPLAY, action) {
|
||||
module.exports = function (state = DEFAULT_LABEL_DISPLAY, action) {
|
||||
const handler = handlers[action.type];
|
||||
return handler ? handler(state, action) : state;
|
||||
};
|
||||
@@ -7,9 +7,10 @@ DevToolsModules(
|
||||
'allocations.js',
|
||||
'census-display.js',
|
||||
'diffing.js',
|
||||
'dominator-tree-display.js',
|
||||
'errors.js',
|
||||
'filter.js',
|
||||
'individuals.js',
|
||||
'label-display.js',
|
||||
'sizes.js',
|
||||
'snapshots.js',
|
||||
'tree-map-display.js',
|
||||
|
||||
@@ -210,12 +210,18 @@ handlers[actions.DELETE_SNAPSHOTS_END] = function (snapshots) {
|
||||
return snapshots;
|
||||
};
|
||||
|
||||
handlers[actions.CHANGE_VIEW] = function (snapshots, { view }) {
|
||||
return view === viewState.DIFFING
|
||||
handlers[actions.CHANGE_VIEW] = function (snapshots, { newViewState }) {
|
||||
return newViewState === viewState.DIFFING
|
||||
? snapshots.map(s => immutableUpdate(s, { selected: false }))
|
||||
: snapshots;
|
||||
};
|
||||
|
||||
handlers[actions.POP_VIEW] = function (snapshots, { previousView }) {
|
||||
return snapshots.map(s => immutableUpdate(s, {
|
||||
selected: s.id === previousView.selected
|
||||
}));
|
||||
};
|
||||
|
||||
handlers[actions.COMPUTE_DOMINATOR_TREE_START] = function (snapshots, { id }) {
|
||||
const dominatorTree = Object.freeze({
|
||||
state: dominatorTreeState.COMPUTING,
|
||||
|
||||
@@ -3,15 +3,47 @@
|
||||
* You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
"use strict";
|
||||
|
||||
const { assert } = require("devtools/shared/DevToolsUtils");
|
||||
const { actions, viewState } = require("../constants");
|
||||
|
||||
const handlers = Object.create(null);
|
||||
|
||||
handlers[actions.CHANGE_VIEW] = function (_, { view }) {
|
||||
return view;
|
||||
handlers[actions.POP_VIEW] = function (view, _) {
|
||||
assert(view.previous, "Had better have a previous view state when POP_VIEW");
|
||||
return Object.freeze({
|
||||
state: view.previous.state,
|
||||
previous: null,
|
||||
});
|
||||
};
|
||||
|
||||
module.exports = function (view = viewState.TREE_MAP, action) {
|
||||
handlers[actions.CHANGE_VIEW] = function (view, action) {
|
||||
const { newViewState, oldDiffing, oldSelected } = action;
|
||||
assert(newViewState);
|
||||
|
||||
if (newViewState === viewState.INDIVIDUALS) {
|
||||
assert(oldDiffing || oldSelected);
|
||||
return Object.freeze({
|
||||
state: newViewState,
|
||||
previous: Object.freeze({
|
||||
state: view.state,
|
||||
selected: oldSelected,
|
||||
diffing: oldDiffing,
|
||||
}),
|
||||
});
|
||||
}
|
||||
|
||||
return Object.freeze({
|
||||
state: newViewState,
|
||||
previous: null,
|
||||
});
|
||||
};
|
||||
|
||||
const DEFAULT_VIEW = {
|
||||
state: viewState.TREE_MAP,
|
||||
previous: null,
|
||||
};
|
||||
|
||||
module.exports = function (view = DEFAULT_VIEW, action) {
|
||||
const handler = handlers[action.type];
|
||||
return handler ? handler(view, action) : view;
|
||||
};
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
|
||||
const { telemetry } = require("Services");
|
||||
const { makeInfallible, immutableUpdate } = require("devtools/shared/DevToolsUtils");
|
||||
const { dominatorTreeDisplays, treeMapDisplays, censusDisplays } = require("./constants");
|
||||
const { labelDisplays, treeMapDisplays, censusDisplays } = require("./constants");
|
||||
|
||||
exports.countTakeSnapshot = makeInfallible(function () {
|
||||
const histogram = telemetry.getHistogramById("DEVTOOLS_MEMORY_TAKE_SNAPSHOT_COUNT");
|
||||
@@ -81,9 +81,9 @@ exports.countDominatorTree = makeInfallible(function ({ display }) {
|
||||
histogram.add(1);
|
||||
|
||||
histogram = telemetry.getKeyedHistogramById("DEVTOOLS_MEMORY_BREAKDOWN_DOMINATOR_TREE_COUNT");
|
||||
if (display === dominatorTreeDisplays.coarseType) {
|
||||
if (display === labelDisplays.coarseType) {
|
||||
histogram.add(COARSE_TYPE);
|
||||
} else if (display === dominatorTreeDisplays.allocationStack) {
|
||||
} else if (display === labelDisplays.allocationStack) {
|
||||
histogram.add(ALLOCATION_STACK);
|
||||
} else {
|
||||
histogram.add(CUSTOM);
|
||||
|
||||
@@ -16,6 +16,7 @@ support-files =
|
||||
[browser_memory_dominator_trees_01.js]
|
||||
[browser_memory_dominator_trees_02.js]
|
||||
[browser_memory_filter_01.js]
|
||||
[browser_memory_individuals_01.js]
|
||||
[browser_memory_keyboard.js]
|
||||
[browser_memory_keyboard-snapshot-list.js]
|
||||
[browser_memory_no_allocation_stacks.js]
|
||||
|
||||
@@ -5,7 +5,6 @@
|
||||
|
||||
"use strict";
|
||||
|
||||
const { waitForTime } = require("devtools/shared/DevToolsUtils");
|
||||
const { toggleRecordingAllocationStacks } = require("devtools/client/memory/actions/allocations");
|
||||
const { takeSnapshotAndCensus } = require("devtools/client/memory/actions/snapshot");
|
||||
const censusDisplayActions = require("devtools/client/memory/actions/census-display");
|
||||
|
||||
@@ -5,7 +5,6 @@
|
||||
|
||||
"use strict";
|
||||
|
||||
const { waitForTime } = require("devtools/shared/DevToolsUtils");
|
||||
const {
|
||||
snapshotState,
|
||||
diffingState,
|
||||
|
||||
@@ -0,0 +1,67 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
// Sanity test that we can show census group individuals, and then go back to
|
||||
// the previous view.
|
||||
|
||||
"use strict";
|
||||
|
||||
const {
|
||||
individualsState,
|
||||
viewState,
|
||||
censusState,
|
||||
} = require("devtools/client/memory/constants");
|
||||
const { changeViewAndRefresh, changeView } = require("devtools/client/memory/actions/view");
|
||||
|
||||
const TEST_URL = "http://example.com/browser/devtools/client/memory/test/browser/doc_steady_allocation.html";
|
||||
|
||||
this.test = makeMemoryTest(TEST_URL, function* ({ tab, panel }) {
|
||||
const heapWorker = panel.panelWin.gHeapAnalysesClient;
|
||||
const front = panel.panelWin.gFront;
|
||||
const store = panel.panelWin.gStore;
|
||||
const { getState, dispatch } = store;
|
||||
const doc = panel.panelWin.document;
|
||||
|
||||
dispatch(changeView(viewState.CENSUS));
|
||||
|
||||
// Take a snapshot and wait for the census to finish.
|
||||
|
||||
const takeSnapshotButton = doc.getElementById("take-snapshot");
|
||||
EventUtils.synthesizeMouseAtCenter(takeSnapshotButton, {}, panel.panelWin);
|
||||
|
||||
yield waitUntilState(store, state => {
|
||||
return state.snapshots.length === 1 &&
|
||||
state.snapshots[0].census &&
|
||||
state.snapshots[0].census.state === censusState.SAVED;
|
||||
});
|
||||
|
||||
// Click on the first individuals button found, and wait for the individuals
|
||||
// to be fetched.
|
||||
|
||||
const individualsButton = doc.querySelector(".individuals-button");
|
||||
EventUtils.synthesizeMouseAtCenter(individualsButton, {}, panel.panelWin);
|
||||
|
||||
yield waitUntilState(store, state => {
|
||||
return state.view.state === viewState.INDIVIDUALS &&
|
||||
state.individuals &&
|
||||
state.individuals.state === individualsState.FETCHED;
|
||||
});
|
||||
|
||||
ok(doc.getElementById("shortest-paths"),
|
||||
"Should be showing the shortest paths component");
|
||||
ok(doc.querySelector(".heap-tree-item"),
|
||||
"Should be showing the individuals");
|
||||
|
||||
// Go back to the previous view.
|
||||
|
||||
const popViewButton = doc.getElementById("pop-view-button");
|
||||
ok(popViewButton, "Should be showing the #pop-view-button");
|
||||
EventUtils.synthesizeMouseAtCenter(popViewButton, {}, panel.panelWin);
|
||||
|
||||
yield waitUntilState(store, state => {
|
||||
return state.view.state === viewState.CENSUS;
|
||||
});
|
||||
|
||||
ok(!doc.getElementById("shortest-paths"),
|
||||
"Should not be showing the shortest paths component anymore");
|
||||
});
|
||||
@@ -193,9 +193,10 @@ function waitUntilCensusState (store, getCensus, expected) {
|
||||
(census && census.state === state);
|
||||
});
|
||||
};
|
||||
info(`Waiting for snapshots' censuses to be of state: ${expected}`);
|
||||
info(`Waiting for snapshot censuses to be of state: ${expected}`);
|
||||
return waitUntilState(store, predicate);
|
||||
}
|
||||
|
||||
/**
|
||||
* Mock out the requestAnimationFrame.
|
||||
*
|
||||
|
||||
@@ -31,7 +31,7 @@ var constants = require("devtools/client/memory/constants");
|
||||
var {
|
||||
censusDisplays,
|
||||
diffingState,
|
||||
dominatorTreeDisplays,
|
||||
labelDisplays,
|
||||
dominatorTreeState,
|
||||
snapshotState,
|
||||
viewState,
|
||||
@@ -135,7 +135,7 @@ var TEST_DOMINATOR_TREE = Object.freeze({
|
||||
expanded: new Set(),
|
||||
focused: null,
|
||||
error: null,
|
||||
display: dominatorTreeDisplays.coarseType,
|
||||
display: labelDisplays.coarseType,
|
||||
activeFetchRequestCount: null,
|
||||
state: dominatorTreeState.LOADED,
|
||||
});
|
||||
@@ -174,7 +174,7 @@ var TEST_HEAP_PROPS = Object.freeze({
|
||||
onDominatorTreeFocus: noop,
|
||||
onViewSourceInDebugger: noop,
|
||||
diffing: null,
|
||||
view: viewState.CENSUS,
|
||||
view: { state: viewState.CENSUS, },
|
||||
snapshot: Object.freeze({
|
||||
id: 1337,
|
||||
selected: true,
|
||||
@@ -221,6 +221,7 @@ var TEST_TOOLBAR_PROPS = Object.freeze({
|
||||
censusDisplays.allocationStack,
|
||||
censusDisplays.invertedAllocationStack,
|
||||
],
|
||||
censusDisplay: censusDisplays.coarseType,
|
||||
onTakeSnapshotClick: noop,
|
||||
onImportClick: noop,
|
||||
onCensusDisplayChange: noop,
|
||||
@@ -232,13 +233,14 @@ var TEST_TOOLBAR_PROPS = Object.freeze({
|
||||
setFilterString: noop,
|
||||
diffing: null,
|
||||
onToggleDiffing: noop,
|
||||
view: viewState.CENSUS,
|
||||
view: { state: viewState.CENSUS, },
|
||||
onViewChange: noop,
|
||||
dominatorTreeDisplays: [
|
||||
dominatorTreeDisplays.coarseType,
|
||||
dominatorTreeDisplays.allocationStack,
|
||||
labelDisplays: [
|
||||
labelDisplays.coarseType,
|
||||
labelDisplays.allocationStack,
|
||||
],
|
||||
onDominatorTreeDisplayChange: noop,
|
||||
labelDisplay: labelDisplays.coarseType,
|
||||
onLabelDisplayChange: noop,
|
||||
snapshots: [],
|
||||
});
|
||||
|
||||
|
||||
@@ -13,6 +13,7 @@ Test that rendering a dominator tree error is handled correctly.
|
||||
<div id="container"></div>
|
||||
<pre id="test">
|
||||
<script src="head.js" type="application/javascript;version=1.8"></script>
|
||||
|
||||
<script type="application/javascript;version=1.8">
|
||||
window.onload = Task.async(function* () {
|
||||
try {
|
||||
@@ -23,7 +24,7 @@ Test that rendering a dominator tree error is handled correctly.
|
||||
const container = document.getElementById("container");
|
||||
|
||||
const props = immutableUpdate(TEST_HEAP_PROPS, {
|
||||
view: viewState.DOMINATOR_TREE,
|
||||
view: { state: viewState.DOMINATOR_TREE, },
|
||||
snapshot: immutableUpdate(TEST_HEAP_PROPS.snapshot, {
|
||||
dominatorTree: {
|
||||
error: new Error(errorMessage),
|
||||
|
||||
@@ -25,7 +25,7 @@ Test that the currently selected view is rendered.
|
||||
// Dominator tree view.
|
||||
|
||||
yield renderComponent(Heap(immutableUpdate(TEST_HEAP_PROPS, {
|
||||
view: viewState.DOMINATOR_TREE,
|
||||
view: { state: viewState.DOMINATOR_TREE, },
|
||||
})), container);
|
||||
|
||||
ok(container.querySelector(`[data-state=${dominatorTreeState.LOADED}]`),
|
||||
@@ -34,7 +34,7 @@ Test that the currently selected view is rendered.
|
||||
// Census view.
|
||||
|
||||
yield renderComponent(Heap(immutableUpdate(TEST_HEAP_PROPS, {
|
||||
view: viewState.CENSUS,
|
||||
view: { state: viewState.CENSUS, },
|
||||
})), container);
|
||||
|
||||
ok(container.querySelector(`[data-state=${censusState.SAVED}]`),
|
||||
@@ -43,7 +43,7 @@ Test that the currently selected view is rendered.
|
||||
// Diffing view.
|
||||
|
||||
yield renderComponent(Heap(immutableUpdate(TEST_HEAP_PROPS, {
|
||||
view: viewState.DIFFING,
|
||||
view: { state: viewState.DIFFING, },
|
||||
snapshot: null,
|
||||
diffing: {
|
||||
firstSnapshotId: null,
|
||||
|
||||
@@ -21,7 +21,7 @@ but not in other dominator tree states.
|
||||
|
||||
for (let state of [dominatorTreeState.COMPUTING, dominatorTreeState.FETCHING]) {
|
||||
yield renderComponent(Heap(immutableUpdate(TEST_HEAP_PROPS, {
|
||||
view: viewState.DOMINATOR_TREE,
|
||||
view: { state: viewState.DOMINATOR_TREE, },
|
||||
snapshot: immutableUpdate(TEST_HEAP_PROPS.snapshot, {
|
||||
dominatorTree: immutableUpdate(TEST_HEAP_PROPS.snapshot.dominatorTree, {
|
||||
state,
|
||||
@@ -37,7 +37,7 @@ but not in other dominator tree states.
|
||||
|
||||
for (let state of [dominatorTreeState.LOADED, dominatorTreeState.INCREMENTAL_FETCHING]) {
|
||||
yield renderComponent(Heap(immutableUpdate(TEST_HEAP_PROPS, {
|
||||
view: viewState.DOMINATOR_TREE,
|
||||
view: { state: viewState.DOMINATOR_TREE, },
|
||||
snapshot: immutableUpdate(TEST_HEAP_PROPS.snapshot, {
|
||||
dominatorTree: immutableUpdate(TEST_HEAP_PROPS.snapshot.dominatorTree, {
|
||||
state,
|
||||
@@ -51,7 +51,7 @@ but not in other dominator tree states.
|
||||
}
|
||||
|
||||
yield renderComponent(Heap(immutableUpdate(TEST_HEAP_PROPS, {
|
||||
view: viewState.DOMINATOR_TREE,
|
||||
view: { state: viewState.DOMINATOR_TREE, },
|
||||
snapshot: immutableUpdate(TEST_HEAP_PROPS.snapshot, {
|
||||
dominatorTree: {
|
||||
state: dominatorTreeState.ERROR,
|
||||
|
||||
@@ -92,7 +92,7 @@ Test that we show a message when the census results are empty.
|
||||
// Empty Diffing Report
|
||||
|
||||
yield renderComponent(Heap(immutableUpdate(TEST_HEAP_PROPS, {
|
||||
view: viewState.DIFFING,
|
||||
view: { state: viewState.DIFFING, },
|
||||
diffing: {
|
||||
firstSnapshotId: 1,
|
||||
secondSnapshotId: 2,
|
||||
|
||||
@@ -22,7 +22,7 @@ Test that the Toolbar component shows the view switcher only at the appropriate
|
||||
|
||||
for (let view of [viewState.CENSUS, viewState.DOMINATOR_TREE]) {
|
||||
yield renderComponent(Toolbar(immutableUpdate(TEST_TOOLBAR_PROPS, {
|
||||
view,
|
||||
view: { state: view },
|
||||
})), container);
|
||||
|
||||
ok(container.querySelector("#select-view"),
|
||||
@@ -30,7 +30,7 @@ Test that the Toolbar component shows the view switcher only at the appropriate
|
||||
}
|
||||
|
||||
yield renderComponent(Toolbar(immutableUpdate(TEST_TOOLBAR_PROPS, {
|
||||
view: viewState.DIFFING,
|
||||
view: { state: viewState.DIFFING, },
|
||||
})), container);
|
||||
|
||||
ok(!container.querySelector("#select-view"),
|
||||
|
||||
@@ -80,6 +80,23 @@ function waitUntilSnapshotState (store, expected) {
|
||||
return waitUntilState(store, predicate);
|
||||
}
|
||||
|
||||
function findReportLeafIndex(node, name = null) {
|
||||
if (node.reportLeafIndex && (!name || node.name === name)) {
|
||||
return node.reportLeafIndex;
|
||||
}
|
||||
|
||||
if (node.children) {
|
||||
for (let child of node.children) {
|
||||
const found = findReportLeafIndex(child);
|
||||
if (found) {
|
||||
return found;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
function waitUntilCensusState (store, getCensus, expected) {
|
||||
let predicate = () => {
|
||||
let snapshots = store.getState().snapshots;
|
||||
|
||||
@@ -25,7 +25,7 @@ add_task(function* () {
|
||||
let { subscribe, dispatch, getState } = store;
|
||||
|
||||
dispatch(changeViewAndRefresh(viewState.DOMINATOR_TREE, heapWorker));
|
||||
equal(getState().view, viewState.DOMINATOR_TREE,
|
||||
equal(getState().view.state, viewState.DOMINATOR_TREE,
|
||||
"We should now be in the DOMINATOR_TREE view");
|
||||
|
||||
let i = 0;
|
||||
|
||||
@@ -52,6 +52,9 @@ add_task(function *() {
|
||||
yield waitUntilCensusState(store, snapshot => snapshot.census,
|
||||
[censusState.SAVED]);
|
||||
|
||||
equal(getState().snapshots[0].census.display, censusDisplays.allocationStack,
|
||||
"New snapshot's census uses correct display");
|
||||
|
||||
// Updates when changing display during `SAVING`
|
||||
dispatch(takeSnapshotAndCensus(front, heapWorker));
|
||||
yield waitUntilCensusState(store, snapshot => snapshot.census,
|
||||
@@ -59,6 +62,8 @@ add_task(function *() {
|
||||
dispatch(setCensusDisplayAndRefresh(heapWorker, censusDisplays.coarseType));
|
||||
yield waitUntilCensusState(store, snapshot => snapshot.census,
|
||||
[censusState.SAVED, censusState.SAVED]);
|
||||
equal(getState().snapshots[1].census.display, censusDisplays.coarseType,
|
||||
"Changing display while saving a snapshot results in a census using the new display");
|
||||
|
||||
|
||||
// Updates when changing display during `SAVING_CENSUS`
|
||||
@@ -72,13 +77,13 @@ add_task(function *() {
|
||||
[censusState.SAVED,
|
||||
censusState.SAVED,
|
||||
censusState.SAVED]);
|
||||
|
||||
equal(getState().snapshots[2].census.display, censusDisplays.allocationStack,
|
||||
"Display can be changed while saving census, stores updated display in snapshot");
|
||||
|
||||
// Updates census on currently selected snapshot when changing display
|
||||
ok(getState().snapshots[2].selected, "Third snapshot currently selected");
|
||||
dispatch(setCensusDisplayAndRefresh(heapWorker, censusDisplays.coarseType));
|
||||
yield waitUntilState(store, state => state.snapshots[2].census.state === censusState.SAVING);
|
||||
yield waitUntilState(store, state => state.snapshots[2].census.state === censusState.SAVED);
|
||||
equal(getState().snapshots[2].census.display, censusDisplays.coarseType,
|
||||
"Snapshot census updated when changing displays after already generating one census");
|
||||
@@ -88,12 +93,12 @@ add_task(function *() {
|
||||
equal(getState().snapshots[2].census.display, censusDisplays.allocationStack,
|
||||
"Snapshot census updated when changing displays after already generating one census");
|
||||
|
||||
// Does not update unselected censuses
|
||||
ok(!getState().snapshots[1].selected, "Second snapshot unselected currently");
|
||||
// Does not update unselected censuses.
|
||||
ok(!getState().snapshots[1].selected, "Second snapshot selected currently");
|
||||
equal(getState().snapshots[1].census.display, censusDisplays.coarseType,
|
||||
"Second snapshot using `coarseType` display still and not yet updated to correct display");
|
||||
|
||||
// Updates to current display when switching to stale snapshot
|
||||
// Updates to current display when switching to stale snapshot.
|
||||
dispatch(selectSnapshotAndRefresh(heapWorker, getState().snapshots[1].id));
|
||||
yield waitUntilCensusState(store, snapshot => snapshot.census,
|
||||
[censusState.SAVED,
|
||||
|
||||
@@ -38,6 +38,8 @@ add_task(function *() {
|
||||
// Test error case of wrong state.
|
||||
store.dispatch(actions.takeCensus(heapWorker, snapshot.id));
|
||||
yield waitUntilState(store, () => store.getState().errors.length === 1);
|
||||
|
||||
dumpn("Found error: " + store.getState().errors[0]);
|
||||
ok(/Assertion failure/.test(store.getState().errors[0]),
|
||||
"Error thrown when taking a census of a snapshot that has not been read.");
|
||||
|
||||
|
||||
@@ -28,7 +28,7 @@ add_task(function *() {
|
||||
let { getState, dispatch } = store;
|
||||
|
||||
dispatch(changeView(viewState.DOMINATOR_TREE));
|
||||
equal(getState().view, viewState.DOMINATOR_TREE,
|
||||
equal(getState().view.state, viewState.DOMINATOR_TREE,
|
||||
"We should now be in the DOMINATOR_TREE view");
|
||||
|
||||
dispatch(takeSnapshotAndCensus(front, heapWorker));
|
||||
|
||||
@@ -37,7 +37,7 @@ add_task(function *() {
|
||||
yield waitUntilSnapshotState(store, [intermediateSnapshotState]);
|
||||
|
||||
dispatch(changeView(viewState.DOMINATOR_TREE));
|
||||
equal(getState().view, viewState.DOMINATOR_TREE,
|
||||
equal(getState().view.state, viewState.DOMINATOR_TREE,
|
||||
"We should now be in the DOMINATOR_TREE view");
|
||||
|
||||
// Wait for the dominator tree to start being computed.
|
||||
|
||||
@@ -8,12 +8,12 @@ const {
|
||||
snapshotState: states,
|
||||
dominatorTreeState,
|
||||
viewState,
|
||||
dominatorTreeDisplays,
|
||||
labelDisplays,
|
||||
treeMapState
|
||||
} = require("devtools/client/memory/constants");
|
||||
const {
|
||||
setDominatorTreeDisplayAndRefresh
|
||||
} = require("devtools/client/memory/actions/dominator-tree-display");
|
||||
setLabelDisplayAndRefresh
|
||||
} = require("devtools/client/memory/actions/label-display");
|
||||
const {
|
||||
changeView,
|
||||
} = require("devtools/client/memory/actions/view");
|
||||
@@ -47,19 +47,19 @@ add_task(function *() {
|
||||
state.snapshots[0].dominatorTree &&
|
||||
state.snapshots[0].dominatorTree.state === dominatorTreeState.LOADED);
|
||||
|
||||
ok(getState().dominatorTreeDisplay,
|
||||
ok(getState().labelDisplay,
|
||||
"We have a default display for describing nodes in a dominator tree");
|
||||
equal(getState().dominatorTreeDisplay,
|
||||
dominatorTreeDisplays.coarseType,
|
||||
equal(getState().labelDisplay,
|
||||
labelDisplays.coarseType,
|
||||
"and the default is coarse type");
|
||||
equal(getState().dominatorTreeDisplay,
|
||||
equal(getState().labelDisplay,
|
||||
getState().snapshots[0].dominatorTree.display,
|
||||
"and the newly computed dominator tree has that display");
|
||||
|
||||
// Switch to the allocationStack display.
|
||||
dispatch(setDominatorTreeDisplayAndRefresh(
|
||||
dispatch(setLabelDisplayAndRefresh(
|
||||
heapWorker,
|
||||
dominatorTreeDisplays.allocationStack));
|
||||
labelDisplays.allocationStack));
|
||||
|
||||
yield waitUntilState(store, state =>
|
||||
state.snapshots[0].dominatorTree.state === dominatorTreeState.FETCHING);
|
||||
@@ -70,10 +70,10 @@ add_task(function *() {
|
||||
yield waitUntilState(store, state =>
|
||||
state.snapshots[0].dominatorTree.state === dominatorTreeState.LOADED);
|
||||
equal(getState().snapshots[0].dominatorTree.display,
|
||||
dominatorTreeDisplays.allocationStack,
|
||||
labelDisplays.allocationStack,
|
||||
"The new dominator tree's display is allocationStack");
|
||||
equal(getState().dominatorTreeDisplay,
|
||||
dominatorTreeDisplays.allocationStack,
|
||||
equal(getState().labelDisplay,
|
||||
labelDisplays.allocationStack,
|
||||
"as is our requested dominator tree display");
|
||||
|
||||
heapWorker.destroy();
|
||||
|
||||
@@ -8,12 +8,12 @@ const {
|
||||
snapshotState: states,
|
||||
dominatorTreeState,
|
||||
viewState,
|
||||
dominatorTreeDisplays,
|
||||
labelDisplays,
|
||||
treeMapState,
|
||||
} = require("devtools/client/memory/constants");
|
||||
const {
|
||||
setDominatorTreeDisplayAndRefresh
|
||||
} = require("devtools/client/memory/actions/dominator-tree-display");
|
||||
setLabelDisplayAndRefresh
|
||||
} = require("devtools/client/memory/actions/label-display");
|
||||
const {
|
||||
changeView,
|
||||
} = require("devtools/client/memory/actions/view");
|
||||
@@ -47,30 +47,30 @@ add_task(function *() {
|
||||
state.snapshots[0].dominatorTree &&
|
||||
state.snapshots[0].dominatorTree.state === dominatorTreeState.FETCHING);
|
||||
|
||||
ok(getState().dominatorTreeDisplay,
|
||||
ok(getState().labelDisplay,
|
||||
"We have a default display for describing nodes in a dominator tree");
|
||||
equal(getState().dominatorTreeDisplay,
|
||||
dominatorTreeDisplays.coarseType,
|
||||
equal(getState().labelDisplay,
|
||||
labelDisplays.coarseType,
|
||||
"and the default is coarse type");
|
||||
equal(getState().dominatorTreeDisplay,
|
||||
equal(getState().labelDisplay,
|
||||
getState().snapshots[0].dominatorTree.display,
|
||||
"and the newly computed dominator tree has that display");
|
||||
|
||||
// Switch to the allocationStack display while we are still fetching the
|
||||
// dominator tree.
|
||||
dispatch(setDominatorTreeDisplayAndRefresh(
|
||||
dispatch(setLabelDisplayAndRefresh(
|
||||
heapWorker,
|
||||
dominatorTreeDisplays.allocationStack));
|
||||
labelDisplays.allocationStack));
|
||||
|
||||
// Wait for the dominator tree to finish being fetched.
|
||||
yield waitUntilState(store, state =>
|
||||
state.snapshots[0].dominatorTree.state === dominatorTreeState.LOADED);
|
||||
|
||||
equal(getState().snapshots[0].dominatorTree.display,
|
||||
dominatorTreeDisplays.allocationStack,
|
||||
labelDisplays.allocationStack,
|
||||
"The new dominator tree's display is allocationStack");
|
||||
equal(getState().dominatorTreeDisplay,
|
||||
dominatorTreeDisplays.allocationStack,
|
||||
equal(getState().labelDisplay,
|
||||
labelDisplays.allocationStack,
|
||||
"as is our requested dominator tree display");
|
||||
|
||||
heapWorker.destroy();
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
let {
|
||||
snapshotState: states,
|
||||
dominatorTreeState,
|
||||
dominatorTreeDisplays,
|
||||
labelDisplays,
|
||||
viewState,
|
||||
} = require("devtools/client/memory/constants");
|
||||
let {
|
||||
@@ -18,8 +18,8 @@ const {
|
||||
changeView,
|
||||
} = require("devtools/client/memory/actions/view");
|
||||
const {
|
||||
setDominatorTreeDisplayAndRefresh,
|
||||
} = require("devtools/client/memory/actions/dominator-tree-display");
|
||||
setLabelDisplayAndRefresh,
|
||||
} = require("devtools/client/memory/actions/label-display");
|
||||
|
||||
function run_test() {
|
||||
run_next_test();
|
||||
@@ -49,12 +49,12 @@ add_task(function *() {
|
||||
equal(root, getState().snapshots[0].dominatorTree.focused,
|
||||
"The root should be focused.");
|
||||
|
||||
equal(getState().dominatorTreeDisplay, dominatorTreeDisplays.coarseType,
|
||||
"Using dominatorTreeDisplays.coarseType by default");
|
||||
dispatch(setDominatorTreeDisplayAndRefresh(heapWorker,
|
||||
dominatorTreeDisplays.allocationStack));
|
||||
equal(getState().dominatorTreeDisplay, dominatorTreeDisplays.allocationStack,
|
||||
"Using dominatorTreeDisplays.allocationStack now");
|
||||
equal(getState().labelDisplay, labelDisplays.coarseType,
|
||||
"Using labelDisplays.coarseType by default");
|
||||
dispatch(setLabelDisplayAndRefresh(heapWorker,
|
||||
labelDisplays.allocationStack));
|
||||
equal(getState().labelDisplay, labelDisplays.allocationStack,
|
||||
"Using labelDisplays.allocationStack now");
|
||||
|
||||
yield waitUntilState(store, state =>
|
||||
state.snapshots[0].dominatorTree.state === dominatorTreeState.FETCHING);
|
||||
|
||||
76
devtools/client/memory/test/unit/test_individuals_01.js
Normal file
76
devtools/client/memory/test/unit/test_individuals_01.js
Normal file
@@ -0,0 +1,76 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
"use strict";
|
||||
|
||||
// Basic test for switching to the individuals view.
|
||||
|
||||
const {
|
||||
censusState,
|
||||
viewState,
|
||||
individualsState,
|
||||
} = require("devtools/client/memory/constants");
|
||||
const {
|
||||
fetchIndividuals,
|
||||
takeSnapshotAndCensus,
|
||||
} = require("devtools/client/memory/actions/snapshot");
|
||||
const {
|
||||
changeView,
|
||||
} = require("devtools/client/memory/actions/view");
|
||||
|
||||
function run_test() {
|
||||
run_next_test();
|
||||
}
|
||||
|
||||
const EXPECTED_INDIVIDUAL_STATES = [
|
||||
individualsState.COMPUTING_DOMINATOR_TREE,
|
||||
individualsState.FETCHING,
|
||||
individualsState.FETCHED,
|
||||
];
|
||||
|
||||
add_task(function*() {
|
||||
let front = new StubbedMemoryFront();
|
||||
let heapWorker = new HeapAnalysesClient();
|
||||
yield front.attach();
|
||||
let store = Store();
|
||||
const { getState, dispatch } = store;
|
||||
|
||||
equal(getState().individuals, null,
|
||||
"no individuals state by default");
|
||||
|
||||
dispatch(changeView(viewState.CENSUS));
|
||||
dispatch(takeSnapshotAndCensus(front, heapWorker));
|
||||
yield waitUntilCensusState(store, s => s.census, [censusState.SAVED]);
|
||||
|
||||
const root = getState().snapshots[0].census.report;
|
||||
ok(root, "Should have a census");
|
||||
|
||||
const reportLeafIndex = findReportLeafIndex(root);
|
||||
ok(reportLeafIndex, "Should get a reportLeafIndex");
|
||||
|
||||
const snapshotId = getState().snapshots[0].id;
|
||||
ok(snapshotId, "Should have a snapshot id");
|
||||
|
||||
const breakdown = getState().snapshots[0].census.display.breakdown;
|
||||
ok(breakdown, "Should have a breakdown");
|
||||
|
||||
dispatch(fetchIndividuals(heapWorker, snapshotId, breakdown,
|
||||
reportLeafIndex));
|
||||
|
||||
// Wait for each expected state.
|
||||
for (let state of EXPECTED_INDIVIDUAL_STATES) {
|
||||
yield waitUntilState(store, s => {
|
||||
return s.view.state === viewState.INDIVIDUALS &&
|
||||
s.individuals &&
|
||||
s.individuals.state === state;
|
||||
});
|
||||
ok(true, `Reached state = ${state}`);
|
||||
}
|
||||
|
||||
ok(getState().individuals, "Should have individuals state");
|
||||
ok(getState().individuals.nodes, "Should have individuals nodes");
|
||||
ok(getState().individuals.nodes.length > 0,
|
||||
"Should have a positive number of nodes");
|
||||
|
||||
heapWorker.destroy();
|
||||
yield front.detach();
|
||||
});
|
||||
88
devtools/client/memory/test/unit/test_individuals_02.js
Normal file
88
devtools/client/memory/test/unit/test_individuals_02.js
Normal file
@@ -0,0 +1,88 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
"use strict";
|
||||
|
||||
// Test switching to the individuals view when we are in the middle of computing
|
||||
// a dominator tree.
|
||||
|
||||
const {
|
||||
censusState,
|
||||
dominatorTreeState,
|
||||
viewState,
|
||||
individualsState,
|
||||
} = require("devtools/client/memory/constants");
|
||||
const {
|
||||
fetchIndividuals,
|
||||
takeSnapshotAndCensus,
|
||||
computeDominatorTree,
|
||||
} = require("devtools/client/memory/actions/snapshot");
|
||||
const {
|
||||
changeView,
|
||||
} = require("devtools/client/memory/actions/view");
|
||||
|
||||
function run_test() {
|
||||
run_next_test();
|
||||
}
|
||||
|
||||
const EXPECTED_INDIVIDUAL_STATES = [
|
||||
individualsState.COMPUTING_DOMINATOR_TREE,
|
||||
individualsState.FETCHING,
|
||||
individualsState.FETCHED,
|
||||
];
|
||||
|
||||
add_task(function*() {
|
||||
let front = new StubbedMemoryFront();
|
||||
let heapWorker = new HeapAnalysesClient();
|
||||
yield front.attach();
|
||||
let store = Store();
|
||||
const { getState, dispatch } = store;
|
||||
|
||||
equal(getState().individuals, null,
|
||||
"no individuals state by default");
|
||||
|
||||
dispatch(changeView(viewState.CENSUS));
|
||||
dispatch(takeSnapshotAndCensus(front, heapWorker));
|
||||
yield waitUntilCensusState(store, s => s.census, [censusState.SAVED]);
|
||||
|
||||
const root = getState().snapshots[0].census.report;
|
||||
ok(root, "Should have a census");
|
||||
|
||||
const reportLeafIndex = findReportLeafIndex(root);
|
||||
ok(reportLeafIndex, "Should get a reportLeafIndex");
|
||||
|
||||
const snapshotId = getState().snapshots[0].id;
|
||||
ok(snapshotId, "Should have a snapshot id");
|
||||
|
||||
const breakdown = getState().snapshots[0].census.display.breakdown;
|
||||
ok(breakdown, "Should have a breakdown");
|
||||
|
||||
// Start computing a dominator tree.
|
||||
|
||||
dispatch(computeDominatorTree(heapWorker, snapshotId));
|
||||
equal(getState().snapshots[0].dominatorTree.state,
|
||||
dominatorTreeState.COMPUTING,
|
||||
"Should be computing dominator tree");
|
||||
|
||||
// Fetch individuals in the middle of computing the dominator tree.
|
||||
|
||||
dispatch(fetchIndividuals(heapWorker, snapshotId, breakdown,
|
||||
reportLeafIndex));
|
||||
|
||||
// Wait for each expected state.
|
||||
for (let state of EXPECTED_INDIVIDUAL_STATES) {
|
||||
yield waitUntilState(store, s => {
|
||||
return s.view.state === viewState.INDIVIDUALS &&
|
||||
s.individuals &&
|
||||
s.individuals.state === state;
|
||||
});
|
||||
ok(true, `Reached state = ${state}`);
|
||||
}
|
||||
|
||||
ok(getState().individuals, "Should have individuals state");
|
||||
ok(getState().individuals.nodes, "Should have individuals nodes");
|
||||
ok(getState().individuals.nodes.length > 0,
|
||||
"Should have a positive number of nodes");
|
||||
|
||||
heapWorker.destroy();
|
||||
yield front.detach();
|
||||
});
|
||||
106
devtools/client/memory/test/unit/test_individuals_03.js
Normal file
106
devtools/client/memory/test/unit/test_individuals_03.js
Normal file
@@ -0,0 +1,106 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
"use strict";
|
||||
|
||||
// Test switching to the individuals view when we are in the diffing view.
|
||||
|
||||
const {
|
||||
censusState,
|
||||
diffingState,
|
||||
viewState,
|
||||
individualsState,
|
||||
} = require("devtools/client/memory/constants");
|
||||
const {
|
||||
fetchIndividuals,
|
||||
takeSnapshotAndCensus,
|
||||
} = require("devtools/client/memory/actions/snapshot");
|
||||
const {
|
||||
changeView,
|
||||
popViewAndRefresh,
|
||||
} = require("devtools/client/memory/actions/view");
|
||||
const {
|
||||
selectSnapshotForDiffingAndRefresh,
|
||||
} = require("devtools/client/memory/actions/diffing");
|
||||
|
||||
function run_test() {
|
||||
run_next_test();
|
||||
}
|
||||
|
||||
const EXPECTED_INDIVIDUAL_STATES = [
|
||||
individualsState.COMPUTING_DOMINATOR_TREE,
|
||||
individualsState.FETCHING,
|
||||
individualsState.FETCHED,
|
||||
];
|
||||
|
||||
add_task(function*() {
|
||||
let front = new StubbedMemoryFront();
|
||||
let heapWorker = new HeapAnalysesClient();
|
||||
yield front.attach();
|
||||
let store = Store();
|
||||
const { getState, dispatch } = store;
|
||||
|
||||
dispatch(changeView(viewState.CENSUS));
|
||||
|
||||
// Take two snapshots and diff them from each other.
|
||||
|
||||
dispatch(takeSnapshotAndCensus(front, heapWorker));
|
||||
dispatch(takeSnapshotAndCensus(front, heapWorker));
|
||||
yield waitUntilCensusState(store, s => s.census, [censusState.SAVED,
|
||||
censusState.SAVED]);
|
||||
|
||||
dispatch(changeView(viewState.DIFFING));
|
||||
dispatch(selectSnapshotForDiffingAndRefresh(heapWorker, getState().snapshots[0]));
|
||||
dispatch(selectSnapshotForDiffingAndRefresh(heapWorker, getState().snapshots[1]));
|
||||
|
||||
yield waitUntilState(store, state => {
|
||||
return state.diffing &&
|
||||
state.diffing.state === diffingState.TOOK_DIFF;
|
||||
});
|
||||
ok(getState().diffing.census);
|
||||
|
||||
// Fetch individuals.
|
||||
|
||||
const root = getState().diffing.census.report;
|
||||
ok(root, "Should have a census");
|
||||
|
||||
const reportLeafIndex = findReportLeafIndex(root);
|
||||
ok(reportLeafIndex, "Should get a reportLeafIndex");
|
||||
|
||||
const snapshotId = getState().diffing.secondSnapshotId;
|
||||
ok(snapshotId, "Should have a snapshot id");
|
||||
|
||||
const breakdown = getState().censusDisplay.breakdown;
|
||||
ok(breakdown, "Should have a breakdown");
|
||||
|
||||
dispatch(fetchIndividuals(heapWorker, snapshotId, breakdown,
|
||||
reportLeafIndex));
|
||||
|
||||
for (let state of EXPECTED_INDIVIDUAL_STATES) {
|
||||
yield waitUntilState(store, s => {
|
||||
return s.view.state === viewState.INDIVIDUALS &&
|
||||
s.individuals &&
|
||||
s.individuals.state === state;
|
||||
});
|
||||
ok(true, `Reached state = ${state}`);
|
||||
}
|
||||
|
||||
ok(getState().individuals, "Should have individuals state");
|
||||
ok(getState().individuals.nodes, "Should have individuals nodes");
|
||||
ok(getState().individuals.nodes.length > 0,
|
||||
"Should have a positive number of nodes");
|
||||
|
||||
// Pop the view back to the diffing.
|
||||
|
||||
dispatch(popViewAndRefresh(heapWorker));
|
||||
|
||||
yield waitUntilState(store, state => {
|
||||
return state.diffing &&
|
||||
state.diffing.state === diffingState.TOOK_DIFF;
|
||||
});
|
||||
|
||||
ok(getState().diffing.census.report,
|
||||
"We have our census diff again after popping back to the last view");
|
||||
|
||||
heapWorker.destroy();
|
||||
yield front.detach();
|
||||
});
|
||||
89
devtools/client/memory/test/unit/test_individuals_04.js
Normal file
89
devtools/client/memory/test/unit/test_individuals_04.js
Normal file
@@ -0,0 +1,89 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
"use strict";
|
||||
|
||||
// Test showing individual Array objects.
|
||||
|
||||
const {
|
||||
censusState,
|
||||
viewState,
|
||||
individualsState,
|
||||
} = require("devtools/client/memory/constants");
|
||||
const {
|
||||
fetchIndividuals,
|
||||
takeSnapshotAndCensus,
|
||||
} = require("devtools/client/memory/actions/snapshot");
|
||||
const {
|
||||
changeView,
|
||||
} = require("devtools/client/memory/actions/view");
|
||||
const {
|
||||
setFilterString,
|
||||
} = require("devtools/client/memory/actions/filter");
|
||||
|
||||
function run_test() {
|
||||
run_next_test();
|
||||
}
|
||||
|
||||
const EXPECTED_INDIVIDUAL_STATES = [
|
||||
individualsState.COMPUTING_DOMINATOR_TREE,
|
||||
individualsState.FETCHING,
|
||||
individualsState.FETCHED,
|
||||
];
|
||||
|
||||
add_task(function*() {
|
||||
let front = new StubbedMemoryFront();
|
||||
let heapWorker = new HeapAnalysesClient();
|
||||
yield front.attach();
|
||||
let store = Store();
|
||||
const { getState, dispatch } = store;
|
||||
|
||||
dispatch(changeView(viewState.CENSUS));
|
||||
dispatch(setFilterString("Array"));
|
||||
|
||||
// Take a snapshot and wait for the census to finish.
|
||||
|
||||
dispatch(takeSnapshotAndCensus(front, heapWorker));
|
||||
yield waitUntilCensusState(store, s => s.census, [censusState.SAVED]);
|
||||
|
||||
// Fetch individuals.
|
||||
|
||||
const root = getState().snapshots[0].census.report;
|
||||
ok(root, "Should have a census");
|
||||
|
||||
const reportLeafIndex = findReportLeafIndex(root, "Array");
|
||||
ok(reportLeafIndex, "Should get a reportLeafIndex for Array");
|
||||
|
||||
const snapshotId = getState().snapshots[0].id;
|
||||
ok(snapshotId, "Should have a snapshot id");
|
||||
|
||||
const breakdown = getState().censusDisplay.breakdown;
|
||||
ok(breakdown, "Should have a breakdown");
|
||||
|
||||
dispatch(fetchIndividuals(heapWorker, snapshotId, breakdown,
|
||||
reportLeafIndex));
|
||||
|
||||
for (let state of EXPECTED_INDIVIDUAL_STATES) {
|
||||
yield waitUntilState(store, s => {
|
||||
return s.view.state === viewState.INDIVIDUALS &&
|
||||
s.individuals &&
|
||||
s.individuals.state === state;
|
||||
});
|
||||
ok(true, `Reached state = ${state}`);
|
||||
}
|
||||
|
||||
ok(getState().individuals, "Should have individuals state");
|
||||
ok(getState().individuals.nodes, "Should have individuals nodes");
|
||||
ok(getState().individuals.nodes.length > 0,
|
||||
"Should have a positive number of nodes");
|
||||
|
||||
// Assert that all the individuals are `Array`s.
|
||||
|
||||
for (let node of getState().individuals.nodes) {
|
||||
dumpn("Checking node: " + node.label.join(" > "));
|
||||
ok(node.label.find(part => part === "Array"),
|
||||
"The node should be an Array node");
|
||||
}
|
||||
|
||||
heapWorker.destroy();
|
||||
yield front.detach();
|
||||
});
|
||||
82
devtools/client/memory/test/unit/test_individuals_05.js
Normal file
82
devtools/client/memory/test/unit/test_individuals_05.js
Normal file
@@ -0,0 +1,82 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
"use strict";
|
||||
|
||||
// Test showing individual objects that do not have allocation stacks.
|
||||
|
||||
const {
|
||||
censusState,
|
||||
viewState,
|
||||
individualsState,
|
||||
censusDisplays,
|
||||
} = require("devtools/client/memory/constants");
|
||||
const {
|
||||
fetchIndividuals,
|
||||
takeSnapshotAndCensus,
|
||||
} = require("devtools/client/memory/actions/snapshot");
|
||||
const {
|
||||
changeView,
|
||||
} = require("devtools/client/memory/actions/view");
|
||||
const {
|
||||
setCensusDisplay,
|
||||
} = require("devtools/client/memory/actions/census-display");
|
||||
|
||||
function run_test() {
|
||||
run_next_test();
|
||||
}
|
||||
|
||||
const EXPECTED_INDIVIDUAL_STATES = [
|
||||
individualsState.COMPUTING_DOMINATOR_TREE,
|
||||
individualsState.FETCHING,
|
||||
individualsState.FETCHED,
|
||||
];
|
||||
|
||||
add_task(function*() {
|
||||
let front = new StubbedMemoryFront();
|
||||
let heapWorker = new HeapAnalysesClient();
|
||||
yield front.attach();
|
||||
let store = Store();
|
||||
const { getState, dispatch } = store;
|
||||
|
||||
dispatch(changeView(viewState.CENSUS));
|
||||
dispatch(setCensusDisplay(censusDisplays.invertedAllocationStack));
|
||||
|
||||
// Take a snapshot and wait for the census to finish.
|
||||
|
||||
dispatch(takeSnapshotAndCensus(front, heapWorker));
|
||||
yield waitUntilCensusState(store, s => s.census, [censusState.SAVED]);
|
||||
|
||||
// Fetch individuals.
|
||||
|
||||
const root = getState().snapshots[0].census.report;
|
||||
ok(root, "Should have a census");
|
||||
|
||||
const reportLeafIndex = findReportLeafIndex(root, "noStack");
|
||||
ok(reportLeafIndex, "Should get a reportLeafIndex for noStack");
|
||||
|
||||
const snapshotId = getState().snapshots[0].id;
|
||||
ok(snapshotId, "Should have a snapshot id");
|
||||
|
||||
const breakdown = getState().censusDisplay.breakdown;
|
||||
ok(breakdown, "Should have a breakdown");
|
||||
|
||||
dispatch(fetchIndividuals(heapWorker, snapshotId, breakdown,
|
||||
reportLeafIndex));
|
||||
|
||||
for (let state of EXPECTED_INDIVIDUAL_STATES) {
|
||||
yield waitUntilState(store, s => {
|
||||
return s.view.state === viewState.INDIVIDUALS &&
|
||||
s.individuals &&
|
||||
s.individuals.state === state;
|
||||
});
|
||||
ok(true, `Reached state = ${state}`);
|
||||
}
|
||||
|
||||
ok(getState().individuals, "Should have individuals state");
|
||||
ok(getState().individuals.nodes, "Should have individuals nodes");
|
||||
ok(getState().individuals.nodes.length > 0,
|
||||
"Should have a positive number of nodes");
|
||||
|
||||
heapWorker.destroy();
|
||||
yield front.detach();
|
||||
});
|
||||
84
devtools/client/memory/test/unit/test_individuals_06.js
Normal file
84
devtools/client/memory/test/unit/test_individuals_06.js
Normal file
@@ -0,0 +1,84 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
"use strict";
|
||||
|
||||
// Test that clearing the current individuals' snapshot leaves the individuals
|
||||
// view.
|
||||
|
||||
const {
|
||||
censusState,
|
||||
viewState,
|
||||
individualsState,
|
||||
} = require("devtools/client/memory/constants");
|
||||
const {
|
||||
fetchIndividuals,
|
||||
takeSnapshotAndCensus,
|
||||
clearSnapshots,
|
||||
} = require("devtools/client/memory/actions/snapshot");
|
||||
const {
|
||||
changeView,
|
||||
} = require("devtools/client/memory/actions/view");
|
||||
|
||||
function run_test() {
|
||||
run_next_test();
|
||||
}
|
||||
|
||||
const EXPECTED_INDIVIDUAL_STATES = [
|
||||
individualsState.COMPUTING_DOMINATOR_TREE,
|
||||
individualsState.FETCHING,
|
||||
individualsState.FETCHED,
|
||||
];
|
||||
|
||||
add_task(function*() {
|
||||
let front = new StubbedMemoryFront();
|
||||
let heapWorker = new HeapAnalysesClient();
|
||||
yield front.attach();
|
||||
let store = Store();
|
||||
const { getState, dispatch } = store;
|
||||
|
||||
dispatch(changeView(viewState.CENSUS));
|
||||
|
||||
// Take a snapshot and wait for the census to finish.
|
||||
|
||||
dispatch(takeSnapshotAndCensus(front, heapWorker));
|
||||
yield waitUntilCensusState(store, s => s.census, [censusState.SAVED]);
|
||||
|
||||
// Fetch individuals.
|
||||
|
||||
const root = getState().snapshots[0].census.report;
|
||||
ok(root, "Should have a census");
|
||||
|
||||
const reportLeafIndex = findReportLeafIndex(root);
|
||||
ok(reportLeafIndex, "Should get a reportLeafIndex");
|
||||
|
||||
const snapshotId = getState().snapshots[0].id;
|
||||
ok(snapshotId, "Should have a snapshot id");
|
||||
|
||||
const breakdown = getState().censusDisplay.breakdown;
|
||||
ok(breakdown, "Should have a breakdown");
|
||||
|
||||
dispatch(fetchIndividuals(heapWorker, snapshotId, breakdown,
|
||||
reportLeafIndex));
|
||||
|
||||
for (let state of EXPECTED_INDIVIDUAL_STATES) {
|
||||
yield waitUntilState(store, s => {
|
||||
return s.view.state === viewState.INDIVIDUALS &&
|
||||
s.individuals &&
|
||||
s.individuals.state === state;
|
||||
});
|
||||
ok(true, `Reached state = ${state}`);
|
||||
}
|
||||
|
||||
ok(getState().individuals, "Should have individuals state");
|
||||
ok(getState().individuals.nodes, "Should have individuals nodes");
|
||||
ok(getState().individuals.nodes.length > 0,
|
||||
"Should have a positive number of nodes");
|
||||
|
||||
dispatch(clearSnapshots(heapWorker));
|
||||
|
||||
equal(getState().view.state, viewState.CENSUS,
|
||||
"Went back to census view");
|
||||
|
||||
heapWorker.destroy();
|
||||
yield front.detach();
|
||||
});
|
||||
81
devtools/client/memory/test/unit/test_pop_view_01.js
Normal file
81
devtools/client/memory/test/unit/test_pop_view_01.js
Normal file
@@ -0,0 +1,81 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
"use strict";
|
||||
|
||||
// Test popping views from each intermediate individuals model state.
|
||||
|
||||
const {
|
||||
censusState,
|
||||
viewState,
|
||||
individualsState,
|
||||
} = require("devtools/client/memory/constants");
|
||||
const {
|
||||
fetchIndividuals,
|
||||
takeSnapshotAndCensus,
|
||||
} = require("devtools/client/memory/actions/snapshot");
|
||||
const {
|
||||
changeView,
|
||||
popViewAndRefresh,
|
||||
} = require("devtools/client/memory/actions/view");
|
||||
|
||||
function run_test() {
|
||||
run_next_test();
|
||||
}
|
||||
|
||||
const TEST_STATES = [
|
||||
individualsState.COMPUTING_DOMINATOR_TREE,
|
||||
individualsState.FETCHING,
|
||||
individualsState.FETCHED,
|
||||
];
|
||||
|
||||
add_task(function*() {
|
||||
let front = new StubbedMemoryFront();
|
||||
let heapWorker = new HeapAnalysesClient();
|
||||
yield front.attach();
|
||||
let store = Store();
|
||||
const { getState, dispatch } = store;
|
||||
|
||||
equal(getState().individuals, null,
|
||||
"no individuals state by default");
|
||||
|
||||
dispatch(changeView(viewState.CENSUS));
|
||||
dispatch(takeSnapshotAndCensus(front, heapWorker));
|
||||
yield waitUntilCensusState(store, s => s.census, [censusState.SAVED]);
|
||||
|
||||
const root = getState().snapshots[0].census.report;
|
||||
ok(root, "Should have a census");
|
||||
|
||||
const reportLeafIndex = findReportLeafIndex(root);
|
||||
ok(reportLeafIndex, "Should get a reportLeafIndex");
|
||||
|
||||
const snapshotId = getState().snapshots[0].id;
|
||||
ok(snapshotId, "Should have a snapshot id");
|
||||
|
||||
const breakdown = getState().snapshots[0].census.display.breakdown;
|
||||
ok(breakdown, "Should have a breakdown");
|
||||
|
||||
for (let state of TEST_STATES) {
|
||||
dumpn(`Testing popping back to the old view from state = ${state}`);
|
||||
|
||||
dispatch(fetchIndividuals(heapWorker, snapshotId, breakdown,
|
||||
reportLeafIndex));
|
||||
|
||||
// Wait for the expected test state.
|
||||
yield waitUntilState(store, s => {
|
||||
return s.view.state === viewState.INDIVIDUALS &&
|
||||
s.individuals &&
|
||||
s.individuals.state === state;
|
||||
});
|
||||
ok(true, `Reached state = ${state}`);
|
||||
|
||||
// Pop back to the CENSUS state.
|
||||
dispatch(popViewAndRefresh(heapWorker));
|
||||
yield waitUntilState(store, s => {
|
||||
return s.view.state === viewState.CENSUS;
|
||||
});
|
||||
ok(!getState().individuals, "Should no longer have individuals");
|
||||
}
|
||||
|
||||
heapWorker.destroy();
|
||||
yield front.detach();
|
||||
});
|
||||
@@ -9,7 +9,7 @@
|
||||
*/
|
||||
|
||||
let utils = require("devtools/client/memory/utils");
|
||||
let { snapshotState: states, censusDisplays } = require("devtools/client/memory/constants");
|
||||
let { snapshotState: states, viewState } = require("devtools/client/memory/constants");
|
||||
let { Preferences } = require("resource://gre/modules/Preferences.jsm");
|
||||
|
||||
function run_test() {
|
||||
@@ -17,8 +17,8 @@ function run_test() {
|
||||
}
|
||||
|
||||
add_task(function *() {
|
||||
let s1 = utils.createSnapshot({});
|
||||
let s2 = utils.createSnapshot({});
|
||||
let s1 = utils.createSnapshot({ view: { state: viewState.CENSUS } });
|
||||
let s2 = utils.createSnapshot({ view: { state: viewState.CENSUS } });
|
||||
equal(s1.state, states.SAVING, "utils.createSnapshot() creates snapshot in saving state");
|
||||
ok(s1.id !== s2.id, "utils.createSnapshot() creates snapshot with unique ids");
|
||||
|
||||
|
||||
@@ -43,6 +43,13 @@ skip-if = toolkit == 'android' || toolkit == 'gonk'
|
||||
[test_dominator_trees_08.js]
|
||||
[test_dominator_trees_09.js]
|
||||
[test_dominator_trees_10.js]
|
||||
[test_individuals_01.js]
|
||||
[test_individuals_02.js]
|
||||
[test_individuals_03.js]
|
||||
[test_individuals_04.js]
|
||||
[test_individuals_05.js]
|
||||
[test_individuals_06.js]
|
||||
[test_pop_view_01.js]
|
||||
[test_tree-map-01.js]
|
||||
[test_tree-map-02.js]
|
||||
[test_utils.js]
|
||||
|
||||
@@ -12,7 +12,7 @@ const { OS } = require("resource://gre/modules/osfile.jsm");
|
||||
const { assert } = require("devtools/shared/DevToolsUtils");
|
||||
const { Preferences } = require("resource://gre/modules/Preferences.jsm");
|
||||
const CUSTOM_CENSUS_DISPLAY_PREF = "devtools.memory.custom-census-displays";
|
||||
const CUSTOM_DOMINATOR_TREE_DISPLAY_PREF = "devtools.memory.custom-dominator-tree-displays";
|
||||
const CUSTOM_LABEL_DISPLAY_PREF = "devtools.memory.custom-label-displays";
|
||||
const CUSTOM_TREE_MAP_DISPLAY_PREF = "devtools.memory.custom-tree-map-displays";
|
||||
const BYTES = 1024;
|
||||
const KILOBYTES = Math.pow(BYTES, 2);
|
||||
@@ -23,9 +23,8 @@ const {
|
||||
diffingState,
|
||||
censusState,
|
||||
treeMapState,
|
||||
censusDisplays,
|
||||
dominatorTreeDisplays,
|
||||
dominatorTreeState
|
||||
dominatorTreeState,
|
||||
individualsState,
|
||||
} = require("./constants");
|
||||
|
||||
/**
|
||||
@@ -77,12 +76,12 @@ exports.getCustomCensusDisplays = function () {
|
||||
|
||||
/**
|
||||
* Returns custom displays defined in
|
||||
* `devtools.memory.custom-dominator-tree-displays` pref.
|
||||
* `devtools.memory.custom-label-displays` pref.
|
||||
*
|
||||
* @return {Object}
|
||||
*/
|
||||
exports.getCustomDominatorTreeDisplays = function () {
|
||||
return getCustomDisplaysHelper(CUSTOM_DOMINATOR_TREE_DISPLAY_PREF);
|
||||
exports.getCustomLabelDisplays = function () {
|
||||
return getCustomDisplaysHelper(CUSTOM_LABEL_DISPLAY_PREF);
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -135,6 +134,7 @@ exports.getStatusText = function (state) {
|
||||
return L10N.getStr("diffing.state.selecting");
|
||||
|
||||
case dominatorTreeState.COMPUTING:
|
||||
case individualsState.COMPUTING_DOMINATOR_TREE:
|
||||
return L10N.getStr("dominatorTree.state.computing");
|
||||
|
||||
case dominatorTreeState.COMPUTED:
|
||||
@@ -147,6 +147,12 @@ exports.getStatusText = function (state) {
|
||||
case dominatorTreeState.ERROR:
|
||||
return L10N.getStr("dominatorTree.state.error");
|
||||
|
||||
case individualsState.ERROR:
|
||||
return L10N.getStr("individuals.state.error");
|
||||
|
||||
case individualsState.FETCHING:
|
||||
return L10N.getStr("individuals.state.fetching");
|
||||
|
||||
// These states do not have any message to show as other content will be
|
||||
// displayed.
|
||||
case dominatorTreeState.LOADED:
|
||||
@@ -154,6 +160,7 @@ exports.getStatusText = function (state) {
|
||||
case states.READ:
|
||||
case censusState.SAVED:
|
||||
case treeMapState.SAVED:
|
||||
case individualsState.FETCHED:
|
||||
return "";
|
||||
|
||||
default:
|
||||
@@ -202,6 +209,7 @@ exports.getStatusTextFull = function (state) {
|
||||
return L10N.getStr("diffing.state.selecting.full");
|
||||
|
||||
case dominatorTreeState.COMPUTING:
|
||||
case individualsState.COMPUTING_DOMINATOR_TREE:
|
||||
return L10N.getStr("dominatorTree.state.computing.full");
|
||||
|
||||
case dominatorTreeState.COMPUTED:
|
||||
@@ -214,6 +222,12 @@ exports.getStatusTextFull = function (state) {
|
||||
case dominatorTreeState.ERROR:
|
||||
return L10N.getStr("dominatorTree.state.error.full");
|
||||
|
||||
case individualsState.ERROR:
|
||||
return L10N.getStr("individuals.state.error.full");
|
||||
|
||||
case individualsState.FETCHING:
|
||||
return L10N.getStr("individuals.state.fetching.full");
|
||||
|
||||
// These states do not have any full message to show as other content will
|
||||
// be displayed.
|
||||
case dominatorTreeState.LOADED:
|
||||
@@ -221,6 +235,7 @@ exports.getStatusTextFull = function (state) {
|
||||
case states.READ:
|
||||
case censusState.SAVED:
|
||||
case treeMapState.SAVED:
|
||||
case individualsState.FETCHED:
|
||||
return "";
|
||||
|
||||
default:
|
||||
@@ -257,6 +272,16 @@ exports.getSnapshot = function getSnapshot (state, id) {
|
||||
return found;
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the ID of the selected snapshot, if one is selected, null otherwise.
|
||||
*
|
||||
* @returns {SnapshotId|null}
|
||||
*/
|
||||
exports.findSelectedSnapshot = function (state) {
|
||||
const found = state.snapshots.find(s => s.selected);
|
||||
return found ? found.id : null;
|
||||
};
|
||||
|
||||
/**
|
||||
* Creates a new snapshot object.
|
||||
*
|
||||
@@ -266,7 +291,7 @@ exports.getSnapshot = function getSnapshot (state, id) {
|
||||
let ID_COUNTER = 0;
|
||||
exports.createSnapshot = function createSnapshot(state) {
|
||||
let dominatorTree = null;
|
||||
if (state.view === dominatorTreeState.DOMINATOR_TREE) {
|
||||
if (state.view.state === dominatorTreeState.DOMINATOR_TREE) {
|
||||
dominatorTree = Object.freeze({
|
||||
dominatorTreeId: null,
|
||||
root: null,
|
||||
|
||||
@@ -105,9 +105,12 @@ pref("devtools.debugger.ui.variables-searchbox-visible", false);
|
||||
pref("devtools.memory.enabled", false);
|
||||
|
||||
pref("devtools.memory.custom-census-displays", "{}");
|
||||
pref("devtools.memory.custom-dominator-tree-displays", "{}");
|
||||
pref("devtools.memory.custom-label-displays", "{}");
|
||||
pref("devtools.memory.custom-tree-map-displays", "{}");
|
||||
|
||||
pref("devtools.memory.max-individuals", 1000);
|
||||
pref("devtools.memory.max-retaining-paths", 10);
|
||||
|
||||
// Enable the Performance tools
|
||||
pref("devtools.performance.enabled", true);
|
||||
|
||||
|
||||
@@ -36,8 +36,8 @@ html, body, #app, #memory-tool {
|
||||
* If --heap-tree-row-height changes, be sure to change HEAP_TREE_ROW_HEIGHT
|
||||
* in `devtools/client/memory/components/heap.js`.
|
||||
*/
|
||||
--heap-tree-row-height: 14px;
|
||||
--heap-tree-header-height: 17px;
|
||||
--heap-tree-row-height: 18px;
|
||||
--heap-tree-header-height: 18px;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -115,7 +115,15 @@ html, body, #app, #memory-tool {
|
||||
background-image: url(chrome://devtools/skin/images/diff.svg);
|
||||
}
|
||||
|
||||
#import-snapshot {
|
||||
#record-allocation-stacks-label,
|
||||
#pop-view-button-label {
|
||||
border-inline-end: 1px solid var(--theme-splitter-color);
|
||||
padding-inline-end: 5px;
|
||||
}
|
||||
|
||||
#import-snapshot,
|
||||
#clear-snapshots {
|
||||
-moz-box-align: center;
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
@@ -399,6 +407,7 @@ html, body, #app, #memory-tool {
|
||||
color: var(--theme-selection-background);
|
||||
}
|
||||
|
||||
.heap-tree-item-individuals,
|
||||
.heap-tree-item-bytes,
|
||||
.heap-tree-item-count,
|
||||
.heap-tree-item-total-bytes,
|
||||
@@ -485,6 +494,20 @@ html, body, #app, #memory-tool {
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
.heap-tree-item-individuals {
|
||||
width: 38px;
|
||||
min-width: 20px;
|
||||
overflow: hidden;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.heap-tree-item-individuals > button {
|
||||
height: 10px;
|
||||
width: 32px;
|
||||
margin: 0 auto;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Tree map
|
||||
*/
|
||||
@@ -531,6 +554,10 @@ html, body, #app, #memory-tool {
|
||||
margin-right: .5em;
|
||||
}
|
||||
|
||||
.heap-tree-item-address {
|
||||
font-family: monospace;
|
||||
}
|
||||
|
||||
.no-allocation-stacks {
|
||||
border-color: var(--theme-splitter-color);
|
||||
border-style: solid;
|
||||
|
||||
@@ -231,6 +231,8 @@ HeapAnalysesClient.prototype.computeDominatorTree = function (snapshotFilePath)
|
||||
* - {Number} maxSiblings
|
||||
* The maximum number of siblings to visit within each traversed node's
|
||||
* children.
|
||||
* - {Number} maxRetainingPaths
|
||||
* The maximum number of retaining paths to find for each node.
|
||||
*
|
||||
* @returns {Promise<DominatorTreeNode>}
|
||||
*/
|
||||
|
||||
Reference in New Issue
Block a user