зеркало из https://github.com/mozilla/gecko-dev.git
Merge fx-team to m-c. a=merge
This commit is contained in:
Коммит
5e083ec726
|
@ -239,6 +239,7 @@ Tools.performance = {
|
|||
ordinal: 7,
|
||||
icon: "chrome://browser/skin/devtools/tool-profiler.svg",
|
||||
invertIconForLightTheme: true,
|
||||
highlightedicon: "chrome://browser/skin/devtools/tool-profiler-active.svg",
|
||||
url: "chrome://browser/content/devtools/performance.xul",
|
||||
visibilityswitch: "devtools.performance.enabled",
|
||||
label: l10n("profiler.label2", profilerStrings),
|
||||
|
|
|
@ -2264,6 +2264,7 @@ function TextEditor(aContainer, aNode, aTemplate) {
|
|||
stopOnReturn: true,
|
||||
trigger: "dblclick",
|
||||
multiline: true,
|
||||
trimOutput: false,
|
||||
done: (aVal, aCommit) => {
|
||||
if (!aCommit) {
|
||||
return;
|
||||
|
|
|
@ -37,7 +37,8 @@ const DEFAULT_ALLOCATION_SITES_PULL_TIMEOUT = 200; // ms
|
|||
// Events to pipe from PerformanceActorsConnection to the PerformanceFront
|
||||
const CONNECTION_PIPE_EVENTS = [
|
||||
"console-profile-start", "console-profile-ending", "console-profile-end",
|
||||
"timeline-data", "profiler-already-active", "profiler-activated"
|
||||
"timeline-data", "profiler-already-active", "profiler-activated",
|
||||
"recording-started", "recording-stopped"
|
||||
];
|
||||
|
||||
// Events to listen to from the profiler actor
|
||||
|
@ -100,9 +101,9 @@ function PerformanceActorsConnection(target) {
|
|||
|
||||
PerformanceActorsConnection.prototype = {
|
||||
|
||||
// Properties set when mocks are being used
|
||||
_usingMockMemory: false,
|
||||
_usingMockTimeline: false,
|
||||
// Properties set based off of server actor support
|
||||
_memorySupported: true,
|
||||
_timelineSupported: true,
|
||||
|
||||
/**
|
||||
* Initializes a connection to the profiler and other miscellaneous actors.
|
||||
|
@ -174,9 +175,9 @@ PerformanceActorsConnection.prototype = {
|
|||
if (supported) {
|
||||
this._timeline = new TimelineFront(this._target.client, this._target.form);
|
||||
} else {
|
||||
this._usingMockTimeline = true;
|
||||
this._timeline = new compatibility.MockTimelineFront();
|
||||
}
|
||||
this._timelineSupported = supported;
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -187,9 +188,9 @@ PerformanceActorsConnection.prototype = {
|
|||
if (supported) {
|
||||
this._memory = new MemoryFront(this._target.client, this._target.form);
|
||||
} else {
|
||||
this._usingMockMemory = true;
|
||||
this._memory = new compatibility.MockMemoryFront();
|
||||
}
|
||||
this._memorySupported = supported;
|
||||
}),
|
||||
|
||||
/**
|
||||
|
@ -413,6 +414,7 @@ PerformanceActorsConnection.prototype = {
|
|||
model.populate(data);
|
||||
this._recordings.push(model);
|
||||
|
||||
this.emit("recording-started", model);
|
||||
return model;
|
||||
}),
|
||||
|
||||
|
@ -467,6 +469,7 @@ PerformanceActorsConnection.prototype = {
|
|||
memoryEndTime: memoryEndTime
|
||||
});
|
||||
|
||||
this.emit("recording-stopped", model);
|
||||
return model;
|
||||
}),
|
||||
|
||||
|
@ -620,8 +623,8 @@ function PerformanceFront(connection) {
|
|||
this._request = connection._request;
|
||||
|
||||
// Set when mocks are being used
|
||||
this._usingMockMemory = connection._usingMockMemory;
|
||||
this._usingMockTimeline = connection._usingMockTimeline;
|
||||
this._memorySupported = connection._memorySupported;
|
||||
this._timelineSupported = connection._timelineSupported;
|
||||
|
||||
// Pipe the console profile events from the connection
|
||||
// to the front so that the UI can listen.
|
||||
|
@ -636,7 +639,8 @@ PerformanceFront.prototype = {
|
|||
*
|
||||
* @param object options
|
||||
* An options object to pass to the actors. Supported properties are
|
||||
* `withTicks`, `withMemory` and `withAllocations`, `probability` and `maxLogLength`.
|
||||
* `withTicks`, `withMemory` and `withAllocations`,
|
||||
* `probability` and `maxLogLength`.
|
||||
* @return object
|
||||
* A promise that is resolved once recording has started.
|
||||
*/
|
||||
|
@ -658,13 +662,24 @@ PerformanceFront.prototype = {
|
|||
},
|
||||
|
||||
/**
|
||||
* Returns an object indicating if mock actors are being used or not.
|
||||
* Returns an object indicating what server actors are available and
|
||||
* initialized. A falsy value indicates that the server does not support
|
||||
* that feature, or that mock actors were explicitly requested (tests).
|
||||
*/
|
||||
getMocksInUse: function () {
|
||||
getActorSupport: function () {
|
||||
return {
|
||||
memory: this._usingMockMemory,
|
||||
timeline: this._usingMockTimeline
|
||||
memory: this._memorySupported,
|
||||
timeline: this._timelineSupported
|
||||
};
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns a boolean indicating whether or not the current performance connection is recording.
|
||||
*
|
||||
* @return Boolean
|
||||
*/
|
||||
isRecording: function () {
|
||||
return this._connection.isRecording();
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -673,6 +688,7 @@ PerformanceFront.prototype = {
|
|||
*/
|
||||
function getRecordingModelPrefs () {
|
||||
return {
|
||||
withMarkers: true,
|
||||
withMemory: Services.prefs.getBoolPref("devtools.performance.ui.enable-memory"),
|
||||
withTicks: Services.prefs.getBoolPref("devtools.performance.ui.enable-framerate"),
|
||||
withAllocations: Services.prefs.getBoolPref("devtools.performance.ui.enable-memory"),
|
||||
|
|
|
@ -0,0 +1,409 @@
|
|||
/* 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";
|
||||
|
||||
/**
|
||||
* This file contains the base line graph that all Performance line graphs use.
|
||||
*/
|
||||
|
||||
const {Cc, Ci, Cu, Cr} = require("chrome");
|
||||
|
||||
Cu.import("resource:///modules/devtools/Graphs.jsm");
|
||||
Cu.import("resource:///modules/devtools/ViewHelpers.jsm");
|
||||
Cu.import("resource://gre/modules/Task.jsm");
|
||||
Cu.import("resource://gre/modules/Promise.jsm");
|
||||
|
||||
const { colorUtils: { setAlpha }} = require("devtools/css-color");
|
||||
const { getColor } = require("devtools/shared/theme");
|
||||
|
||||
loader.lazyRequireGetter(this, "ProfilerGlobal",
|
||||
"devtools/shared/profiler/global");
|
||||
loader.lazyRequireGetter(this, "TimelineGlobal",
|
||||
"devtools/shared/timeline/global");
|
||||
loader.lazyRequireGetter(this, "MarkersOverview",
|
||||
"devtools/shared/timeline/markers-overview", true);
|
||||
loader.lazyRequireGetter(this, "EventEmitter",
|
||||
"devtools/toolkit/event-emitter");
|
||||
|
||||
/**
|
||||
* For line graphs
|
||||
*/
|
||||
const HEIGHT = 35; // px
|
||||
const STROKE_WIDTH = 1; // px
|
||||
const DAMPEN_VALUES = 0.95;
|
||||
const CLIPHEAD_LINE_COLOR = "#666";
|
||||
const SELECTION_LINE_COLOR = "#555";
|
||||
const SELECTION_BACKGROUND_COLOR_NAME = "highlight-blue";
|
||||
const FRAMERATE_GRAPH_COLOR_NAME = "highlight-green";
|
||||
const MEMORY_GRAPH_COLOR_NAME = "highlight-blue";
|
||||
|
||||
/**
|
||||
* For timeline overview
|
||||
*/
|
||||
const MARKERS_GRAPH_HEADER_HEIGHT = 14; // px
|
||||
const MARKERS_GRAPH_ROW_HEIGHT = 10; // px
|
||||
const MARKERS_GROUP_VERTICAL_PADDING = 4; // px
|
||||
|
||||
/**
|
||||
* A base class for performance graphs to inherit from.
|
||||
*
|
||||
* @param nsIDOMNode parent
|
||||
* The parent node holding the overview.
|
||||
* @param string metric
|
||||
* The unit of measurement for this graph.
|
||||
*/
|
||||
function PerformanceGraph(parent, metric) {
|
||||
LineGraphWidget.call(this, parent, { metric });
|
||||
this.setTheme();
|
||||
}
|
||||
|
||||
PerformanceGraph.prototype = Heritage.extend(LineGraphWidget.prototype, {
|
||||
strokeWidth: STROKE_WIDTH,
|
||||
dampenValuesFactor: DAMPEN_VALUES,
|
||||
fixedHeight: HEIGHT,
|
||||
clipheadLineColor: CLIPHEAD_LINE_COLOR,
|
||||
selectionLineColor: SELECTION_LINE_COLOR,
|
||||
withTooltipArrows: false,
|
||||
withFixedTooltipPositions: true,
|
||||
|
||||
/**
|
||||
* Disables selection and empties this graph.
|
||||
*/
|
||||
clearView: function() {
|
||||
this.selectionEnabled = false;
|
||||
this.dropSelection();
|
||||
this.setData([]);
|
||||
},
|
||||
|
||||
/**
|
||||
* Sets the theme via `theme` to either "light" or "dark",
|
||||
* and updates the internal styling to match. Requires a redraw
|
||||
* to see the effects.
|
||||
*/
|
||||
setTheme: function (theme) {
|
||||
theme = theme || "light";
|
||||
let mainColor = getColor(this.mainColor || "highlight-blue", theme);
|
||||
this.backgroundColor = getColor("body-background", theme);
|
||||
this.strokeColor = mainColor;
|
||||
this.backgroundGradientStart = setAlpha(mainColor, 0.2);
|
||||
this.backgroundGradientEnd = setAlpha(mainColor, 0.2);
|
||||
this.selectionBackgroundColor = setAlpha(getColor(SELECTION_BACKGROUND_COLOR_NAME, theme), 0.25);
|
||||
this.selectionStripesColor = "rgba(255, 255, 255, 0.1)";
|
||||
this.maximumLineColor = setAlpha(mainColor, 0.4);
|
||||
this.averageLineColor = setAlpha(mainColor, 0.7);
|
||||
this.minimumLineColor = setAlpha(mainColor, 0.9);
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Constructor for the framerate graph. Inherits from PerformanceGraph.
|
||||
*
|
||||
* @param nsIDOMNode parent
|
||||
* The parent node holding the overview.
|
||||
*/
|
||||
function FramerateGraph(parent) {
|
||||
PerformanceGraph.call(this, parent, ProfilerGlobal.L10N.getStr("graphs.fps"));
|
||||
}
|
||||
|
||||
FramerateGraph.prototype = Heritage.extend(PerformanceGraph.prototype, {
|
||||
mainColor: FRAMERATE_GRAPH_COLOR_NAME,
|
||||
setPerformanceData: function ({ duration, ticks }, resolution) {
|
||||
this.dataDuration = duration;
|
||||
return this.setDataFromTimestamps(ticks, resolution, duration);
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Constructor for the memory graph. Inherits from PerformanceGraph.
|
||||
*
|
||||
* @param nsIDOMNode parent
|
||||
* The parent node holding the overview.
|
||||
*/
|
||||
function MemoryGraph(parent) {
|
||||
PerformanceGraph.call(this, parent, TimelineGlobal.L10N.getStr("graphs.memory"));
|
||||
}
|
||||
|
||||
MemoryGraph.prototype = Heritage.extend(PerformanceGraph.prototype, {
|
||||
mainColor: MEMORY_GRAPH_COLOR_NAME,
|
||||
setPerformanceData: function ({ duration, memory }) {
|
||||
this.dataDuration = duration;
|
||||
return this.setData(memory);
|
||||
}
|
||||
});
|
||||
|
||||
function TimelineGraph(parent, blueprint) {
|
||||
MarkersOverview.call(this, parent, blueprint);
|
||||
}
|
||||
|
||||
TimelineGraph.prototype = Heritage.extend(MarkersOverview.prototype, {
|
||||
headerHeight: MARKERS_GRAPH_HEADER_HEIGHT,
|
||||
rowHeight: MARKERS_GRAPH_ROW_HEIGHT,
|
||||
groupPadding: MARKERS_GROUP_VERTICAL_PADDING,
|
||||
setPerformanceData: MarkersOverview.prototype.setData
|
||||
});
|
||||
|
||||
/**
|
||||
* Definitions file for GraphsController, indicating the constructor,
|
||||
* selector and other meta for each of the graphs controller by
|
||||
* GraphsController.
|
||||
*/
|
||||
const GRAPH_DEFINITIONS = {
|
||||
memory: {
|
||||
constructor: MemoryGraph,
|
||||
selector: "#memory-overview",
|
||||
},
|
||||
framerate: {
|
||||
constructor: FramerateGraph,
|
||||
selector: "#time-framerate",
|
||||
},
|
||||
timeline: {
|
||||
constructor: TimelineGraph,
|
||||
selector: "#markers-overview",
|
||||
needsBlueprints: true,
|
||||
primaryLink: true
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* A controller for orchestrating the performance's tool overview graphs. Constructs,
|
||||
* syncs, toggles displays and defines the memory, framerate and timeline view.
|
||||
*
|
||||
* @param {object} definition
|
||||
* @param {DOMElement} root
|
||||
* @param {function} getBlueprint
|
||||
* @param {function} getTheme
|
||||
*/
|
||||
function GraphsController ({ definition, root, getBlueprint, getTheme }) {
|
||||
this._graphs = {};
|
||||
this._enabled = new Set();
|
||||
this._definition = definition || GRAPH_DEFINITIONS;
|
||||
this._root = root;
|
||||
this._getBlueprint = getBlueprint;
|
||||
this._getTheme = getTheme;
|
||||
this._primaryLink = Object.keys(this._definition).filter(name => this._definition[name].primaryLink)[0];
|
||||
this.$ = root.ownerDocument.querySelector.bind(root.ownerDocument);
|
||||
|
||||
EventEmitter.decorate(this);
|
||||
this._onSelecting = this._onSelecting.bind(this);
|
||||
}
|
||||
|
||||
GraphsController.prototype = {
|
||||
|
||||
/**
|
||||
* Returns the corresponding graph by `graphName`.
|
||||
*/
|
||||
get: function (graphName) {
|
||||
return this._graphs[graphName];
|
||||
},
|
||||
|
||||
/**
|
||||
* Iterates through all graphs and renders the data
|
||||
* from a RecordingModel. Takes a resolution value used in
|
||||
* some graphs.
|
||||
* Saves rendering progress as a promise to be consumed by `destroy`,
|
||||
* to wait for cleaning up rendering during destruction.
|
||||
*/
|
||||
render: Task.async(function *(recordingData, resolution) {
|
||||
// Get the previous render promise so we don't start rendering
|
||||
// until the previous render cycle completes, which can occur
|
||||
// especially when a recording is finished, and triggers a
|
||||
// fresh rendering at a higher rate
|
||||
yield (this._rendering && this._rendering.promise);
|
||||
|
||||
// Check after yielding to ensure we're not tearing down,
|
||||
// as this can create a race condition in tests
|
||||
if (this._destroyed) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._rendering = Promise.defer();
|
||||
for (let graph of (yield this._getEnabled())) {
|
||||
yield graph.setPerformanceData(recordingData, resolution);
|
||||
this.emit("rendered", graph.graphName);
|
||||
}
|
||||
this._rendering.resolve();
|
||||
}),
|
||||
|
||||
/**
|
||||
* Destroys the underlying graphs.
|
||||
*/
|
||||
destroy: Task.async(function *() {
|
||||
let primary = this._getPrimaryLink();
|
||||
|
||||
this._destroyed = true;
|
||||
|
||||
if (primary) {
|
||||
primary.off("selecting", this._onSelecting);
|
||||
}
|
||||
|
||||
// If there was rendering, wait until the most recent render cycle
|
||||
// has finished
|
||||
if (this._rendering) {
|
||||
yield this._rendering.promise;
|
||||
}
|
||||
|
||||
for (let graphName in this._graphs) {
|
||||
yield this._graphs[graphName].destroy();
|
||||
}
|
||||
}),
|
||||
|
||||
/**
|
||||
* Applies the theme to the underlying graphs. Optionally takes
|
||||
* a `redraw` boolean in the options to force redraw.
|
||||
*/
|
||||
setTheme: function (options={}) {
|
||||
let theme = options.theme || this._getTheme();
|
||||
for (let graph in this._graphs) {
|
||||
this._graphs[graph].setTheme(theme);
|
||||
this._graphs[graph].refresh({ force: options.redraw });
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Sets up the graph, if needed. Returns a promise resolving
|
||||
* to the graph if it is enabled once it's ready, or otherwise returns
|
||||
* null if disabled.
|
||||
*/
|
||||
isAvailable: Task.async(function *(graphName) {
|
||||
if (!this._enabled.has(graphName)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
let graph = this.get(graphName);
|
||||
|
||||
if (!graph) {
|
||||
graph = yield this._construct(graphName);
|
||||
}
|
||||
|
||||
yield graph.ready();
|
||||
return graph;
|
||||
}),
|
||||
|
||||
/**
|
||||
* Enable or disable a subgraph controlled by GraphsController.
|
||||
* This determines what graphs are visible and get rendered.
|
||||
*/
|
||||
enable: function (graphName, isEnabled) {
|
||||
let el = this.$(this._definition[graphName].selector);
|
||||
el.hidden = !isEnabled;
|
||||
|
||||
// If no status change, just return
|
||||
if (this._enabled.has(graphName) === isEnabled) {
|
||||
return;
|
||||
}
|
||||
if (isEnabled) {
|
||||
this._enabled.add(graphName);
|
||||
} else {
|
||||
this._enabled.delete(graphName);
|
||||
}
|
||||
|
||||
// Invalidate our cache of ready-to-go graphs
|
||||
this._enabledGraphs = null;
|
||||
},
|
||||
|
||||
/**
|
||||
* Disables all graphs controller by the GraphsController, and
|
||||
* also hides the root element. This is a one way switch, and used
|
||||
* when older platforms do not have any timeline data.
|
||||
*/
|
||||
disableAll: function () {
|
||||
this._root.hidden = true;
|
||||
// Hide all the subelements
|
||||
Object.keys(this._definition).forEach(graphName => this.enable(graphName, false));
|
||||
},
|
||||
|
||||
/**
|
||||
* Sets a mapped selection on the graph that is the main controller
|
||||
* for keeping the graphs' selections in sync.
|
||||
*/
|
||||
setMappedSelection: function (selection, { mapStart, mapEnd }) {
|
||||
return this._getPrimaryLink().setMappedSelection(selection, { mapStart, mapEnd });
|
||||
},
|
||||
|
||||
getMappedSelection: function ({ mapStart, mapEnd }) {
|
||||
return this._getPrimaryLink().getMappedSelection({ mapStart, mapEnd });
|
||||
},
|
||||
|
||||
/**
|
||||
* Drops the selection.
|
||||
*/
|
||||
dropSelection: function () {
|
||||
if (this._getPrimaryLink()) {
|
||||
return this._getPrimaryLink().dropSelection();
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Makes sure the selection is enabled or disabled in all the graphs.
|
||||
*/
|
||||
selectionEnabled: Task.async(function *(enabled) {
|
||||
for (let graph of (yield this._getEnabled())) {
|
||||
graph.selectionEnabled = enabled;
|
||||
}
|
||||
}),
|
||||
|
||||
/**
|
||||
* Creates the graph `graphName` and initializes it.
|
||||
*/
|
||||
_construct: Task.async(function *(graphName) {
|
||||
let def = this._definition[graphName];
|
||||
let el = this.$(def.selector);
|
||||
let blueprint = def.needsBlueprints ? this._getBlueprint() : void 0;
|
||||
let graph = this._graphs[graphName] = new def.constructor(el, blueprint);
|
||||
graph.graphName = graphName;
|
||||
|
||||
yield graph.ready();
|
||||
|
||||
// Sync the graphs' animations and selections together
|
||||
if (def.primaryLink) {
|
||||
graph.on("selecting", this._onSelecting);
|
||||
} else {
|
||||
CanvasGraphUtils.linkAnimation(this._getPrimaryLink(), graph);
|
||||
CanvasGraphUtils.linkSelection(this._getPrimaryLink(), graph);
|
||||
}
|
||||
|
||||
this.setTheme();
|
||||
return graph;
|
||||
}),
|
||||
|
||||
/**
|
||||
* Returns the main graph for this collection, that all graphs
|
||||
* are bound to for syncing and selection.
|
||||
*/
|
||||
_getPrimaryLink: function () {
|
||||
return this.get(this._primaryLink);
|
||||
},
|
||||
|
||||
/**
|
||||
* Emitted when a selection occurs.
|
||||
*/
|
||||
_onSelecting: function () {
|
||||
this.emit("selecting");
|
||||
},
|
||||
|
||||
/**
|
||||
* Resolves to an array with all graphs that are enabled, and
|
||||
* creates them if needed. Different than just iterating over `this._graphs`,
|
||||
* as those could be enabled. Uses caching, as rendering happens many times per second,
|
||||
* compared to how often which graphs/features are changed (rarely).
|
||||
*/
|
||||
_getEnabled: Task.async(function *() {
|
||||
if (this._enabledGraphs) {
|
||||
return this._enabledGraphs;
|
||||
}
|
||||
let enabled = [];
|
||||
for (let graphName of this._enabled) {
|
||||
let graph;
|
||||
if (graph = yield this.isAvailable(graphName)) {
|
||||
enabled.push(graph);
|
||||
}
|
||||
}
|
||||
return this._enabledGraphs = enabled;
|
||||
}),
|
||||
};
|
||||
|
||||
exports.FramerateGraph = FramerateGraph;
|
||||
exports.MemoryGraph = MemoryGraph;
|
||||
exports.TimelineGraph = TimelineGraph;
|
||||
exports.GraphsController = GraphsController;
|
|
@ -148,7 +148,15 @@ function convertLegacyData (legacyData) {
|
|||
memory: [],
|
||||
ticks: ticksData,
|
||||
allocations: { sites: [], timestamps: [], frames: [], counts: [] },
|
||||
profile: profilerData.profile
|
||||
profile: profilerData.profile,
|
||||
// Fake a configuration object here if there's tick data,
|
||||
// so that it can be rendered
|
||||
configuration: {
|
||||
withTicks: !!ticksData.length,
|
||||
withMarkers: false,
|
||||
withMemory: false,
|
||||
withAllocations: false
|
||||
}
|
||||
};
|
||||
|
||||
return data;
|
||||
|
|
|
@ -1,113 +0,0 @@
|
|||
/* 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";
|
||||
|
||||
/**
|
||||
* This file contains the base line graph that all Performance line graphs use.
|
||||
*/
|
||||
|
||||
const {Cc, Ci, Cu, Cr} = require("chrome");
|
||||
|
||||
Cu.import("resource:///modules/devtools/Graphs.jsm");
|
||||
Cu.import("resource:///modules/devtools/ViewHelpers.jsm");
|
||||
|
||||
const { colorUtils: { setAlpha }} = require("devtools/css-color");
|
||||
const { getColor } = require("devtools/shared/theme");
|
||||
|
||||
loader.lazyRequireGetter(this, "ProfilerGlobal",
|
||||
"devtools/shared/profiler/global");
|
||||
loader.lazyRequireGetter(this, "TimelineGlobal",
|
||||
"devtools/shared/timeline/global");
|
||||
|
||||
const HEIGHT = 35; // px
|
||||
const STROKE_WIDTH = 1; // px
|
||||
const DAMPEN_VALUES = 0.95;
|
||||
const CLIPHEAD_LINE_COLOR = "#666";
|
||||
const SELECTION_LINE_COLOR = "#555";
|
||||
const SELECTION_BACKGROUND_COLOR_NAME = "highlight-blue";
|
||||
const FRAMERATE_GRAPH_COLOR_NAME = "highlight-green";
|
||||
const MEMORY_GRAPH_COLOR_NAME = "highlight-blue";
|
||||
|
||||
/**
|
||||
* A base class for performance graphs to inherit from.
|
||||
*
|
||||
* @param nsIDOMNode parent
|
||||
* The parent node holding the overview.
|
||||
* @param string metric
|
||||
* The unit of measurement for this graph.
|
||||
*/
|
||||
function PerformanceGraph(parent, metric) {
|
||||
LineGraphWidget.call(this, parent, { metric });
|
||||
this.setTheme();
|
||||
}
|
||||
|
||||
PerformanceGraph.prototype = Heritage.extend(LineGraphWidget.prototype, {
|
||||
strokeWidth: STROKE_WIDTH,
|
||||
dampenValuesFactor: DAMPEN_VALUES,
|
||||
fixedHeight: HEIGHT,
|
||||
clipheadLineColor: CLIPHEAD_LINE_COLOR,
|
||||
selectionLineColor: SELECTION_LINE_COLOR,
|
||||
withTooltipArrows: false,
|
||||
withFixedTooltipPositions: true,
|
||||
|
||||
/**
|
||||
* Disables selection and empties this graph.
|
||||
*/
|
||||
clearView: function() {
|
||||
this.selectionEnabled = false;
|
||||
this.dropSelection();
|
||||
this.setData([]);
|
||||
},
|
||||
|
||||
/**
|
||||
* Sets the theme via `theme` to either "light" or "dark",
|
||||
* and updates the internal styling to match. Requires a redraw
|
||||
* to see the effects.
|
||||
*/
|
||||
setTheme: function (theme) {
|
||||
theme = theme || "light";
|
||||
let mainColor = getColor(this.mainColor || "highlight-blue", theme);
|
||||
this.backgroundColor = getColor("body-background", theme);
|
||||
this.strokeColor = mainColor;
|
||||
this.backgroundGradientStart = setAlpha(mainColor, 0.2);
|
||||
this.backgroundGradientEnd = setAlpha(mainColor, 0.2);
|
||||
this.selectionBackgroundColor = setAlpha(getColor(SELECTION_BACKGROUND_COLOR_NAME, theme), 0.25);
|
||||
this.selectionStripesColor = "rgba(255, 255, 255, 0.1)";
|
||||
this.maximumLineColor = setAlpha(mainColor, 0.4);
|
||||
this.averageLineColor = setAlpha(mainColor, 0.7);
|
||||
this.minimumLineColor = setAlpha(mainColor, 0.9);
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Constructor for the framerate graph. Inherits from PerformanceGraph.
|
||||
*
|
||||
* @param nsIDOMNode parent
|
||||
* The parent node holding the overview.
|
||||
*/
|
||||
function FramerateGraph(parent) {
|
||||
PerformanceGraph.call(this, parent, ProfilerGlobal.L10N.getStr("graphs.fps"));
|
||||
}
|
||||
|
||||
FramerateGraph.prototype = Heritage.extend(PerformanceGraph.prototype, {
|
||||
mainColor: FRAMERATE_GRAPH_COLOR_NAME
|
||||
});
|
||||
|
||||
exports.FramerateGraph = FramerateGraph;
|
||||
|
||||
/**
|
||||
* Constructor for the memory graph. Inherits from PerformanceGraph.
|
||||
*
|
||||
* @param nsIDOMNode parent
|
||||
* The parent node holding the overview.
|
||||
*/
|
||||
function MemoryGraph(parent) {
|
||||
PerformanceGraph.call(this, parent, TimelineGlobal.L10N.getStr("graphs.memory"));
|
||||
}
|
||||
|
||||
MemoryGraph.prototype = Heritage.extend(PerformanceGraph.prototype, {
|
||||
mainColor: MEMORY_GRAPH_COLOR_NAME
|
||||
});
|
||||
|
||||
exports.MemoryGraph = MemoryGraph;
|
|
@ -22,6 +22,7 @@ const RecordingModel = function (options={}) {
|
|||
this._console = options.console || false;
|
||||
|
||||
this._configuration = {
|
||||
withMarkers: options.withMarkers || false,
|
||||
withTicks: options.withTicks || false,
|
||||
withMemory: options.withMemory || false,
|
||||
withAllocations: options.withAllocations || false,
|
||||
|
@ -70,6 +71,7 @@ RecordingModel.prototype = {
|
|||
this._ticks = recordingData.ticks;
|
||||
this._allocations = recordingData.allocations;
|
||||
this._profile = recordingData.profile;
|
||||
this._configuration = recordingData.configuration || {};
|
||||
}),
|
||||
|
||||
/**
|
||||
|
@ -226,7 +228,8 @@ RecordingModel.prototype = {
|
|||
let ticks = this.getTicks();
|
||||
let allocations = this.getAllocations();
|
||||
let profile = this.getProfile();
|
||||
return { label, duration, markers, frames, memory, ticks, allocations, profile };
|
||||
let configuration = this.getConfiguration();
|
||||
return { label, duration, markers, frames, memory, ticks, allocations, profile, configuration };
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -263,10 +266,13 @@ RecordingModel.prototype = {
|
|||
return;
|
||||
}
|
||||
|
||||
let config = this.getConfiguration();
|
||||
|
||||
switch (eventName) {
|
||||
// Accumulate timeline markers into an array. Furthermore, the timestamps
|
||||
// do not have a zero epoch, so offset all of them by the start time.
|
||||
case "markers": {
|
||||
if (!config.withMarkers) { break; }
|
||||
let [markers] = data;
|
||||
RecordingUtils.offsetMarkerTimes(markers, this._timelineStartTime);
|
||||
Array.prototype.push.apply(this._markers, markers);
|
||||
|
@ -274,6 +280,7 @@ RecordingModel.prototype = {
|
|||
}
|
||||
// Accumulate stack frames into an array.
|
||||
case "frames": {
|
||||
if (!config.withMarkers) { break; }
|
||||
let [, frames] = data;
|
||||
Array.prototype.push.apply(this._frames, frames);
|
||||
break;
|
||||
|
@ -281,6 +288,7 @@ RecordingModel.prototype = {
|
|||
// Accumulate memory measurements into an array. Furthermore, the timestamp
|
||||
// does not have a zero epoch, so offset it by the actor's start time.
|
||||
case "memory": {
|
||||
if (!config.withMemory) { break; }
|
||||
let [currentTime, measurement] = data;
|
||||
this._memory.push({
|
||||
delta: currentTime - this._timelineStartTime,
|
||||
|
@ -290,6 +298,7 @@ RecordingModel.prototype = {
|
|||
}
|
||||
// Save the accumulated refresh driver ticks.
|
||||
case "ticks": {
|
||||
if (!config.withTicks) { break; }
|
||||
let [, timestamps] = data;
|
||||
this._ticks = timestamps;
|
||||
break;
|
||||
|
@ -298,6 +307,7 @@ RecordingModel.prototype = {
|
|||
// do not have a zero epoch, and are microseconds instead of milliseconds,
|
||||
// so offset all of them by the start time, also converting from µs to ms.
|
||||
case "allocations": {
|
||||
if (!config.withAllocations) { break; }
|
||||
let [{ sites, timestamps, frames, counts }] = data;
|
||||
let timeOffset = this._memoryStartTime * 1000;
|
||||
let timeScale = 1000;
|
||||
|
|
|
@ -6,8 +6,8 @@
|
|||
EXTRA_JS_MODULES.devtools.performance += [
|
||||
'modules/compatibility.js',
|
||||
'modules/front.js',
|
||||
'modules/graphs.js',
|
||||
'modules/io.js',
|
||||
'modules/performance-graphs.js',
|
||||
'modules/recording-model.js',
|
||||
'modules/recording-utils.js',
|
||||
'panel.js'
|
||||
|
|
|
@ -34,6 +34,7 @@ PerformancePanel.prototype = {
|
|||
open: Task.async(function*() {
|
||||
this.panelWin.gToolbox = this._toolbox;
|
||||
this.panelWin.gTarget = this.target;
|
||||
this._onRecordingStartOrStop = this._onRecordingStartOrStop.bind(this);
|
||||
|
||||
// Connection is already created in the toolbox; reuse
|
||||
// the same connection.
|
||||
|
@ -43,6 +44,8 @@ PerformancePanel.prototype = {
|
|||
yield this._connection.open();
|
||||
|
||||
this.panelWin.gFront = new PerformanceFront(this._connection);
|
||||
this.panelWin.gFront.on("recording-started", this._onRecordingStartOrStop);
|
||||
this.panelWin.gFront.on("recording-stopped", this._onRecordingStartOrStop);
|
||||
|
||||
yield this.panelWin.startupPerformance();
|
||||
|
||||
|
@ -61,8 +64,19 @@ PerformancePanel.prototype = {
|
|||
return;
|
||||
}
|
||||
|
||||
this.panelWin.gFront.off("recording-started", this._onRecordingStartOrStop);
|
||||
this.panelWin.gFront.off("recording-stopped", this._onRecordingStartOrStop);
|
||||
yield this.panelWin.shutdownPerformance();
|
||||
this.emit("destroyed");
|
||||
this._destroyed = true;
|
||||
})
|
||||
}),
|
||||
|
||||
_onRecordingStartOrStop: function () {
|
||||
let front = this.panelWin.gFront;
|
||||
if (front.isRecording()) {
|
||||
this._toolbox.highlightTool("performance");
|
||||
} else {
|
||||
this._toolbox.unhighlightTool("performance");
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
|
@ -27,12 +27,8 @@ devtools.lazyRequireGetter(this, "RecordingUtils",
|
|||
"devtools/performance/recording-utils", true);
|
||||
devtools.lazyRequireGetter(this, "RecordingModel",
|
||||
"devtools/performance/recording-model", true);
|
||||
devtools.lazyRequireGetter(this, "FramerateGraph",
|
||||
"devtools/performance/performance-graphs", true);
|
||||
devtools.lazyRequireGetter(this, "MemoryGraph",
|
||||
"devtools/performance/performance-graphs", true);
|
||||
devtools.lazyRequireGetter(this, "MarkersOverview",
|
||||
"devtools/shared/timeline/markers-overview", true);
|
||||
devtools.lazyRequireGetter(this, "GraphsController",
|
||||
"devtools/performance/graphs", true);
|
||||
devtools.lazyRequireGetter(this, "Waterfall",
|
||||
"devtools/shared/timeline/waterfall", true);
|
||||
devtools.lazyRequireGetter(this, "MarkerDetails",
|
||||
|
@ -52,8 +48,6 @@ devtools.lazyRequireGetter(this, "FlameGraphUtils",
|
|||
devtools.lazyRequireGetter(this, "FlameGraph",
|
||||
"devtools/shared/widgets/FlameGraph", true);
|
||||
|
||||
devtools.lazyImporter(this, "CanvasGraphUtils",
|
||||
"resource:///modules/devtools/Graphs.jsm");
|
||||
devtools.lazyImporter(this, "SideMenuWidget",
|
||||
"resource:///modules/devtools/SideMenuWidget.jsm");
|
||||
devtools.lazyImporter(this, "PluralForm",
|
||||
|
@ -291,6 +285,7 @@ let PerformanceController = {
|
|||
*/
|
||||
startRecording: Task.async(function *() {
|
||||
let options = {
|
||||
withMarkers: true,
|
||||
withMemory: this.getOption("enable-memory"),
|
||||
withTicks: this.getOption("enable-framerate"),
|
||||
withAllocations: this.getOption("enable-memory"),
|
||||
|
@ -470,6 +465,42 @@ let PerformanceController = {
|
|||
return this._recordings;
|
||||
},
|
||||
|
||||
/**
|
||||
* Utility method taking the currently selected recording item's features, or optionally passed
|
||||
* in recording item, as well as the actor support on the server, returning a boolean
|
||||
* indicating if the requirements pass or not. Used to toggle features' visibility mostly.
|
||||
*
|
||||
* @option {Array<string>} features
|
||||
* An array of strings indicating what configuration is needed on the recording
|
||||
* model, like `withTicks`, or `withMemory`.
|
||||
* @option {Array<string>} actors
|
||||
* An array of strings indicating what actors must exist.
|
||||
* @option {boolean} isRecording
|
||||
* A boolean indicating whether the recording must be either recording or not
|
||||
* recording. Setting to undefined will allow either state.
|
||||
* @param {RecordingModel} recording
|
||||
* An optional recording model to use instead of the currently selected.
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
isFeatureSupported: function ({ features, actors, isRecording: shouldBeRecording }, recording) {
|
||||
recording = recording || this.getCurrentRecording();
|
||||
let recordingConfig = recording ? recording.getConfiguration() : {};
|
||||
let currentRecordingState = recording ? recording.isRecording() : void 0;
|
||||
let actorsSupported = gFront.getActorSupport();
|
||||
|
||||
if (shouldBeRecording != null && shouldBeRecording !== currentRecordingState) {
|
||||
return false;
|
||||
}
|
||||
if (actors && !actors.every(a => actorsSupported[a])) {
|
||||
return false;
|
||||
}
|
||||
if (features && !features.every(f => recordingConfig[f])) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
},
|
||||
|
||||
toString: () => "[object PerformanceController]"
|
||||
};
|
||||
|
||||
|
|
|
@ -102,22 +102,27 @@
|
|||
<toolbarbutton id="select-waterfall-view"
|
||||
class="devtools-toolbarbutton devtools-button"
|
||||
label="&profilerUI.toolbar.waterfall;"
|
||||
hidden="true"
|
||||
data-view="waterfall" />
|
||||
<toolbarbutton id="select-js-calltree-view"
|
||||
class="devtools-toolbarbutton devtools-button"
|
||||
label="&profilerUI.toolbar.js-calltree;"
|
||||
hidden="true"
|
||||
data-view="js-calltree" />
|
||||
<toolbarbutton id="select-js-flamegraph-view"
|
||||
class="devtools-toolbarbutton devtools-button"
|
||||
label="&profilerUI.toolbar.js-flamegraph;"
|
||||
hidden="true"
|
||||
data-view="js-flamegraph" />
|
||||
<toolbarbutton id="select-memory-calltree-view"
|
||||
class="devtools-toolbarbutton devtools-button"
|
||||
label="&profilerUI.toolbar.memory-calltree1;"
|
||||
hidden="true"
|
||||
data-view="memory-calltree" />
|
||||
<toolbarbutton id="select-memory-flamegraph-view"
|
||||
class="devtools-toolbarbutton devtools-button"
|
||||
label="&profilerUI.toolbar.memory-flamegraph1;"
|
||||
hidden="true"
|
||||
data-view="memory-flamegraph" />
|
||||
</hbox>
|
||||
<spacer flex="1"></spacer>
|
||||
|
|
|
@ -11,9 +11,8 @@ support-files =
|
|||
# that need to be moved over to performance tool
|
||||
|
||||
[browser_perf-aaa-run-first-leaktest.js]
|
||||
|
||||
[browser_markers-gc.js]
|
||||
[browser_markers-parse-html.js]
|
||||
|
||||
[browser_perf-allocations-to-samples.js]
|
||||
[browser_perf-compatibility-01.js]
|
||||
[browser_perf-compatibility-02.js]
|
||||
|
@ -43,6 +42,7 @@ support-files =
|
|||
[browser_perf-details-02.js]
|
||||
[browser_perf-details-03.js]
|
||||
[browser_perf-details-04.js]
|
||||
[browser_perf-details-05.js]
|
||||
[browser_perf-events-calltree.js]
|
||||
[browser_perf-front-basic-profiler-01.js]
|
||||
[browser_perf-front-basic-timeline-01.js]
|
||||
|
@ -54,6 +54,7 @@ support-files =
|
|||
#[browser_perf-front-profiler-06.js]
|
||||
[browser_perf-front-01.js]
|
||||
[browser_perf-front-02.js]
|
||||
[browser_perf-highlighted.js]
|
||||
[browser_perf-jit-view-01.js]
|
||||
[browser_perf-jit-view-02.js]
|
||||
[browser_perf-jit-model-01.js]
|
||||
|
|
|
@ -0,0 +1,47 @@
|
|||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
/**
|
||||
* Test that we get a "GarbageCollection" marker.
|
||||
*/
|
||||
|
||||
const TIME_CLOSE_TO = 10000;
|
||||
|
||||
function* spawnTest () {
|
||||
let { target, front } = yield initBackend(SIMPLE_URL);
|
||||
let markers;
|
||||
|
||||
front.on("timeline-data", handler);
|
||||
let model = yield front.startRecording({ withTicks: true });
|
||||
|
||||
// Check async for markers found while GC/CCing between
|
||||
yield waitUntil(() => {
|
||||
forceCC();
|
||||
return !!markers;
|
||||
}, 100);
|
||||
|
||||
front.off("timeline-data", handler);
|
||||
yield front.stopRecording(model);
|
||||
|
||||
info(`Got ${markers.length} markers.`);
|
||||
|
||||
let maxMarkerTime = model._timelineStartTime + model.getDuration() + TIME_CLOSE_TO;
|
||||
|
||||
ok(markers.every(({name}) => name === "GarbageCollection"), "All markers found are GC markers");
|
||||
ok(markers.length > 0, "found atleast one GC marker");
|
||||
ok(markers.every(({start}) => typeof start === "number" && start > 0 && start < maxMarkerTime),
|
||||
"All markers have a start time between the valid range.");
|
||||
ok(markers.every(({end}) => typeof end === "number" && end > 0 && end < maxMarkerTime),
|
||||
"All markers have an end time between the valid range.");
|
||||
ok(markers.every(({causeName}) => typeof causeName === "string"),
|
||||
"All markers have a causeName.");
|
||||
|
||||
yield removeTab(target.tab);
|
||||
finish();
|
||||
|
||||
function handler (_, name, m) {
|
||||
if (name === "markers" && m[0].name === "GarbageCollection") {
|
||||
markers = m;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -16,7 +16,7 @@ function* getMarkers(front) {
|
|||
};
|
||||
front.on("timeline-data", handler);
|
||||
|
||||
yield front.startRecording({ withTicks: true });
|
||||
yield front.startRecording({ withMarkers: true, withTicks: true });
|
||||
|
||||
const markers = yield promise;
|
||||
front.off("timeline-data", handler);
|
||||
|
|
|
@ -11,14 +11,13 @@ function spawnTest () {
|
|||
// Enable platform data to show the `busyWait` function in the tree.
|
||||
Services.prefs.setBoolPref(PLATFORM_DATA_PREF, true);
|
||||
|
||||
yield DetailsView.selectView("js-calltree");
|
||||
ok(DetailsView.isViewSelected(JsCallTreeView), "The call tree is now selected.");
|
||||
|
||||
yield startRecording(panel);
|
||||
yield busyWait(1000);
|
||||
|
||||
let rendered = once(JsCallTreeView, EVENTS.JS_CALL_TREE_RENDERED);
|
||||
yield stopRecording(panel);
|
||||
yield DetailsView.selectView("js-calltree");
|
||||
ok(DetailsView.isViewSelected(JsCallTreeView), "The call tree is now selected.");
|
||||
yield rendered;
|
||||
|
||||
testCells($, $$, {
|
||||
|
|
|
@ -11,14 +11,13 @@ function spawnTest () {
|
|||
// Enable memory to test.
|
||||
Services.prefs.setBoolPref(MEMORY_PREF, true);
|
||||
|
||||
yield DetailsView.selectView("memory-calltree");
|
||||
ok(DetailsView.isViewSelected(MemoryCallTreeView), "The call tree is now selected.");
|
||||
|
||||
yield startRecording(panel);
|
||||
yield busyWait(1000);
|
||||
|
||||
let rendered = once(MemoryCallTreeView, EVENTS.MEMORY_CALL_TREE_RENDERED);
|
||||
yield stopRecording(panel);
|
||||
yield DetailsView.selectView("memory-calltree");
|
||||
ok(DetailsView.isViewSelected(MemoryCallTreeView), "The call tree is now selected.");
|
||||
yield rendered;
|
||||
|
||||
testCells($, $$, {
|
||||
|
|
|
@ -14,12 +14,13 @@ function spawnTest () {
|
|||
});
|
||||
Services.prefs.setBoolPref(MEMORY_PREF, true);
|
||||
|
||||
let { memory, timeline } = front.getMocksInUse();
|
||||
ok(memory, "memory should be mocked.");
|
||||
ok(timeline, "timeline should be mocked.");
|
||||
let { memory, timeline } = front.getActorSupport();
|
||||
ok(!memory, "memory should be mocked.");
|
||||
ok(!timeline, "timeline should be mocked.");
|
||||
|
||||
let recording = yield front.startRecording({
|
||||
withTicks: true,
|
||||
withMarkers: true,
|
||||
withMemory: true,
|
||||
withAllocations: true,
|
||||
allocationsSampleProbability: +Services.prefs.getCharPref(MEMORY_SAMPLE_PROB_PREF),
|
||||
|
|
|
@ -16,9 +16,9 @@ let test = Task.async(function*() {
|
|||
Services.prefs.setBoolPref(MEMORY_PREF, true);
|
||||
let { EVENTS, $, gFront, PerformanceController, PerformanceView, DetailsView, JsCallTreeView } = panel.panelWin;
|
||||
|
||||
let { memory: memoryMock, timeline: timelineMock } = gFront.getMocksInUse();
|
||||
ok(memoryMock, "memory should be mocked.");
|
||||
ok(timelineMock, "timeline should be mocked.");
|
||||
let { memory: memorySupport, timeline: timelineSupport } = gFront.getActorSupport();
|
||||
ok(!memorySupport, "memory should be mocked.");
|
||||
ok(!timelineSupport, "timeline should be mocked.");
|
||||
|
||||
yield startRecording(panel, { waitForOverview: false });
|
||||
busyWait(WAIT_TIME); // allow the profiler module to sample some cpu activity
|
||||
|
|
|
@ -13,12 +13,13 @@ function spawnTest () {
|
|||
});
|
||||
Services.prefs.setBoolPref(MEMORY_PREF, true);
|
||||
|
||||
let { memory, timeline } = front.getMocksInUse();
|
||||
ok(memory, "memory should be mocked.");
|
||||
ok(!timeline, "timeline should not be mocked.");
|
||||
let { memory, timeline } = front.getActorSupport();
|
||||
ok(!memory, "memory should be mocked.");
|
||||
ok(timeline, "timeline should not be mocked.");
|
||||
|
||||
let recording = yield front.startRecording({
|
||||
withTicks: true,
|
||||
withMarkers: true,
|
||||
withMemory: true,
|
||||
withAllocations: true,
|
||||
allocationsSampleProbability: +Services.prefs.getCharPref(MEMORY_SAMPLE_PROB_PREF),
|
||||
|
|
|
@ -16,9 +16,9 @@ let test = Task.async(function*() {
|
|||
let { EVENTS, $, gFront, PerformanceController, PerformanceView, DetailsView, WaterfallView } = panel.panelWin;
|
||||
|
||||
|
||||
let { memory: memoryMock, timeline: timelineMock } = gFront.getMocksInUse();
|
||||
ok(memoryMock, "memory should be mocked.");
|
||||
ok(!timelineMock, "timeline should not be mocked.");
|
||||
let { memory: memorySupport, timeline: timelineSupport } = gFront.getActorSupport();
|
||||
ok(!memorySupport, "memory should be mocked.");
|
||||
ok(timelineSupport, "timeline should not be mocked.");
|
||||
|
||||
yield startRecording(panel);
|
||||
yield busyWait(100);
|
||||
|
|
|
@ -29,9 +29,7 @@ function spawnTest () {
|
|||
yield once(OverviewView, EVENTS.OVERVIEW_RENDERED);
|
||||
yield once(OverviewView, EVENTS.OVERVIEW_RENDERED);
|
||||
|
||||
let detailsRendered = once(WaterfallView, EVENTS.WATERFALL_RENDERED);
|
||||
yield consoleProfileEnd(panel.panelWin, "rust");
|
||||
yield detailsRendered;
|
||||
|
||||
recordings = PerformanceController.getRecordings();
|
||||
is(recordings.length, 2, "two recordings found in the performance panel.");
|
||||
|
@ -40,7 +38,7 @@ function spawnTest () {
|
|||
is(RecordingsView.selectedItem.attachment.isRecording(), false,
|
||||
"The first console recording should no longer be recording.");
|
||||
|
||||
detailsRendered = once(WaterfallView, EVENTS.WATERFALL_RENDERED);
|
||||
let detailsRendered = once(WaterfallView, EVENTS.WATERFALL_RENDERED);
|
||||
yield consoleProfileEnd(panel.panelWin, "golang");
|
||||
yield detailsRendered;
|
||||
|
||||
|
|
|
@ -8,6 +8,9 @@ function spawnTest () {
|
|||
let { panel } = yield initPerformance(SIMPLE_URL);
|
||||
let { EVENTS, $, DetailsView, document: doc } = panel.panelWin;
|
||||
|
||||
yield startRecording(panel);
|
||||
yield stopRecording(panel);
|
||||
|
||||
info("views on startup");
|
||||
checkViews(DetailsView, doc, "waterfall");
|
||||
|
||||
|
|
|
@ -9,6 +9,9 @@ function spawnTest () {
|
|||
let { EVENTS, DetailsView } = panel.panelWin;
|
||||
let { WaterfallView, JsCallTreeView, JsFlameGraphView } = panel.panelWin;
|
||||
|
||||
yield startRecording(panel);
|
||||
yield stopRecording(panel);
|
||||
|
||||
ok(DetailsView.isViewSelected(WaterfallView),
|
||||
"The waterfall view is selected by default in the details view.");
|
||||
|
||||
|
@ -18,7 +21,7 @@ function spawnTest () {
|
|||
yield Promise.all([selected, notified]);
|
||||
|
||||
ok(DetailsView.isViewSelected(JsCallTreeView),
|
||||
"The waterfall view is now selected in the details view.");
|
||||
"The jscalltree view is now selected in the details view.");
|
||||
|
||||
selected = DetailsView.whenViewSelected(JsFlameGraphView);
|
||||
notified = DetailsView.once(EVENTS.DETAILS_VIEW_SELECTED);
|
||||
|
|
|
@ -2,17 +2,23 @@
|
|||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
/**
|
||||
* Tests that the details view hides the memory buttons when `enable-memory` is toggled,
|
||||
* and that it switches to default panel if toggling while a memory panel is selected.
|
||||
* Tests that the details view hides the memory buttons when a recording does not
|
||||
* have memory data (withMemory: false), and that when a memory panel is selected,
|
||||
* switching to a panel that does not have memory goes to a default panel instead.
|
||||
*/
|
||||
function spawnTest () {
|
||||
let { panel } = yield initPerformance(SIMPLE_URL);
|
||||
let { EVENTS, PerformanceController, OverviewView, DetailsView } = panel.panelWin;
|
||||
let { $, WaterfallView, MemoryCallTreeView, MemoryFlameGraphView } = panel.panelWin;
|
||||
let { $, RecordingsView, WaterfallView, MemoryCallTreeView, MemoryFlameGraphView } = panel.panelWin;
|
||||
|
||||
Services.prefs.setBoolPref(MEMORY_PREF, false);
|
||||
yield startRecording(panel);
|
||||
yield stopRecording(panel);
|
||||
|
||||
ok(DetailsView.isViewSelected(WaterfallView),
|
||||
"The waterfall view is selected by default in the details view.");
|
||||
|
||||
Services.prefs.setBoolPref(MEMORY_PREF, true);
|
||||
// The toolbar buttons will always be hidden when a recording isn't available,
|
||||
// so make sure we have one that's finished.
|
||||
yield startRecording(panel);
|
||||
|
@ -21,13 +27,8 @@ function spawnTest () {
|
|||
let flameBtn = $("toolbarbutton[data-view='memory-flamegraph']");
|
||||
let callBtn = $("toolbarbutton[data-view='memory-calltree']");
|
||||
|
||||
Services.prefs.setBoolPref(MEMORY_PREF, false);
|
||||
is(flameBtn.hidden, true, "memory-flamegraph button hidden when enable-memory=false");
|
||||
is(callBtn.hidden, true, "memory-calltree button hidden when enable-memory=false");
|
||||
|
||||
Services.prefs.setBoolPref(MEMORY_PREF, true);
|
||||
is(flameBtn.hidden, false, "memory-flamegraph button shown when enable-memory=true");
|
||||
is(callBtn.hidden, false, "memory-calltree button shown when enable-memory=true");
|
||||
is(flameBtn.hidden, false, "memory-flamegraph button shown when recording has memory data");
|
||||
is(callBtn.hidden, false, "memory-calltree button shown when recording has memory data");
|
||||
|
||||
let selected = DetailsView.whenViewSelected(MemoryCallTreeView);
|
||||
let notified = DetailsView.once(EVENTS.DETAILS_VIEW_SELECTED);
|
||||
|
@ -45,15 +46,21 @@ function spawnTest () {
|
|||
ok(DetailsView.isViewSelected(MemoryFlameGraphView),
|
||||
"The memory flamegraph view can now be selected.");
|
||||
|
||||
// Select the first recording with no memory data
|
||||
selected = DetailsView.whenViewSelected(WaterfallView);
|
||||
notified = DetailsView.once(EVENTS.DETAILS_VIEW_SELECTED);
|
||||
Services.prefs.setBoolPref(MEMORY_PREF, false);
|
||||
RecordingsView.selectedIndex = 0;
|
||||
yield Promise.all([selected, notified]);
|
||||
|
||||
ok(DetailsView.isViewSelected(WaterfallView),
|
||||
"The waterfall view is now selected when toggling off enable-memory when a memory panel is selected.");
|
||||
"The waterfall view is now selected when switching back to a recording that does not have memory data");
|
||||
is(flameBtn.hidden, true, "memory-flamegraph button hidden when recording does not have memory data");
|
||||
is(callBtn.hidden, true, "memory-calltree button hidden when recording does not have memory data");
|
||||
|
||||
Services.prefs.setBoolPref(MEMORY_PREF, true);
|
||||
// Go back to the recording with memory data
|
||||
let render = WaterfallView.once(EVENTS.WATERFALL_RENDERED);
|
||||
RecordingsView.selectedIndex = 1;
|
||||
yield render;
|
||||
|
||||
selected = DetailsView.whenViewSelected(MemoryCallTreeView);
|
||||
notified = DetailsView.once(EVENTS.DETAILS_VIEW_SELECTED);
|
||||
|
@ -61,23 +68,9 @@ function spawnTest () {
|
|||
yield Promise.all([selected, notified]);
|
||||
|
||||
ok(DetailsView.isViewSelected(MemoryCallTreeView),
|
||||
"The memory call tree view can be selected again after re-enabling memory.");
|
||||
|
||||
selected = DetailsView.whenViewSelected(MemoryFlameGraphView);
|
||||
notified = DetailsView.once(EVENTS.DETAILS_VIEW_SELECTED);
|
||||
DetailsView.selectView("memory-flamegraph");
|
||||
yield Promise.all([selected, notified]);
|
||||
|
||||
ok(DetailsView.isViewSelected(MemoryFlameGraphView),
|
||||
"The memory flamegraph view can be selected again after re-enabling memory.");
|
||||
|
||||
selected = DetailsView.whenViewSelected(WaterfallView);
|
||||
notified = DetailsView.once(EVENTS.DETAILS_VIEW_SELECTED);
|
||||
Services.prefs.setBoolPref(MEMORY_PREF, false);
|
||||
yield Promise.all([selected, notified]);
|
||||
|
||||
ok(DetailsView.isViewSelected(WaterfallView),
|
||||
"The waterfall view is now selected when toggling off enable-memory when a memory panel is selected.");
|
||||
"The memory call tree view can be selected again after going back to the view with memory data");
|
||||
is(flameBtn.hidden, false, "memory-flamegraph button shown when recording has memory data");
|
||||
is(callBtn.hidden, false, "memory-calltree button shown when recording has memory data");
|
||||
|
||||
yield teardown(panel);
|
||||
finish();
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
*/
|
||||
function spawnTest () {
|
||||
let { panel } = yield initPerformance(SIMPLE_URL);
|
||||
let { EVENTS, $, $$, PerformanceController, RecordingsView, DetailsView } = panel.panelWin;
|
||||
let { EVENTS, $, $$, PerformanceController, RecordingsView, WaterfallView } = panel.panelWin;
|
||||
|
||||
let waterfallBtn = $("toolbarbutton[data-view='waterfall']");
|
||||
let jsFlameBtn = $("toolbarbutton[data-view='js-flamegraph']");
|
||||
|
@ -46,8 +46,9 @@ function spawnTest () {
|
|||
is(memCallBtn.hidden, true, "memory-calltree button hidden when another recording starts.");
|
||||
|
||||
let select = once(PerformanceController, EVENTS.RECORDING_SELECTED);
|
||||
let render = once(WaterfallView, EVENTS.WATERFALL_RENDERED);
|
||||
mousedown(panel.panelWin, $$(".recording-item")[0]);
|
||||
yield select;
|
||||
yield Promise.all([select, render]);
|
||||
|
||||
is(RecordingsView.selectedIndex, 0,
|
||||
"The first recording was selected again.");
|
||||
|
@ -71,7 +72,9 @@ function spawnTest () {
|
|||
is(memFlameBtn.hidden, true, "memory-flamegraph button still hidden when second recording selected.");
|
||||
is(memCallBtn.hidden, true, "memory-calltree button still hidden when second recording selected.");
|
||||
|
||||
render = once(WaterfallView, EVENTS.WATERFALL_RENDERED);
|
||||
yield stopRecording(panel);
|
||||
yield render;
|
||||
|
||||
is(RecordingsView.selectedIndex, 1,
|
||||
"The second recording is still selected.");
|
||||
|
|
|
@ -0,0 +1,38 @@
|
|||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
/**
|
||||
* Tests that the details view utility functions work as advertised.
|
||||
*/
|
||||
function spawnTest () {
|
||||
let { panel } = yield initPerformance(SIMPLE_URL);
|
||||
let { EVENTS, DetailsView } = panel.panelWin;
|
||||
let { PerformanceController, WaterfallView, JsCallTreeView } = panel.panelWin;
|
||||
|
||||
yield startRecording(panel);
|
||||
yield stopRecording(panel);
|
||||
|
||||
ok(DetailsView.isViewSelected(WaterfallView),
|
||||
"The waterfall view is selected by default in the details view.");
|
||||
|
||||
let selected = DetailsView.whenViewSelected(JsCallTreeView);
|
||||
let notified = DetailsView.once(EVENTS.DETAILS_VIEW_SELECTED);
|
||||
yield DetailsView.selectView("js-calltree");
|
||||
yield Promise.all([selected, notified]);
|
||||
|
||||
ok(DetailsView.isViewSelected(JsCallTreeView),
|
||||
"The jscalltree view is now selected in the details view.");
|
||||
|
||||
yield PerformanceController.clearRecordings();
|
||||
|
||||
yield startRecording(panel);
|
||||
let render = once(JsCallTreeView, EVENTS.JS_CALL_TREE_RENDERED);
|
||||
yield stopRecording(panel);
|
||||
yield render;
|
||||
|
||||
ok(DetailsView.isViewSelected(JsCallTreeView),
|
||||
"The jscalltree view is still selected in the details view");
|
||||
|
||||
yield teardown(panel);
|
||||
finish();
|
||||
}
|
|
@ -8,14 +8,13 @@ function spawnTest () {
|
|||
let { panel } = yield initPerformance(SIMPLE_URL);
|
||||
let { EVENTS, DetailsView, JsCallTreeView } = panel.panelWin;
|
||||
|
||||
yield DetailsView.selectView("js-calltree");
|
||||
ok(DetailsView.isViewSelected(JsCallTreeView), "The call tree is now selected.");
|
||||
|
||||
yield startRecording(panel);
|
||||
yield busyWait(100);
|
||||
yield stopRecording(panel);
|
||||
|
||||
let rendered = once(JsCallTreeView, EVENTS.JS_CALL_TREE_RENDERED);
|
||||
yield stopRecording(panel);
|
||||
yield DetailsView.selectView("js-calltree");
|
||||
ok(DetailsView.isViewSelected(JsCallTreeView), "The call tree is now selected.");
|
||||
yield rendered;
|
||||
|
||||
ok(true, "JsCallTreeView rendered after recording is stopped.");
|
||||
|
|
|
@ -8,14 +8,13 @@ function spawnTest () {
|
|||
let { panel } = yield initPerformance(SIMPLE_URL);
|
||||
let { EVENTS, DetailsView, JsFlameGraphView } = panel.panelWin;
|
||||
|
||||
yield DetailsView.selectView("js-flamegraph");
|
||||
ok(DetailsView.isViewSelected(JsFlameGraphView), "The flamegraph is now selected.");
|
||||
|
||||
yield startRecording(panel);
|
||||
yield busyWait(100);
|
||||
yield stopRecording(panel);
|
||||
|
||||
let rendered = once(JsFlameGraphView, EVENTS.JS_FLAMEGRAPH_RENDERED);
|
||||
yield stopRecording(panel);
|
||||
yield DetailsView.selectView("js-flamegraph");
|
||||
ok(DetailsView.isViewSelected(JsFlameGraphView), "The flamegraph is now selected.");
|
||||
yield rendered;
|
||||
|
||||
ok(true, "JsFlameGraphView rendered after recording is stopped.");
|
||||
|
|
|
@ -11,14 +11,13 @@ function spawnTest () {
|
|||
// Enable memory to test.
|
||||
Services.prefs.setBoolPref(MEMORY_PREF, true);
|
||||
|
||||
yield DetailsView.selectView("memory-calltree");
|
||||
ok(DetailsView.isViewSelected(MemoryCallTreeView), "The call tree is now selected.");
|
||||
|
||||
yield startRecording(panel);
|
||||
yield busyWait(100);
|
||||
yield stopRecording(panel);
|
||||
|
||||
let rendered = once(MemoryCallTreeView, EVENTS.MEMORY_CALL_TREE_RENDERED);
|
||||
yield stopRecording(panel);
|
||||
yield DetailsView.selectView("memory-calltree");
|
||||
ok(DetailsView.isViewSelected(MemoryCallTreeView), "The call tree is now selected.");
|
||||
yield rendered;
|
||||
|
||||
ok(true, "MemoryCallTreeView rendered after recording is stopped.");
|
||||
|
|
|
@ -11,14 +11,13 @@ function spawnTest () {
|
|||
// Enable memory to test.
|
||||
Services.prefs.setBoolPref(MEMORY_PREF, true);
|
||||
|
||||
yield DetailsView.selectView("memory-flamegraph");
|
||||
ok(DetailsView.isViewSelected(MemoryFlameGraphView), "The flamegraph is now selected.");
|
||||
|
||||
yield startRecording(panel);
|
||||
yield busyWait(100);
|
||||
yield stopRecording(panel);
|
||||
|
||||
let rendered = once(MemoryFlameGraphView, EVENTS.MEMORY_FLAMEGRAPH_RENDERED);
|
||||
yield stopRecording(panel);
|
||||
yield DetailsView.selectView("memory-flamegraph");
|
||||
ok(DetailsView.isViewSelected(MemoryFlameGraphView), "The flamegraph is now selected.");
|
||||
yield rendered;
|
||||
|
||||
ok(true, "MemoryFlameGraphView rendered after recording is stopped.");
|
||||
|
|
|
@ -8,14 +8,13 @@ function spawnTest () {
|
|||
let { panel } = yield initPerformance(SIMPLE_URL);
|
||||
let { EVENTS, PerformanceController, DetailsView, WaterfallView } = panel.panelWin;
|
||||
|
||||
ok(DetailsView.isViewSelected(WaterfallView),
|
||||
"The waterfall view is selected by default in the details view.");
|
||||
|
||||
yield startRecording(panel);
|
||||
yield waitUntil(() => PerformanceController.getCurrentRecording().getMarkers().length);
|
||||
|
||||
let rendered = once(WaterfallView, EVENTS.WATERFALL_RENDERED);
|
||||
yield stopRecording(panel);
|
||||
ok(DetailsView.isViewSelected(WaterfallView),
|
||||
"The waterfall view is selected by default in the details view.");
|
||||
yield rendered;
|
||||
|
||||
ok(true, "WaterfallView rendered after recording is stopped.");
|
||||
|
|
|
@ -10,7 +10,7 @@ let WAIT_TIME = 1000;
|
|||
|
||||
function spawnTest () {
|
||||
let { target, front } = yield initBackend(SIMPLE_URL);
|
||||
let config = { withMemory: true, withTicks: true };
|
||||
let config = { withMarkers: true, withMemory: true, withTicks: true };
|
||||
|
||||
yield front._request("memory", "attach");
|
||||
|
||||
|
|
|
@ -25,7 +25,7 @@ function spawnTest () {
|
|||
|
||||
front.on("timeline-data", handler);
|
||||
|
||||
yield front.startRecording({ withMemory: true, withTicks: true });
|
||||
yield front.startRecording({ withMarkers: true, withMemory: true, withTicks: true });
|
||||
yield Promise.all(Object.keys(deferreds).map(type => deferreds[type].promise));
|
||||
yield front.stopRecording();
|
||||
front.off("timeline-data", handler);
|
||||
|
|
|
@ -0,0 +1,48 @@
|
|||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
/**
|
||||
* Tests that the toolbox tab for performance is highlighted when recording,
|
||||
* whether already loaded, or via console.profile with an unloaded performance tools.
|
||||
*/
|
||||
|
||||
let { getPerformanceActorsConnection } = devtools.require("devtools/performance/front");
|
||||
|
||||
function spawnTest () {
|
||||
let profilerConnected = waitForProfilerConnection();
|
||||
let { target, toolbox, console } = yield initConsole(SIMPLE_URL);
|
||||
yield profilerConnected;
|
||||
let connection = getPerformanceActorsConnection(target);
|
||||
let tab = toolbox.doc.getElementById("toolbox-tab-performance");
|
||||
|
||||
let profileStart = once(connection, "console-profile-start");
|
||||
console.profile("rust");
|
||||
yield profileStart;
|
||||
|
||||
ok(tab.hasAttribute("highlighted"),
|
||||
"performance tab is highlighted during recording from console.profile when unloaded");
|
||||
|
||||
let profileEnd = once(connection, "console-profile-end");
|
||||
console.profileEnd("rust");
|
||||
yield profileEnd;
|
||||
|
||||
ok(!tab.hasAttribute("highlighted"),
|
||||
"performance tab is no longer highlighted when console.profile recording finishes");
|
||||
|
||||
yield gDevTools.showToolbox(target, "performance");
|
||||
let panel = toolbox.getCurrentPanel();
|
||||
let { panelWin: { PerformanceController, RecordingsView }} = panel;
|
||||
|
||||
yield startRecording(panel);
|
||||
|
||||
ok(tab.hasAttribute("highlighted"),
|
||||
"performance tab is highlighted during recording while in performance tool");
|
||||
|
||||
yield stopRecording(panel);
|
||||
|
||||
ok(!tab.hasAttribute("highlighted"),
|
||||
"performance tab is no longer highlighted when recording finishes");
|
||||
|
||||
yield teardown(panel);
|
||||
finish();
|
||||
}
|
|
@ -8,22 +8,22 @@
|
|||
function spawnTest () {
|
||||
let { panel } = yield initPerformance(SIMPLE_URL);
|
||||
let { EVENTS, PerformanceController, $ } = panel.panelWin;
|
||||
|
||||
Services.prefs.setBoolPref(FRAMERATE_PREF, false);
|
||||
ok($("#time-framerate").hidden, "fps graph is hidden when ticks disabled");
|
||||
|
||||
yield startRecording(panel);
|
||||
yield stopRecording(panel);
|
||||
|
||||
is(PerformanceController.getCurrentRecording().getConfiguration().withTicks, false,
|
||||
"PerformanceFront started without ticks recording.");
|
||||
ok($("#time-framerate").hidden, "fps graph is hidden when ticks disabled");
|
||||
|
||||
Services.prefs.setBoolPref(FRAMERATE_PREF, true);
|
||||
ok(!$("#time-framerate").hidden, "fps graph is not hidden when ticks enabled");
|
||||
ok($("#time-framerate").hidden, "fps graph is still hidden if recording does not contain ticks.");
|
||||
|
||||
yield startRecording(panel);
|
||||
yield stopRecording(panel);
|
||||
|
||||
ok(!$("#time-framerate").hidden, "fps graph is not hidden when ticks enabled before recording");
|
||||
is(PerformanceController.getCurrentRecording().getConfiguration().withTicks, true,
|
||||
"PerformanceFront started with ticks recording.");
|
||||
|
||||
|
|
|
@ -10,7 +10,6 @@ function spawnTest () {
|
|||
let { EVENTS, PerformanceController, $ } = panel.panelWin;
|
||||
|
||||
Services.prefs.setBoolPref(MEMORY_PREF, false);
|
||||
ok($("#memory-overview").hidden, "memory graph is hidden when memory disabled");
|
||||
|
||||
yield startRecording(panel);
|
||||
yield stopRecording(panel);
|
||||
|
@ -19,13 +18,16 @@ function spawnTest () {
|
|||
"PerformanceFront started without memory recording.");
|
||||
is(PerformanceController.getCurrentRecording().getConfiguration().withAllocations, false,
|
||||
"PerformanceFront started without allocations recording.");
|
||||
ok($("#memory-overview").hidden, "memory graph is hidden when memory disabled");
|
||||
|
||||
Services.prefs.setBoolPref(MEMORY_PREF, true);
|
||||
ok(!$("#memory-overview").hidden, "memory graph is not hidden when memory enabled");
|
||||
ok($("#memory-overview").hidden,
|
||||
"memory graph is still hidden after enabling if recording did not start recording memory");
|
||||
|
||||
yield startRecording(panel);
|
||||
yield stopRecording(panel);
|
||||
|
||||
ok(!$("#memory-overview").hidden, "memory graph is not hidden when memory enabled before recording");
|
||||
is(PerformanceController.getCurrentRecording().getConfiguration().withMemory, true,
|
||||
"PerformanceFront started with memory recording.");
|
||||
is(PerformanceController.getCurrentRecording().getConfiguration().withAllocations, true,
|
||||
|
|
|
@ -10,13 +10,12 @@ function spawnTest () {
|
|||
|
||||
Services.prefs.setBoolPref(FLATTEN_PREF, true);
|
||||
|
||||
yield DetailsView.selectView("js-flamegraph");
|
||||
|
||||
yield startRecording(panel);
|
||||
yield busyWait(100);
|
||||
|
||||
let rendered = once(JsFlameGraphView, EVENTS.JS_FLAMEGRAPH_RENDERED);
|
||||
yield stopRecording(panel);
|
||||
let rendered = once(JsFlameGraphView, EVENTS.JS_FLAMEGRAPH_RENDERED);
|
||||
yield DetailsView.selectView("js-flamegraph");
|
||||
yield rendered;
|
||||
|
||||
let samples1 = PerformanceController.getCurrentRecording().getProfile().threads[0].samples;
|
||||
|
|
|
@ -12,13 +12,12 @@ function spawnTest () {
|
|||
Services.prefs.setBoolPref(MEMORY_PREF, true);
|
||||
Services.prefs.setBoolPref(FLATTEN_PREF, true);
|
||||
|
||||
yield DetailsView.selectView("memory-flamegraph");
|
||||
|
||||
yield startRecording(panel);
|
||||
yield busyWait(100);
|
||||
|
||||
let rendered = once(MemoryFlameGraphView, EVENTS.MEMORY_FLAMEGRAPH_RENDERED);
|
||||
yield stopRecording(panel);
|
||||
yield DetailsView.selectView("memory-flamegraph");
|
||||
yield rendered;
|
||||
|
||||
let allocations1 = PerformanceController.getCurrentRecording().getAllocations();
|
||||
|
|
|
@ -10,14 +10,13 @@ function spawnTest () {
|
|||
|
||||
Services.prefs.setBoolPref(INVERT_PREF, true);
|
||||
|
||||
yield DetailsView.selectView("js-calltree");
|
||||
ok(DetailsView.isViewSelected(JsCallTreeView), "The call tree is now selected.");
|
||||
|
||||
yield startRecording(panel);
|
||||
yield busyWait(100);
|
||||
yield stopRecording(panel);
|
||||
|
||||
let rendered = once(JsCallTreeView, EVENTS.JS_CALL_TREE_RENDERED);
|
||||
yield stopRecording(panel);
|
||||
yield DetailsView.selectView("js-calltree");
|
||||
ok(DetailsView.isViewSelected(JsCallTreeView), "The call tree is now selected.");
|
||||
yield rendered;
|
||||
|
||||
rendered = once(JsCallTreeView, EVENTS.JS_CALL_TREE_RENDERED);
|
||||
|
|
|
@ -12,14 +12,13 @@ function spawnTest () {
|
|||
Services.prefs.setBoolPref(MEMORY_PREF, true);
|
||||
Services.prefs.setBoolPref(INVERT_PREF, true);
|
||||
|
||||
yield DetailsView.selectView("memory-calltree");
|
||||
ok(DetailsView.isViewSelected(MemoryCallTreeView), "The call tree is now selected.");
|
||||
|
||||
yield startRecording(panel);
|
||||
yield busyWait(100);
|
||||
yield stopRecording(panel);
|
||||
|
||||
let rendered = once(MemoryCallTreeView, EVENTS.MEMORY_CALL_TREE_RENDERED);
|
||||
yield stopRecording(panel);
|
||||
yield DetailsView.selectView("memory-calltree");
|
||||
ok(DetailsView.isViewSelected(MemoryCallTreeView), "The call tree is now selected.");
|
||||
yield rendered;
|
||||
|
||||
rendered = once(MemoryCallTreeView, EVENTS.MEMORY_CALL_TREE_RENDERED);
|
||||
|
|
|
@ -8,15 +8,14 @@ function spawnTest () {
|
|||
let { panel } = yield initPerformance(SIMPLE_URL);
|
||||
let { EVENTS, DetailsView, JsFlameGraphView } = panel.panelWin;
|
||||
|
||||
yield DetailsView.selectView("js-flamegraph");
|
||||
|
||||
Services.prefs.setBoolPref(INVERT_FLAME_PREF, true);
|
||||
|
||||
yield startRecording(panel);
|
||||
yield busyWait(100);
|
||||
yield stopRecording(panel);
|
||||
|
||||
let rendered = once(JsFlameGraphView, EVENTS.JS_FLAMEGRAPH_RENDERED);
|
||||
yield stopRecording(panel);
|
||||
yield DetailsView.selectView("js-flamegraph");
|
||||
yield rendered;
|
||||
|
||||
rendered = once(JsFlameGraphView, EVENTS.JS_FLAMEGRAPH_RENDERED);
|
||||
|
|
|
@ -11,13 +11,12 @@ function spawnTest () {
|
|||
Services.prefs.setBoolPref(MEMORY_PREF, true);
|
||||
Services.prefs.setBoolPref(INVERT_FLAME_PREF, true);
|
||||
|
||||
yield DetailsView.selectView("memory-flamegraph");
|
||||
|
||||
yield startRecording(panel);
|
||||
yield busyWait(100);
|
||||
yield stopRecording(panel);
|
||||
|
||||
let rendered = once(MemoryFlameGraphView, EVENTS.MEMORY_FLAMEGRAPH_RENDERED);
|
||||
yield stopRecording(panel);
|
||||
yield DetailsView.selectView("memory-flamegraph");
|
||||
yield rendered;
|
||||
|
||||
rendered = once(MemoryFlameGraphView, EVENTS.MEMORY_FLAMEGRAPH_RENDERED);
|
||||
|
|
|
@ -10,13 +10,12 @@ function spawnTest () {
|
|||
|
||||
Services.prefs.setBoolPref(IDLE_PREF, true);
|
||||
|
||||
yield DetailsView.selectView("js-flamegraph");
|
||||
|
||||
yield startRecording(panel);
|
||||
yield busyWait(100);
|
||||
yield stopRecording(panel);
|
||||
|
||||
let rendered = once(JsFlameGraphView, EVENTS.JS_FLAMEGRAPH_RENDERED);
|
||||
yield stopRecording(panel);
|
||||
yield DetailsView.selectView("js-flamegraph");
|
||||
yield rendered;
|
||||
|
||||
rendered = once(JsFlameGraphView, EVENTS.JS_FLAMEGRAPH_RENDERED);
|
||||
|
|
|
@ -12,13 +12,12 @@ function spawnTest () {
|
|||
Services.prefs.setBoolPref(MEMORY_PREF, true);
|
||||
Services.prefs.setBoolPref(IDLE_PREF, true);
|
||||
|
||||
yield DetailsView.selectView("memory-flamegraph");
|
||||
|
||||
yield startRecording(panel);
|
||||
yield busyWait(100);
|
||||
yield stopRecording(panel);
|
||||
|
||||
let rendered = once(MemoryFlameGraphView, EVENTS.MEMORY_FLAMEGRAPH_RENDERED);
|
||||
yield stopRecording(panel);
|
||||
yield DetailsView.selectView("memory-flamegraph");
|
||||
yield rendered;
|
||||
|
||||
rendered = once(MemoryFlameGraphView, EVENTS.MEMORY_FLAMEGRAPH_RENDERED);
|
||||
|
|
|
@ -10,13 +10,12 @@ function spawnTest () {
|
|||
|
||||
Services.prefs.setBoolPref(PLATFORM_DATA_PREF, true);
|
||||
|
||||
yield DetailsView.selectView("js-calltree");
|
||||
|
||||
yield startRecording(panel);
|
||||
yield busyWait(100);
|
||||
yield stopRecording(panel);
|
||||
|
||||
let rendered = once(JsCallTreeView, EVENTS.JS_CALL_TREE_RENDERED);
|
||||
yield stopRecording(panel);
|
||||
yield DetailsView.selectView("js-calltree");
|
||||
yield rendered;
|
||||
|
||||
rendered = once(JsCallTreeView, EVENTS.JS_CALL_TREE_RENDERED);
|
||||
|
|
|
@ -10,13 +10,12 @@ function spawnTest () {
|
|||
|
||||
Services.prefs.setBoolPref(PLATFORM_DATA_PREF, false);
|
||||
|
||||
yield DetailsView.selectView("js-flamegraph");
|
||||
|
||||
yield startRecording(panel);
|
||||
yield busyWait(100);
|
||||
yield stopRecording(panel);
|
||||
|
||||
let rendered = once(JsFlameGraphView, EVENTS.JS_FLAMEGRAPH_RENDERED);
|
||||
yield stopRecording(panel);
|
||||
yield DetailsView.selectView("js-flamegraph");
|
||||
yield rendered;
|
||||
|
||||
rendered = once(JsFlameGraphView, EVENTS.JS_FLAMEGRAPH_RENDERED);
|
||||
|
|
|
@ -21,25 +21,28 @@ function spawnTest () {
|
|||
once(OverviewView, EVENTS.OVERVIEW_RENDERED),
|
||||
]);
|
||||
|
||||
ok("selectionEnabled" in OverviewView.framerateGraph,
|
||||
let framerate = OverviewView.graphs.get("framerate");
|
||||
ok("selectionEnabled" in framerate,
|
||||
"The selection should not be enabled for the framerate overview (1).");
|
||||
is(OverviewView.framerateGraph.selectionEnabled, false,
|
||||
is(framerate.selectionEnabled, false,
|
||||
"The selection should not be enabled for the framerate overview (2).");
|
||||
is(OverviewView.framerateGraph.hasSelection(), false,
|
||||
is(framerate.hasSelection(), false,
|
||||
"The framerate overview shouldn't have a selection before recording.");
|
||||
|
||||
ok("selectionEnabled" in OverviewView.markersOverview,
|
||||
let markers = OverviewView.graphs.get("timeline");
|
||||
ok("selectionEnabled" in markers,
|
||||
"The selection should not be enabled for the markers overview (1).");
|
||||
is(OverviewView.markersOverview.selectionEnabled, false,
|
||||
is(markers.selectionEnabled, false,
|
||||
"The selection should not be enabled for the markers overview (2).");
|
||||
is(OverviewView.markersOverview.hasSelection(), false,
|
||||
is(markers.hasSelection(), false,
|
||||
"The markers overview shouldn't have a selection before recording.");
|
||||
|
||||
ok("selectionEnabled" in OverviewView.memoryOverview,
|
||||
let memory = OverviewView.graphs.get("memory");
|
||||
ok("selectionEnabled" in memory,
|
||||
"The selection should not be enabled for the memory overview (1).");
|
||||
is(OverviewView.memoryOverview.selectionEnabled, false,
|
||||
is(memory.selectionEnabled, false,
|
||||
"The selection should not be enabled for the memory overview (2).");
|
||||
is(OverviewView.memoryOverview.hasSelection(), false,
|
||||
is(memory.hasSelection(), false,
|
||||
"The memory overview shouldn't have a selection before recording.");
|
||||
|
||||
let updated = 0;
|
||||
|
@ -48,36 +51,36 @@ function spawnTest () {
|
|||
ok((yield waitUntil(() => updated > 10)),
|
||||
"The overviews were updated several times.");
|
||||
|
||||
ok("selectionEnabled" in OverviewView.framerateGraph,
|
||||
ok("selectionEnabled" in framerate,
|
||||
"The selection should still not be enabled for the framerate overview (1).");
|
||||
is(OverviewView.framerateGraph.selectionEnabled, false,
|
||||
is(framerate.selectionEnabled, false,
|
||||
"The selection should still not be enabled for the framerate overview (2).");
|
||||
is(OverviewView.framerateGraph.hasSelection(), false,
|
||||
is(framerate.hasSelection(), false,
|
||||
"The framerate overview still shouldn't have a selection before recording.");
|
||||
|
||||
ok("selectionEnabled" in OverviewView.markersOverview,
|
||||
ok("selectionEnabled" in markers,
|
||||
"The selection should still not be enabled for the markers overview (1).");
|
||||
is(OverviewView.markersOverview.selectionEnabled, false,
|
||||
is(markers.selectionEnabled, false,
|
||||
"The selection should still not be enabled for the markers overview (2).");
|
||||
is(OverviewView.markersOverview.hasSelection(), false,
|
||||
is(markers.hasSelection(), false,
|
||||
"The markers overview still shouldn't have a selection before recording.");
|
||||
|
||||
ok("selectionEnabled" in OverviewView.memoryOverview,
|
||||
ok("selectionEnabled" in memory,
|
||||
"The selection should still not be enabled for the memory overview (1).");
|
||||
is(OverviewView.memoryOverview.selectionEnabled, false,
|
||||
is(memory.selectionEnabled, false,
|
||||
"The selection should still not be enabled for the memory overview (2).");
|
||||
is(OverviewView.memoryOverview.hasSelection(), false,
|
||||
is(memory.hasSelection(), false,
|
||||
"The memory overview still shouldn't have a selection before recording.");
|
||||
|
||||
yield stopRecording(panel);
|
||||
|
||||
is(OverviewView.framerateGraph.selectionEnabled, true,
|
||||
is(framerate.selectionEnabled, true,
|
||||
"The selection should now be enabled for the framerate overview.");
|
||||
|
||||
is(OverviewView.markersOverview.selectionEnabled, true,
|
||||
is(markers.selectionEnabled, true,
|
||||
"The selection should now be enabled for the markers overview.");
|
||||
|
||||
is(OverviewView.memoryOverview.selectionEnabled, true,
|
||||
is(memory.selectionEnabled, true,
|
||||
"The selection should now be enabled for the memory overview.");
|
||||
|
||||
yield teardown(panel);
|
||||
|
|
|
@ -24,41 +24,40 @@ function spawnTest () {
|
|||
|
||||
yield stopRecording(panel);
|
||||
|
||||
ok(OverviewView.markersOverview.width > 0,
|
||||
"The overview's framerate graph has a width.");
|
||||
ok(OverviewView.markersOverview.dataScaleX > 0,
|
||||
"The overview's framerate graph has a data scale factor.");
|
||||
let markers = OverviewView.graphs.get("timeline");
|
||||
let framerate = OverviewView.graphs.get("framerate");
|
||||
let memory = OverviewView.graphs.get("memory");
|
||||
|
||||
ok(OverviewView.memoryOverview.width > 0,
|
||||
ok(markers.width > 0,
|
||||
"The overview's markers graph has a width.");
|
||||
ok(markers.dataScaleX > 0,
|
||||
"The overview's markers graph has a data scale factor.");
|
||||
|
||||
ok(memory.width > 0,
|
||||
"The overview's memory graph has a width.");
|
||||
ok(memory.dataDuration > 0,
|
||||
"The overview's memory graph has a data duration.");
|
||||
ok(memory.dataScaleX > 0,
|
||||
"The overview's memory graph has a data scale factor.");
|
||||
|
||||
ok(framerate.width > 0,
|
||||
"The overview's framerate graph has a width.");
|
||||
ok(OverviewView.memoryOverview.dataDuration > 0,
|
||||
ok(framerate.dataDuration > 0,
|
||||
"The overview's framerate graph has a data duration.");
|
||||
ok(OverviewView.memoryOverview.dataScaleX > 0,
|
||||
ok(framerate.dataScaleX > 0,
|
||||
"The overview's framerate graph has a data scale factor.");
|
||||
|
||||
ok(OverviewView.framerateGraph.width > 0,
|
||||
"The overview's framerate graph has a width.");
|
||||
ok(OverviewView.framerateGraph.dataDuration > 0,
|
||||
"The overview's framerate graph has a data duration.");
|
||||
ok(OverviewView.framerateGraph.dataScaleX > 0,
|
||||
"The overview's framerate graph has a data scale factor.");
|
||||
|
||||
is(OverviewView.markersOverview.width,
|
||||
OverviewView.memoryOverview.width,
|
||||
"The markers and memory graphs widths are the same.")
|
||||
is(OverviewView.markersOverview.width,
|
||||
OverviewView.framerateGraph.width,
|
||||
is(markers.width, memory.width,
|
||||
"The markers and memory graphs widths are the same.");
|
||||
is(markers.width, framerate.width,
|
||||
"The markers and framerate graphs widths are the same.");
|
||||
|
||||
is(OverviewView.memoryOverview.dataDuration,
|
||||
OverviewView.framerateGraph.dataDuration,
|
||||
is(memory.dataDuration, framerate.dataDuration,
|
||||
"The memory and framerate graphs data duration are the same.");
|
||||
|
||||
is(OverviewView.markersOverview.dataScaleX,
|
||||
OverviewView.memoryOverview.dataScaleX,
|
||||
"The markers and memory graphs data scale are the same.")
|
||||
is(OverviewView.markersOverview.dataScaleX,
|
||||
OverviewView.framerateGraph.dataScaleX,
|
||||
is(markers.dataScaleX, memory.dataScaleX,
|
||||
"The markers and memory graphs data scale are the same.");
|
||||
is(markers.dataScaleX, framerate.dataScaleX,
|
||||
"The markers and framerate graphs data scale are the same.");
|
||||
|
||||
yield teardown(panel);
|
||||
|
|
|
@ -19,7 +19,7 @@ function spawnTest () {
|
|||
|
||||
yield stopRecording(panel);
|
||||
|
||||
let graph = OverviewView.markersOverview;
|
||||
let graph = OverviewView.graphs.get("timeline");
|
||||
let MAX = graph.width;
|
||||
|
||||
// Select the first half of the graph
|
||||
|
|
|
@ -20,9 +20,9 @@ function spawnTest () {
|
|||
once(OverviewView, EVENTS.OVERVIEW_RENDERED),
|
||||
]);
|
||||
|
||||
let markersOverview = OverviewView.markersOverview;
|
||||
let memoryOverview = OverviewView.memoryOverview;
|
||||
let framerateGraph = OverviewView.framerateGraph;
|
||||
let markersOverview = OverviewView.graphs.get("timeline");
|
||||
let memoryOverview = OverviewView.graphs.get("memory");
|
||||
let framerateGraph = OverviewView.graphs.get("framerate");
|
||||
|
||||
ok(markersOverview,
|
||||
"The markers graph should have been created now.");
|
||||
|
|
|
@ -22,9 +22,9 @@ function spawnTest () {
|
|||
|
||||
yield stopRecording(panel);
|
||||
|
||||
let framerateGraph = OverviewView.framerateGraph;
|
||||
let markersOverview = OverviewView.markersOverview;
|
||||
let memoryOverview = OverviewView.memoryOverview;
|
||||
let framerateGraph = OverviewView.graphs.get("framerate");
|
||||
let markersOverview = OverviewView.graphs.get("timeline");
|
||||
let memoryOverview = OverviewView.graphs.get("memory");
|
||||
let MAX = framerateGraph.width;
|
||||
|
||||
// Perform a selection inside the framerate graph.
|
||||
|
|
|
@ -17,6 +17,9 @@ let test = Task.async(function*() {
|
|||
// `waitForWidgetsRendered`.
|
||||
DetailsSubview.canUpdateWhileHidden = true;
|
||||
|
||||
yield startRecording(panel);
|
||||
yield stopRecording(panel);
|
||||
|
||||
// Cycle through all the views to initialize them, otherwise we can't use
|
||||
// `waitForWidgetsRendered`. The waterfall is shown by default, but all the
|
||||
// other views are created lazily, so won't emit any events.
|
||||
|
@ -28,9 +31,6 @@ let test = Task.async(function*() {
|
|||
yield startRecording(panel);
|
||||
yield stopRecording(panel);
|
||||
|
||||
yield startRecording(panel);
|
||||
yield stopRecording(panel);
|
||||
|
||||
let rerender = waitForWidgetsRendered(panel);
|
||||
RecordingsView.selectedIndex = 0;
|
||||
yield rerender;
|
||||
|
|
|
@ -11,15 +11,15 @@ function spawnTest () {
|
|||
is(PerformanceView.getState(), "empty",
|
||||
"The intial state of the performance panel view is correct.");
|
||||
|
||||
ok(!("markersOverview" in OverviewView),
|
||||
ok(!(OverviewView.graphs.get("timeline")),
|
||||
"The markers graph should not have been created yet.");
|
||||
ok(!("memoryOverview" in OverviewView),
|
||||
ok(!(OverviewView.graphs.get("memory")),
|
||||
"The memory graph should not have been created yet.");
|
||||
ok(!("framerateGraph" in OverviewView),
|
||||
ok(!(OverviewView.graphs.get("framerate")),
|
||||
"The framerate graph should not have been created yet.");
|
||||
|
||||
ok(DetailsView.components["waterfall"].initialized,
|
||||
"The waterfall detail view should have been created by default.");
|
||||
ok(!DetailsView.components["waterfall"].initialized,
|
||||
"The waterfall detail view should not have been created yet.");
|
||||
ok(!DetailsView.components["js-calltree"].initialized,
|
||||
"The js-calltree detail view should not have been created yet.");
|
||||
ok(!DetailsView.components["js-flamegraph"].initialized,
|
||||
|
@ -31,11 +31,11 @@ function spawnTest () {
|
|||
|
||||
Services.prefs.setBoolPref(MEMORY_PREF, true);
|
||||
|
||||
ok(!("markersOverview" in OverviewView),
|
||||
ok(!(OverviewView.graphs.get("timeline")),
|
||||
"The markers graph should still not have been created yet.");
|
||||
ok(!("memoryOverview" in OverviewView),
|
||||
ok(!(OverviewView.graphs.get("memory")),
|
||||
"The memory graph should still not have been created yet.");
|
||||
ok(!("framerateGraph" in OverviewView),
|
||||
ok(!(OverviewView.graphs.get("framerate")),
|
||||
"The framerate graph should still not have been created yet.");
|
||||
|
||||
let stateChanged = once(PerformanceView, EVENTS.UI_STATE_CHANGED);
|
||||
|
@ -44,9 +44,9 @@ function spawnTest () {
|
|||
|
||||
is(PerformanceView.getState(), "recording",
|
||||
"The current state of the performance panel view is 'recording'.");
|
||||
ok(OverviewView.memoryOverview,
|
||||
ok(OverviewView.graphs.get("memory"),
|
||||
"The memory graph should have been created now.");
|
||||
ok(OverviewView.framerateGraph,
|
||||
ok(OverviewView.graphs.get("framerate"),
|
||||
"The framerate graph should have been created now.");
|
||||
|
||||
stateChanged = once(PerformanceView, EVENTS.UI_STATE_CHANGED);
|
||||
|
|
|
@ -17,20 +17,21 @@ function spawnTest () {
|
|||
let { EVENTS, $, OverviewView, document: doc } = panel.panelWin;
|
||||
|
||||
yield startRecording(panel);
|
||||
is(OverviewView.markersOverview.backgroundColor, DARK_BG,
|
||||
let markers = OverviewView.graphs.get("timeline");
|
||||
is(markers.backgroundColor, DARK_BG,
|
||||
"correct theme on load for markers.");
|
||||
yield stopRecording(panel);
|
||||
|
||||
let refreshed = once(OverviewView.markersOverview, "refresh");
|
||||
let refreshed = once(markers, "refresh");
|
||||
setTheme("light");
|
||||
yield refreshed;
|
||||
|
||||
ok(true, "markers were rerendered after theme change.");
|
||||
is(OverviewView.markersOverview.backgroundColor, LIGHT_BG,
|
||||
is(markers.backgroundColor, LIGHT_BG,
|
||||
"correct theme on after toggle for markers.");
|
||||
|
||||
// reset back to dark
|
||||
refreshed = once(OverviewView.markersOverview, "refresh");
|
||||
refreshed = once(markers, "refresh");
|
||||
setTheme("dark");
|
||||
yield refreshed;
|
||||
|
||||
|
@ -39,26 +40,27 @@ function spawnTest () {
|
|||
Services.prefs.setBoolPref(MEMORY_PREF, true);
|
||||
|
||||
yield startRecording(panel);
|
||||
is(OverviewView.memoryOverview.backgroundColor, DARK_BG,
|
||||
let memory = OverviewView.graphs.get("memory");
|
||||
is(memory.backgroundColor, DARK_BG,
|
||||
"correct theme on load for memory.");
|
||||
yield stopRecording(panel);
|
||||
|
||||
refreshed = Promise.all([
|
||||
once(OverviewView.markersOverview, "refresh"),
|
||||
once(OverviewView.memoryOverview, "refresh"),
|
||||
once(markers, "refresh"),
|
||||
once(memory, "refresh"),
|
||||
]);
|
||||
setTheme("light");
|
||||
yield refreshed;
|
||||
|
||||
ok(true, "Both memory and markers were rerendered after theme change.");
|
||||
is(OverviewView.markersOverview.backgroundColor, LIGHT_BG,
|
||||
is(markers.backgroundColor, LIGHT_BG,
|
||||
"correct theme on after toggle for markers.");
|
||||
is(OverviewView.memoryOverview.backgroundColor, LIGHT_BG,
|
||||
is(memory.backgroundColor, LIGHT_BG,
|
||||
"correct theme on after toggle for memory.");
|
||||
|
||||
refreshed = Promise.all([
|
||||
once(OverviewView.markersOverview, "refresh"),
|
||||
once(OverviewView.memoryOverview, "refresh"),
|
||||
once(markers, "refresh"),
|
||||
once(memory, "refresh"),
|
||||
]);
|
||||
|
||||
// Set theme back to light
|
||||
|
|
|
@ -11,6 +11,14 @@ let test = Task.async(function*() {
|
|||
|
||||
// Enable memory to test the memory-calltree and memory-flamegraph.
|
||||
Services.prefs.setBoolPref(MEMORY_PREF, true);
|
||||
Services.prefs.setBoolPref(FRAMERATE_PREF, true);
|
||||
|
||||
// Need to allow widgets to be updated while hidden, otherwise we can't use
|
||||
// `waitForWidgetsRendered`.
|
||||
DetailsSubview.canUpdateWhileHidden = true;
|
||||
|
||||
yield startRecording(panel);
|
||||
yield stopRecording(panel);
|
||||
|
||||
// Cycle through all the views to initialize them, otherwise we can't use
|
||||
// `waitForWidgetsRendered`. The waterfall is shown by default, but all the
|
||||
|
@ -20,12 +28,6 @@ let test = Task.async(function*() {
|
|||
yield DetailsView.selectView("memory-calltree");
|
||||
yield DetailsView.selectView("memory-flamegraph");
|
||||
|
||||
// Need to allow widgets to be updated while hidden, otherwise we can't use
|
||||
// `waitForWidgetsRendered`.
|
||||
DetailsSubview.canUpdateWhileHidden = true;
|
||||
|
||||
yield startRecording(panel);
|
||||
yield stopRecording(panel);
|
||||
|
||||
// Verify original recording.
|
||||
|
||||
|
@ -60,19 +62,23 @@ let test = Task.async(function*() {
|
|||
let importedData = PerformanceController.getCurrentRecording().getAllData();
|
||||
|
||||
is(importedData.label, originalData.label,
|
||||
"The impored data is identical to the original data (1).");
|
||||
"The imported data is identical to the original data (1).");
|
||||
is(importedData.duration, originalData.duration,
|
||||
"The impored data is identical to the original data (2).");
|
||||
"The imported data is identical to the original data (2).");
|
||||
is(importedData.markers.toSource(), originalData.markers.toSource(),
|
||||
"The impored data is identical to the original data (3).");
|
||||
"The imported data is identical to the original data (3).");
|
||||
is(importedData.memory.toSource(), originalData.memory.toSource(),
|
||||
"The impored data is identical to the original data (4).");
|
||||
"The imported data is identical to the original data (4).");
|
||||
is(importedData.ticks.toSource(), originalData.ticks.toSource(),
|
||||
"The impored data is identical to the original data (5).");
|
||||
"The imported data is identical to the original data (5).");
|
||||
is(importedData.allocations.toSource(), originalData.allocations.toSource(),
|
||||
"The impored data is identical to the original data (6).");
|
||||
"The imported data is identical to the original data (6).");
|
||||
is(importedData.profile.toSource(), originalData.profile.toSource(),
|
||||
"The impored data is identical to the original data (7).");
|
||||
"The imported data is identical to the original data (7).");
|
||||
is(importedData.configuration.withTicks, originalData.configuration.withTicks,
|
||||
"The imported data is identical to the original data (8).");
|
||||
is(importedData.configuration.withMemory, originalData.configuration.withMemory,
|
||||
"The imported data is identical to the original data (9).");
|
||||
|
||||
yield teardown(panel);
|
||||
finish();
|
||||
|
|
|
@ -3,28 +3,16 @@
|
|||
|
||||
/**
|
||||
* Tests if the performance tool can import profiler data from the
|
||||
* original profiler tool.
|
||||
* original profiler tool and the correct views and graphs are loaded.
|
||||
*/
|
||||
|
||||
let test = Task.async(function*() {
|
||||
let { target, panel, toolbox } = yield initPerformance(SIMPLE_URL);
|
||||
let { EVENTS, PerformanceController, DetailsView, DetailsSubview } = panel.panelWin;
|
||||
let { $, EVENTS, PerformanceController, DetailsView, OverviewView, JsCallTreeView } = panel.panelWin;
|
||||
|
||||
// Enable memory to test the memory-calltree and memory-flamegraph.
|
||||
Services.prefs.setBoolPref(MEMORY_PREF, true);
|
||||
|
||||
// Cycle through all the views to initialize them, otherwise we can't use
|
||||
// `waitForWidgetsRendered`. The waterfall is shown by default, but all the
|
||||
// other views are created lazily, so won't emit any events.
|
||||
yield DetailsView.selectView("js-calltree");
|
||||
yield DetailsView.selectView("js-flamegraph");
|
||||
yield DetailsView.selectView("memory-calltree");
|
||||
yield DetailsView.selectView("memory-flamegraph");
|
||||
|
||||
// Need to allow widgets to be updated while hidden, otherwise we can't use
|
||||
// `waitForWidgetsRendered`.
|
||||
DetailsSubview.canUpdateWhileHidden = true;
|
||||
|
||||
yield startRecording(panel);
|
||||
yield stopRecording(panel);
|
||||
|
||||
|
@ -49,16 +37,30 @@ let test = Task.async(function*() {
|
|||
|
||||
// Import recording.
|
||||
|
||||
let rerendered = waitForWidgetsRendered(panel);
|
||||
let calltreeRendered = once(OverviewView, EVENTS.FRAMERATE_GRAPH_RENDERED);
|
||||
let fpsRendered = once(JsCallTreeView, EVENTS.JS_CALL_TREE_RENDERED);
|
||||
let imported = once(PerformanceController, EVENTS.RECORDING_IMPORTED);
|
||||
yield PerformanceController.importRecording("", file);
|
||||
|
||||
yield imported;
|
||||
ok(true, "The original profiler data appears to have been successfully imported.");
|
||||
|
||||
yield rerendered;
|
||||
yield calltreeRendered;
|
||||
yield fpsRendered;
|
||||
ok(true, "The imported data was re-rendered.");
|
||||
|
||||
// Ensure that only framerate and js calltree/flamegraph view are available
|
||||
is($("#overview-pane").hidden, false, "overview graph container still shown");
|
||||
is($("#memory-overview").hidden, true, "memory graph hidden");
|
||||
is($("#markers-overview").hidden, true, "markers overview graph hidden");
|
||||
is($("#time-framerate").hidden, false, "fps graph shown");
|
||||
is($("#select-waterfall-view").hidden, true, "waterfall button hidden");
|
||||
is($("#select-js-calltree-view").hidden, false, "jscalltree button shown");
|
||||
is($("#select-js-flamegraph-view").hidden, false, "jsflamegraph button shown");
|
||||
is($("#select-memory-calltree-view").hidden, true, "memorycalltree button hidden");
|
||||
is($("#select-memory-flamegraph-view").hidden, true, "memoryflamegraph button hidden");
|
||||
ok(DetailsView.isViewSelected(JsCallTreeView), "jscalltree view selected as its the only option");
|
||||
|
||||
// Verify imported recording.
|
||||
|
||||
let importedData = PerformanceController.getCurrentRecording().getAllData();
|
||||
|
@ -79,6 +81,12 @@ let test = Task.async(function*() {
|
|||
"The imported legacy data was successfully converted for the current tool (7).");
|
||||
is(importedData.profile.toSource(), data.profile.toSource(),
|
||||
"The imported legacy data was successfully converted for the current tool (8).");
|
||||
is(importedData.configuration.withTicks, true,
|
||||
"The imported legacy data was successfully converted for the current tool (9).");
|
||||
is(importedData.configuration.withMemory, false,
|
||||
"The imported legacy data was successfully converted for the current tool (10).");
|
||||
is(importedData.configuration.sampleFrequency, void 0,
|
||||
"The imported legacy data was successfully converted for the current tool (11).");
|
||||
|
||||
yield teardown(panel);
|
||||
finish();
|
||||
|
|
|
@ -8,7 +8,8 @@
|
|||
function spawnTest () {
|
||||
let { panel } = yield initPerformance(SIMPLE_URL);
|
||||
let { $, $$, PerformanceController, OverviewView, WaterfallView } = panel.panelWin;
|
||||
let { MARKERS_GRAPH_ROW_HEIGHT } = panel.panelWin;
|
||||
let { TimelineGraph } = devtools.require("devtools/performance/graphs");
|
||||
let { rowHeight: MARKERS_GRAPH_ROW_HEIGHT } = TimelineGraph.prototype;
|
||||
|
||||
yield startRecording(panel);
|
||||
ok(true, "Recording has started.");
|
||||
|
@ -23,7 +24,7 @@ function spawnTest () {
|
|||
|
||||
yield stopRecording(panel);
|
||||
|
||||
let overview = OverviewView.markersOverview;
|
||||
let overview = OverviewView.graphs.get("timeline");
|
||||
let waterfall = WaterfallView.waterfall;
|
||||
|
||||
// Select everything
|
||||
|
|
|
@ -354,6 +354,7 @@ function* startRecording(panel, options = {
|
|||
: Promise.resolve();
|
||||
|
||||
yield hasStarted;
|
||||
|
||||
let overviewRendered = options.waitForOverview
|
||||
? once(win.OverviewView, win.EVENTS.OVERVIEW_RENDERED)
|
||||
: Promise.resolve();
|
||||
|
@ -379,6 +380,7 @@ function* stopRecording(panel, options = {
|
|||
let willStop = panel.panelWin.PerformanceController.once(win.EVENTS.RECORDING_WILL_STOP);
|
||||
let hasStopped = panel.panelWin.PerformanceController.once(win.EVENTS.RECORDING_STOPPED);
|
||||
let button = win.$("#main-record-button");
|
||||
let overviewRendered = null;
|
||||
|
||||
ok(button.hasAttribute("checked"),
|
||||
"The record button should already be checked.");
|
||||
|
@ -399,12 +401,17 @@ function* stopRecording(panel, options = {
|
|||
: Promise.resolve();
|
||||
|
||||
yield hasStopped;
|
||||
let overviewRendered = options.waitForOverview
|
||||
? once(win.OverviewView, win.EVENTS.OVERVIEW_RENDERED)
|
||||
: Promise.resolve();
|
||||
|
||||
// Wait for the final rendering of the overview, not a low res
|
||||
// incremental rendering and less likely to be from another rendering that was selected
|
||||
while (!overviewRendered && options.waitForOverview) {
|
||||
let [_, res] = yield onceSpread(win.OverviewView, win.EVENTS.OVERVIEW_RENDERED);
|
||||
if (res === win.FRAMERATE_GRAPH_HIGH_RES_INTERVAL) {
|
||||
overviewRendered = true;
|
||||
}
|
||||
}
|
||||
|
||||
yield stateChanged;
|
||||
yield overviewRendered;
|
||||
|
||||
is(win.PerformanceView.getState(), "recorded",
|
||||
"The current state is 'recorded'.");
|
||||
|
@ -490,3 +497,13 @@ function reload (aTarget, aEvent = "navigate") {
|
|||
aTarget.activeTab.reload();
|
||||
return once(aTarget, aEvent);
|
||||
}
|
||||
|
||||
/**
|
||||
* Forces cycle collection and GC, used in AudioNode destruction tests.
|
||||
*/
|
||||
function forceCC () {
|
||||
info("Triggering GC/CC...");
|
||||
SpecialPowers.DOMWindowUtils.cycleCollect();
|
||||
SpecialPowers.DOMWindowUtils.garbageCollect();
|
||||
SpecialPowers.DOMWindowUtils.garbageCollect();
|
||||
}
|
||||
|
|
|
@ -16,7 +16,8 @@ let DetailsView = {
|
|||
"waterfall": {
|
||||
id: "waterfall-view",
|
||||
view: WaterfallView,
|
||||
requires: ["timeline"]
|
||||
actors: ["timeline"],
|
||||
features: ["withMarkers"]
|
||||
},
|
||||
"js-calltree": {
|
||||
id: "js-profile-view",
|
||||
|
@ -25,19 +26,19 @@ let DetailsView = {
|
|||
"js-flamegraph": {
|
||||
id: "js-flamegraph-view",
|
||||
view: JsFlameGraphView,
|
||||
requires: ["timeline"]
|
||||
actors: ["timeline"]
|
||||
},
|
||||
"memory-calltree": {
|
||||
id: "memory-calltree-view",
|
||||
view: MemoryCallTreeView,
|
||||
requires: ["memory"],
|
||||
pref: "enable-memory"
|
||||
actors: ["memory"],
|
||||
features: ["withAllocations"]
|
||||
},
|
||||
"memory-flamegraph": {
|
||||
id: "memory-flamegraph-view",
|
||||
view: MemoryFlameGraphView,
|
||||
requires: ["memory", "timeline"],
|
||||
pref: "enable-memory"
|
||||
actors: ["memory", "timeline"],
|
||||
features: ["withAllocations"]
|
||||
}
|
||||
},
|
||||
|
||||
|
@ -56,7 +57,6 @@ let DetailsView = {
|
|||
button.addEventListener("command", this._onViewToggle);
|
||||
}
|
||||
|
||||
yield this.selectDefaultView();
|
||||
yield this.setAvailableViews();
|
||||
|
||||
PerformanceController.on(EVENTS.CONSOLE_RECORDING_STOPPED, this._onRecordingStoppedOrSelected);
|
||||
|
@ -84,32 +84,53 @@ let DetailsView = {
|
|||
}),
|
||||
|
||||
/**
|
||||
* Sets the possible views based off of prefs and server actor support by hiding/showing the
|
||||
* buttons that select them and going to default view if currently selected.
|
||||
* Called when a preference changes in `devtools.performance.ui.`.
|
||||
* Sets the possible views based off of recording features and server actor support
|
||||
* by hiding/showing the buttons that select them and going to default view
|
||||
* if currently selected. Called when a preference changes in `devtools.performance.ui.`.
|
||||
*/
|
||||
setAvailableViews: Task.async(function* () {
|
||||
let mocks = gFront.getMocksInUse();
|
||||
let recording = PerformanceController.getCurrentRecording();
|
||||
let isRecording = recording && recording.isRecording();
|
||||
let invalidCurrentView = false;
|
||||
|
||||
for (let [name, { view, pref, requires }] of Iterator(this.components)) {
|
||||
let recording = PerformanceController.getCurrentRecording();
|
||||
for (let [name, { view }] of Iterator(this.components)) {
|
||||
let isSupported = this._isViewSupported(name, false);
|
||||
|
||||
let isRecorded = recording && !recording.isRecording();
|
||||
// View is enabled by its corresponding pref
|
||||
let isEnabled = !pref || PerformanceController.getOption(pref);
|
||||
// View is supported by the server actor, and the requried actor is not being mocked
|
||||
let isSupported = !requires || requires.every(r => !mocks[r]);
|
||||
$(`toolbarbutton[data-view=${name}]`).hidden = !isSupported;
|
||||
|
||||
$(`toolbarbutton[data-view=${name}]`).hidden = !isRecorded || !(isEnabled && isSupported);
|
||||
|
||||
// If the view is currently selected and not enabled, go back to the
|
||||
// If the view is currently selected and not supported, go back to the
|
||||
// default view.
|
||||
if (!isEnabled && this.isViewSelected(view)) {
|
||||
yield this.selectDefaultView();
|
||||
if (!isSupported && this.isViewSelected(view)) {
|
||||
invalidCurrentView = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Two scenarios in which we select the default view.
|
||||
//
|
||||
// 1: If we currently have selected a view that is no longer valid due
|
||||
// to feature support, and this isn't the first view, and the current recording
|
||||
// is not recording.
|
||||
//
|
||||
// 2. If we have a finished recording and no panel was selected yet,
|
||||
// use a default now that we have the recording configurations
|
||||
if ((this._initialized && !isRecording && invalidCurrentView) ||
|
||||
(!this._initialized && !isRecording && recording)) {
|
||||
yield this.selectDefaultView();
|
||||
}
|
||||
}),
|
||||
|
||||
/**
|
||||
* Takes a view name and optionally if there must be a currently recording in progress.
|
||||
*
|
||||
* @param {string} viewName
|
||||
* @param {boolean?} isRecording
|
||||
* @return {boolean}
|
||||
*/
|
||||
_isViewSupported: function (viewName, isRecording) {
|
||||
let { features, actors } = this.components[viewName];
|
||||
return PerformanceController.isFeatureSupported({ features, actors, isRecording });
|
||||
},
|
||||
|
||||
/**
|
||||
* Select one of the DetailView's subviews to be rendered,
|
||||
* hiding the others.
|
||||
|
@ -131,6 +152,10 @@ let DetailsView = {
|
|||
}
|
||||
}
|
||||
|
||||
// Set a flag indicating that a view was explicitly set based on a
|
||||
// recording's features.
|
||||
this._initialized = true;
|
||||
|
||||
this.emit(EVENTS.DETAILS_VIEW_SELECTED, viewName);
|
||||
}),
|
||||
|
||||
|
@ -139,14 +164,15 @@ let DetailsView = {
|
|||
* and preferences enabled.
|
||||
*/
|
||||
selectDefaultView: function () {
|
||||
let { timeline: mockTimeline } = gFront.getMocksInUse();
|
||||
// If timelines are mocked, the first view available is the js-calltree.
|
||||
if (mockTimeline) {
|
||||
return this.selectView("js-calltree");
|
||||
} else {
|
||||
// In every other scenario with preferences and mocks, waterfall will
|
||||
// be the default view.
|
||||
// We want the waterfall to be default view in almost all cases, except when
|
||||
// timeline actor isn't supported, or we have markers disabled (which should only
|
||||
// occur temporarily via bug 1156499
|
||||
if (this._isViewSupported("waterfall")) {
|
||||
return this.selectView("waterfall");
|
||||
} else {
|
||||
// The JS CallTree should always be supported since the profiler
|
||||
// actor is as old as the world.
|
||||
return this.selectView("js-calltree");
|
||||
}
|
||||
},
|
||||
|
||||
|
@ -157,6 +183,12 @@ let DetailsView = {
|
|||
* @return boolean
|
||||
*/
|
||||
isViewSelected: function(viewObject) {
|
||||
// If not initialized, and we have no recordings,
|
||||
// no views are selected (even though there's a selected panel)
|
||||
if (!this._initialized) {
|
||||
return false;
|
||||
}
|
||||
|
||||
let selectedPanel = this.el.selectedPanel;
|
||||
let selectedId = selectedPanel.id;
|
||||
|
||||
|
|
|
@ -7,26 +7,45 @@
|
|||
// backend. Make sure this isn't lower than DEFAULT_TIMELINE_DATA_PULL_TIMEOUT
|
||||
// in toolkit/devtools/server/actors/timeline.js
|
||||
const OVERVIEW_UPDATE_INTERVAL = 200; // ms
|
||||
|
||||
const FRAMERATE_GRAPH_LOW_RES_INTERVAL = 100; // ms
|
||||
const FRAMERATE_GRAPH_HIGH_RES_INTERVAL = 16; // ms
|
||||
|
||||
const MARKERS_GRAPH_HEADER_HEIGHT = 14; // px
|
||||
const MARKERS_GRAPH_ROW_HEIGHT = 10; // px
|
||||
const MARKERS_GROUP_VERTICAL_PADDING = 4; // px
|
||||
const GRAPH_REQUIREMENTS = {
|
||||
timeline: {
|
||||
actors: ["timeline"],
|
||||
features: ["withMarkers"]
|
||||
},
|
||||
framerate: {
|
||||
actors: ["timeline"],
|
||||
features: ["withTicks"]
|
||||
},
|
||||
memory: {
|
||||
actors: ["memory"],
|
||||
features: ["withMemory"]
|
||||
},
|
||||
}
|
||||
|
||||
/**
|
||||
* View handler for the overview panel's time view, displaying
|
||||
* framerate, markers and memory over time.
|
||||
* framerate, timeline and memory over time.
|
||||
*/
|
||||
let OverviewView = {
|
||||
|
||||
/**
|
||||
* Sets up the view with event binding.
|
||||
*/
|
||||
initialize: function () {
|
||||
if (gFront.getMocksInUse().timeline) {
|
||||
this.graphs = new GraphsController({
|
||||
root: $("#overview-pane"),
|
||||
getBlueprint: () => PerformanceController.getTimelineBlueprint(),
|
||||
getTheme: () => PerformanceController.getTheme(),
|
||||
});
|
||||
|
||||
// If no timeline support, shut it all down.
|
||||
if (!gFront.getActorSupport().timeline) {
|
||||
this.disable();
|
||||
return;
|
||||
}
|
||||
|
||||
this._onRecordingWillStart = this._onRecordingWillStart.bind(this);
|
||||
this._onRecordingStarted = this._onRecordingStarted.bind(this);
|
||||
this._onRecordingWillStop = this._onRecordingWillStop.bind(this);
|
||||
|
@ -34,14 +53,12 @@ let OverviewView = {
|
|||
this._onRecordingSelected = this._onRecordingSelected.bind(this);
|
||||
this._onRecordingTick = this._onRecordingTick.bind(this);
|
||||
this._onGraphSelecting = this._onGraphSelecting.bind(this);
|
||||
this._onGraphRendered = this._onGraphRendered.bind(this);
|
||||
this._onPrefChanged = this._onPrefChanged.bind(this);
|
||||
this._onThemeChanged = this._onThemeChanged.bind(this);
|
||||
|
||||
// Toggle the initial visibility of memory and framerate graph containers
|
||||
// based off of prefs.
|
||||
$("#memory-overview").hidden = !PerformanceController.getOption("enable-memory");
|
||||
$("#time-framerate").hidden = !PerformanceController.getOption("enable-framerate");
|
||||
|
||||
PerformanceController.on(EVENTS.PREF_CHANGED, this._onPrefChanged);
|
||||
PerformanceController.on(EVENTS.THEME_CHANGED, this._onThemeChanged);
|
||||
PerformanceController.on(EVENTS.RECORDING_WILL_START, this._onRecordingWillStart);
|
||||
|
@ -52,22 +69,14 @@ let OverviewView = {
|
|||
PerformanceController.on(EVENTS.CONSOLE_RECORDING_STARTED, this._onRecordingStarted);
|
||||
PerformanceController.on(EVENTS.CONSOLE_RECORDING_STOPPED, this._onRecordingStopped);
|
||||
PerformanceController.on(EVENTS.CONSOLE_RECORDING_WILL_STOP, this._onRecordingWillStop);
|
||||
this.graphs.on("selecting", this._onGraphSelecting);
|
||||
this.graphs.on("rendered", this._onGraphRendered);
|
||||
},
|
||||
|
||||
/**
|
||||
* Unbinds events.
|
||||
*/
|
||||
destroy: Task.async(function*() {
|
||||
if (this.markersOverview) {
|
||||
yield this.markersOverview.destroy();
|
||||
}
|
||||
if (this.memoryOverview) {
|
||||
yield this.memoryOverview.destroy();
|
||||
}
|
||||
if (this.framerateGraph) {
|
||||
yield this.framerateGraph.destroy();
|
||||
}
|
||||
|
||||
PerformanceController.off(EVENTS.PREF_CHANGED, this._onPrefChanged);
|
||||
PerformanceController.off(EVENTS.THEME_CHANGED, this._onThemeChanged);
|
||||
PerformanceController.off(EVENTS.RECORDING_WILL_START, this._onRecordingWillStart);
|
||||
|
@ -78,16 +87,19 @@ let OverviewView = {
|
|||
PerformanceController.off(EVENTS.CONSOLE_RECORDING_STARTED, this._onRecordingStarted);
|
||||
PerformanceController.off(EVENTS.CONSOLE_RECORDING_STOPPED, this._onRecordingStopped);
|
||||
PerformanceController.off(EVENTS.CONSOLE_RECORDING_WILL_STOP, this._onRecordingWillStop);
|
||||
this.graphs.off("selecting", this._onGraphSelecting);
|
||||
this.graphs.off("rendered", this._onGraphRendered);
|
||||
yield this.graphs.destroy();
|
||||
}),
|
||||
|
||||
/**
|
||||
* Disabled in the event we're using a Timeline mock, so we'll have no
|
||||
* markers, ticks or memory data to show, so just block rendering and hide
|
||||
* timeline, ticks or memory data to show, so just block rendering and hide
|
||||
* the panel.
|
||||
*/
|
||||
disable: function () {
|
||||
this._disabled = true;
|
||||
$("#overview-pane").hidden = true;
|
||||
this.graphs.disableAll();
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -99,28 +111,6 @@ let OverviewView = {
|
|||
return this._disabled;
|
||||
},
|
||||
|
||||
/**
|
||||
* Sets the theme for the markers overview and memory overview.
|
||||
*/
|
||||
setTheme: function (options={}) {
|
||||
let theme = options.theme || PerformanceController.getTheme();
|
||||
|
||||
if (this.framerateGraph) {
|
||||
this.framerateGraph.setTheme(theme);
|
||||
this.framerateGraph.refresh({ force: options.redraw });
|
||||
}
|
||||
|
||||
if (this.markersOverview) {
|
||||
this.markersOverview.setTheme(theme);
|
||||
this.markersOverview.refresh({ force: options.redraw });
|
||||
}
|
||||
|
||||
if (this.memoryOverview) {
|
||||
this.memoryOverview.setTheme(theme);
|
||||
this.memoryOverview.refresh({ force: options.redraw });
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Sets the time interval selection for all graphs in this overview.
|
||||
*
|
||||
|
@ -139,7 +129,7 @@ let OverviewView = {
|
|||
let mapEnd = () => recording.getDuration();
|
||||
let selection = { start: interval.startTime, end: interval.endTime };
|
||||
this._stopSelectionChangeEventPropagation = options.stopPropagation;
|
||||
this.markersOverview.setMappedSelection(selection, { mapStart, mapEnd });
|
||||
this.graphs.setMappedSelection(selection, { mapStart, mapEnd });
|
||||
this._stopSelectionChangeEventPropagation = false;
|
||||
},
|
||||
|
||||
|
@ -159,80 +149,10 @@ let OverviewView = {
|
|||
}
|
||||
let mapStart = () => 0;
|
||||
let mapEnd = () => recording.getDuration();
|
||||
let selection = this.markersOverview.getMappedSelection({ mapStart, mapEnd });
|
||||
let selection = this.graphs.getMappedSelection({ mapStart, mapEnd });
|
||||
return { startTime: selection.min, endTime: selection.max };
|
||||
},
|
||||
|
||||
/**
|
||||
* Sets up the markers overivew graph, if needed.
|
||||
*
|
||||
* @return object
|
||||
* A promise resolved to `true` when the graph was initialized.
|
||||
*/
|
||||
_markersGraphAvailable: Task.async(function *() {
|
||||
if (this.markersOverview) {
|
||||
yield this.markersOverview.ready();
|
||||
return true;
|
||||
}
|
||||
let blueprint = PerformanceController.getTimelineBlueprint();
|
||||
this.markersOverview = new MarkersOverview($("#markers-overview"), blueprint);
|
||||
this.markersOverview.headerHeight = MARKERS_GRAPH_HEADER_HEIGHT;
|
||||
this.markersOverview.rowHeight = MARKERS_GRAPH_ROW_HEIGHT;
|
||||
this.markersOverview.groupPadding = MARKERS_GROUP_VERTICAL_PADDING;
|
||||
this.markersOverview.on("selecting", this._onGraphSelecting);
|
||||
yield this.markersOverview.ready();
|
||||
this.setTheme();
|
||||
return true;
|
||||
}),
|
||||
|
||||
/**
|
||||
* Sets up the memory overview graph, if allowed and needed.
|
||||
*
|
||||
* @return object
|
||||
* A promise resolved to `true` if the graph was initialized and is
|
||||
* ready to use, `false` if the graph is disabled.
|
||||
*/
|
||||
_memoryGraphAvailable: Task.async(function *() {
|
||||
if (!PerformanceController.getOption("enable-memory")) {
|
||||
return false;
|
||||
}
|
||||
if (this.memoryOverview) {
|
||||
yield this.memoryOverview.ready();
|
||||
return true;
|
||||
}
|
||||
this.memoryOverview = new MemoryGraph($("#memory-overview"));
|
||||
yield this.memoryOverview.ready();
|
||||
this.setTheme();
|
||||
|
||||
CanvasGraphUtils.linkAnimation(this.markersOverview, this.memoryOverview);
|
||||
CanvasGraphUtils.linkSelection(this.markersOverview, this.memoryOverview);
|
||||
return true;
|
||||
}),
|
||||
|
||||
/**
|
||||
* Sets up the framerate graph, if allowed and needed.
|
||||
*
|
||||
* @return object
|
||||
* A promise resolved to `true` if the graph was initialized and is
|
||||
* ready to use, `false` if the graph is disabled.
|
||||
*/
|
||||
_framerateGraphAvailable: Task.async(function *() {
|
||||
if (!PerformanceController.getOption("enable-framerate")) {
|
||||
return false;
|
||||
}
|
||||
if (this.framerateGraph) {
|
||||
yield this.framerateGraph.ready();
|
||||
return true;
|
||||
}
|
||||
this.framerateGraph = new FramerateGraph($("#time-framerate"));
|
||||
yield this.framerateGraph.ready();
|
||||
this.setTheme();
|
||||
|
||||
CanvasGraphUtils.linkAnimation(this.markersOverview, this.framerateGraph);
|
||||
CanvasGraphUtils.linkSelection(this.markersOverview, this.framerateGraph);
|
||||
return true;
|
||||
}),
|
||||
|
||||
/**
|
||||
* Method for handling all the set up for rendering the overview graphs.
|
||||
*
|
||||
|
@ -244,29 +164,10 @@ let OverviewView = {
|
|||
return;
|
||||
}
|
||||
let recording = PerformanceController.getCurrentRecording();
|
||||
let duration = recording.getDuration();
|
||||
let markers = recording.getMarkers();
|
||||
let memory = recording.getMemory();
|
||||
let timestamps = recording.getTicks();
|
||||
|
||||
// Empty or older recordings might yield no markers, memory or timestamps.
|
||||
if (markers && (yield this._markersGraphAvailable())) {
|
||||
this.markersOverview.setData({ markers, duration });
|
||||
this.emit(EVENTS.MARKERS_GRAPH_RENDERED);
|
||||
}
|
||||
if (memory && (yield this._memoryGraphAvailable())) {
|
||||
this.memoryOverview.dataDuration = duration;
|
||||
this.memoryOverview.setData(memory);
|
||||
this.emit(EVENTS.MEMORY_GRAPH_RENDERED);
|
||||
}
|
||||
if (timestamps && (yield this._framerateGraphAvailable())) {
|
||||
this.framerateGraph.dataDuration = duration;
|
||||
yield this.framerateGraph.setDataFromTimestamps(timestamps, resolution);
|
||||
this.emit(EVENTS.FRAMERATE_GRAPH_RENDERED);
|
||||
}
|
||||
yield this.graphs.render(recording.getAllData(), resolution);
|
||||
|
||||
// Finished rendering all graphs in this overview.
|
||||
this.emit(EVENTS.OVERVIEW_RENDERED);
|
||||
this.emit(EVENTS.OVERVIEW_RENDERED, resolution);
|
||||
}),
|
||||
|
||||
/**
|
||||
|
@ -290,24 +191,6 @@ let OverviewView = {
|
|||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Fired when the graph selection has changed. Called by
|
||||
* mouseup and scroll events.
|
||||
*/
|
||||
_onGraphSelecting: function () {
|
||||
if (this._stopSelectionChangeEventPropagation) {
|
||||
return;
|
||||
}
|
||||
// If the range is smaller than a pixel (which can happen when performing
|
||||
// a click on the graphs), treat this as a cleared selection.
|
||||
let interval = this.getTimeInterval();
|
||||
if (interval.endTime - interval.startTime < 1) {
|
||||
this.emit(EVENTS.OVERVIEW_RANGE_CLEARED);
|
||||
} else {
|
||||
this.emit(EVENTS.OVERVIEW_RANGE_SELECTED, interval);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Called when recording will start. No recording because it does not
|
||||
* exist yet, but can just disable from here. This will only trigger for
|
||||
|
@ -316,7 +199,7 @@ let OverviewView = {
|
|||
_onRecordingWillStart: Task.async(function* () {
|
||||
this._onRecordingStateChange();
|
||||
yield this._checkSelection();
|
||||
this.markersOverview.dropSelection();
|
||||
this.graphs.dropSelection();
|
||||
}),
|
||||
|
||||
/**
|
||||
|
@ -358,12 +241,14 @@ let OverviewView = {
|
|||
return;
|
||||
}
|
||||
this._onRecordingStateChange();
|
||||
this._setGraphVisibilityFromRecordingFeatures(recording);
|
||||
|
||||
// If this recording is complete, render the high res graph
|
||||
if (!recording.isRecording()) {
|
||||
yield this.render(FRAMERATE_GRAPH_HIGH_RES_INTERVAL);
|
||||
}
|
||||
yield this._checkSelection(recording);
|
||||
this.markersOverview.dropSelection();
|
||||
this.graphs.dropSelection();
|
||||
}),
|
||||
|
||||
/**
|
||||
|
@ -407,49 +292,73 @@ let OverviewView = {
|
|||
* based on whether a recording currently exists and is not in progress.
|
||||
*/
|
||||
_checkSelection: Task.async(function* (recording) {
|
||||
let selectionEnabled = recording ? !recording.isRecording() : false;
|
||||
|
||||
if (yield this._markersGraphAvailable()) {
|
||||
this.markersOverview.selectionEnabled = selectionEnabled;
|
||||
}
|
||||
if (yield this._memoryGraphAvailable()) {
|
||||
this.memoryOverview.selectionEnabled = selectionEnabled;
|
||||
}
|
||||
if (yield this._framerateGraphAvailable()) {
|
||||
this.framerateGraph.selectionEnabled = selectionEnabled;
|
||||
}
|
||||
let isEnabled = recording ? !recording.isRecording() : false;
|
||||
yield this.graphs.selectionEnabled(isEnabled);
|
||||
}),
|
||||
|
||||
/**
|
||||
* Called whenever a preference in `devtools.performance.ui.` changes. Used
|
||||
* to toggle the visibility of memory and framerate graphs.
|
||||
* Fired when the graph selection has changed. Called by
|
||||
* mouseup and scroll events.
|
||||
*/
|
||||
_onGraphSelecting: function () {
|
||||
if (this._stopSelectionChangeEventPropagation) {
|
||||
return;
|
||||
}
|
||||
// If the range is smaller than a pixel (which can happen when performing
|
||||
// a click on the graphs), treat this as a cleared selection.
|
||||
let interval = this.getTimeInterval();
|
||||
if (interval.endTime - interval.startTime < 1) {
|
||||
this.emit(EVENTS.OVERVIEW_RANGE_CLEARED);
|
||||
} else {
|
||||
this.emit(EVENTS.OVERVIEW_RANGE_SELECTED, interval);
|
||||
}
|
||||
},
|
||||
|
||||
_onGraphRendered: function (_, graphName) {
|
||||
switch (graphName) {
|
||||
case "timeline":
|
||||
this.emit(EVENTS.MARKERS_GRAPH_RENDERED);
|
||||
break;
|
||||
case "memory":
|
||||
this.emit(EVENTS.MEMORY_GRAPH_RENDERED);
|
||||
break;
|
||||
case "framerate":
|
||||
this.emit(EVENTS.FRAMERATE_GRAPH_RENDERED);
|
||||
break;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Called whenever a preference in `devtools.performance.ui.` changes.
|
||||
* Does not care about the enabling of memory/framerate graphs,
|
||||
* because those will set values on a recording model, and
|
||||
* the graphs will render based on the existence.
|
||||
*/
|
||||
_onPrefChanged: Task.async(function* (_, prefName, prefValue) {
|
||||
switch (prefName) {
|
||||
case "enable-memory": {
|
||||
$("#memory-overview").hidden = !prefValue;
|
||||
break;
|
||||
}
|
||||
case "enable-framerate": {
|
||||
$("#time-framerate").hidden = !prefValue;
|
||||
break;
|
||||
}
|
||||
case "hidden-markers": {
|
||||
if (yield this._markersGraphAvailable()) {
|
||||
let graph;
|
||||
if (graph = yield this.graphs.isAvailable("timeline")) {
|
||||
let blueprint = PerformanceController.getTimelineBlueprint();
|
||||
this.markersOverview.setBlueprint(blueprint);
|
||||
this.markersOverview.refresh({ force: true });
|
||||
graph.setBlueprint(blueprint);
|
||||
graph.refresh({ force: true });
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}),
|
||||
|
||||
_setGraphVisibilityFromRecordingFeatures: function (recording) {
|
||||
for (let [graphName, requirements] of Iterator(GRAPH_REQUIREMENTS)) {
|
||||
this.graphs.enable(graphName, PerformanceController.isFeatureSupported(requirements));
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Called when `devtools.theme` changes.
|
||||
*/
|
||||
_onThemeChanged: function (_, theme) {
|
||||
this.setTheme({ theme, redraw: true });
|
||||
this.graphs.setTheme({ theme, redraw: true });
|
||||
},
|
||||
|
||||
toString: () => "[object OverviewView]"
|
||||
|
|
|
@ -83,6 +83,10 @@ Cu.import("resource://gre/modules/devtools/event-emitter.js");
|
|||
* focusable element.
|
||||
* {string} trigger: The DOM event that should trigger editing,
|
||||
* defaults to "click"
|
||||
* {boolean} multiline: Should the editor be a multiline textarea?
|
||||
* defaults to false
|
||||
* {boolean} trimOutput: Should the returned string be trimmed?
|
||||
* defaults to true
|
||||
*/
|
||||
function editableField(aOptions)
|
||||
{
|
||||
|
@ -189,6 +193,7 @@ function InplaceEditor(aOptions, aEvent)
|
|||
this.destroy = aOptions.destroy;
|
||||
this.initial = aOptions.initial ? aOptions.initial : this.elt.textContent;
|
||||
this.multiline = aOptions.multiline || false;
|
||||
this.trimOutput = aOptions.trimOutput === undefined ? true : !!aOptions.trimOutput;
|
||||
this.stopOnShiftTab = !!aOptions.stopOnShiftTab;
|
||||
this.stopOnTab = !!aOptions.stopOnTab;
|
||||
this.stopOnReturn = !!aOptions.stopOnReturn;
|
||||
|
@ -254,6 +259,12 @@ exports.InplaceEditor = InplaceEditor;
|
|||
InplaceEditor.CONTENT_TYPES = CONTENT_TYPES;
|
||||
|
||||
InplaceEditor.prototype = {
|
||||
|
||||
get currentInputValue() {
|
||||
let val = this.trimOutput ? this.input.value.trim() : this.input.value;
|
||||
return val;
|
||||
},
|
||||
|
||||
_createInput: function InplaceEditor_createEditor()
|
||||
{
|
||||
this.input =
|
||||
|
@ -398,7 +409,7 @@ InplaceEditor.prototype = {
|
|||
|
||||
// Call the user's change handler if available.
|
||||
if (this.change) {
|
||||
this.change(this.input.value.trim());
|
||||
this.change(this.currentInputValue);
|
||||
}
|
||||
|
||||
return true;
|
||||
|
@ -790,8 +801,8 @@ InplaceEditor.prototype = {
|
|||
this._applied = true;
|
||||
|
||||
if (this.done) {
|
||||
let val = this.input.value.trim();
|
||||
return this.done(this.cancelled ? this.initial : val, !this.cancelled, direction);
|
||||
let val = this.cancelled ? this.initial : this.currentInputValue;
|
||||
return this.done(val, !this.cancelled, direction);
|
||||
}
|
||||
|
||||
return null;
|
||||
|
@ -1034,7 +1045,7 @@ InplaceEditor.prototype = {
|
|||
|
||||
// Call the user's change handler if available.
|
||||
if (this.change) {
|
||||
this.change(this.input.value.trim());
|
||||
this.change(this.currentInputValue);
|
||||
}
|
||||
},
|
||||
|
||||
|
|
|
@ -117,14 +117,20 @@ exports.filterPlatformData = function filterPlatformData (frames) {
|
|||
*/
|
||||
function nsIURL(url) {
|
||||
let cached = gNSURLStore.get(url);
|
||||
if (cached) {
|
||||
// If we cached a valid URI, or `null` in the case
|
||||
// of a failure, return it.
|
||||
if (cached !== void 0) {
|
||||
return cached;
|
||||
}
|
||||
let uri = null;
|
||||
try {
|
||||
uri = Services.io.newURI(url, null, null).QueryInterface(Ci.nsIURL);
|
||||
// Access the host, because the constructor doesn't necessarily throw
|
||||
// if it's invalid, but accessing the host can throw as well
|
||||
uri.host;
|
||||
} catch(e) {
|
||||
// The passed url string is invalid.
|
||||
uri = null;
|
||||
}
|
||||
gNSURLStore.set(url, uri);
|
||||
return uri;
|
||||
|
|
|
@ -70,7 +70,9 @@ support-files =
|
|||
[browser_graphs-12.js]
|
||||
[browser_graphs-13.js]
|
||||
[browser_graphs-14.js]
|
||||
[browser_inplace-editor.js]
|
||||
[browser_inplace-editor-01.js]
|
||||
[browser_inplace-editor-02.js]
|
||||
[browser_graphs-15.js]
|
||||
[browser_layoutHelpers.js]
|
||||
skip-if = e10s # Layouthelpers test should not run in a content page.
|
||||
[browser_layoutHelpers-getBoxQuads.js]
|
||||
|
|
|
@ -0,0 +1,47 @@
|
|||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
// Tests that graph widgets correctly emit mouse input events.
|
||||
|
||||
const FAST_FPS = 60;
|
||||
const SLOW_FPS = 10;
|
||||
// Each element represents a second
|
||||
const FRAMES= [FAST_FPS, FAST_FPS, FAST_FPS, SLOW_FPS, FAST_FPS];
|
||||
const TEST_DATA = [];
|
||||
const INTERVAL = 100;
|
||||
const DURATION = 5000; // 5s
|
||||
let t = 0;
|
||||
for (let frameRate of FRAMES) {
|
||||
for (let i = 0; i < frameRate; i++) {
|
||||
let delta = Math.floor(1000 / frameRate); // Duration between frames at this rate
|
||||
t += delta;
|
||||
TEST_DATA.push(t);
|
||||
}
|
||||
}
|
||||
|
||||
let {LineGraphWidget} = Cu.import("resource:///modules/devtools/Graphs.jsm", {});
|
||||
let {Promise} = devtools.require("resource://gre/modules/Promise.jsm");
|
||||
|
||||
add_task(function*() {
|
||||
yield promiseTab("about:blank");
|
||||
yield performTest();
|
||||
gBrowser.removeCurrentTab();
|
||||
});
|
||||
|
||||
function* performTest() {
|
||||
let [host, win, doc] = yield createHost();
|
||||
let graph = new LineGraphWidget(doc.body, "fps");
|
||||
|
||||
yield testGraph(graph);
|
||||
|
||||
yield graph.destroy();
|
||||
host.destroy();
|
||||
}
|
||||
|
||||
function* testGraph(graph) {
|
||||
|
||||
console.log("test data", TEST_DATA);
|
||||
yield graph.setDataFromTimestamps(TEST_DATA, INTERVAL, DURATION);
|
||||
is(graph._avgTooltip.querySelector("[text=value]").textContent, "50",
|
||||
"The average tooltip displays the correct value.");
|
||||
}
|
|
@ -0,0 +1,91 @@
|
|||
/* vim: set ts=2 et sw=2 tw=80: */
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
"use strict";
|
||||
|
||||
let {Promise: promise} = Cu.import("resource://gre/modules/Promise.jsm", {});
|
||||
let {editableField, getInplaceEditorForSpan: inplaceEditor} = devtools.require("devtools/shared/inplace-editor");
|
||||
|
||||
// Test that the trimOutput option for the inplace editor works correctly.
|
||||
|
||||
add_task(function*() {
|
||||
yield promiseTab("data:text/html;charset=utf-8,inline editor tests");
|
||||
let [host, win, doc] = yield createHost();
|
||||
|
||||
yield testNonTrimmed(doc);
|
||||
yield testTrimmed(doc);
|
||||
|
||||
host.destroy();
|
||||
gBrowser.removeCurrentTab();
|
||||
});
|
||||
|
||||
function testNonTrimmed(doc) {
|
||||
info("Testing the trimOutput=false option");
|
||||
let def = promise.defer();
|
||||
|
||||
let initial = "\nMultiple\nLines\n";
|
||||
let changed = " \nMultiple\nLines\n with more whitespace ";
|
||||
createInplaceEditorAndClick({
|
||||
trimOutput: false,
|
||||
multiline: true,
|
||||
initial: initial,
|
||||
start: function(editor) {
|
||||
is(editor.input.value, initial, "Explicit initial value should be used.");
|
||||
editor.input.value = changed;
|
||||
EventUtils.sendKey("return");
|
||||
},
|
||||
done: onDone(changed, true, def)
|
||||
}, doc);
|
||||
|
||||
return def.promise;
|
||||
}
|
||||
|
||||
function testTrimmed(doc) {
|
||||
info("Testing the trimOutput=true option (default value)");
|
||||
let def = promise.defer();
|
||||
|
||||
let initial = "\nMultiple\nLines\n";
|
||||
let changed = " \nMultiple\nLines\n with more whitespace ";
|
||||
createInplaceEditorAndClick({
|
||||
initial: initial,
|
||||
multiline: true,
|
||||
start: function(editor) {
|
||||
is(editor.input.value, initial, "Explicit initial value should be used.");
|
||||
editor.input.value = changed;
|
||||
EventUtils.sendKey("return");
|
||||
},
|
||||
done: onDone(changed.trim(), true, def)
|
||||
}, doc);
|
||||
|
||||
return def.promise;
|
||||
}
|
||||
|
||||
function onDone(value, isCommit, def) {
|
||||
return function(actualValue, actualCommit) {
|
||||
info("Inplace-editor's done callback executed, checking its state");
|
||||
is(actualValue, value, "The value is correct");
|
||||
is(actualCommit, isCommit, "The commit boolean is correct");
|
||||
def.resolve();
|
||||
}
|
||||
}
|
||||
|
||||
function createInplaceEditorAndClick(options, doc) {
|
||||
doc.body.innerHTML = "";
|
||||
let span = options.element = createSpan(doc);
|
||||
|
||||
info("Creating an inplace-editor field");
|
||||
editableField(options);
|
||||
|
||||
info("Clicking on the inplace-editor field to turn to edit mode");
|
||||
span.click();
|
||||
}
|
||||
|
||||
function createSpan(doc) {
|
||||
info("Creating a new span element");
|
||||
let span = doc.createElement("span");
|
||||
span.setAttribute("tabindex", "0");
|
||||
span.textContent = "Edit Me!";
|
||||
doc.body.appendChild(span);
|
||||
return span;
|
||||
}
|
|
@ -21,7 +21,8 @@ const L10N = new ViewHelpers.L10N(STRINGS_URI);
|
|||
* - label: the label used in the waterfall to identify the marker
|
||||
* - colorName: the name of the DevTools color used for this marker. If adding
|
||||
* a new color, be sure to check that there's an entry for
|
||||
* `.marker-details-bullet.{COLORNAME}` for the equivilent entry.
|
||||
* `.marker-details-bullet.{COLORNAME}` for the equivilent entry
|
||||
* in ./browser/themes/shared/devtools/performance.inc.css
|
||||
* https://developer.mozilla.org/en-US/docs/Tools/DevToolsColors
|
||||
*
|
||||
* Whenever this is changed, browser_timeline_waterfall-styles.js *must* be
|
||||
|
@ -68,6 +69,11 @@ const TIMELINE_BLUEPRINT = {
|
|||
colorName: "highlight-bluegrey",
|
||||
label: L10N.getStr("timeline.label.consoleTime")
|
||||
},
|
||||
"GarbageCollection": {
|
||||
group: 1,
|
||||
colorName: "highlight-red",
|
||||
label: L10N.getStr("timeline.label.garbageCollection")
|
||||
},
|
||||
};
|
||||
|
||||
// Exported symbols.
|
||||
|
|
|
@ -1302,14 +1302,15 @@ LineGraphWidget.prototype = Heritage.extend(AbstractCanvasGraph.prototype, {
|
|||
* represents the elapsed time on each refresh driver tick.
|
||||
* @param number interval
|
||||
* The maximum amount of time to wait between calculations.
|
||||
* @param number duration
|
||||
* The duration of the recording in milliseconds.
|
||||
*/
|
||||
setDataFromTimestamps: Task.async(function*(timestamps, interval) {
|
||||
setDataFromTimestamps: Task.async(function*(timestamps, interval, duration) {
|
||||
let {
|
||||
plottedData,
|
||||
plottedMinMaxSum
|
||||
} = yield CanvasGraphUtils._performTaskInWorker("plotTimestampsGraph", {
|
||||
timestamps: timestamps,
|
||||
interval: interval
|
||||
timestamps, interval, duration
|
||||
});
|
||||
|
||||
this._tempMinMaxSum = plottedMinMaxSum;
|
||||
|
|
|
@ -21,10 +21,11 @@ self.onmessage = e => {
|
|||
* @param number id
|
||||
* @param array timestamps
|
||||
* @param number interval
|
||||
* @param number duration
|
||||
*/
|
||||
function plotTimestampsGraph(id, args) {
|
||||
let plottedData = plotTimestamps(args.timestamps, args.interval);
|
||||
let plottedMinMaxSum = getMinMaxSum(plottedData);
|
||||
let plottedMinMaxSum = getMinMaxAvg(plottedData, args.timestamps, args.duration);
|
||||
|
||||
let response = { id, plottedData, plottedMinMaxSum };
|
||||
self.postMessage(response);
|
||||
|
@ -33,21 +34,25 @@ function plotTimestampsGraph(id, args) {
|
|||
/**
|
||||
* Gets the min, max and average of the values in an array.
|
||||
* @param array source
|
||||
* @param array timestamps
|
||||
* @param number duration
|
||||
* @return object
|
||||
*/
|
||||
function getMinMaxSum(source) {
|
||||
function getMinMaxAvg(source, timestamps, duration) {
|
||||
let totalTicks = source.length;
|
||||
let totalFrames = timestamps.length;
|
||||
let maxValue = Number.MIN_SAFE_INTEGER;
|
||||
let minValue = Number.MAX_SAFE_INTEGER;
|
||||
let avgValue = 0;
|
||||
let sumValues = 0;
|
||||
// Calculate the average by counting how many frames occurred
|
||||
// in the duration of the recording, rather than average the frame points
|
||||
// we have, as that weights higher FPS, as there'll be more timestamps for those
|
||||
// values
|
||||
let avgValue = totalFrames / (duration / 1000);
|
||||
|
||||
for (let { value } of source) {
|
||||
maxValue = Math.max(value, maxValue);
|
||||
minValue = Math.min(value, minValue);
|
||||
sumValues += value;
|
||||
}
|
||||
avgValue = sumValues / totalTicks;
|
||||
|
||||
return { minValue, maxValue, avgValue };
|
||||
}
|
||||
|
|
|
@ -44,6 +44,7 @@ timeline.label.parseHTML=Parse HTML
|
|||
timeline.label.parseXML=Parse XML
|
||||
timeline.label.domevent=DOM Event
|
||||
timeline.label.consoleTime=Console
|
||||
timeline.label.garbageCollection=GC Event
|
||||
|
||||
# LOCALIZATION NOTE (graphs.memory):
|
||||
# This string is displayed in the memory graph of the Performance tool,
|
||||
|
|
|
@ -578,7 +578,7 @@ menuitem:not([type]):not(.menuitem-tooltip):not(.menuitem-iconic-tooltip) {
|
|||
.findbar-button > .toolbarbutton-text,
|
||||
:-moz-any(#TabsToolbar, #nav-bar) .toolbarbutton-1 > .toolbarbutton-menubutton-button > .toolbarbutton-icon,
|
||||
:-moz-any(#TabsToolbar, #nav-bar) .toolbarbutton-1 > .toolbarbutton-menubutton-dropmarker > .dropmarker-icon,
|
||||
:-moz-any(#TabsToolbar, #nav-bar) .toolbarbutton-1 > .toolbarbutton-badge-container > .toolbarbutton-icon,
|
||||
:-moz-any(#TabsToolbar, #nav-bar) .toolbarbutton-1 > .toolbarbutton-badge-container,
|
||||
:-moz-any(#TabsToolbar, #nav-bar) .toolbarbutton-1 > .toolbarbutton-icon {
|
||||
-moz-margin-end: 0;
|
||||
padding: 2px 6px;
|
||||
|
@ -589,7 +589,7 @@ menuitem:not([type]):not(.menuitem-tooltip):not(.menuitem-iconic-tooltip) {
|
|||
}
|
||||
|
||||
:-moz-any(#TabsToolbar, #nav-bar) .toolbarbutton-1:not(:-moz-any(@primaryToolbarButtons@)) > .toolbarbutton-icon,
|
||||
:-moz-any(#TabsToolbar, #nav-bar) .toolbarbutton-1:not(:-moz-any(@primaryToolbarButtons@)) > .toolbarbutton-badge-container > .toolbarbutton-icon,
|
||||
:-moz-any(#TabsToolbar, #nav-bar) .toolbarbutton-1:not(:-moz-any(@primaryToolbarButtons@)) > .toolbarbutton-badge-container,
|
||||
:-moz-any(#TabsToolbar, #nav-bar) .toolbarbutton-1:not(:-moz-any(@primaryToolbarButtons@)) > .toolbarbutton-menubutton-button > .toolbarbutton-icon {
|
||||
padding: 3px 7px;
|
||||
}
|
||||
|
@ -614,7 +614,7 @@ toolbarbutton[constrain-size="true"][cui-areatype="toolbar"] > .toolbarbutton-ba
|
|||
:-moz-any(#TabsToolbar, #nav-bar) .toolbarbutton-1[open]:not([disabled=true]) > .toolbarbutton-menubutton-button > .toolbarbutton-icon,
|
||||
:-moz-any(#TabsToolbar, #nav-bar) .toolbarbutton-1:not([disabled=true]):not([open]):hover > .toolbarbutton-menubutton-button > .toolbarbutton-icon,
|
||||
:-moz-any(#TabsToolbar, #nav-bar) .toolbarbutton-1:not([disabled=true]):not([open]):hover > .toolbarbutton-menubutton-dropmarker > .dropmarker-icon,
|
||||
:-moz-any(#TabsToolbar, #nav-bar) .toolbarbutton-1:not([disabled=true]):hover > .toolbarbutton-badge-container > .toolbarbutton-icon,
|
||||
:-moz-any(#TabsToolbar, #nav-bar) .toolbarbutton-1:not([disabled=true]):hover > .toolbarbutton-badge-container,
|
||||
:-moz-any(#TabsToolbar, #nav-bar) .toolbarbutton-1:not([disabled=true]):hover > .toolbarbutton-icon {
|
||||
background: var(--toolbarbutton-hover-background);
|
||||
border-width: 1px;
|
||||
|
@ -632,7 +632,7 @@ toolbarbutton[constrain-size="true"][cui-areatype="toolbar"] > .toolbarbutton-ba
|
|||
.findbar-button:not([disabled=true]):-moz-any([checked="true"],:hover:active) > .toolbarbutton-text,
|
||||
:-moz-any(#TabsToolbar, #nav-bar) .toolbarbutton-1 > .toolbarbutton-menubutton-button:not([disabled=true]):-moz-any(:hover:active, [open="true"]) > .toolbarbutton-icon,
|
||||
:-moz-any(#TabsToolbar, #nav-bar) .toolbarbutton-1[open="true"] > .toolbarbutton-menubutton-dropmarker:not([disabled=true]) > .dropmarker-icon,
|
||||
:-moz-any(#TabsToolbar, #nav-bar) .toolbarbutton-1:not([disabled=true]):-moz-any([open],[checked],:hover:active) > .toolbarbutton-badge-container > .toolbarbutton-icon,
|
||||
:-moz-any(#TabsToolbar, #nav-bar) .toolbarbutton-1:not([disabled=true]):-moz-any([open],[checked],:hover:active) > .toolbarbutton-badge-container,
|
||||
:-moz-any(#TabsToolbar, #nav-bar) .toolbarbutton-1:not([disabled=true]):-moz-any([open],[checked],:hover:active) > .toolbarbutton-icon {
|
||||
background: var(--toolbarbutton-active-background);
|
||||
box-shadow: var(--toolbarbutton-active-boxshadow);
|
||||
|
|
|
@ -367,6 +367,7 @@ browser.jar:
|
|||
skin/classic/browser/devtools/tool-styleeditor.svg (../shared/devtools/images/tool-styleeditor.svg)
|
||||
skin/classic/browser/devtools/tool-storage.svg (../shared/devtools/images/tool-storage.svg)
|
||||
skin/classic/browser/devtools/tool-profiler.svg (../shared/devtools/images/tool-profiler.svg)
|
||||
skin/classic/browser/devtools/tool-profiler-active.svg (../shared/devtools/images/tool-profiler-active.svg)
|
||||
skin/classic/browser/devtools/tool-network.svg (../shared/devtools/images/tool-network.svg)
|
||||
skin/classic/browser/devtools/tool-scratchpad.svg (../shared/devtools/images/tool-scratchpad.svg)
|
||||
skin/classic/browser/devtools/tool-webaudio.svg (../shared/devtools/images/tool-webaudio.svg)
|
||||
|
|
|
@ -500,6 +500,7 @@ browser.jar:
|
|||
skin/classic/browser/devtools/tool-styleeditor.svg (../shared/devtools/images/tool-styleeditor.svg)
|
||||
skin/classic/browser/devtools/tool-storage.svg (../shared/devtools/images/tool-storage.svg)
|
||||
skin/classic/browser/devtools/tool-profiler.svg (../shared/devtools/images/tool-profiler.svg)
|
||||
skin/classic/browser/devtools/tool-profiler-active.svg (../shared/devtools/images/tool-profiler-active.svg)
|
||||
skin/classic/browser/devtools/tool-network.svg (../shared/devtools/images/tool-network.svg)
|
||||
skin/classic/browser/devtools/tool-scratchpad.svg (../shared/devtools/images/tool-scratchpad.svg)
|
||||
skin/classic/browser/devtools/tool-webaudio.svg (../shared/devtools/images/tool-webaudio.svg)
|
||||
|
|
|
@ -0,0 +1,14 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16" xmlns:xlink="http://www.w3.org/1999/xlink" enable-background="new 0 0 16 16">
|
||||
<g fill="#71c054" fill-rule="evenodd">
|
||||
<path d="m8,1c-3.9,0-7,3.1-7,7s3.1,7 7,7c3.9,0 7-3.1 7-7s-3.1-7-7-7zm-.1,12c-2.8,0-5-2.2-5-5 0-2.8 2.2-5 5-5s5,2.2 5,5c0,2.8-2.2,5-5,5z"/>
|
||||
<path d="m8,6.9c.6,0 1.1,.5 1.1,1.1 0,.6-.5,1.1-1.1,1.1-.6,0-1.1-.5-1.1-1.1 0-.6 .5-1.1 1.1-1.1z"/>
|
||||
<path d="m11.3,4.6l-3.9,2.5 1.5,1.4 2.4-3.9z"/>
|
||||
<path opacity=".4" d="m4.6,10c.7,1.2 2,2 3.4,2 1.5,0 2.7-.8 3.4-2h-6.8z"/>
|
||||
<g opacity=".3">
|
||||
<path d="m7.1,5.1l-.6-1.3-.9,.4 .7,1.3c.2-.1 .5-.3 .8-.4z"/>
|
||||
<path d="m9.8,5.6l.7-1.4-.9-.4-.7,1.3c.3,.2 .6,.3 .9,.5z"/>
|
||||
<path d="m10.8,7c.1,.3 .2,.7 .2,1h2v-1h-2.2z"/>
|
||||
<path d="m5,8c0-.3 .1-.7 .2-1h-2.2l-.1,1h2.1z"/>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
После Ширина: | Высота: | Размер: 814 B |
|
@ -447,6 +447,13 @@
|
|||
.waterfall-marker-bullet.highlight-lightorange {
|
||||
background-color: var(--theme-highlight-lightorange);
|
||||
}
|
||||
#performance-filter-menupopup > menuitem.highlight-red:before,
|
||||
.marker-details-bullet.highlight-red,
|
||||
.waterfall-marker-bar.highlight-red,
|
||||
.waterfall-marker-bullet.highlight-red {
|
||||
background-color: var(--theme-highlight-red);
|
||||
}
|
||||
|
||||
|
||||
#waterfall-details > * {
|
||||
padding-top: 3px;
|
||||
|
|
|
@ -442,6 +442,7 @@ browser.jar:
|
|||
skin/classic/browser/devtools/tool-styleeditor.svg (../shared/devtools/images/tool-styleeditor.svg)
|
||||
skin/classic/browser/devtools/tool-storage.svg (../shared/devtools/images/tool-storage.svg)
|
||||
skin/classic/browser/devtools/tool-profiler.svg (../shared/devtools/images/tool-profiler.svg)
|
||||
skin/classic/browser/devtools/tool-profiler-active.svg (../shared/devtools/images/tool-profiler-active.svg)
|
||||
skin/classic/browser/devtools/tool-network.svg (../shared/devtools/images/tool-network.svg)
|
||||
skin/classic/browser/devtools/tool-scratchpad.svg (../shared/devtools/images/tool-scratchpad.svg)
|
||||
skin/classic/browser/devtools/tool-webaudio.svg (../shared/devtools/images/tool-webaudio.svg)
|
||||
|
|
|
@ -2,6 +2,9 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="org.mozilla.roboexample.test"
|
||||
#ifdef MOZ_ANDROID_SHARED_ID
|
||||
android:sharedUserId="@MOZ_ANDROID_SHARED_ID@"
|
||||
#endif
|
||||
android:versionCode="1"
|
||||
android:versionName="1.0" >
|
||||
|
||||
|
|
|
@ -39,14 +39,19 @@ java-tests := \
|
|||
$(wildcard $(TESTPATH)/components/*.java) \
|
||||
$(wildcard $(TESTPATH)/helpers/*.java)
|
||||
|
||||
PP_TARGETS += manifest
|
||||
manifest := $(srcdir)/AndroidManifest.xml.in
|
||||
manifest_TARGET := AndroidManifest.xml
|
||||
PP_TARGETS += manifest
|
||||
manifest := $(srcdir)/AndroidManifest.xml.in
|
||||
manifest_TARGET := export
|
||||
manifest_FLAGS += \
|
||||
-DMOZ_ANDROID_SHARED_ID='$(ANDROID_PACKAGE_NAME).sharedID' \
|
||||
-DMOZ_ANDROID_SHARED_ACCOUNT_TYPE='$(ANDROID_PACKAGE_NAME)_sync' \
|
||||
$(NULL)
|
||||
|
||||
ANDROID_MANIFEST_FILE := $(CURDIR)/AndroidManifest.xml
|
||||
|
||||
# Install robocop configs and helper
|
||||
INSTALL_TARGETS += robocop
|
||||
robocop_TARGET := libs
|
||||
robocop_TARGET := export
|
||||
robocop_DEST := $(CURDIR)
|
||||
robocop_FILES := \
|
||||
$(TESTPATH)/robocop.ini \
|
||||
|
@ -68,7 +73,8 @@ ROBOCOP_FILES := \
|
|||
$(wildcard $(TESTPATH)/reader_mode_pages) \
|
||||
$(NULL)
|
||||
|
||||
ROBOCOP_DEST = $(DEPTH)/_tests/testing/mochitest/tests/robocop/
|
||||
ROBOCOP_DEST := $(DEPTH)/_tests/testing/mochitest/tests/robocop/
|
||||
ROBOCOP_TARGET := export
|
||||
INSTALL_TARGETS += ROBOCOP
|
||||
|
||||
GARBAGE += \
|
||||
|
|
|
@ -5192,6 +5192,7 @@ if test "$MOZ_WIDGET_TOOLKIT" = "cocoa"; then
|
|||
MOZ_APPLEMEDIA=1
|
||||
fi
|
||||
|
||||
if test "$COMPILE_ENVIRONMENT"; then
|
||||
if test -n "$MOZ_APPLEMEDIA"; then
|
||||
AC_DEFINE(MOZ_APPLEMEDIA)
|
||||
# hack in frameworks for fmp4 - see bug 1029974
|
||||
|
@ -5201,6 +5202,7 @@ if test -n "$MOZ_APPLEMEDIA"; then
|
|||
AC_CHECK_HEADER([CoreMedia/CoreMedia.h], [],
|
||||
[AC_MSG_ERROR([MacOS X 10.7 SDK or later is required])])
|
||||
fi
|
||||
fi # COMPILE_ENVIRONMENT
|
||||
|
||||
dnl ========================================================
|
||||
dnl = DirectShow support
|
||||
|
|
|
@ -155,7 +155,7 @@ compartment.
|
|||
and end events.
|
||||
|
||||
`reason`
|
||||
: A very short string describing th reason why the collection was
|
||||
: A very short string describing the reason why the collection was
|
||||
triggered. Known values include the following:
|
||||
|
||||
* "API"
|
||||
|
@ -199,7 +199,13 @@ compartment.
|
|||
: If SpiderMonkey's collector determined it could not incrementally
|
||||
collect garbage, and had to do a full GC all at once, this is a short
|
||||
string describing the reason it determined the full GC was necessary.
|
||||
Otherwise, `null` is returned.
|
||||
Otherwise, `null` is returned. Known values include the following:
|
||||
|
||||
* "GC mode"
|
||||
* "malloc bytes trigger"
|
||||
* "allocation trigger"
|
||||
* "requested"
|
||||
|
||||
|
||||
Function Properties of the `Debugger.Memory.prototype` Object
|
||||
-------------------------------------------------------------
|
||||
|
|
|
@ -54,6 +54,7 @@ class MachCommands(MachCommandBase):
|
|||
ensure_exit_code=False, # Don't throw on non-zero exit code.
|
||||
cwd=mozpath.join(self.topobjdir, 'mobile', 'android', 'gradle'))
|
||||
|
||||
|
||||
@Command('gradle-install', category='devenv',
|
||||
description='Install gradle environment.',
|
||||
conditions=[conditions.is_android])
|
||||
|
@ -115,6 +116,7 @@ class MachCommands(MachCommandBase):
|
|||
|
||||
srcdir('app/build.gradle', 'mobile/android/gradle/app/build.gradle')
|
||||
objdir('app/src/main/AndroidManifest.xml', 'mobile/android/base/AndroidManifest.xml')
|
||||
objdir('app/src/androidTest/AndroidManifest.xml', 'build/mobile/robocop/AndroidManifest.xml')
|
||||
srcdir('app/src/androidTest/res', 'build/mobile/robocop/res')
|
||||
srcdir('app/src/androidTest/assets', 'mobile/android/base/tests/assets')
|
||||
objdir('app/src/debug/assets', 'dist/fennec/assets')
|
||||
|
|
|
@ -475,12 +475,20 @@
|
|||
<xul:image class="popup-notification-icon"
|
||||
xbl:inherits="popupid,src=icon"/>
|
||||
</xul:vbox>
|
||||
<xul:vbox flex="1">
|
||||
<xul:label class="popup-notification-originHost header"
|
||||
xbl:inherits="value=originhost"
|
||||
crop="end"/>
|
||||
<xul:description class="popup-notification-description"
|
||||
xbl:inherits="xbl:text=label,popupid"/>
|
||||
<xul:vbox>
|
||||
<xul:hbox align="start">
|
||||
<xul:vbox flex="1">
|
||||
<xul:label class="popup-notification-originHost header"
|
||||
xbl:inherits="value=originhost"
|
||||
crop="end"/>
|
||||
<xul:description class="popup-notification-description"
|
||||
xbl:inherits="xbl:text=label,popupid"/>
|
||||
</xul:vbox>
|
||||
<xul:toolbarbutton anonid="closebutton"
|
||||
class="messageCloseButton close-icon popup-notification-closebutton tabbable"
|
||||
xbl:inherits="oncommand=closebuttoncommand"
|
||||
tooltiptext="&closeNotification.tooltip;"/>
|
||||
</xul:hbox>
|
||||
<children includes="popupnotificationcontent"/>
|
||||
<xul:label class="text-link popup-notification-learnmore-link"
|
||||
xbl:inherits="href=learnmoreurl">&learnMore;</xul:label>
|
||||
|
@ -502,12 +510,6 @@
|
|||
</xul:button>
|
||||
</xul:hbox>
|
||||
</xul:vbox>
|
||||
<xul:vbox pack="start">
|
||||
<xul:toolbarbutton anonid="closebutton"
|
||||
class="messageCloseButton close-icon popup-notification-closebutton tabbable"
|
||||
xbl:inherits="oncommand=closebuttoncommand"
|
||||
tooltiptext="&closeNotification.tooltip;"/>
|
||||
</xul:vbox>
|
||||
</content>
|
||||
<resources>
|
||||
<stylesheet src="chrome://global/skin/notification.css"/>
|
||||
|
|
|
@ -4,41 +4,22 @@
|
|||
|
||||
"use strict";
|
||||
|
||||
const { Cc, Ci, Cu } = require("chrome");
|
||||
let protocol = require("devtools/server/protocol");
|
||||
let { method, RetVal, Arg, types } = protocol;
|
||||
const { reportException } = require("devtools/toolkit/DevToolsUtils");
|
||||
const protocol = require("devtools/server/protocol");
|
||||
const { method, RetVal, Arg, types } = protocol;
|
||||
const { MemoryBridge } = require("./utils/memory-bridge");
|
||||
loader.lazyRequireGetter(this, "events", "sdk/event/core");
|
||||
loader.lazyRequireGetter(this, "StackFrameCache",
|
||||
"devtools/server/actors/utils/stack", true);
|
||||
|
||||
/**
|
||||
* A method decorator that ensures the actor is in the expected state before
|
||||
* proceeding. If the actor is not in the expected state, the decorated method
|
||||
* returns a rejected promise.
|
||||
*
|
||||
* @param String expectedState
|
||||
* The expected state.
|
||||
* @param String activity
|
||||
* Additional info about what's going on.
|
||||
* @param Function method
|
||||
* The actor method to proceed with when the actor is in the expected
|
||||
* state.
|
||||
*
|
||||
* @returns Function
|
||||
* The decorated method.
|
||||
* Proxies a call to the MemoryActor to the underlying MemoryBridge,
|
||||
* allowing access to MemoryBridge features by defining the RDP
|
||||
* request/response signature.
|
||||
*/
|
||||
function expectState(expectedState, method, activity) {
|
||||
return function(...args) {
|
||||
if (this.state !== expectedState) {
|
||||
const msg = `Wrong state while ${activity}:` +
|
||||
`Expected '${expectedState}',` +
|
||||
`but current state is '${this.state}'.`;
|
||||
return Promise.reject(new Error(msg));
|
||||
}
|
||||
|
||||
return method.apply(this, args);
|
||||
};
|
||||
function linkBridge (methodName, definition) {
|
||||
return method(function () {
|
||||
return this.bridge[methodName].apply(this.bridge, arguments);
|
||||
}, definition);
|
||||
}
|
||||
|
||||
types.addDictType("AllocationsRecordingOptions", {
|
||||
|
@ -66,6 +47,7 @@ let MemoryActor = protocol.ActorClass({
|
|||
* The set of unsolicited events the MemoryActor emits that will be sent over
|
||||
* the RDP (by protocol.js).
|
||||
*/
|
||||
|
||||
events: {
|
||||
// Same format as the data passed to the
|
||||
// `Debugger.Memory.prototype.onGarbageCollection` hook. See
|
||||
|
@ -76,37 +58,17 @@ let MemoryActor = protocol.ActorClass({
|
|||
},
|
||||
},
|
||||
|
||||
get dbg() {
|
||||
if (!this._dbg) {
|
||||
this._dbg = this.parent.makeDebugger();
|
||||
}
|
||||
return this._dbg;
|
||||
},
|
||||
|
||||
initialize: function(conn, parent, frameCache = new StackFrameCache()) {
|
||||
protocol.Actor.prototype.initialize.call(this, conn);
|
||||
this.parent = parent;
|
||||
this._mgr = Cc["@mozilla.org/memory-reporter-manager;1"]
|
||||
.getService(Ci.nsIMemoryReporterManager);
|
||||
this.state = "detached";
|
||||
this._dbg = null;
|
||||
this._frameCache = frameCache;
|
||||
|
||||
this._onGarbageCollection = data =>
|
||||
events.emit(this, "garbage-collection", data);
|
||||
|
||||
this._onWindowReady = this._onWindowReady.bind(this);
|
||||
|
||||
events.on(this.parent, "window-ready", this._onWindowReady);
|
||||
this._onGarbageCollection = this._onGarbageCollection.bind(this);
|
||||
this.bridge = new MemoryBridge(parent, frameCache);
|
||||
this.bridge.on("garbage-collection", this._onGarbageCollection);
|
||||
},
|
||||
|
||||
destroy: function() {
|
||||
events.off(this.parent, "window-ready", this._onWindowReady);
|
||||
|
||||
this._mgr = null;
|
||||
if (this.state === "attached") {
|
||||
this.detach();
|
||||
}
|
||||
this.bridge.off("garbage-collection", this._onGarbageCollection);
|
||||
this.bridge.destroy();
|
||||
protocol.Actor.prototype.destroy.call(this);
|
||||
},
|
||||
|
||||
|
@ -117,12 +79,7 @@ let MemoryActor = protocol.ActorClass({
|
|||
* recording allocations or take a census of the heap. In addition, the
|
||||
* MemoryActor will start emitting GC events.
|
||||
*/
|
||||
attach: method(expectState("detached", function() {
|
||||
this.dbg.addDebuggees();
|
||||
this.dbg.memory.onGarbageCollection = this._onGarbageCollection;
|
||||
this.state = "attached";
|
||||
},
|
||||
`attaching to the debugger`), {
|
||||
attach: linkBridge("attach", {
|
||||
request: {},
|
||||
response: {
|
||||
type: "attached"
|
||||
|
@ -132,13 +89,7 @@ let MemoryActor = protocol.ActorClass({
|
|||
/**
|
||||
* Detach from this MemoryActor.
|
||||
*/
|
||||
detach: method(expectState("attached", function() {
|
||||
this._clearDebuggees();
|
||||
this.dbg.enabled = false;
|
||||
this._dbg = null;
|
||||
this.state = "detached";
|
||||
},
|
||||
`detaching from the debugger`), {
|
||||
detach: linkBridge("detach", {
|
||||
request: {},
|
||||
response: {
|
||||
type: "detached"
|
||||
|
@ -148,51 +99,17 @@ let MemoryActor = protocol.ActorClass({
|
|||
/**
|
||||
* Gets the current MemoryActor attach/detach state.
|
||||
*/
|
||||
getState: method(function() {
|
||||
return this.state;
|
||||
}, {
|
||||
getState: linkBridge("getState", {
|
||||
response: {
|
||||
state: RetVal(0, "string")
|
||||
}
|
||||
}),
|
||||
|
||||
_clearDebuggees: function() {
|
||||
if (this._dbg) {
|
||||
if (this.dbg.memory.trackingAllocationSites) {
|
||||
this.dbg.memory.drainAllocationsLog();
|
||||
}
|
||||
this._clearFrames();
|
||||
this.dbg.removeAllDebuggees();
|
||||
}
|
||||
},
|
||||
|
||||
_clearFrames: function() {
|
||||
if (this.dbg.memory.trackingAllocationSites) {
|
||||
this._frameCache.clearFrames();
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Handler for the parent actor's "window-ready" event.
|
||||
*/
|
||||
_onWindowReady: function({ isTopLevel }) {
|
||||
if (this.state == "attached") {
|
||||
if (isTopLevel && this.dbg.memory.trackingAllocationSites) {
|
||||
this._clearDebuggees();
|
||||
this._frameCache.initFrames();
|
||||
}
|
||||
this.dbg.addDebuggees();
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Take a census of the heap. See js/src/doc/Debugger/Debugger.Memory.md for
|
||||
* more information.
|
||||
*/
|
||||
takeCensus: method(expectState("attached", function() {
|
||||
return this.dbg.memory.takeCensus();
|
||||
},
|
||||
`taking census`), {
|
||||
takeCensus: linkBridge("takeCensus", {
|
||||
request: {},
|
||||
response: RetVal("json")
|
||||
}),
|
||||
|
@ -203,24 +120,7 @@ let MemoryActor = protocol.ActorClass({
|
|||
* @param AllocationsRecordingOptions options
|
||||
* See the protocol.js definition of AllocationsRecordingOptions above.
|
||||
*/
|
||||
startRecordingAllocations: method(expectState("attached", function(options = {}) {
|
||||
if (this.dbg.memory.trackingAllocationSites) {
|
||||
return Date.now();
|
||||
}
|
||||
|
||||
this._frameCache.initFrames();
|
||||
|
||||
this.dbg.memory.allocationSamplingProbability = options.probability != null
|
||||
? options.probability
|
||||
: 1.0;
|
||||
if (options.maxLogLength != null) {
|
||||
this.dbg.memory.maxAllocationsLogLength = options.maxLogLength;
|
||||
}
|
||||
this.dbg.memory.trackingAllocationSites = true;
|
||||
|
||||
return Date.now();
|
||||
},
|
||||
`starting recording allocations`), {
|
||||
startRecordingAllocations: linkBridge("startRecordingAllocations", {
|
||||
request: {
|
||||
options: Arg(0, "nullable:AllocationsRecordingOptions")
|
||||
},
|
||||
|
@ -233,13 +133,7 @@ let MemoryActor = protocol.ActorClass({
|
|||
/**
|
||||
* Stop recording allocation sites.
|
||||
*/
|
||||
stopRecordingAllocations: method(expectState("attached", function() {
|
||||
this.dbg.memory.trackingAllocationSites = false;
|
||||
this._clearFrames();
|
||||
|
||||
return Date.now();
|
||||
},
|
||||
`stopping recording allocations`), {
|
||||
stopRecordingAllocations: linkBridge("stopRecordingAllocations", {
|
||||
request: {},
|
||||
response: {
|
||||
// Accept `nullable` in the case of server Gecko <= 37, handled on the front
|
||||
|
@ -251,115 +145,14 @@ let MemoryActor = protocol.ActorClass({
|
|||
* Return settings used in `startRecordingAllocations` for `probability`
|
||||
* and `maxLogLength`. Currently only uses in tests.
|
||||
*/
|
||||
getAllocationsSettings: method(expectState("attached", function() {
|
||||
return {
|
||||
maxLogLength: this.dbg.memory.maxAllocationsLogLength,
|
||||
probability: this.dbg.memory.allocationSamplingProbability
|
||||
};
|
||||
},
|
||||
`getting allocations settings`), {
|
||||
getAllocationsSettings: linkBridge("getAllocationsSettings", {
|
||||
request: {},
|
||||
response: {
|
||||
options: RetVal(0, "json")
|
||||
}
|
||||
}),
|
||||
|
||||
/**
|
||||
* Get a list of the most recent allocations since the last time we got
|
||||
* allocations, as well as a summary of all allocations since we've been
|
||||
* recording.
|
||||
*
|
||||
* @returns Object
|
||||
* An object of the form:
|
||||
*
|
||||
* {
|
||||
* allocations: [<index into "frames" below>, ...],
|
||||
* allocationsTimestamps: [
|
||||
* <timestamp for allocations[0]>,
|
||||
* <timestamp for allocations[1]>,
|
||||
* ...
|
||||
* ],
|
||||
* frames: [
|
||||
* {
|
||||
* line: <line number for this frame>,
|
||||
* column: <column number for this frame>,
|
||||
* source: <filename string for this frame>,
|
||||
* functionDisplayName: <this frame's inferred function name function or null>,
|
||||
* parent: <index into "frames">
|
||||
* },
|
||||
* ...
|
||||
* ],
|
||||
* counts: [
|
||||
* <number of allocations in frames[0]>,
|
||||
* <number of allocations in frames[1]>,
|
||||
* <number of allocations in frames[2]>,
|
||||
* ...
|
||||
* ]
|
||||
* }
|
||||
*
|
||||
* The timestamps' unit is microseconds since the epoch.
|
||||
*
|
||||
* Subsequent `getAllocations` request within the same recording and
|
||||
* tab navigation will always place the same stack frames at the same
|
||||
* indices as previous `getAllocations` requests in the same
|
||||
* recording. In other words, it is safe to use the index as a
|
||||
* unique, persistent id for its frame.
|
||||
*
|
||||
* Additionally, the root node (null) is always at index 0.
|
||||
*
|
||||
* Note that the allocation counts include "self" allocations only,
|
||||
* and don't account for allocations in child frames.
|
||||
*
|
||||
* We use the indices into the "frames" array to avoid repeating the
|
||||
* description of duplicate stack frames both when listing
|
||||
* allocations, and when many stacks share the same tail of older
|
||||
* frames. There shouldn't be any duplicates in the "frames" array,
|
||||
* as that would defeat the purpose of this compression trick.
|
||||
*
|
||||
* In the future, we might want to split out a frame's "source" and
|
||||
* "functionDisplayName" properties out the same way we have split
|
||||
* frames out with the "frames" array. While this would further
|
||||
* compress the size of the response packet, it would increase CPU
|
||||
* usage to build the packet, and it should, of course, be guided by
|
||||
* profiling and done only when necessary.
|
||||
*/
|
||||
getAllocations: method(expectState("attached", function() {
|
||||
if (this.dbg.memory.allocationsLogOverflowed) {
|
||||
// Since the last time we drained the allocations log, there have been
|
||||
// more allocations than the log's capacity, and we lost some data. There
|
||||
// isn't anything actionable we can do about this, but put a message in
|
||||
// the browser console so we at least know that it occurred.
|
||||
reportException("MemoryActor.prototype.getAllocations",
|
||||
"Warning: allocations log overflowed and lost some data.");
|
||||
}
|
||||
|
||||
const allocations = this.dbg.memory.drainAllocationsLog()
|
||||
const packet = {
|
||||
allocations: [],
|
||||
allocationsTimestamps: []
|
||||
};
|
||||
|
||||
for (let { frame: stack, timestamp } of allocations) {
|
||||
if (stack && Cu.isDeadWrapper(stack)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Safe because SavedFrames are frozen/immutable.
|
||||
let waived = Cu.waiveXrays(stack);
|
||||
|
||||
// Ensure that we have a form, count, and index for new allocations
|
||||
// because we potentially haven't seen some or all of them yet. After this
|
||||
// loop, we can rely on the fact that every frame we deal with already has
|
||||
// its metadata stored.
|
||||
let index = this._frameCache.addFrame(waived);
|
||||
|
||||
packet.allocations.push(index);
|
||||
packet.allocationsTimestamps.push(timestamp);
|
||||
}
|
||||
|
||||
return this._frameCache.updateFramePacket(packet);
|
||||
},
|
||||
`getting allocations`), {
|
||||
getAllocations: linkBridge("getAllocations", {
|
||||
request: {},
|
||||
response: RetVal("json")
|
||||
}),
|
||||
|
@ -367,11 +160,7 @@ let MemoryActor = protocol.ActorClass({
|
|||
/*
|
||||
* Force a browser-wide GC.
|
||||
*/
|
||||
forceGarbageCollection: method(function() {
|
||||
for (let i = 0; i < 3; i++) {
|
||||
Cu.forceGC();
|
||||
}
|
||||
}, {
|
||||
forceGarbageCollection: linkBridge("forceGarbageCollection", {
|
||||
request: {},
|
||||
response: {}
|
||||
}),
|
||||
|
@ -381,9 +170,7 @@ let MemoryActor = protocol.ActorClass({
|
|||
* collection, see
|
||||
* https://developer.mozilla.org/en-US/docs/Interfacing_with_the_XPCOM_cycle_collector#What_the_cycle_collector_does
|
||||
*/
|
||||
forceCycleCollection: method(function() {
|
||||
Cu.forceCC();
|
||||
}, {
|
||||
forceCycleCollection: linkBridge("forceCycleCollection", {
|
||||
request: {},
|
||||
response: {}
|
||||
}),
|
||||
|
@ -394,47 +181,23 @@ let MemoryActor = protocol.ActorClass({
|
|||
*
|
||||
* @returns object
|
||||
*/
|
||||
measure: method(function() {
|
||||
let result = {};
|
||||
|
||||
let jsObjectsSize = {};
|
||||
let jsStringsSize = {};
|
||||
let jsOtherSize = {};
|
||||
let domSize = {};
|
||||
let styleSize = {};
|
||||
let otherSize = {};
|
||||
let totalSize = {};
|
||||
let jsMilliseconds = {};
|
||||
let nonJSMilliseconds = {};
|
||||
|
||||
try {
|
||||
this._mgr.sizeOfTab(this.parent.window, jsObjectsSize, jsStringsSize, jsOtherSize,
|
||||
domSize, styleSize, otherSize, totalSize, jsMilliseconds, nonJSMilliseconds);
|
||||
result.total = totalSize.value;
|
||||
result.domSize = domSize.value;
|
||||
result.styleSize = styleSize.value;
|
||||
result.jsObjectsSize = jsObjectsSize.value;
|
||||
result.jsStringsSize = jsStringsSize.value;
|
||||
result.jsOtherSize = jsOtherSize.value;
|
||||
result.otherSize = otherSize.value;
|
||||
result.jsMilliseconds = jsMilliseconds.value.toFixed(1);
|
||||
result.nonJSMilliseconds = nonJSMilliseconds.value.toFixed(1);
|
||||
} catch (e) {
|
||||
reportException("MemoryActor.prototype.measure", e);
|
||||
}
|
||||
|
||||
return result;
|
||||
}, {
|
||||
measure: linkBridge("measure", {
|
||||
request: {},
|
||||
response: RetVal("json"),
|
||||
}),
|
||||
|
||||
residentUnique: method(function() {
|
||||
return this._mgr.residentUnique;
|
||||
}, {
|
||||
residentUnique: linkBridge("residentUnique", {
|
||||
request: {},
|
||||
response: { value: RetVal("number") }
|
||||
})
|
||||
}),
|
||||
|
||||
/**
|
||||
* Called when the underlying MemoryBridge fires a "garbage-collection" events.
|
||||
* Propagates over RDP.
|
||||
*/
|
||||
_onGarbageCollection: function (data) {
|
||||
events.emit(this, "garbage-collection", data);
|
||||
},
|
||||
});
|
||||
|
||||
exports.MemoryActor = MemoryActor;
|
||||
|
|
|
@ -24,8 +24,9 @@ const protocol = require("devtools/server/protocol");
|
|||
const {method, Arg, RetVal, Option} = protocol;
|
||||
const events = require("sdk/event/core");
|
||||
const {setTimeout, clearTimeout} = require("sdk/timers");
|
||||
const {Task} = Cu.import("resource://gre/modules/Task.jsm", {});
|
||||
|
||||
const {MemoryActor} = require("devtools/server/actors/memory");
|
||||
const {MemoryBridge} = require("devtools/server/actors/utils/memory-bridge");
|
||||
const {FramerateActor} = require("devtools/server/actors/framerate");
|
||||
const {StackFrameCache} = require("devtools/server/actors/utils/stack");
|
||||
|
||||
|
@ -109,9 +110,11 @@ let TimelineActor = exports.TimelineActor = protocol.ActorClass({
|
|||
|
||||
this._isRecording = false;
|
||||
this._stackFrames = null;
|
||||
this._memoryBridge = null;
|
||||
|
||||
// Make sure to get markers from new windows as they become available
|
||||
this._onWindowReady = this._onWindowReady.bind(this);
|
||||
this._onGarbageCollection = this._onGarbageCollection.bind(this);
|
||||
events.on(this.tabActor, "window-ready", this._onWindowReady);
|
||||
},
|
||||
|
||||
|
@ -132,6 +135,7 @@ let TimelineActor = exports.TimelineActor = protocol.ActorClass({
|
|||
|
||||
events.off(this.tabActor, "window-ready", this._onWindowReady);
|
||||
this.tabActor = null;
|
||||
this._memoryBridge = null;
|
||||
|
||||
protocol.Actor.prototype.destroy.call(this);
|
||||
},
|
||||
|
@ -173,10 +177,7 @@ let TimelineActor = exports.TimelineActor = protocol.ActorClass({
|
|||
* markers, memory, tick and frames events, if any.
|
||||
*/
|
||||
_pullTimelineData: function() {
|
||||
if (!this._isRecording) {
|
||||
return;
|
||||
}
|
||||
if (!this.docShells.length) {
|
||||
if (!this._isRecording || !this.docShells.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -209,10 +210,10 @@ let TimelineActor = exports.TimelineActor = protocol.ActorClass({
|
|||
if (markers.length > 0) {
|
||||
events.emit(this, "markers", markers, endTime);
|
||||
}
|
||||
if (this._memoryActor) {
|
||||
events.emit(this, "memory", endTime, this._memoryActor.measure());
|
||||
if (this._withMemory) {
|
||||
events.emit(this, "memory", endTime, this._memoryBridge.measure());
|
||||
}
|
||||
if (this._framerateActor) {
|
||||
if (this._withTicks) {
|
||||
events.emit(this, "ticks", endTime, this._framerateActor.getPendingTicks());
|
||||
}
|
||||
|
||||
|
@ -235,9 +236,20 @@ let TimelineActor = exports.TimelineActor = protocol.ActorClass({
|
|||
|
||||
/**
|
||||
* Start recording profile markers.
|
||||
*
|
||||
* @option {boolean} withMemory
|
||||
* Boolean indiciating whether we want memory measurements sampled. A memory actor
|
||||
* will be created regardless (to hook into GC events), but this determines
|
||||
* whether or not a `memory` event gets fired.
|
||||
* @option {boolean} withTicks
|
||||
* Boolean indicating whether a `ticks` event is fired and a FramerateActor
|
||||
* is created.
|
||||
*/
|
||||
start: method(function({ withMemory, withTicks }) {
|
||||
var startTime = this.docShells[0].now();
|
||||
start: method(Task.async(function *({ withMemory, withTicks }) {
|
||||
var startTime = this._startTime = this.docShells[0].now();
|
||||
// Store the start time from unix epoch so we can normalize
|
||||
// markers from the memory actor
|
||||
this._unixStartTime = Date.now();
|
||||
|
||||
if (this._isRecording) {
|
||||
return startTime;
|
||||
|
@ -246,14 +258,16 @@ let TimelineActor = exports.TimelineActor = protocol.ActorClass({
|
|||
this._isRecording = true;
|
||||
this._stackFrames = new StackFrameCache();
|
||||
this._stackFrames.initFrames();
|
||||
this._withMemory = withMemory;
|
||||
this._withTicks = withTicks;
|
||||
|
||||
for (let docShell of this.docShells) {
|
||||
docShell.recordProfileTimelineMarkers = true;
|
||||
}
|
||||
|
||||
if (withMemory) {
|
||||
this._memoryActor = new MemoryActor(this.conn, this.tabActor, this._stackFrames);
|
||||
}
|
||||
this._memoryBridge = new MemoryBridge(this.tabActor, this._stackFrames);
|
||||
this._memoryBridge.attach();
|
||||
events.on(this._memoryBridge, "garbage-collection", this._onGarbageCollection);
|
||||
|
||||
if (withTicks) {
|
||||
this._framerateActor = new FramerateActor(this.conn, this.tabActor);
|
||||
|
@ -262,7 +276,7 @@ let TimelineActor = exports.TimelineActor = protocol.ActorClass({
|
|||
|
||||
this._pullTimelineData();
|
||||
return startTime;
|
||||
}, {
|
||||
}), {
|
||||
request: {
|
||||
withMemory: Option(0, "boolean"),
|
||||
withTicks: Option(0, "boolean")
|
||||
|
@ -275,16 +289,15 @@ let TimelineActor = exports.TimelineActor = protocol.ActorClass({
|
|||
/**
|
||||
* Stop recording profile markers.
|
||||
*/
|
||||
stop: method(function() {
|
||||
stop: method(Task.async(function *() {
|
||||
if (!this._isRecording) {
|
||||
return;
|
||||
}
|
||||
this._isRecording = false;
|
||||
this._stackFrames = null;
|
||||
|
||||
if (this._memoryActor) {
|
||||
this._memoryActor = null;
|
||||
}
|
||||
events.off(this._memoryBridge, "garbage-collection", this._onGarbageCollection);
|
||||
this._memoryBridge.detach();
|
||||
|
||||
if (this._framerateActor) {
|
||||
this._framerateActor.stopRecording();
|
||||
|
@ -297,7 +310,7 @@ let TimelineActor = exports.TimelineActor = protocol.ActorClass({
|
|||
|
||||
clearTimeout(this._dataPullTimeout);
|
||||
return this.docShells[0].now();
|
||||
}, {
|
||||
}), {
|
||||
response: {
|
||||
// Set as possibly nullable due to the end time possibly being
|
||||
// undefined during destruction
|
||||
|
@ -316,7 +329,38 @@ let TimelineActor = exports.TimelineActor = protocol.ActorClass({
|
|||
.QueryInterface(Ci.nsIDocShell);
|
||||
docShell.recordProfileTimelineMarkers = true;
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Fired when the MemoryActor emits a `garbage-collection` event. Used to
|
||||
* emit the data to the front end and in similar format to other markers.
|
||||
*
|
||||
* A GC "marker" here represents a full GC cycle, which may contain several incremental
|
||||
* events within its `collection` array. The marker contains a `reason` field, indicating
|
||||
* why there was a GC, and may contain a `nonincrementalReason` when SpiderMonkey could
|
||||
* not incrementally collect garbage.
|
||||
*/
|
||||
_onGarbageCollection: function ({ collections, reason, nonincrementalReason }) {
|
||||
if (!this._isRecording || !this.docShells.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Normalize the start time to docshell start time, and convert it
|
||||
// to microseconds.
|
||||
let startTime = (this._unixStartTime - this._startTime) * 1000;
|
||||
let endTime = this.docShells[0].now();
|
||||
|
||||
events.emit(this, "markers", collections.map(({ startTimestamp: start, endTimestamp: end }) => {
|
||||
return {
|
||||
name: "GarbageCollection",
|
||||
causeName: reason,
|
||||
nonincrementalReason: nonincrementalReason,
|
||||
// Both timestamps are in microseconds -- convert to milliseconds to match other markers
|
||||
start: (start - startTime) / 1000,
|
||||
end: (end - startTime) / 1000
|
||||
};
|
||||
}), endTime);
|
||||
},
|
||||
});
|
||||
|
||||
exports.TimelineFront = protocol.FrontClass(TimelineActor, {
|
||||
|
|
|
@ -0,0 +1,367 @@
|
|||
/* 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 { Cc, Ci, Cu } = require("chrome");
|
||||
const { reportException } = require("devtools/toolkit/DevToolsUtils");
|
||||
const { Class } = require("sdk/core/heritage");
|
||||
loader.lazyRequireGetter(this, "events", "sdk/event/core");
|
||||
loader.lazyRequireGetter(this, "EventTarget", "sdk/event/target", true);
|
||||
loader.lazyRequireGetter(this, "StackFrameCache",
|
||||
"devtools/server/actors/utils/stack", true);
|
||||
|
||||
/**
|
||||
* A method decorator that ensures the actor is in the expected state before
|
||||
* proceeding. If the actor is not in the expected state, the decorated method
|
||||
* returns a rejected promise.
|
||||
*
|
||||
* @param String expectedState
|
||||
* The expected state.
|
||||
* @param String activity
|
||||
* Additional info about what's going on.
|
||||
* @param Function method
|
||||
* The actor method to proceed with when the actor is in the expected
|
||||
* state.
|
||||
*
|
||||
* @returns Function
|
||||
* The decorated method.
|
||||
*/
|
||||
function expectState(expectedState, method, activity) {
|
||||
return function(...args) {
|
||||
if (this.state !== expectedState) {
|
||||
const msg = `Wrong state while ${activity}:` +
|
||||
`Expected '${expectedState}',` +
|
||||
`but current state is '${this.state}'.`;
|
||||
return Promise.reject(new Error(msg));
|
||||
}
|
||||
|
||||
return method.apply(this, args);
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* A class that returns memory data for a parent actor's window.
|
||||
* Using a tab-scoped actor with this instance will measure the memory footprint of its
|
||||
* parent tab. Using a global-scoped actor instance however, will measure the memory
|
||||
* footprint of the chrome window referenced by its root actor.
|
||||
*
|
||||
* To be consumed by actor's, like MemoryActor using MemoryBridge to
|
||||
* send information over RDP, and TimelineActor for using more light-weight
|
||||
* utilities like GC events and measuring memory consumption.
|
||||
*/
|
||||
let MemoryBridge = Class({
|
||||
extends: EventTarget,
|
||||
|
||||
/**
|
||||
* Requires a root actor and a StackFrameCache.
|
||||
*/
|
||||
initialize: function (parent, frameCache = new StackFrameCache()) {
|
||||
this.parent = parent;
|
||||
this._mgr = Cc["@mozilla.org/memory-reporter-manager;1"]
|
||||
.getService(Ci.nsIMemoryReporterManager);
|
||||
this.state = "detached";
|
||||
this._dbg = null;
|
||||
this._frameCache = frameCache;
|
||||
|
||||
this._onGarbageCollection = this._onGarbageCollection.bind(this);
|
||||
this._onWindowReady = this._onWindowReady.bind(this);
|
||||
|
||||
events.on(this.parent, "window-ready", this._onWindowReady);
|
||||
},
|
||||
|
||||
destroy: function() {
|
||||
events.off(this.parent, "window-ready", this._onWindowReady);
|
||||
|
||||
this._mgr = null;
|
||||
if (this.state === "attached") {
|
||||
this.detach();
|
||||
}
|
||||
},
|
||||
|
||||
get dbg() {
|
||||
if (!this._dbg) {
|
||||
this._dbg = this.parent.makeDebugger();
|
||||
}
|
||||
return this._dbg;
|
||||
},
|
||||
|
||||
|
||||
/**
|
||||
* Attach to this MemoryBridge.
|
||||
*
|
||||
* This attaches the MemoryBridge's Debugger instance so that you can start
|
||||
* recording allocations or take a census of the heap. In addition, the
|
||||
* MemoryBridge will start emitting GC events.
|
||||
*/
|
||||
attach: expectState("detached", function() {
|
||||
this.dbg.addDebuggees();
|
||||
this.dbg.memory.onGarbageCollection = this._onGarbageCollection.bind(this);
|
||||
this.state = "attached";
|
||||
}, `attaching to the debugger`),
|
||||
|
||||
/**
|
||||
* Detach from this MemoryBridge.
|
||||
*/
|
||||
detach: expectState("attached", function() {
|
||||
this._clearDebuggees();
|
||||
this.dbg.enabled = false;
|
||||
this._dbg = null;
|
||||
this.state = "detached";
|
||||
}, `detaching from the debugger`),
|
||||
|
||||
/**
|
||||
* Gets the current MemoryBridge attach/detach state.
|
||||
*/
|
||||
getState: function () {
|
||||
return this.state;
|
||||
},
|
||||
|
||||
_clearDebuggees: function() {
|
||||
if (this._dbg) {
|
||||
if (this.dbg.memory.trackingAllocationSites) {
|
||||
this.dbg.memory.drainAllocationsLog();
|
||||
}
|
||||
this._clearFrames();
|
||||
this.dbg.removeAllDebuggees();
|
||||
}
|
||||
},
|
||||
|
||||
_clearFrames: function() {
|
||||
if (this.dbg.memory.trackingAllocationSites) {
|
||||
this._frameCache.clearFrames();
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Handler for the parent actor's "window-ready" event.
|
||||
*/
|
||||
_onWindowReady: function({ isTopLevel }) {
|
||||
if (this.state == "attached") {
|
||||
if (isTopLevel && this.dbg.memory.trackingAllocationSites) {
|
||||
this._clearDebuggees();
|
||||
this._frameCache.initFrames();
|
||||
}
|
||||
this.dbg.addDebuggees();
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Handler for GC events on the Debugger.Memory instance.
|
||||
*/
|
||||
_onGarbageCollection: function (data) {
|
||||
events.emit(this, "garbage-collection", data);
|
||||
},
|
||||
|
||||
/**
|
||||
* Take a census of the heap. See js/src/doc/Debugger/Debugger.Memory.md for
|
||||
* more information.
|
||||
*/
|
||||
takeCensus: expectState("attached", function() {
|
||||
return this.dbg.memory.takeCensus();
|
||||
}, `taking census`),
|
||||
|
||||
/**
|
||||
* Start recording allocation sites.
|
||||
*
|
||||
* @param AllocationsRecordingOptions options
|
||||
* See the protocol.js definition of AllocationsRecordingOptions above.
|
||||
*/
|
||||
startRecordingAllocations: expectState("attached", function(options = {}) {
|
||||
if (this.dbg.memory.trackingAllocationSites) {
|
||||
return Date.now();
|
||||
}
|
||||
|
||||
this._frameCache.initFrames();
|
||||
|
||||
this.dbg.memory.allocationSamplingProbability = options.probability != null
|
||||
? options.probability
|
||||
: 1.0;
|
||||
if (options.maxLogLength != null) {
|
||||
this.dbg.memory.maxAllocationsLogLength = options.maxLogLength;
|
||||
}
|
||||
this.dbg.memory.trackingAllocationSites = true;
|
||||
|
||||
return Date.now();
|
||||
}, `starting recording allocations`),
|
||||
|
||||
/**
|
||||
* Stop recording allocation sites.
|
||||
*/
|
||||
stopRecordingAllocations: expectState("attached", function() {
|
||||
this.dbg.memory.trackingAllocationSites = false;
|
||||
this._clearFrames();
|
||||
|
||||
return Date.now();
|
||||
}, `stopping recording allocations`),
|
||||
|
||||
/**
|
||||
* Return settings used in `startRecordingAllocations` for `probability`
|
||||
* and `maxLogLength`. Currently only uses in tests.
|
||||
*/
|
||||
getAllocationsSettings: expectState("attached", function() {
|
||||
return {
|
||||
maxLogLength: this.dbg.memory.maxAllocationsLogLength,
|
||||
probability: this.dbg.memory.allocationSamplingProbability
|
||||
};
|
||||
}, `getting allocations settings`),
|
||||
|
||||
/**
|
||||
* Get a list of the most recent allocations since the last time we got
|
||||
* allocations, as well as a summary of all allocations since we've been
|
||||
* recording.
|
||||
*
|
||||
* @returns Object
|
||||
* An object of the form:
|
||||
*
|
||||
* {
|
||||
* allocations: [<index into "frames" below>, ...],
|
||||
* allocationsTimestamps: [
|
||||
* <timestamp for allocations[0]>,
|
||||
* <timestamp for allocations[1]>,
|
||||
* ...
|
||||
* ],
|
||||
* frames: [
|
||||
* {
|
||||
* line: <line number for this frame>,
|
||||
* column: <column number for this frame>,
|
||||
* source: <filename string for this frame>,
|
||||
* functionDisplayName: <this frame's inferred function name function or null>,
|
||||
* parent: <index into "frames">
|
||||
* },
|
||||
* ...
|
||||
* ],
|
||||
* counts: [
|
||||
* <number of allocations in frames[0]>,
|
||||
* <number of allocations in frames[1]>,
|
||||
* <number of allocations in frames[2]>,
|
||||
* ...
|
||||
* ]
|
||||
* }
|
||||
*
|
||||
* The timestamps' unit is microseconds since the epoch.
|
||||
*
|
||||
* Subsequent `getAllocations` request within the same recording and
|
||||
* tab navigation will always place the same stack frames at the same
|
||||
* indices as previous `getAllocations` requests in the same
|
||||
* recording. In other words, it is safe to use the index as a
|
||||
* unique, persistent id for its frame.
|
||||
*
|
||||
* Additionally, the root node (null) is always at index 0.
|
||||
*
|
||||
* Note that the allocation counts include "self" allocations only,
|
||||
* and don't account for allocations in child frames.
|
||||
*
|
||||
* We use the indices into the "frames" array to avoid repeating the
|
||||
* description of duplicate stack frames both when listing
|
||||
* allocations, and when many stacks share the same tail of older
|
||||
* frames. There shouldn't be any duplicates in the "frames" array,
|
||||
* as that would defeat the purpose of this compression trick.
|
||||
*
|
||||
* In the future, we might want to split out a frame's "source" and
|
||||
* "functionDisplayName" properties out the same way we have split
|
||||
* frames out with the "frames" array. While this would further
|
||||
* compress the size of the response packet, it would increase CPU
|
||||
* usage to build the packet, and it should, of course, be guided by
|
||||
* profiling and done only when necessary.
|
||||
*/
|
||||
getAllocations: expectState("attached", function() {
|
||||
if (this.dbg.memory.allocationsLogOverflowed) {
|
||||
// Since the last time we drained the allocations log, there have been
|
||||
// more allocations than the log's capacity, and we lost some data. There
|
||||
// isn't anything actionable we can do about this, but put a message in
|
||||
// the browser console so we at least know that it occurred.
|
||||
reportException("MemoryBridge.prototype.getAllocations",
|
||||
"Warning: allocations log overflowed and lost some data.");
|
||||
}
|
||||
|
||||
const allocations = this.dbg.memory.drainAllocationsLog()
|
||||
const packet = {
|
||||
allocations: [],
|
||||
allocationsTimestamps: []
|
||||
};
|
||||
|
||||
for (let { frame: stack, timestamp } of allocations) {
|
||||
if (stack && Cu.isDeadWrapper(stack)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Safe because SavedFrames are frozen/immutable.
|
||||
let waived = Cu.waiveXrays(stack);
|
||||
|
||||
// Ensure that we have a form, count, and index for new allocations
|
||||
// because we potentially haven't seen some or all of them yet. After this
|
||||
// loop, we can rely on the fact that every frame we deal with already has
|
||||
// its metadata stored.
|
||||
let index = this._frameCache.addFrame(waived);
|
||||
|
||||
packet.allocations.push(index);
|
||||
packet.allocationsTimestamps.push(timestamp);
|
||||
}
|
||||
|
||||
return this._frameCache.updateFramePacket(packet);
|
||||
}, `getting allocations`),
|
||||
|
||||
/*
|
||||
* Force a browser-wide GC.
|
||||
*/
|
||||
forceGarbageCollection: function () {
|
||||
for (let i = 0; i < 3; i++) {
|
||||
Cu.forceGC();
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Force an XPCOM cycle collection. For more information on XPCOM cycle
|
||||
* collection, see
|
||||
* https://developer.mozilla.org/en-US/docs/Interfacing_with_the_XPCOM_cycle_collector#What_the_cycle_collector_does
|
||||
*/
|
||||
forceCycleCollection: function () {
|
||||
Cu.forceCC();
|
||||
},
|
||||
|
||||
/**
|
||||
* A method that returns a detailed breakdown of the memory consumption of the
|
||||
* associated window.
|
||||
*
|
||||
* @returns object
|
||||
*/
|
||||
measure: function () {
|
||||
let result = {};
|
||||
|
||||
let jsObjectsSize = {};
|
||||
let jsStringsSize = {};
|
||||
let jsOtherSize = {};
|
||||
let domSize = {};
|
||||
let styleSize = {};
|
||||
let otherSize = {};
|
||||
let totalSize = {};
|
||||
let jsMilliseconds = {};
|
||||
let nonJSMilliseconds = {};
|
||||
|
||||
try {
|
||||
this._mgr.sizeOfTab(this.parent.window, jsObjectsSize, jsStringsSize, jsOtherSize,
|
||||
domSize, styleSize, otherSize, totalSize, jsMilliseconds, nonJSMilliseconds);
|
||||
result.total = totalSize.value;
|
||||
result.domSize = domSize.value;
|
||||
result.styleSize = styleSize.value;
|
||||
result.jsObjectsSize = jsObjectsSize.value;
|
||||
result.jsStringsSize = jsStringsSize.value;
|
||||
result.jsOtherSize = jsOtherSize.value;
|
||||
result.otherSize = otherSize.value;
|
||||
result.jsMilliseconds = jsMilliseconds.value.toFixed(1);
|
||||
result.nonJSMilliseconds = nonJSMilliseconds.value.toFixed(1);
|
||||
} catch (e) {
|
||||
reportException("MemoryBridge.prototype.measure", e);
|
||||
}
|
||||
|
||||
return result;
|
||||
},
|
||||
|
||||
residentUnique: function () {
|
||||
return this._mgr.residentUnique;
|
||||
}
|
||||
});
|
||||
|
||||
exports.MemoryBridge = MemoryBridge;
|
|
@ -79,6 +79,7 @@ EXTRA_JS_MODULES.devtools.server.actors.utils += [
|
|||
'actors/utils/automation-timeline.js',
|
||||
'actors/utils/make-debugger.js',
|
||||
'actors/utils/map-uri-to-addon-id.js',
|
||||
'actors/utils/memory-bridge.js',
|
||||
'actors/utils/ScriptStore.js',
|
||||
'actors/utils/stack.js',
|
||||
'actors/utils/TabSources.js'
|
||||
|
|
Загрузка…
Ссылка в новой задаче