mirror of
https://repo.dactyloidae.xyz/Dactyloidae/UXP.git
synced 2026-09-07 00:08:39 +09:00
import FIREFOX_52_6_0esr_RELEASE from mozilla-esr52 hg repo
This commit is contained in:
commit
dcd9973243
150858 changed files with 23884658 additions and 0 deletions
20
devtools/client/memory/actions/allocations.js
Normal file
20
devtools/client/memory/actions/allocations.js
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
/* 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 { actions, ALLOCATION_RECORDING_OPTIONS } = require("../constants");
|
||||
|
||||
exports.toggleRecordingAllocationStacks = function (front) {
|
||||
return function* (dispatch, getState) {
|
||||
dispatch({ type: actions.TOGGLE_RECORD_ALLOCATION_STACKS_START });
|
||||
|
||||
if (getState().recordingAllocationStacks) {
|
||||
yield front.stopRecordingAllocations();
|
||||
} else {
|
||||
yield front.startRecordingAllocations(ALLOCATION_RECORDING_OPTIONS);
|
||||
}
|
||||
|
||||
dispatch({ type: actions.TOGGLE_RECORD_ALLOCATION_STACKS_END });
|
||||
};
|
||||
};
|
||||
34
devtools/client/memory/actions/census-display.js
Normal file
34
devtools/client/memory/actions/census-display.js
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
/* 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");
|
||||
const { actions } = require("../constants");
|
||||
const { refresh } = require("./refresh");
|
||||
|
||||
exports.setCensusDisplayAndRefresh = function (heapWorker, display) {
|
||||
return function* (dispatch, getState) {
|
||||
dispatch(setCensusDisplay(display));
|
||||
yield dispatch(refresh(heapWorker));
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Clears out all cached census data in the snapshots and sets new display data
|
||||
* for censuses.
|
||||
*
|
||||
* @param {censusDisplayModel} display
|
||||
*/
|
||||
const setCensusDisplay = exports.setCensusDisplay = function (display) {
|
||||
assert(typeof display === "object"
|
||||
&& display
|
||||
&& display.breakdown
|
||||
&& display.breakdown.by,
|
||||
`Breakdowns must be an object with a \`by\` property, attempted to set: ${uneval(display)}`);
|
||||
|
||||
return {
|
||||
type: actions.SET_CENSUS_DISPLAY,
|
||||
display,
|
||||
};
|
||||
};
|
||||
201
devtools/client/memory/actions/diffing.js
Normal file
201
devtools/client/memory/actions/diffing.js
Normal file
|
|
@ -0,0 +1,201 @@
|
|||
/* 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, reportException } = require("devtools/shared/DevToolsUtils");
|
||||
const { actions, diffingState, viewState } = require("../constants");
|
||||
const telemetry = require("../telemetry");
|
||||
const {
|
||||
getSnapshot,
|
||||
censusIsUpToDate,
|
||||
snapshotIsDiffable,
|
||||
findSelectedSnapshot,
|
||||
} = require("../utils");
|
||||
// This is a circular dependency, so do not destructure the needed properties.
|
||||
const snapshotActions = require("./snapshot");
|
||||
|
||||
/**
|
||||
* Toggle diffing mode on or off.
|
||||
*/
|
||||
const toggleDiffing = exports.toggleDiffing = function () {
|
||||
return function (dispatch, getState) {
|
||||
dispatch({
|
||||
type: actions.CHANGE_VIEW,
|
||||
newViewState: getState().diffing ? viewState.CENSUS : viewState.DIFFING,
|
||||
oldDiffing: getState().diffing,
|
||||
oldSelected: findSelectedSnapshot(getState()),
|
||||
});
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Select the given snapshot for diffing.
|
||||
*
|
||||
* @param {snapshotModel} snapshot
|
||||
*/
|
||||
const selectSnapshotForDiffing = exports.selectSnapshotForDiffing = function (snapshot) {
|
||||
assert(snapshotIsDiffable(snapshot),
|
||||
"To select a snapshot for diffing, it must be diffable");
|
||||
return { type: actions.SELECT_SNAPSHOT_FOR_DIFFING, snapshot };
|
||||
};
|
||||
|
||||
/**
|
||||
* Compute the difference between the first and second snapshots.
|
||||
*
|
||||
* @param {HeapAnalysesClient} heapWorker
|
||||
* @param {snapshotModel} first
|
||||
* @param {snapshotModel} second
|
||||
*/
|
||||
const takeCensusDiff = exports.takeCensusDiff = function (heapWorker, first, second) {
|
||||
return function* (dispatch, getState) {
|
||||
assert(snapshotIsDiffable(first),
|
||||
`First snapshot must be in a diffable state, found ${first.state}`);
|
||||
assert(snapshotIsDiffable(second),
|
||||
`Second snapshot must be in a diffable state, found ${second.state}`);
|
||||
|
||||
let report, parentMap;
|
||||
let display = getState().censusDisplay;
|
||||
let filter = getState().filter;
|
||||
|
||||
if (censusIsUpToDate(filter, display, getState().diffing.census)) {
|
||||
return;
|
||||
}
|
||||
|
||||
do {
|
||||
if (!getState().diffing
|
||||
|| getState().diffing.firstSnapshotId !== first.id
|
||||
|| getState().diffing.secondSnapshotId !== second.id) {
|
||||
// If we stopped diffing or stopped and then started diffing a different
|
||||
// pair of snapshots, then just give up with diffing this pair. In the
|
||||
// latter case, a newly spawned task will handle the diffing for the new
|
||||
// pair.
|
||||
return;
|
||||
}
|
||||
|
||||
display = getState().censusDisplay;
|
||||
filter = getState().filter;
|
||||
|
||||
dispatch({
|
||||
type: actions.TAKE_CENSUS_DIFF_START,
|
||||
first,
|
||||
second,
|
||||
filter,
|
||||
display,
|
||||
});
|
||||
|
||||
let opts = display.inverted
|
||||
? { asInvertedTreeNode: true }
|
||||
: { asTreeNode: true };
|
||||
opts.filter = filter || null;
|
||||
|
||||
try {
|
||||
({ delta: report, parentMap } = yield heapWorker.takeCensusDiff(
|
||||
first.path,
|
||||
second.path,
|
||||
{ breakdown: display.breakdown },
|
||||
opts));
|
||||
} catch (error) {
|
||||
reportException("actions/diffing/takeCensusDiff", error);
|
||||
dispatch({ type: actions.DIFFING_ERROR, error });
|
||||
return;
|
||||
}
|
||||
}
|
||||
while (filter !== getState().filter
|
||||
|| display !== getState().censusDisplay);
|
||||
|
||||
dispatch({
|
||||
type: actions.TAKE_CENSUS_DIFF_END,
|
||||
first,
|
||||
second,
|
||||
report,
|
||||
parentMap,
|
||||
filter,
|
||||
display,
|
||||
});
|
||||
|
||||
telemetry.countDiff({ filter, display });
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Ensure that the current diffing data is up to date with the currently
|
||||
* selected display, filter, etc. If the state is not up-to-date, then a
|
||||
* recompute is triggered.
|
||||
*
|
||||
* @param {HeapAnalysesClient} heapWorker
|
||||
*/
|
||||
const refreshDiffing = exports.refreshDiffing = function (heapWorker) {
|
||||
return function* (dispatch, getState) {
|
||||
if (getState().diffing.secondSnapshotId === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
assert(getState().diffing.firstSnapshotId,
|
||||
"Should have first snapshot id");
|
||||
|
||||
if (getState().diffing.state === diffingState.TAKING_DIFF) {
|
||||
// There is an existing task that will ensure that the diffing data is
|
||||
// up-to-date.
|
||||
return;
|
||||
}
|
||||
|
||||
const { firstSnapshotId, secondSnapshotId } = getState().diffing;
|
||||
|
||||
const first = getSnapshot(getState(), firstSnapshotId);
|
||||
const second = getSnapshot(getState(), secondSnapshotId);
|
||||
dispatch(takeCensusDiff(heapWorker, first, second));
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Select the given snapshot for diffing and refresh the diffing data if
|
||||
* necessary (for example, if two snapshots are now selected for diffing).
|
||||
*
|
||||
* @param {HeapAnalysesClient} heapWorker
|
||||
* @param {snapshotModel} snapshot
|
||||
*/
|
||||
const selectSnapshotForDiffingAndRefresh = exports.selectSnapshotForDiffingAndRefresh = function (heapWorker, snapshot) {
|
||||
return function* (dispatch, getState) {
|
||||
assert(getState().diffing,
|
||||
"If we are selecting for diffing, we must be in diffing mode");
|
||||
dispatch(selectSnapshotForDiffing(snapshot));
|
||||
yield dispatch(refreshDiffing(heapWorker));
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Expand the given node in the diffing's census's delta-report.
|
||||
*
|
||||
* @param {CensusTreeNode} node
|
||||
*/
|
||||
const expandDiffingCensusNode = exports.expandDiffingCensusNode = function (node) {
|
||||
return {
|
||||
type: actions.EXPAND_DIFFING_CENSUS_NODE,
|
||||
node,
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Collapse the given node in the diffing's census's delta-report.
|
||||
*
|
||||
* @param {CensusTreeNode} node
|
||||
*/
|
||||
const collapseDiffingCensusNode = exports.collapseDiffingCensusNode = function (node) {
|
||||
return {
|
||||
type: actions.COLLAPSE_DIFFING_CENSUS_NODE,
|
||||
node,
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Focus the given node in the snapshot's census's report.
|
||||
*
|
||||
* @param {DominatorTreeNode} node
|
||||
*/
|
||||
const focusDiffingCensusNode = exports.focusDiffingCensusNode = function (node) {
|
||||
return {
|
||||
type: actions.FOCUS_DIFFING_CENSUS_NODE,
|
||||
node,
|
||||
};
|
||||
};
|
||||
35
devtools/client/memory/actions/filter.js
Normal file
35
devtools/client/memory/actions/filter.js
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
/* 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 { actions } = require("../constants");
|
||||
const { refresh } = require("./refresh");
|
||||
|
||||
const setFilterString = exports.setFilterString = function (filterString) {
|
||||
return {
|
||||
type: actions.SET_FILTER_STRING,
|
||||
filter: filterString
|
||||
};
|
||||
};
|
||||
|
||||
// The number of milliseconds we should wait before kicking off a new census
|
||||
// when the filter string is updated. This helps us avoid doing any work while
|
||||
// the user is still typing.
|
||||
const FILTER_INPUT_DEBOUNCE_MS = 250;
|
||||
|
||||
// The timer id for the debounced census refresh.
|
||||
let timerId = null;
|
||||
|
||||
exports.setFilterStringAndRefresh = function (filterString, heapWorker) {
|
||||
return function* (dispatch, getState) {
|
||||
dispatch(setFilterString(filterString));
|
||||
|
||||
if (timerId !== null) {
|
||||
clearTimeout(timerId);
|
||||
}
|
||||
|
||||
timerId = setTimeout(() => dispatch(refresh(heapWorker)),
|
||||
FILTER_INPUT_DEBOUNCE_MS);
|
||||
};
|
||||
};
|
||||
97
devtools/client/memory/actions/io.js
Normal file
97
devtools/client/memory/actions/io.js
Normal file
|
|
@ -0,0 +1,97 @@
|
|||
/* 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 { immutableUpdate, reportException, assert } = require("devtools/shared/DevToolsUtils");
|
||||
const { snapshotState: states, actions, viewState } = require("../constants");
|
||||
const { L10N, openFilePicker, createSnapshot } = require("../utils");
|
||||
const telemetry = require("../telemetry");
|
||||
const { OS } = require("resource://gre/modules/osfile.jsm");
|
||||
const {
|
||||
selectSnapshot,
|
||||
computeSnapshotData,
|
||||
readSnapshot,
|
||||
takeCensus,
|
||||
takeTreeMap
|
||||
} = require("./snapshot");
|
||||
const VALID_EXPORT_STATES = [states.SAVED, states.READ];
|
||||
|
||||
exports.pickFileAndExportSnapshot = function (snapshot) {
|
||||
return function* (dispatch, getState) {
|
||||
let outputFile = yield openFilePicker({
|
||||
title: L10N.getFormatStr("snapshot.io.save.window"),
|
||||
defaultName: OS.Path.basename(snapshot.path),
|
||||
filters: [[L10N.getFormatStr("snapshot.io.filter"), "*.fxsnapshot"]],
|
||||
mode: "save",
|
||||
});
|
||||
|
||||
if (!outputFile) {
|
||||
return;
|
||||
}
|
||||
|
||||
yield dispatch(exportSnapshot(snapshot, outputFile.path));
|
||||
};
|
||||
};
|
||||
|
||||
const exportSnapshot = exports.exportSnapshot = function (snapshot, dest) {
|
||||
return function* (dispatch, getState) {
|
||||
telemetry.countExportSnapshot();
|
||||
|
||||
dispatch({ type: actions.EXPORT_SNAPSHOT_START, snapshot });
|
||||
|
||||
assert(VALID_EXPORT_STATES.includes(snapshot.state),
|
||||
`Snapshot is in invalid state for exporting: ${snapshot.state}`);
|
||||
|
||||
try {
|
||||
yield OS.File.copy(snapshot.path, dest);
|
||||
} catch (error) {
|
||||
reportException("exportSnapshot", error);
|
||||
dispatch({ type: actions.EXPORT_SNAPSHOT_ERROR, snapshot, error });
|
||||
}
|
||||
|
||||
dispatch({ type: actions.EXPORT_SNAPSHOT_END, snapshot });
|
||||
};
|
||||
};
|
||||
|
||||
const pickFileAndImportSnapshotAndCensus = exports.pickFileAndImportSnapshotAndCensus = function (heapWorker) {
|
||||
return function* (dispatch, getState) {
|
||||
let input = yield openFilePicker({
|
||||
title: L10N.getFormatStr("snapshot.io.import.window"),
|
||||
filters: [[L10N.getFormatStr("snapshot.io.filter"), "*.fxsnapshot"]],
|
||||
mode: "open",
|
||||
});
|
||||
|
||||
if (!input) {
|
||||
return;
|
||||
}
|
||||
|
||||
yield dispatch(importSnapshotAndCensus(heapWorker, input.path));
|
||||
};
|
||||
};
|
||||
|
||||
const importSnapshotAndCensus = exports.importSnapshotAndCensus = function (heapWorker, path) {
|
||||
return function* (dispatch, getState) {
|
||||
telemetry.countImportSnapshot();
|
||||
|
||||
const snapshot = immutableUpdate(createSnapshot(getState()), {
|
||||
path,
|
||||
state: states.IMPORTING,
|
||||
imported: true,
|
||||
});
|
||||
const id = snapshot.id;
|
||||
|
||||
dispatch({ type: actions.IMPORT_SNAPSHOT_START, snapshot });
|
||||
dispatch(selectSnapshot(snapshot.id));
|
||||
|
||||
try {
|
||||
yield dispatch(readSnapshot(heapWorker, id));
|
||||
yield dispatch(computeSnapshotData(heapWorker, id));
|
||||
} catch (error) {
|
||||
reportException("importSnapshot", error);
|
||||
dispatch({ type: actions.IMPORT_SNAPSHOT_ERROR, error, id });
|
||||
}
|
||||
|
||||
dispatch({ type: actions.IMPORT_SNAPSHOT_END, id });
|
||||
};
|
||||
};
|
||||
38
devtools/client/memory/actions/label-display.js
Normal file
38
devtools/client/memory/actions/label-display.js
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
/* 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");
|
||||
const { actions } = require("../constants");
|
||||
const { refresh } = require("./refresh");
|
||||
|
||||
/**
|
||||
* 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(setLabelDisplay(display));
|
||||
yield dispatch(refresh(heapWorker));
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Change the display we use for labeling individual nodes.
|
||||
*
|
||||
* @param {labelDisplayModel} display
|
||||
*/
|
||||
const setLabelDisplay = exports.setLabelDisplay = function (display) {
|
||||
assert(typeof display === "object"
|
||||
&& display
|
||||
&& display.breakdown
|
||||
&& display.breakdown.by,
|
||||
`Breakdowns must be an object with a \`by\` property, attempted to set: ${uneval(display)}`);
|
||||
|
||||
return {
|
||||
type: actions.SET_LABEL_DISPLAY,
|
||||
display,
|
||||
};
|
||||
};
|
||||
19
devtools/client/memory/actions/moz.build
Normal file
19
devtools/client/memory/actions/moz.build
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
# vim: set filetype=python:
|
||||
# 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/.
|
||||
|
||||
DevToolsModules(
|
||||
'allocations.js',
|
||||
'census-display.js',
|
||||
'diffing.js',
|
||||
'filter.js',
|
||||
'io.js',
|
||||
'label-display.js',
|
||||
'refresh.js',
|
||||
'sizes.js',
|
||||
'snapshot.js',
|
||||
'task-cache.js',
|
||||
'tree-map-display.js',
|
||||
'view.js',
|
||||
)
|
||||
44
devtools/client/memory/actions/refresh.js
Normal file
44
devtools/client/memory/actions/refresh.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/. */
|
||||
"use strict";
|
||||
|
||||
const { assert } = require("devtools/shared/DevToolsUtils");
|
||||
const { viewState } = require("../constants");
|
||||
const { refreshDiffing } = require("./diffing");
|
||||
const snapshot = require("./snapshot");
|
||||
|
||||
/**
|
||||
* Refresh the main thread's data from the heap analyses worker, if needed.
|
||||
*
|
||||
* @param {HeapAnalysesWorker} heapWorker
|
||||
*/
|
||||
exports.refresh = function (heapWorker) {
|
||||
return function* (dispatch, getState) {
|
||||
switch (getState().view.state) {
|
||||
case viewState.DIFFING:
|
||||
assert(getState().diffing, "Should have diffing state if in diffing view");
|
||||
yield dispatch(refreshDiffing(heapWorker));
|
||||
return;
|
||||
|
||||
case viewState.CENSUS:
|
||||
yield dispatch(snapshot.refreshSelectedCensus(heapWorker));
|
||||
return;
|
||||
|
||||
case viewState.DOMINATOR_TREE:
|
||||
yield dispatch(snapshot.refreshSelectedDominatorTree(heapWorker));
|
||||
return;
|
||||
|
||||
case viewState.TREE_MAP:
|
||||
yield dispatch(snapshot.refreshSelectedTreeMap(heapWorker));
|
||||
return;
|
||||
|
||||
case viewState.INDIVIDUALS:
|
||||
yield dispatch(snapshot.refreshIndividuals(heapWorker));
|
||||
return;
|
||||
|
||||
default:
|
||||
assert(false, `Unexpected view state: ${getState().view.state}`);
|
||||
}
|
||||
};
|
||||
};
|
||||
13
devtools/client/memory/actions/sizes.js
Normal file
13
devtools/client/memory/actions/sizes.js
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
/* 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 { actions } = require("../constants");
|
||||
|
||||
exports.resizeShortestPaths = function (newSize) {
|
||||
return {
|
||||
type: actions.RESIZE_SHORTEST_PATHS,
|
||||
size: newSize,
|
||||
};
|
||||
};
|
||||
865
devtools/client/memory/actions/snapshot.js
Normal file
865
devtools/client/memory/actions/snapshot.js
Normal file
|
|
@ -0,0 +1,865 @@
|
|||
/* 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 { Preferences } = require("resource://gre/modules/Preferences.jsm");
|
||||
const { assert, reportException, isSet } = require("devtools/shared/DevToolsUtils");
|
||||
const {
|
||||
censusIsUpToDate,
|
||||
getSnapshot,
|
||||
createSnapshot,
|
||||
dominatorTreeIsComputed,
|
||||
} = require("../utils");
|
||||
const {
|
||||
actions,
|
||||
snapshotState: states,
|
||||
viewState,
|
||||
censusState,
|
||||
treeMapState,
|
||||
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
|
||||
* initial census from a snapshot.
|
||||
*
|
||||
* @param {MemoryFront}
|
||||
* @param {HeapAnalysesClient}
|
||||
* @param {Object}
|
||||
*/
|
||||
const takeSnapshotAndCensus = exports.takeSnapshotAndCensus = function (front, heapWorker) {
|
||||
return function* (dispatch, getState) {
|
||||
const id = yield dispatch(takeSnapshot(front));
|
||||
if (id === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
yield dispatch(readSnapshot(heapWorker, id));
|
||||
if (getSnapshot(getState(), id).state !== states.READ) {
|
||||
return;
|
||||
}
|
||||
|
||||
yield dispatch(computeSnapshotData(heapWorker, id));
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Create the census for the snapshot with the provided snapshot id. If the
|
||||
* current view is the DOMINATOR_TREE view, create the dominator tree for this
|
||||
* snapshot as well.
|
||||
*
|
||||
* @param {HeapAnalysesClient} heapWorker
|
||||
* @param {snapshotId} id
|
||||
*/
|
||||
const computeSnapshotData = exports.computeSnapshotData = function (heapWorker, id) {
|
||||
return function* (dispatch, getState) {
|
||||
if (getSnapshot(getState(), id).state !== states.READ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Decide which type of census to take.
|
||||
const censusTaker = getCurrentCensusTaker(getState().view.state);
|
||||
yield dispatch(censusTaker(heapWorker, id));
|
||||
|
||||
if (getState().view.state === viewState.DOMINATOR_TREE &&
|
||||
!getSnapshot(getState(), id).dominatorTree) {
|
||||
yield dispatch(computeAndFetchDominatorTree(heapWorker, id));
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Selects a snapshot and if the snapshot's census is using a different
|
||||
* display, take a new census.
|
||||
*
|
||||
* @param {HeapAnalysesClient} heapWorker
|
||||
* @param {snapshotId} id
|
||||
*/
|
||||
const selectSnapshotAndRefresh = exports.selectSnapshotAndRefresh = function (heapWorker, id) {
|
||||
return function* (dispatch, getState) {
|
||||
if (getState().diffing || getState().individuals) {
|
||||
dispatch(view.changeView(viewState.CENSUS));
|
||||
}
|
||||
|
||||
dispatch(selectSnapshot(id));
|
||||
yield dispatch(refresh.refresh(heapWorker));
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Take a snapshot and return its id on success, or null on failure.
|
||||
*
|
||||
* @param {MemoryFront} front
|
||||
* @returns {Number|null}
|
||||
*/
|
||||
const takeSnapshot = exports.takeSnapshot = function (front) {
|
||||
return function* (dispatch, getState) {
|
||||
telemetry.countTakeSnapshot();
|
||||
|
||||
if (getState().diffing || getState().individuals) {
|
||||
dispatch(view.changeView(viewState.CENSUS));
|
||||
}
|
||||
|
||||
const snapshot = createSnapshot(getState());
|
||||
const id = snapshot.id;
|
||||
dispatch({ type: actions.TAKE_SNAPSHOT_START, snapshot });
|
||||
dispatch(selectSnapshot(id));
|
||||
|
||||
let path;
|
||||
try {
|
||||
path = yield front.saveHeapSnapshot();
|
||||
} catch (error) {
|
||||
reportException("takeSnapshot", error);
|
||||
dispatch({ type: actions.SNAPSHOT_ERROR, id, error });
|
||||
return null;
|
||||
}
|
||||
|
||||
dispatch({ type: actions.TAKE_SNAPSHOT_END, id, path });
|
||||
return snapshot.id;
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Reads a snapshot into memory; necessary to do before taking
|
||||
* a census on the snapshot. May only be called once per snapshot.
|
||||
*
|
||||
* @param {HeapAnalysesClient} heapWorker
|
||||
* @param {snapshotId} id
|
||||
*/
|
||||
const readSnapshot = exports.readSnapshot =
|
||||
TaskCache.declareCacheableTask({
|
||||
getCacheKey(_, id) {
|
||||
return id;
|
||||
},
|
||||
|
||||
task: function* (heapWorker, id, removeFromCache, dispatch, getState) {
|
||||
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}`);
|
||||
|
||||
let creationTime;
|
||||
|
||||
dispatch({ type: actions.READ_SNAPSHOT_START, id });
|
||||
try {
|
||||
yield heapWorker.readHeapSnapshot(snapshot.path);
|
||||
creationTime = yield heapWorker.getCreationTime(snapshot.path);
|
||||
} catch (error) {
|
||||
removeFromCache();
|
||||
reportException("readSnapshot", error);
|
||||
dispatch({ type: actions.SNAPSHOT_ERROR, id, error });
|
||||
return;
|
||||
}
|
||||
|
||||
removeFromCache();
|
||||
dispatch({ type: actions.READ_SNAPSHOT_END, id, creationTime });
|
||||
}
|
||||
});
|
||||
|
||||
let takeCensusTaskCounter = 0;
|
||||
|
||||
/**
|
||||
* Census and tree maps both require snapshots. This function shares the logic
|
||||
* of creating snapshots, but is configurable with specific actions for the
|
||||
* individual census types.
|
||||
*
|
||||
* @param {getDisplay} Get the display object from the state.
|
||||
* @param {getCensus} Get the census from the snapshot.
|
||||
* @param {beginAction} Action to send at the beginning of a heap snapshot.
|
||||
* @param {endAction} Action to send at the end of a heap snapshot.
|
||||
* @param {errorAction} Action to send if a snapshot has an error.
|
||||
*/
|
||||
function makeTakeCensusTask({ getDisplay, getFilter, getCensus, beginAction,
|
||||
endAction, errorAction, canTakeCensus }) {
|
||||
/**
|
||||
* @param {HeapAnalysesClient} heapWorker
|
||||
* @param {snapshotId} id
|
||||
*
|
||||
* @see {Snapshot} model defined in devtools/client/memory/models.js
|
||||
* @see `devtools/shared/heapsnapshot/HeapAnalysesClient.js`
|
||||
* @see `js/src/doc/Debugger/Debugger.Memory.md` for breakdown details
|
||||
*/
|
||||
let thisTakeCensusTaskId = ++takeCensusTaskCounter;
|
||||
return TaskCache.declareCacheableTask({
|
||||
getCacheKey(_, id) {
|
||||
return `take-census-task-${thisTakeCensusTaskId}-${id}`;
|
||||
},
|
||||
|
||||
task: function* (heapWorker, id, removeFromCache, dispatch, getState) {
|
||||
const snapshot = getSnapshot(getState(), id);
|
||||
if (!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: null }).state}`);
|
||||
|
||||
let report, parentMap;
|
||||
let display = getDisplay(getState());
|
||||
let filter = getFilter(getState());
|
||||
|
||||
// If display, filter and inversion haven't changed, don't do anything.
|
||||
if (censusIsUpToDate(filter, display, getCensus(snapshot))) {
|
||||
removeFromCache();
|
||||
return;
|
||||
}
|
||||
|
||||
// Keep taking a census if the display changes while our request is in
|
||||
// flight. Recheck that the display used for the census is the same as the
|
||||
// state's display.
|
||||
do {
|
||||
display = getDisplay(getState());
|
||||
filter = getState().filter;
|
||||
|
||||
dispatch({
|
||||
type: beginAction,
|
||||
id,
|
||||
filter,
|
||||
display
|
||||
});
|
||||
|
||||
let opts = display.inverted
|
||||
? { asInvertedTreeNode: true }
|
||||
: { asTreeNode: true };
|
||||
|
||||
opts.filter = filter || null;
|
||||
|
||||
try {
|
||||
({ report, parentMap } = yield heapWorker.takeCensus(
|
||||
snapshot.path,
|
||||
{ breakdown: display.breakdown },
|
||||
opts));
|
||||
} catch (error) {
|
||||
removeFromCache();
|
||||
reportException("takeCensus", error);
|
||||
dispatch({ type: errorAction, id, error });
|
||||
return;
|
||||
}
|
||||
}
|
||||
while (filter !== getState().filter ||
|
||||
display !== getDisplay(getState()));
|
||||
|
||||
removeFromCache();
|
||||
dispatch({
|
||||
type: endAction,
|
||||
id,
|
||||
display,
|
||||
filter,
|
||||
report,
|
||||
parentMap
|
||||
});
|
||||
|
||||
telemetry.countCensus({ filter, display });
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Take a census.
|
||||
*/
|
||||
const takeCensus = exports.takeCensus = makeTakeCensusTask({
|
||||
getDisplay: (state) => state.censusDisplay,
|
||||
getFilter: (state) => state.filter,
|
||||
getCensus: (snapshot) => snapshot.census,
|
||||
beginAction: actions.TAKE_CENSUS_START,
|
||||
endAction: actions.TAKE_CENSUS_END,
|
||||
errorAction: actions.TAKE_CENSUS_ERROR,
|
||||
canTakeCensus: snapshot =>
|
||||
snapshot.state === states.READ &&
|
||||
(!snapshot.census || snapshot.census.state === censusState.SAVED),
|
||||
});
|
||||
|
||||
/**
|
||||
* Take a census for the treemap.
|
||||
*/
|
||||
const takeTreeMap = exports.takeTreeMap = makeTakeCensusTask({
|
||||
getDisplay: (state) => state.treeMapDisplay,
|
||||
getFilter: () => null,
|
||||
getCensus: (snapshot) => snapshot.treeMap,
|
||||
beginAction: actions.TAKE_TREE_MAP_START,
|
||||
endAction: actions.TAKE_TREE_MAP_END,
|
||||
errorAction: actions.TAKE_TREE_MAP_ERROR,
|
||||
canTakeCensus: snapshot =>
|
||||
snapshot.state === states.READ &&
|
||||
(!snapshot.treeMap || snapshot.treeMap.state === treeMapState.SAVED),
|
||||
});
|
||||
|
||||
/**
|
||||
* Define what should be the default mode for taking a census based on the
|
||||
* default view of the tool.
|
||||
*/
|
||||
const defaultCensusTaker = takeTreeMap;
|
||||
|
||||
/**
|
||||
* Pick the default census taker when taking a snapshot. This should be
|
||||
* determined by the current view. If the view doesn't include a census, then
|
||||
* use the default one defined above. Some census information is always needed
|
||||
* to display some basic information about a snapshot.
|
||||
*
|
||||
* @param {string} value from viewState
|
||||
*/
|
||||
const getCurrentCensusTaker = exports.getCurrentCensusTaker = function (currentView) {
|
||||
switch (currentView) {
|
||||
case viewState.TREE_MAP:
|
||||
return takeTreeMap;
|
||||
case viewState.CENSUS:
|
||||
return takeCensus;
|
||||
default:
|
||||
return defaultCensusTaker;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 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).
|
||||
*
|
||||
* @param {HeapAnalysesClient} heapWorker
|
||||
*/
|
||||
const refreshSelectedCensus = exports.refreshSelectedCensus = function (heapWorker) {
|
||||
return function* (dispatch, getState) {
|
||||
let snapshot = getState().snapshots.find(s => s.selected);
|
||||
if (!snapshot || snapshot.state !== states.READ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Intermediate snapshot states will get handled by the task action that is
|
||||
// orchestrating them. For example, if the snapshot census's state is
|
||||
// SAVING, then the takeCensus action will keep taking a census until
|
||||
// the inverted property matches the inverted state. If the snapshot is
|
||||
// still in the process of being saved or read, the takeSnapshotAndCensus
|
||||
// task action will follow through and ensure that a census is taken.
|
||||
if ((snapshot.census && snapshot.census.state === censusState.SAVED) ||
|
||||
!snapshot.census) {
|
||||
yield dispatch(takeCensus(heapWorker, snapshot.id));
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Refresh the selected snapshot's tree map data, if need be (for example,
|
||||
* display configuration changed).
|
||||
*
|
||||
* @param {HeapAnalysesClient} heapWorker
|
||||
*/
|
||||
const refreshSelectedTreeMap = exports.refreshSelectedTreeMap = function (heapWorker) {
|
||||
return function* (dispatch, getState) {
|
||||
let snapshot = getState().snapshots.find(s => s.selected);
|
||||
if (!snapshot || snapshot.state !== states.READ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Intermediate snapshot states will get handled by the task action that is
|
||||
// orchestrating them. For example, if the snapshot census's state is
|
||||
// SAVING, then the takeCensus action will keep taking a census until
|
||||
// the inverted property matches the inverted state. If the snapshot is
|
||||
// still in the process of being saved or read, the takeSnapshotAndCensus
|
||||
// task action will follow through and ensure that a census is taken.
|
||||
if ((snapshot.treeMap && snapshot.treeMap.state === treeMapState.SAVED) ||
|
||||
!snapshot.treeMap) {
|
||||
yield dispatch(takeTreeMap(heapWorker, snapshot.id));
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Request that the `HeapAnalysesWorker` compute the dominator tree for the
|
||||
* snapshot with the given `id`.
|
||||
*
|
||||
* @param {HeapAnalysesClient} heapWorker
|
||||
* @param {SnapshotId} id
|
||||
*
|
||||
* @returns {Promise<DominatorTreeId>}
|
||||
*/
|
||||
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");
|
||||
|
||||
dispatch({ type: actions.COMPUTE_DOMINATOR_TREE_START, id });
|
||||
|
||||
let dominatorTreeId;
|
||||
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
|
||||
* snapshot-with-the-given-id's dominator tree.
|
||||
*
|
||||
* @param {HeapAnalysesClient} heapWorker
|
||||
* @param {SnapshotId} id
|
||||
*
|
||||
* @returns {Promise<DominatorTreeNode>}
|
||||
*/
|
||||
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");
|
||||
|
||||
let display;
|
||||
let root;
|
||||
do {
|
||||
display = getState().labelDisplay;
|
||||
assert(display && display.breakdown,
|
||||
`Should have a breakdown to describe nodes with, got: ${uneval(display)}`);
|
||||
|
||||
dispatch({ type: actions.FETCH_DOMINATOR_TREE_START, id, display });
|
||||
|
||||
try {
|
||||
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().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
|
||||
* `lazyChildren` from snapshot-with-the-given-id's dominator tree.
|
||||
*
|
||||
* @param {HeapAnalysesClient} heapWorker
|
||||
* @param {SnapshotId} id
|
||||
* @param {DominatorTreeLazyChildren} lazyChildren
|
||||
*/
|
||||
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 ||
|
||||
snapshot.dominatorTree.state === dominatorTreeState.INCREMENTAL_FETCHING,
|
||||
"Cannot fetch immediately dominated nodes in a dominator tree unless " +
|
||||
" the dominator tree has already been computed");
|
||||
|
||||
let display;
|
||||
let response;
|
||||
do {
|
||||
display = getState().labelDisplay;
|
||||
assert(display, "Should have a display to describe nodes with.");
|
||||
|
||||
dispatch({ type: actions.FETCH_IMMEDIATELY_DOMINATED_START, id });
|
||||
|
||||
try {
|
||||
response = yield heapWorker.getImmediatelyDominated({
|
||||
dominatorTreeId: snapshot.dominatorTree.dominatorTreeId,
|
||||
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().labelDisplay);
|
||||
|
||||
removeFromCache();
|
||||
dispatch({
|
||||
type: actions.FETCH_IMMEDIATELY_DOMINATED_END,
|
||||
id,
|
||||
path: response.path,
|
||||
nodes: response.nodes,
|
||||
moreChildrenAvailable: response.moreChildrenAvailable,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Compute and then fetch the dominator tree of the snapshot with the given
|
||||
* `id`.
|
||||
*
|
||||
* @param {HeapAnalysesClient} heapWorker
|
||||
* @param {SnapshotId} id
|
||||
*
|
||||
* @returns {Promise<DominatorTreeNode>}
|
||||
*/
|
||||
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.
|
||||
*
|
||||
* @param {HeapAnalysesClient} heapWorker
|
||||
*/
|
||||
const refreshSelectedDominatorTree = exports.refreshSelectedDominatorTree = function (heapWorker) {
|
||||
return function* (dispatch, getState) {
|
||||
let snapshot = getState().snapshots.find(s => s.selected);
|
||||
if (!snapshot) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (snapshot.dominatorTree &&
|
||||
!(snapshot.dominatorTree.state === dominatorTreeState.COMPUTED ||
|
||||
snapshot.dominatorTree.state === dominatorTreeState.LOADED ||
|
||||
snapshot.dominatorTree.state === dominatorTreeState.INCREMENTAL_FETCHING)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (snapshot.state === states.READ) {
|
||||
if (snapshot.dominatorTree) {
|
||||
yield dispatch(fetchDominatorTree(heapWorker, snapshot.id));
|
||||
} else {
|
||||
yield dispatch(computeAndFetchDominatorTree(heapWorker, snapshot.id));
|
||||
}
|
||||
} else {
|
||||
// If there was an error, we can't continue. If we are still saving or
|
||||
// reading the snapshot, then takeSnapshotAndCensus will finish the job
|
||||
// for us.
|
||||
return;
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Select the snapshot with the given id.
|
||||
*
|
||||
* @param {snapshotId} id
|
||||
* @see {Snapshot} model defined in devtools/client/memory/models.js
|
||||
*/
|
||||
const selectSnapshot = exports.selectSnapshot = function (id) {
|
||||
return {
|
||||
type: actions.SELECT_SNAPSHOT,
|
||||
id
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Delete all snapshots that are in the READ or ERROR state
|
||||
*
|
||||
* @param {HeapAnalysesClient} heapWorker
|
||||
*/
|
||||
const clearSnapshots = exports.clearSnapshots = function (heapWorker) {
|
||||
return function* (dispatch, getState) {
|
||||
let snapshots = getState().snapshots.filter(s => {
|
||||
let snapshotReady = s.state === states.READ || s.state === states.ERROR;
|
||||
let censusReady = (s.treeMap && s.treeMap.state === treeMapState.SAVED) ||
|
||||
(s.census && s.census.state === censusState.SAVED);
|
||||
|
||||
return snapshotReady && censusReady;
|
||||
});
|
||||
|
||||
let ids = snapshots.map(s => s.id);
|
||||
|
||||
dispatch({ type: actions.DELETE_SNAPSHOTS_START, ids });
|
||||
|
||||
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 => {
|
||||
reportException("clearSnapshots", error);
|
||||
dispatch({ type: actions.SNAPSHOT_ERROR, id: snapshot.id, error });
|
||||
});
|
||||
}));
|
||||
|
||||
dispatch({ type: actions.DELETE_SNAPSHOTS_END, ids });
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Delete a snapshot
|
||||
*
|
||||
* @param {HeapAnalysesClient} heapWorker
|
||||
* @param {snapshotModel} snapshot
|
||||
*/
|
||||
const deleteSnapshot = exports.deleteSnapshot = function (heapWorker, snapshot) {
|
||||
return function* (dispatch, getState) {
|
||||
dispatch({ type: actions.DELETE_SNAPSHOTS_START, ids: [snapshot.id] });
|
||||
|
||||
try {
|
||||
yield heapWorker.deleteHeapSnapshot(snapshot.path);
|
||||
} catch (error) {
|
||||
reportException("deleteSnapshot", error);
|
||||
dispatch({ type: actions.SNAPSHOT_ERROR, id: snapshot.id, error });
|
||||
}
|
||||
|
||||
dispatch({ type: actions.DELETE_SNAPSHOTS_END, ids: [snapshot.id] });
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Expand the given node in the snapshot's census report.
|
||||
*
|
||||
* @param {CensusTreeNode} node
|
||||
*/
|
||||
const expandCensusNode = exports.expandCensusNode = function (id, node) {
|
||||
return {
|
||||
type: actions.EXPAND_CENSUS_NODE,
|
||||
id,
|
||||
node,
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Collapse the given node in the snapshot's census report.
|
||||
*
|
||||
* @param {CensusTreeNode} node
|
||||
*/
|
||||
const collapseCensusNode = exports.collapseCensusNode = function (id, node) {
|
||||
return {
|
||||
type: actions.COLLAPSE_CENSUS_NODE,
|
||||
id,
|
||||
node,
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Focus the given node in the snapshot's census's report.
|
||||
*
|
||||
* @param {SnapshotId} id
|
||||
* @param {DominatorTreeNode} node
|
||||
*/
|
||||
const focusCensusNode = exports.focusCensusNode = function (id, node) {
|
||||
return {
|
||||
type: actions.FOCUS_CENSUS_NODE,
|
||||
id,
|
||||
node,
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Expand the given node in the snapshot's dominator tree.
|
||||
*
|
||||
* @param {DominatorTreeTreeNode} node
|
||||
*/
|
||||
const expandDominatorTreeNode = exports.expandDominatorTreeNode = function (id, node) {
|
||||
return {
|
||||
type: actions.EXPAND_DOMINATOR_TREE_NODE,
|
||||
id,
|
||||
node,
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Collapse the given node in the snapshot's dominator tree.
|
||||
*
|
||||
* @param {DominatorTreeTreeNode} node
|
||||
*/
|
||||
const collapseDominatorTreeNode = exports.collapseDominatorTreeNode = function (id, node) {
|
||||
return {
|
||||
type: actions.COLLAPSE_DOMINATOR_TREE_NODE,
|
||||
id,
|
||||
node,
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Focus the given node in the snapshot's dominator tree.
|
||||
*
|
||||
* @param {SnapshotId} id
|
||||
* @param {DominatorTreeNode} node
|
||||
*/
|
||||
const focusDominatorTreeNode = exports.focusDominatorTreeNode = function (id, node) {
|
||||
return {
|
||||
type: actions.FOCUS_DOMINATOR_TREE_NODE,
|
||||
id,
|
||||
node,
|
||||
};
|
||||
};
|
||||
99
devtools/client/memory/actions/task-cache.js
Normal file
99
devtools/client/memory/actions/task-cache.js
Normal file
|
|
@ -0,0 +1,99 @@
|
|||
/* 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) {
|
||||
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) {
|
||||
return extantResult;
|
||||
}
|
||||
|
||||
// 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);
|
||||
};
|
||||
};
|
||||
};
|
||||
37
devtools/client/memory/actions/tree-map-display.js
Normal file
37
devtools/client/memory/actions/tree-map-display.js
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
/* 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");
|
||||
const { actions } = require("../constants");
|
||||
const { refresh } = require("./refresh");
|
||||
/**
|
||||
* Sets the tree map display as the current display and refreshes the tree map
|
||||
* census.
|
||||
*/
|
||||
exports.setTreeMapAndRefresh = function (heapWorker, display) {
|
||||
return function* (dispatch, getState) {
|
||||
dispatch(setTreeMap(display));
|
||||
yield dispatch(refresh(heapWorker));
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Clears out all cached census data in the snapshots and sets new display data
|
||||
* for tree maps.
|
||||
*
|
||||
* @param {treeMapModel} display
|
||||
*/
|
||||
const setTreeMap = exports.setTreeMap = function (display) {
|
||||
assert(typeof display === "object"
|
||||
&& display
|
||||
&& display.breakdown
|
||||
&& display.breakdown.by,
|
||||
`Breakdowns must be an object with a \`by\` property, attempted to set: ${uneval(display)}`);
|
||||
|
||||
return {
|
||||
type: actions.SET_TREE_MAP_DISPLAY,
|
||||
display,
|
||||
};
|
||||
};
|
||||
67
devtools/client/memory/actions/view.js
Normal file
67
devtools/client/memory/actions/view.js
Normal file
|
|
@ -0,0 +1,67 @@
|
|||
/* 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");
|
||||
const { actions } = require("../constants");
|
||||
const { findSelectedSnapshot } = require("../utils");
|
||||
const refresh = require("./refresh");
|
||||
|
||||
/**
|
||||
* Change the currently selected view.
|
||||
*
|
||||
* @param {viewState} view
|
||||
*/
|
||||
const changeView = exports.changeView = function (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,
|
||||
});
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Change the currently selected view and ensure all our data is up to date from
|
||||
* the heap worker.
|
||||
*
|
||||
* @param {viewState} view
|
||||
* @param {HeapAnalysesClient} heapWorker
|
||||
*/
|
||||
exports.changeViewAndRefresh = function (view, heapWorker) {
|
||||
return function* (dispatch, getState) {
|
||||
dispatch(changeView(view));
|
||||
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));
|
||||
};
|
||||
};
|
||||
Loading…
Add table
Add a link
Reference in a new issue