mirror of
https://gitlab.winehq.org/wine/wine-gecko.git
synced 2024-09-13 09:24:08 -07:00
Bug 1214872 - Set up state changes in the memory tool. r=fitzgen
This commit is contained in:
parent
82b855996a
commit
f0d14e473f
@ -3,16 +3,82 @@
|
||||
* You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
"use strict";
|
||||
|
||||
const { PROMISE } = require("devtools/client/shared/redux/middleware/promise");
|
||||
const { actions } = require("../constants");
|
||||
// @TODO 1215606
|
||||
// Use this assert instead of utils when fixed.
|
||||
// const { assert } = require("devtools/shared/DevToolsUtils");
|
||||
const { createSnapshot, assert } = require("../utils");
|
||||
const { actions, snapshotState: states } = require("../constants");
|
||||
|
||||
/**
|
||||
* 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 takeSnapshotAndCensus (front, heapWorker) {
|
||||
return function *(dispatch, getStore) {
|
||||
let snapshot = yield dispatch(takeSnapshot(front));
|
||||
yield dispatch(readSnapshot(heapWorker, snapshot));
|
||||
yield dispatch(takeCensus(heapWorker, snapshot));
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* @param {MemoryFront}
|
||||
*/
|
||||
const takeSnapshot = exports.takeSnapshot = function takeSnapshot (front) {
|
||||
return {
|
||||
type: actions.TAKE_SNAPSHOT,
|
||||
[PROMISE]: front.saveHeapSnapshot()
|
||||
return function *(dispatch, getStore) {
|
||||
let snapshot = createSnapshot();
|
||||
dispatch({ type: actions.TAKE_SNAPSHOT_START, snapshot });
|
||||
dispatch(selectSnapshot(snapshot));
|
||||
|
||||
let path = yield front.saveHeapSnapshot();
|
||||
dispatch({ type: actions.TAKE_SNAPSHOT_END, snapshot, path });
|
||||
|
||||
return snapshot;
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Reads a snapshot into memory; necessary to do before taking
|
||||
* a census on the snapshot. May only be called once per snapshot.
|
||||
*
|
||||
* @param {HeapAnalysesClient}
|
||||
* @param {Snapshot} snapshot,
|
||||
*/
|
||||
const readSnapshot = exports.readSnapshot = function readSnapshot (heapWorker, snapshot) {
|
||||
return function *(dispatch, getStore) {
|
||||
// @TODO 1215606
|
||||
assert(snapshot.state === states.SAVED,
|
||||
"Should only read a snapshot once");
|
||||
|
||||
dispatch({ type: actions.READ_SNAPSHOT_START, snapshot });
|
||||
yield heapWorker.readHeapSnapshot(snapshot.path);
|
||||
dispatch({ type: actions.READ_SNAPSHOT_END, snapshot });
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* @param {HeapAnalysesClient} heapWorker
|
||||
* @param {Snapshot} snapshot,
|
||||
*
|
||||
* @see {Snapshot} model defined in devtools/client/memory/app.js
|
||||
* @see `devtools/shared/heapsnapshot/HeapAnalysesClient.js`
|
||||
* @see `js/src/doc/Debugger/Debugger.Memory.md` for breakdown details
|
||||
*/
|
||||
const takeCensus = exports.takeCensus = function takeCensus (heapWorker, snapshot) {
|
||||
return function *(dispatch, getStore) {
|
||||
// @TODO 1215606
|
||||
assert([states.READ, states.SAVED_CENSUS].includes(snapshot.state),
|
||||
"Can only take census of snapshots in READ or SAVED_CENSUS state");
|
||||
|
||||
let breakdown = getStore().breakdown;
|
||||
dispatch({ type: actions.TAKE_CENSUS_START, snapshot, breakdown });
|
||||
|
||||
let census = yield heapWorker.takeCensus(snapshot.path, { breakdown }, { asTreeNode: true });
|
||||
dispatch({ type: actions.TAKE_CENSUS_END, snapshot, census });
|
||||
};
|
||||
};
|
||||
|
||||
@ -26,3 +92,4 @@ const selectSnapshot = exports.selectSnapshot = function takeSnapshot (snapshot)
|
||||
snapshot
|
||||
};
|
||||
};
|
||||
|
||||
|
@ -1,9 +1,11 @@
|
||||
const { DOM: dom, createClass, createFactory, PropTypes } = require("devtools/client/shared/vendor/react");
|
||||
const { connect } = require("devtools/client/shared/vendor/react-redux");
|
||||
const { selectSnapshot, takeSnapshot } = require("./actions/snapshot");
|
||||
const { selectSnapshot, takeSnapshotAndCensus } = require("./actions/snapshot");
|
||||
const { snapshotState } = require("./constants");
|
||||
const Toolbar = createFactory(require("./components/toolbar"));
|
||||
const List = createFactory(require("./components/list"));
|
||||
const SnapshotListItem = createFactory(require("./components/snapshot-list-item"));
|
||||
const HeapView = createFactory(require("./components/heap"));
|
||||
|
||||
const stateModel = {
|
||||
/**
|
||||
@ -12,23 +14,42 @@ const stateModel = {
|
||||
*/
|
||||
front: PropTypes.any,
|
||||
|
||||
/**
|
||||
* {HeapAnalysesClient}
|
||||
* Used to communicate with the worker that performs analyses on heaps.
|
||||
*/
|
||||
heapWorker: PropTypes.any,
|
||||
|
||||
/**
|
||||
* The breakdown object DSL describing how we want
|
||||
* the census data to be.
|
||||
* @see `js/src/doc/Debugger/Debugger.Memory.md`
|
||||
*/
|
||||
breakdown: PropTypes.object.isRequired,
|
||||
|
||||
/**
|
||||
* {Array<Snapshot>}
|
||||
* List of references to all snapshots taken
|
||||
*/
|
||||
snapshots: PropTypes.arrayOf(PropTypes.shape({
|
||||
// Unique ID for a snapshot
|
||||
id: PropTypes.number.isRequired,
|
||||
snapshotId: PropTypes.string,
|
||||
// fs path to where the snapshot is stored; used to
|
||||
// identify the snapshot for HeapAnalysesClient.
|
||||
path: PropTypes.string,
|
||||
// Whether or not this snapshot is currently selected.
|
||||
selected: PropTypes.bool.isRequired,
|
||||
status: PropTypes.oneOf([
|
||||
"start",
|
||||
"done",
|
||||
"error",
|
||||
]).isRequired,
|
||||
// Whther or not the snapshot has been read into memory.
|
||||
// Only needed to do once.
|
||||
snapshotRead: PropTypes.bool.isRequired,
|
||||
// State the snapshot is in
|
||||
// @see ./constants.js
|
||||
state: PropTypes.oneOf(Object.keys(snapshotState)).isRequired,
|
||||
// Data of a census breakdown
|
||||
census: PropTypes.any,
|
||||
}))
|
||||
};
|
||||
|
||||
|
||||
const App = createClass({
|
||||
displayName: "memory-tool",
|
||||
|
||||
@ -36,31 +57,42 @@ const App = createClass({
|
||||
|
||||
childContextTypes: {
|
||||
front: PropTypes.any,
|
||||
heapWorker: PropTypes.any,
|
||||
},
|
||||
|
||||
getChildContext() {
|
||||
return {
|
||||
front: this.props.front,
|
||||
heapWorker: this.props.heapWorker,
|
||||
}
|
||||
},
|
||||
|
||||
render() {
|
||||
let { dispatch, snapshots, front } = this.props;
|
||||
let { dispatch, snapshots, front, heapWorker, breakdown } = this.props;
|
||||
let selectedSnapshot = snapshots.find(s => s.selected);
|
||||
|
||||
return (
|
||||
dom.div({ id: "memory-tool" }, [
|
||||
|
||||
Toolbar({
|
||||
buttons: [{
|
||||
className: "take-snapshot",
|
||||
onClick: () => dispatch(takeSnapshot(front))
|
||||
onClick: () => dispatch(takeSnapshotAndCensus(front, heapWorker))
|
||||
}]
|
||||
}),
|
||||
|
||||
List({
|
||||
itemComponent: SnapshotListItem,
|
||||
items: snapshots,
|
||||
onClick: snapshot => dispatch(selectSnapshot(snapshot))
|
||||
})
|
||||
dom.div({ id: "memory-tool-container" }, [
|
||||
List({
|
||||
itemComponent: SnapshotListItem,
|
||||
items: snapshots,
|
||||
onClick: snapshot => dispatch(selectSnapshot(snapshot))
|
||||
}),
|
||||
|
||||
HeapView({
|
||||
snapshot: selectedSnapshot,
|
||||
onSnapshotClick: () => dispatch(takeSnapshotAndCensus(front, heapWorker))
|
||||
}),
|
||||
])
|
||||
])
|
||||
);
|
||||
},
|
||||
|
49
devtools/client/memory/components/heap.js
Normal file
49
devtools/client/memory/components/heap.js
Normal file
@ -0,0 +1,49 @@
|
||||
const { DOM: dom, createClass, PropTypes } = require("devtools/client/shared/vendor/react");
|
||||
const { getSnapshotStatusText } = require("../utils");
|
||||
const { snapshotState: states } = require("../constants");
|
||||
const TAKE_SNAPSHOT_TEXT = "Take snapshot";
|
||||
|
||||
/**
|
||||
* Main view for the memory tool -- contains several panels for different states;
|
||||
* an initial state of only a button to take a snapshot, loading states, and the
|
||||
* heap view tree.
|
||||
*/
|
||||
|
||||
const Heap = module.exports = createClass({
|
||||
displayName: "heap-view",
|
||||
|
||||
propTypes: {
|
||||
onSnapshotClick: PropTypes.func.isRequired,
|
||||
snapshot: PropTypes.any,
|
||||
},
|
||||
|
||||
render() {
|
||||
let { snapshot, onSnapshotClick } = this.props;
|
||||
let pane;
|
||||
let census = snapshot ? snapshot.census : null;
|
||||
let state = snapshot ? snapshot.state : "initial";
|
||||
let statusText = getSnapshotStatusText(snapshot);
|
||||
|
||||
switch (state) {
|
||||
case "initial":
|
||||
pane = dom.div({ className: "heap-view-panel", "data-state": "initial" },
|
||||
dom.button({ className: "take-snapshot", onClick: onSnapshotClick }, TAKE_SNAPSHOT_TEXT)
|
||||
);
|
||||
break;
|
||||
case states.SAVING:
|
||||
case states.SAVED:
|
||||
case states.READING:
|
||||
case states.READ:
|
||||
case states.SAVING_CENSUS:
|
||||
pane = dom.div({ className: "heap-view-panel", "data-state": state }, statusText);
|
||||
break;
|
||||
case states.SAVED_CENSUS:
|
||||
pane = dom.div({ className: "heap-view-panel", "data-state": "loaded" }, JSON.stringify(census || {}));
|
||||
break;
|
||||
}
|
||||
|
||||
return (
|
||||
dom.div({ id: "heap-view", "data-state": state }, pane)
|
||||
)
|
||||
}
|
||||
});
|
@ -4,6 +4,7 @@
|
||||
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
DevToolsModules(
|
||||
'heap.js',
|
||||
'list.js',
|
||||
'snapshot-list-item.js',
|
||||
'toolbar.js',
|
||||
|
@ -1,4 +1,5 @@
|
||||
const { DOM: dom, createClass, PropTypes } = require("devtools/client/shared/vendor/react");
|
||||
const { getSnapshotStatusText } = require("../utils");
|
||||
|
||||
const SnapshotListItem = module.exports = createClass({
|
||||
displayName: "snapshot-list-item",
|
||||
@ -12,9 +13,15 @@ const SnapshotListItem = module.exports = createClass({
|
||||
render() {
|
||||
let { index, item, onClick } = this.props;
|
||||
let className = `snapshot-list-item ${item.selected ? " selected" : ""}`;
|
||||
let statusText = getSnapshotStatusText(item);
|
||||
|
||||
return (
|
||||
dom.li({ className, onClick },
|
||||
dom.span({ className: "snapshot-title" }, `Snapshot #${index}`)
|
||||
dom.span({
|
||||
className: `snapshot-title ${statusText ? " devtools-throbber" : ""}`
|
||||
}, `Snapshot #${index}`),
|
||||
|
||||
statusText ? dom.span({ className: "snapshot-state" }, statusText) : void 0
|
||||
)
|
||||
);
|
||||
}
|
||||
|
@ -6,7 +6,34 @@
|
||||
const actions = exports.actions = {};
|
||||
|
||||
// Fired by UI to request a snapshot from the actor.
|
||||
actions.TAKE_SNAPSHOT = "take-snapshot";
|
||||
actions.TAKE_SNAPSHOT_START = "take-snapshot-start";
|
||||
actions.TAKE_SNAPSHOT_END = "take-snapshot-end";
|
||||
|
||||
// When a heap snapshot is read into memory -- only fired
|
||||
// once per snapshot.
|
||||
actions.READ_SNAPSHOT_START = "read-snapshot-start";
|
||||
actions.READ_SNAPSHOT_END = "read-snapshot-end";
|
||||
|
||||
// When a census is being performed on a heap snapshot
|
||||
actions.TAKE_CENSUS_START = "take-census-start";
|
||||
actions.TAKE_CENSUS_END = "take-census-end";
|
||||
|
||||
// Fired by UI to select a snapshot to view.
|
||||
actions.SELECT_SNAPSHOT = "select-snapshot";
|
||||
|
||||
const snapshotState = exports.snapshotState = {};
|
||||
|
||||
/**
|
||||
* Various states a snapshot can be in.
|
||||
* An FSM describing snapshot states:
|
||||
*
|
||||
* SAVING -> SAVED -> READING -> READ <- <- <- SAVED_CENSUS
|
||||
* ↘ ↗
|
||||
* SAVING_CENSUS
|
||||
*/
|
||||
snapshotState.SAVING = "snapshot-state-saving";
|
||||
snapshotState.SAVED = "snapshot-state-saved";
|
||||
snapshotState.READING = "snapshot-state-reading";
|
||||
snapshotState.READ = "snapshot-state-read";
|
||||
snapshotState.SAVING_CENSUS = "snapshot-state-saving-census";
|
||||
snapshotState.SAVED_CENSUS = "snapshot-state-saved-census";
|
||||
|
@ -14,20 +14,15 @@ const App = createFactory(require("devtools/client/memory/app"));
|
||||
const Store = require("devtools/client/memory/store");
|
||||
|
||||
/**
|
||||
* The current target, toolbox and MemoryFront, set by this tool's host.
|
||||
* The current target, toolbox, MemoryFront, and HeapAnalysesClient, set by this tool's host.
|
||||
*/
|
||||
var gToolbox, gTarget, gFront;
|
||||
|
||||
/**
|
||||
* The current target, toolbox and MemoryFront, set by this tool's host.
|
||||
*/
|
||||
var gToolbox, gTarget, gFront;
|
||||
var gToolbox, gTarget, gFront, gHeapAnalysesClient;
|
||||
|
||||
function initialize () {
|
||||
return Task.spawn(function*() {
|
||||
let root = document.querySelector("#app");
|
||||
let store = Store();
|
||||
let app = createElement(App, { front: gFront });
|
||||
let app = createElement(App, { front: gFront, heapWorker: gHeapAnalysesClient });
|
||||
let provider = createElement(Provider, { store }, app);
|
||||
render(provider, root);
|
||||
});
|
||||
|
@ -17,6 +17,7 @@ DevToolsModules(
|
||||
'panel.js',
|
||||
'reducers.js',
|
||||
'store.js',
|
||||
'utils.js',
|
||||
)
|
||||
|
||||
BROWSER_CHROME_MANIFESTS += ['test/browser/browser.ini']
|
||||
|
@ -9,6 +9,7 @@ const { Cc, Ci, Cu, Cr } = require("chrome");
|
||||
const { Task } = require("resource://gre/modules/Task.jsm");
|
||||
const EventEmitter = require("devtools/shared/event-emitter");
|
||||
const { MemoryFront } = require("devtools/server/actors/memory");
|
||||
const HeapAnalysesClient = require("devtools/shared/heapsnapshot/HeapAnalysesClient");
|
||||
const promise = require("promise");
|
||||
|
||||
function MemoryPanel (iframeWindow, toolbox) {
|
||||
@ -31,6 +32,7 @@ MemoryPanel.prototype = {
|
||||
this.panelWin.gFront = new MemoryFront(this.target.client,
|
||||
this.target.form,
|
||||
rootForm);
|
||||
this.panelWin.gHeapAnalysesClient = new HeapAnalysesClient();
|
||||
|
||||
yield this.panelWin.gFront.attach();
|
||||
return this._opening = this.panelWin.initialize().then(() => {
|
||||
@ -57,6 +59,7 @@ MemoryPanel.prototype = {
|
||||
return this._destroyer = this.panelWin.destroy().then(() => {
|
||||
// Destroy front to ensure packet handler is removed from client
|
||||
this.panelWin.gFront.destroy();
|
||||
this.panelWin.gHeapAnalysesClient.destroy();
|
||||
this.panelWin = null;
|
||||
this.emit("destroyed");
|
||||
return this;
|
||||
|
@ -1 +1,3 @@
|
||||
exports.snapshots = require("./reducers/snapshot");
|
||||
exports.snapshots = require("./reducers/snapshots");
|
||||
exports.breakdown = require("./reducers/breakdown");
|
||||
exports.errors = require("./reducers/errors");
|
||||
|
15
devtools/client/memory/reducers/breakdown.js
Normal file
15
devtools/client/memory/reducers/breakdown.js
Normal file
@ -0,0 +1,15 @@
|
||||
const { actions } = require("../constants");
|
||||
|
||||
// Hardcoded breakdown for now
|
||||
const DEFAULT_BREAKDOWN = {
|
||||
by: "internalType",
|
||||
then: { by: "count", count: true, bytes: true }
|
||||
};
|
||||
|
||||
/**
|
||||
* Not much to do here yet until we can change breakdowns,
|
||||
* but this gets it in our store.
|
||||
*/
|
||||
module.exports = function (state=DEFAULT_BREAKDOWN, action) {
|
||||
return Object.assign({}, DEFAULT_BREAKDOWN);
|
||||
};
|
13
devtools/client/memory/reducers/errors.js
Normal file
13
devtools/client/memory/reducers/errors.js
Normal file
@ -0,0 +1,13 @@
|
||||
const { ERROR_TYPE: TASK_ERROR_TYPE } = require("devtools/client/shared/redux/middleware/task");
|
||||
|
||||
/**
|
||||
* Handle errors dispatched from task middleware and
|
||||
* store them so we can check in tests or dump them out.
|
||||
*/
|
||||
module.exports = function (state=[], action) {
|
||||
switch (action.type) {
|
||||
case TASK_ERROR_TYPE:
|
||||
return [...state, action.error];
|
||||
}
|
||||
return state;
|
||||
};
|
@ -4,5 +4,7 @@
|
||||
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
DevToolsModules(
|
||||
'snapshot.js',
|
||||
'breakdown.js',
|
||||
'errors.js',
|
||||
'snapshots.js',
|
||||
)
|
||||
|
@ -1,54 +0,0 @@
|
||||
const { actions } = require("../constants");
|
||||
const { PROMISE } = require("devtools/client/shared/redux/middleware/promise");
|
||||
const DevToolsUtils = require("devtools/shared/DevToolsUtils");
|
||||
|
||||
function handleTakeSnapshot (state, action) {
|
||||
switch (action.status) {
|
||||
|
||||
case "start":
|
||||
return [...state, {
|
||||
id: action.seqId,
|
||||
status: action.status,
|
||||
// auto selected if this is the first snapshot
|
||||
selected: state.length === 0
|
||||
}];
|
||||
|
||||
case "done":
|
||||
let snapshot = state.find(s => s.id === action.seqId);
|
||||
if (!snapshot) {
|
||||
DevToolsUtils.reportException(`No snapshot with id "${action.seqId}" for TAKE_SNAPSHOT`);
|
||||
break;
|
||||
}
|
||||
snapshot.status = "done";
|
||||
snapshot.snapshotId = action.value;
|
||||
return [...state];
|
||||
|
||||
case "error":
|
||||
DevToolsUtils.reportException(`No async state found for ${action.type}`);
|
||||
}
|
||||
return [...state];
|
||||
}
|
||||
|
||||
function handleSelectSnapshot (state, action) {
|
||||
let selected = state.find(s => s.id === action.snapshot.id);
|
||||
|
||||
if (!selected) {
|
||||
DevToolsUtils.reportException(`Cannot select non-existant snapshot ${snapshot.id}`);
|
||||
}
|
||||
|
||||
return state.map(s => {
|
||||
s.selected = s === selected;
|
||||
return s;
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = function (state=[], action) {
|
||||
switch (action.type) {
|
||||
case actions.TAKE_SNAPSHOT:
|
||||
return handleTakeSnapshot(state, action);
|
||||
case actions.SELECT_SNAPSHOT:
|
||||
return handleSelectSnapshot(state, action);
|
||||
}
|
||||
|
||||
return state;
|
||||
};
|
59
devtools/client/memory/reducers/snapshots.js
Normal file
59
devtools/client/memory/reducers/snapshots.js
Normal file
@ -0,0 +1,59 @@
|
||||
const { actions, snapshotState: states } = require("../constants");
|
||||
const { getSnapshot } = require("../utils");
|
||||
|
||||
let handlers = Object.create({});
|
||||
|
||||
handlers[actions.TAKE_SNAPSHOT_START] = function (snapshots, { snapshot }) {
|
||||
return [...snapshots, snapshot];
|
||||
};
|
||||
|
||||
handlers[actions.TAKE_SNAPSHOT_END] = function (snapshots, action) {
|
||||
return snapshots.map(snapshot => {
|
||||
if (snapshot.id === action.snapshot.id) {
|
||||
snapshot.state = states.SAVED;
|
||||
snapshot.path = action.path;
|
||||
}
|
||||
return snapshot;
|
||||
});
|
||||
};
|
||||
|
||||
handlers[actions.READ_SNAPSHOT_START] = function (snapshots, action) {
|
||||
let snapshot = getSnapshot(snapshots, action.snapshot);
|
||||
snapshot.state = states.READING;
|
||||
return [...snapshots];
|
||||
};
|
||||
|
||||
handlers[actions.READ_SNAPSHOT_END] = function (snapshots, action) {
|
||||
let snapshot = getSnapshot(snapshots, action.snapshot);
|
||||
snapshot.state = states.READ;
|
||||
return [...snapshots];
|
||||
};
|
||||
|
||||
handlers[actions.TAKE_CENSUS_START] = function (snapshots, action) {
|
||||
let snapshot = getSnapshot(snapshots, action.snapshot);
|
||||
snapshot.state = states.SAVING_CENSUS;
|
||||
snapshot.census = null;
|
||||
return [...snapshots];
|
||||
};
|
||||
|
||||
handlers[actions.TAKE_CENSUS_END] = function (snapshots, action) {
|
||||
let snapshot = getSnapshot(snapshots, action.snapshot);
|
||||
snapshot.state = states.SAVED_CENSUS;
|
||||
snapshot.census = action.census;
|
||||
return [...snapshots];
|
||||
};
|
||||
|
||||
handlers[actions.SELECT_SNAPSHOT] = function (snapshots, action) {
|
||||
return snapshots.map(s => {
|
||||
s.selected = s.id === action.snapshot.id;
|
||||
return s;
|
||||
});
|
||||
};
|
||||
|
||||
module.exports = function (snapshots=[], action) {
|
||||
let handler = handlers[action.type];
|
||||
if (handler) {
|
||||
return handler(snapshots, action);
|
||||
}
|
||||
return snapshots;
|
||||
};
|
@ -14,6 +14,7 @@ var promise = require("promise");
|
||||
var { Task } = Cu.import("resource://gre/modules/Task.jsm", {});
|
||||
var { expectState } = require("devtools/server/actors/common");
|
||||
var HeapSnapshotFileUtils = require("devtools/shared/heapsnapshot/HeapSnapshotFileUtils");
|
||||
var HeapAnalysesClient = require("devtools/shared/heapsnapshot/HeapAnalysesClient");
|
||||
var { addDebuggerToGlobal } = require("resource://gre/modules/jsdebugger.jsm");
|
||||
var Store = require("devtools/client/memory/store");
|
||||
var SYSTEM_PRINCIPAL = Cc["@mozilla.org/systemprincipal;1"].createInstance(Ci.nsIPrincipal);
|
||||
@ -39,8 +40,7 @@ StubbedMemoryFront.prototype.detach = Task.async(function *() {
|
||||
});
|
||||
|
||||
StubbedMemoryFront.prototype.saveHeapSnapshot = expectState("attached", Task.async(function *() {
|
||||
let path = ThreadSafeChromeUtils.saveHeapSnapshot({ debugger: this.dbg });
|
||||
return HeapSnapshotFileUtils.getSnapshotIdFromPath(path);
|
||||
return ThreadSafeChromeUtils.saveHeapSnapshot({ runtime: true });
|
||||
}), "saveHeapSnapshot");
|
||||
|
||||
function waitUntilState (store, predicate) {
|
||||
|
@ -6,6 +6,7 @@
|
||||
*/
|
||||
|
||||
let actions = require("devtools/client/memory/actions/snapshot");
|
||||
let { snapshotState: states } = require("devtools/client/memory/constants");
|
||||
|
||||
function run_test() {
|
||||
run_next_test();
|
||||
@ -22,9 +23,7 @@ add_task(function *() {
|
||||
|
||||
yield waitUntilState(store, ({ snapshots }) => snapshots.length === 5 && snapshots.every(isDone));
|
||||
|
||||
ok(store.getState().snapshots[0].selected, "snapshot[0] selected by default");
|
||||
|
||||
for (let i = 1; i < 5; i++) {
|
||||
for (let i = 0; i < 5; i++) {
|
||||
do_print(`Selecting snapshot[${i}]`);
|
||||
store.dispatch(actions.selectSnapshot(store.getState().snapshots[i]));
|
||||
yield waitUntilState(store, ({ snapshots }) => snapshots[i].selected);
|
||||
@ -35,4 +34,4 @@ add_task(function *() {
|
||||
}
|
||||
});
|
||||
|
||||
function isDone (s) { return s.status === "done"; }
|
||||
function isDone (s) { return s.state === states.SAVED; }
|
||||
|
49
devtools/client/memory/test/unit/test_action-take-census.js
Normal file
49
devtools/client/memory/test/unit/test_action-take-census.js
Normal file
@ -0,0 +1,49 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
/**
|
||||
* Tests the async reducer responding to the action `takeCensus(heapWorker, snapshot)`
|
||||
*/
|
||||
|
||||
var { snapshotState: states } = require("devtools/client/memory/constants");
|
||||
var { ERROR_TYPE } = require("devtools/client/shared/redux/middleware/task");
|
||||
var actions = require("devtools/client/memory/actions/snapshot");
|
||||
|
||||
function run_test() {
|
||||
run_next_test();
|
||||
}
|
||||
|
||||
add_task(function *() {
|
||||
let front = new StubbedMemoryFront();
|
||||
let heapWorker = new HeapAnalysesClient();
|
||||
yield front.attach();
|
||||
let store = Store();
|
||||
|
||||
store.dispatch(actions.takeSnapshot(front));
|
||||
yield waitUntilState(store, () => {
|
||||
let snapshots = store.getState().snapshots;
|
||||
return snapshots.length === 1 && snapshots[0].state === states.SAVED;
|
||||
});
|
||||
|
||||
let snapshot = store.getState().snapshots[0];
|
||||
equal(snapshot.census, null, "No census data exists yet on the snapshot.");
|
||||
|
||||
// Test error case of wrong state
|
||||
store.dispatch(actions.takeCensus(heapWorker, snapshot));
|
||||
yield waitUntilState(store, () => store.getState().errors.length === 1);
|
||||
ok(/Assertion failure/.test(store.getState().errors[0]),
|
||||
"Error thrown when taking a census of a snapshot that has not been read.");
|
||||
|
||||
store.dispatch(actions.readSnapshot(heapWorker, snapshot));
|
||||
yield waitUntilState(store, () => store.getState().snapshots[0].state === states.READ);
|
||||
|
||||
store.dispatch(actions.takeCensus(heapWorker, snapshot));
|
||||
yield waitUntilState(store, () => store.getState().snapshots[0].state === states.SAVING_CENSUS);
|
||||
yield waitUntilState(store, () => store.getState().snapshots[0].state === states.SAVED_CENSUS);
|
||||
|
||||
snapshot = store.getState().snapshots[0];
|
||||
ok(snapshot.census, "Snapshot has census after saved census");
|
||||
ok(snapshot.census.children.length, "Census is in tree node form with the default breakdown");
|
||||
ok(snapshot.census.children.find(t => t.name === "JSObject"),
|
||||
"Census is in tree node form with the default breakdown");
|
||||
});
|
@ -0,0 +1,46 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
/**
|
||||
* Tests the task creator `takeSnapshotAndCensus()` for the whole flow of
|
||||
* taking a snapshot, and its sub-actions.
|
||||
*/
|
||||
|
||||
let { snapshotState: states } = require("devtools/client/memory/constants");
|
||||
let actions = require("devtools/client/memory/actions/snapshot");
|
||||
|
||||
function run_test() {
|
||||
run_next_test();
|
||||
}
|
||||
|
||||
add_task(function *() {
|
||||
let front = new StubbedMemoryFront();
|
||||
let heapWorker = new HeapAnalysesClient();
|
||||
yield front.attach();
|
||||
let store = Store();
|
||||
|
||||
let i = 0;
|
||||
let expected = ["SAVING", "SAVED", "READING", "READ", "SAVING_CENSUS", "SAVED_CENSUS"];
|
||||
let expectStates = () => {
|
||||
if (i >= expected.length) { return false; }
|
||||
|
||||
let snapshot = store.getState().snapshots[0] || {};
|
||||
let isCorrectState = snapshot.state === states[expected[i]];
|
||||
if (isCorrectState) {
|
||||
ok(true, `Found expected state ${expected[i]}`);
|
||||
i++;
|
||||
}
|
||||
return isCorrectState;
|
||||
};
|
||||
|
||||
let unsubscribe = store.subscribe(expectStates);
|
||||
store.dispatch(actions.takeSnapshotAndCensus(front, heapWorker));
|
||||
|
||||
yield waitUntilState(store, () => i === 6);
|
||||
unsubscribe();
|
||||
|
||||
ok(true, "takeSnapshotAndCensus() produces the correct sequence of states in a snapshot");
|
||||
let snapshot = store.getState().snapshots[0];
|
||||
ok(snapshot.census, "snapshot has census data");
|
||||
ok(snapshot.selected, "snapshot is selected");
|
||||
});
|
@ -5,7 +5,8 @@
|
||||
* Tests the async reducer responding to the action `takeSnapshot(front)`
|
||||
*/
|
||||
|
||||
var actions = require("devtools/client/memory/actions/snapshot");
|
||||
let actions = require("devtools/client/memory/actions/snapshot");
|
||||
let { snapshotState: states } = require("devtools/client/memory/constants");
|
||||
|
||||
function run_test() {
|
||||
run_next_test();
|
||||
@ -20,44 +21,34 @@ add_task(function *() {
|
||||
|
||||
let foundPendingState = false;
|
||||
let foundDoneState = false;
|
||||
let foundAllSnapshots = false;
|
||||
|
||||
function checkState () {
|
||||
let { snapshots } = store.getState();
|
||||
let lastSnapshot = snapshots[snapshots.length - 1];
|
||||
|
||||
if (snapshots.length === 1 && snapshots[0].status === "start") {
|
||||
if (lastSnapshot.state === states.SAVING) {
|
||||
foundPendingState = true;
|
||||
ok(foundPendingState, "Got state change for pending heap snapshot request");
|
||||
ok(snapshots[0].selected, "First snapshot is auto-selected");
|
||||
ok(!(snapshots[0].snapshotId), "Snapshot does not yet have a snapshotId");
|
||||
ok(!lastSnapshot.path, "Snapshot does not yet have a path");
|
||||
ok(!lastSnapshot.census, "Has no census data when loading");
|
||||
}
|
||||
if (snapshots.length === 1 && snapshots[0].status === "done") {
|
||||
else if (lastSnapshot.state === states.SAVED) {
|
||||
foundDoneState = true;
|
||||
ok(foundDoneState, "Got state change for completed heap snapshot request");
|
||||
ok(snapshots[0].snapshotId, "Snapshot fetched with a snapshotId");
|
||||
}
|
||||
if (snapshots.length === 1 && snapshots[0].status === "error") {
|
||||
ok(false, "takeSnapshot's promise returned with an error");
|
||||
}
|
||||
|
||||
if (snapshots.length === 5 && snapshots.every(s => s.status === "done")) {
|
||||
foundAllSnapshots = true;
|
||||
ok(snapshots.every(s => s.status === "done"), "All snapshots have a snapshotId");
|
||||
equal(snapshots.length, 5, "Found 5 snapshots");
|
||||
ok(snapshots.every(s => s.snapshotId), "All snapshots have a snapshotId");
|
||||
ok(snapshots[0].selected, "First snapshot still selected");
|
||||
equal(snapshots.filter(s => !s.selected).length, 4, "All other snapshots are unselected");
|
||||
ok(foundPendingState, "SAVED state occurs after SAVING state");
|
||||
ok(lastSnapshot.path, "Snapshot fetched with a path");
|
||||
ok(snapshots.every(s => s.selected === (s.id === lastSnapshot.id)),
|
||||
"Only recent snapshot is selected");
|
||||
}
|
||||
}
|
||||
|
||||
store.dispatch(actions.takeSnapshot(front));
|
||||
|
||||
yield waitUntilState(store, () => foundPendingState && foundDoneState);
|
||||
|
||||
for (let i = 0; i < 4; i++) {
|
||||
store.dispatch(actions.takeSnapshot(front));
|
||||
yield waitUntilState(store, () => foundPendingState && foundDoneState);
|
||||
|
||||
// reset state trackers
|
||||
foundDoneState = foundPendingState = false;
|
||||
}
|
||||
|
||||
yield waitUntilState(store, () => foundAllSnapshots);
|
||||
unsubscribe();
|
||||
});
|
||||
|
@ -6,4 +6,6 @@ firefox-appdir = browser
|
||||
skip-if = toolkit == 'android' || toolkit == 'gonk'
|
||||
|
||||
[test_action-select-snapshot.js]
|
||||
[test_action-take-census.js]
|
||||
[test_action-take-snapshot.js]
|
||||
[test_action-take-snapshot-and-census.js]
|
||||
|
68
devtools/client/memory/utils.js
Normal file
68
devtools/client/memory/utils.js
Normal file
@ -0,0 +1,68 @@
|
||||
const DevToolsUtils = require("devtools/shared/DevToolsUtils");
|
||||
const { snapshotState: states } = require("./constants");
|
||||
const SAVING_SNAPSHOT_TEXT = "Saving snapshot...";
|
||||
const READING_SNAPSHOT_TEXT = "Reading snapshot...";
|
||||
const SAVING_CENSUS_TEXT = "Taking heap census...";
|
||||
|
||||
// @TODO 1215606
|
||||
// Use DevToolsUtils.assert when fixed.
|
||||
exports.assert = function (condition, message) {
|
||||
if (!condition) {
|
||||
const err = new Error("Assertion failure: " + message);
|
||||
DevToolsUtils.reportException("DevToolsUtils.assert", err);
|
||||
throw err;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns a string representing a readable form of the snapshot's state.
|
||||
*
|
||||
* @param {Snapshot} snapshot
|
||||
* @return {String}
|
||||
*/
|
||||
exports.getSnapshotStatusText = function (snapshot) {
|
||||
switch (snapshot && snapshot.state) {
|
||||
case states.SAVING:
|
||||
return SAVING_SNAPSHOT_TEXT;
|
||||
case states.SAVED:
|
||||
case states.READING:
|
||||
return READING_SNAPSHOT_TEXT;
|
||||
case states.READ:
|
||||
case states.SAVING_CENSUS:
|
||||
return SAVING_CENSUS_TEXT;
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
/**
|
||||
* Takes an array of snapshots and a snapshot and returns
|
||||
* the snapshot instance in `snapshots` that matches
|
||||
* the snapshot passed in.
|
||||
*
|
||||
* @param {Array<Snapshot>} snapshots
|
||||
* @param {Snapshot}
|
||||
* @return ?Snapshot
|
||||
*/
|
||||
exports.getSnapshot = function getSnapshot (snapshots, snapshot) {
|
||||
let found = snapshots.find(s => s.id === snapshot.id);
|
||||
if (!found) {
|
||||
DevToolsUtils.reportException(`No matching snapshot found for ${snapshot.id}`);
|
||||
}
|
||||
return found || null;
|
||||
};
|
||||
|
||||
/**
|
||||
* Creates a new snapshot object.
|
||||
*
|
||||
* @return {Snapshot}
|
||||
*/
|
||||
let INC_ID = 0;
|
||||
exports.createSnapshot = function createSnapshot () {
|
||||
let id = ++INC_ID;
|
||||
return {
|
||||
id,
|
||||
state: states.SAVING,
|
||||
census: null,
|
||||
path: null,
|
||||
};
|
||||
};
|
@ -20,6 +20,11 @@
|
||||
--row-hover-background-color: rgba(76,158,217,0.2);
|
||||
}
|
||||
|
||||
#memory-tool-container {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
/**
|
||||
* TODO bug 1213100
|
||||
* should generalize toolbar buttons with images in them
|
||||
@ -103,6 +108,35 @@
|
||||
color: var(--theme-selection-color);
|
||||
}
|
||||
|
||||
.snapshot-list-item span {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.snapshot-list-item .snapshot-state {
|
||||
font-size: 90%;
|
||||
color: var(--theme-body-color-alt);
|
||||
}
|
||||
|
||||
.snapshot-list-item.selected .snapshot-state {
|
||||
/* Text inside a selected item should not be custom colored. */
|
||||
color: inherit !important;
|
||||
}
|
||||
|
||||
/**
|
||||
* Main panel
|
||||
*/
|
||||
|
||||
#heap-view {
|
||||
flex: 1 1 auto;
|
||||
}
|
||||
|
||||
#heap-view .heap-view-panel {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
#heap-view .take-snapshot {
|
||||
}
|
||||
|
||||
/**
|
||||
* Heap View
|
||||
|
Loading…
Reference in New Issue
Block a user