Bug 1214872 - Set up state changes in the memory tool. r=fitzgen

This commit is contained in:
Jordan Santell 2015-10-14 23:13:17 -07:00
parent 82b855996a
commit f0d14e473f
23 changed files with 524 additions and 116 deletions

View File

@ -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
};
};

View File

@ -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))
}),
])
])
);
},

View 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)
)
}
});

View File

@ -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',

View File

@ -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
)
);
}

View File

@ -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";

View File

@ -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);
});

View File

@ -17,6 +17,7 @@ DevToolsModules(
'panel.js',
'reducers.js',
'store.js',
'utils.js',
)
BROWSER_CHROME_MANIFESTS += ['test/browser/browser.ini']

View File

@ -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;

View File

@ -1 +1,3 @@
exports.snapshots = require("./reducers/snapshot");
exports.snapshots = require("./reducers/snapshots");
exports.breakdown = require("./reducers/breakdown");
exports.errors = require("./reducers/errors");

View 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);
};

View 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;
};

View File

@ -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',
)

View File

@ -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;
};

View 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;
};

View File

@ -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) {

View File

@ -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; }

View 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");
});

View File

@ -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");
});

View File

@ -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();
});

View File

@ -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]

View 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,
};
};

View File

@ -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